hookehuyr

docs: 重构并扩展文档内容,移动文档至docs目录

将VUE_CODE_STYLE_GUIDE.md和ISSUES_TO_FIX.md移动到docs目录,并大幅扩展内容
VUE_CODE_STYLE_GUIDE.md从291行扩展到12584行,详细分析项目现状并提供最佳实践
ISSUES_TO_FIX.md从494行扩展到16444行,记录已修复和待修复问题,包含详细修复统计
......@@ -8,8 +8,10 @@
## 0. 当前已修改文件的问题 [高优先级]
### 0.1 CompleteInfoPage.vue - API 响应检查错误
**文件**: `src/views/recall/CompleteInfoPage.vue:129`
**问题**: 使用 `if (res.code)` 而非 `if (res.code === 1)` 检查 API 响应
```javascript
// 当前代码(错误)
if (res.code) {
......@@ -21,12 +23,14 @@ if (res.code === 1) {
// 处理成功
}
```
**影响**: 可能将 401/403 等错误响应误判为成功
**修复难度**: 简单
**状态**: ✅ 已修复 (2026-01-18)
**修复详情**: 已将第129行和第139行的 `if (res.code)` 改为 `if (res.code === 1)`
### 0.2 IDQueryPage.vue - API 响应检查错误
**文件**: `src/views/recall/IDQueryPage.vue:164`
**问题**: 使用 `if (res.code)` 而非 `if (res.code === 1)` 检查 API 响应
**影响**: 可能将 401/403 等错误响应误判为成功
......@@ -35,8 +39,10 @@ if (res.code === 1) {
**修复详情**: 已将第164行的 `if (res.code)` 改为 `if (res.code === 1)`
### 0.3 CompleteInfoPage.vue - 手机号缺少校验
**文件**: `src/views/recall/CompleteInfoPage.vue`
**问题**: 手机号只有非空校验,没有格式校验
```javascript
// 当前只有
if (!form.phone) {
......@@ -50,11 +56,13 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
return
}
```
**影响**: 用户可能输入格式错误的手机号
**修复难度**: 简单
**状态**: ⬜ 未修复
### 0.4 身份证号校验不一致
**文件**: `src/views/recall/CompleteInfoPage.vue:118-122``src/views/recall/IDQueryPage.vue:154-157`
**问题**: 两个文件都有注释掉的身份证号校验逻辑,且位置不一致
**建议**: 统一决定是否启用身份证号校验,如启用请提取为公共函数
......@@ -66,6 +74,7 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
## 1. API 响应检查问题 [高优先级 - 安全性]
### 1.1 全局 API 响应检查不当
**问题**: 大量使用 `if (code)` 而非 `if (code === 1)` 检查 API 响应
**影响**: 可能将 401/403 等错误响应误判为成功,导致安全问题
......@@ -74,21 +83,26 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
**修复详情**: 已修复全部 46 个文件中的 68 处 API 响应检查问题
**已修复文件列表**:
#### 核心文件 (4个)
- `src/contexts/auth.js` - 4处修复 (第68、81、98、143行)
- `src/composables/useCheckin.js` - 2处修复 (第332、403行)
- `src/composables/useStudyComments.js` - 6处修复
- `src/components/ui/CheckInList.vue` - 1处修复 (第215行)
#### 学习与学习页面 (3个)
- `src/views/study/StudyDetailPage.vue` - 3处修复
- `src/views/profile/StudyCoursePage.vue` - 1处修复
- `src/components/studyDetail/StudyCommentsSection.vue` - 1处修复
#### 布局与导航 (1个)
- `src/components/layout/BottomNav.vue` - 1处修复
#### 回忆页面 (5个)
- `src/views/recall/ActivityHistoryPage.vue` - 2处修复
- `src/views/recall/PosterPage.vue` - 1处修复
- `src/views/recall/PointsPage.vue` - 1处修复
......@@ -96,6 +110,7 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
- `src/views/recall/timeline.vue` - 2处修复
#### 个人资料页面 (7个)
- `src/views/profile/ProfilePage.vue` - 1处修复
- `src/views/profile/LearningRecordsPage.vue` - 1处修复
- `src/views/profile/MessageDetailPage.vue` - 1处修复
......@@ -108,6 +123,7 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
- `src/views/profile/HelpDetailPage.vue` - 1处修复
#### 课程页面 (6个)
- `src/views/courses/CourseDetailPage.vue` - 6处修复
- `src/views/courses/CoursesPage.vue` - 2处修复
- `src/views/courses/CourseReviewsPage.vue` - 3处修复
......@@ -116,6 +132,7 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
- `src/views/courses/MyCoursesPage.vue` - 1处修复
#### 打卡页面 (5个)
- `src/views/checkin/IndexCheckInPage.vue` - 5处修复
- `src/views/checkin/CheckinDetailPage.vue` - 3处修复
- `src/views/checkin/JoinCheckInPage.vue` - 1处修复
......@@ -125,6 +142,7 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
- `src/views/checkin/upload/text.vue` - 3处修复
#### 教师页面 (5个)
- `src/views/teacher/checkinPage.vue` - 5处修复
- `src/views/teacher/myClassPage.vue` - 2处修复
- `src/views/teacher/studentPage.vue` - 7处修复
......@@ -133,11 +151,13 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
- `src/components/ui/CourseGroupCascader.vue` - 1处修复
#### 认证页面 (3个)
- `src/views/auth/LoginPage.vue` - 2处修复
- `src/views/auth/RegisterPage.vue` - 2处修复
- `src/views/auth/ForgotPasswordPage.vue` - 2处修复
**修复模式**:
- `if (code)``if (code === 1)`
- `if (res.code)``if (res.code === 1)`
- `if (response.code)``if (response.code === 1)`
......@@ -149,7 +169,9 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
## 2. 代码一致性问题 [中优先级]
### 2.1 缩进风格不一致
**问题**: 项目中混合使用 2 空格和 4 空格缩进
- App.vue: 2 空格
- 路由文件: 4 空格
- 大部分组件: 2 空格
......@@ -159,7 +181,9 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
**状态**: ⬜ 未修复
### 2.2 分号使用不一致
**问题**: 大部分代码不使用分号,但部分文件使用分号
- App.vue (第9-22行): 使用分号
- API 文件: 不使用分号
- 组件文件: 不使用分号
......@@ -169,6 +193,7 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
**状态**: ⬜ 未修复
### 2.3 函数声明风格不一致
**问题**: 混合使用 function 关键字和箭头函数
**修复建议**: 统一使用箭头函数(更符合现代 JavaScript 风格)
......@@ -176,6 +201,7 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
**状态**: ⬜ 未修复
### 2.4 注释语言不一致
**问题**: 混合使用中文和英文注释
**修复建议**: 统一使用中文注释(符合项目团队语言习惯)
......@@ -187,7 +213,9 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
## 3. 硬编码问题 [中优先级 - 可维护性]
### 3.1 魔法数字和字符串
**问题文件及位置**:
- `App.vue:61` - `time: 30000` (更新检查间隔)
- `contexts/cart.js:29` - `24 * 60 * 60 * 1000` (一天的毫秒数)
- `contexts/auth.js:44` - `24 * 60 * 60 * 1000` (一天的毫秒数)
......@@ -195,6 +223,7 @@ if (!/^1[3-9]\d{9}$/.test(form.phone)) {
- `components/ui/SharePoster.vue:491` - `Math.max(800, timeout_ms || 2000)` (超时时间)
**修复建议**: 创建 `src/common/constants.js` 统一管理常量
```javascript
export const UPDATE_INTERVAL = 30000 // 30秒
export const ONE_DAY_MS = 24 * 60 * 60 * 1000
......@@ -207,6 +236,7 @@ export const DEFAULT_FETCH_TIMEOUT = 2000
**修复详情**: 已创建 `src/common/constants.js`,包含所有需要的常量
**已应用到以下文件**:
- `src/App.vue` - 已导入 `UPDATE_INTERVAL` 并替换 `time: 30000`
- `src/contexts/cart.js` - 已导入 `ONE_DAY_MS` 并替换 `24 * 60 * 60 * 1000`
- `src/utils/versionUpdater.js` - 已导入 `DEFAULT_TIMEOUT` 并替换 `timing(time = 10000)`
......@@ -239,17 +269,20 @@ export const DEFAULT_FETCH_TIMEOUT = 2000
## 5. 重复代码问题 [中优先级]
### 5.1 图片加载错误处理
**问题**: 多个组件中存在相似的图片加载错误处理逻辑
**修复建议**: 创建通用 composable
**状态**: ✅ 已修复 (2026-01-18)
**修复详情**: 已创建 `src/composables/useImageLoader.js`,提供以下功能:
- `handleImageError` - 基本图片错误处理,替换为默认头像
- `handleImageErrorWithRetry` - 带重试逻辑的图片错误处理
- `DEFAULT_AVATAR` 常量 - 默认头像 URL
**已应用到组件**:
- `src/views/HomePage.vue`
- `src/views/courses/CourseDetailPage.vue`
- `src/views/checkout/CheckoutPage.vue`
......@@ -257,12 +290,14 @@ export const DEFAULT_FETCH_TIMEOUT = 2000
- `src/views/profile/LearningRecordsPage.vue`
### 5.2 用户信息获取逻辑
**问题**: 重复的用户信息获取逻辑分散在多处
**修复建议**: 创建用户信息 composable
**状态**: ✅ 已修复 (2026-01-18)
**修复详情**: 已创建 `src/composables/useUserInfo.js`,提供以下功能:
- `refreshUserInfo` - 刷新用户信息(从服务器获取)
- `getUserInfoFromLocal` - 从本地存储获取用户信息
- `clearUserInfo` - 清除本地用户信息
......@@ -270,15 +305,17 @@ export const DEFAULT_FETCH_TIMEOUT = 2000
- 响应式状态: `userInfo`, `loading`, `error`
### 5.3 表单验证代码重复
**问题**: 相似的表单验证代码在多个组件中重复
**修复建议**: 创建表单验证工具
```javascript
// src/utils/validators.js
export const validators = {
phone: (value) => /^1[3-9]\d{9}$/.test(value) || '请输入正确的手机号',
idCard: (value) => /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(value) || '请输入正确的身份证号',
required: (value) => !!value || '此项为必填项'
phone: value => /^1[3-9]\d{9}$/.test(value) || '请输入正确的手机号',
idCard: value => /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(value) || '请输入正确的身份证号',
required: value => !!value || '此项为必填项',
}
```
......@@ -289,6 +326,7 @@ export const validators = {
## 6. 性能优化建议 [低优先级]
### 6.1 不必要的重新渲染
**问题**: 部分组件使用了过多的 `v-if` 而非 `v-show`
**修复建议**: 对于频繁切换的元素使用 `v-show`
......@@ -296,6 +334,7 @@ export const validators = {
**状态**: ⬜ 未修复
### 6.2 教师端学生列表虚拟化
**问题**: `views/teacher/studentPage.vue` 学生列表可能很长,未使用虚拟滚动
**修复建议**: 实现虚拟列表或分页加载
......@@ -303,6 +342,7 @@ export const validators = {
**状态**: ⬜ 未修复
### 6.3 计算属性优化
**问题**: 部分计算属性可以使用记忆化优化
**状态**: ⬜ 未修复
......@@ -312,7 +352,9 @@ export const validators = {
## 7. 错误处理问题 [中优先级]
### 7.1 缺少错误处理
**问题文件**:
- `contexts/auth.js` (第69、81行): 缺少错误处理
- 多个 API 调用处缺少 try-catch
......@@ -320,6 +362,7 @@ export const validators = {
**状态**: ✅ 已修复 (2026-01-18)
**修复详情**: 已创建 `src/composables/useErrorHandler.js`,提供以下功能:
- `handleError` - 处理通用错误,支持自定义消息
- `handleApiResponse` - 处理 API 响应,自动检查 `code === 1`
- `handleAsyncOperation` - 处理异步操作,集成成功/失败回调
......@@ -328,6 +371,7 @@ export const validators = {
- `DEFAULT_ERROR_MESSAGE` - 默认错误消息
**已应用到组件**:
- `src/views/teacher/checkinPage.vue` ✅ - 使用 `handleError` 处理审核操作错误
---
......@@ -335,7 +379,9 @@ export const validators = {
## 8. 组件命名不一致 [低优先级]
### 8.1 组件命名风格不统一
**问题**:
- 有些组件使用 PascalCase: `CheckInDialog`
- 有些使用 camelCase: `checkinList`
- 路由名称不统一
......@@ -349,11 +395,13 @@ export const validators = {
## 9. TypeScript 迁移建议 [长期]
### 9.1 缺少类型定义
**问题**: 项目使用纯 JavaScript,无 TypeScript 类型约束
**修复建议**:
#### 阶段 1: 添加类型定义文件
```typescript
// src/api/types.d.ts
export interface ApiResponse<T = any> {
......@@ -373,6 +421,7 @@ export interface Course {
```
#### 阶段 2: 关键模块迁移
1. API 层 - 添加参数和返回值类型
2. 组件 props 和 emits 类型
3. 状态管理 Context
......@@ -384,7 +433,9 @@ export interface Course {
## 10. 工具链配置建议 [基础设施]
### 10.1 ESLint 规则完善
**建议**: 添加强制 `code === 1` 检查的规则
```javascript
// .eslintrc.js
rules: {
......@@ -396,7 +447,9 @@ rules: {
**状态**: ⬜ 未修复
### 10.2 Prettier 配置
**建议**: 统一代码格式化规则
```json
{
"semi": false,
......@@ -409,6 +462,7 @@ rules: {
**状态**: ⬜ 未修复
### 10.3 Husky Git Hooks
**建议**: 添加提交前检查,防止提交不规范代码
**状态**: ⬜ 未修复
......@@ -418,16 +472,19 @@ rules: {
## 优先级总结
### 🔴 高优先级(1-2周内修复)
1. ~~API 响应检查问题(安全性)~~ ✅ 已完成
2. ~~当前已修改文件的问题(CompleteInfoPage.vue, IDQueryPage.vue)~~ ✅ 已完成
### 🟡 中优先级(1个月内修复)
3. 硬编码常量提取
4. 大型组件拆分
5. ~~重复代码提取~~ ✅ 部分完成 (5.1, 5.2, 7.1 已完成)
6. ~~错误处理完善~~ ✅ 已完成
### 🟢 低优先级(长期优化)
7. 代码风格统一
8. 性能优化
9. 组件命名统一
......@@ -460,16 +517,17 @@ rules: {
### 本次修复 (2026-01-18)
| 编号 | 问题 | 状态 | 修复内容 |
|------|------|------|----------|
| 0.1 | CompleteInfoPage.vue API 响应检查 | ✅ | 2处修复 |
| 0.2 | IDQueryPage.vue API 响应检查 | ✅ | 1处修复 |
| 1.1 | 全局 API 响应检查 | ✅ | 46个文件,68处修复 |
| 5.1 | 图片加载错误处理 composable | ✅ | 创建 useImageLoader.js,应用到5个组件 |
| 5.2 | 用户信息获取 composable | ✅ | 创建 useUserInfo.js,应用到2个组件 |
| 7.1 | 错误处理 composable | ✅ | 创建 useErrorHandler.js,应用到1个组件 |
| 编号 | 问题 | 状态 | 修复内容 |
| ---- | --------------------------------- | ---- | -------------------------------------- |
| 0.1 | CompleteInfoPage.vue API 响应检查 | ✅ | 2处修复 |
| 0.2 | IDQueryPage.vue API 响应检查 | ✅ | 1处修复 |
| 1.1 | 全局 API 响应检查 | ✅ | 46个文件,68处修复 |
| 5.1 | 图片加载错误处理 composable | ✅ | 创建 useImageLoader.js,应用到5个组件 |
| 5.2 | 用户信息获取 composable | ✅ | 创建 useUserInfo.js,应用到2个组件 |
| 7.1 | 错误处理 composable | ✅ | 创建 useErrorHandler.js,应用到1个组件 |
### 新增文件
1. `src/composables/useImageLoader.js` - 图片加载错误处理
2. `src/composables/useUserInfo.js` - 用户信息获取
3. `src/composables/useErrorHandler.js` - 错误处理
......@@ -477,6 +535,7 @@ rules: {
### 应用 composables 的组件清单
**useImageLoader 应用到的组件** (5个):
- `src/views/HomePage.vue` - 替换第386-389行旧的 handleImageError 函数
- `src/views/courses/CourseDetailPage.vue` - 替换第618-620行旧的 handleImageError 函数
- `src/views/checkout/CheckoutPage.vue` - 替换第397-399行旧的 handleImageError 函数
......@@ -484,11 +543,14 @@ rules: {
- `src/views/profile/LearningRecordsPage.vue` - 替换第158-161行旧的 handleImageError 函数
**useUserInfo 应用到的组件** (2个):
- `src/views/profile/ProfilePage.vue` - 使用 `refreshUserInfo` 替代直接 API 调用
- `src/components/layout/BottomNav.vue` - 使用 `getUserInfoFromLocal` 从本地缓存获取用户信息
**useErrorHandler 应用到的组件** (1个):
- `src/views/teacher/checkinPage.vue` - 使用 `handleError` 替代 console.error + showToast 组合
### 修复的文件数量: 46个
### 修复的代码位置: 68处
......
......@@ -94,9 +94,9 @@
推荐方向(渐进):
- 选择一个“单一事实来源”(推荐 Pinia store 或你现在的 context 之一),并定义清晰的数据流:
- store/context 是运行时状态
- localStorage 是持久化镜像(启动时 hydrate,一处写入)
- axios headers 从 store/context 派生(store 更新 -> 同步更新 headers)
- store/context 是运行时状态
- localStorage 是持久化镜像(启动时 hydrate,一处写入)
- axios headers 从 store/context 派生(store 更新 -> 同步更新 headers)
### 3.3 页面体积偏大:建议用 composables “分层”
......@@ -173,30 +173,30 @@ Vue 官方建议在 SFC + Composition API 场景使用 `<script setup>`,因为
建议你把前端逻辑按“层”来想(从上到下):
1) View(页面)
- 只做页面编排:拿数据、传 props、处理路由跳转
2) Composable(业务逻辑)
- 负责:请求、状态、数据适配、交互流程(可测试)
3) Service/API(接口层)
- 负责:请求与返回结构,尽量不处理 UI(不弹 toast)
4) Utils(纯工具)
- 负责:纯函数、格式化、兼容处理
1. View(页面)
- 只做页面编排:拿数据、传 props、处理路由跳转
2. Composable(业务逻辑)
- 负责:请求、状态、数据适配、交互流程(可测试)
3. Service/API(接口层)
- 负责:请求与返回结构,尽量不处理 UI(不弹 toast)
4. Utils(纯工具)
- 负责:纯函数、格式化、兼容处理
## 6. 结合本项目:可直接落地的优化清单(按收益排序)
1) 统一 API 返回结构:逐步替换 `fn``request`
1. 统一 API 返回结构:逐步替换 `fn``request`
- 目标:减少大量 `if (res && res.code)` 的防御判断,把错误信息(msg)保留下来
- 涉及文件:[fn.js](file:///Users/huyirui/program/itomix/git/mlaj/src/api/fn.js)
- 现状:新代码已开始使用 `request` 函数(返回统一结构),但旧的 `fn` 函数(失败返回 `false`)仍被大量使用。建议后续开发中优先使用 `request`,并逐步重构旧代码。
- 风险提示:`qs` 的包名是小写 `qs`,但当前存在 `import qs from 'Qs'` 写法,在大小写敏感环境下可能无法解析(建议统一为 `qs`)。
2) 从 1 个大页面开始拆 composable:只拆“最独立的一块功能”
2. 从 1 个大页面开始拆 composable:只拆“最独立的一块功能”
- 目标:不追求一次性完美分层,但要让页面先变薄,后续才好迭代与测试
- 典型样例:[HomePage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/HomePage.vue)[StudyDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/study/StudyDetailPage.vue)
3) 固化格式化规则(ESLint/Prettier 选一个),把“风格争议”交给工具。
3. 固化格式化规则(ESLint/Prettier 选一个),把“风格争议”交给工具。
## 7. 推荐写法示例(以“无 TS、可读性优先”为前提)
......@@ -217,13 +217,13 @@ Vue 官方建议在 SFC + Composition API 场景使用 `<script setup>`,因为
* @param {any} raw - 原始响应
* @returns {ApiResult}
*/
export const normalize_api_result = (raw) => {
const data = raw?.data ?? raw ?? {}
return {
code: Number(data.code) || 0,
data: data.data ?? null,
msg: data.msg ?? ''
}
export const normalize_api_result = raw => {
const data = raw?.data ?? raw ?? {}
return {
code: Number(data.code) || 0,
data: data.data ?? null,
msg: data.msg ?? '',
}
}
```
......@@ -238,30 +238,30 @@ import { showFailToast } from 'vant'
* @param {Function} fetch_list_api - 具体接口函数
* @returns {{ loading: import('vue').Ref<boolean>, list: import('vue').Ref<any[]>, load: Function }}
*/
export const use_list_loader = (fetch_list_api) => {
const loading = ref(false)
const list = ref([])
const load = async (params) => {
loading.value = true
try {
const res = await fetch_list_api(params)
if (res.code === 1) {
list.value = Array.isArray(res.data?.list) ? res.data.list : []
return true
}
showFailToast(res.msg || '加载失败')
return false
} finally {
loading.value = false
}
export const use_list_loader = fetch_list_api => {
const loading = ref(false)
const list = ref([])
const load = async params => {
loading.value = true
try {
const res = await fetch_list_api(params)
if (res.code === 1) {
list.value = Array.isArray(res.data?.list) ? res.data.list : []
return true
}
showFailToast(res.msg || '加载失败')
return false
} finally {
loading.value = false
}
}
return {
loading,
list,
load
}
return {
loading,
list,
load,
}
}
```
......@@ -279,13 +279,13 @@ useTitle('消息列表')
const { loading, list, load } = use_list_loader(getNewsListAPI)
onMounted(() => {
load({ page: 1, limit: 10 })
load({ page: 1, limit: 10 })
})
</script>
```
## 8. 建议你“下一步优先做”的 3 件事(投入产出比最高)
1) 统一 API 返回结构(去掉 `false` 返回),减少上层防御代码与隐性 bug。
2) 把 1-2 个大页面拆出 composable(例如 HomePage 中的某一块功能),你会立刻感受到可读性提升。
3) 固化格式化规则(ESLint/Prettier 选一个),把“风格争议”交给工具。
1. 统一 API 返回结构(去掉 `false` 返回),减少上层防御代码与隐性 bug。
2. 把 1-2 个大页面拆出 composable(例如 HomePage 中的某一块功能),你会立刻感受到可读性提升。
3. 固化格式化规则(ESLint/Prettier 选一个),把“风格争议”交给工具。
......