import SwiftUI // MARK: - Tabular Control (表格/数据列表) struct TabularControl: View { let schema: ControlSchema @ObservedObject var engine: BricksEngine @State private var rows: [[String: Any]] = [] @State private var selectedRow: Int? = nil @State private var currentPage: Int = 0 @State private var isLoading: Bool = false var body: some View { VStack(spacing: 0) { // 工具栏 if let tools = schema.options.toolbar?.tools, !tools.isEmpty { toolbarView(tools: tools) } // 表头 headerRow // 数据行 if isLoading { ProgressView("加载中...") .frame(maxWidth: .infinity, maxHeight: .infinity) } else if rows.isEmpty { Text("暂无数据") .foregroundColor(.secondary) .frame(maxWidth: .infinity, maxHeight: .infinity) } else { ScrollView { LazyVStack(spacing: 0) { ForEach(Array(pagedRows.enumerated()), id: \.offset) { idx, row in dataRow(row, index: idx + currentPage * pageRows) } } } } // 分页 if totalPages > 1 { paginationBar } } .background(Color.secondary.opacity(0.05)) .cornerRadius(8) .task { await loadData() } } // MARK: - 表头 private var headerRow: some View { HStack(spacing: 0) { ForEach(visibleFields, id: \.name) { field in Text(field.label ?? field.title ?? field.name) .font(.caption.bold()) .foregroundColor(.secondary) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 8) .padding(.vertical, 8) } } .background(Color.secondary.opacity(0.1)) } // MARK: - 数据行 private func dataRow(_ row: [String: Any], index: Int) -> some View { HStack(spacing: 0) { ForEach(visibleFields, id: \.name) { field in cellView(row: row, field: field) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 8) .padding(.vertical, 6) } } .background(selectedRow == index ? Color.accentColor.opacity(0.15) : Color.clear) .border(Color.secondary.opacity(0.1), width: 0.5) .onTapGesture { selectedRow = index engine.store.selectRow(tabularId: schema.effectiveId, index: index) Task { await engine.triggerEvent(widgetId: schema.effectiveId, event: "select", data: EventData(["row": row, "index": index])) } } } private func cellView(row: [String: Any], field: FieldSchema) -> some View { let value = row[field.name] as? String ?? "\(row[field.name] ?? "")" // 检查alters(代码字段映射) if let alter = schema.options.row_options?.browserfields?.alters?[field.name], let codes = alter.codes ?? alter.data, let match = codes.first(where: { $0.value == value }) { return Text(engine.i18n.t(match.text)) .font(.body) .lineLimit(1) } return Text(value) .font(.body) .lineLimit(1) } // MARK: - 工具栏 private func toolbarView(tools: [ToolbarTool]) -> some View { HStack(spacing: 8) { ForEach(tools, id: \.name) { tool in Button(engine.i18n.t(tool.label ?? tool.name)) { handleToolbarAction(tool) } .buttonStyle(.bordered) .disabled(tool.selected_row == true && selectedRow == nil) } Spacer() } .padding(8) .background(Color.secondary.opacity(0.05)) } private func handleToolbarAction(_ tool: ToolbarTool) { if tool.selected_row == true && selectedRow == nil { engine.errorMessage = "请先选择一行" return } Task { var data = EventData() if selectedRow != nil, let rowData = engine.store.getSelectedRowData(tabularId: schema.effectiveId) { for (k, v) in rowData { if let s = v as? String { data[k] = s } } } await engine.triggerEvent(widgetId: schema.effectiveId, event: tool.name, data: data) } } // MARK: - 分页 private var paginationBar: some View { HStack { Button("上一页") { currentPage -= 1 } .disabled(currentPage <= 0) Text("\(currentPage + 1) / \(totalPages)") .font(.caption) Button("下一页") { currentPage += 1 } .disabled(currentPage >= totalPages - 1) } .padding(8) } // MARK: - 数据加载 private func loadData() async { guard let url = schema.options.data_url else { return } isLoading = true let resolvedURL = engine.resolveTemplate(url, params: [:]) do { let response = try await engine.rpc.get(resolvedURL) if response.isSuccess, let dict = response.dictionary, let r = dict["rows"] as? [[String: Any]] { rows = r engine.store.setTableData(id: schema.effectiveId, rows: r) } } catch { engine.errorMessage = "加载失败: \(error.localizedDescription)" } isLoading = false } // MARK: - 计算属性 private var visibleFields: [FieldSchema] { guard let fields = schema.options.row_options?.fields else { return [] } let excluded = schema.options.row_options?.browserfields?.exclouded ?? [] return fields.filter { !excluded.contains($0.name) } } private var pageRows: Int { schema.options.page_rows ?? 20 } private var pagedRows: [[String: Any]] { let start = currentPage * pageRows let end = min(start + pageRows, rows.count) guard start < rows.count else { return [] } return Array(rows[start..