- 删除分组实体:分组视图/编辑器/仓储与 groupId 全部移除,升级首启清理 instance-groups.json 与 fleet.groupMode - 新增批量标签表:三态覆盖度(未贴 / N/M / 全部),整批加减与新建标签 - 配色重构,全应用零蓝色:石墨墨色承担主操作与选中态,颜色只留给状态语义; 浅色改中性冷灰,深色改低明度石墨三层 - 选中态改实心主色 + onPrimary,替换不可读的低透明度色雾(主题块、图例胶囊) - 应用图标重做为扁平石墨底 + 黄铜 SIM 面板,参数化光栅化脚本与参考稿 - 运行概览:检测进度并入副标题,读数栅格固定八格两行并保留不可用占位 - 实例详情:仪表改两列发丝线栅格,缺数据留轨道,修掉头部高度回弹; 补齐设备名称/存储总量/可用存储/使用率四个字段名 - 版本号显示去掉括号,设置页只显示 0.2.2 - 文档:新增 docs/ios-native-0.2.2.md,同步 ios-native.md 与两处 README
148 lines
4.4 KiB
Swift
148 lines
4.4 KiB
Swift
import SwiftUI
|
|
|
|
struct InstanceFormView: View {
|
|
@Environment(\.dismiss) private var dismiss
|
|
@ObservedObject var model: FleetViewModel
|
|
let instance: Instance?
|
|
@State private var name = ""
|
|
@State private var originText = ""
|
|
@State private var tags: [String] = []
|
|
@State private var tagDraft = ""
|
|
@State private var password = ""
|
|
@State private var validationMessage: String?
|
|
@State private var isSaving = false
|
|
|
|
init(model: FleetViewModel, instance: Instance? = nil) {
|
|
self.model = model
|
|
self.instance = instance
|
|
_name = State(initialValue: instance?.name ?? "")
|
|
_originText = State(initialValue: instance?.origin.absoluteString ?? "")
|
|
_tags = State(initialValue: instance?.tags ?? [])
|
|
}
|
|
|
|
private var canSave: Bool {
|
|
!name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
&& !originText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
ScrollView {
|
|
VStack(spacing: 12) {
|
|
AICard {
|
|
VStack(alignment: .leading, spacing: 14) {
|
|
field("名称", text: $name, placeholder: "Home Node")
|
|
field("地址", text: $originText, placeholder: "http://192.168.1.10:3000")
|
|
tagEditor
|
|
field(
|
|
"实例密码",
|
|
text: $password,
|
|
placeholder: instance == nil ? "可选,仅保存在本机" : "留空则不修改密码",
|
|
secure: true
|
|
)
|
|
}
|
|
}
|
|
if let validationMessage {
|
|
AIStatusBanner(
|
|
validationMessage, tint: AITheme.danger, systemImage: "exclamationmark.triangle")
|
|
}
|
|
}
|
|
.padding(16)
|
|
.padding(.bottom, 8)
|
|
}
|
|
.aiBottomActionBar {
|
|
AIActionRow {
|
|
Button("取消") { dismiss() }
|
|
.buttonStyle(AIButtonStyle(kind: .secondary, size: .regular, fillsWidth: true))
|
|
|
|
Button {
|
|
save()
|
|
} label: {
|
|
HStack(spacing: 8) {
|
|
if isSaving {
|
|
ProgressView()
|
|
.controlSize(.small)
|
|
}
|
|
Text(isSaving ? "保存中" : "保存实例")
|
|
}
|
|
}
|
|
.buttonStyle(AIButtonStyle(kind: .primary, size: .regular, fillsWidth: true))
|
|
.disabled(!canSave || isSaving)
|
|
}
|
|
}
|
|
.background(AITheme.canvas.ignoresSafeArea())
|
|
.navigationTitle(instance == nil ? "添加实例" : "编辑实例")
|
|
#if os(iOS)
|
|
.scrollDismissesKeyboard(.interactively)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
private var tagEditor: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
AIFieldLabel("标签")
|
|
TagEditorView(tags: $tags, suggestions: model.allTags)
|
|
}
|
|
.accessibilityElement(children: .contain)
|
|
}
|
|
|
|
private func field(
|
|
_ title: String,
|
|
text: Binding<String>,
|
|
placeholder: String,
|
|
secure: Bool = false
|
|
) -> some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
AIFieldLabel(title)
|
|
if secure {
|
|
SecureField(placeholder, text: text)
|
|
.textFieldStyle(AIFieldStyle())
|
|
.textContentType(.password)
|
|
.accessibilityLabel(title)
|
|
} else {
|
|
TextField(placeholder, text: text)
|
|
.textFieldStyle(AIFieldStyle())
|
|
.accessibilityLabel(title)
|
|
.submitLabel(.done)
|
|
#if os(iOS)
|
|
.keyboardType(title == "地址" ? .URL : .default)
|
|
.textInputAutocapitalization(.never)
|
|
#endif
|
|
.autocorrectionDisabled()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func save() {
|
|
guard !isSaving else { return }
|
|
isSaving = true
|
|
let normalizedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard let origin = URL(string: originText.trimmingCharacters(in: .whitespacesAndNewlines)),
|
|
origin.scheme == "http" || origin.scheme == "https",
|
|
origin.host != nil
|
|
else {
|
|
validationMessage = "请输入有效的 HTTP 或 HTTPS 根地址。"
|
|
isSaving = false
|
|
return
|
|
}
|
|
let input = InstanceInput(
|
|
name: normalizedName,
|
|
origin: origin,
|
|
tags: tags,
|
|
password: password.isEmpty ? nil : password
|
|
)
|
|
Task {
|
|
do {
|
|
if let instance {
|
|
try await model.update(instance.id, input)
|
|
} else {
|
|
try await model.add(input)
|
|
}
|
|
} catch {
|
|
validationMessage = "保存失败,请检查地址是否重复。"
|
|
}
|
|
isSaving = false
|
|
}
|
|
}
|
|
}
|