Files
2026-10-29 10:08:39 +08:00

558 lines
26 KiB
Markdown
Raw Permalink 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.
# 爱学蝶变家长端
## 1. 文档用途
本文档是本项目的实现契约。阅读本文档后,其他 AI 应能够在不阅读原始页面代码的情况下,使用 uni-app x 重新生成结构、功能、校验、接口调用、状态共享和页面跳转基本一致的家长端应用。
实现或重构时必须以本文档为准,并保持以下行为不变:
- 登录、验证码登录、找回密码和家长注册流程完整可用。
- 注册流程最多绑定 3 名学生,至少绑定 1 名学生才能继续。
- 学生信息不能通过 URL 参数传递。
- Web 与 Android/iOS 的扫码实现必须区分平台。
- 业务 API 使用统一请求封装,当前默认启用 mock 响应。
- 表单和页面必须适配手机窄屏及底部安全区。
## 2. 技术基线
| 项目项 | 当前约定 |
| --- | --- |
| 框架 | uni-app x |
| Vue | Vue 3,`<script setup lang="ts">` |
| 渲染模式 | Vapor 配置开启;当前 Web 编译日志显示 VDOM,需以实际目标平台编译结果为准 |
| HBuilderX | 5.33 |
| 目标平台 | Web、Android、iOS |
| 样式隔离 | style isolation 2.0 |
| API 状态 | `utils/request.ts` 中 `USE_MOCK_API = true` |
| mock 基地址 | `https://mock-api.example.com` |
| Web 扫码 | `@zxing/browser` + 浏览器 `HTMLVideoElement` |
| 原生扫码 | uni-app x 内置 `camera`,`mode="scanCode"` |
### 2.1 uni-app x 约束
- 所有 `display: flex` 必须显式声明 `flex-direction`。
- 页面样式只使用 class 选择器,不使用标签选择器、子选择器和通配选择器。
- 长页面使用纵向 `scroll-view`,底部固定操作区时正文必须预留底部空间。
- 密码框直接使用原生 `input type="password"`,不要添加显示/隐藏按钮。
- 传统 uni-app 的 `uni-easyinput`、`uni-forms`、`uni-icons` 不能直接当作 uni-app x Vapor 组件使用。
- 项目已安装的 uni-ui x 组件可通过 EasyCom 使用,例如 `uni-badge-view`、`uni-nav-bar`、`uni-tab` 等。没有对应官方组件时使用内置 `input`、`button`、`form`、`picker`、`scroll-view`。
- 不使用 emoji、箭头字符、叉号字符作为界面图标。需要状态标记时使用 CSS 图形或明确的文字按钮。
## 3. 页面与路由
| 页面 | 路径 | 页面职责 | 主要进入方式 |
| --- | --- | --- | --- |
| 密码登录 | `pages/login/index` | 手机号密码登录、记住密码、跳转验证码登录、找回密码、注册 | 应用入口、退出登录、未授权跳转 |
| 验证码登录 | `pages/login/verify-code` | 手机号验证码登录 | 密码登录页点击“验证码登录” |
| 忘记密码 | `pages/login/forgot-password` | 发送验证码、验证验证码、重置密码 | 密码登录页点击“忘记密码” |
| 注册入口 | `pages/register/index` | 清空注册学生状态并转到扫码绑定页 | 登录页点击“注册” |
| 扫码绑定学生 | `pages/register/scan-student` | 扫码、解析二维码、校验并添加学生 | 注册入口、输入页切换 |
| 输入学生账号 | `pages/register/input-student` | 手动填写学生账号和姓名并校验 | 扫码页切换 |
| 设置家长账号 | `pages/register/parent-account` | 填写手机号、关系、密码并提交注册 | 两个学生绑定页点击“下一步” |
| 注册完成 | `pages/register/complete` | 展示注册结果和已绑定学生 | 注册成功后 redirect |
| 首页 | `pages/index/index` | 登录后的学生概览、预警和常用入口 | 登录成功、完成页返回首页、底部导航 |
| 学情 | `pages/study/index` | 周综合表现、学习趋势和学科表现 | 底部导航“学情” |
| 任务 | `pages/tasks/index` | 待完成、即将截止和近期完成的学习任务 | 底部导航“任务” |
| 我的 | `pages/profile/index` | 家长账户信息、设置入口和退出登录 | 底部导航“我的” |
| 验证学生 | `pages/profile/verify-student` | 登录后扫码或手动验证学生,不创建绑定 | 我的页面入口 |
`pages/register/index` 是流程初始化页,不展示业务内容。`onLoad` 中调用 `resetRegistrationStudents()`,然后 `redirectTo('/pages/register/scan-student')`。
## 4. 全局状态
### 4.1 注册学生状态
文件:`store/registration.ts`
```ts
export const registrationStudents = ref<StudentInfo[]>([])
```
规则:
- 扫码页、手动输入页、家长账号页、完成页都直接 import 同一个 `registrationStudents`。
- 不通过页面 URL 传递学生对象,不使用 `JSON.stringify` 拼接导航参数。
- 注册入口进入时清空列表;注册完成页读取当前列表用于展示。
- 学生列表对象类型为 `StudentInfo`:`id`、`name`、`account`、`avatar`、`grade`、`class`。
### 4.2 用户状态
文件:`store/user.ts`
登录或注册成功后调用 `setUserInfo(userInfo)`,保存:
- `token`
- `refreshToken`
- `userInfo`
本地存储 key:`token`、`refreshToken`、`userInfo`、`rememberInfo`。
密码登录页 `onLoad` 调用 `getRememberInfo()` 恢复手机号、密码和记住密码状态。用户取消记住密码时删除 `rememberInfo`。
### 4.3 首页学生资料缓存
文件:`store/home.ts`
- `homeStudentProfile` 仅在当前登录会话内保存首页学生资料,不写入本地存储。
- 首页首次进入且缓存为空时调用 `apiGetStudentHomeProfile()`;从学情、任务、我的切回首页时直接复用缓存,不重复请求。
- 首页 `scroll-view` 开启 `refresher-enabled`,用户下拉触发 `@refresherrefresh` 后强制重新请求,并用新响应替换缓存。
- `clearUserInfo()` 会同时清空该缓存,确保退出登录后的下一次登录重新获取学生信息。
## 5. 页面详细契约
### 5.1 密码登录页
路径:`/pages/login/index`
#### UI
- 顶部显示 `/static/logo.png`、应用名和副标题。
- 表单包含手机号输入框、密码输入框、记住密码、忘记密码入口。
- 密码输入使用原生 `type="password"`,不添加额外按钮。
- 底部或表单下方提供“验证码登录”和“注册”入口。
- 登录按钮根据 `canSubmit` 展示可用/禁用状态。
#### 状态
```ts
type LoginForm = {
phone: string
password: string
remember: boolean
}
```
`canSubmit`:手机号匹配 `/^1[3-9]\d{9}$/` 且密码长度至少 6 位。
#### 行为
1. 点击登录时,`canSubmit` 为 false 直接返回。
2. 调用 `apiLogin({ phone, password, remember })`。
3. 成功后调用 `setUserInfo` 和 `saveRememberInfo`。
4. 显示“登录成功”,约 1 秒后 `reLaunch('/pages/index/index')`。
5. 点击“验证码登录”使用 `navigateTo('/pages/login/verify-code')`。
6. 点击“忘记密码”使用 `navigateTo('/pages/login/forgot-password')`。
7. 点击“注册”使用 `navigateTo('/pages/register/index')`。
### 5.2 验证码登录页
路径:`/pages/login/verify-code`
#### 状态与校验
- `phone`:手机号。
- `code`:验证码。
- `canSubmit`:手机号有效且验证码长度为 6。
#### 行为
- 使用 `CountdownBtn` 组件发送验证码,发送类型为 `login`。
- 手机号不合法时提示“请输入正确的手机号”,不发请求。
- 发送成功后启动 60 秒倒计时,按钮显示剩余秒数,结束后显示“重新发送”。
- 调用 `apiLoginByCode({ phone, code })` 登录。
- 成功后保存用户信息并 `reLaunch('/pages/index/index')`。
- “密码登录”使用 `navigateBack()`。
- “注册”使用 `navigateTo('/pages/register/index')`。
### 5.3 忘记密码页
路径:`/pages/login/forgot-password`
页面是 3 个步骤,由 `step` 控制:
#### Step 1:验证手机号
- 输入手机号和 6 位验证码。
- 使用 `CountdownBtn`,验证码类型为 `forgot-password`。
- `canStep1`:手机号有效且验证码长度为 6。
- 调用 `apiVerifyCode({ phone, code })`。
- 成功后保存返回的 `resetToken`,进入 Step 2。
#### Step 2:设置新密码
- 输入新密码和确认密码。
- 两个密码长度必须为 6 至 20 位,且完全一致。
- 不一致提示“两次密码输入不一致”。
- 调用 `apiResetPassword({ phone, newPassword, resetToken })`。
- 成功后进入 Step 3。
#### Step 3:完成
- 显示重置成功状态。
- 点击“去登录”使用 `navigateTo('/pages/login/index')`。
- 页面返回操作使用 `navigateBack()`。
### 5.4 注册入口页
路径:`/pages/register/index`
只做初始化:
1. 调用 `resetRegistrationStudents()`。
2. `redirectTo('/pages/register/scan-student')`。
这样可以保证用户重新进入注册流程时不会带入上一次未完成注册的学生列表。
### 5.5 扫码绑定学生页
路径:`/pages/register/scan-student`
#### UI
- 显示三步进度:扫码绑定孩子、家长账号与密码、注册完成。
- 提供扫码绑定和输入学生账号两个切换入口。
- 显示扫码区域、学生列表和底部“下一步”按钮。
- 学生数量使用 `uni-badge-view` 展示,例如 `1/3`。
- 列表中的移除操作使用文字按钮“移除”,不使用叉号字符。
#### 扫码平台差异
Web 条件编译:
- 创建原生 `HTMLVideoElement` 并插入 `#web-scan-video-container`。
- 设置 `autoplay`、`muted`、`playsInline`。
- 使用 `BrowserQRCodeReader.decodeFromConstraints`,摄像头约束使用后置摄像头 `facingMode: 'environment'`。
- 页面挂载时启动扫描,页面卸载时停止扫描并清理控制器。
- 识别到结果后停止当前扫描,5 秒后重新启动;最多绑定 3 人后不再重启。
Android/iOS 条件编译:
- 使用内置 `<camera mode="scanCode" device-position="back" @scancode="handleCameraScan">`。
- 摄像头错误提示“无法使用摄像头,请检查权限”。
#### 二维码格式
二维码文本必须是:
```text
学生账号,学生姓名
```
使用第一个逗号拆分:逗号前是 `account`,逗号后是 `name`。缺少逗号、账号为空或姓名为空时提示“二维码格式无效”。
#### 防重复与绑定规则
- `MAX_STUDENTS = 3`。
- 扫码时若正在验证,或距离上次扫码不足 `5000ms`,直接忽略。
- 请求前按 `student.account` 检查重复。
- 请求成功后再次按返回数据的 `account` 检查重复。
- 成功调用 `apiVerifyStudentQRCode({ qrCode, account, name })`,将返回学生 push 到共享列表。
- 没有学生时点击下一步提示“请至少绑定一个学生”。
- 有学生时 `navigateTo('/pages/register/parent-account')`。
- 切换到手动输入使用 `redirectTo('/pages/register/input-student')`。
### 5.6 手动输入学生页
路径:`/pages/register/input-student`
#### 表单
```ts
type StudentForm = {
account: string
name: string
}
```
#### 校验顺序
1. 账号和姓名不能为空。
2. 学生数量不能达到 3 人上限。
3. 当前列表不能已有相同账号。
4. 姓名必须匹配 `/^[\u4e00-\u9fa5]{2,10}$/`,即 2 至 10 个汉字。
#### 行为
- 调用 `apiVerifyStudentAccount({ account, name })`。
- 成功后再次检查返回账号是否重复,再 push 到 `registrationStudents`。
- 成功后清空输入表单并提示“添加成功”。
- 点击学生“移除”前使用 `uni.showModal` 二次确认。
- 点击“扫码绑定”使用 `redirectTo('/pages/register/scan-student')`。
- 点击“下一步”前必须至少有一名学生,然后 `navigateTo('/pages/register/parent-account')`。
### 5.7 设置家长账号页
路径:`/pages/register/parent-account`
#### 表单
```ts
type ParentAccountForm = {
parentName: string
phone: string
relationship: string
password: string
confirmPassword: string
}
```
家长姓名为必填项,必须匹配 `/^[\u4e00-\u9fa5]{2,10}$/`,即 2 至 10 个汉字。
关系选择器使用内置 `picker`,选项固定为:
```text
爸爸、妈妈、爷爷、奶奶、外公、外婆、其他亲属
```
密码和确认密码均使用原生 `type="password"`,不额外添加显示/隐藏按钮。
#### 提交条件
`canSubmit` 同时满足:
- 手机号匹配 `/^1[3-9]\d{9}$/`。
- 家长姓名匹配 `/^[\u4e00-\u9fa5]{2,10}$/`。
- 已选择关系。
- 密码长度 6 至 20 位。
- 两次密码完全一致。
#### 提交流程
1. 不满足条件时按家长姓名、手机号、关系、密码长度、密码一致性的顺序提示。
2. 调用 `apiCheckPhone(form.phone)` 检查手机号。
3. `exists === true` 时提示手机号已注册,停止注册并引导登录。
4. 将共享学生列表转换为 ID 数组:`students.value.map(s => s.id)`。
5. 调用:
```ts
apiRegister({
parentName,
phone,
relationship,
password,
students: studentIds
})
```
6. 注册成功后调用 `setUserInfo`。
7. 使用 `redirectTo('/pages/register/complete')`,不把学生信息放到 URL。
### 5.8 注册完成页
路径:`/pages/register/complete`
- 直接读取 `registrationStudents`,不解析路由参数。
- 显示注册完成、绑定学生数量和学生列表。
- “去登录”使用 `reLaunch('/pages/login/index')`。
- “返回首页”使用 `reLaunch('/pages/index/index')`。
### 5.9 首页
路径:`/pages/index/index`
首页按参考视觉稿实现为三段式结构:固定顶部 Header、中间独立滚动内容、固定底部 Footer。页面根节点使用纵向 flex,只有 `content-scroll` 设置 `scroll-y="true"`,因此上下区域不会随着中间内容滚动。
#### 5.9.1 顶部 Header
- 背景为紫色渐变,左右下角保持页面顶部的整幅色块效果。
- 左侧依次显示问候语“下午好,王女士”、主标题“孩子今天的学习情况”、数据更新时间说明。
- 右侧是紫色半透明圆形通知按钮,内部显示白色铃铛和黄色未读点。
- Header 属于根布局的固定子节点,不放进 `scroll-view`。
#### 5.9.2 学生概览卡
- 白色圆角卡片,包含学生头像、姓名“张一诺”、年级标签“初二”和“切换”按钮。
- 学生头像采用紫色圆角底和 CSS 图形字符组合,不依赖不兼容的图标组件。
- 学生头像、姓名、年级和综合等级通过 `apiGetStudentHomeProfile()` 获取;头像为空时显示姓名首字作为降级展示。
- 学生资料和六项学习概览仅首次进入首页自动请求;切换一级页面后返回首页复用会话缓存。用户下拉刷新,重新获取并替换当前学生的资料和概览。
- 点击“切换”使用内置 `page-container` 从底部打开学生选择卡片。首次打开时调用 `apiGetBoundStudents()`,每张卡片展示头像、姓名、账号、年级和学校。
- 选择卡片仅更新临时选择;点击“确定”后才调用 `apiGetStudentHomeProfile(studentId)` 更新首页。点击“取消”、遮罩返回或下滑关闭不改变首页当前学生。
- 预警提醒通过 `apiGetStudentAlerts(studentId, 1, 20, 3)` 获取,首页最多展示 3 条。点击“全部预警”或顶部通知图标进入 `pages/alerts/index` 查看当前学生的预警列表。
- 常用入口固定展示“今日学习”“周报月报”“英语词汇”“成长激励”四个板块,仅保留图标和名称。
- 显示综合等级徽章、标签“综合等级”和绿色等级值。接口等级枚举 `excellent`、`good`、`qualified`、`needs_improvement` 分别转换为“优秀”“良好”“合格”“待提升”。
- 下方为 3 列、2 行指标网格:今日上线、今日打卡、今日学习时长、今日正确率、本周学习天数、待完成作业。
- 指标值分别为“已上线”“已打卡”“2h35m”“92%”“5/5 天”“1 个”,绿色表示完成状态,蓝色表示周统计,橙色表示待处理事项。
#### 5.9.3 预警提醒卡
- 白色圆角卡片标题为“预警提醒”,右侧显示“全部预警 >”。
- 三条提醒使用浅灰背景的横向条目,每条由彩色标签和可换行正文组成。
- 提醒内容依次为作业未完成、正确率下降、读完失败。
- 黄色表示待提交事项,红色表示成绩或任务异常。
#### 5.9.4 常用入口
- 标题为“常用入口”,下方显示学习报告、成长记录两个白色入口卡片。
- 每个入口包含 CSS 图形图标、标题和辅助说明。
- 入口位于 `content-scroll` 中,内容超出屏幕高度时随中间区域滚动。
#### 5.9.5 底部 Footer
- 白色固定底栏,顶部有浅灰分割线,并预留 `var(--uni-safe-area-inset-bottom)` 安全区。
- 四项导航从左到右为:首页、学情、任务、我的。
- 当前首页使用紫色图标和文字,其余导航使用灰色图标和文字。
- 图标使用页面内独立 class 节点绘制,避免 emoji、字符箭头、叉号和传统 `uni-icons`。
- Footer 属于根布局的固定子节点,不放进 `scroll-view`。
#### 5.9.6 视觉参数
- 页面背景:`#F5F7FB`。
- 主色:`#554BE9` 至 `#8236E9` 的紫色系,正文使用深蓝灰,状态使用绿、蓝、橙、红区分。
- 内容卡片圆角约 12 至 16px;Header 不做浮动卡片处理。
- 内容左右边距 20px,卡片内边距约 16px,指标卡使用 3 列等宽布局。
- 所有 flex 容器必须显式设置 `flex-direction`;页面样式只使用 class 选择器。
### 5.10 一级导航页面
首页、学情、任务、我的共用 `components/main-tab-bar.uvue`。组件接收 `activeTab`,高亮当前入口;点击其他入口使用 `uni.reLaunch` 跳转,避免一级页面堆叠在导航栈中。每个一级页面都由固定 Header、可滚动 `scroll-view` 和固定 Footer 组成。
#### 5.10.1 学情
- 路径:`/pages/study/index`。
- 显示学生名和年级、本周综合得分、进度条、近 7 天柱状学习趋势和数学、语文、英语的学科表现。
- 该页面当前使用展示数据,后续接入学习报告接口时保持现有字段布局。
#### 5.10.2 任务
- 路径:`/pages/tasks/index`。
- 顶部统计待完成、已完成、即将截止数量。
- 中间展示待完成任务卡、截止时间、任务状态和查看入口;下方展示近期已完成任务。
- 该页面当前使用展示数据,后续接入任务接口时保持任务卡字段布局。
#### 5.10.3 我的
- 路径:`/pages/profile/index`。
- 从本地 `userInfo` 读取并显示家长昵称和手机号。
- 包含孩子管理、账号与安全、消息设置、帮助与反馈、关于爱学蝶变入口;尚未接入的功能统一提示“功能开发中”。
- 点击“退出登录”调用 `clearUserInfo()` 后 `reLaunch('/pages/login/index')`。
- 扫码及手动入口进入学生验证页;该页先读取已绑定列表,加载失败时可重试,不将失败视为空列表。最多 3 名的限制及重复提示基于已加载列表。
- 扫码复用 `qr-scanner` 和 `apiVerifyStudentQRCode`,手动输入使用 `apiVerifyStudentAccount`。验证通过仅展示“已验证,尚未绑定”,不会写入已绑定列表或注册状态;登录后新增绑定需要接入真实后端接口。当前项目默认 mock,验证结果也可能来自 mock,不能作为真实身份或绑定凭据。
## 6. 接口契约
所有接口必须通过 `api/*.ts` 调用 `utils/request.ts` 的统一方法,不在页面中直接调用 `uni.request`。
### 6.1 响应格式
```ts
type ApiResponse<T> = {
code: number
message: string
data: T | null
}
```
`code === 0` 表示业务成功。失败时统一 toast,接口返回 `null` 时页面不得继续执行成功跳转。
### 6.2 认证接口
| 函数 | 方法 | 地址 | 参数 |
| --- | --- | --- | --- |
| `apiLogin` | POST | `/api/auth/login` | `phone`、`password`、可选 `remember` |
| `apiLoginByCode` | POST | `/api/auth/login-by-code` | `phone`、`code` |
| `apiSendCode` | POST | `/api/auth/send-code` | `phone`、`type` |
| `apiVerifyCode` | POST | `/api/auth/forgot-password/verify` | `phone`、`code` |
| `apiResetPassword` | POST | `/api/auth/forgot-password/reset` | `phone`、`newPassword`、`resetToken` |
| `apiRegister` | POST | `/api/auth/register` | `parentName`、`phone`、`relationship`、`password`、`students` |
验证码类型:`login`、`register`、`forgot-password`。当前注册页面不发送注册验证码,但类型保留用于后续扩展。
### 6.3 学生接口
| 函数 | 方法 | 地址 | 参数 |
| --- | --- | --- | --- |
| `apiVerifyStudentQRCode` | POST | `/api/student/verify-qrcode` | `qrCode`、`account`、`name` |
| `apiVerifyStudentAccount` | POST | `/api/student/verify-account` | `account`、`name` |
| `apiCheckPhone` | GET | `/api/parent/check-phone?phone=...` | 手机号 |
| `apiGetStudentHomeProfile` | GET | `/api/parent/home-profile` | 无;服务端由 Bearer Token 确定当前家长的默认绑定学生 |
| `apiGetBoundStudents` | GET | `/api/parent/students` | 无;返回当前家长绑定的学生列表和默认学生 ID |
| `apiGetStudentHomeMetrics` | GET | `/api/parent/home-metrics?studentId={studentId}` | 无;返回今日上线、打卡、时长、正确率、本周学习天数、待完成作业和更新时间 |
| `apiGetStudentAlerts` | GET | `/api/parent/alerts?studentId={studentId}&limit=3` | 无;首页最多返回 3 条预警,列表页以 `page`、`pageSize` 分页获取 |
`apiGetStudentHomeProfile(studentId?)` 返回 `id`、`name`、`account`、`grade`、`school`、`avatar`、`comprehensiveLevel`。传入 `studentId` 时真实服务必须校验请求 token 与该学生的绑定关系;未登录返回 401,无绑定权限返回 403。
`apiGetStudentHomeMetrics(studentId)` 返回 `onlineToday`、`checkedInToday`、`learningMinutesToday`、`accuracyToday`、`learningDaysThisWeek`、`pendingHomeworkCount` 和 `updatedAt`。其中学习时长的单位为分钟;无答题记录时 `accuracyToday` 为 `null`,页面显示 `--`。
`apiGetStudentAlerts` 的单条预警包含 `level`、`title`、`content`、`occurredAt` 和 `isRead`。真实服务必须校验当前家长与 `studentId` 的绑定关系。
首页下拉刷新调用学生资料和学习概览接口时传入 `showLoading = false`,仅显示 `scroll-view` 自身的刷新状态,不展示全局 loading 覆盖层。
### Mock 网络时延
`USE_MOCK_API = true` 时,所有 mock 接口统一随机延迟 `0` 至 `2000ms` 后返回,用于模拟正常网络环境。真实后端接口不施加额外时延。
### 6.4 mock 行为
当 `USE_MOCK_API = true` 时,请求约 150ms 后返回成功:
- 登录、注册返回测试家长 `test-parent-001`、`test-token` 和 `test-refresh-token`。
- 发送验证码返回 `expireTime: 300`。
- 找回密码验证码返回 `test-reset-token`。
- 学生验证根据请求账号和姓名生成学生对象,学生 ID 为 `test-student-${account}`。
- 检查手机号始终返回 `exists: false`。
- 首页学生信息返回测试学生“张一诺”、年级“初二”、综合等级 `excellent`。
## 7. 统一请求和错误处理
- 请求拦截器将相对路径拼接到 `BASE_URL`。
- 自动设置 `Content-Type: application/json`。
- 本地存在 token 时自动设置 `Authorization: Bearer <token>`。
- 默认显示全局 loading,使用引用计数避免并发请求提前隐藏 loading。
- HTTP 非 200 或业务 `code !== 0` 时显示错误提示。
- 401 时清除 `token`、`userInfo` 并 `reLaunch('/pages/login/index')`。
- 403、404、500 分别提示无权限、资源不存在、服务器错误。
- 页面提交、验证码发送、学生校验期间应避免重复操作。
## 8. AI 复刻顺序
生成相似项目时按以下顺序实施:
1. 创建 uni-app x Vue 3 项目并启用 TypeScript、Vapor、style isolation 2.0。
2. 创建 `api/auth.ts`、`api/student.ts`、`utils/request.ts`、`store/user.ts`、`store/registration.ts`。
3. 先实现登录和用户持久化,再实现验证码登录和找回密码。
4. 创建注册入口、扫码绑定、手动输入、家长账号、完成页五个页面。
5. 使用模块级 `registrationStudents` 贯穿注册流程,禁止用 URL 传学生对象。
6. 使用条件编译实现 Web ZXing 扫码和原生 camera 扫码。
7. 添加所有表单校验、上限校验、重复学生校验、loading、toast 和确认弹窗。
8. 最后实现首页概览并统一页面间跳转方式。
9. 编译 Web 和 Android,确认页面无横向溢出、固定底栏不遮挡内容、扫码资源正确释放。
## 9. 验收清单
- [ ] 密码登录成功后保存 token 并进入首页。
- [ ] 记住密码可以恢复和清除。
- [ ] 验证码发送有 60 秒倒计时,倒计时期间不可重复发送。
- [ ] 验证码登录成功后进入首页。
- [ ] 找回密码必须依次完成验证码验证和新密码设置。
- [ ] 注册入口会清空旧学生列表。
- [ ] Web 扫码使用浏览器摄像头,原生端使用 camera 扫码。
- [ ] 二维码按“账号,姓名”解析,格式错误有提示。
- [ ] 学生最多 3 人,至少 1 人才能下一步。
- [ ] 相同学生账号不能重复添加。
- [ ] 手动输入姓名必须是 2 至 10 个汉字。
- [ ] 注册页关系为必选项,密码为 6 至 20 位且两次一致。
- [ ] 学生信息从不出现在页面 URL 中。
- [ ] 注册成功后完成页能显示共享学生列表。
- [ ] 401 会清理登录态并回到登录页。
- [ ] 所有密码框使用原生默认密码样式,无额外显隐按钮。
- [ ] 没有 emoji、箭头字符或叉号字符充当 UI 图标。
- [ ] Web 编译成功且控制台没有新增错误。
## 10. 常用命令
```powershell
# Web 发行构建
& "F:\HBuilderX\cli.exe" publish web --platform Web --project "E:\project\App.k12.Parents" --webTitle "爱学蝶变家长端"
# Android Vapor 编译验证
& "F:\HBuilderX\cli.exe" launch app-android --project "E:\project\App.k12.Parents" --compile true
```
Web 构建产物目录:`unpackage/dist/build/web`。
## 11. 文件职责
| 文件 | 职责 |
| --- | --- |
| `manifest.json` | 应用名称、AppID、平台配置,当前名称为“爱学蝶变家长端” |
| `pages.json` | 页面注册和导航栏配置 |
| `api/auth.ts` | 认证接口类型和方法 |
| `api/student.ts` | 学生验证和手机号检查接口 |
| `utils/request.ts` | 请求拦截、mock、loading、错误处理 |
| `store/user.ts` | 用户登录态、token、记住密码 |
| `store/registration.ts` | 注册流程共享学生列表 |
| `components/countdown-btn.uvue` | 验证码发送和 60 秒倒计时 |
| `pages/login/*` | 登录、验证码登录、找回密码 |
| `pages/register/*` | 学生绑定、家长账号、注册完成 |
| `pages/index/index.uvue` | 登录后首页概览 |