import SwiftUI struct InstanceGroupManagerView: View { @Environment(\.dismiss) private var dismiss @ObservedObject var model: FleetViewModel @State private var newName = "" @State private var editingGroup: InstanceGroup? @State private var editingName = "" @State private var validationMessage: String? @State private var isWorking = false @State private var pendingDelete: InstanceGroup? var body: some View { NavigationStack { ScrollView { LazyVStack(spacing: 12) { AICard { VStack(alignment: .leading, spacing: 10) { AIFieldLabel("新增分组") TextField("例如:家里、外场、测试机", text: $newName) .textFieldStyle(AIFieldStyle()) .submitLabel(.done) } } if let validationMessage { AIStatusBanner( validationMessage, tint: AITheme.danger, systemImage: "exclamationmark.triangle" ) } if model.groups.isEmpty { AICard { AIEmptyState( systemImage: "folder.badge.plus", title: "暂无分组", message: "创建分组后,实例可按业务或位置归类。" ) } } else { ForEach(model.groups) { group in AICard { HStack(spacing: 12) { Image(systemName: "folder") .font(.subheadline.weight(.semibold)) .foregroundStyle(AITheme.primary) .frame(width: 34, height: 34) .background(AITheme.primary.opacity(0.1)) .clipShape(RoundedRectangle(cornerRadius: 11, style: .continuous)) .accessibilityHidden(true) VStack(alignment: .leading, spacing: 3) { Text(group.name) .font(.subheadline.weight(.semibold)) .foregroundStyle(AITheme.ink) .lineLimit(1) Text("\(instanceCount(group.id)) 个实例") .font(.caption) .foregroundStyle(AITheme.muted) } Spacer() AISymbolAction("重命名分组 \(group.name)", systemImage: "pencil", tint: AITheme.muted) { editingGroup = group editingName = group.name validationMessage = nil } AISymbolAction("删除分组 \(group.name)", systemImage: "trash", tint: AITheme.danger) { pendingDelete = group } .disabled(isWorking) } } } } } .padding(16) .padding(.bottom, 8) } .background(AITheme.canvas.ignoresSafeArea()) .navigationTitle("分组管理") #if os(iOS) .navigationBarTitleDisplayMode(.inline) #endif .toolbar { ToolbarItem(placement: .cancellationAction) { Button("取消") { dismiss() } } ToolbarItem(placement: .confirmationAction) { Button("完成") { dismiss() } } } .aiBottomActionBar { AIActionRow { Button { Task { await create() } } label: { Label("创建分组", systemImage: "plus") } .buttonStyle(AIButtonStyle(kind: .primary, size: .regular, fillsWidth: true)) .disabled(isWorking) } } .alert("编辑分组", isPresented: Binding( get: { editingGroup != nil }, set: { if !$0 { editingGroup = nil } } )) { TextField("分组名称", text: $editingName) Button("保存") { Task { await saveEditing() } } Button("取消", role: .cancel) {} } message: { Text("分组名称最多 100 个字符。") } .confirmationDialog( pendingDelete == nil ? "删除分组?" : "删除「\(pendingDelete!.name)」?该分组下的 \(instanceCount(pendingDelete!.id)) 个实例会回到未分组。", isPresented: Binding( get: { pendingDelete != nil }, set: { if !$0 { pendingDelete = nil } } ), titleVisibility: .visible ) { if let group = pendingDelete { Button("删除分组", role: .destructive) { Task { await delete(group) } } } Button("取消", role: .cancel) {} } .task { await model.loadGroups() } } } private func instanceCount(_ id: UUID) -> Int { model.rows.filter { $0.instance.groupId == id }.count } private func create() async { guard !isWorking else { return } isWorking = true validationMessage = nil do { try await model.createGroup(named: newName) newName = "" } catch { validationMessage = "创建失败,名称不能为空且不能重复。" } isWorking = false } private func saveEditing() async { guard let editingGroup else { return } isWorking = true validationMessage = nil do { try await model.updateGroup(editingGroup.id, name: editingName) self.editingGroup = nil } catch { validationMessage = "更新失败,名称不能为空且不能重复。" } isWorking = false } private func delete(_ group: InstanceGroup) async { guard !isWorking else { return } isWorking = true pendingDelete = nil do { try await model.deleteGroup(group.id) } catch { validationMessage = "删除失败,请稍后重试。" } isWorking = false } }