Files
CleanMM/Docs/DESIGN_SPEC.md
2026-03-10 17:09:35 +08:00

879 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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冗余
**改动**:
```swift
// 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" overlineline 109-113 整块删除)
- 使用 `AtlasTypography.screenTitle` 替换 line 117 的硬编码字号
```swift
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
```swift
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
```swift
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`
**改动**:
```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无视觉亮点。
**改为**:
```swift
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 按钮添加活跃任务计数徽章
```swift
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 闭包中添加视图标识和转场:
```swift
} 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`
```swift
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`,导致两个主要按钮视觉权重相同。
**改动**: 根据当前状态动态切换按钮层级。
```swift
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 限制):
```swift
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 重构为更紧凑的布局:
```swift
// 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` 连续堆叠,视觉过重。
**改动**:
1. **General 区域** (line 35): 保留 `AtlasInfoCard`,不变
2. **Exclusions 区域** (line 118): 保留,不变
3. **Trust & Transparency** (line 143): 保留,不变
4. **Acknowledgement** (line 177): 改为 `DisclosureGroup`
5. **Notices** (line 187): 改为 `DisclosureGroup`
```swift
// 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 区域添加条件按钮:
```swift
// 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)
}
}
```
添加跳转函数:
```swift
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 — 设计系统核心迁移
1.`AtlasDesignSystem.swift` 中删除 `AtlasPalette`,所有引用改为 `AtlasColor.*`
2. 删除旧的 `cardBackground`/`cardBorder` 函数,所有引用改为 `atlasCardBackground`/`atlasCardBorder`
3. 用 Token 重写 `AtlasScreen`§2.1
4. 用 Token 重写 `AtlasMetricCard`§2.2
5. 用 Token 重写 `AtlasInfoCard`§2.3
6. 用 Token 重写 `AtlasCallout`§2.4
7. 用 Token 重写 `AtlasDetailRow`§2.5
8. 用 Token 重写 `AtlasStatusChip`§2.6
9. 用 Token 重写 `AtlasEmptyState`§2.7
10. 用 Token 重写 `AtlasLoadingState`§2.8
### Phase 2 — App Shell
11. 侧边栏行升级§3.1
12. 工具栏图标增强§3.2
13. 详情页转场动画§3.3
### Phase 3 — Feature Screen 优化
14. Overview 英雄指标§4.1
15. SmartClean 双 CTA 修复§4.2
16. Apps 行内按钮§4.3
17. Settings 轻量化§4.4
18. Permissions 授权入口§4.5
19. History Token 替换§4.6
20. TaskCenter Token 替换§4.7
### Phase 4 — 全局清理
21. 批量替换 spacing/font 硬编码§5
22. 新增本地化字符串§6
23. 编译验证 + 全量 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 |