Files
multi-simadmin/apps/ios/Sources/MultiSimAdmin/Features/Fleet/InstanceGroupManagerView.swift
T
chick 3607eddc9f release(ios): 发布 0.2.1 并重构图标与界面细节
- 应用图标改用与 Web favicon 同源的扁平标记,金色 SIM 卡体加三格递增信号,29pt 下仍可读
- 标签统一为胶囊形状,提高深色主题下的描边权重,避免标签在暗色下消失
- 消息卡片与详情分区卡片收紧行距,单屏信息密度明显提升
- 详情头部指标精简为 4 行,数据模块改为单条读数
- 批量选择勾选框移出名称区域,工具栏控件统一为等高 34pt
- 分组编辑器改为点选即生效,移除说明文字与常驻禁用按钮
2026-09-14 16:30:39 +08:00

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
}
}