import SwiftUI struct FleetView: View { @StateObject private var model: FleetViewModel @Environment(\.refresh) private var refreshEnvironment init(model: FleetViewModel? = nil) { _model = StateObject(wrappedValue: model ?? DefaultContainer.makeFleetModel()) } var body: some View { NavigationStack { ZStack { AITheme.canvas.ignoresSafeArea() ScrollView { LazyVStack(spacing: 12) { searchField if model.rows.isEmpty && !model.isLoading { AICard { VStack(spacing: 10) { Image(systemName: "server.rack") .font(.system(size: 28)) .foregroundStyle(AITheme.muted) Text("暂无实例") .font(.headline) Text("添加 SimAdmin 实例后即可开始管理。") .font(.subheadline) .foregroundStyle(AITheme.muted) } .frame(maxWidth: .infinity) .padding(.vertical, 18) } } ForEach(model.rows) { row in instanceCard(row) } } .padding(16) } } .navigationTitle("节点总览") .toolbar { ToolbarItemGroup(placement: .primaryAction) { Button { model.selectionMode.toggle() model.selectedIDs.removeAll() } label: { Label("批量选择", systemImage: model.selectionMode ? "xmark" : "checklist") } .buttonStyle(AIButtonStyle(kind: .secondary, size: .regular)) Button { model.showingForm = true } label: { Label("添加实例", systemImage: "plus") } .buttonStyle(AIButtonStyle(kind: .primary, size: .regular)) } } .refreshable { await model.refresh() } .task { await model.refresh() } .sheet(isPresented: $model.showingForm) { InstanceFormView(model: model) } } } private var searchField: some View { HStack(spacing: 8) { Image(systemName: "magnifyingglass") .foregroundStyle(AITheme.muted) TextField("搜索实例或地址", text: $model.searchText) .textFieldStyle(.plain) .onSubmit { Task { await model.refresh() } } } .padding(.horizontal, 14) .frame(minHeight: AITheme.buttonHeight) .background(AITheme.surface) .clipShape(RoundedRectangle(cornerRadius: AITheme.buttonRadius)) .overlay { RoundedRectangle(cornerRadius: AITheme.buttonRadius) .strokeBorder(AITheme.line) } } private func instanceCard(_ row: FleetViewModel.Row) -> some View { AICard { HStack(alignment: .top, spacing: 12) { if model.selectionMode { Button { toggle(row.id) } label: { Image(systemName: model.selectedIDs.contains(row.id) ? "checkmark.circle.fill" : "circle") .font(.system(size: 20)) .foregroundStyle(model.selectedIDs.contains(row.id) ? AITheme.primary : AITheme.muted) } .buttonStyle(.plain) .frame(width: 32, height: 32) } VStack(alignment: .leading, spacing: 8) { HStack(spacing: 8) { Text(row.instance.name) .font(.headline) .foregroundStyle(AITheme.ink) Spacer() AITag(row.status.label, tint: statusColor(row.status)) } Text(row.instance.origin.absoluteString) .font(.subheadline) .foregroundStyle(AITheme.muted) if !row.instance.tags.isEmpty { HStack(spacing: 6) { ForEach(row.instance.tags, id: \.self) { tag in AITag(tag) } } } } } } } private func statusColor(_ status: ConnectionStatus) -> Color { switch status { case .online: return AITheme.success case .offline: return AITheme.danger case .authRequired: return AITheme.warning case .degraded: return AITheme.warning case .unknown: return AITheme.muted } } private func toggle(_ id: UUID) { if model.selectedIDs.contains(id) { model.selectedIDs.remove(id) } else { model.selectedIDs.insert(id) } } }