release(ios): 0.2.2 构建 9 修复消息发送遮挡并中性化石墨灰阶

- 消息中心发送入口从被标签栏盖住的底部操作条移到导航栏右侧,发信恢复可用
- 停用态主按钮保留主色淡底与同色描边,与次级按钮拉开层级
- 消息行删除改安静线性图标,去掉一屏红块
- 缺数据读数统一为短横,卡片延迟补空格单位
- 中性灰阶改为严格 R=G=B,浅色主题不再读出冷调;图标石墨底同步去蓝
- 详情页延迟量表改用与卡片同一算法,身份信息值改为右对齐
- 标签加号按钮与输入框同高同圆角
This commit is contained in:
chick
2026-09-15 08:19:18 +08:00
parent 9ad0c46088
commit c3d17b3daf
21 changed files with 203 additions and 125 deletions
@@ -180,6 +180,9 @@ struct FleetView: View {
}
private extension FleetView {
/// 概览读数缺数据时只画一根短横:「不可用」在四列窄格里既占位又容易被读成故障结论。
static let noDataText = "—"
private static let relativeFormatter: RelativeDateTimeFormatter = {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .abbreviated
@@ -361,7 +364,7 @@ private extension FleetView {
)
FleetHeadlineStat(
title: "异常节点",
value: settledCount == 0 ? "--" : "\(abnormalCount)",
value: settledCount == 0 ? Self.noDataText : "\(abnormalCount)",
detail: abnormalDetail,
tint: abnormalTint
)
@@ -666,7 +669,7 @@ private extension FleetView {
private var hasSettledResults: Bool { summaryRows.isEmpty == false && settledCount > 0 }
private var onlineRateValue: String {
guard hasSettledResults else { return "--" }
guard hasSettledResults else { return Self.noDataText }
return "\(Int((Double(onlineCount) / Double(summaryRows.count) * 100).rounded()))%"
}
@@ -749,10 +752,10 @@ private extension FleetView {
private var storageSummary: String {
let pairs = storagePairs
guard pairs.isEmpty == false else { return "不可用" }
guard pairs.isEmpty == false else { return Self.noDataText }
let used = pairs.map { max(0, $0.total - $0.available) }.reduce(0, +)
let total = pairs.map(\.total).reduce(0, +)
guard total > 0 else { return "不可用" }
guard total > 0 else { return Self.noDataText }
return "\(Int((Double(used) / Double(total) * 100).rounded()))% 已用"
}
@@ -777,7 +780,7 @@ private extension FleetView {
private var trafficSummary: String {
let values = summaryRows.compactMap(\.downloadRateBytesPerSecond) + summaryRows.compactMap(\.uploadRateBytesPerSecond)
guard values.isEmpty == false else { return "不可用" }
guard values.isEmpty == false else { return Self.noDataText }
return ByteCountFormatter.string(fromByteCount: Int64(values.reduce(0, +)), countStyle: .decimal) + "/s"
}
@@ -793,7 +796,7 @@ private extension FleetView {
}
private func formatAverage(_ values: [Double], transform: (Double) -> String) -> String {
guard values.isEmpty == false else { return "不可用" }
guard values.isEmpty == false else { return Self.noDataText }
let average = values.reduce(0, +) / Double(values.count)
return transform(average)
}
@@ -927,25 +930,25 @@ private extension FleetView {
[
FleetGauge(
label: "信号",
value: row.signalPercent.map { "\($0)%" } ?? "--",
value: row.signalPercent.map { "\($0)%" } ?? Self.noDataText,
ratio: row.signalPercent.map { Double($0) / 100 },
tint: row.signalPercent.flatMap(signalColor) ?? AITheme.muted
),
FleetGauge(
label: "延迟",
value: row.latencyMilliseconds.map { "\($0)ms" } ?? "--",
value: row.latencyMilliseconds.map { "\($0) ms" } ?? Self.noDataText,
ratio: row.latencyMilliseconds.map { max(0, 1 - Double($0) / 1000) },
tint: row.latencyMilliseconds.flatMap(latencyColor) ?? AITheme.muted
),
FleetGauge(
label: "CPU",
value: row.cpuPercent.map(Self.percentText) ?? "--",
value: row.cpuPercent.map(Self.percentText) ?? Self.noDataText,
ratio: row.cpuPercent.map { $0 / 100 },
tint: row.cpuPercent.flatMap(resourceColor) ?? AITheme.muted
),
FleetGauge(
label: "内存",
value: row.memoryPercent.map(Self.percentText) ?? "--",
value: row.memoryPercent.map(Self.percentText) ?? Self.noDataText,
ratio: row.memoryPercent.map { $0 / 100 },
tint: row.memoryPercent.flatMap(resourceColor) ?? AITheme.muted
),
@@ -993,7 +996,7 @@ private extension FleetView {
private static func cpuValue(percent: Double?, cores: Int?) -> String? {
guard percent != nil || cores != nil else { return nil }
let usage = percent.map(Self.percentText) ?? "--"
let usage = percent.map(Self.percentText) ?? FleetView.noDataText
guard let cores, cores > 0 else { return usage }
return "\(usage) · \(cores) 核"
}
@@ -1296,7 +1299,7 @@ extension FleetView {
// 没有数据时用弱色占位,读起来不会像"0%"那样的假结论。
var isPlaceholder: Bool {
value == "--" || value == "不可用" || value == "暂无"
value == "--" || value == "—" || value == "不可用" || value == "暂无"
}
}
@@ -1546,7 +1549,7 @@ extension FleetView {
.font(.caption2)
.foregroundStyle(AITheme.faint)
.lineLimit(1)
Text(value ?? "--")
Text(value ?? FleetView.noDataText)
.font(.footnote.weight(.semibold))
.monospacedDigit()
.foregroundStyle(isAlert ? tint : AITheme.ink)
@@ -22,16 +22,17 @@ struct TagEditorView: View {
commit(draft)
} label: {
Image(systemName: "plus")
.font(.system(size: 15, weight: .semibold))
.frame(width: 44, height: 44)
// 加号按钮和左边的输入框同一高度、同一圆角,两个控件才读得出是一组。
.font(.system(size: 16, weight: .semibold))
.frame(width: AITheme.inputHeight, height: AITheme.inputHeight)
.foregroundStyle(canAdd ? AITheme.primary : AITheme.muted)
.background(canAdd ? AITheme.primary.opacity(0.10) : AITheme.control)
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
.clipShape(RoundedRectangle(cornerRadius: AITheme.inputRadius, style: .continuous))
.overlay {
RoundedRectangle(cornerRadius: 14, style: .continuous)
RoundedRectangle(cornerRadius: AITheme.inputRadius, style: .continuous)
.strokeBorder(canAdd ? AITheme.primary.opacity(0.40) : AITheme.line)
}
.contentShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
.contentShape(RoundedRectangle(cornerRadius: AITheme.inputRadius, style: .continuous))
}
.buttonStyle(.plain)
.disabled(canAdd == false)