import SwiftUI // MARK: - Button Control struct ButtonControl: View { let schema: ControlSchema @ObservedObject var engine: BricksEngine @State private var isLoading: Bool = false var body: some View { Button(action: handleAction) { HStack(spacing: 6) { if isLoading { ProgressView() .scaleEffect(0.7) } if let icon = schema.options.icon { Text(icon) } Text(displayLabel) } .padding(.horizontal, 12) .padding(.vertical, 6) } .buttonStyle(buttonStyle) .disabled(isLoading) } private var displayLabel: String { let raw = schema.options.label ?? "" return (schema.options.i18n != false) ? engine.i18n.t(raw) : raw } private var buttonStyle: BricksButtonStyle { let css = schema.options.css ?? "" if css.contains("primary") { return BricksButtonStyle(variant: .primary) } else if css.contains("danger") { return BricksButtonStyle(variant: .danger) } else if css.contains("text") { return BricksButtonStyle(variant: .text) } return BricksButtonStyle(variant: .default) } private func handleAction() { // 如果有binds,触发click事件 Task { await engine.triggerEvent(widgetId: schema.effectiveId, event: "click") } } } // MARK: - Button Style struct BricksButtonStyle: ButtonStyle { enum Variant { case `default`, primary, danger, text } let variant: Variant func makeBody(configuration: Configuration) -> some View { configuration.label .foregroundColor(textColor) .background(backgroundColor(configuration.isPressed)) .cornerRadius(6) .opacity(configuration.isPressed ? 0.8 : 1.0) } private var textColor: Color { switch variant { case .primary: return .white case .danger: return .white case .text: return .accentColor case .default: return .primary } } private func backgroundColor(_ pressed: Bool) -> Color { switch variant { case .primary: return pressed ? .blue.opacity(0.7) : .blue case .danger: return pressed ? .red.opacity(0.7) : .red case .text: return .clear case .default: return pressed ? Color.secondary.opacity(0.2) : Color.secondary.opacity(0.1) } } } // MARK: - Select Control struct SelectControl: View { let schema: ControlSchema @ObservedObject var engine: BricksEngine @State private var selectedValue: String = "" @State private var items: [CodeItem] = [] var body: some View { VStack(alignment: .leading, spacing: 4) { if let label = schema.options.label ?? schema.options.title { Text(engine.i18n.t(label)) .font(.caption) .foregroundColor(.secondary) } Picker("", selection: $selectedValue) { ForEach(items, id: \.value) { item in Text(engine.i18n.t(item.text)).tag(item.value) } } .pickerStyle(.menu) } .onAppear { loadItems() selectedValue = schema.options.value ?? "" } .onChange(of: selectedValue) { _, newValue in engine.store.setValue(id: schema.effectiveId, value: newValue) Task { await engine.triggerEvent(widgetId: schema.effectiveId, event: "change", data: EventData(["value": newValue])) } } .task { await loadRemoteData() } } private func loadItems() { if let data = schema.options.data ?? schema.options.codes { items = data } else { items = [] } } private func loadRemoteData() async { guard let dataurl = schema.options.dataurl else { return } do { let response = try await engine.rpc.get(dataurl) if response.isSuccess, let dict = response.dictionary { if let rows = dict["rows"] as? [[String: Any]] { let vf = schema.options.valueField ?? "value" let tf = schema.options.textField ?? "text" items = rows.compactMap { row in guard let v = row[vf] as? String, let t = row[tf] as? String else { return nil } return CodeItem(value: v, text: t) } } } } catch { print("[SwiftBricks] Select data load failed: \(error)") } } }