polish(ios): 中性化标签与方向胶囊并收紧详情头部间距

- AITag 拆为两类:不传状态色时是中性归类胶囊(tile 底、发丝描边、墨字,可带 10pt 图标),
  传状态色时才用 14% 色雾与 30% 描边。颜色只服务状态语义。
- 详情页标签改用 AITag(tag, systemImage: "tag"),与节点卡片同一枚形状;头部 VStack 间距
  14 -> 12,读数格 minHeight 62 -> 56,加载完成后不再明显小于下方卡片。
- 消息中心「收件 / 发件」由绿/墨配色改回中性胶囊配 arrow.down.left / arrow.up.right,
  绿色重新只表示健康状态。
- 批量标签表在无勾选时给出「先在节点列表勾选要归类的节点」,不再留空白行。
- 文档:0.2.2 增量记录与校验值更新为 0e4ffb74…;版本号后不再带括号,
  0.2.0/0.2.1/0.2.2 与 README、现行说明统一改写为「0.2.x 构建 N」。
This commit is contained in:
chick
2026-09-15 03:56:31 +08:00
parent eb880e0d14
commit 36ce791215
9 changed files with 61 additions and 36 deletions
@@ -105,7 +105,7 @@ struct InstanceDetailView: View {
private var headerCard: some View {
AICard(padding: 16, emphasis: true) {
VStack(alignment: .leading, spacing: 14) {
VStack(alignment: .leading, spacing: 12) {
// 导航栏已经显示实例名,卡片内不再重复,只留地址与状态。
HStack(spacing: 8) {
Image(systemName: "globe")
@@ -128,10 +128,11 @@ struct InstanceDetailView: View {
moduleAvailability
if !model.instance.tags.isEmpty {
// 与节点卡片同一枚标签形状(同一胶囊、同一图标),两处只有一处能点。
if model.instance.tags.isEmpty == false {
FlowLayout(spacing: 7) {
ForEach(model.instance.tags, id: \.self) { tag in
AITag(tag)
AITag(tag, systemImage: "tag")
}
}
.frame(maxWidth: .infinity, alignment: .leading)
@@ -490,7 +491,7 @@ struct InstanceDetailView: View {
.frame(height: 4)
}
}
.frame(maxWidth: .infinity, minHeight: 62, alignment: .topLeading)
.frame(maxWidth: .infinity, minHeight: 56, alignment: .topLeading)
.accessibilityElement(children: .combine)
}
}
@@ -47,11 +47,17 @@ struct FleetTagBatchSheet: View {
AICard {
VStack(alignment: .leading, spacing: 10) {
AISectionTitle("已选 \(targets.count) 台", systemImage: "checklist")
Text(selectedNames.joined(separator: "、"))
.font(.caption)
.foregroundStyle(AITheme.muted)
.lineLimit(3)
.truncationMode(.tail)
if targets.isEmpty {
Text("先在节点列表勾选要归类的节点。")
.font(.caption)
.foregroundStyle(AITheme.muted)
} else {
Text(selectedNames.joined(separator: "、"))
.font(.caption)
.foregroundStyle(AITheme.muted)
.lineLimit(3)
.truncationMode(.tail)
}
}
}
}
@@ -134,7 +134,7 @@ struct MessagesView: View {
.layoutPriority(1)
AITag(
message.direction.label,
tint: message.direction == .outgoing ? AITheme.primary : AITheme.success)
systemImage: message.direction == .outgoing ? "arrow.up.right" : "arrow.down.left")
Spacer(minLength: 8)
Text(TimestampText.short(message.timestamp))
.font(.caption2.weight(.medium))
@@ -47,29 +47,42 @@ extension AICard {
public struct AITag: View {
private let text: String
private let tint: Color
private let tint: Color?
private let systemImage: String?
public init(_ text: String, tint: Color = AITheme.primary) {
/// 不传 `tint` 时是中性归类标签(标签、方向这类不带健康含义的信息),
/// 传了 `tint` 才是状态标签,用同色色雾与描边。颜色只留给后者。
public init(_ text: String, tint: Color? = nil, systemImage: String? = nil) {
self.text = text
self.tint = tint
self.systemImage = systemImage
}
public var body: some View {
Text(text)
.font(.caption2.weight(.semibold))
.foregroundStyle(tint)
.lineLimit(1)
.truncationMode(.middle)
.padding(.horizontal, 10)
// 与节点卡片上的标签胶囊同一形状,同一高度;深色底上 10% 色雾几乎看不见,
// 所以描边权重必须够,不能让标签在暗色主题里消失。
.frame(height: 28)
.background(tint.opacity(0.14))
.clipShape(Capsule(style: .continuous))
.overlay {
Capsule(style: .continuous)
.strokeBorder(tint.opacity(0.30))
let tint = self.tint
HStack(spacing: 4) {
if let systemImage {
Image(systemName: systemImage)
.font(.system(size: 10, weight: .semibold))
.accessibilityHidden(true)
}
Text(text)
.font(.caption2.weight(.semibold))
.lineLimit(1)
.truncationMode(.middle)
}
.padding(.horizontal, 10)
// 与节点卡片上的标签胶囊同一形状,同一高度;深色底上 10% 色雾几乎看不见,
// 所以描边权重必须够,不能让标签在暗色主题里消失。
.frame(height: 28)
.foregroundStyle(tint == nil ? AITheme.ink : tint!)
.background(tint.map { $0.opacity(0.14) } ?? AITheme.tile)
.clipShape(Capsule(style: .continuous))
.overlay {
Capsule(style: .continuous)
.strokeBorder(tint.map { $0.opacity(0.30) } ?? AITheme.tileLine)
}
.accessibilityLabel(text)
}
}