docs(CLAUDE): 添加 Hash 路由模式重要警告
在项目文档中突出显示路由配置规则: - 所有 URL 必须使用 /#/ 前缀 - 适用于代码生成、测试编写、文档编写 - 标记为 CRITICAL 级别规则 确保在计划阶段和生成阶段都能看到此重要规则。
Showing
1 changed file
with
82 additions
and
3 deletions
| ... | @@ -35,11 +35,13 @@ | ... | @@ -35,11 +35,13 @@ |
| 35 | ### 环境准备 | 35 | ### 环境准备 |
| 36 | 36 | ||
| 37 | 1. **Node.js 版本**: 使用 `.nvm` 管理 Node.js 18.19.1 | 37 | 1. **Node.js 版本**: 使用 `.nvm` 管理 Node.js 18.19.1 |
| 38 | + | ||
| 38 | ```bash | 39 | ```bash |
| 39 | nvm use 18.19.1 | 40 | nvm use 18.19.1 |
| 40 | ``` | 41 | ``` |
| 41 | 42 | ||
| 42 | 2. **安装依赖**: | 43 | 2. **安装依赖**: |
| 44 | + | ||
| 43 | ```bash | 45 | ```bash |
| 44 | pnpm install | 46 | pnpm install |
| 45 | ``` | 47 | ``` |
| ... | @@ -52,21 +54,25 @@ | ... | @@ -52,21 +54,25 @@ |
| 52 | ### 新手指南 | 54 | ### 新手指南 |
| 53 | 55 | ||
| 54 | **添加新页面**: | 56 | **添加新页面**: |
| 57 | + | ||
| 55 | 1. 在 `/src/views/` 相应模块下创建页面组件 | 58 | 1. 在 `/src/views/` 相应模块下创建页面组件 |
| 56 | 2. 在 `/src/router/` 对应路由文件中注册路由 | 59 | 2. 在 `/src/router/` 对应路由文件中注册路由 |
| 57 | 3. 如需认证,在路由 `meta` 中添加 `requiresAuth: true` | 60 | 3. 如需认证,在路由 `meta` 中添加 `requiresAuth: true` |
| 58 | 61 | ||
| 59 | **调用 API**: | 62 | **调用 API**: |
| 63 | + | ||
| 60 | 1. 在 `/src/api/` 对应模块中定义 API 函数 | 64 | 1. 在 `/src/api/` 对应模块中定义 API 函数 |
| 61 | 2. 遵循统一返回结构:`{ code: 1, data: any, msg: string }` | 65 | 2. 遵循统一返回结构:`{ code: 1, data: any, msg: string }` |
| 62 | 3. 使用 `if (res.code === 1)` 检查成功(而非 `if (res.code)`) | 66 | 3. 使用 `if (res.code === 1)` 检查成功(而非 `if (res.code)`) |
| 63 | 67 | ||
| 64 | **创建可复用组件**: | 68 | **创建可复用组件**: |
| 69 | + | ||
| 65 | 1. 放置在 `/src/components/ui/`(通用)或相应业务目录 | 70 | 1. 放置在 `/src/components/ui/`(通用)或相应业务目录 |
| 66 | 2. 使用 `defineProps` 和 `defineEmits` 定义接口 | 71 | 2. 使用 `defineProps` 和 `defineEmits` 定义接口 |
| 67 | 3. 通过 emit 事件向父组件传递操作,避免直接调用 API | 72 | 3. 通过 emit 事件向父组件传递操作,避免直接调用 API |
| 68 | 73 | ||
| 69 | **提取逻辑到 Composable**: | 74 | **提取逻辑到 Composable**: |
| 75 | + | ||
| 70 | 1. 在 `/src/composables/` 创建 `useXxx.js` | 76 | 1. 在 `/src/composables/` 创建 `useXxx.js` |
| 71 | 2. 返回响应式 refs 和函数 | 77 | 2. 返回响应式 refs 和函数 |
| 72 | 3. 内部处理副作用(API 调用、事件监听等) | 78 | 3. 内部处理副作用(API 调用、事件监听等) |
| ... | @@ -74,6 +80,7 @@ | ... | @@ -74,6 +80,7 @@ |
| 74 | ## 常用开发命令 | 80 | ## 常用开发命令 |
| 75 | 81 | ||
| 76 | ### 核心开发 | 82 | ### 核心开发 |
| 83 | + | ||
| 77 | ```bash | 84 | ```bash |
| 78 | pnpm dev # 启动开发服务器 (使用 Node.js 18.19.1) | 85 | pnpm dev # 启动开发服务器 (使用 Node.js 18.19.1) |
| 79 | pnpm build # 生产环境构建 | 86 | pnpm build # 生产环境构建 |
| ... | @@ -82,6 +89,7 @@ pnpm test # 使用 Vitest 运行测试 | ... | @@ -82,6 +89,7 @@ pnpm test # 使用 Vitest 运行测试 |
| 82 | ``` | 89 | ``` |
| 83 | 90 | ||
| 84 | ### 部署 | 91 | ### 部署 |
| 92 | + | ||
| 85 | ```bash | 93 | ```bash |
| 86 | pnpm dev_upload # 部署到开发服务器 | 94 | pnpm dev_upload # 部署到开发服务器 |
| 87 | pnpm behalo_upload # 部署到 behalo 环境 | 95 | pnpm behalo_upload # 部署到 behalo 环境 |
| ... | @@ -89,6 +97,7 @@ pnpm oa_upload # 部署到 OA 服务器 | ... | @@ -89,6 +97,7 @@ pnpm oa_upload # 部署到 OA 服务器 |
| 89 | ``` | 97 | ``` |
| 90 | 98 | ||
| 91 | ### 开发流程 | 99 | ### 开发流程 |
| 100 | + | ||
| 92 | - 每个部署命令都会:构建、归档(tar.gz)、通过 scp 上传、在服务器上解压、清理归档文件。 | 101 | - 每个部署命令都会:构建、归档(tar.gz)、通过 scp 上传、在服务器上解压、清理归档文件。 |
| 93 | 102 | ||
| 94 | ## 目录结构 | 103 | ## 目录结构 |
| ... | @@ -137,6 +146,7 @@ src/ | ... | @@ -137,6 +146,7 @@ src/ |
| 137 | ## 路径别名 | 146 | ## 路径别名 |
| 138 | 147 | ||
| 139 | 在 `vite.config.js` 和 `jsconfig.json` 中配置: | 148 | 在 `vite.config.js` 和 `jsconfig.json` 中配置: |
| 149 | + | ||
| 140 | - `@/` → `src/` | 150 | - `@/` → `src/` |
| 141 | - `@components/` → `src/components/` | 151 | - `@components/` → `src/components/` |
| 142 | - `@composables/` → `src/composables/` | 152 | - `@composables/` → `src/composables/` |
| ... | @@ -155,6 +165,7 @@ src/ | ... | @@ -155,6 +165,7 @@ src/ |
| 155 | **位置**: `/src/api/` | 165 | **位置**: `/src/api/` |
| 156 | 166 | ||
| 157 | 所有接口遵循统一的响应结构: | 167 | 所有接口遵循统一的响应结构: |
| 168 | + | ||
| 158 | ```javascript | 169 | ```javascript |
| 159 | { | 170 | { |
| 160 | code: 1, // 1 = 成功,其他 = 失败 | 171 | code: 1, // 1 = 成功,其他 = 失败 |
| ... | @@ -166,6 +177,7 @@ src/ | ... | @@ -166,6 +177,7 @@ src/ |
| 166 | **重要**: 检查成功时使用 `if (res.code === 1)` 而不是 `if (res.code)`,避免将 401/403 误判为成功。 | 177 | **重要**: 检查成功时使用 `if (res.code === 1)` 而不是 `if (res.code)`,避免将 401/403 误判为成功。 |
| 167 | 178 | ||
| 168 | **认证头** 通过 axios 拦截器自动添加 (`/src/utils/axios.js`): | 179 | **认证头** 通过 axios 拦截器自动添加 (`/src/utils/axios.js`): |
| 180 | + | ||
| 169 | - 每次请求读取 `localStorage.user_info` | 181 | - 每次请求读取 `localStorage.user_info` |
| 170 | - 设置 `User-Id` 和 `User-Token` 请求头 | 182 | - 设置 `User-Id` 和 `User-Token` 请求头 |
| 171 | - 401 响应处理:仅当当前路由需要认证时才重定向到登录页 (见 guards.js 中的 `checkAuth`) | 183 | - 401 响应处理:仅当当前路由需要认证时才重定向到登录页 (见 guards.js 中的 `checkAuth`) |
| ... | @@ -173,17 +185,20 @@ src/ | ... | @@ -173,17 +185,20 @@ src/ |
| 173 | ### 组件分层 | 185 | ### 组件分层 |
| 174 | 186 | ||
| 175 | **视图页面** (`/src/views/`): 页面级组件,负责编排: | 187 | **视图页面** (`/src/views/`): 页面级组件,负责编排: |
| 188 | + | ||
| 176 | - 通过 API 获取数据 | 189 | - 通过 API 获取数据 |
| 177 | - 通过 refs/composables 管理状态 | 190 | - 通过 refs/composables 管理状态 |
| 178 | - 路由和导航 | 191 | - 路由和导航 |
| 179 | - 集成更小的组件 | 192 | - 集成更小的组件 |
| 180 | 193 | ||
| 181 | **UI 组件** (`/src/components/ui/`): 可复用、无状态或自包含: | 194 | **UI 组件** (`/src/components/ui/`): 可复用、无状态或自包含: |
| 195 | + | ||
| 182 | - VideoPlayer, AudioPlayer, CheckInDialog, SharePoster, SearchBar 等 | 196 | - VideoPlayer, AudioPlayer, CheckInDialog, SharePoster, SearchBar 等 |
| 183 | - 应尽可能避免直接调用 API | 197 | - 应尽可能避免直接调用 API |
| 184 | - 通过 emit 事件向父组件传递操作 | 198 | - 通过 emit 事件向父组件传递操作 |
| 185 | 199 | ||
| 186 | **组合式函数** (`/src/composables/`): 可复用逻辑: | 200 | **组合式函数** (`/src/composables/`): 可复用逻辑: |
| 201 | + | ||
| 187 | - `useAuth`: 认证状态管理与登录流程 | 202 | - `useAuth`: 认证状态管理与登录流程 |
| 188 | - `useCheckin`: 打卡流程和提交(七牛上传、校验、提交) | 203 | - `useCheckin`: 打卡流程和提交(七牛上传、校验、提交) |
| 189 | - `useCheckinDraft`: 打卡草稿自动缓存(防止数据丢失) | 204 | - `useCheckinDraft`: 打卡草稿自动缓存(防止数据丢失) |
| ... | @@ -193,7 +208,27 @@ src/ | ... | @@ -193,7 +208,27 @@ src/ |
| 193 | 208 | ||
| 194 | ### 路由与认证 | 209 | ### 路由与认证 |
| 195 | 210 | ||
| 211 | +> **⚠️ CRITICAL: 本项目使用 Hash 模式路由** | ||
| 212 | +> | ||
| 213 | +> 在生成任何路由 URL 前,必须确认使用 `/#/` 前缀: | ||
| 214 | +> | ||
| 215 | +> - ✅ 正确:`/#/login`, `/#/profile`, `/#/courses/123` | ||
| 216 | +> - ❌ 错误:`/login`, `/profile`, `/courses/123` | ||
| 217 | +> | ||
| 218 | +> 配置来源:`src/router/index.js:15` | ||
| 219 | +> | ||
| 220 | +> ```javascript | ||
| 221 | +> history: createWebHashHistory(import.meta.env.VITE_BASE || '/') | ||
| 222 | +> ``` | ||
| 223 | +> | ||
| 224 | +> 此规则适用于: | ||
| 225 | +> | ||
| 226 | +> - 代码生成:`router.push()`, `<router-link to="">` | ||
| 227 | +> - 测试编写:Playwright `page.goto()`, Vitest 路由测试 | ||
| 228 | +> - 文档编写:所有示例 URL | ||
| 229 | + | ||
| 196 | **路由** 使用懒加载进行代码分割: | 230 | **路由** 使用懒加载进行代码分割: |
| 231 | + | ||
| 197 | ```javascript | 232 | ```javascript |
| 198 | { | 233 | { |
| 199 | path: '/courses/:id', | 234 | path: '/courses/:id', |
| ... | @@ -202,6 +237,7 @@ src/ | ... | @@ -202,6 +237,7 @@ src/ |
| 202 | ``` | 237 | ``` |
| 203 | 238 | ||
| 204 | **认证守卫** (`/src/router/guards.js`): | 239 | **认证守卫** (`/src/router/guards.js`): |
| 240 | + | ||
| 205 | - `checkAuth(route)` - 返回 `true` 或重定向对象 | 241 | - `checkAuth(route)` - 返回 `true` 或重定向对象 |
| 206 | - 不再自动触发微信认证;仅通过登录页手动触发 | 242 | - 不再自动触发微信认证;仅通过登录页手动触发 |
| 207 | - 路由可标记 `meta: { requireAuth: false }` 以公开访问 | 243 | - 路由可标记 `meta: { requireAuth: false }` 以公开访问 |
| ... | @@ -209,45 +245,53 @@ src/ | ... | @@ -209,45 +245,53 @@ src/ |
| 209 | ### 样式策略 | 245 | ### 样式策略 |
| 210 | 246 | ||
| 211 | **TailwindCSS** 是主要样式方案: | 247 | **TailwindCSS** 是主要样式方案: |
| 248 | + | ||
| 212 | - 用于布局、间距、排版、颜色 | 249 | - 用于布局、间距、排版、颜色 |
| 213 | - 自定义颜色定义在 `tailwind.config.js` 中 (primary: #4caf50) | 250 | - 自定义颜色定义在 `tailwind.config.js` 中 (primary: #4caf50) |
| 214 | 251 | ||
| 215 | **Less** 用于组件特定样式: | 252 | **Less** 用于组件特定样式: |
| 253 | + | ||
| 216 | - 组件边界内的嵌套选择器 | 254 | - 组件边界内的嵌套选择器 |
| 217 | - 使用 `<style lang="less" scoped>` 限制作用域到组件 | 255 | - 使用 `<style lang="less" scoped>` 限制作用域到组件 |
| 218 | - 使用 `:deep()` 修改 Vant/VideoJS 内部样式 | 256 | - 使用 `:deep()` 修改 Vant/VideoJS 内部样式 |
| 219 | 257 | ||
| 220 | **自动导入**: | 258 | **自动导入**: |
| 259 | + | ||
| 221 | - Vant 组件自动导入(无需手动导入) | 260 | - Vant 组件自动导入(无需手动导入) |
| 222 | - Vue 组合式函数自动导入 (ref, computed, onMounted 等) | 261 | - Vue 组合式函数自动导入 (ref, computed, onMounted 等) |
| 223 | 262 | ||
| 224 | ### 状态管理模式 | 263 | ### 状态管理模式 |
| 225 | 264 | ||
| 226 | **用户认证状态**: | 265 | **用户认证状态**: |
| 266 | + | ||
| 227 | - 运行时: `contexts/auth.js` → `currentUser` ref | 267 | - 运行时: `contexts/auth.js` → `currentUser` ref |
| 228 | - 持久化: `localStorage.currentUser` 和 `localStorage.user_info` | 268 | - 持久化: `localStorage.currentUser` 和 `localStorage.user_info` |
| 229 | - Axios 请求头: 从 localStorage 派生(在拦截器中) | 269 | - Axios 请求头: 从 localStorage 派生(在拦截器中) |
| 230 | - 辅助函数: `getUserInfoFromStorage()`, `removeUserInfoFromStorage()` | 270 | - 辅助函数: `getUserInfoFromStorage()`, `removeUserInfoFromStorage()` |
| 231 | 271 | ||
| 232 | **购物车/订单状态**: | 272 | **购物车/订单状态**: |
| 273 | + | ||
| 233 | - 运行时: `contexts/cart.js` | 274 | - 运行时: `contexts/cart.js` |
| 234 | - 通过 provide/inject 在页面间保持 | 275 | - 通过 provide/inject 在页面间保持 |
| 235 | 276 | ||
| 236 | ### 微信集成 | 277 | ### 微信集成 |
| 237 | 278 | ||
| 238 | **微信 JS SDK** 初始化在 `App.vue` 中: | 279 | **微信 JS SDK** 初始化在 `App.vue` 中: |
| 280 | + | ||
| 239 | - 仅在浏览器环境中 | 281 | - 仅在浏览器环境中 |
| 240 | - 通过 `wxInfo()` 工具检测环境 | 282 | - 通过 `wxInfo()` 工具检测环境 |
| 241 | - `wxInfo().isWeiXin` - 在微信浏览器中运行 | 283 | - `wxInfo().isWeiXin` - 在微信浏览器中运行 |
| 242 | - `wxInfo().isPc` - 在 PC 上运行 | 284 | - `wxInfo().isPc` - 在 PC 上运行 |
| 243 | 285 | ||
| 244 | **微信认证流程**: | 286 | **微信认证流程**: |
| 287 | + | ||
| 245 | 1. 用户在登录页点击微信图标 | 288 | 1. 用户在登录页点击微信图标 |
| 246 | 2. 重定向到微信 OAuth | 289 | 2. 重定向到微信 OAuth |
| 247 | 3. 回调 → `getUserIsLoginAPI()` → 写入用户信息到存储 | 290 | 3. 回调 → `getUserIsLoginAPI()` → 写入用户信息到存储 |
| 248 | 4. 重定向到原页面或首页 | 291 | 4. 重定向到原页面或首页 |
| 249 | 292 | ||
| 250 | **微信支付**: | 293 | **微信支付**: |
| 294 | + | ||
| 251 | - 集成在结账流程中 | 295 | - 集成在结账流程中 |
| 252 | - 生产环境: 需要微信浏览器环境 (`wxInfo().isWeiXin`) | 296 | - 生产环境: 需要微信浏览器环境 (`wxInfo().isWeiXin`) |
| 253 | - 免费课程: 跳过环境验证 | 297 | - 免费课程: 跳过环境验证 |
| ... | @@ -255,17 +299,21 @@ src/ | ... | @@ -255,17 +299,21 @@ src/ |
| 255 | ### 文件预览系统 | 299 | ### 文件预览系统 |
| 256 | 300 | ||
| 257 | **PDF 预览**: | 301 | **PDF 预览**: |
| 302 | + | ||
| 258 | - 组件: `@sunsetglow/vue-pdf-viewer` | 303 | - 组件: `@sunsetglow/vue-pdf-viewer` |
| 259 | - 专用路由: `/pdfPreview` 用于浏览器内预览 | 304 | - 专用路由: `/pdfPreview` 用于浏览器内预览 |
| 260 | 305 | ||
| 261 | **Office 文档** (Word, Excel, PPT): | 306 | **Office 文档** (Word, Excel, PPT): |
| 307 | + | ||
| 262 | - `@vue-office/docx`, `@vue-office/excel`, `@vue-office/pptx` | 308 | - `@vue-office/docx`, `@vue-office/excel`, `@vue-office/pptx` |
| 263 | - 弹窗预览组件,带错误处理 | 309 | - 弹窗预览组件,带错误处理 |
| 264 | 310 | ||
| 265 | **图片**: | 311 | **图片**: |
| 312 | + | ||
| 266 | - 使用 Vant 的 `van-image-preview` | 313 | - 使用 Vant 的 `van-image-preview` |
| 267 | 314 | ||
| 268 | **视频播放器**: | 315 | **视频播放器**: |
| 316 | + | ||
| 269 | - 基于 `@videojs-player/vue` (Video.js 7.21.7 的封装) | 317 | - 基于 `@videojs-player/vue` (Video.js 7.21.7 的封装) |
| 270 | - 自定义 `VideoPlayer.vue` 组件添加错误处理、自动重试和播放速率控制 | 318 | - 自定义 `VideoPlayer.vue` 组件添加错误处理、自动重试和播放速率控制 |
| 271 | 319 | ||
| ... | @@ -274,6 +322,7 @@ src/ | ... | @@ -274,6 +322,7 @@ src/ |
| 274 | ### 视频播放器问题 | 322 | ### 视频播放器问题 |
| 275 | 323 | ||
| 276 | **VideoPlayer 使用 `v-show` vs `v-if`**: | 324 | **VideoPlayer 使用 `v-show` vs `v-if`**: |
| 325 | + | ||
| 277 | - Video.js 在 `v-show` (display: none) 下无法正常工作 | 326 | - Video.js 在 `v-show` (display: none) 下无法正常工作 |
| 278 | - 使用 `v-if` 确保视频元素在初始化前完全挂载 | 327 | - 使用 `v-if` 确保视频元素在初始化前完全挂载 |
| 279 | - 参考 `StudyDetailPage.vue` 中的正确模式:设置 `isPlaying = true`,然后 `setTimeout` 再调用 `play()` | 328 | - 参考 `StudyDetailPage.vue` 中的正确模式:设置 `isPlaying = true`,然后 `setTimeout` 再调用 `play()` |
| ... | @@ -281,6 +330,7 @@ src/ | ... | @@ -281,6 +330,7 @@ src/ |
| 281 | ### 401 响应处理 | 330 | ### 401 响应处理 |
| 282 | 331 | ||
| 283 | `src/utils/axios.js` 中的 axios 响应拦截器: | 332 | `src/utils/axios.js` 中的 axios 响应拦截器: |
| 333 | + | ||
| 284 | - 仅当 `checkAuth(to)` 返回重定向时才跳转到登录页 | 334 | - 仅当 `checkAuth(to)` 返回重定向时才跳转到登录页 |
| 285 | - 公开页面(如课程详情)即使在 401 时也保持当前页 | 335 | - 公开页面(如课程详情)即使在 401 时也保持当前页 |
| 286 | - 页面组件应为自己的用户操作处理 401 | 336 | - 页面组件应为自己的用户操作处理 401 |
| ... | @@ -288,6 +338,7 @@ src/ | ... | @@ -288,6 +338,7 @@ src/ |
| 288 | ### API 响应安全性 | 338 | ### API 响应安全性 |
| 289 | 339 | ||
| 290 | 始终检查 `res.code === 1`(而不仅仅是 `res.code`): | 340 | 始终检查 `res.code === 1`(而不仅仅是 `res.code`): |
| 341 | + | ||
| 291 | ```javascript | 342 | ```javascript |
| 292 | // 正确 | 343 | // 正确 |
| 293 | const { code, data } = await getCourseDetailAPI({ i: courseId }) | 344 | const { code, data } = await getCourseDetailAPI({ i: courseId }) |
| ... | @@ -300,6 +351,7 @@ if (code) { ... } | ... | @@ -300,6 +351,7 @@ if (code) { ... } |
| 300 | ### 组件自动导入 | 351 | ### 组件自动导入 |
| 301 | 352 | ||
| 302 | Vant 组件通过 `unplugin-vue-components` 自动导入: | 353 | Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 354 | + | ||
| 303 | - **不要导入** `import { Button } from 'vant'` | 355 | - **不要导入** `import { Button } from 'vant'` |
| 304 | - 直接使用 `<van-button>` | 356 | - 直接使用 `<van-button>` |
| 305 | - 参见 `src/components.d.ts` 查看所有可用组件 | 357 | - 参见 `src/components.d.ts` 查看所有可用组件 |
| ... | @@ -307,22 +359,26 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: | ... | @@ -307,22 +359,26 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 307 | ### 打卡系统 | 359 | ### 打卡系统 |
| 308 | 360 | ||
| 309 | **核心 composable**: `useCheckin.js` 封装打卡流程: | 361 | **核心 composable**: `useCheckin.js` 封装打卡流程: |
| 362 | + | ||
| 310 | - 文件上传:七牛云 + Hash 秒传(避免重复上传) | 363 | - 文件上传:七牛云 + Hash 秒传(避免重复上传) |
| 311 | - 支持类型:文本、图片、视频、音频、计数打卡 | 364 | - 支持类型:文本、图片、视频、音频、计数打卡 |
| 312 | - 数据校验:文件大小、数量、类型验证 | 365 | - 数据校验:文件大小、数量、类型验证 |
| 313 | - 提交流程:新增/编辑统一处理 | 366 | - 提交流程:新增/编辑统一处理 |
| 314 | 367 | ||
| 315 | **草稿缓存**: `useCheckinDraft.js` 防止数据丢失: | 368 | **草稿缓存**: `useCheckinDraft.js` 防止数据丢失: |
| 369 | + | ||
| 316 | - 自动保存:输入内容实时缓存到 localStorage | 370 | - 自动保存:输入内容实时缓存到 localStorage |
| 317 | - 智能恢复:页面加载时自动填充上次未提交的内容 | 371 | - 智能恢复:页面加载时自动填充上次未提交的内容 |
| 318 | - 清理机制:提交成功或手动清除时移除草稿 | 372 | - 清理机制:提交成功或手动清除时移除草稿 |
| 319 | 373 | ||
| 320 | **统一组件**: `CheckInDialog.vue` 处理所有打卡流程: | 374 | **统一组件**: `CheckInDialog.vue` 处理所有打卡流程: |
| 375 | + | ||
| 321 | - 接收 `items_today` 和 `items_history` props | 376 | - 接收 `items_today` 和 `items_history` props |
| 322 | - 每项包含 `id`, `name`, `task_type` ('checkin'/'upload'), `is_gray` | 377 | - 每项包含 `id`, `name`, `task_type` ('checkin'/'upload'), `is_gray` |
| 323 | - 完成时触发 `check-in-success` 事件 | 378 | - 完成时触发 `check-in-success` 事件 |
| 324 | 379 | ||
| 325 | **可复用列表组件**: `CheckInList.vue` | 380 | **可复用列表组件**: `CheckInList.vue` |
| 381 | + | ||
| 326 | - 用于首页和 CheckInDialog | 382 | - 用于首页和 CheckInDialog |
| 327 | - 对话框使用紧凑模式,页面使用正常模式 | 383 | - 对话框使用紧凑模式,页面使用正常模式 |
| 328 | - `submit-success` 事件向上冒泡 | 384 | - `submit-success` 事件向上冒泡 |
| ... | @@ -330,6 +386,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: | ... | @@ -330,6 +386,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 330 | ### 环境配置 | 386 | ### 环境配置 |
| 331 | 387 | ||
| 332 | 使用 `.env.development`, `.env.production` 等: | 388 | 使用 `.env.development`, `.env.production` 等: |
| 389 | + | ||
| 333 | - `VITE_PORT` - 开发服务器端口 | 390 | - `VITE_PORT` - 开发服务器端口 |
| 334 | - `VITE_BASE` - 基础路径 | 391 | - `VITE_BASE` - 基础路径 |
| 335 | - `VITE_PROXY_PREFIX` - API 代理前缀 | 392 | - `VITE_PROXY_PREFIX` - API 代理前缀 |
| ... | @@ -337,6 +394,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: | ... | @@ -337,6 +394,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 337 | - `VITE_OUTDIR` - 构建输出目录 | 394 | - `VITE_OUTDIR` - 构建输出目录 |
| 338 | 395 | ||
| 339 | **环境与部署**: | 396 | **环境与部署**: |
| 397 | + | ||
| 340 | - **开发环境**: `http://oa-dev.onwall.cn/` | 398 | - **开发环境**: `http://oa-dev.onwall.cn/` |
| 341 | - **生产环境**: `http://oa.onwall.cn` | 399 | - **生产环境**: `http://oa.onwall.cn` |
| 342 | - **部署流程**: 构建 → 归档(tar.gz) → SCP 上传 → 服务器解压 → 清理归档 | 400 | - **部署流程**: 构建 → 归档(tar.gz) → SCP 上传 → 服务器解压 → 清理归档 |
| ... | @@ -345,6 +403,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: | ... | @@ -345,6 +403,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 345 | ### Speckit 框架命令 | 403 | ### Speckit 框架命令 |
| 346 | 404 | ||
| 347 | 位于 `.cursor/commands/`: | 405 | 位于 `.cursor/commands/`: |
| 406 | + | ||
| 348 | - `/speckit.specify` - 从需求生成规范 | 407 | - `/speckit.specify` - 从需求生成规范 |
| 349 | - `/speckit.plan` - 创建实现计划 | 408 | - `/speckit.plan` - 创建实现计划 |
| 350 | - `/speckit.tasks` - 生成任务分解 | 409 | - `/speckit.tasks` - 生成任务分解 |
| ... | @@ -357,6 +416,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: | ... | @@ -357,6 +416,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 357 | ### 代码风格说明 (来自 VUE_CODE_STYLE_GUIDE.md) | 416 | ### 代码风格说明 (来自 VUE_CODE_STYLE_GUIDE.md) |
| 358 | 417 | ||
| 359 | **当前实践**: | 418 | **当前实践**: |
| 419 | + | ||
| 360 | - 新组件使用 `<script setup>` 和 Composition API | 420 | - 新组件使用 `<script setup>` 和 Composition API |
| 361 | - API 函数应始终返回 `{ code, data, msg }` - 永不返回 `false` | 421 | - API 函数应始终返回 `{ code, data, msg }` - 永不返回 `false` |
| 362 | - 尽可能将逻辑提取到 composables (`/src/composables/useXxx.js`) | 422 | - 尽可能将逻辑提取到 composables (`/src/composables/useXxx.js`) |
| ... | @@ -364,6 +424,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: | ... | @@ -364,6 +424,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 364 | - 除非必要避免 JSX;大多数页面使用 Vue 模板 | 424 | - 除非必要避免 JSX;大多数页面使用 Vue 模板 |
| 365 | 425 | ||
| 366 | **已知不一致性** (需注意): | 426 | **已知不一致性** (需注意): |
| 427 | + | ||
| 367 | - 文件中混合使用分号/无分号 | 428 | - 文件中混合使用分号/无分号 |
| 368 | - 混合使用 2 空格/4 空格缩进 | 429 | - 混合使用 2 空格/4 空格缩进 |
| 369 | - 混合使用 function/箭头函数 | 430 | - 混合使用 function/箭头函数 |
| ... | @@ -372,6 +433,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: | ... | @@ -372,6 +433,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 372 | - 部分页面组件过大,应考虑拆分 | 433 | - 部分页面组件过大,应考虑拆分 |
| 373 | 434 | ||
| 374 | **改进建议**: | 435 | **改进建议**: |
| 436 | + | ||
| 375 | - 统一使用 ESLint + Prettier 进行代码格式化 | 437 | - 统一使用 ESLint + Prettier 进行代码格式化 |
| 376 | - 制定统一的 API 返回值规范 | 438 | - 制定统一的 API 返回值规范 |
| 377 | - 大型页面组件应拆分为多个子组件 | 439 | - 大型页面组件应拆分为多个子组件 |
| ... | @@ -380,16 +442,19 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: | ... | @@ -380,16 +442,19 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 380 | ## 特殊功能 | 442 | ## 特殊功能 |
| 381 | 443 | ||
| 382 | ### 分享海报生成 | 444 | ### 分享海报生成 |
| 445 | + | ||
| 383 | - 基于 Canvas 的海报生成,带二维码 (`qrcode` 包) | 446 | - 基于 Canvas 的海报生成,带二维码 (`qrcode` 包) |
| 384 | - 自动优化 `cdn.ipadbiz.cn` URL 的图片 | 447 | - 自动优化 `cdn.ipadbiz.cn` URL 的图片 |
| 385 | - 使用 `crossorigin="anonymous"` 处理跨域 Canvas 污染 | 448 | - 使用 `crossorigin="anonymous"` 处理跨域 Canvas 污染 |
| 386 | - 组件: `SharePoster.vue` (可复用) | 449 | - 组件: `SharePoster.vue` (可复用) |
| 387 | 450 | ||
| 388 | ### 动态 Open Graph 元标签 | 451 | ### 动态 Open Graph 元标签 |
| 452 | + | ||
| 389 | - 课程详情页动态添加 `og:title`, `og:description`, `og:image`, `og:url` | 453 | - 课程详情页动态添加 `og:title`, `og:description`, `og:image`, `og:url` |
| 390 | - 页面卸载时移除标签以避免冲突 | 454 | - 页面卸载时移除标签以避免冲突 |
| 391 | 455 | ||
| 392 | ### 标签指示器动画 | 456 | ### 标签指示器动画 |
| 457 | + | ||
| 393 | - 基于 ResizeObserver 的指示器定位 | 458 | - 基于 ResizeObserver 的指示器定位 |
| 394 | - 处理异步加载第三列而不错位 | 459 | - 处理异步加载第三列而不错位 |
| 395 | - 参考 `StudyCoursePage.vue` 的实现 | 460 | - 参考 `StudyCoursePage.vue` 的实现 |
| ... | @@ -491,6 +556,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: | ... | @@ -491,6 +556,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 491 | ### Q: 为什么 Video.js 在切换显示/隐藏时无法正常工作? | 556 | ### Q: 为什么 Video.js 在切换显示/隐藏时无法正常工作? |
| 492 | 557 | ||
| 493 | **A**: Video.js 在 `v-show` (display: none) 状态下初始化会失败。 | 558 | **A**: Video.js 在 `v-show` (display: none) 状态下初始化会失败。 |
| 559 | + | ||
| 494 | - **解决**: 使用 `v-if` 而非 `v-show` | 560 | - **解决**: 使用 `v-if` 而非 `v-show` |
| 495 | - **正确模式**: 设置 `isPlaying = true`,然后 `setTimeout` 再调用 `play()` | 561 | - **正确模式**: 设置 `isPlaying = true`,然后 `setTimeout` 再调用 `play()` |
| 496 | - **参考**: `StudyDetailPage.vue` 中的实现 | 562 | - **参考**: `StudyDetailPage.vue` 中的实现 |
| ... | @@ -498,6 +564,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: | ... | @@ -498,6 +564,7 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 498 | ### Q: 为什么我的 401 响应导致页面跳转到登录页? | 564 | ### Q: 为什么我的 401 响应导致页面跳转到登录页? |
| 499 | 565 | ||
| 500 | **A**: 这是预期的行为,但仅对需要认证的页面生效。 | 566 | **A**: 这是预期的行为,但仅对需要认证的页面生效。 |
| 567 | + | ||
| 501 | - 公开页面(如课程详情)不会跳转 | 568 | - 公开页面(如课程详情)不会跳转 |
| 502 | - 受限页面会清理登录信息并重定向 | 569 | - 受限页面会清理登录信息并重定向 |
| 503 | - 如需自定义处理,在组件内监听 API 错误 | 570 | - 如需自定义处理,在组件内监听 API 错误 |
| ... | @@ -505,18 +572,24 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: | ... | @@ -505,18 +572,24 @@ Vant 组件通过 `unplugin-vue-components` 自动导入: |
| 505 | ### Q: 如何正确检查 API 响应是否成功? | 572 | ### Q: 如何正确检查 API 响应是否成功? |
| 506 | 573 | ||
| 507 | **A**: 始终使用 `if (res.code === 1)` 而非 `if (res.code)`。 | 574 | **A**: 始终使用 `if (res.code === 1)` 而非 `if (res.code)`。 |
| 575 | + | ||
| 508 | ```javascript | 576 | ```javascript |
| 509 | // ✓ 正确 | 577 | // ✓ 正确 |
| 510 | const { code, data } = await getCourseDetailAPI({ i: courseId }) | 578 | const { code, data } = await getCourseDetailAPI({ i: courseId }) |
| 511 | -if (code === 1) { /* 成功 */ } | 579 | +if (code === 1) { |
| 580 | + /* 成功 */ | ||
| 581 | +} | ||
| 512 | 582 | ||
| 513 | // ✗ 错误 - 会将 401/403 当作成功 | 583 | // ✗ 错误 - 会将 401/403 当作成功 |
| 514 | -if (code) { /* ... */ } | 584 | +if (code) { |
| 585 | + /* ... */ | ||
| 586 | +} | ||
| 515 | ``` | 587 | ``` |
| 516 | 588 | ||
| 517 | ### Q: 为什么 Vant 组件导入后报错或无法使用? | 589 | ### Q: 为什么 Vant 组件导入后报错或无法使用? |
| 518 | 590 | ||
| 519 | **A**: Vant 组件已配置自动导入,无需手动导入。 | 591 | **A**: Vant 组件已配置自动导入,无需手动导入。 |
| 592 | + | ||
| 520 | ```javascript | 593 | ```javascript |
| 521 | // ✗ 错误 | 594 | // ✗ 错误 |
| 522 | import { Button } from 'vant' | 595 | import { Button } from 'vant' |
| ... | @@ -528,6 +601,7 @@ import { Button } from 'vant' | ... | @@ -528,6 +601,7 @@ import { Button } from 'vant' |
| 528 | ### Q: 如何在微信环境下正确处理支付和授权? | 601 | ### Q: 如何在微信环境下正确处理支付和授权? |
| 529 | 602 | ||
| 530 | **A**: 使用 `wxInfo()` 工具函数检测环境: | 603 | **A**: 使用 `wxInfo()` 工具函数检测环境: |
| 604 | + | ||
| 531 | ```javascript | 605 | ```javascript |
| 532 | import { wxInfo } from '@/utils/tools' | 606 | import { wxInfo } from '@/utils/tools' |
| 533 | 607 | ||
| ... | @@ -542,6 +616,7 @@ if (wxInfo().isWeiXin) { | ... | @@ -542,6 +616,7 @@ if (wxInfo().isWeiXin) { |
| 542 | ### Q: 打卡时如何避免数据丢失? | 616 | ### Q: 打卡时如何避免数据丢失? |
| 543 | 617 | ||
| 544 | **A**: 使用 `useCheckinDraft` 自动缓存草稿: | 618 | **A**: 使用 `useCheckinDraft` 自动缓存草稿: |
| 619 | + | ||
| 545 | ```javascript | 620 | ```javascript |
| 546 | import { useCheckinDraft } from '@/composables/useCheckinDraft' | 621 | import { useCheckinDraft } from '@/composables/useCheckinDraft' |
| 547 | 622 | ||
| ... | @@ -560,22 +635,26 @@ onSubmitSuccess(() => clearDraft()) | ... | @@ -560,22 +635,26 @@ onSubmitSuccess(() => clearDraft()) |
| 560 | ### 最近重要更新 | 635 | ### 最近重要更新 |
| 561 | 636 | ||
| 562 | **打卡系统增强** (2025): | 637 | **打卡系统增强** (2025): |
| 638 | + | ||
| 563 | - 打卡详情页重构 (`/checkin/detail`):统一文本、媒体上传和计数打卡入口 | 639 | - 打卡详情页重构 (`/checkin/detail`):统一文本、媒体上传和计数打卡入口 |
| 564 | - 新增草稿缓存功能 (`useCheckinDraft`):防止数据丢失,自动保存和恢复 | 640 | - 新增草稿缓存功能 (`useCheckinDraft`):防止数据丢失,自动保存和恢复 |
| 565 | - 优化附件预览和编辑回填逻辑 | 641 | - 优化附件预览和编辑回填逻辑 |
| 566 | - 实现基于 `useCheckin` 的通用提交流程 | 642 | - 实现基于 `useCheckin` 的通用提交流程 |
| 567 | 643 | ||
| 568 | **教师端完善** (2025): | 644 | **教师端完善** (2025): |
| 645 | + | ||
| 569 | - 作业管理页面 (`/teacher/tasks`) | 646 | - 作业管理页面 (`/teacher/tasks`) |
| 570 | - 作业主页 (`/teacher/tasks/:id`):统计与日历视图 | 647 | - 作业主页 (`/teacher/tasks/:id`):统计与日历视图 |
| 571 | - 学员作业记录页面 (`/teacher/student-record`) | 648 | - 学员作业记录页面 (`/teacher/student-record`) |
| 572 | 649 | ||
| 573 | **召回系统** (2025): | 650 | **召回系统** (2025): |
| 651 | + | ||
| 574 | - 身份证查询历史功能 | 652 | - 身份证查询历史功能 |
| 575 | - 时光机旅程 (Timeline) 展示用户里程碑 | 653 | - 时光机旅程 (Timeline) 展示用户里程碑 |
| 576 | - 专属海报生成与分享 | 654 | - 专属海报生成与分享 |
| 577 | 655 | ||
| 578 | **基础体验优化** (2025): | 656 | **基础体验优化** (2025): |
| 657 | + | ||
| 579 | - 登录逻辑调整:仅在点击微信图标时触发授权 | 658 | - 登录逻辑调整:仅在点击微信图标时触发授权 |
| 580 | - 搜索栏优化:支持 iOS 软键盘搜索键 | 659 | - 搜索栏优化:支持 iOS 软键盘搜索键 |
| 581 | - 课程详情页:动态 Open Graph 标签,优化分享体验 | 660 | - 课程详情页:动态 Open Graph 标签,优化分享体验 |
| ... | @@ -713,7 +792,7 @@ onSubmitSuccess(() => clearDraft()) | ... | @@ -713,7 +792,7 @@ onSubmitSuccess(() => clearDraft()) |
| 713 | 项目包含 **12个主要组件目录**,涵盖了从基础 UI 到业务功能的完整组件体系: | 792 | 项目包含 **12个主要组件目录**,涵盖了从基础 UI 到业务功能的完整组件体系: |
| 714 | 793 | ||
| 715 | | 目录 | 组件示例 | 说明 | | 794 | | 目录 | 组件示例 | 说明 | |
| 716 | -|------|---------|------| | 795 | +| -------------- | ------------------------------------------------------------------------- | -------------- | |
| 717 | | `ui/` | `VideoPlayer`, `AudioPlayer`, `CheckInDialog`, `SharePoster`, `SearchBar` | 通用 UI 组件库 | | 796 | | `ui/` | `VideoPlayer`, `AudioPlayer`, `CheckInDialog`, `SharePoster`, `SearchBar` | 通用 UI 组件库 | |
| 718 | | `checkin/` | `CheckInDialog`, `CheckInList`, `CheckInResult` | 打卡业务组件 | | 797 | | `checkin/` | `CheckInDialog`, `CheckInList`, `CheckInResult` | 打卡业务组件 | |
| 719 | | `media/` | `AudioPlayer`, `VideoPlayer`, `MusicPlayer` | 音视频播放组件 | | 798 | | `media/` | `AudioPlayer`, `VideoPlayer`, `MusicPlayer` | 音视频播放组件 | | ... | ... |
-
Please register or login to post a comment