32 KiB
Atlas for Mac — Design Specification v2
Status: Ready for implementation Brand Token 文件:
Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasBrand.swift(已创建并编译通过)
1. Brand Identity
1.1 品牌概念:Calm Authority(沉稳的权威感)
Atlas — 如同制图师为你的系统绘制地形图。精确、可信、从容不迫。
1.2 色彩体系
| Token | Light Mode | Dark Mode | 用途 |
|---|---|---|---|
AtlasColor.brand |
#0F766E 深青绿 |
#148F85 亮青绿 |
主色调、主要按钮、激活状态 |
AtlasColor.accent |
#34D399 清新薄荷绿 |
#52E2B5 明亮薄荷绿 |
高亮、徽章、品牌点缀 |
AtlasColor.success |
systemGreen | systemGreen | 安全、已授权、已完成 |
AtlasColor.warning |
systemOrange | systemOrange | 需审查、运行中 |
AtlasColor.danger |
systemRed | systemRed | 失败、高级风险 |
AtlasColor.card |
controlBackgroundColor | controlBackgroundColor | 卡片基底 |
AtlasColor.cardRaised |
white @ 65% |
white @ 6% |
浮起卡片的玻璃质感层 |
AtlasColor.border |
primary @ 8% |
primary @ 8% |
普通卡片描边 |
AtlasColor.borderEmphasis |
primary @ 14% |
primary @ 14% |
高亮卡片/焦点态描边 |
1.3 字体标尺
| Token | 定义 | 使用场景 |
|---|---|---|
AtlasTypography.heroMetric |
40pt bold rounded | Dashboard 最重要的单一数值 |
AtlasTypography.screenTitle |
34pt bold rounded | 每个屏幕的大标题 |
AtlasTypography.cardMetric |
28pt bold rounded | 网格中的指标卡数值 |
AtlasTypography.sectionTitle |
title3 semibold | InfoCard 内的分区标题 |
AtlasTypography.label |
subheadline semibold | 指标标题、侧边栏主文本 |
AtlasTypography.rowTitle |
headline | DetailRow 标题 |
AtlasTypography.body |
subheadline | 正文说明 |
AtlasTypography.caption |
caption semibold | Chip、脚注、overline |
1.4 间距网格 (4pt base)
| Token | 值 | 场景 |
|---|---|---|
AtlasSpacing.xxs |
4pt | 最小内边距 |
AtlasSpacing.xs |
6pt | Chip 内边距 |
AtlasSpacing.sm |
8pt | 行间距紧凑 |
AtlasSpacing.md |
12pt | 元素间默认间距 |
AtlasSpacing.lg |
16pt | 卡片内边距、分区间距 |
AtlasSpacing.xl |
20pt | 宽卡片内边距 |
AtlasSpacing.xxl |
24pt | 屏幕级垂直节奏 |
AtlasSpacing.screenH |
28pt | 屏幕水平边距 |
AtlasSpacing.section |
32pt | 大分区间隔 |
1.5 圆角
| Token | 值 | 场景 |
|---|---|---|
AtlasRadius.sm |
8pt | Chip、Tag |
AtlasRadius.md |
12pt | Callout、内嵌卡片 |
AtlasRadius.lg |
16pt | DetailRow、紧凑卡片 |
AtlasRadius.xl |
20pt | 标准 InfoCard/MetricCard |
AtlasRadius.xxl |
24pt | 高亮/英雄卡片 |
1.6 三级高程(Elevation)
| 级别 | 阴影 | 圆角 | 描边 | 用途 |
|---|---|---|---|---|
.flat |
无 | 16pt | 4% opacity | 嵌套内容、行内子卡片 |
.raised |
r18 y10 @5% | 20pt | 8% opacity | 默认卡片(AtlasInfoCard/MetricCard) |
.prominent |
r28 y16 @9% + 内发光 | 24pt | 12% opacity, 1.5pt | 英雄指标、主操作区 |
1.7 动画曲线
| Token | 值 | 场景 |
|---|---|---|
AtlasMotion.fast |
snappy 0.15s | hover、按压、chip |
AtlasMotion.standard |
snappy 0.22s | 选择、切换、卡片状态 |
AtlasMotion.slow |
snappy 0.35s | 页面转场、英雄揭示 |
AtlasMotion.spring |
spring(0.45, 0.7) | 完成庆祝、弹性反馈 |
1.8 按钮层级
| 样式 | 外观 | 场景 |
|---|---|---|
.atlasPrimary |
品牌色填充胶囊 + 投影 + 按压缩放 | 每屏唯一最重要 CTA |
.atlasSecondary |
品牌色描边胶囊 + 淡底 | 辅助操作 |
.atlasGhost |
纯文字 + hover 淡底 | 低频操作 |
2. 设计系统组件迁移
所有修改在
AtlasDesignSystem.swift中进行。AtlasBrand.swift已包含新 Token,不需要修改。
2.1 AtlasScreen — 约束阅读宽度 + 移除冗余 overline
文件: Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasDesignSystem.swift
当前问题:
- line 100:
.frame(maxWidth: .infinity)导致宽窗口下文本行过长 - line 109: 每屏都显示 "Atlas for Mac" overline,冗余
改动:
// body 中 ScrollView 内的 VStack 改为:
ScrollView {
VStack(alignment: .leading, spacing: AtlasSpacing.xxl) {
header
content
}
.frame(maxWidth: AtlasLayout.maxReadingWidth, alignment: .leading)
.padding(.horizontal, AtlasSpacing.screenH)
.padding(.vertical, AtlasSpacing.xxl)
.frame(maxWidth: .infinity, alignment: .leading) // 外层居中容器
}
header 改为:
- 移除 "Atlas for Mac" overline(line 109-113 整块删除)
- 使用
AtlasTypography.screenTitle替换 line 117 的硬编码字号
private var header: some View {
VStack(alignment: .leading, spacing: AtlasSpacing.sm) {
Text(title)
.font(AtlasTypography.screenTitle)
Text(subtitle)
.font(AtlasTypography.body)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
}
2.2 AtlasMetricCard — 支持 elevation 参数 + 使用 Token
文件: AtlasDesignSystem.swift
改动:
- 新增
elevation: AtlasElevation = .raised参数 - 替换 line 165 硬编码字号为
AtlasTypography.cardMetric - 替换 line 160 硬编码字号为
AtlasTypography.label - 替换 line 175 硬编码
padding(18)为padding(AtlasSpacing.xl) - 替换 line 176-177 的
cardBackground/cardBorder为atlasCardBackground/atlasCardBorder(传入 elevation)
public struct AtlasMetricCard: View {
private let title: String
private let value: String
private let detail: String
private let tone: AtlasTone
private let systemImage: String?
private let elevation: AtlasElevation // 新增
public init(
title: String,
value: String,
detail: String,
tone: AtlasTone = .neutral,
systemImage: String? = nil,
elevation: AtlasElevation = .raised // 新增
) {
self.title = title
self.value = value
self.detail = detail
self.tone = tone
self.systemImage = systemImage
self.elevation = elevation
}
public var body: some View {
VStack(alignment: .leading, spacing: AtlasSpacing.lg) {
HStack(alignment: .center, spacing: AtlasSpacing.md) {
if let systemImage {
Image(systemName: systemImage)
.font(.headline)
.foregroundStyle(tone.tint)
.accessibilityHidden(true)
}
Text(title)
.font(AtlasTypography.label)
.foregroundStyle(.secondary)
}
Text(value)
.font(elevation == .prominent ? AtlasTypography.heroMetric : AtlasTypography.cardMetric)
.foregroundStyle(.primary)
.contentTransition(.numericText())
Text(detail)
.font(AtlasTypography.body)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(AtlasSpacing.xl)
.background(atlasCardBackground(tone: tone, elevation: elevation))
.overlay(atlasCardBorder(tone: tone, elevation: elevation))
.accessibilityElement(children: .ignore)
.accessibilityLabel(Text(title))
.accessibilityValue(Text(value))
.accessibilityHint(Text(detail))
}
}
2.3 AtlasInfoCard — 使用 Token
文件: AtlasDesignSystem.swift
改动:
- 替换 line 204
spacing: 18→AtlasSpacing.xl - 替换 line 209
.title3.weight(.semibold)→AtlasTypography.sectionTitle - 替换 line 214
.subheadline→AtlasTypography.body - 替换 line 224
padding(22)→padding(AtlasSpacing.xxl) - 替换 line 225-226 为
atlasCardBackground/atlasCardBorder
2.4 AtlasCallout — 使用 Token
文件: AtlasDesignSystem.swift
改动:
- 替换 line 249
spacing: 14→AtlasSpacing.lg - 替换 line 256
spacing: 6→AtlasSpacing.xs - 替换 line 258
.headline→AtlasTypography.rowTitle - 替换 line 261
.subheadline→AtlasTypography.body - 替换 line 266
padding(16)→padding(AtlasSpacing.lg) - 替换 line 269
cornerRadius: 16→AtlasRadius.lg - 替换 line 273
cornerRadius: 16→AtlasRadius.lg
2.5 AtlasDetailRow — 使用 Token + 添加 hover 效果
文件: AtlasDesignSystem.swift
改动:
- line 307
spacing: 14→AtlasSpacing.lg - line 312
frame(width: 36, height: 36)→frame(width: AtlasLayout.sidebarIconSize + 4, height: AtlasLayout.sidebarIconSize + 4) - line 321
spacing: 6→AtlasSpacing.xs - line 338
Spacer(minLength: 16)→Spacer(minLength: AtlasSpacing.lg) - line 343
padding(16)→padding(AtlasSpacing.lg) - line 345-347 替换为
.fill(AtlasColor.cardRaised)并使用AtlasRadius.lg - line 350
Color.primary.opacity(0.06)→AtlasColor.border - 新增: 在
.overlay之后添加.atlasHover()
2.6 AtlasStatusChip — 使用 Token
文件: AtlasDesignSystem.swift
改动:
- line 421
.caption.weight(.semibold)→AtlasTypography.caption - line 422
padding(.horizontal, 10)→padding(.horizontal, AtlasSpacing.md) - line 423
padding(.vertical, 6)→padding(.vertical, AtlasSpacing.xs)
2.7 AtlasEmptyState — 更有个性
文件: AtlasDesignSystem.swift
改动:
- 图标容器从 56x56 放大到 72x72
- 圆形背景改为渐变填充
- 添加外圈装饰环
- 增加整体 padding
public var body: some View {
VStack(spacing: AtlasSpacing.lg) {
ZStack {
// 外圈装饰环
Circle()
.strokeBorder(tone.border, lineWidth: 0.5)
.frame(width: 80, height: 80)
// 渐变填充背景
Circle()
.fill(
LinearGradient(
colors: [tone.softFill, tone.softFill.opacity(0.3)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 72, height: 72)
Image(systemName: systemImage)
.font(.system(size: 28, weight: .semibold))
.foregroundStyle(tone.tint)
.accessibilityHidden(true)
}
VStack(spacing: AtlasSpacing.xs) {
Text(title)
.font(AtlasTypography.rowTitle)
Text(detail)
.font(AtlasTypography.body)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.fixedSize(horizontal: false, vertical: true)
}
}
.frame(maxWidth: .infinity)
.padding(AtlasSpacing.section)
.background(
RoundedRectangle(cornerRadius: AtlasRadius.xl, style: .continuous)
.fill(Color.primary.opacity(0.03))
)
.overlay(
RoundedRectangle(cornerRadius: AtlasRadius.xl, style: .continuous)
.strokeBorder(Color.primary.opacity(0.06), lineWidth: 1)
)
.accessibilityElement(children: .ignore)
.accessibilityLabel(Text(title))
.accessibilityValue(Text(detail))
}
2.8 AtlasLoadingState — 添加脉冲动画 + 使用 Token
文件: AtlasDesignSystem.swift
改动:
public struct AtlasLoadingState: View {
private let title: String
private let detail: String
private let progress: Double?
@State private var pulsePhase = false
public init(title: String, detail: String, progress: Double? = nil) {
self.title = title
self.detail = detail
self.progress = progress
}
public var body: some View {
VStack(alignment: .leading, spacing: AtlasSpacing.lg) {
HStack(spacing: AtlasSpacing.md) {
ProgressView()
.controlSize(.small)
.accessibilityHidden(true)
Text(title)
.font(AtlasTypography.rowTitle)
}
Text(detail)
.font(AtlasTypography.body)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
if let progress {
ProgressView(value: progress, total: 1)
.controlSize(.large)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(AtlasSpacing.xl)
.background(
RoundedRectangle(cornerRadius: AtlasRadius.lg, style: .continuous)
.fill(Color.primary.opacity(pulsePhase ? 0.05 : 0.03))
)
.overlay(
RoundedRectangle(cornerRadius: AtlasRadius.lg, style: .continuous)
.strokeBorder(Color.primary.opacity(0.08), lineWidth: 1)
)
.onAppear {
withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) {
pulsePhase = true
}
}
.accessibilityElement(children: .ignore)
.accessibilityLabel(Text(title))
.accessibilityValue(Text(progress.map { "\(Int(($0 * 100).rounded())) percent complete" } ?? detail))
.accessibilityHint(Text(detail))
}
}
2.9 删除旧的私有辅助函数
文件: AtlasDesignSystem.swift
删除 line 540-560 的旧 cardBackground 和 cardBorder 函数。它们被 AtlasBrand.swift 中的 atlasCardBackground 和 atlasCardBorder 替代。
注意: 确保所有引用点都已迁移到新函数后再删除。也删除旧的 AtlasPalette 枚举(line 66-73),因为它被 AtlasColor 替代。对 AtlasScreen 中引用 AtlasPalette.canvasTop/canvasBottom 的地方,改为 AtlasColor.canvasTop/AtlasColor.canvasBottom。
3. App Shell 改进
3.1 侧边栏行视觉升级
文件: Apps/AtlasApp/Sources/AtlasApp/AppShellView.swift
当前 (line 162-186): 标准 Label + VStack,无视觉亮点。
改为:
private struct SidebarRouteRow: View {
let route: AtlasRoute
var body: some View {
Label {
VStack(alignment: .leading, spacing: AtlasSpacing.xxs) {
Text(route.title)
.font(AtlasTypography.rowTitle)
Text(route.subtitle)
.font(AtlasTypography.captionSmall)
.foregroundStyle(.secondary)
.lineLimit(2)
}
} icon: {
// Apple System Settings 风格:圆角矩形图标背景
ZStack {
RoundedRectangle(cornerRadius: AtlasRadius.sm, style: .continuous)
.fill(AtlasColor.brand.opacity(0.1))
.frame(width: AtlasLayout.sidebarIconSize, height: AtlasLayout.sidebarIconSize)
Image(systemName: route.systemImage)
.font(.system(size: 14, weight: .semibold))
.foregroundStyle(AtlasColor.brand)
.accessibilityHidden(true)
}
}
.padding(.vertical, AtlasSpacing.sm)
.contentShape(Rectangle())
.listRowSeparator(.hidden)
.accessibilityElement(children: .combine)
.accessibilityIdentifier("route.\(route.id)")
.accessibilityLabel("\(route.title). \(route.subtitle)")
.accessibilityHint(AtlasL10n.string("sidebar.route.hint", route.shortcutNumber))
}
}
3.2 工具栏图标增强
文件: AppShellView.swift
当前 (line 28-61): 标准 toolbar 按钮,无视觉层次。
改动:
- 对所有 toolbar
Image(systemName:)添加.symbolRenderingMode(.hierarchical) - 给 TaskCenter 按钮添加活跃任务计数徽章
ToolbarItemGroup {
Button {
model.openTaskCenter()
} label: {
Label(AtlasL10n.string("toolbar.taskcenter"), systemImage: AtlasIcon.taskCenter)
.symbolRenderingMode(.hierarchical)
}
// ... 其他修饰符不变
Button {
model.navigate(to: .permissions)
Task { await model.inspectPermissions() }
} label: {
Label(AtlasL10n.string("toolbar.permissions"), systemImage: AtlasIcon.permissions)
.symbolRenderingMode(.hierarchical)
}
// ... 其他修饰符不变
Button {
model.navigate(to: .settings)
} label: {
Label(AtlasL10n.string("toolbar.settings"), systemImage: AtlasIcon.settings)
.symbolRenderingMode(.hierarchical)
}
// ... 其他修饰符不变
}
3.3 详情页转场动画
文件: AppShellView.swift
当前 (line 24): detailView(for:) 无转场效果。
改动: 在 detail 闭包中添加视图标识和转场:
} detail: {
detailView(for: model.selection ?? .overview)
.id(model.selection) // 关键:强制视图切换时触发转场
.transition(.opacity)
.searchable(...)
.toolbar { ... }
.animation(AtlasMotion.slow, value: model.selection)
}
4. Feature Screen 改进
4.1 OverviewFeatureView — 英雄指标 + 共享列定义
文件: Packages/AtlasFeaturesOverview/Sources/AtlasFeaturesOverview/OverviewFeatureView.swift
改动 1 — 英雄指标差异化 (line 31-53):
将"可回收空间"指标升级为 .prominent 高程,其余保持 .raised:
LazyVGrid(columns: AtlasLayout.metricColumns, spacing: AtlasSpacing.lg) {
AtlasMetricCard(
title: AtlasL10n.string("overview.metric.reclaimable.title"),
value: AtlasFormatters.byteCount(snapshot.reclaimableSpaceBytes),
detail: AtlasL10n.string("overview.metric.reclaimable.detail"),
tone: .success,
systemImage: "sparkles",
elevation: .prominent // 英雄指标
)
AtlasMetricCard(
title: AtlasL10n.string("overview.metric.findings.title"),
value: "\(snapshot.findings.count)",
detail: AtlasL10n.string("overview.metric.findings.detail"),
tone: .neutral,
systemImage: "line.3.horizontal.decrease.circle"
// elevation 默认 .raised
)
AtlasMetricCard(
title: AtlasL10n.string("overview.metric.permissions.title"),
value: "\(grantedPermissionCount)/\(snapshot.permissions.count)",
detail: grantedPermissionCount == snapshot.permissions.count
? AtlasL10n.string("overview.metric.permissions.ready")
: AtlasL10n.string("overview.metric.permissions.limited"),
tone: grantedPermissionCount == snapshot.permissions.count ? .success : .warning,
systemImage: "lock.shield"
// elevation 默认 .raised
)
}
改动 2 — 删除私有 columns 属性 (line 185-191),全部替换为 AtlasLayout.metricColumns。
改动 3 — 所有 spacing: 16 替换为 AtlasSpacing.lg,所有 spacing: 12 替换为 AtlasSpacing.md。
4.2 SmartCleanFeatureView — 解决双 CTA 竞争
文件: Packages/AtlasFeaturesSmartClean/Sources/AtlasFeaturesSmartClean/SmartCleanFeatureView.swift
核心问题: line 85 和 line 112 同时使用 .borderedProminent,导致两个主要按钮视觉权重相同。
改动: 根据当前状态动态切换按钮层级。
HStack(spacing: AtlasSpacing.md) {
// Run Scan 按钮
Button(action: onStartScan) {
Label(AtlasL10n.string("smartclean.action.runScan"), systemImage: "sparkles")
}
.buttonStyle(plan.items.isEmpty ? .atlasPrimary : .atlasSecondary)
.disabled(isScanning || isExecutingPlan)
.keyboardShortcut(plan.items.isEmpty ? .defaultAction : KeyEquivalent("s"), modifiers: plan.items.isEmpty ? [] : [.command, .option])
.accessibilityIdentifier("smartclean.runScan")
.accessibilityHint(AtlasL10n.string("smartclean.action.runScan.hint"))
// Refresh Preview 按钮
Button(action: onRefreshPreview) {
Label(AtlasL10n.string("smartclean.action.refreshPreview"), systemImage: "arrow.clockwise")
}
.buttonStyle(.atlasGhost)
.disabled(isScanning || isExecutingPlan)
.accessibilityIdentifier("smartclean.refreshPreview")
.accessibilityHint(AtlasL10n.string("smartclean.action.refreshPreview.hint"))
Spacer()
// Execute 按钮 — 仅当 plan 有内容时为主要按钮
Button(action: onExecutePlan) {
Label(AtlasL10n.string("smartclean.action.execute"), systemImage: "play.fill")
}
.buttonStyle(plan.items.isEmpty ? .atlasSecondary : .atlasPrimary)
.disabled(isScanning || isExecutingPlan || plan.items.isEmpty)
.keyboardShortcut(plan.items.isEmpty ? nil : .defaultAction)
.accessibilityIdentifier("smartclean.executePreview")
.accessibilityHint(AtlasL10n.string("smartclean.action.execute.hint"))
}
注意:
.keyboardShortcut条件赋值在 SwiftUI 中需要用if/else包裹两个完整的Button,不能直接三元。保持现有的Group { if ... else ... }结构,但把内部的.buttonStyle改为条件化。
实际可编译方案(考虑 SwiftUI 限制):
HStack(spacing: AtlasSpacing.md) {
Group {
if plan.items.isEmpty {
Button(action: onStartScan) {
Label(AtlasL10n.string("smartclean.action.runScan"), systemImage: "sparkles")
}
.keyboardShortcut(.defaultAction)
} else {
Button(action: onStartScan) {
Label(AtlasL10n.string("smartclean.action.runScan"), systemImage: "sparkles")
}
}
}
.buttonStyle(plan.items.isEmpty ? .borderedProminent : .bordered) // 关键改动
.controlSize(.large)
.disabled(isScanning || isExecutingPlan)
.accessibilityIdentifier("smartclean.runScan")
Button(action: onRefreshPreview) {
Label(AtlasL10n.string("smartclean.action.refreshPreview"), systemImage: "arrow.clockwise")
}
.buttonStyle(.bordered)
.controlSize(.large)
.disabled(isScanning || isExecutingPlan)
.accessibilityIdentifier("smartclean.refreshPreview")
Spacer()
Group {
if !plan.items.isEmpty {
Button(action: onExecutePlan) {
Label(AtlasL10n.string("smartclean.action.execute"), systemImage: "play.fill")
}
.keyboardShortcut(.defaultAction)
} else {
Button(action: onExecutePlan) {
Label(AtlasL10n.string("smartclean.action.execute"), systemImage: "play.fill")
}
}
}
.buttonStyle(!plan.items.isEmpty ? .borderedProminent : .bordered) // 关键改动
.controlSize(.large)
.disabled(isScanning || isExecutingPlan || plan.items.isEmpty)
.accessibilityIdentifier("smartclean.executePreview")
}
额外改动: 删除私有 columns (line 231-237),替换为 AtlasLayout.metricColumns。所有 spacing: 16 → AtlasSpacing.lg。
4.3 AppsFeatureView — 行内按钮水平化
文件: Packages/AtlasFeaturesApps/Sources/AtlasFeaturesApps/AppsFeatureView.swift
当前问题: line 181-208 的 trailing 区域是 VStack,包含 byteCount + chip + HStack(两个按钮),导致每行非常高。
改动: 将 trailing 重构为更紧凑的布局:
// line 181 trailing 改为:
VStack(alignment: .trailing, spacing: AtlasSpacing.sm) {
HStack(spacing: AtlasSpacing.sm) {
AtlasStatusChip(
AtlasL10n.string("apps.list.row.leftovers", app.leftoverItems),
tone: app.leftoverItems > 0 ? .warning : .success
)
Text(AtlasFormatters.byteCount(app.bytes))
.font(AtlasTypography.label)
.foregroundStyle(.secondary)
}
HStack(spacing: AtlasSpacing.sm) {
Button(activePreviewAppID == app.id ? AtlasL10n.string("apps.preview.running") : AtlasL10n.string("apps.preview.action")) {
onPreviewAppUninstall(app.id)
}
.buttonStyle(.bordered)
.controlSize(.small)
.disabled(isRunning)
Button(activeUninstallAppID == app.id ? AtlasL10n.string("apps.uninstall.running") : AtlasL10n.string("apps.uninstall.action")) {
onExecuteAppUninstall(app.id)
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
.disabled(isRunning)
}
}
额外改动: 删除私有 columns,替换为 AtlasLayout.metricColumns。
4.4 SettingsFeatureView — 轻量化设置页
文件: Packages/AtlasFeaturesSettings/Sources/AtlasFeaturesSettings/SettingsFeatureView.swift
当前问题: 5 个 AtlasInfoCard 连续堆叠,视觉过重。
改动:
- General 区域 (line 35): 保留
AtlasInfoCard,不变 - Exclusions 区域 (line 118): 保留,不变
- Trust & Transparency (line 143): 保留,不变
- Acknowledgement (line 177): 改为
DisclosureGroup - Notices (line 187): 改为
DisclosureGroup
// 替换 line 177-195 的两个 AtlasInfoCard 为:
AtlasInfoCard(
title: AtlasL10n.string("settings.legal.title"), // 新增合并标题:"法律信息"
subtitle: AtlasL10n.string("settings.legal.subtitle")
) {
VStack(alignment: .leading, spacing: AtlasSpacing.md) {
DisclosureGroup(AtlasL10n.string("settings.acknowledgement.title")) {
Text(settings.acknowledgementText)
.font(AtlasTypography.body)
.foregroundStyle(.secondary)
.textSelection(.enabled)
.padding(.top, AtlasSpacing.sm)
}
Divider()
DisclosureGroup(AtlasL10n.string("settings.notices.title")) {
Text(settings.thirdPartyNoticesText)
.font(AtlasTypography.body)
.foregroundStyle(.secondary)
.textSelection(.enabled)
.padding(.top, AtlasSpacing.sm)
}
}
}
注意: 需要在 Localizable.strings 中新增
settings.legal.title和settings.legal.subtitle两个 key。中文值分别为 "法律信息" 和 "致谢与第三方声明"。英文值分别为 "Legal" 和 "Acknowledgements and third-party notices"。
4.5 PermissionsFeatureView — 添加授权入口
文件: Packages/AtlasFeaturesPermissions/Sources/AtlasFeaturesPermissions/PermissionsFeatureView.swift
当前问题: 未授权的权限行只显示 "Needed Later" chip,无操作入口。
改动: 在 line 109-113 的 trailing 区域添加条件按钮:
// line 109 trailing 改为:
VStack(alignment: .trailing, spacing: AtlasSpacing.sm) {
AtlasStatusChip(
state.isGranted ? AtlasL10n.string("common.granted") : AtlasL10n.string("common.neededLater"),
tone: state.isGranted ? .success : .warning
)
if !state.isGranted {
Button(AtlasL10n.string("permissions.grant.action")) {
openSystemPreferences(for: state.kind)
}
.buttonStyle(.bordered)
.controlSize(.small)
}
}
添加跳转函数:
private func openSystemPreferences(for kind: PermissionKind) {
let urlString: String
switch kind {
case .fullDiskAccess:
urlString = "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"
case .accessibility:
urlString = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
case .notifications:
urlString = "x-apple.systempreferences:com.apple.preference.security?Privacy_Notifications"
}
if let url = URL(string: urlString) {
NSWorkspace.shared.open(url)
}
}
额外改动: 删除私有 columns,替换为 AtlasLayout.metricColumns。
4.6 HistoryFeatureView — 使用 Token
文件: Packages/AtlasFeaturesHistory/Sources/AtlasFeaturesHistory/HistoryFeatureView.swift
改动: 仅 Token 替换,无结构性变化。
- 所有
spacing: 12→AtlasSpacing.md - 所有
spacing: 10→AtlasSpacing.md
4.7 TaskCenterView — 使用 Token + 添加分隔线
文件: Apps/AtlasApp/Sources/AtlasApp/TaskCenterView.swift
改动:
- line 11
spacing: 18→AtlasSpacing.xl - line 12
spacing: 8→AtlasSpacing.sm - line 14
.title2.weight(.semibold)→AtlasTypography.sectionTitle - line 17
.subheadline→AtlasTypography.body - line 38
spacing: 10→AtlasSpacing.md - line 62
padding(20)→padding(AtlasSpacing.xl) - 在标题和 callout 之间添加
Divider()
5. 全局搜索替换清单
以下是可以安全地在所有 Feature View 文件中批量替换的模式:
| 搜索 | 替换 | 范围 |
|---|---|---|
spacing: 16) (在 LazyVGrid/VStack 中) |
spacing: AtlasSpacing.lg) |
所有 Feature View |
spacing: 12) (在 VStack 中) |
spacing: AtlasSpacing.md) |
所有 Feature View |
spacing: 8) (在 VStack 中) |
spacing: AtlasSpacing.sm) |
所有 Feature View |
spacing: 10) |
spacing: AtlasSpacing.md) |
TaskCenterView |
.font(.subheadline) (非 .weight) |
.font(AtlasTypography.body) |
所有文件 |
.font(.subheadline.weight(.semibold)) |
.font(AtlasTypography.label) |
所有文件 |
.font(.headline) |
.font(AtlasTypography.rowTitle) |
所有文件(非 icon 处) |
.font(.caption.weight(.semibold)) |
.font(AtlasTypography.caption) |
所有文件 |
私有 columns 属性 |
AtlasLayout.metricColumns |
Overview/SmartClean/Apps/Permissions |
6. 新增本地化字符串
在 zh-Hans.lproj/Localizable.strings 和 en.lproj/Localizable.strings 中添加:
| Key | 中文 | English |
|---|---|---|
settings.legal.title |
法律信息 | Legal |
settings.legal.subtitle |
致谢与第三方声明 | Acknowledgements and third-party notices |
permissions.grant.action |
前往授权 | Grant Access |
7. 实施顺序
Phase 1 — 设计系统核心迁移
- 在
AtlasDesignSystem.swift中删除AtlasPalette,所有引用改为AtlasColor.* - 删除旧的
cardBackground/cardBorder函数,所有引用改为atlasCardBackground/atlasCardBorder - 用 Token 重写
AtlasScreen(§2.1) - 用 Token 重写
AtlasMetricCard(§2.2) - 用 Token 重写
AtlasInfoCard(§2.3) - 用 Token 重写
AtlasCallout(§2.4) - 用 Token 重写
AtlasDetailRow(§2.5) - 用 Token 重写
AtlasStatusChip(§2.6) - 用 Token 重写
AtlasEmptyState(§2.7) - 用 Token 重写
AtlasLoadingState(§2.8)
Phase 2 — App Shell
- 侧边栏行升级(§3.1)
- 工具栏图标增强(§3.2)
- 详情页转场动画(§3.3)
Phase 3 — Feature Screen 优化
- Overview 英雄指标(§4.1)
- SmartClean 双 CTA 修复(§4.2)
- Apps 行内按钮(§4.3)
- Settings 轻量化(§4.4)
- Permissions 授权入口(§4.5)
- History Token 替换(§4.6)
- TaskCenter Token 替换(§4.7)
Phase 4 — 全局清理
- 批量替换 spacing/font 硬编码(§5)
- 新增本地化字符串(§6)
- 编译验证 + 全量 UI 测试
8. 文件清单
| 文件 | 改动类型 |
|---|---|
Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasBrand.swift |
✅ 已创建 |
Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasDesignSystem.swift |
重构 |
Apps/AtlasApp/Sources/AtlasApp/AppShellView.swift |
修改 |
Apps/AtlasApp/Sources/AtlasApp/TaskCenterView.swift |
修改 |
Packages/AtlasFeaturesOverview/Sources/.../OverviewFeatureView.swift |
修改 |
Packages/AtlasFeaturesSmartClean/Sources/.../SmartCleanFeatureView.swift |
修改 |
Packages/AtlasFeaturesApps/Sources/.../AppsFeatureView.swift |
修改 |
Packages/AtlasFeaturesHistory/Sources/.../HistoryFeatureView.swift |
修改 |
Packages/AtlasFeaturesPermissions/Sources/.../PermissionsFeatureView.swift |
修改 |
Packages/AtlasFeaturesSettings/Sources/.../SettingsFeatureView.swift |
修改 |
Packages/AtlasDomain/Sources/.../Resources/zh-Hans.lproj/Localizable.strings |
新增 3 个 key |
Packages/AtlasDomain/Sources/.../Resources/en.lproj/Localizable.strings |
新增 3 个 key |