- 应用图标改用与 Web favicon 同源的扁平标记,金色 SIM 卡体加三格递增信号,29pt 下仍可读 - 标签统一为胶囊形状,提高深色主题下的描边权重,避免标签在暗色下消失 - 消息卡片与详情分区卡片收紧行距,单屏信息密度明显提升 - 详情头部指标精简为 4 行,数据模块改为单条读数 - 批量选择勾选框移出名称区域,工具栏控件统一为等高 34pt - 分组编辑器改为点选即生效,移除说明文字与常驻禁用按钮
185 lines
5.7 KiB
Swift
185 lines
5.7 KiB
Swift
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
|
|
}
|
|
}
|