diff --git a/Sources/SwiftBricks/Controls/TabViewControl.swift b/Sources/SwiftBricks/Controls/TabViewControl.swift index 1dae2fe..212edb1 100644 --- a/Sources/SwiftBricks/Controls/TabViewControl.swift +++ b/Sources/SwiftBricks/Controls/TabViewControl.swift @@ -80,43 +80,47 @@ struct MenuControl: View { } @ViewBuilder - private func menuItemView(_ item: MenuItemSchema, depth: Int) -> some View { + private func menuItemView(_ item: MenuItemSchema, depth: Int) -> AnyView { let hasChildren = !(item.submenu?.isEmpty ?? true) let isExpanded = expandedItems.contains(item.name) let isSelected = selectedItem == item.name - Button(action: { handleMenuClick(item, hasChildren: hasChildren) }) { - HStack(spacing: 8) { - if let icon = item.icon { - Text(icon) - .frame(width: 20) + return AnyView( + VStack(alignment: .leading, spacing: 0) { + Button(action: { handleMenuClick(item, hasChildren: hasChildren) }) { + HStack(spacing: 8) { + if let icon = item.icon { + Text(icon) + .frame(width: 20) + } + Text(engine.i18n.t(item.label)) + .font(.subheadline) + .lineLimit(1) + Spacer() + if hasChildren { + Image(systemName: isExpanded ? "chevron.down" : "chevron.right") + .font(.caption) + .foregroundColor(.secondary) + } + } + .padding(.horizontal, 12 + CGFloat(depth * 16)) + .padding(.vertical, 8) + .foregroundColor(isSelected ? .accentColor : .primary) + .background(isSelected ? Color.accentColor.opacity(0.1) : Color.clear) + .cornerRadius(6) } - Text(engine.i18n.t(item.label)) - .font(.subheadline) - .lineLimit(1) - Spacer() - if hasChildren { - Image(systemName: isExpanded ? "chevron.down" : "chevron.right") - .font(.caption) - .foregroundColor(.secondary) + .buttonStyle(.plain) + + // 子菜单 + if hasChildren && isExpanded { + if let subitems = item.submenu { + ForEach(subitems, id: \.name) { sub in + menuItemView(sub, depth: depth + 1) + } + } } } - .padding(.horizontal, 12 + CGFloat(depth * 16)) - .padding(.vertical, 8) - .foregroundColor(isSelected ? .accentColor : .primary) - .background(isSelected ? Color.accentColor.opacity(0.1) : Color.clear) - .cornerRadius(6) - } - .buttonStyle(.plain) - - // 子菜单 - if hasChildren && isExpanded { - if let subitems = item.submenu { - ForEach(subitems, id: \.name) { sub in - menuItemView(sub, depth: depth + 1) - } - } - } + ) } private func handleMenuClick(_ item: MenuItemSchema, hasChildren: Bool) { @@ -173,7 +177,11 @@ struct PopupWindowControl: View { .padding(12) } } + #if os(iOS) .background(Color(.systemBackground)) + #else + .background(Color(nsColor: .windowBackgroundColor)) + #endif .cornerRadius(12) .shadow(radius: 8) } diff --git a/Sources/SwiftBricks/Core/Engine.swift b/Sources/SwiftBricks/Core/Engine.swift index 107a5c5..16d5d46 100644 --- a/Sources/SwiftBricks/Core/Engine.swift +++ b/Sources/SwiftBricks/Core/Engine.swift @@ -140,7 +140,7 @@ public final class BricksEngine: ObservableObject { } /// 执行bind动作 - public func handleBind(_ bind: BindSchema, sourceId: String, eventData: EventData = [:]) async { + public func handleBind(_ bind: BindSchema, sourceId: String, eventData: EventData = EventData()) async { guard let actionType = ActionType(rawValue: bind.actiontype) else { print("[SwiftBricks] Invalid actiontype: \(bind.actiontype)") return @@ -306,7 +306,7 @@ public final class BricksEngine: ObservableObject { // MARK: - 触发事件 /// 触发widget事件 - public func triggerEvent(widgetId: String, event: String, data: EventData = [:]) async { + public func triggerEvent(widgetId: String, event: String, data: EventData = EventData()) async { await eventBus.dispatch("\(widgetId).\(event)", data: data) } diff --git a/Sources/SwiftBricks/Core/EventBus.swift b/Sources/SwiftBricks/Core/EventBus.swift index 402593e..3c01657 100644 --- a/Sources/SwiftBricks/Core/EventBus.swift +++ b/Sources/SwiftBricks/Core/EventBus.swift @@ -34,7 +34,7 @@ public final class BricksEventBus: ObservableObject { // MARK: - 发布 /// 派发事件 - public func dispatch(_ eventName: String, data: EventData = [:]) async { + public func dispatch(_ eventName: String, data: EventData = EventData()) async { // 记录日志 eventLog.append((name: eventName, data: data, timestamp: Date())) if eventLog.count > maxLogSize { @@ -50,7 +50,7 @@ public final class BricksEventBus: ObservableObject { } /// 同步派发(用于UI事件) - public func dispatchSync(_ eventName: String, data: EventData = [:]) { + public func dispatchSync(_ eventName: String, data: EventData = EventData()) { Task { @MainActor in await dispatch(eventName, data: data) } diff --git a/Sources/SwiftBricks/Core/RPC.swift b/Sources/SwiftBricks/Core/RPC.swift index bae6081..93d780f 100644 --- a/Sources/SwiftBricks/Core/RPC.swift +++ b/Sources/SwiftBricks/Core/RPC.swift @@ -131,7 +131,7 @@ public final class BricksRPC: ObservableObject { throw BricksRPCError.invalidResponse } - let responseHeaders = Dictionary( + let responseHeaders: [String: String] = Dictionary( uniqueKeysWithValues: httpResponse.allHeaderFields.compactMap { key, value in guard let keyStr = key as? String, let valStr = value as? String else { return nil } return (keyStr, valStr) diff --git a/Sources/SwiftBricks/Core/Schema.swift b/Sources/SwiftBricks/Core/Schema.swift index 8c36eaf..92620e4 100644 --- a/Sources/SwiftBricks/Core/Schema.swift +++ b/Sources/SwiftBricks/Core/Schema.swift @@ -30,6 +30,20 @@ public struct ControlSchema: Codable, Identifiable, Sendable { self.binds = binds self.subwidgets = subwidgets } + + /// 自定义解码:options缺失时默认为空,避免Filler等无options的widget解码失败 + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decodeIfPresent(String.self, forKey: .id) + self.widgettype = try container.decode(String.self, forKey: .widgettype) + self.options = try container.decodeIfPresent(ControlOptions.self, forKey: .options) ?? ControlOptions() + self.binds = try container.decodeIfPresent([BindSchema].self, forKey: .binds) + self.subwidgets = try container.decodeIfPresent([ControlSchema].self, forKey: .subwidgets) + } + + private enum CodingKeys: String, CodingKey { + case id, widgettype, options, binds, subwidgets + } } /// 控件选项 — 使用字典+Codable混合方式