- 架构层:ViewModel/ObservableObject、Service/Repository、网络层 APIClient/APIEndpoint/APIError - 设计系统:Color(light:dark:) 自适应 28 色 Token、ColorSchemeManager 深浅色切换 - 全页面:AI 对话/反馈/回忆/薄弱点、知识库 CRUD、学习工作台、复习计划、学习分析、个人中心/设置 - 登录与引导:Sign in with Apple、AppSession 状态管理、引导流程、演示模式 - 本地持久化:FileCache + PersistenceController(学习任务/复习任务/学习记录) - 本地化:zh-Hans Localizable.strings ~120 条、ZXStrings 程序化引用、LanguageManager - 组件库:ZXTabBar/ZXBackHeader/ZXSTaskRow/ZXChartView/ZXTypingIndicator 等 22 个共享组件 - 等待名单:WaitlistView 邮箱收集表单 - 动效:ZXTypingIndicator AI 打字动画、ZXShimmerModifier 骨架屏 - 测试:StudyHomeViewModel/AIChatViewModel/ReviewPlanViewModel/FileCache 共 28 条 - Dynamic Type 支持 + 范围限制 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
36 KiB
缺失项与待补全方向
基于 v0.1 创业计划文档与当前 iOS 代码对比分析 整理时间:2026-05-10
本文档系统性列出知习 iOS App 当前在架构、页面、功能、设计等方面的缺失项,并给出优先级建议。
一、架构层缺失
1.1 MVVM 分层
现状:全部代码写在 SwiftUI View 中,无任何 ViewModel/ObservableObject/@Published。grep 搜索 ViewModel、ObservableObject、@Published 均为零结果。
缺失:
- 无 ViewModel 层,业务逻辑、状态管理、数据转换全部堆在 View 里
- 无 Model 层,数据结构通过 View 内的局部 struct 或硬编码数据隐式定义
- 代码不可测试,无法单独验证业务逻辑
计划要求(官网与技术基础.md 第 5.3 节):
AIStudyApp/
├── Features/
│ ├── Onboarding/
│ │ ├── Views/ ← 当前有,但无 ViewModel/Model 子目录
│ │ ├── ViewModels/ ← 缺失
│ │ └── Models/ ← 缺失
1.2 Service 层
现状:无任何 Service 类,AI 分析、学习记录、用户管理等概念没有对应的服务抽象。
缺失:
| Service | 职责 | 设计文档 |
|---|---|---|
| AuthService | Apple 登录、Token 管理、会话维护 | docs/AI对话.md(详细设计) |
| LearningService | 学习记录 CRUD、进度追踪 | — |
| AIService | AI 分析请求代理、结果解析 | — |
| ReviewService | 复习任务生成、调度 | — |
| KnowledgeService | 知识库/路径/课程查询 | — |
| FeedbackService | 用户反馈提交 | — |
Auth 模块已有详细文件清单(来自 docs/AI对话.md):
Features/Auth/Views/LoginView.swift
Features/Auth/ViewModels/LoginViewModel.swift
Core/Services/AuthService.swift
Core/Services/AuthServiceProtocol.swift
Core/Storage/KeychainStore.swift
Core/Storage/TokenStore.swift
App/AppSession.swift
Core/Models/AuthModels.swift
Core/Models/User.swift
1.3 Repository 层
现状:零数据持久化,所有"数据"均为 View 中硬编码的 mock。
缺失:
- 无数据访问抽象(未来可能切换 CoreData → API,需要 Repository 隔离)
- 无本地缓存层
- 无网络数据源层
1.4 网络层
现状:无任何网络请求代码,无 APIClient,无 URLSession 调用。
缺失:
- APIClient(封装 URLSession,注入 baseURL、header、token)
- APIEndpoint(枚举化 API 路径,统一请求构建)
- APIError(统一错误模型和处理)
- 请求/响应拦截器(日志、token 刷新)
- Mock 层(本地开发和 UI 预览用)
1.5 依赖注入
现状:无任何 DI 模式,Service 和 ViewModel 尚未创建,暂时不存在注入问题。但需要在架构搭建时建立模式。
建议:初期使用构造函数注入 + @EnvironmentObject,避免引入第三方 DI 框架。
二、核心能力缺失
2.1 Sign in with Apple(已有详细设计文档)
现状:LoginPage 有 UI(手机号/邮箱/微信/Apple 入口),但 AIStudyAppApp 仅用 @AppStorage("hasCompletedOnboarding") 控制是否进入主界面,无实际认证。
计划要求:第一版登录方式仅为 Sign in with Apple(Demo与MVP.md 第 5.2 节)。
详细设计方案见 docs/AI对话.md,核心结论:
登录页只保留一个入口:
Sign in with Apple
删除的入口:手机号登录、邮箱登录、微信登录、验证码登录
"跳过"按钮处理:
#if DEBUG
Button("跳过,进入演示模式") { ... }
#endif
正式环境不展示跳过入口,避免后续匿名用户迁移问题。
登录流程设计:
App 启动
↓
AppSession 检查 Keychain 是否有 refreshToken
↓
有 token → 调用 /auth/refresh 或 /users/me
↓
成功 → 进入主界面
失败 → 清空 token,进入登录页
↓
无 token → 进入登录页
↓
用户点击 Sign in with Apple
↓
获取 identityToken / authorizationCode / userIdentifier
↓
POST /api/auth/apple
↓
后端返回 accessToken / refreshToken / user
↓
token 存入 Keychain(不要 UserDefaults)
↓
判断 user.onboardingCompleted
↓
未完成 → 引导/目标设置
已完成 → 主界面
登录相关新增文件:
Features/Auth/Views/LoginView.swift # 仅 Apple 登录按钮
Features/Auth/ViewModels/LoginViewModel.swift # @MainActor, @Published isLoading/errorMessage
Core/Services/AuthService.swift
Core/Services/AuthServiceProtocol.swift
Core/Storage/KeychainStore.swift
Core/Storage/TokenStore.swift
App/AppSession.swift # 全局登录状态管理
Core/Models/AuthModels.swift # AppleLoginRequest, AuthResponse
Core/Models/User.swift
API Contract(Auth):
// 请求
struct AppleLoginRequest: Encodable {
let identityToken: String
let authorizationCode: String?
let userIdentifier: String
let fullName: AppleFullName?
let email: String?
}
// 响应
struct AuthResponse: Decodable {
let accessToken: String
let refreshToken: String
let expiresIn: Int
let user: User
}
关键约束:
- 不再用
@AppStorage("hasCompletedOnboarding")单独决定是否进入主界面 - 登录状态必须由
AppSession+ Keychain token 决定 - Token 不存 UserDefaults
- View 里不写网络请求,不直接处理 Apple 登录细节
2.2 后端 API 对接
现状:所有页面为静态 UI,无任何网络请求。
计划定义的 P0 API:
POST /ai/analyze-learning-input— AI 分析用户学习输入POST /ai/chat— AI 对话- 用户/知识库/学习记录/反馈 CRUD
2.3 真实 AI 集成
现状:AI 相关页面全为静态文本。
需对接:
- 后端 AI Provider 抽象层(MiniMax/DeepSeek/OpenAI 等)
- 结构化 JSON 输出解析
- AI 分析结果展示(掌握度评分、优缺点、建议)
- AI 对话流式响应
2.4 本地数据持久化
现状:零持久化实现。
需实现:
- UserDefaults / @AppStorage(简单偏好)
- Keychain(Token、敏感信息)
- 后续可考虑 CoreData 或 SwiftData(学习记录离线缓存)
2.5 多语言本地化
现状:所有文案硬编码在 View 中,无 Localizable.xcstrings 文件。
计划要求(Demo与MVP.md 第 6 节):
- 默认简体中文
- 预留英文
- App UI 文案使用本地化资源
需实现:
- 创建
Localizable.xcstrings - 将所有硬编码文案迁移为
LocalizedStringKey - 支持语言切换
2.6 错误/加载/空状态处理
现状:无任何错误处理、加载态、空状态 UI。
至少需要:
- 网络请求 loading 指示器
- 网络错误提示和重试按钮
- AI 分析中的等待状态
- 列表空状态(如知识库为空时的引导)
- 登录失败错误提示
三、页面层面差距
3.1 与计划页面对比
| 计划页面 | 计划优先级 | 当前状态 | 说明 |
|---|---|---|---|
| 启动页/欢迎页 | P1 | ✅ 已实现 | SplashPage + WelcomePage |
| 登录页 | P0 | ✅ 已实现 | 新 LoginView 替换旧 LoginPage,仅 Apple 登录 |
| 语言与偏好页 | P1 | ❌ 未实现 | 无页面 |
| 学习方向选择页 | P0 | ⚠️ 部分实现 | GoalSetupPage 有目标选择,但非学习方向选择 |
| 学习路径页 | P0 | ✅ 已实现 | LibraryDetailPage |
| 今日学习任务页 | P0 | ✅ 已实现 | StudyHomeView |
| 内容阅读页 | P0 | ✅ 已实现 | KnowledgeDetailPage |
| 主动回忆/笔记输入页 | P0 | ✅ 已实现 | DailyThinkingPage + RecallTestPage |
| AI 分析结果页 | P0 | ✅ 已实现 | AIFeedbackPage |
| AI 对话页 | P0 | ✅ 已实现 | AIChatPage |
| 复习计划页 | P0 | ✅ 已实现 | ReviewPlanView,含今天/明天/本周分组 + 复习类型标签 |
| 学习进度页 | P1 | ✅ 已实现 | AnalysisHomeView |
| 设置页 | P1 | ⚠️ 部分实现 | ProfileView 有设置菜单,但功能入口为空 |
| 反馈页 | P1 | ✅ 已实现 | FeedbackView + FeedbackViewModel,分类选择 + 提交确认 |
3.2 复习计划页(P0 ✅)
计划描述:系统生成复习任务,用户查看待复习内容,按推荐时间安排学习。
当前:已实现 Features/Review/ReviewPlanView.swift,含今天/明天/本周三组、复习类型标签(间隔重复/费曼/回忆/薄弱)、完成勾选、播放按钮。数据层使用 ReviewTask Model + mock 数据。
待对接:接入 ReviewService 后端数据。
3.3 反馈页(P1 ✅)
计划描述:App 内反馈入口,让内测用户提交问题和建议。
当前:已实现 Features/Feedback/FeedbackView.swift + FeedbackViewModel.swift,含 4 类反馈分类(Bug/功能建议/内容问题/其他)的图标选择器、文本描述输入、提交通知。入口位于 ProfileView 菜单末项。
待对接:接入后端 /feedback API(FeedbackService)。
3.4 等待名单入口
计划:官网 /waitlist 页面收集用户,App 内也需要引导用户加入等待名单/申请内测。
需考虑:是否在 App 内嵌等待名单入口(如 Welcome 页或设置页)。
四、设计与交互差距
4.1 Tab 结构调整
计划设计:4 个 Tab — 学习 | 知识库 | AI助手 | 我的
当前实现:5 个 Tab — AI | 知识库 | 学习 | 分析 | 我的
差异分析:
- 当前把"学习"和"分析"拆成了两个独立 Tab
- 计划把"AI助手"独立为一个 Tab,当前 AI 已是独立 Tab
- "分析"在计划中属于"学习"Tab 下的子页面,不需要顶层 Tab
建议(两种方案):
- 方案 A:完全对齐计划 → 合并学习和分析为一个 Tab,保持 4 Tab
- 方案 B:保留 5 Tab 结构 → 更新计划文档,论证"分析"独立为 Tab 的合理性(学习数据可视化、学习进度监控是独立价值)
4.2 登录流程简化(已有详细设计)
计划要求:仅 Sign in with Apple,不做手机号/邮箱/微信登录。
当前 UI:包含 4 种登录方式入口。
最终方案(详见 docs/AI对话.md):
- 登录页只保留 Sign in with Apple 一个按钮
- 删除手机号、邮箱、微信、验证码登录
- 跳过按钮仅限
#if DEBUG,Release 不展示 - 登录页文案极简:品牌 + 一句话价值主张 + Apple 登录按钮 + 协议入口
4.3 浅色/深色模式双主题(P1 新增)✅ 已完成
实现方式:
| 改造项 | 状态 |
|---|---|
Color(light:dark:) 自适应 helper |
✅ DesignTokens.swift 新增基于 UITraitCollection.userInterfaceStyle 的动态颜色 |
| 28 个颜色 token 双主题化 | ✅ 背景(4) + 文字(9) + 边框(5) + 填充(6) + 品牌色/彩色半透不变 |
| 渐变自适应 | ✅ page/splash 渐变改用自适应 Color token |
| 硬编码色值替换 | ✅ 13 处内联 Color(hex:) 替换为自适应 token |
| ColorSchemeManager | ✅ Core/Appearance/ColorSchemeManager.swift,@AppStorage 持久化,支持系统/浅色/深色 |
| 移除强制深色 | ✅ 移除 4 处 .preferredColorScheme(.dark) |
| 设置页切换入口 | ✅ ProfileView 外观行改为可点击,confirmationDialog 三选一 |
文件变更:
- 新增:
Core/Appearance/ColorSchemeManager.swift - 修改:
DesignTokens.swift(颜色全量自适应 + 渐变) - 修改:
AIStudyAppApp.swift(根视图用 manager 控制 scheme) - 修改:
ContentView.swift、LoginView.swift(移除强制暗黑) - 修改:
ProfileView.swift(外观切换入口) - 修改:
SplashPage.swift、ZXTabBar.swift、ZXAIInteractionRow.swift、ZXChartView.swift、AIFeedbackPage.swift、WelcomePage.swift、OnboardingPage.swift、GoalSetupPage.swift、ZXSTaskRow.swift、ZXIconBtn.swift(内联色值→token)
4.4 语言系统(P1 新增)
现状:所有文案硬编码中文在 SwiftUI View 中,无 Localizable.strings。
要求:先只支持中文,但搭建好本地化基础设施,后续加语言时只需加翻译文件。
需实现:
| 步骤 | 说明 |
|---|---|
创建 Localizable.strings (Base) |
中文作为 Base 语言,不设 zh-Hans |
封装 ZXLocalized 辅助 |
String(localized:) + Text("key") 的 SwiftUI 原生方式 |
| 迁移硬编码文案 | 逐文件将文案替换为 LocalizedStringKey |
| 设置页语言入口 | 预留语言切换 UI,当前仅显示"中文" |
4.5 无障碍
现状:未考虑 VoiceOver、Dynamic Type、高对比度等无障碍需求。
至少需做:
- 关键按钮添加
.accessibilityLabel - 确保 Dynamic Type 下布局不破碎
- 重点页面 VoiceOver 测试
4.6 样式规范
已于 docs/样式规范.md 中梳理完整的样式规范文档,涵盖:
- 色彩系统(背景/文字/品牌语义色/边框/填充)
- 渐变体系(页面、品牌、卡片、进度条、CTA 等 11 组渐变)
- 圆角、间距、尺寸 token
- 字体层级(12 级定义)
- 共享组件目录(20+ 组件,含导航、按钮、卡片行、数据展示、输入、标签等)
- 页面布局模式(主 Tab 页、子页面、卡片、输入框、状态标签的标准写法)
- 设计决策与约束
后续新页面必须遵循 docs/样式规范.md,复用已有组件和 token,禁止随意使用内联颜色/间距/字体。
4.7 动效
计划要求(官网与技术基础.md 第 6.3 节):
- P0:页面过渡、按钮反馈、加载状态、AI 分析中状态、学习完成反馈
- P1:今日任务卡片动效、进度条更新、AI 结果分块出现
当前:仅有基础 SwiftUI 隐式动画(withAnimation),未实现任何计划中的动效。
五、数据层缺失
5.1 Model 定义
现状:所有数据通过 View 内局部变量或硬编码定义,无独立 Model 文件。
计划中定义的核心实体(Demo与MVP.md):
User
├── id, appleUserId, displayName, email
├── preferredLanguage, createdAt, lastLoginAt, status
KnowledgeBase
├── id, title, description, language, targetUser
├── createdAt, updatedAt
LearningPath
├── id, knowledgeBaseId, title, description
├── estimatedDays, order
Lesson
├── id, pathId, title, content, objectives
├── keyPoints, recallQuestions, practicePrompt
├── order, estimatedMinutes
LearningSession
├── id, userId, lessonId
├── startedAt, endedAt, userInput
├── aiAnalysis, masteryScore, weakPoints
├── nextSuggestion, reviewAt
AIAnalysis
├── id, userId, sessionId
├── inputText, outputJson, masteryScore
├── weakPoints, suggestions
├── modelName, createdAt, costEstimate
ReviewTask
├── id, userId, lessonId, sourceSessionId
├── reviewType, scheduledAt, completedAt, status
Feedback
UserLearningProfile
需实现:在 Features/*/Models/ 下创建对应的 Swift struct(需 Codable、Identifiable)。
5.2 API Contract
现状:无 API 类型定义。
建议:参考计划中定义的 JSON 结构,先创建 Swift Model,再定义 API 请求/响应类型(Request/Response struct),实现前后端类型同构。
5.3 数据流规范
现状:View 直接持有 @State,无数据流管理。
建议:
- ViewModel 持有 @Published 状态
- ViewModel 通过 Service 获取数据
- Service 通过 Repository 访问数据源
- View 通过 @StateObject / @ObservedObject 绑定 ViewModel
六、工程化缺失
6.1 大文件拆分 ✅
已完成:
| 原文件 | 拆分结果 |
|---|---|
AIStudyAppApp.swift (~190行) |
保留 App/Root/OnboardingFlowView,页面移入 Features/Onboarding/ |
DailyThinkingPage.swift (~200行) |
拆为 5 个文件(DailyThinking / RecallTest / WeakPoints / AIFeedback / AIChat) |
LibrarySubpages.swift (~112行) |
已删除,拆为 6 个独立页面文件 |
6.2 共享组件管理 ✅
已完成:20 个共享组件集中到 Shared/Components/,原文件中的定义已移除。
Shared/Components/
├── ZXTabBar.swift ├── ZXBackHeader.swift
├── ZXIconBtn.swift ├── ZXScoreBox.swift
├── ZXAIInputBar.swift ├── ZXOutlineBtn.swift
├── ZXQuickAction.swift ├── ZXAIInteractionRow.swift
├── ZXCardRow.swift ├── ZXChip.swift
├── ZXImportOption.swift ├── ZXWeakRow.swift
├── ZXStatBadge.swift ├── ZXProfileStat.swift
├── ZXProfileMenuRow.swift ├── ZXAchievementBadge.swift
├── ZXChartView.swift ├── ZXSTaskRow.swift
├── FeatureRow.swift ├── ReviewTaskRow.swift
├── ZXLoadingView.swift ├── ZXErrorView.swift
└── ZXEmptyView.swift
6.3 测试
现状:无任何测试代码。
至少需要:
- ViewModel 单元测试(当 ViewModel 创建后)
- Service 层单元测试(Mock Repository)
- 关键 UI 流程的 Snapshot 测试
6.4 CI/CD
现状:无。
建议(后续):
- GitHub Actions / Xcode Cloud 自动构建
- TestFlight 自动分发
6.5 崩溃监控与埋点
现状:无。
建议:接入 Firebase Crashlytics 或类似服务,至少在 TestFlight 阶段要有崩溃收集能力。
七、优先级建议
P0 — 必须在接后端前完成
| 步骤 | 项目 | 理由 | 设计文档 |
|---|---|---|---|
| ① | 创建 Auth Model(AuthModels + User) | 所有后续步骤的数据基础 | 第十章 步骤 1 ✅ |
| ② | 实现 Keychain 存储层 | Token 安全存储是登录的前提 | 第十章 步骤 2 ✅ |
| ③ | 搭建网络层最小实现(APIClient) | 所有后端交互的唯一通道 | 第十章 步骤 3 ✅ |
| ④ | 实现 AuthService(Apple 登录 + 后端调用) | 用户身份是学习记录的前提 | docs/AI对话.md、第十章 步骤 4 ✅ |
| ⑤ | 实现 AppSession(全局登录状态) | 统一的登录态管理 | 第十章 步骤 5 ✅ |
| ⑥ | 实现 LoginView + LoginViewModel | 替换当前过度实现的登录页 | docs/AI对话.md、第十章 步骤 6 ✅ |
| ⑦ | 改造 App 入口启动逻辑 | Token 分流替换 @AppStorage | 第十章 步骤 7 ✅ |
| — | 创建 Model 层(其余数据实体) | 是 Service/ViewModel/API 的基础 | 本文档 5.1 节 ✅ |
| — | 实现复习计划独立页 | 计划标记 P0 | ✅ |
| — | 拆分大文件 + 集中共享组件 | 降低后续修改的认知负担 | 本文档 6.1/6.2 节 ✅ |
| — | 添加加载/错误/空状态处理 | 真机使用的基本体验保障 | Shared/Components/ (ZXLoadingView, ZXErrorView, ZXEmptyView) ✅ |
P1 — 与后端对接同步推进
| 优先级 | 项目 | 理由 |
|---|---|---|
| P1 | 浅色/深色模式双主题 | 覆盖所有页面和 DesignTokens,工作量较大 ✅ |
| P1 | 语言系统搭建(中文 Base) | 先建基础设施,后续加语言不返工 ✅ |
| P1 | 搭建 ViewModel 层(逐步迁移) | 架构分层,但不阻塞功能开发 ✅ |
| P1 | 搭建 Service 层 | 随 API 对接自然建立 ✅ |
| P1 | 实现反馈页 | TestFlight 内测必须 ✅ |
| P1 | 实现设置页完整功能 | 外观切换、语言入口、复习提醒等 ✅ |
P2 — App Store 前完成
| 优先级 | 项目 | 理由 |
|---|---|---|
| P2 | Repository 层 | 当需要本地缓存 + 网络切换时再做 ✅ |
| P2 | 动效补充 | 体验优化,不阻塞功能 ✅ |
| P2 | 无障碍适配 | App Store 审核加分项 ✅ |
| P2 | 测试 | 用户量增长后需要 ✅ |
| P2 | Tab 结构调整决策 | 需要更多用户反馈来决策 ✅ |
Tab 结构分析
当前 5-Tab 结构:
| Tab | 页面 | 核心功能 |
|---|---|---|
| AI | AIHomeView | AI 对话入口 + 每日思考 + 薄弱点 |
| 知识库 | LibraryHomeView | 知识库浏览 + 导入 + 搜索 |
| 学习 | StudyHomeView | 今日任务 + 进度 + 每周活跃 |
| 分析 | AnalysisHomeView | 学习时长 + 掌握度 + 雷达图 |
| 我的 | ProfileView | 个人信息 + 设置 + 成就 |
问题诊断:
- AI 与学习边界模糊 — AI 对话产生学习记录,但学习任务在独立 Tab,用户需要在两个 Tab 间切换
- 分析 Tab 内容单薄 — 纯展示仪表盘,无交互深度,与"学习"Tab 的进度卡片有重叠
- 知识库入口过重 — 知识库本质是学习的前置步骤,独立 Tab 使其脱离学习流程
可选方案:
| 方案 | 结构 | 优点 | 缺点 |
|---|---|---|---|
| A: 保持现状 | 5 Tab 不变 | 无改动成本 | 上述问题持续 |
| B: 合并 AI+学习 | 4 Tab(学习/AI、知识库、分析、我的) | AI 与学习一体化 | 需重设计学习首页 |
| C: 合并分析入学习 | 4 Tab(AI、知识库、学习+分析、我的) | 分析数据有上下文 | 学习页信息密度增加 |
| D: 精简 3 Tab | 3 Tab(学习、知识库、我的) | 最简洁,AI 内嵌学习 | 分析页降级为次级入口 |
建议:MVP 阶段保持方案 A,收集用户反馈后优先尝试方案 C(分析并入学习)。触发条件:分析 Tab 的周活跃用户 < 20%。
八、总结
当前 iOS 项目完成了 UI 层的全量搭建(21 页),但缺少"能让产品真正运转"的架构底座和数据能力。核心矛盾是:
UI 超前,架构滞后。页面能点,但无数据、无认证、无 AI、无服务。
当前进度(2026-05-10)
P0 — 全部完成 ✅:
- ✅ Apple 登录 + Auth 体系(9 个文件)
- ✅ Model 层(10 个数据实体)
- ✅ 网络层最小实现(APIClient + Endpoint + Error)
- ✅ App 入口重构(AppSession 驱动路由,替代 @AppStorage)
- ✅ 复习计划独立页(ReviewPlanView)
- ✅ 大文件拆分(3 个大文件拆为 15+ 个独立文件)
- ✅ 共享组件集中管理(
Shared/Components/下 20+ 个组件) - ✅ 加载/错误/空状态处理(ZXLoadingView / ZXErrorView / ZXEmptyView)
P1 — 部分完成:
- ✅ 浅色/深色模式双主题(DesignTokens 自适应 + ColorSchemeManager + 移除强制暗黑)
- ✅ 语言系统搭建(中文 Base,Localizable.strings + ZXStrings + LanguageManager)
- ✅ 搭建 ViewModel 层(ReviewPlanViewModel + AIChatViewModel + StudyHomeViewModel)
- ✅ 搭建 Service 层(5 个 Service 协议 + 实现 + 20 个 APIEndpoint)
- ✅ 实现反馈页(TestFlight 内测必须)
- ✅ 设置页外观切换(ColorSchemeManager + ProfileView confirmationDialog)
- ✅ 设置页完整功能(5 个子页面 + 外观/语言切换)
接下来推荐顺序:
1. 语言系统搭建(基础设施优先,避免后续返工)
├── 创建 Localizable.strings (Base = 中文)
├── 迁移硬编码文案到 LocalizedStringKey
└── 设置页预留语言入口(当前仅显示中文)
↓
2. 浅色/深色模式双主题(体验升级,需全量回归)
├── DesignTokens 定义 light/dark 双套色值
├── 替换所有内联 Color(hex:) 为 token 引用
├── 设置页新增外观切换(跟随系统 / 浅色 / 深色)
└── 全页面浅色模式验证
↓
3. 反馈页 + 设置页补全(独立页面,不依赖其他改造)
↓
4. ViewModel 层迁移(逐步,不阻塞功能)
↓
5. Service 层搭建(随 API 对接自然建立)
九、AI对话.md 登录方案摘要
docs/AI对话.md是登录模块的详细实现规范。以下为关键决策的结构化提取,便于对照实施。
9.1 登录入口决策
第一版只保留:
Sign in with Apple
删除这些入口:
- 手机号登录 / 邮箱登录 / 微信登录 / 验证码登录
"跳过"按钮:
#if DEBUG保留,Release 不展示- 理由:避免匿名用户后续迁移(学习记录、AI 分析绑定用户身份)
9.2 登录页内容
知习
更懂你,更会学。
用 AI 把知识库、主动回忆和间隔复习连接起来,
从"看过"走向"真正学会"。
[ Sign in with Apple ]
登录即代表你同意《用户服务协议》和《隐私政策》
9.3 完整登录流程
App 启动
↓
AppSession 检查 Keychain 是否有 refreshToken
↓
有 token → 调用 /auth/refresh 或 /users/me
├─ 成功 → 进入主界面
└─ 失败 → 清空 token,进入登录页
↓
无 token → 进入登录页
↓
用户点击 Sign in with Apple
↓
ASAuthorizationController 获取:
· identityToken
· authorizationCode
· userIdentifier
· email / fullName(Apple 可能不返回)
↓
POST /api/auth/apple
↓
后端返回 { accessToken, refreshToken, expiresIn, user }
↓
accessToken / refreshToken 存入 Keychain
↓
判断 user.onboardingCompleted
├─ false → 引导页 / 学习目标设置
└─ true → 主界面(ContentView)
9.4 需要新增的 9 个文件
| 层 | 文件 | 职责 |
|---|---|---|
| Model | Core/Models/AuthModels.swift |
AppleLoginRequest、AuthResponse 等 Codable struct |
| Model | Core/Models/User.swift |
用户实体 |
| Storage | Core/Storage/KeychainStore.swift |
通用 Keychain 读写封装 |
| Storage | Core/Storage/TokenStore.swift |
Token 专用存取(save/load/clear) |
| Service | Core/Services/AuthServiceProtocol.swift |
AuthService 协议定义 |
| Service | Core/Services/AuthService.swift |
ASAuthorizationController 集成 + 后端调用 |
| App | App/AppSession.swift |
@MainActor 全局登录状态 |
| View | Features/Auth/Views/LoginView.swift |
纯 Apple 登录按钮 UI |
| ViewModel | Features/Auth/ViewModels/LoginViewModel.swift |
@Published isLoading/errorMessage |
9.5 API Contract
// 请求 → POST /api/auth/apple
struct AppleLoginRequest: Encodable {
let identityToken: String
let authorizationCode: String?
let userIdentifier: String
let fullName: AppleFullName?
let email: String?
}
struct AppleFullName: Encodable {
let givenName: String?
let familyName: String?
}
// 响应
struct AuthResponse: Decodable {
let accessToken: String
let refreshToken: String
let expiresIn: Int
let user: User
}
9.6 关键约束
| 约束 | 说明 |
|---|---|
| Token 存 Keychain | 不存 UserDefaults |
| 不用 @AppStorage 控制登录 | 登录状态由 AppSession + Keychain token 决定 |
| View 不写网络请求 | 网络调用在 Service 层 |
| View 不处理 Apple 登录细节 | ASAuthorizationController 逻辑在 AuthService |
| 不改变现有主页面 UI | 只替换入口路由逻辑 |
十、登录模块可行实施计划
以下是按依赖关系排列的 7 个实施步骤。每步独立可验证,后一步依赖前一步完成。
整体依赖图
步骤1: Model ─────────────────────────────────────────┐
↓ │
步骤2: KeychainStore / TokenStore ────────────────────┤
↓ │
步骤4: APIClient / APIEndpoint ─┐ │
↓ │ │
步骤3: AuthService ←────────────┘ │
↓ │
步骤5: AppSession ─────────────────────────────────────┤
↓ │
步骤6: LoginView + LoginViewModel ────────────────────┘
↓
步骤7: 改造 App 入口启动逻辑(AIStudyAppApp.swift)
步骤 1:创建 Auth Model ✅ 已完成(2026-05-10)
产出文件:
Core/Models/AuthModels.swiftCore/Models/User.swift
内容:
// AuthModels.swift
struct AppleLoginRequest: Encodable { ... }
struct AppleFullName: Encodable { ... }
struct AuthResponse: Decodable { ... }
// User.swift
struct User: Codable, Identifiable {
let id: String
let appleUserId: String
let displayName: String?
let email: String?
let preferredLanguage: String
let onboardingCompleted: Bool
let createdAt: String
let lastLoginAt: String?
let status: String
}
依赖:无
验证:Xcode 编译通过
步骤 2:实现 Keychain 存储层 ✅ 已完成(2026-05-10)
产出文件:
Core/Storage/KeychainStore.swiftCore/Storage/TokenStore.swift
KeychainStore 职责:通用 Keychain 读写,封装 SecItemAdd/SecItemCopyMatching/SecItemDelete,支持 save/load/delete 操作。
TokenStore 职责:
protocol TokenStoreProtocol {
func saveAccessToken(_ token: String) throws
func getAccessToken() throws -> String?
func saveRefreshToken(_ token: String) throws
func getRefreshToken() throws -> String?
func clearAll() throws
}
依赖:步骤 1(Model 定义,具体来说不需要 Model 依赖,TokenStore 操作的是原始 String)
验证:可写简单单元测试验证存取清除
步骤 3:搭建网络层最小实现 ✅ 已完成(2026-05-10)
注意:这一步和 AuthService 互相依赖——AuthService 需要 APIClient 发请求,但可以先建网络层骨架。做的时候步骤 3 和 4 可以部分并行:先建 APIClient 基础,再写 AuthService 时补充 Auth 相关 endpoint。
产出文件:
Core/Network/APIClient.swiftCore/Network/APIEndpoint.swiftCore/Network/APIError.swift
最小接口:
// APIClient
class APIClient {
init(baseURL: URL, tokenStore: TokenStoreProtocol?)
func request<T: Decodable>(_ endpoint: APIEndpoint) async throws -> T
func requestVoid(_ endpoint: APIEndpoint) async throws
}
// APIEndpoint
enum APIEndpoint {
case appleLogin(AppleLoginRequest)
case refreshToken(String)
case me
// 后续扩展其他 endpoint
}
// APIError
enum APIError: Error {
case network(Error)
case httpError(Int)
case decoding(Error)
case unauthorized
}
依赖:步骤 1(Model)、步骤 2(TokenStore)
验证:Xcode 编译通过,可以先 mock 一个请求验证 pipeline 跑通
步骤 4:实现 AuthService ✅ 已完成(2026-05-10)
产出文件:
Core/Services/AuthServiceProtocol.swiftCore/Services/AuthService.swift
AuthServiceProtocol:
protocol AuthServiceProtocol {
func loginWithApple() async throws -> AuthResponse
func refreshSession() async throws -> AuthResponse
func logout() async throws
func fetchCurrentUser() async throws -> User
}
AuthService 实现要点:
- 集成
ASAuthorizationController(需import AuthenticationServices) - 获取 identityToken、authorizationCode、userIdentifier
- 调用
APIClient.request(.appleLogin(request)) - 将返回的 token 写入 TokenStore
- refreshSession:用 refreshToken 换新 token
依赖:步骤 1(AuthModels)、步骤 2(TokenStore)、步骤 3(APIClient)
验证:Xcode 编译通过,可在模拟器点击 Apple 登录(后端未就绪时用 mock)
步骤 5:实现 AppSession ✅ 已完成(2026-05-10)
产出文件:
App/AppSession.swift
关键代码骨架:
@MainActor
final class AppSession: ObservableObject {
@Published var currentUser: User?
@Published var isAuthenticated = false
@Published var isLoading = true
@Published var authError: String?
private let authService: AuthServiceProtocol
private let tokenStore: TokenStoreProtocol
func bootstrap() async {
// 1. 检查 Keychain 是否有 refreshToken
// 2. 有 → 调用 refreshSession()
// 3. 成功 → isAuthenticated=true, currentUser=user
// 4. 失败 → 清空 token, isAuthenticated=false
// 5. 无 → isAuthenticated=false
// 6. isLoading = false
}
func loginWithApple() async { ... }
func logout() { ... }
}
依赖:步骤 4(AuthService)、步骤 2(TokenStore)
验证:模拟器启动时可根据 Keychain 状态正确分流
步骤 6:实现 LoginView + LoginViewModel ✅ 已完成(2026-05-10)
产出文件:
Features/Auth/Views/LoginView.swiftFeatures/Auth/ViewModels/LoginViewModel.swift
LoginView:
- 品牌标题"知习" + 副标题
- Sign in with Apple 按钮(ASAuthorizationAppleIDButton)
- Loading 状态(ProgressView)
- Error 提示
#if DEBUG跳过按钮- 协议入口链接
LoginViewModel:
@MainActor
final class LoginViewModel: ObservableObject {
@Published var isLoading = false
@Published var errorMessage: String?
func loginWithApple() async {
isLoading = true
errorMessage = nil
do {
try await appSession.loginWithApple()
} catch {
errorMessage = "登录失败:\(error.localizedDescription)"
isLoading = false
}
}
}
依赖:步骤 5(AppSession)
验证:模拟器显示登录页,点击 Apple 登录按钮触发流程,loading 状态可展示
步骤 7:改造 App 入口启动逻辑 ✅ 已完成(2026-05-10)
修改文件:AIStudyAppApp.swift
改动要点:
- 注入
AppSession为@StateObject - 启动时调用
appSession.bootstrap() - 用
appSession.isLoading / isAuthenticated / currentUser?.onboardingCompleted替换原来的@AppStorage("hasCompletedOnboarding") - 路由逻辑:
isLoading → Splash(启动加载中) !isAuthenticated → LoginView onboardingCompleted == false → OnboardingFlowView onboardingCompleted == true → ContentView
依赖:步骤 1-6 全部完成
验证:完整启动流程测试——
- 首次启动 → 登录页 → Apple 登录(mock) → 引导页 → 目标设置 → 主界面
- 二次启动(有有效 token)→ 直接进主界面
- Token 过期 → 登录页
实施俯视图
步骤 1 ──→ 步骤 2 ──→ 步骤 3 ──→ 步骤 4 ──→ 步骤 5 ──→ 步骤 6 ──→ 步骤 7
Model Keychain 网络层 AuthSvc AppSession LoginUI 入口改造
(0依赖) (无依赖) (依赖1,2) (依赖1-3) (依赖2,4) (依赖5) (依赖1-6)
每一步均可独立编译验证。后端 API 未就绪时,步骤 4-7 可以用 mock 数据先行开发,后端就绪后仅替换 APIClient 的真实 endpoint。
文件目录结构(完成后)
AIStudyApp/
├── App/
│ └── AppSession.swift ← 新增
├── Core/
│ ├── Models/
│ │ ├── AuthModels.swift ← 新增
│ │ └── User.swift ← 新增
│ ├── Network/
│ │ ├── APIClient.swift ← 新增
│ │ ├── APIEndpoint.swift ← 新增
│ │ └── APIError.swift ← 新增
│ ├── Services/
│ │ ├── AuthServiceProtocol.swift ← 新增
│ │ └── AuthService.swift ← 新增
│ ├── Storage/
│ │ ├── KeychainStore.swift ← 新增
│ │ └── TokenStore.swift ← 新增
│ └── DesignSystem/
│ └── DesignTokens.swift (已有,不变)
├── Features/
│ ├── Auth/
│ │ ├── Views/
│ │ │ └── LoginView.swift ← 新增
│ │ └── ViewModels/
│ │ └── LoginViewModel.swift ← 新增
│ ├── AI/ (已有)
│ ├── Library/ (已有)
│ ├── Study/ (已有)
│ ├── Analysis/ (已有)
│ └── Profile/ (已有)
└── AIStudyAppApp.swift ← 修改