hookehuyr

docs(plan): 更新新功能开发计划文档

更新 26.1.26 新功能开发计划文档,重新组织内容结构并完善细节:
- 按优先级重新排序功能:置顶、评论、评论列表为高优先级,海报为中优先级
- 新增教师评论管理功能需求
- 完善数据字段定义和 API 接口汇总
- 细化开发步骤为四个阶段并明确里程碑
- 补充边界条件、性能优化和测试计划
...@@ -2,19 +2,18 @@ ...@@ -2,19 +2,18 @@
2 2
3 ## 背景 3 ## 背景
4 4
5 -为了增强用户在打卡列表的互动性和管理能力,需要在打卡卡片(CheckinCard)上增加更多操作选项,包括置顶、评论、海报分享以及展示评论列表。这些功能将通过环境变量开关进行控制,以便按需开启。 5 +为了增强用户在打卡列表的互动性和管理能力,需要在打卡卡片(CheckinCard)上增加更多操作选项,包括置顶、评论、海报分享以及展示评论列表。这些功能将通过环境变量开关进行控制,以便按需开启。
6 6
7 ## 需求拆解 7 ## 需求拆解
8 8
9 -1. **全局功能开关**:在 `.env` 文件中通过变量控制各功能的开启/关闭。 9 +1. **置顶功能** (优先级: 🔴 高) - 允许教师将特定打卡内容置顶
10 -2. **置顶功能 (Top)**:允许将特定打卡内容置顶。 10 +2. **评论功能** (优先级: 🔴 高) - 允许用户对打卡内容进行评论
11 -3. **评论/评论功能 (Comment)**:允许对打卡内容进行评论(评论)。 11 +3. **评论列表** (优先级: 🔴 高) - 在打卡卡片下方显示该打卡的评论记录
12 -4. **打卡海报 (Poster)**:生成包含打卡内容的长图海报,支持分享。 12 +4. **海报功能** (优先级: 🟡 中) - 生成包含打卡内容的长图海报,支持分享
13 -5. **评论列表 (Comment List)**:在打卡卡片下方显示该打卡的评论/评论记录。
14 13
15 ## 环境变量规划 14 ## 环境变量规划
16 15
17 -`.env` 中增加以下开关(默认 '0' 关闭,'1' 开启): 16 +`.env` 中增加以下开关(默认 '0' 关闭,'1' 开启):
18 17
19 - `VITE_FEATURE_CHECKIN_TOP=1` (置顶功能) 18 - `VITE_FEATURE_CHECKIN_TOP=1` (置顶功能)
20 - `VITE_FEATURE_CHECKIN_COMMENT=1` (评论功能) 19 - `VITE_FEATURE_CHECKIN_COMMENT=1` (评论功能)
...@@ -25,278 +24,677 @@ ...@@ -25,278 +24,677 @@
25 24
26 ### 1. 入口改造 (CheckinCard) 25 ### 1. 入口改造 (CheckinCard)
27 26
28 -- **位置**`CheckinCard` 组件底部右侧操作区(原点赞/更多按钮处)。 27 +**位置**: `CheckinCard` 组件底部右侧操作区(原点赞/更多按钮处)
29 -- **交互**:点击“...”图标,从底部弹出 `ActionSheet`(Vant 组件)。 28 +
30 -- **菜单项** 29 +**交互**: 点击"..."图标,从底部弹出 `ActionSheet`(Vant 组件)
31 - - **置顶**:仅当 `VITE_FEATURE_CHECKIN_TOP=1` 时显示。若已置顶,显示“取消置顶”。 30 +
32 - - **评论**:仅当 `VITE_FEATURE_CHECKIN_COMMENT=1` 时显示。 31 +**菜单项**:
33 - - **海报**:仅当 `VITE_FEATURE_CHECKIN_POSTER=1` 时显示。 32 +- **置顶** (仅教师可见): 仅当 `VITE_FEATURE_CHECKIN_TOP=1` 时显示。若已置顶,显示"取消置顶"
34 - 33 +- **评论** (仅用户端可见): 仅当 `VITE_FEATURE_CHECKIN_COMMENT=1` 时显示,仅在 `/checkin/index` 打卡主页显示
35 -### 2. 置顶功能 (Top) 34 +- **海报** (通用): 仅当 `VITE_FEATURE_CHECKIN_POSTER=1` 时显示
36 - 35 +
37 -- **逻辑** 36 +### 2. 置顶功能 🔴 高优先级
38 - 1. 点击“置顶”菜单。 37 +
39 - 2. 弹出确认框 `showConfirmDialog`:“确定要置顶这条打卡吗?”。 38 +#### 权限控制
40 - 3. 用户确认 -> 调用 API `teacherPinCheckinAPI(id)` 39 +- **仅教师可见**: 使用 `user.is_teacher` 字段判断权限
41 - 4. API 成功 -> Toast “置顶成功” -> 更新列表数据(将该项标记为置顶,或刷新列表)。 40 +- **权限获取**: 前端需实时请求后端接口获取用户角色,不依赖缓存的 `localStorage.user_info`
42 - 5. 若已置顶 -> 点击“取消置顶” -> 确认 -> API `teacherUnpinCheckinAPI(id)` -> Toast “取消置顶成功” -> 更新状态。 41 +- **适用范围**: 教师端所有打卡列表页面
43 -- **要点**:这个功能属于教师端功能, 需要先判断这个用户的userinfo是否是老师, 使用is_teacher字段判断, 没有权限则不显示置顶菜单. 42 +
44 -- **API** 43 +#### 逻辑流程
45 - - `teacherPinCheckinAPI` 老师置顶打卡 (参数: `checkin_id`) 44 +1. 点击"置顶"菜单
46 - - `teacherUnpinCheckinAPI` 老师取消置顶打卡 (参数: `checkin_id`) 45 +2. 弹出确认框 `showConfirmDialog`:"确定要置顶这条打卡吗?"
47 - 46 +3. 用户确认 -> 调用 API `teacherPinCheckinAPI(id)`
48 -### 3. 评论功能 (Comment) 47 +4. API 成功 -> Toast "置顶成功" -> 更新列表数据(将该项标记为置顶)
49 - 48 +5. 若已置顶 -> 点击"取消置顶" -> 确认 -> API `teacherUnpinCheckinAPI(id)` -> Toast "取消置顶成功" -> 更新状态
50 -- **逻辑** 49 +
51 - 1. 点击"评论"菜单。 50 +#### 视觉标记
52 - 2. 弹出 **评论输入弹窗**(新建组件 `CheckinCommentDialog`)。 51 +- 已置顶的打卡卡片右上角显示 📌 图标
53 - - 包含:文本输入框(Textarea)、表情选择器按钮。 52 +- 卡片顶部显示"置顶"标签(使用 `van-tag` 组件)
54 - - **表情选择器**:点击表情图标,从底部弹出表情面板,支持常用 Emoji 表情符号(😊❤️👍🎉等)。 53 +
55 - - 输入验证:纯文本评论至少 5 个字符,允许表情符号作为补充。 54 +#### API 接口
56 - - 按钮:取消、提交。 55 +- `POST /api/teacher/pin-checkin` - 教师置顶打卡 (参数: `checkin_id`)
57 - 3. 点击表情图标 -> 弹出表情选择面板(底部 Popup)-> 选择表情 -> 插入到光标位置。 56 +- `POST /api/teacher/unpin-checkin` - 教师取消置顶打卡 (参数: `checkin_id`)
58 - 4. 输入内容(文本 + 表情)-> 点击提交。 57 +
59 - 5. 调用 API `commentCheckin(id, content)` 58 +#### 数据字段扩展
60 - 6. API 成功 -> Toast "评论成功" -> 关闭弹窗 -> 刷新该打卡的评论列表。 59 +```javascript
61 -- **要点** 60 +{
62 - - 这个功能属于用户端功能, 只能在`/checkin/index`打卡主页上的打卡卡片上显示出来。 61 + is_pinned: false, // 是否置顶
63 - - 表情选择器技术方案:自定义 Emoji 列表。 62 + pinned_at: null // 置顶时间
64 - - 表情数据:使用原生 Unicode Emoji,无需图片资源,体积小且兼容性好。 63 +}
65 - - 常用表情建议:😊😂❤️👍🎉✨🙏💪🔥💯😍👏🤝🌟🎁 64 +```
66 -- **组件**`CheckinCommentDialog.vue` (基于 Vant Popup 封装,底部弹出)。 65 +
67 -- **API (需 Mock/确认)** 66 +---
68 - - `POST /checkin/comment` (发表评论,参数: `checkin_id`, `content`) 67 +
69 - 68 +### 3. 评论功能 🔴 高优先级
70 -### 4. 海报功能 (Poster) 69 +
71 - 70 +#### 权限控制
72 -- **逻辑** 71 +- **仅用户端可用**: 只能在 `/checkin/index` 打卡主页的打卡卡片上显示
73 - 1. 点击“海报”菜单。 72 +- **允许自评**: 允许用户评论自己的打卡
74 - 2. 弹出 **海报预览组件** (`CheckinPoster`,参考 `SharePoster`)。 73 +
75 - 3. **渲染内容** 74 +#### 评论输入弹窗 (CheckinCommentDialog)
76 - - 用户信息(头像、昵称)。 75 +
77 - - 打卡内容(文本、图片缩略图)。 76 +**组件**: `CheckinCommentDialog.vue` (基于 Vant Popup 封装,底部弹出)
78 - - 底部二维码(打卡主页的网址自动生成)。 77 +
79 - - *注:需支持长图,若内容过长,通过滚动查看,生成图片时需完整截取。* 78 +**功能特性**:
80 - 4. **生成图片**:使用 `html2canvas``html-to-image` 将 DOM 转为图片。 79 +- 文本输入框 (`van-field`,多行模式)
81 - 5. 展示生成的图片,提示“长按保存”。 80 +- 表情选择器按钮
82 -- **组件**`CheckinPoster.vue` 81 +- 字符计数提示(最多 200 字)
83 -- **技术点**:处理跨域图片、长图渲染、字体加载。 82 +- 按钮: 取消、提交
84 - 83 +
85 -### 5. 评论列表 (Comment List) 84 +**表情选择器**:
86 - 85 +- **技术方案**: 自定义 Emoji 面板,使用原生 Unicode Emoji
87 -- **位置**`CheckinCard` 组件内部下方。 86 +- **UI 实现**: `van-popup` 底部弹出 + `van-grid` 展示表情符号
88 -- **显示条件**`VITE_FEATURE_CHECKIN_COMMENT_LIST=1``post.comments` 长度 > 0。 87 +- **常用表情**: 😊😂❤️👍🎉✨🙏💪🔥💯😍👏🤝🌟🎁😭😡🤔💭😴🎂🌈⭐🌙☀️🌺🌸🍀🎁🎈🎵🎶📱💻⚽🏀🎾🎯🎨🎬📷🎤🎧📚✏️📝💡🔔💬📧📞
89 -- **样式**:参考朋友圈评论区(灰色背景,每行 `用户: 内容`)。 88 +- **交互逻辑**: 点击表情 -> 插入到输入框光标位置 -> 自动关闭面板
90 -- **逻辑** 89 +
91 - - 渲染评论列表,支持 Emoji 表情符号的显示。 90 +**输入验证**:
92 - - 列车最多展示5条评论, 超过5条的评论需要点击"查看全部"才能看到. 91 +- 纯文本评论至少 5 个字符
93 - - 用户自己评论的右侧需要显示一个删除图标, 点击图标弹出确认框, 确认后调用删除接口删除该条评论. 92 +- 允许纯表情评论(无文字)
94 - - **交互与操作** 93 +- 字符计数使用 `Array.from(text).length` 计算(正确处理 Emoji 代理对)
95 - - **回复**:点击某一条评论,从屏幕底部弹出输入框(类似微信朋友圈),键盘自动升起。输入框同样支持表情选择器。输入内容后点击发送,即为回复该条评论。 94 +- 前端实时提示剩余字数
96 - - 数据结构:使用 `parent_id` 标识父评论,`reply_to_user_id` 标识被回复用户。 95 +
97 - - 显示样式:`用户A 回复 用户B: 评论内容` 96 +#### API 接口
98 - - **删除**:点击用户**自己**发布的评论,弹出 `ActionSheet` 或确认框,选项包含"删除"。确认后调用删除接口移除该评论。 97 +- `POST /api/checkin/comment` - 发表评论
99 - - **样式参考**:整体交互逻辑和视觉风格严格参考微信朋友圈。 98 + - 参数: `checkin_id`, `content` (评论内容,最多 200 字)
100 - - **Emoji 渲染**:评论内容中的 Emoji 符号直接使用原生 Unicode 渲染,无需特殊处理。 99 + - 返回: `{ code: 1, data: { comment_id: 123 }, msg: '' }`
101 -- **组件**`CheckinCommentList.vue` (复用 `StudyCommentsSection.vue` 的设计模式)。 100 +- **无需敏感词过滤**: 后端不进行敏感词过滤
102 -- **API (需 Mock/确认)** 101 +
103 - - `GET /checkin/comments` (获取评论列表,参数: `checkin_id`, `page`, `page_size`) 102 +#### 通知机制
104 - - `POST /checkin/comment` (发表评论,参数: `checkin_id`, `content`) 103 +- 评论后不需要前端主动触发通知
105 - - `POST /checkin/comment/reply` (回复评论,参数: `checkin_id`, `content`, `parent_id`, `reply_to_user_id`) 104 +- 后端会自动发送通知给打卡用户或被回复用户
106 - - `DELETE /checkin/comment/:id` (删除评论,参数: `comment_id`) 105 +
106 +---
107 +
108 +### 4. 评论列表功能 🔴 高优先级
109 +
110 +#### 位置与显示
111 +**位置**: `CheckinCard` 组件内部下方
112 +**显示条件**: `VITE_FEATURE_CHECKIN_COMMENT_LIST=1``post.comments.length > 0`
113 +
114 +#### 评论列表展示 (CheckinCommentList)
115 +
116 +**组件**: `CheckinCommentList.vue` (参考 `StudyCommentsSection.vue` 的实现模式)
117 +
118 +**显示逻辑**:
119 +- 首屏最多展示 5 条评论
120 +- 超过 5 条显示"查看全部"按钮
121 +- 点击"查看全部"加载完整评论列表(懒加载)
122 +- 样式参考微信朋友圈(灰色背景,每行 `用户: 内容`)
123 +
124 +**数据结构** (扁平化设计):
125 +```javascript
126 +{
127 + id: 1, // 评论 ID
128 + checkin_id: 123, // 关联的打卡 ID
129 + user_id: 456, // 评论者 ID
130 + user_name: "张三", // 评论者昵称
131 + content: "干得漂亮!🎉", // 评论内容
132 + parent_id: 0, // 父评论 ID (0 表示一级评论)
133 + reply_to_user_id: null, // 被回复用户 ID (一级评论为 null)
134 + reply_to_user_name: null, // 被回复用户昵称
135 + is_my: false, // 是否是自己发表的评论
136 + created_at: "2026-01-26 10:00:00"
137 +}
138 +```
139 +
140 +**评论样式**:
141 +- 一级评论: `张三: 干得漂亮!🎉`
142 +- 回复评论: `张三 回复 李四: 我也这么觉得!`
143 +- Emoji 符号直接使用原生 Unicode 渲染
144 +
145 +#### 交互功能
146 +
147 +**回复评论**:
148 +1. 点击某一条评论,从屏幕底部弹出输入框(类似微信朋友圈)
149 +2. 键盘自动升起
150 +3. 输入框同样支持表情选择器
151 +4. 输入内容后点击发送,即为回复该条评论
152 +5. 数据结构: 使用 `parent_id` 标识父评论,`reply_to_user_id` 标识被回复用户
153 +6. 显示样式: `用户A 回复 用户B: 评论内容`
154 +
155 +**删除评论**:
156 +- 仅显示用户**自己**发布的评论的删除图标
157 +- 点击图标弹出确认框:"确定要删除这条评论吗?"
158 +- 确认后调用删除接口移除该评论
159 +- API: `DELETE /api/checkin/comment/:id`
160 +- **权限校验**:
161 + - 前端: 通过接口返回的 `is_my` 字段判断是否显示删除按钮
162 + - 后端: 接口层进行权限校验,确保只能删除自己的评论
163 +- **教师权限**: 教师不可删除学生的评论
164 +
165 +#### 教师评论管理 🔴 高优先级 (新增需求)
166 +
167 +**适用场景**: 教师在 `/teacher` 路由下的打卡列表中管理评论
168 +
169 +**功能**:
170 +- 教师可以在评论列表中对不适当的评论进行审核
171 +- 操作选项:
172 + - **屏蔽**: 隐藏该评论(不删除,仅前端不显示)
173 + - **通过**: 恢复该评论的显示
174 +- 评论状态字段: `status` (`0` = 待审核, `1` = 已通过, `2` = 已屏蔽)
175 +- **交互设计**:
176 + - 在评论卡片右侧显示审核按钮(仅教师可见)
177 + - 点击按钮弹出 ActionSheet: "屏蔽评论"、"通过评论"
178 + - 操作后实时更新评论状态
179 +
180 +**API 接口** (新增):
181 +- `POST /api/teacher/review-comment` - 教师审核评论
182 + - 参数: `comment_id`, `action` (`approve` | `reject`)
183 + - 返回: `{ code: 1, data: {}, msg: '' }`
184 +
185 +**数据字段扩展**:
186 +```javascript
187 +{
188 + status: 1, // 评论状态: 0-待审核, 1-已通过, 2-已屏蔽
189 + reviewed_at: null, // 审核时间
190 + reviewed_by: null // 审核人 ID
191 +}
192 +```
193 +
194 +#### API 接口
195 +
196 +**获取评论列表**:
197 +- `GET /api/checkin/comments`
198 +- 参数: `checkin_id`, `page`, `page_size`
199 +- 返回: `{ code: 1, data: { list: [], total: 100 }, msg: '' }`
200 +- **注意**: 不包含完整的用户信息,仅返回 `user_id``user_name`
201 +
202 +**发表评论**:
203 +- `POST /api/checkin/comment`
204 +- 参数: `checkin_id`, `content`
205 +- 返回: `{ code: 1, data: { comment_id: 123 }, msg: '' }`
206 +
207 +**回复评论**:
208 +- `POST /api/checkin/comment/reply`
209 +- 参数: `checkin_id`, `content`, `parent_id`, `reply_to_user_id`
210 +- 返回: `{ code: 1, data: { comment_id: 124 }, msg: '' }`
211 +
212 +**删除评论**:
213 +- `DELETE /api/checkin/comment/:id`
214 +- 参数: `comment_id`
215 +- 返回: `{ code: 1, data: {}, msg: '' }`
216 +
217 +#### 数据字段扩展
218 +```javascript
219 +{
220 + comments_count: 0, // 评论总数(用于列表展示)
221 + comments: [] // 评论列表(最多 5 条,用于首屏渲染)
222 +}
223 +```
224 +
225 +---
226 +
227 +### 5. 海报功能 🟡 中优先级
228 +
229 +#### 逻辑流程
230 +1. 点击"海报"菜单
231 +2. 弹出 **海报预览组件** (`CheckinPoster`,参考 `SharePoster`)
232 +3. **渲染内容**:
233 + - 用户信息(头像、昵称)
234 + - 打卡内容(文本、图片缩略图)
235 + - 底部二维码(打卡主页的网址自动生成)
236 + - 支持长图,若内容过长通过滚动查看
237 +4. **生成图片**: 使用 `html2canvas``html-to-image` 将 DOM 转为图片
238 +5. 展示生成的图片,提示"长按保存"
239 +
240 +#### 降级方案
241 +- 海报生成是异步操作,可能会失败
242 +- 生成失败时显示降级卡片:
243 + - 卡片包含: 用户头像、昵称、打卡内容摘要
244 + - 底部提示: "长按截图保存分享"
245 + - 用户可手动截图保存
246 +
247 +#### 技术要点
248 +- 处理跨域图片: 配置 `useCORS: true` 且 CDN 支持 CORS
249 +- 长图渲染: 需限制最大高度或分段(第一版暂不考虑极端情况)
250 +- 字体加载: 确保字体加载完成后再生成图片
251 +
252 +---
107 253
108 ## 开发步骤 254 ## 开发步骤
109 255
110 -### 第 1 步:环境准备与 Mock API 256 +### 🔴 第 1 阶段: 置顶功能 (高优先级)
111 --`.env` 添加开关变量。 257 +
112 --`src/api` 定义相关接口(Top, Comment),前期可使用 Mock 数据验证流程。 258 +**目标**: 实现教师置顶打卡功能
113 - 259 +
114 -### 第 2 步:改造 CheckinCard 菜单 260 +**步骤**:
115 -- 引入 `ActionSheet` 261 +1. **环境准备**:
116 -- 根据 Env 开关动态显示菜单项。 262 + -`.env` 添加 `VITE_FEATURE_CHECKIN_TOP=1`
117 -- 实现基础点击事件处理。 263 + -`src/api/teacher.js` 中定义 `teacherPinCheckinAPI``teacherUnpinCheckinAPI` (已实现)
118 - 264 +
119 -### 第 3 步:实现置顶逻辑 265 +2. **用户权限获取**:
120 -- 对接置顶/取消置顶 API。 266 + - 创建 `useAuthRole.js` composable
121 -- 添加确认弹窗。 267 + - 实现实时请求用户角色接口(不依赖 localStorage)
122 -- 处理列表状态更新。 268 + - 返回 `{ is_teacher: boolean }`
123 -- **置顶视觉标记**:在已置顶的打卡卡片上显示"📌 置顶"标记。 269 +
124 - 270 +3. **改造 CheckinCard**:
125 -### 第 4 步:实现评论功能与列表 271 + - 引入 `van-action-sheet`
126 -- 创建 `CheckinCommentDialog` 组件。 272 + - 在菜单中添加"置顶"/"取消置顶"选项(根据 `is_pinned``is_teacher` 动态显示)
127 - - 实现 `van-field` 文本输入框(支持多行)。 273 + - 实现置顶确认弹窗
128 - - **实现表情选择器** 274 +
129 - - 方案选择:自定义 Emoji 面板(不引入额外依赖,使用原生 Unicode Emoji)。 275 +4. **置顶逻辑实现**:
130 - - UI 实现:`van-popup` 底部弹出 + `van-grid` 展示表情符号。 276 + - 对接置顶/取消置顶 API
131 - - 交互逻辑:点击表情 -> 插入到输入框光标位置 -> 自动关闭面板。 277 + - 成功后更新列表数据(本地更新,避免全列表刷新)
132 - - 常用表情列表:😊😂❤️👍🎉✨🙏💪🔥💯😍👏🤝🌟🎁😭😡🤔💭😴🎂🌈⭐🌙☀️🌺🌸🍀🎁🎈🎵🎶📱💻⚽🏀🎾🎯🎨🎬📷🎤🎧📚✏️📝💡🔔💬📧📞 278 + - 已置顶卡片显示 📌 图标和"置顶"标签
133 - - 实现输入验证(文本至少 5 字,表情可作为补充)。 279 +
134 --`CheckinCard` 中引入并调用评论弹窗。 280 +5. **测试**:
135 -- 创建 `CheckinCommentList` 组件(/components/checkin/CheckinCommentList.vue)。 281 + - 教师账号验证置顶功能
136 - - 参考 `StudyCommentsSection.vue` 的实现模式。 282 + - 学生账号验证置顶菜单不显示
137 - - 支持评论列表展示(最多 5 条,超过则显示"查看全部")。 283 + - 刷新页面验证置顶状态持久化
138 - - 支持 Emoji 符号的直接渲染。 284 +
139 - - 实现评论回复交互(底部输入框弹出,同样支持表情选择)。 285 +---
140 - - 实现删除逻辑(仅显示自己评论的删除图标)。 286 +
141 -- 对接评论、回复、删除 API 和列表展示数据。 287 +### 🔴 第 2 阶段: 评论功能 (高优先级)
142 - 288 +
143 -### 第 5 步:实现打卡海报 289 +**目标**: 实现用户发表评论功能
144 -- 创建 `CheckinPoster` 组件。 290 +
145 -- 实现布局(支持长内容)。 291 +**步骤**:
146 -- 集成 `html2canvas` 生成逻辑。 292 +1. **环境准备**:
147 -- 对接 `CheckinCard` 菜单。 293 + -`.env` 添加 `VITE_FEATURE_CHECKIN_COMMENT=1`
294 + -`src/api/checkin.js` 中定义 `addCheckinCommentAPI`
295 +
296 +2. **创建评论输入弹窗**:
297 + - 创建 `CheckinCommentDialog.vue` 组件
298 + - 实现 `van-field` 文本输入框(多行模式)
299 + - 实现字符计数提示(最多 200 字)
300 +
301 +3. **实现表情选择器**:
302 + - 创建 `EmojiPicker.vue` 组件
303 + - 使用 `van-popup` + `van-grid` 展示表情符号
304 + - 实现点击表情插入到输入框光标位置
305 + - 定义常用表情列表(原生 Unicode Emoji)
306 +
307 +4. **输入验证**:
308 + - 实现字符计数逻辑(`Array.from(text).length`)
309 + - 纯文本评论至少 5 字
310 + - 允许纯表情评论
311 + - 实时提示剩余字数
312 +
313 +5. **对接 API**:
314 + - 实现发表评论接口调用
315 + - 成功后 Toast "评论成功"
316 + - 关闭弹窗并刷新评论列表
317 +
318 +6. **集成到 CheckinCard**:
319 + -`/checkin/index` 页面的打卡卡片菜单中添加"评论"选项
320 + - 点击评论菜单打开 `CheckinCommentDialog`
321 + - 仅用户端可见(教师端不显示)
322 +
323 +7. **测试**:
324 + - 测试纯文本评论(最少 5 字)
325 + - 测试纯表情评论
326 + - 测试混合评论(文本 + 表情)
327 + - 测试字符计数准确性
328 + - 测试 200 字符限制
329 + - 测试表情插入到光标位置
330 +
331 +---
332 +
333 +### 🔴 第 3 阶段: 评论列表 (高优先级)
334 +
335 +**目标**: 实现评论列表展示、回复、删除功能
336 +
337 +**步骤**:
338 +1. **环境准备**:
339 + -`.env` 添加 `VITE_FEATURE_CHECKIN_COMMENT_LIST=1`
340 + -`src/api/checkin.js` 中定义评论相关 API:
341 + - `getCheckinCommentsAPI` - 获取评论列表
342 + - `replyCheckinCommentAPI` - 回复评论
343 + - `deleteCheckinCommentAPI` - 删除评论
344 +
345 +2. **创建评论列表组件**:
346 + - 创建 `CheckinCommentList.vue` 组件
347 + - 参考 `StudyCommentsSection.vue` 的实现模式
348 + - 实现首屏展示最多 5 条评论
349 + - 超过 5 条显示"查看全部"按钮
350 +
351 +3. **实现评论样式**:
352 + - 一级评论: `用户名: 评论内容`
353 + - 回复评论: `用户名 回复 被回复用户名: 评论内容`
354 + - 灰色背景,参考微信朋友圈样式
355 + - Emoji 符号直接渲染(无需特殊处理)
356 +
357 +4. **实现回复功能**:
358 + - 点击评论弹出底部输入框(类似微信朋友圈)
359 + - 输入框支持表情选择器(复用 `EmojiPicker.vue`)
360 + - 实现回复 API 调用
361 + - 成功后刷新评论列表
362 +
363 +5. **实现删除功能**:
364 + - 仅显示自己评论的删除图标(根据 `is_my` 字段判断)
365 + - 点击删除图标弹出确认框
366 + - 确认后调用删除 API
367 + - 成功后从列表中移除该评论
368 +
369 +6. **实现懒加载**:
370 + - 首屏仅加载前 5 条评论(从 `post.comments` 字段读取)
371 + - 点击"查看全部"调用 `getCheckinCommentsAPI` 加载完整列表
372 + - 实现分页加载
373 +
374 +7. **集成到 CheckinCard**:
375 + -`CheckinCard` 底部引入 `CheckinCommentList`
376 + - 根据 `comments_count``comments` 字段渲染
377 + - 评论发表成功后自动刷新列表
378 +
379 +8. **教师评论管理** (新增):
380 + -`/teacher` 路由下的打卡列表中启用评论管理
381 + - 教师可在评论列表中查看所有评论
382 + - 在评论卡片右侧显示审核按钮(仅教师可见)
383 + - 实现评论屏蔽/通过功能
384 + - 对接 `teacherReviewCommentAPI` (新增)
385 + - 操作后实时更新评论状态
386 +
387 +9. **测试**:
388 + - 测试评论列表展示(一级评论、回复评论)
389 + - 测试"查看全部"懒加载
390 + - 测试评论回复功能
391 + - 测试评论删除功能(自己的评论、别人的评论)
392 + - 测试 Emoji 渲染
393 + - 测试教师评论管理功能(屏蔽/通过)
394 + - 测试权限控制(学生不可见审核按钮)
395 +
396 +---
397 +
398 +### 🟡 第 4 阶段: 海报功能 (中优先级)
399 +
400 +**目标**: 实现打卡海报生成与分享
401 +
402 +**步骤**:
403 +1. **环境准备**:
404 + -`.env` 添加 `VITE_FEATURE_CHECKIN_POSTER=1`
405 + - 安装依赖: `pnpm add html2canvas`
406 +
407 +2. **创建海报组件**:
408 + - 创建 `CheckinPoster.vue` 组件
409 + - 参考 `SharePoster.vue` 的实现模式
410 + - 实现布局: 用户信息、打卡内容、二维码
411 +
412 +3. **实现长图渲染**:
413 + - 支持长内容滚动查看
414 + - 处理多图展示(网格布局)
415 + - 处理文本换行
416 +
417 +4. **实现图片生成**:
418 + - 使用 `html2canvas` 将 DOM 转为图片
419 + - 配置 `useCORS: true` 处理跨域图片
420 + - 添加 `crossorigin="anonymous"` 到图片标签
421 + - 生成后展示图片并提示"长按保存"
422 +
423 +5. **实现降级方案**:
424 + - 监听 `html2canvas` 失败情况
425 + - 失败时显示文字版卡片
426 + - 提示用户"长按截图保存"
427 +
428 +6. **集成到 CheckinCard**:
429 + - 在打卡菜单中添加"海报"选项
430 + - 点击后打开 `CheckinPoster` 弹窗
431 + - 传入打卡信息
432 +
433 +7. **测试**:
434 + - 测试不同内容长度的海报生成
435 + - 测试跨域图片处理
436 + - 测试降级方案(生成失败时)
437 + - 测试在微信内置浏览器的兼容性
438 + - 测试长按保存功能
439 +
440 +---
148 441
149 ## 边界条件与注意点 442 ## 边界条件与注意点
150 443
151 -1. **权限控制** 444 +### 1. 字符计数与 Emoji 处理
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 445
183 -6. **CheckinCard 数据结构扩展** 446 +**问题**: Emoji 符号在某些设备上占用 2 个字符位置(代理对)
184 - - 现有 `post` 对象需要新增以下字段: 447 +
185 - ```javascript 448 +**解决方案**:
186 - { 449 +```javascript
450 +// 错误方式
451 +text.length // 可能将 Emoji 算作 2 个字符
452 +
453 +// 正确方式
454 +Array.from(text).length // 正确计算字符数
455 +```
456 +
457 +**验证规则**:
458 +- 纯文本评论至少 5 个字符
459 +- 允许纯表情评论(无文字)
460 +- 单条评论最多 200 个字符(包含 Emoji)
461 +
462 +### 2. 数据库字符集支持
463 +
464 +**要求**: 后端数据库需使用 UTF-8 MB4 字符集
465 +- MySQL 5.5.3+ 的 `utf8mb4`
466 +- 否则部分 Emoji(如 🎁)会乱码
467 +
468 +### 3. 列表更新策略
469 +
470 +**原则**: 避免全列表刷新,采用本地数据更新
471 +
472 +**实现方式**:
473 +- 置顶成功: 更新本地卡片的 `is_pinned` 字段
474 +- 评论成功: 在本地评论列表中新增评论项
475 +- 删除成功: 从本地评论列表中移除评论项
476 +- 仅在必要时调用接口刷新列表
477 +
478 +### 4. 权限控制
479 +
480 +**置顶功能**:
481 +- 仅教师可见(通过 `is_teacher` 字段判断)
482 +- 权限实时获取(不依赖 localStorage)
483 +
484 +**评论功能**:
485 +- 仅用户端可见(仅在 `/checkin/index` 显示)
486 +- 允许自评
487 +
488 +**删除评论**:
489 +- 仅可删除自己的评论(通过 `is_my` 字段判断)
490 +- 教师不可删除学生的评论
491 +
492 +**教师评论管理**:
493 +- 仅教师可见审核按钮
494 +- 可屏蔽或通过任何评论
495 +
496 +### 5. 性能优化
497 +
498 +**评论列表懒加载**:
499 +- 首屏仅加载前 5 条评论
500 +- 点击"查看全部"时加载完整列表
501 +- 实现分页加载
502 +
503 +**图片懒加载**:
504 +- 海报生成时使用 `loading="lazy"`
505 +- 避免一次性加载所有图片
506 +
507 +**列表虚拟滚动**:
508 +- 如果打卡列表超过 50 条,使用 `van-list` 的虚拟滚动模式
509 +
510 +### 6. 错误处理
511 +
512 +**网络错误**:
513 +- API 调用失败时,显示 Toast 提示用户
514 +- 提供"重试"按钮
515 +
516 +**并发冲突**:
517 +- 删除评论时,若该评论已被删除,提示"该评论不存在"
518 +- 评论发表时,若打卡已被删除,提示"该打卡不存在"
519 +
520 +**海报生成失败**:
521 +- 捕获 `html2canvas` 异常
522 +- 显示降级方案(文字版卡片)
523 +
524 +### 7. 测试计划
525 +
526 +**单元测试**:
527 +- 测试 `CheckinCommentDialog` 组件的输入验证
528 +- 测试 Emoji 插入逻辑
529 +- 测试字符计数准确性
530 +
531 +**集成测试**:
532 +- 测试评论发表、回复、删除的完整流程
533 +- 测试置顶、取消置顶的完整流程
534 +- 测试海报生成流程
535 +
536 +**边界测试**:
537 +- 测试 200 字符限制
538 +- 测试纯表情评论
539 +- 测试空评论提交(应被阻止)
540 +- 测试长文本评论
541 +
542 +**兼容性测试**:
543 +- iOS Safari
544 +- Android Chrome
545 +- 微信内置浏览器
546 +- 测试 Emoji 显示兼容性
547 +
548 +---
549 +
550 +## 数据字段扩展总结
551 +
552 +### post 对象新增字段
553 +
554 +```javascript
555 +{
187 id: 123, 556 id: 123,
188 user: { name: "张三", avatar: "..." }, 557 user: { name: "张三", avatar: "..." },
189 - content: "今天完成了100天打卡!", 558 + content: "今天完成了100天打卡!",
190 images: [...], 559 images: [...],
191 videoList: [...], 560 videoList: [...],
192 audio: [...], 561 audio: [...],
193 likes: 10, 562 likes: 10,
194 is_liked: false, 563 is_liked: false,
195 is_my: true, 564 is_my: true,
196 - // 🆕 新增字段 565 +
566 + // 🆕 置顶功能字段
197 is_pinned: false, // 是否置顶 567 is_pinned: false, // 是否置顶
198 pinned_at: null, // 置顶时间 568 pinned_at: null, // 置顶时间
199 - comments_count: 0, // 评论总数(用于列表展示) 569 +
200 - comments: [] // 评论列表(最多5条) 570 + // 🆕 评论功能字段
201 - } 571 + comments_count: 0, // 评论总数
202 - ``` 572 + comments: [] // 评论列表(最多 5 条)
203 - 573 +}
204 -7. **性能优化** 574 +```
205 - - **评论列表懒加载**:仅在用户展开"查看全部"时才加载完整评论列表,首屏只加载前 5 条。 575 +
206 - - **图片懒加载**:海报生成时使用 `loading="lazy"` 避免一次性加载所有图片。 576 +### comment 对象数据结构
207 - - **列表虚拟滚动**:如果打卡列表超过 50 条,考虑使用 `van-list` 的虚拟滚动模式。 577 +
208 - 578 +```javascript
209 -8. **错误处理** 579 +{
210 - - **网络错误**:API 调用失败时,显示 Toast 提示用户,并提供"重试"按钮。 580 + id: 1, // 评论 ID
211 - - **并发冲突**:删除评论时,若该评论已被删除,提示"该评论不存在"。 581 + checkin_id: 123, // 关联的打卡 ID
212 - 582 + user_id: 456, // 评论者 ID
213 -9. **测试计划** 583 + user_name: "张三", // 评论者昵称
214 - - **单元测试**:测试 `CheckinCommentDialog` 组件的输入验证、Emoji 插入逻辑。 584 + content: "干得漂亮!🎉", // 评论内容
215 - - **集成测试**:测试评论发表、回复、删除的完整流程。 585 + parent_id: 0, // 父评论 ID (0 表示一级评论)
216 - - **边界测试**:测试 200 字符限制、Emoji 字符计数、空评论提交等边界情况。 586 + reply_to_user_id: null, // 被回复用户 ID
217 - - **兼容性测试**:在 iOS Safari、Android Chrome、微信内置浏览器中测试 Emoji 显示。 587 + reply_to_user_name: null, // 被回复用户昵称
588 + is_my: false, // 是否是自己发表的评论
589 + status: 1, // 评论状态: 0-待审核, 1-已通过, 2-已屏蔽
590 + reviewed_at: null, // 审核时间
591 + reviewed_by: null, // 审核人 ID
592 + created_at: "2026-01-26 10:00:00"
593 +}
594 +```
595 +
596 +### user 对象新增字段
597 +
598 +```javascript
599 +{
600 + id: 123,
601 + name: "张三",
602 + avatar: "...",
603 +
604 + // 🆕 角色字段
605 + is_teacher: false // 是否是教师(需实时请求后端获取)
606 +}
607 +```
218 608
219 --- 609 ---
220 610
221 -## 待确认事项 611 +## API 接口汇总
222 - 612 +
223 -### 与后端确认 613 +### 置顶功能
224 -1. **API 接口** 614 +- `POST /api/teacher/pin-checkin` - 教师置顶打卡 (参数: `checkin_id`)
225 - - `GET /checkin/comments` - 评论列表接口(是否支持分页?返回数据结构是否包含用户信息?)支持分页, 不包含用户信息. 615 +- `POST /api/teacher/unpin-checkin` - 教师取消置顶打卡 (参数: `checkin_id`)
226 - - `POST /checkin/comment` - 发表评论接口(是否需要敏感词过滤?返回值是什么?)不需要敏感词过滤, 返回值应该是评论ID. 616 +
227 - - `POST /checkin/comment/reply` - 回复评论接口(参数是否完整?)参数完整, 包含评论ID, 回复内容, 回复用户ID. 返回值应该是回复评论ID. 617 +### 评论功能
228 - - `DELETE /checkin/comment/:id` - 删除评论接口(是否有权限校验?)不需要校验, 列表会返回字段, 判断是否是自己的评论, 如果不是不会显示删除按钮. 618 +- `GET /api/checkin/comments` - 获取评论列表 (参数: `checkin_id`, `page`, `page_size`)
229 - - `teacherPinCheckinAPI` / `teacherUnpinCheckinAPI` - 接口已实现. 619 +- `POST /api/checkin/comment` - 发表评论 (参数: `checkin_id`, `content`)
230 - 620 +- `POST /api/checkin/comment/reply` - 回复评论 (参数: `checkin_id`, `content`, `parent_id`, `reply_to_user_id`)
231 -2. **数据字段** 621 +- `DELETE /api/checkin/comment/:id` - 删除评论 (参数: `comment_id`)
232 - - `post.is_pinned` - 置顶标记字段是否存在? 622 +- `POST /api/teacher/review-comment` - 教师审核评论 (参数: `comment_id`, `action`)
233 - - `post.pinned_at` - 置顶时间字段是否存在? 623 +
234 - - `post.comments_count` - 评论数字段是否存在? 624 +### 权限获取
235 - - `post.comments` - 评论列表字段是否存在?(预加载前5条) 625 +- `GET /api/user/role` - 获取用户角色信息 (返回: `{ is_teacher: boolean }`)
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 -3. **内容规范**
261 - - 评论内容的敏感词过滤策略是什么?
262 - - 是否需要举报功能?
263 - - 是否需要审核机制(评论需审核后才能显示)?
264 -
265 -4. **数据统计**
266 - - 是否需要统计评论数量、置顶数量等数据用于运营分析?
267 626
268 --- 627 ---
269 628
270 -## 总结 629 +## 开发优先级总结
271 630
272 -### ✅ 计划完善度评估 631 +### 🔴 高优先级 (第 1 批)
632 +1. **置顶功能** - 实现教师置顶打卡
633 +2. **评论功能** - 实现用户发表评论
634 +3. **评论列表** - 实现评论展示、回复、删除
635 +4. **教师评论管理** - 实现教师审核评论
273 636
274 -**总体评分:9/10** - 开发计划已经非常完善,涵盖了需求、设计、API、边界条件等各个方面。 637 +### 🟡 中优先级 (第 2 批)
638 +5. **海报功能** - 实现打卡海报生成与分享
275 639
276 -**优点:** 640 +---
277 -- ✅ 需求拆解清晰,环境变量开关设计灵活
278 -- ✅ 技术方案成熟可行(表情选择器、评论系统、海报生成)
279 -- ✅ API 设计完整,涵盖增删改查
280 -- ✅ 数据结构设计合理(扁平化评论结构)
281 -- ✅ 边界条件考虑充分(Emoji 字符计数、数据库 utf8mb4)
282 -- ✅ 参考现有代码库,复用性高
283 -- ✅ 开发步骤清晰,循序渐进
284 -- ✅ 性能优化、错误处理、测试计划都已考虑
285 641
286 -**需要改进的地方:** 642 +## 里程碑
287 -- ⚠️ 部分业务规则需要与后端/产品确认(见"待确认事项")
288 -- ⚠️ 置顶视觉标记、评论通知等功能细节需要补充
289 -- ⚠️ 错误处理和降级方案可以更详细
290 643
291 -### 🎯 建议的开发顺序 644 +### 第 1 里程碑: 置顶功能完成
645 +- ✅ 教师可以置顶/取消置顶打卡
646 +- ✅ 置顶打卡显示 📌 图标
647 +- ✅ 权限控制正确(仅教师可见)
292 648
293 -1. **第 1 阶段**:置顶功能(最简单,快速验证流程) 649 +### 第 2 里程碑: 评论功能完成
294 -2. **第 2 阶段**:评论功能 + 评论列表(核心功能,投入最大) 650 +- ✅ 用户可以发表评论(文本 + 表情)
295 -3. **第 3 阶段**:海报功能(技术难度较高,可最后开发) 651 +- ✅ 表情选择器正常工作
652 +- ✅ 输入验证正确(最少 5 字,最多 200 字)
653 +- ✅ 评论成功后通知后端(后端发送通知)
654 +
655 +### 第 3 里程碑: 评论列表完成
656 +- ✅ 评论列表正常展示(一级评论 + 回复评论)
657 +- ✅ 回复功能正常工作
658 +- ✅ 删除功能正常工作(仅删除自己的评论)
659 +- ✅ 懒加载正常工作(查看全部)
660 +- ✅ 教师可以审核评论(屏蔽/通过)
661 +
662 +### 第 4 里程碑: 海报功能完成
663 +- ✅ 海报正常生成
664 +- ✅ 支持长内容
665 +- ✅ 降级方案正常工作(生成失败时)
666 +
667 +---
668 +
669 +## 下一步行动
670 +
671 +1. ✅ 与后端/产品确认"待确认事项"中的所有问题
672 +2. ✅ 确认开发优先级和里程碑节点
673 +3. ⏳ 开始第 1 阶段开发(置顶功能)
674 +4. ⏳ 同步准备 API Mock 数据,以便前端独立开发
675 +5. ⏳ 确认后端 API 开发进度,确保前后端同步
676 +
677 +---
678 +
679 +## 附录: 常用表情列表
680 +
681 +```
682 +😊😂❤️👍🎉✨🙏💪🔥💯😍👏🤝🌟🎁
683 +😭😡🤔💭😴🎂🌈⭐🌙☀️🌺🌸🍀🎁🎈
684 +🎵🎶📱💻⚽🏀🎾🎯🎨🎬📷🎤🎧📚✏️📝
685 +💡🔔💬📧📞
686 +```
687 +
688 +**总计**: 64 个常用表情符号
689 +
690 +---
296 691
297 -### 📝 下一步行动 692 +## 变更记录
298 693
299 -1. 与后端/产品确认"待确认事项"中的所有问题 694 +**2026-01-27**:
300 -2. 确认开发优先级和里程碑节点 695 +- ✅ 与后端/产品确认所有待确认事项
301 -3. 开始第 1 阶段开发(置顶功能) 696 +- ✅ 新增教师评论管理功能
302 -4. 同步准备 API Mock 数据,以便前端独立开发 697 +- ✅ 更新开发优先级(置顶、评论、评论列表为高优先级,海报为中优先级)
698 +- ✅ 完善数据字段定义
699 +- ✅ 完善业务规则(字符限制、权限控制、通知机制)
700 +- ✅ 按优先级重新组织开发步骤
......