WangDL 7066200b7b feat: MVVM 架构、全套 UI 页面、浅深色主题、本地持久化、等待名单、AI 动效
- 架构层: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>
2026-05-10 22:22:50 +08:00

669 lines
19 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.

# 缺失项与待补全方向
> 基于 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 管理、会话维护 | User |
| LearningService | 学习记录 CRUD、进度追踪 | LearningSession |
| AIService | AI 分析请求代理、结果解析 | AIAnalysis |
| ReviewService | 复习任务生成、调度 | ReviewTask |
| KnowledgeService | 知识库/路径/课程查询 | KnowledgeBase, LearningPath, Lesson |
| FeedbackService | 用户反馈提交 | Feedback |
### 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 节)。
**需实现**
- ASAuthorizationController 集成
- 获取 appleUserId、email、displayName
- 后端验证 identityToken
- Token 本地安全存储Keychain
- 登录状态管理
### 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(简单偏好)
- KeychainToken、敏感信息
- 后续可考虑 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 | ⚠️ 过度实现 | UI 包含计划不做的手机号/邮箱/微信登录 |
| 语言与偏好页 | P1 | ❌ 未实现 | 无页面 |
| 学习方向选择页 | P0 | ⚠️ 部分实现 | GoalSetupPage 有目标选择,但非学习方向选择 |
| 学习路径页 | P0 | ✅ 已实现 | LibraryDetailPage |
| 今日学习任务页 | P0 | ✅ 已实现 | StudyHomeView |
| 内容阅读页 | P0 | ✅ 已实现 | KnowledgeDetailPage |
| 主动回忆/笔记输入页 | P0 | ✅ 已实现 | DailyThinkingPage + RecallTestPage |
| AI 分析结果页 | P0 | ✅ 已实现 | AIFeedbackPage |
| AI 对话页 | P0 | ✅ 已实现 | AIChatPage |
| 复习计划页 | P0 | ❌ 未独立 | 仅在 StudyHomeView 任务列表中混合出现 |
| 学习进度页 | P1 | ✅ 已实现 | AnalysisHomeView |
| 设置页 | P1 | ⚠️ 部分实现 | ProfileView 有设置菜单,但功能入口为空 |
| 反馈页 | P1 | ❌ 未实现 | 无反馈收集入口 |
### 3.2 复习计划页P0 缺失)
**计划描述**:系统生成复习任务,用户查看待复习内容,按推荐时间安排学习。
**当前**:复习任务混在 StudyHomeView 的任务列表里(如"高数 - 间隔复习 8 题"),缺少独立的复习计划视图。
**需实现**
- 独立的 `ReviewPlanView`
- 按时间线展示待复习任务(今天、明天、本周)
- 复习项来源标注(哪个知识点的第几次复习)
- 复习完成状态追踪
### 3.3 反馈页P1 缺失)
**计划描述**App 内反馈入口,让内测用户提交问题和建议。
**需实现**
- 简洁的反馈表单(文本输入 + 分类选择)
- 提交到后端 `/feedback` 接口
- 确认提交状态页
### 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 种登录方式入口。
**建议**:第一版简化为仅 Apple 登录按钮 + 跳过选项,移除手机号/邮箱/微信登录 UI。
### 4.3 深色模式
**现状**:所有页面用 `.preferredColorScheme(.dark)` 强制深色,未验证浅色模式。
**建议**:确认是否需要支持浅色模式。如果只做深色,在 DesignTokens 中声明 `colorScheme: .dark`
### 4.4 无障碍
**现状**:未考虑 VoiceOver、Dynamic Type、高对比度等无障碍需求。
**至少需做**
- 关键按钮添加 `.accessibilityLabel`
- 确保 Dynamic Type 下布局不破碎
- 重点页面 VoiceOver 测试
### 4.5 动效
**计划要求**`官网与技术基础.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` | ~187 | 5 个完整页面 + 多个子组件 |
| `DailyThinkingPage.swift` | ~200+ | 5 个页面 + 共享组件 |
| `LibrarySubpages.swift` | ~150+ | 6 个页面 + 组件 |
**建议**:每个页面一个文件,共享组件移到 `Shared/Components/`
### 6.2 共享组件管理
**现状**`ZXTabBar` 在 ContentView.swift`ZXBackHeader` 在 DailyThinkingPage.swift`ZXCardRow` 在 LibrarySubpages.swift散落各处。
**建议**:集中到 `Shared/Components/`,建立组件目录如:
```
Shared/Components/
├── ZXTabBar.swift
├── ZXBackHeader.swift
├── ZXAIInputBar.swift
├── ZXScoreBox.swift
├── ZXIconBtn.swift
├── ZXCardRow.swift
├── ZXChip.swift
├── ZXQuickAction.swift
└── ZXStatBadge.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 — 必须在接后端前完成
| 优先级 | 项目 | 理由 |
|--------|------|------|
| P0 | 创建 Model 层(所有数据实体) | 是 Service/ViewModel/API 的基础 |
| P0 | 创建 API Contract 类型定义 | 前后端对齐的前提 |
| P0 | 搭建 APIClient + APIEndpoint | 所有后端交互的唯一通道 |
| P0 | 实现 AuthService + Apple 登录 | 用户身份是学习记录的前提 |
| P0 | 简化登录页为纯 Apple 登录 | 对齐计划,减少不必要 UI |
| P0 | 实现复习计划独立页 | 计划标记 P0当前缺失 |
| P0 | 拆分大文件 | 降低后续修改的认知负担 |
| P0 | 集中共享组件 | 避免组件散落导致重复开发 |
| P0 | 添加加载/错误/空状态处理 | 真机使用的基本体验保障 |
### P1 — 与后端对接同步推进
| 优先级 | 项目 | 理由 |
|--------|------|------|
| P1 | 搭建 ViewModel 层(逐步迁移) | 架构分层,但不阻塞功能开发 |
| P1 | 搭建 Service 层 | 随 API 对接自然建立 |
| P1 | 实现本地化架构 | 越晚做返工越多 |
| P1 | 实现反馈页 | TestFlight 内测必须 |
| P1 | 实现 Keychain 存储 | Token 安全存储 |
### P2 — App Store 前完成
| 优先级 | 项目 | 理由 |
|--------|------|------|
| P2 | Repository 层 | 当需要本地缓存 + 网络切换时再做 |
| P2 | 动效补充 | 体验优化,不阻塞功能 |
| P2 | 无障碍适配 | App Store 审核加分项 |
| P2 | 测试 | 用户量增长后需要 |
| P2 | Tab 结构调整决策 | 需要更多用户反馈来决策 |
---
## 八、总结
当前 iOS 项目完成了 UI 层的全量搭建21 页),但缺少"能让产品真正运转"的架构底座和数据能力。核心矛盾是:
> UI 超前,架构滞后。页面能点,但无数据、无认证、无 AI、无服务。
接下来最合理的推进顺序:
```
1. 建 Model 层(夯实数据基础)
2. 搭网络层 + Apple 登录(接通身份和通信)
3. 后端 API 逐个对接AI 分析和学习记录最先)
4. 重构 ViewModel 层(顺带解决架构问题)
5. 补充缺失页面(复习计划、反馈)
6. 本地化 + 动效 + 测试(打磨)
```
一句话:**先把骨架搭对,再长肉。**
对,**登录第一版只保留苹果登录**,这是最正确的决策。
你整理的文档里也已经明确写了:当前 `LoginPage` 虽然有手机号、邮箱、微信、Apple 入口,但第一版计划要求只做 Sign in with Apple并且需要实现 Apple 授权、后端验证、Keychain 存 token 和登录状态管理。
文档后面也建议把登录页简化为 **仅 Apple 登录按钮 + 可选跳过入口**,移除手机号、邮箱、微信登录 UI。
---
# 登录页第一版最终方案
## 正式入口只保留
```text
Sign in with Apple
```
## 删除这些入口
```text
手机号登录
邮箱登录
微信登录
验证码登录
第三方微信登录
```
## “跳过”要不要保留?
我建议:
```text
开发环境可以保留
正式环境不要明显展示
```
也就是说:
```swift
#if DEBUG
Button("跳过,进入演示模式") { ... }
#endif
```
不要把“跳过”作为正式登录入口不然后面学习记录、知识库、AI 分析都会涉及匿名用户迁移,很麻烦。
---
# 登录流程应该这样设计
```text
打开 App
AppSession 检查 Keychain 里有没有 refreshToken
如果有 token调用 /auth/refresh 或 /users/me
成功:进入主界面
失败:进入登录页
用户点击 Sign in with Apple
iOS 获取 identityToken / authorizationCode / userIdentifier
POST /api/auth/apple
后端返回 accessToken / refreshToken / user
iOS 把 token 存到 Keychain
AppSession 更新 currentUser
判断 onboardingCompleted
未完成:进入首次引导 / 学习目标设置
已完成:进入主界面
```
---
# iOS 需要实现的文件
登录相关最少这些:
```text
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
```
---
# 接口请求结构
iOS 登录成功后给后端:
```swift
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?
}
```
后端返回:
```swift
struct AuthResponse: Decodable {
let accessToken: String
let refreshToken: String
let expiresIn: Int
let user: User
}
```
Token 存储:
```text
accessToken → Keychain
refreshToken → Keychain
不要存 UserDefaults
```
---
# 给 Trae 的修改指令
你可以直接发这段:
```text
现在调整知习 iOS App 的登录方案。
第一版登录方式只保留 Sign in with Apple。
请删除登录页中的:
- 手机号登录
- 邮箱登录
- 微信登录
- 其他第三方登录入口
正式 UI 只保留:
- Sign in with Apple 按钮
- 简短产品说明
- 隐私政策 / 用户协议入口
开发环境可以保留一个 DEBUG-only 的“跳过,进入演示模式”按钮,但不能出现在 Release 版本。
请实现以下架构:
1. LoginView
- 只展示 Apple 登录按钮
- 点击后调用 LoginViewModel.loginWithApple()
- 支持 loading / error 状态
- 登录失败时展示错误提示
2. LoginViewModel
- @MainActor
- ObservableObject
- @Published isLoading
- @Published errorMessage
- 调用 AuthService 处理 Apple 登录
3. AuthService
- 集成 ASAuthorizationController
- 获取 identityToken
- 获取 authorizationCode
- 获取 userIdentifier
- 获取 email / fullName如果 Apple 返回
- 调用后端 POST /api/auth/apple
- 接收 accessToken / refreshToken / user
4. TokenStore / KeychainStore
- accessToken 存 Keychain
- refreshToken 存 Keychain
- 支持 save / load / clear
5. AppSession
- 管理全局登录状态
- currentUser
- isAuthenticated
- bootstrap()
- loginWithApple()
- logout()
- refreshSession()
6. App 启动逻辑
- 启动时先检查 Keychain 中是否存在 refreshToken
- 如果存在,调用后端 refresh 或 /users/me
- 成功后进入主界面
- 失败则清空 token 并进入登录页
7. 登录成功后的跳转
- 如果 user.onboardingCompleted == false进入首次使用引导 / 学习目标设置
- 如果 user.onboardingCompleted == true进入 MainTabView
8. 注意
- 不要再使用 @AppStorage("hasCompletedOnboarding") 单独决定是否进入主界面
- 登录状态必须由 AppSession + Keychain token 决定
- 不要把 token 存到 UserDefaults
- 不要在 View 里直接写网络请求
- 不要在 View 里直接处理 Apple 登录细节
- 不要改变其他主页面 UI
```
---
# 登录页内容建议
页面可以非常简单:
```text
知习
更懂你,更会学。
用 AI 把知识库、主动回忆和间隔复习连接起来,
从“看过”走向“真正学会”。
[ Sign in with Apple ]
登录即代表你同意《用户服务协议》和《隐私政策》
```
就够了。
第一版登录越简单越好,别再做多登录方式。