docs: 新增项目文档和开发计划
添加项目架构、组件索引、变更记录、工作流和开发计划等文档 - ARCHITECTURE.md: 系统架构与工程配置说明 - COMPONENTS.md: 组件目录索引与说明 - CHANGELOG.md: 功能更新记录 - 工作流.md: 开发工作流程指南 - 暂存用户打卡信息.md: 草稿功能详细规划 - 26.1.26新功能开发计划.md: 打卡互动功能规划 - TODO/26.1.26新功能.md: 功能开发清单
Showing
7 changed files
with
690 additions
and
0 deletions
docs/ARCHITECTURE.md
0 → 100644
| 1 | +# 架构实现与工程配置 | ||
| 2 | + | ||
| 3 | +## 入口与初始化 | ||
| 4 | + | ||
| 5 | +- 应用入口:[/src/main.js](file:///Users/huyirui/program/itomix/git/mlaj/src/main.js) | ||
| 6 | + - 创建 App、注册全局 Icon 组件、挂载路由 | ||
| 7 | + - 全局注入 axios 到 app.config.globalProperties.$http | ||
| 8 | +- 根组件:[/src/App.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/App.vue) | ||
| 9 | + - 初始化全局认证与购物车:provideAuth / provideCart | ||
| 10 | + - 生产环境 + 微信环境:初始化微信 JSSDK(配置签名 URL) | ||
| 11 | + - 生产环境:版本更新探测(弹窗提示刷新) | ||
| 12 | + | ||
| 13 | +## 路由与权限 | ||
| 14 | + | ||
| 15 | +- 路由入口:[/src/router/index.js](file:///Users/huyirui/program/itomix/git/mlaj/src/router/index.js) | ||
| 16 | + - Hash 路由:createWebHashHistory(import.meta.env.VITE_BASE || '/') | ||
| 17 | + - beforeEach:统一登录页回跳处理,并在必要时探测“是否已登录” | ||
| 18 | +- 鉴权策略:[/src/router/guards.js](file:///Users/huyirui/program/itomix/git/mlaj/src/router/guards.js) | ||
| 19 | + - 白名单 + meta.requiresAuth 双策略判断 | ||
| 20 | + - 未登录时重定向 /login 并带 redirect | ||
| 21 | +- 微信授权策略:[/src/router/guards.js](file:///Users/huyirui/program/itomix/git/mlaj/src/router/guards.js) | ||
| 22 | + - 不在路由守卫自动触发授权,避免循环 | ||
| 23 | + - 仅在用户触发(如点击微信图标/购买流程探测)时调用 startWxAuth | ||
| 24 | + | ||
| 25 | +## 请求与登录态注入 | ||
| 26 | + | ||
| 27 | +- Axios 封装:[/src/utils/axios.js](file:///Users/huyirui/program/itomix/git/mlaj/src/utils/axios.js) | ||
| 28 | + - 请求拦截:动态读取本地 user_info 并注入 User-Id / User-Token | ||
| 29 | + - 响应拦截:code=401 时,仅当当前路由确实需要登录才跳转登录(公开页面不强制跳转) | ||
| 30 | +- 登录态管理:[/src/contexts/auth.js](file:///Users/huyirui/program/itomix/git/mlaj/src/contexts/auth.js) | ||
| 31 | + - provide/inject 维护 currentUser/loading/login/logout | ||
| 32 | + - localStorage 持久化 currentUser | ||
| 33 | + - 初始化流程中会探测授权/登录态并拉取用户信息 | ||
| 34 | + | ||
| 35 | +## 购物车与结算 | ||
| 36 | + | ||
| 37 | +- 购物车上下文:[/src/contexts/cart.js](file:///Users/huyirui/program/itomix/git/mlaj/src/contexts/cart.js) | ||
| 38 | + - 单品/多品两种模式(App.vue 默认使用单品模式) | ||
| 39 | + - localStorage 带时间戳的过期策略(默认一天过期) | ||
| 40 | + - handleCheckout 负责构建订单数据并提交订单 | ||
| 41 | + | ||
| 42 | +## 上传与预览 | ||
| 43 | + | ||
| 44 | +- 上传工具:[/src/utils/upload.js](file:///Users/huyirui/program/itomix/git/mlaj/src/utils/upload.js)、[/src/utils/qiniuFileHash.js](file:///Users/huyirui/program/itomix/git/mlaj/src/utils/qiniuFileHash.js) | ||
| 45 | + - 前端计算文件 Hash,支持“秒传检测 + 直传对象存储” | ||
| 46 | +- 关键业务复用:[/src/composables/useCheckin.js](file:///Users/huyirui/program/itomix/git/mlaj/src/composables/useCheckin.js) | ||
| 47 | + - 打卡/作业提交流程:校验、上传、提交、编辑回填等 | ||
| 48 | +- 预览组件:[/src/components/media](file:///Users/huyirui/program/itomix/git/mlaj/src/components/media) | ||
| 49 | + - 视频/音频播放器、PDF/Office 预览等 | ||
| 50 | + | ||
| 51 | +## Vite 配置与环境变量 | ||
| 52 | + | ||
| 53 | +- Vite 配置:[/vite.config.js](file:///Users/huyirui/program/itomix/git/mlaj/vite.config.js) | ||
| 54 | + - 自动按需引入 Vant 组件(unplugin-auto-import / unplugin-vue-components) | ||
| 55 | + - 别名:@ / @components / @utils / @api 等 | ||
| 56 | + - 本地代理:createProxy(viteEnv.VITE_PROXY_PREFIX, viteEnv.VITE_PROXY_TARGET) | ||
| 57 | +- 环境变量示例:[.env](file:///Users/huyirui/program/itomix/git/mlaj/.env) | ||
| 58 | + - VITE_PORT:开发端口 | ||
| 59 | + - VITE_PROXY_TARGET / VITE_PROXY_PREFIX:接口代理目标与前缀 | ||
| 60 | + - VITE_OUTDIR:构建输出目录 | ||
| 61 | + - VITE_CONSOLE:调试开关 | ||
| 62 | + | ||
| 63 | +## 目录结构(详细) | ||
| 64 | + | ||
| 65 | +``` | ||
| 66 | +mlaj/ | ||
| 67 | +├── build/ # Vite 代理封装 | ||
| 68 | +├── docs/ # 项目文档(本目录) | ||
| 69 | +├── public/ # 静态资源 | ||
| 70 | +├── src/ | ||
| 71 | +│ ├── api/ # 按业务域拆分的接口封装(auth/course/checkin/teacher/...) | ||
| 72 | +│ ├── assets/ # 图片等资源 | ||
| 73 | +│ ├── common/ # 常量 | ||
| 74 | +│ ├── components/ # 组件(按业务域归类) | ||
| 75 | +│ ├── composables/ # 组合式函数(逻辑复用,含单测) | ||
| 76 | +│ ├── contexts/ # 全局状态(provide/inject:auth/cart) | ||
| 77 | +│ ├── router/ # 路由定义与守卫 | ||
| 78 | +│ ├── utils/ # 工具层(axios、上传、鉴权存储、版本更新等) | ||
| 79 | +│ └── views/ # 页面(按业务域归类) | ||
| 80 | +├── tailwind.config.js # Tailwind 配置 | ||
| 81 | +└── vite.config.js # Vite 配置 | ||
| 82 | +``` |
docs/CHANGELOG.md
0 → 100644
| 1 | +# 功能更新记录(Recent Changes) | ||
| 2 | + | ||
| 3 | +说明:该章节从 README 迁移到本文件,避免 README 过长。后续新增变更建议追加在文件顶部。 | ||
| 4 | + | ||
| 5 | + | ||
| 6 | +## 2026-01-26 13:40:00 | ||
| 7 | +- 优化打卡卡片组件(CheckinCard): | ||
| 8 | + - 增加长文本折叠功能:内容超过5行自动显示省略号,并提供“全文/收起”切换按钮 | ||
| 9 | + - 增加多附件Tab切换功能:当同时存在多种附件(图片、视频、音频)时,使用 Tab 标签页切换展示,避免页面过长 | ||
| 10 | + | ||
| 11 | +## 2026-01-26 11:43:00 | ||
| 12 | +- 新增草稿恢复时的作业有效性校验:若草稿对应的作业已失效(不在当前任务列表中),则弹窗提示并自动清理该草稿 | ||
| 13 | + | ||
| 14 | +## 2026-01-25 19:28:00 | ||
| 15 | + | ||
| 16 | +- 优化项目配置:从 git 版本控制中移除以下文件夹的跟踪,以避免将开发工具配置和生成的文档提交到远程仓库: | ||
| 17 | + - `.claude` | ||
| 18 | + - `.cursor` | ||
| 19 | + - `.specify` | ||
| 20 | + - `.trae` | ||
| 21 | + - `.github` | ||
| 22 | + - `docs` | ||
| 23 | + | ||
| 24 | +## 2026-01-25 | ||
| 25 | + | ||
| 26 | +- 新增「暂存用户打卡信息」开发规划:[/docs/plan/暂存用户打卡信息.md](file:///Users/huyirui/program/itomix/git/mlaj/docs/plan/%E6%9A%82%E5%AD%98%E7%94%A8%E6%88%B7%E6%89%93%E5%8D%A1%E4%BF%A1%E6%81%AF.md) | ||
| 27 | +- 完成「暂存用户打卡信息」功能开发 | ||
| 28 | + - 核心逻辑:[/src/composables/useCheckinDraft.js](file:///Users/huyirui/program/itomix/git/mlaj/src/composables/useCheckinDraft.js) | ||
| 29 | + - 页面集成:[/src/views/checkin/CheckinDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/checkin/CheckinDetailPage.vue) | ||
| 30 | + - 支持自动保存、过期清理、恢复提示 | ||
| 31 | +- 修复打卡详情页 `countValue` 初始化顺序导致的 ReferenceError 报错 | ||
| 32 | +- 修复附件上传成功后未保存 URL 导致草稿恢复后无法预览的问题 | ||
| 33 | +- 优化文件 URL 获取逻辑:移除硬编码默认域名,优先使用接口返回的 URL 或 src,仅在有域名信息时拼接 URL | ||
| 34 | + | ||
| 35 | +## 打卡详情页重构(/checkin/detail) | ||
| 36 | + | ||
| 37 | +- 统一了文本、媒体上传和计数打卡的入口 | ||
| 38 | +- 实现了基于 composables 的通用提交流程:[/src/composables/useCheckin.js](file:///Users/huyirui/program/itomix/git/mlaj/src/composables/useCheckin.js) | ||
| 39 | +- 页面入口:[/src/views/checkin/CheckinDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/checkin/CheckinDetailPage.vue) | ||
| 40 | +- 优化了附件预览与编辑回填逻辑(音频/视频/图片预览能力) | ||
| 41 | + | ||
| 42 | +## 教师端功能完善(/teacher) | ||
| 43 | + | ||
| 44 | +- 新增作业管理页面:/teacher/tasks(列表展示:名称、开始/截止时间) | ||
| 45 | +- 新增作业主页:/teacher/tasks/:id(统计 + 日历视图) | ||
| 46 | +- 新增学员作业记录页:/teacher/student-record(作业帖子 + 点赞/点评) | ||
| 47 | + | ||
| 48 | +## 基础体验优化 | ||
| 49 | + | ||
| 50 | +- 登录逻辑调整:仅在登录页点击微信图标时触发授权(避免路由守卫自动授权导致的循环) | ||
| 51 | + - 关键文件:[/src/router/guards.js](file:///Users/huyirui/program/itomix/git/mlaj/src/router/guards.js)、[/src/router/index.js](file:///Users/huyirui/program/itomix/git/mlaj/src/router/index.js)、[/src/views/auth/LoginPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/auth/LoginPage.vue) | ||
| 52 | +- 搜索栏优化:提升 iOS 软键盘“搜索”键触发稳定性 | ||
| 53 | +- 课程详情页:增加动态 Open Graph 标签,优化分享体验 | ||
| 54 | + | ||
| 55 | +## 课程详情页动态 Open Graph 元标签 | ||
| 56 | + | ||
| 57 | +- 行为:进入课程详情页时,在 head 中插入 og:title / og:description / og:image / og:url;离开页面时移除 | ||
| 58 | +- CDN 规则:图片域名为 cdn.ipadbiz.cn 时,追加 ?imageMogr2/thumbnail/200x/strip/quality/70 | ||
| 59 | +- 位置:[/src/views/courses/CourseDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/courses/CourseDetailPage.vue) | ||
| 60 | + | ||
| 61 | +## 购买流程环境校验与微信授权探测 | ||
| 62 | + | ||
| 63 | +- 行为:仅对非免费课程在详情页点击“购买”时进行校验;生产环境必须为微信内置浏览器 | ||
| 64 | +- 微信环境内:若未完成微信授权(openid_has=false),会自动发起一次微信授权并中止本次购买,授权后再次点击进入结算 | ||
| 65 | +- 位置:[/src/views/courses/CourseDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/courses/CourseDetailPage.vue) | ||
| 66 | + | ||
| 67 | +## 401 拦截策略优化(公开页面不再跳登录) | ||
| 68 | + | ||
| 69 | +- 行为:接口返回 code=401 时,仅当当前路由确实需要登录时才重定向登录 | ||
| 70 | +- 位置:[/src/utils/axios.js](file:///Users/huyirui/program/itomix/git/mlaj/src/utils/axios.js) | ||
| 71 | + | ||
| 72 | +## 搜索栏回车搜索兼容性提升 | ||
| 73 | + | ||
| 74 | +- 行为:输入框类型改为 search,并可选开启 form submit 机制,同时保留 keyup.enter | ||
| 75 | +- 位置:[/src/components/common/SearchBar.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/components/common/SearchBar.vue) | ||
| 76 | + | ||
| 77 | +## 分享海报弹窗(可复用) | ||
| 78 | + | ||
| 79 | +- 入口:课程详情页底部操作栏“分享”按钮 | ||
| 80 | +- 组件:[/src/components/poster/SharePoster.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/components/poster/SharePoster.vue) | ||
| 81 | +- 能力:弹窗打开时通过 Canvas 合成海报(封面、二维码、文案),生成 dataURL 展示,用户长按保存 | ||
| 82 | + | ||
| 83 | +## 打卡弹窗与列表组件(可复用) | ||
| 84 | + | ||
| 85 | +- 打卡弹窗:[/src/components/checkin/CheckInDialog.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/components/checkin/CheckInDialog.vue) | ||
| 86 | +- 打卡列表:[/src/components/checkin/CheckInList.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/components/checkin/CheckInList.vue) |
docs/COMPONENTS.md
0 → 100644
| 1 | +# /src/components 组件目录索引 | ||
| 2 | + | ||
| 3 | +## 目录划分 | ||
| 4 | + | ||
| 5 | +| 目录 | 代表组件 | 说明 | | ||
| 6 | +| --- | --- | --- | | ||
| 7 | +| activity/ | ActivityCard.vue、ActivityApplyHistoryPopup.vue | 活动卡片、报名/历史相关弹窗 | | ||
| 8 | +| calendar/ | CollapsibleCalendar.vue、TaskCalendar.vue | 日历与任务日历组件 | | ||
| 9 | +| checkin/ | CheckInDialog.vue、CheckInList.vue、CheckinCard.vue、UploadVideoPopup.vue | 打卡/作业相关组件(弹窗、列表、卡片、上传) | | ||
| 10 | +| common/ | ConfirmDialog.vue、GradientHeader.vue、SearchBar.vue、UserAgreement.vue | 通用基础组件(确认、头部、搜索、协议) | | ||
| 11 | +| count/ | AddTargetDialog.vue、CheckinTargetList.vue、postCountModel.vue | 计数型打卡相关组件 | | ||
| 12 | +| courses/ | CourseCard.vue、CourseList.vue、LiveStreamCard.vue、ReviewPopup.vue | 课程展示与列表、直播卡片、评价弹窗等 | | ||
| 13 | +| effects/ | FrostedGlass.vue、StarryBackground.vue | 视觉效果组件 | | ||
| 14 | +| homePage/ | FeaturedCoursesSection.vue、LatestActivitiesSection.vue | 首页区块组件(精选/活动/推荐等) | | ||
| 15 | +| infoEntry/ | formPage.vue | 信息录入相关组件 | | ||
| 16 | +| layout/ | AppLayout.vue、BottomNav.vue | 页面布局与底部导航 | | ||
| 17 | +| media/ | AudioPlayer.vue、VideoPlayer.vue、PdfPreview.vue、OfficeViewer.vue | 音视频播放器与文档预览 | | ||
| 18 | +| payment/ | WechatPayment.vue | 微信支付相关组件 | | ||
| 19 | +| poster/ | RecallPoster.vue、SharePoster.vue | 海报生成与分享相关组件 | | ||
| 20 | +| studyDetail/ | StudyCatalogPopup.vue、StudyCommentsSection.vue、StudyMaterialsPopup.vue | 学习详情页的弹窗与评论区 | | ||
| 21 | +| teacher/ | TaskFilter.vue、TaskCascaderFilter.vue | 教师端筛选与任务相关组件 | | ||
| 22 | + | ||
| 23 | +## 备注 | ||
| 24 | + | ||
| 25 | +- 布局目录已归一:统一使用 [/src/components/layout](file:///Users/huyirui/program/itomix/git/mlaj/src/components/layout),已移除 /src/layouts |
docs/TODO/26.1.26新功能.md
0 → 100644
| 1 | +新功能开发list(在.env加上配置开关控制下面设计到的功能点, 比如是否开启置顶功能,开启点评打卡, 开启打卡海报,开启点评列表): | ||
| 2 | + 入口页IndexCheckInPage涉及功能 | ||
| 3 | + - CheckinCard组件 <#footer-right> 3个点的缩略显示, 点击后从底部弹出vant的ActionSheet组件, 面板有置顶和点评两个选项. | ||
| 4 | + 1. 置顶的功能, 使用图标back-top, 点击图标的时候弹出确认弹窗, 确认后调用接口置顶帖子. | ||
| 5 | + 2. 点评打卡作业的功能, 使用图标comment, 点击图标的时候弹出确认弹窗, 确认后调用接口点评打卡作业. 评论弹框做成一个单独的组件以后扩展. | ||
| 6 | + 3. 海报功能, 使用图标share, 可以参考SharePoster组件的实现, 新增一个组件打卡海报, 这个组件最大的不同就是可能生成的图片是一张长图, 如果超过屏幕高度, 用户可以滑动查看. 现在内容和字段还不确定, 等确定了再实现可以先规划. | ||
| 7 | + - 显示点评列表功能, 在CheckinCard组件里面, 需要新增一个组件专门显示用户点评打卡的列表. 类似于朋友圈下面的评论列表, 放在CheckinCard组件的下面. |
docs/plan/26.1.26新功能开发计划.md
0 → 100644
| 1 | +# 26.1.26 新功能开发计划 | ||
| 2 | + | ||
| 3 | +## 背景 | ||
| 4 | + | ||
| 5 | +为了增强用户在打卡列表的互动性和管理能力,需要在打卡卡片(CheckinCard)上增加更多操作选项,包括置顶、评论、海报分享以及展示评论列表。这些功能将通过环境变量开关进行控制,以便按需开启。 | ||
| 6 | + | ||
| 7 | +## 需求拆解 | ||
| 8 | + | ||
| 9 | +1. **全局功能开关**:在 `.env` 文件中通过变量控制各功能的开启/关闭。 | ||
| 10 | +2. **置顶功能 (Top)**:允许将特定打卡内容置顶。 | ||
| 11 | +3. **评论/评论功能 (Comment)**:允许对打卡内容进行评论(评论)。 | ||
| 12 | +4. **打卡海报 (Poster)**:生成包含打卡内容的长图海报,支持分享。 | ||
| 13 | +5. **评论列表 (Comment List)**:在打卡卡片下方显示该打卡的评论/评论记录。 | ||
| 14 | + | ||
| 15 | +## 环境变量规划 | ||
| 16 | + | ||
| 17 | +在 `.env` 中增加以下开关(默认 '0' 关闭,'1' 开启): | ||
| 18 | + | ||
| 19 | +- `VITE_FEATURE_CHECKIN_TOP=1` (置顶功能) | ||
| 20 | +- `VITE_FEATURE_CHECKIN_COMMENT=1` (评论功能) | ||
| 21 | +- `VITE_FEATURE_CHECKIN_POSTER=1` (海报功能) | ||
| 22 | +- `VITE_FEATURE_CHECKIN_COMMENT_LIST=1` (评论列表显示) | ||
| 23 | + | ||
| 24 | +## 详细设计与逻辑流程 | ||
| 25 | + | ||
| 26 | +### 1. 入口改造 (CheckinCard) | ||
| 27 | + | ||
| 28 | +- **位置**:`CheckinCard` 组件底部右侧操作区(原点赞/更多按钮处)。 | ||
| 29 | +- **交互**:点击“...”图标,从底部弹出 `ActionSheet`(Vant 组件)。 | ||
| 30 | +- **菜单项**: | ||
| 31 | + - **置顶**:仅当 `VITE_FEATURE_CHECKIN_TOP=1` 时显示。若已置顶,显示“取消置顶”。 | ||
| 32 | + - **评论**:仅当 `VITE_FEATURE_CHECKIN_COMMENT=1` 时显示。 | ||
| 33 | + - **海报**:仅当 `VITE_FEATURE_CHECKIN_POSTER=1` 时显示。 | ||
| 34 | + | ||
| 35 | +### 2. 置顶功能 (Top) | ||
| 36 | + | ||
| 37 | +- **逻辑**: | ||
| 38 | + 1. 点击“置顶”菜单。 | ||
| 39 | + 2. 弹出确认框 `showConfirmDialog`:“确定要置顶这条打卡吗?”。 | ||
| 40 | + 3. 用户确认 -> 调用 API `teacherPinCheckinAPI(id)`。 | ||
| 41 | + 4. API 成功 -> Toast “置顶成功” -> 更新列表数据(将该项标记为置顶,或刷新列表)。 | ||
| 42 | + 5. 若已置顶 -> 点击“取消置顶” -> 确认 -> API `teacherUnpinCheckinAPI(id)` -> Toast “取消置顶成功” -> 更新状态。 | ||
| 43 | +- **要点**:这个功能属于教师端功能, 需要先判断这个用户的userinfo是否是老师, 使用is_teacher字段判断, 没有权限则不显示置顶菜单. | ||
| 44 | +- **API**: | ||
| 45 | + - `teacherPinCheckinAPI` 老师置顶打卡 (参数: `checkin_id`) | ||
| 46 | + - `teacherUnpinCheckinAPI` 老师取消置顶打卡 (参数: `checkin_id`) | ||
| 47 | + | ||
| 48 | +### 3. 评论功能 (Comment) | ||
| 49 | + | ||
| 50 | +- **逻辑**: | ||
| 51 | + 1. 点击"评论"菜单。 | ||
| 52 | + 2. 弹出 **评论输入弹窗**(新建组件 `CheckinCommentDialog`)。 | ||
| 53 | + - 包含:文本输入框(Textarea)、表情选择器按钮。 | ||
| 54 | + - **表情选择器**:点击表情图标,从底部弹出表情面板,支持常用 Emoji 表情符号(😊❤️👍🎉等)。 | ||
| 55 | + - 输入验证:纯文本评论至少 5 个字符,允许表情符号作为补充。 | ||
| 56 | + - 按钮:取消、提交。 | ||
| 57 | + 3. 点击表情图标 -> 弹出表情选择面板(底部 Popup)-> 选择表情 -> 插入到光标位置。 | ||
| 58 | + 4. 输入内容(文本 + 表情)-> 点击提交。 | ||
| 59 | + 5. 调用 API `commentCheckin(id, content)`。 | ||
| 60 | + 6. API 成功 -> Toast "评论成功" -> 关闭弹窗 -> 刷新该打卡的评论列表。 | ||
| 61 | +- **要点**: | ||
| 62 | + - 这个功能属于用户端功能, 只能在`/checkin/index`打卡主页上的打卡卡片上显示出来。 | ||
| 63 | + - 表情选择器技术方案:自定义 Emoji 列表。 | ||
| 64 | + - 表情数据:使用原生 Unicode Emoji,无需图片资源,体积小且兼容性好。 | ||
| 65 | + - 常用表情建议:😊😂❤️👍🎉✨🙏💪🔥💯😍👏🤝🌟🎁 | ||
| 66 | +- **组件**:`CheckinCommentDialog.vue` (基于 Vant Popup 封装,底部弹出)。 | ||
| 67 | +- **API (需 Mock/确认)**: | ||
| 68 | + - `POST /checkin/comment` (发表评论,参数: `checkin_id`, `content`) | ||
| 69 | + | ||
| 70 | +### 4. 海报功能 (Poster) | ||
| 71 | + | ||
| 72 | +- **逻辑**: | ||
| 73 | + 1. 点击“海报”菜单。 | ||
| 74 | + 2. 弹出 **海报预览组件** (`CheckinPoster`,参考 `SharePoster`)。 | ||
| 75 | + 3. **渲染内容**: | ||
| 76 | + - 用户信息(头像、昵称)。 | ||
| 77 | + - 打卡内容(文本、图片缩略图)。 | ||
| 78 | + - 底部二维码(打卡主页的网址自动生成)。 | ||
| 79 | + - *注:需支持长图,若内容过长,通过滚动查看,生成图片时需完整截取。* | ||
| 80 | + 4. **生成图片**:使用 `html2canvas` 或 `html-to-image` 将 DOM 转为图片。 | ||
| 81 | + 5. 展示生成的图片,提示“长按保存”。 | ||
| 82 | +- **组件**:`CheckinPoster.vue`。 | ||
| 83 | +- **技术点**:处理跨域图片、长图渲染、字体加载。 | ||
| 84 | + | ||
| 85 | +### 5. 评论列表 (Comment List) | ||
| 86 | + | ||
| 87 | +- **位置**:`CheckinCard` 组件内部下方。 | ||
| 88 | +- **显示条件**:`VITE_FEATURE_CHECKIN_COMMENT_LIST=1` 且 `post.comments` 长度 > 0。 | ||
| 89 | +- **样式**:参考朋友圈评论区(灰色背景,每行 `用户: 内容`)。 | ||
| 90 | +- **逻辑**: | ||
| 91 | + - 渲染评论列表,支持 Emoji 表情符号的显示。 | ||
| 92 | + - 列车最多展示5条评论, 超过5条的评论需要点击"查看全部"才能看到. | ||
| 93 | + - 用户自己评论的右侧需要显示一个删除图标, 点击图标弹出确认框, 确认后调用删除接口删除该条评论. | ||
| 94 | + - **交互与操作**: | ||
| 95 | + - **回复**:点击某一条评论,从屏幕底部弹出输入框(类似微信朋友圈),键盘自动升起。输入框同样支持表情选择器。输入内容后点击发送,即为回复该条评论。 | ||
| 96 | + - 数据结构:使用 `parent_id` 标识父评论,`reply_to_user_id` 标识被回复用户。 | ||
| 97 | + - 显示样式:`用户A 回复 用户B: 评论内容`。 | ||
| 98 | + - **删除**:点击用户**自己**发布的评论,弹出 `ActionSheet` 或确认框,选项包含"删除"。确认后调用删除接口移除该评论。 | ||
| 99 | + - **样式参考**:整体交互逻辑和视觉风格严格参考微信朋友圈。 | ||
| 100 | + - **Emoji 渲染**:评论内容中的 Emoji 符号直接使用原生 Unicode 渲染,无需特殊处理。 | ||
| 101 | +- **组件**:`CheckinCommentList.vue` (复用 `StudyCommentsSection.vue` 的设计模式)。 | ||
| 102 | +- **API (需 Mock/确认)**: | ||
| 103 | + - `GET /checkin/comments` (获取评论列表,参数: `checkin_id`, `page`, `page_size`) | ||
| 104 | + - `POST /checkin/comment` (发表评论,参数: `checkin_id`, `content`) | ||
| 105 | + - `POST /checkin/comment/reply` (回复评论,参数: `checkin_id`, `content`, `parent_id`, `reply_to_user_id`) | ||
| 106 | + - `DELETE /checkin/comment/:id` (删除评论,参数: `comment_id`) | ||
| 107 | + | ||
| 108 | +## 开发步骤 | ||
| 109 | + | ||
| 110 | +### 第 1 步:环境准备与 Mock API | ||
| 111 | +- 在 `.env` 添加开关变量。 | ||
| 112 | +- 在 `src/api` 定义相关接口(Top, Comment),前期可使用 Mock 数据验证流程。 | ||
| 113 | + | ||
| 114 | +### 第 2 步:改造 CheckinCard 菜单 | ||
| 115 | +- 引入 `ActionSheet`。 | ||
| 116 | +- 根据 Env 开关动态显示菜单项。 | ||
| 117 | +- 实现基础点击事件处理。 | ||
| 118 | + | ||
| 119 | +### 第 3 步:实现置顶逻辑 | ||
| 120 | +- 对接置顶/取消置顶 API。 | ||
| 121 | +- 添加确认弹窗。 | ||
| 122 | +- 处理列表状态更新。 | ||
| 123 | +- **置顶视觉标记**:在已置顶的打卡卡片上显示"📌 置顶"标记。 | ||
| 124 | + | ||
| 125 | +### 第 4 步:实现评论功能与列表 | ||
| 126 | +- 创建 `CheckinCommentDialog` 组件。 | ||
| 127 | + - 实现 `van-field` 文本输入框(支持多行)。 | ||
| 128 | + - **实现表情选择器**: | ||
| 129 | + - 方案选择:自定义 Emoji 面板(不引入额外依赖,使用原生 Unicode Emoji)。 | ||
| 130 | + - UI 实现:`van-popup` 底部弹出 + `van-grid` 展示表情符号。 | ||
| 131 | + - 交互逻辑:点击表情 -> 插入到输入框光标位置 -> 自动关闭面板。 | ||
| 132 | + - 常用表情列表:😊😂❤️👍🎉✨🙏💪🔥💯😍👏🤝🌟🎁😭😡🤔💭😴🎂🌈⭐🌙☀️🌺🌸🍀🎁🎈🎵🎶📱💻⚽🏀🎾🎯🎨🎬📷🎤🎧📚✏️📝💡🔔💬📧📞 | ||
| 133 | + - 实现输入验证(文本至少 5 字,表情可作为补充)。 | ||
| 134 | +- 在 `CheckinCard` 中引入并调用评论弹窗。 | ||
| 135 | +- 创建 `CheckinCommentList` 组件(/components/checkin/CheckinCommentList.vue)。 | ||
| 136 | + - 参考 `StudyCommentsSection.vue` 的实现模式。 | ||
| 137 | + - 支持评论列表展示(最多 5 条,超过则显示"查看全部")。 | ||
| 138 | + - 支持 Emoji 符号的直接渲染。 | ||
| 139 | + - 实现评论回复交互(底部输入框弹出,同样支持表情选择)。 | ||
| 140 | + - 实现删除逻辑(仅显示自己评论的删除图标)。 | ||
| 141 | +- 对接评论、回复、删除 API 和列表展示数据。 | ||
| 142 | + | ||
| 143 | +### 第 5 步:实现打卡海报 | ||
| 144 | +- 创建 `CheckinPoster` 组件。 | ||
| 145 | +- 实现布局(支持长内容)。 | ||
| 146 | +- 集成 `html2canvas` 生成逻辑。 | ||
| 147 | +- 对接 `CheckinCard` 菜单。 | ||
| 148 | + | ||
| 149 | +## 边界条件与注意点 | ||
| 150 | + | ||
| 151 | +1. **权限控制**: | ||
| 152 | + - 置顶功能是否仅管理员可见?(前端暂通过 Env 控制全局,根据用户角色判断)。 | ||
| 153 | + - 评论功能是否允许自己评论自己?(需后端确认,前端暂不做限制)。 | ||
| 154 | + | ||
| 155 | +2. **表情输入处理**: | ||
| 156 | + - **字符计数**:Emoji 符号在某些设备上可能占用 2 个字符位置(代理对),需使用 `Array.from(text).length` 计算真实字符数。 | ||
| 157 | + - **输入限制**:限制单条评论最多 200 个字符(包含 Emoji),前端实时提示剩余字数。 | ||
| 158 | + - **兼容性**:原生 Unicode Emoji 在 iOS/Android/微信内置浏览器中均能正常显示,无需降级方案。 | ||
| 159 | + - **存储**:后端需确保数据库字符集支持 UTF-8 MB4(MySQL 5.5.3+ 的 `utf8mb4`),否则部分 Emoji(如 🎁)会乱码。 | ||
| 160 | + | ||
| 161 | +3. **海报生成**: | ||
| 162 | + - 图片跨域问题(需配置 `useCORS: true` 且 CDN 支持)。 | ||
| 163 | + - 内容过长导致 Canvas 内存溢出(需限制最大高度或分段,第一版暂不考虑极端情况)。 | ||
| 164 | + | ||
| 165 | +4. **列表更新**: | ||
| 166 | + - 操作后尽量避免全列表刷新,采用本地数据更新(Update Item)以提升体验。 | ||
| 167 | + | ||
| 168 | +5. **评论回复数据结构**: | ||
| 169 | + - 建议使用扁平化结构(所有评论在同一层级,通过 `parent_id` 关联),便于分页和排序。 | ||
| 170 | + - 示例: | ||
| 171 | + ```javascript | ||
| 172 | + { | ||
| 173 | + id: 1, | ||
| 174 | + checkin_id: 123, | ||
| 175 | + user_id: 456, | ||
| 176 | + content: "干得漂亮!🎉", | ||
| 177 | + parent_id: 0, // 0 表示一级评论 | ||
| 178 | + reply_to_user_id: null, // 一级评论为 null | ||
| 179 | + created_at: "2026-01-26 10:00:00" | ||
| 180 | + } | ||
| 181 | + ``` | ||
| 182 | + | ||
| 183 | +6. **CheckinCard 数据结构扩展**: | ||
| 184 | + - 现有 `post` 对象需要新增以下字段: | ||
| 185 | + ```javascript | ||
| 186 | + { | ||
| 187 | + id: 123, | ||
| 188 | + user: { name: "张三", avatar: "..." }, | ||
| 189 | + content: "今天完成了100天打卡!", | ||
| 190 | + images: [...], | ||
| 191 | + videoList: [...], | ||
| 192 | + audio: [...], | ||
| 193 | + likes: 10, | ||
| 194 | + is_liked: false, | ||
| 195 | + is_my: true, | ||
| 196 | + // 🆕 新增字段 | ||
| 197 | + is_pinned: false, // 是否置顶 | ||
| 198 | + pinned_at: null, // 置顶时间 | ||
| 199 | + comments_count: 0, // 评论总数(用于列表展示) | ||
| 200 | + comments: [] // 评论列表(最多5条) | ||
| 201 | + } | ||
| 202 | + ``` | ||
| 203 | + | ||
| 204 | +7. **性能优化**: | ||
| 205 | + - **评论列表懒加载**:仅在用户展开"查看全部"时才加载完整评论列表,首屏只加载前 5 条。 | ||
| 206 | + - **图片懒加载**:海报生成时使用 `loading="lazy"` 避免一次性加载所有图片。 | ||
| 207 | + - **列表虚拟滚动**:如果打卡列表超过 50 条,考虑使用 `van-list` 的虚拟滚动模式。 | ||
| 208 | + | ||
| 209 | +8. **错误处理**: | ||
| 210 | + - **网络错误**:API 调用失败时,显示 Toast 提示用户,并提供"重试"按钮。 | ||
| 211 | + - **并发冲突**:删除评论时,若该评论已被删除,提示"该评论不存在"。 | ||
| 212 | + | ||
| 213 | +9. **测试计划**: | ||
| 214 | + - **单元测试**:测试 `CheckinCommentDialog` 组件的输入验证、Emoji 插入逻辑。 | ||
| 215 | + - **集成测试**:测试评论发表、回复、删除的完整流程。 | ||
| 216 | + - **边界测试**:测试 200 字符限制、Emoji 字符计数、空评论提交等边界情况。 | ||
| 217 | + - **兼容性测试**:在 iOS Safari、Android Chrome、微信内置浏览器中测试 Emoji 显示。 | ||
| 218 | + | ||
| 219 | +--- | ||
| 220 | + | ||
| 221 | +## 待确认事项 | ||
| 222 | + | ||
| 223 | +### 与后端确认 | ||
| 224 | +1. **API 接口**: | ||
| 225 | + - `GET /checkin/comments` - 评论列表接口(是否支持分页?返回数据结构是否包含用户信息?)支持分页, 不包含用户信息. | ||
| 226 | + - `POST /checkin/comment` - 发表评论接口(是否需要敏感词过滤?返回值是什么?)不需要敏感词过滤, 返回值应该是评论ID. | ||
| 227 | + - `POST /checkin/comment/reply` - 回复评论接口(参数是否完整?)参数完整, 包含评论ID, 回复内容, 回复用户ID. 返回值应该是回复评论ID. | ||
| 228 | + - `DELETE /checkin/comment/:id` - 删除评论接口(是否有权限校验?)不需要校验, 列表会返回字段, 判断是否是自己的评论, 如果不是不会显示删除按钮. | ||
| 229 | + - `teacherPinCheckinAPI` / `teacherUnpinCheckinAPI` - 接口已实现. | ||
| 230 | + | ||
| 231 | +2. **数据字段**: | ||
| 232 | + - `post.is_pinned` - 置顶标记字段是否存在? | ||
| 233 | + - `post.pinned_at` - 置顶时间字段是否存在? | ||
| 234 | + - `post.comments_count` - 评论数字段是否存在? | ||
| 235 | + - `post.comments` - 评论列表字段是否存在?(预加载前5条) | ||
| 236 | + - `user.is_teacher` - 用户角色字段是否存在于 `localStorage.user_info`?在localStorage的currentUser中有存个人信息 | ||
| 237 | + | ||
| 238 | +3. **权限控制**: | ||
| 239 | + - 置顶功能:是否仅教师可见?是否有更细粒度的权限控制?仅教师可以置顶打卡. | ||
| 240 | + - 删除评论:是否只能删除自己的评论?教师是否可以删除所有评论? | ||
| 241 | + - 评论功能:是否允许自己评论自己的打卡?允许. | ||
| 242 | + | ||
| 243 | +4. **业务规则**: | ||
| 244 | + - 评论字符限制:是 200 字还是其他数量? | ||
| 245 | + - 是否允许发送纯表情评论(无文字)? | ||
| 246 | + - 评论后是否需要通知打卡用户或被回复用户? | ||
| 247 | + - 置顶数量限制:是否限制同时置顶的打卡数量? | ||
| 248 | + | ||
| 249 | +### 与产品确认 | ||
| 250 | +1. **功能优先级**: | ||
| 251 | + - 4 个功能(置顶、评论、海报、评论列表)的开发优先级是什么? | ||
| 252 | + - 是否需要在第一版就全部完成,还是可以分阶段上线? | ||
| 253 | + | ||
| 254 | +2. **交互细节**: | ||
| 255 | + - 置顶的打卡是否需要特殊的视觉标记(如"📌 置顶"标签、置顶图标等)? | ||
| 256 | + - 评论删除是否需要二次确认?(计划中已确认需要) | ||
| 257 | + - 表情选择器是否需要分类(如"笑脸"、"手势"、"动物"等)? | ||
| 258 | + - 海报生成失败时,是否需要降级方案(如显示文字版分享链接)? | ||
| 259 | + | ||
| 260 | +--- | ||
| 261 | + | ||
| 262 | +## 总结 | ||
| 263 | + | ||
| 264 | +### ✅ 计划完善度评估 | ||
| 265 | + | ||
| 266 | +**总体评分:9/10** - 开发计划已经非常完善,涵盖了需求、设计、API、边界条件等各个方面。 | ||
| 267 | + | ||
| 268 | +**优点:** | ||
| 269 | +- ✅ 需求拆解清晰,环境变量开关设计灵活 | ||
| 270 | +- ✅ 技术方案成熟可行(表情选择器、评论系统、海报生成) | ||
| 271 | +- ✅ API 设计完整,涵盖增删改查 | ||
| 272 | +- ✅ 数据结构设计合理(扁平化评论结构) | ||
| 273 | +- ✅ 边界条件考虑充分(Emoji 字符计数、数据库 utf8mb4) | ||
| 274 | +- ✅ 参考现有代码库,复用性高 | ||
| 275 | +- ✅ 开发步骤清晰,循序渐进 | ||
| 276 | +- ✅ 性能优化、错误处理、测试计划都已考虑 | ||
| 277 | + | ||
| 278 | +**需要改进的地方:** | ||
| 279 | +- ⚠️ 部分业务规则需要与后端/产品确认(见"待确认事项") | ||
| 280 | +- ⚠️ 置顶视觉标记、评论通知等功能细节需要补充 | ||
| 281 | +- ⚠️ 错误处理和降级方案可以更详细 | ||
| 282 | + | ||
| 283 | +### 🎯 建议的开发顺序 | ||
| 284 | + | ||
| 285 | +1. **第 1 阶段**:置顶功能(最简单,快速验证流程) | ||
| 286 | +2. **第 2 阶段**:评论功能 + 评论列表(核心功能,投入最大) | ||
| 287 | +3. **第 3 阶段**:海报功能(技术难度较高,可最后开发) | ||
| 288 | + | ||
| 289 | +### 📝 下一步行动 | ||
| 290 | + | ||
| 291 | +1. 与后端/产品确认"待确认事项"中的所有问题 | ||
| 292 | +2. 确认开发优先级和里程碑节点 | ||
| 293 | +3. 开始第 1 阶段开发(置顶功能) | ||
| 294 | +4. 同步准备 API Mock 数据,以便前端独立开发 |
docs/plan/暂存用户打卡信息.md
0 → 100644
| 1 | +# 暂存用户打卡信息 | ||
| 2 | + | ||
| 3 | +## 背景 | ||
| 4 | + | ||
| 5 | +用户在“提交作业/打卡”页面输入了较长文字并上传了媒体,但在未点击提交时被中断(误触返回、微信进程被系统回收、来电/切后台等),再次进入页面内容丢失,导致体验断裂。 | ||
| 6 | + | ||
| 7 | +本规划目标是在不改动后端接口的前提下,在前端提供“草稿暂存(文本 + 已上传媒体信息)”能力,支持一周内自动过期清理,并在用户再次进入时提示恢复或删除。 | ||
| 8 | + | ||
| 9 | +涉及页面与核心逻辑参考: | ||
| 10 | + | ||
| 11 | +- 打卡提交页:[CheckinDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/checkin/CheckinDetailPage.vue) | ||
| 12 | +- 打卡核心逻辑:[useCheckin.js](file:///Users/huyirui/program/itomix/git/mlaj/src/composables/useCheckin.js) | ||
| 13 | +- 环境变量约定:[.env](file:///Users/huyirui/program/itomix/git/mlaj/.env) | ||
| 14 | + | ||
| 15 | +## 需求拆解(逐条对齐) | ||
| 16 | + | ||
| 17 | +1. 缓存最多保存一周:写入时记录 saved_at,读写时执行过期清理(>7天删除)。 | ||
| 18 | +2. 用户提交后清空缓存:提交成功(code===1)后删除对应草稿。 | ||
| 19 | +3. 进入页面若存在未完成信息:弹框提示“继续/删除”,继续则回填,删除则清空。 | ||
| 20 | +4. 功能开关放到 .env:新增 VITE_CHECKIN_DRAFT_CACHE(0/1),默认建议 1(也可先默认 0,灰度开启)。 | ||
| 21 | + | ||
| 22 | +## 范围与不做项(第一版) | ||
| 23 | + | ||
| 24 | +- 覆盖:文字内容、已上传成功的媒体(含 url/meta_id/file_type/name)。 | ||
| 25 | +- 不覆盖:未上传完成的 File/Blob(localStorage 无法可靠持久化;要支持需 IndexedDB 存 Blob,复杂度与风险较高)。 | ||
| 26 | +- 编辑模式(route.query.status===edit):第一版建议默认不启用草稿恢复,避免与“编辑回显(来自后端)”冲突;若要覆盖编辑场景,采用独立 key(见“扩展”)。 | ||
| 27 | + | ||
| 28 | +## 关键设计 | ||
| 29 | + | ||
| 30 | +### 1) 存储介质 | ||
| 31 | + | ||
| 32 | +- 使用 localStorage:实现成本低,满足“一周”与“断网/切后台后仍可恢复”。 | ||
| 33 | +- 数据量控制:只存“已上传成功”的附件元数据;不存 File 本体。 | ||
| 34 | + | ||
| 35 | +### 2) 草稿 Key 设计(避免串号) | ||
| 36 | + | ||
| 37 | +建议 key 包含用户与作业上下文,确保不同用户/不同作业互不影响: | ||
| 38 | + | ||
| 39 | +- 前缀:CHECKIN_DRAFT_V1 | ||
| 40 | +- 维度:user_id、task_id、date、task_type、status | ||
| 41 | + | ||
| 42 | +示例: | ||
| 43 | + | ||
| 44 | +- CHECKIN_DRAFT_V1:{user_id}:{task_id}:{date}:{task_type}:{status} | ||
| 45 | + | ||
| 46 | +其中: | ||
| 47 | + | ||
| 48 | +- user_id:来自 currentUser(contexts/auth.js 本地持久化) | ||
| 49 | +- task_id/date/task_type/status:来自路由 query(CheckinDetailPage 已使用 route.query.task_id/date/task_type/status) | ||
| 50 | + | ||
| 51 | +### 3) 数据结构(建议) | ||
| 52 | + | ||
| 53 | +```json | ||
| 54 | +{ | ||
| 55 | + "version": 1, | ||
| 56 | + "saved_at": 1730000000000, | ||
| 57 | + "expires_at": 1730000000000, | ||
| 58 | + "context": { | ||
| 59 | + "user_id": "123", | ||
| 60 | + "task_id": "456", | ||
| 61 | + "date": "2026-01-25", | ||
| 62 | + "task_type": "upload", | ||
| 63 | + "status": "create" | ||
| 64 | + }, | ||
| 65 | + "payload": { | ||
| 66 | + "message": "...", | ||
| 67 | + "active_type": "image", | ||
| 68 | + "subtask_id": "789", | ||
| 69 | + "file_list": [ | ||
| 70 | + { | ||
| 71 | + "meta_id": "xxx", | ||
| 72 | + "url": "https://...", | ||
| 73 | + "name": "a.jpg", | ||
| 74 | + "file_type": "image" | ||
| 75 | + } | ||
| 76 | + ], | ||
| 77 | + "count": { | ||
| 78 | + "gratitude_count": 1, | ||
| 79 | + "gratitude_form_list": [] | ||
| 80 | + } | ||
| 81 | + } | ||
| 82 | +} | ||
| 83 | +``` | ||
| 84 | + | ||
| 85 | +说明: | ||
| 86 | + | ||
| 87 | +- file_list:仅保存 useCheckin.afterRead 上传成功后写入的字段(item.status===done 且 meta_id 存在)。 | ||
| 88 | +- count:来源于 CheckinDetailPage 的 selectedTargets/countValue(第一版可以先不存,或存但不影响非 count 类型)。 | ||
| 89 | + | ||
| 90 | +### 4) 触发保存的时机(自动暂存) | ||
| 91 | + | ||
| 92 | +- 文本变化:watch(message) debounce 500ms 保存。 | ||
| 93 | +- 附件变化:watch(fileList) 深度监听 debounce 500ms 保存(仅保存 done 项)。 | ||
| 94 | +- 作业选择变化:watch(selectedTaskValue) debounce 200ms 保存。 | ||
| 95 | +- 页面离开兜底:beforeRouteLeave 或 window.pagehide/visibilitychange 时强制保存一次(避免最后一次变更没落盘)。 | ||
| 96 | + | ||
| 97 | +落盘时机要遵循开关:VITE_CHECKIN_DRAFT_CACHE === '1' 才启用。 | ||
| 98 | + | ||
| 99 | +### 5) 弹框提示与回填流程 | ||
| 100 | + | ||
| 101 | +进入 [CheckinDetailPage.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/views/checkin/CheckinDetailPage.vue)(且非 edit 模式)时: | ||
| 102 | + | ||
| 103 | +1. 读取 key 对应草稿;若不存在或已过期,直接进入正常流程。 | ||
| 104 | +2. 若存在草稿且 payload 有实际内容(message 有非空或 file_list 非空):弹框提示。 | ||
| 105 | +3. 用户选择: | ||
| 106 | + - 继续:将草稿回填到 message / activeType / fileList / selectedTaskValue(以及 count 数据如启用),并立刻再保存一次(避免回填后又丢)。 | ||
| 107 | + - 删除:删除草稿并保持空表单。 | ||
| 108 | + | ||
| 109 | +弹框建议用 showConfirmDialog(Vant 4),取消分支需要 catch,避免控制台出现 Uncaught (in promise) cancel(Vant 文档:showConfirmDialog.then/catch)[3](https://develop365.gitlab.io/vant/zh-CN/dialog/)。 | ||
| 110 | + | ||
| 111 | +### 6) 清理策略(“定期清除一周前”) | ||
| 112 | + | ||
| 113 | +采用“惰性清理 + 低频全量清理”组合: | ||
| 114 | + | ||
| 115 | +- 惰性清理:每次读/写草稿时,如果 expires_at < now 则删除。 | ||
| 116 | +- 低频全量:进入打卡页时,扫描 localStorage 中以 CHECKIN_DRAFT_V1: 开头的 key,删除所有过期项。 | ||
| 117 | + | ||
| 118 | +说明:localStorage 没有内建 TTL,必须业务侧维护 expires_at。 | ||
| 119 | + | ||
| 120 | +### 7) 提交成功后清空 | ||
| 121 | + | ||
| 122 | +清空动作必须绑定到“真正提交成功”之后: | ||
| 123 | + | ||
| 124 | +- 建议在 useCheckin.onSubmit 中,当 add/edit API 返回 code===1 且后续逻辑准备 router.back 前,删除对应 key。 | ||
| 125 | + | ||
| 126 | +这样可覆盖“不同入口页复用 onSubmit”以及“提交后立即返回上一页”的场景。 | ||
| 127 | + | ||
| 128 | +## 开发步骤(可落地的实现顺序) | ||
| 129 | + | ||
| 130 | +### 第 0 步:验证手段先行(TDD) | ||
| 131 | + | ||
| 132 | +新增 Vitest 用例,先定义以下可验证点: | ||
| 133 | + | ||
| 134 | +- 写入后能读取同一 key 的草稿;过期后读取返回空且自动删除。 | ||
| 135 | +- 仅保存 status===done 且含 meta_id 的附件。 | ||
| 136 | +- 清理函数能删除所有过期 key,不误删其他业务 localStorage。 | ||
| 137 | +- 提交成功时会调用清理(可通过 mock API 返回 code===1 验证)。 | ||
| 138 | + | ||
| 139 | +### 第 1 步:抽离草稿存储模块 | ||
| 140 | + | ||
| 141 | +位置建议:src/utils/checkinDraftCache.js(纯函数、无 UI 依赖)。 | ||
| 142 | + | ||
| 143 | +对外 API(示例): | ||
| 144 | + | ||
| 145 | +- is_enabled(): boolean(读取 env + 可选 query override) | ||
| 146 | +- build_key(context): string | ||
| 147 | +- save_draft(key, draft) | ||
| 148 | +- read_draft(key): draft|null(含 TTL 处理) | ||
| 149 | +- clear_draft(key) | ||
| 150 | +- cleanup_expired(prefix) | ||
| 151 | + | ||
| 152 | +### 第 2 步:在 CheckinDetailPage 接入“检测 + 弹框 + 回填” | ||
| 153 | + | ||
| 154 | +- onMounted:初始化后读取草稿并弹框。 | ||
| 155 | +- 回填时机:建议在任务详情/子任务列表加载完成后再回填 selectedTaskValue,避免 option 未加载导致显示异常。 | ||
| 156 | + | ||
| 157 | +### 第 3 步:在 CheckinDetailPage 接入“自动保存” | ||
| 158 | + | ||
| 159 | +- 对 message/fileList/selectedTaskValue/countValue/selectedTargets 建立 watch + debounce。 | ||
| 160 | +- 页面离开事件兜底(pagehide/visibilitychange)。 | ||
| 161 | + | ||
| 162 | +### 第 4 步:在 useCheckin.onSubmit 接入“成功清理” | ||
| 163 | + | ||
| 164 | +- onSubmit 成功分支清除草稿。 | ||
| 165 | +- 失败分支不清除,保留草稿以便重试。 | ||
| 166 | + | ||
| 167 | +## 边界条件与遗漏点梳理(建议补齐) | ||
| 168 | + | ||
| 169 | +1. 多用户切换:key 必须含 user_id,否则会串草稿。 | ||
| 170 | +2. 多任务并存:key 必须含 task_id/date/task_type,否则会在不同作业之间误恢复。 | ||
| 171 | +3. 附件未上传完成: | ||
| 172 | + - 仅保存已上传成功的项;如果用户退出时仍有 uploading 项,恢复后无法找回该 File。 | ||
| 173 | + - 可在保存时统计未保存数量,并在恢复弹框里追加提示“有 X 个附件上传未完成未被暂存”。 | ||
| 174 | +4. 关闭开关后的行为: | ||
| 175 | + - 关闭后不再读/写;建议仍执行一次 cleanup_expired,避免历史堆积。 | ||
| 176 | +5. 版本升级/数据结构变更:draft.version 不匹配时丢弃并清除,避免解析异常。 | ||
| 177 | +6. localStorage 配额:图片多但只存 url/meta_id 一般不会超;仍需 try/catch JSON 与 setItem 异常。 | ||
| 178 | +7. 编辑模式: | ||
| 179 | + - 要支持“编辑中断恢复”,建议 key 加 post_id 维度,并在 initEditData 回显后再弹框询问是否覆盖当前表单。 | ||
| 180 | + | ||
| 181 | +## 环境变量(规划) | ||
| 182 | + | ||
| 183 | +在 [.env](file:///Users/huyirui/program/itomix/git/mlaj/.env) 增加: | ||
| 184 | + | ||
| 185 | +- VITE_CHECKIN_DRAFT_CACHE = 1 | ||
| 186 | + | ||
| 187 | +约定: | ||
| 188 | + | ||
| 189 | +- '1' 开启,'0' 关闭 | ||
| 190 | +- 可选增加 URL 覆盖用于灰度测试:?enable_draft=1 / ?enable_draft=0(模式同 VITE_CHECKIN_MULTI_ATTACHMENT) |
-
Please register or login to post a comment