Files
CleanMM/Docs/DESIGN_SPEC.md

879 lines
32 KiB
Markdown
Raw Normal View History

2026-03-10 17:09:35 +08:00
# 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 |