import SwiftUI /// BricksView — SwiftBricks的主入口视图 /// 从JSON schema渲染完整的UI页面 /// /// 用法: /// ```swift /// BricksView(json: jsonString, engine: engine) /// BricksView(url: "/api/page.dspy", engine: engine) /// ``` public struct BricksView: View { @ObservedObject var engine: BricksEngine @State private var isLoaded: Bool = false @State private var loadError: String? private var jsonString: String? private var loadURL: String? /// 从JSON字符串创建 public init(json: String, engine: BricksEngine) { self.jsonString = json self.loadURL = nil self._engine = ObservedObject(wrappedValue: engine) } /// 从URL加载 public init(url: String, engine: BricksEngine) { self.jsonString = nil self.loadURL = url self._engine = ObservedObject(wrappedValue: engine) } /// 从已加载的schema创建 public init(schema: ControlSchema, engine: BricksEngine) { self.jsonString = nil self.loadURL = nil self._engine = ObservedObject(wrappedValue: engine) // 立即加载 engine.loadSchema(schema) } public var body: some View { ZStack { if let schema = engine.rootSchema { // 主内容 ControlRenderer(schema: schema, engine: engine) .frame(maxWidth: .infinity, maxHeight: .infinity) // 弹窗层 if let popup = engine.activePopup { popupOverlay(popup) } // 加载指示器 if engine.isLoading { Color.black.opacity(0.01) .overlay(ProgressView()) } } else if let error = loadError { errorView(error) } else { ProgressView("加载中...") .frame(maxWidth: .infinity, maxHeight: .infinity) } } .task { await performLoad() } .alert("错误", isPresented: .init( get: { engine.errorMessage != nil }, set: { if !$0 { engine.errorMessage = nil } } )) { Button("确定") { engine.errorMessage = nil } } message: { Text(engine.errorMessage ?? "") } } // MARK: - 加载 private func performLoad() async { if let json = jsonString { do { try engine.loadJSON(json) isLoaded = true } catch { loadError = "JSON解析失败: \(error.localizedDescription)" } } else if let url = loadURL { do { try await engine.loadFromURL(url) isLoaded = true } catch { loadError = error.localizedDescription } } } // MARK: - 弹窗层 private func popupOverlay(_ popup: PopupInfo) -> some View { ZStack { // 遮罩 Color.black.opacity(0.3) .ignoresSafeArea() .onTapGesture { engine.activePopup = nil } // 弹窗内容 PopupWindowControl(schema: popup.schema, engine: engine) .frame( maxWidth: popupWidth(popup.options), maxHeight: popupHeight(popup.options) ) .padding(20) } } private func popupWidth(_ opts: PopupOptions) -> CGFloat? { if let cw = opts.cwidth { return CGFloat(cw * 8) } if let w = opts.width, let px = Double(w.replacingOccurrences(of: "px", with: "")) { return CGFloat(px) } return 400 } private func popupHeight(_ opts: PopupOptions) -> CGFloat? { if let ch = opts.cheight { return CGFloat(ch * 16) } if let h = opts.height { if h.hasSuffix("%") { return nil } // 百分比用maxHeight if let px = Double(h.replacingOccurrences(of: "px", with: "")) { return CGFloat(px) } } return nil } // MARK: - 错误视图 private func errorView(_ error: String) -> some View { VStack(spacing: 12) { Image(systemName: "exclamationmark.triangle.fill") .font(.largeTitle) .foregroundColor(.orange) Text("加载失败") .font(.headline) Text(error) .font(.caption) .foregroundColor(.secondary) .multilineTextAlignment(.center) Button("重试") { Task { await performLoad() } } } .padding(40) } }