#!/usr/bin/env bash # ============================================================================= # MSH Bug 自动修复 + 回归测试 定时任务主脚本 # # 对应 bug 清单(来自手工测试报告 + Playwright 回归结果): # BUG-001 打卡积分显示提前跳变 & 累加逻辑未生效 # BUG-002 食谱计算器结果页 Tab 选中样式不明显 # BUG-003 食物百科列表缺配图与营养简介 # BUG-004 食物百科详情页提示「数据加载失败」 # BUG-005 AI 营养师始终返回固定默认答复 # BUG-006 「健康知识」与「营养知识」名称不统一 # BUG-007 饮食指南 / 科普文章详情页无任何内容 # BUG-008 帖子详情页营养统计数据未显示 # BUG-009 社区帖子类型命名未统一为中文 # # 总时间估算(含 Playwright 验证):≤ 5 h # 执行顺序:先修快速项(风险低),后修复杂项,最后全量回归 # ============================================================================= set -euo pipefail # ─── 路径配置 ────────────────────────────────────────────────────────────── WORKSPACE="/Users/apple/scott2026/msh-system" SCRIPTS_DIR="$WORKSPACE/scripts" LOG_DIR="$WORKSPACE/scripts/logs" CURSOR="/Applications/Cursor.app/Contents/Resources/app/bin/cursor" PW="npx playwright test tests/e2e/bug-regression.spec.ts --project=mobile-chrome --reporter=list" mkdir -p "$LOG_DIR" MAIN_LOG="$LOG_DIR/fix-bugs-$(date '+%Y%m%d_%H%M').log" LOCK_FILE="$LOG_DIR/fix-bugs.lock" # ─── 锁:防止多实例并发 ──────────────────────────────────────────────────── if [ -e "$LOCK_FILE" ]; then echo "[SKIP] 已有一个修复任务在运行 (lock: $LOCK_FILE),退出。" >&2 exit 0 fi trap 'rm -f "$LOCK_FILE"' EXIT touch "$LOCK_FILE" # ─── 日志工具 ───────────────────────────────────────────────────────────── log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$MAIN_LOG"; } phase() { echo "" | tee -a "$MAIN_LOG" echo "═══════════════════════════════════════════════" | tee -a "$MAIN_LOG" log "▶ $*" echo "═══════════════════════════════════════════════" | tee -a "$MAIN_LOG" } # ─── Cursor Agent 调用工具 ──────────────────────────────────────────────── # 参数: $1=bug_id $2=prompt文本 run_agent() { local bug_id="$1" local prompt="$2" local agent_log="$LOG_DIR/agent-${bug_id}-$(date '+%H%M%S').log" log "[${bug_id}] 启动 Cursor Agent 修复..." "$CURSOR" agent --print --trust --model auto \ --workspace "$WORKSPACE" \ "$prompt" 2>&1 | tee -a "$agent_log" >> "$MAIN_LOG" || true log "[${bug_id}] Agent 执行完成 → $agent_log" } # ─── Playwright 局部验证 ────────────────────────────────────────────────── # 参数: $1=grep_pattern (匹配用例名) run_partial_test() { local pattern="$1" log "[TEST] 验证: $pattern" cd "$WORKSPACE" $PW --grep "$pattern" 2>&1 | tee -a "$MAIN_LOG" || true } # ─── 全量回归 ──────────────────────────────────────────────────────────── run_full_regression() { log "[TEST] 全量回归测试..." cd "$WORKSPACE" $PW 2>&1 | tee -a "$MAIN_LOG" || true log "[TEST] 全量回归完成,报告: tests/e2e/reports/index.html" } # ============================================================================= # 开始 # ============================================================================= START_TS=$(date +%s) phase "阶段 0 — 基线确认(运行回归测试,记录当前失败情况)" # 软失败:仅记录,不中断脚本 cd "$WORKSPACE" $PW 2>&1 | tee -a "$MAIN_LOG" || true log "基线记录完成" # ============================================================================= # 阶段 1:快速修复(~60 min) # BUG-002 Tab 样式 | BUG-006 名称统一 | BUG-009 中文命名 # ============================================================================= phase "阶段 1 — 快速修复(BUG-002, BUG-006, BUG-009)" run_agent "BUG-002" "修复 BUG-002: 文件:msh_single_uniapp/pages/tool/calculator-result.vue 问题:食谱计算器结果页的「健康概览」和「营养配餐」两个 Tab 切换后,选中与未选中的视觉差异不明显,用户难以辨识当前激活项。 要求: 1. 增强 .tab-item.active 的视觉样式,例如:加粗字体(font-weight: 700)、橙色底部下划线(border-bottom: 3px solid #f97316)、字色变为主色(color: #f97316)。 2. 未激活 Tab 字色应明显变灰(color: #9ca3af),不加下划线。 3. 不得修改 Tab 的 JS 逻辑,只改 CSS 样式。 4. 修改前先阅读该文件的 .tab-container / .tab-item / .tab-item.active 样式。 请执行修改。" run_agent "BUG-006" "修复 BUG-006: 文件:msh_single_uniapp/pages/tool_main/index.vue 以及 msh_single_uniapp/pages/tool/nutrition-knowledge.vue 问题:主页「健康知识」区块标题与营养知识页面导航栏标题命名不一致,一处用「健康知识」,另一处用「营养知识」。 要求: 1. 先阅读两个文件,确认各自现有文案。 2. 统一将两处名称改为「健康知识」(若现在 nutrition-knowledge.vue 导航栏写的是「营养知识」,改成「健康知识」)。 3. 仅修改文案字符串,不改动任何逻辑或样式。 请执行修改。" run_agent "BUG-009" "修复 BUG-009: 文件:msh_single_uniapp/pages/tool_main/community.vue 问题:社区页面的 Tab 标签(推荐/最新/关注/热门)和帖子类型标签(.type-tag / .meal-tag)中存在英文命名或非中文命名,需统一为中文。 要求: 1. 阅读 community.vue 的 template 部分,找出所有 Tab 文案和帖子类型标签文案。 2. 将所有英文或拼音命名改为对应中文(如 recommend→推荐, latest→最新 等)。 3. 检查数据中的类型字段(如 type: 'breakfast'),如显示时有 label 映射,确保 label 均为中文。 4. 不改动路由、接口调用等逻辑。 请执行修改。" log "[阶段1] 阶段1修复完成,进行局部验证..." run_partial_test "TC-B02|TC-B06|TC-B09" # ============================================================================= # 阶段 2:中等难度修复(~60 min) # BUG-001 打卡积分 | BUG-007 文章详情内容 # ============================================================================= phase "阶段 2 — 中等修复(BUG-001, BUG-007)" run_agent "BUG-001" "修复 BUG-001(共两个子问题): 文件:msh_single_uniapp/pages/tool/checkin.vue 子问题 A — 打卡前积分提前跳变: 点击「立即打卡」后,在页面跳转前,前端已将积分 +30 显示出来,造成视觉误导。 修复:handleCheckin 方法中,不得在 API 返回成功前修改 currentPoints。 仅在 API 调用成功、且获取到服务端最新积分后才更新 currentPoints(优先用服务端返回的值,不做前端本地累加)。 子问题 B — 打卡成功后积分未实际增加: 后端积分累加逻辑可能未被正确触发。 修复: 1. 阅读 handleCheckin 中的 API 调用,确认调用的是 /api/front/user/checkin 或类似接口。 2. 若调用成功,再发请求 GET /api/front/user/info 刷新用户积分,并将返回值赋给 currentPoints。 3. 不可使用硬编码 +30;积分值必须来自服务端响应。 请先阅读文件,再执行修改。 " run_agent "BUG-007" "修复 BUG-007: 文件:msh_single_uniapp/pages/tool/nutrition-knowledge.vue 问题:切换到「饮食指南」或「科普文章」Tab 后,列表为空(guideList / articleList 均为 []),点击条目进入详情页后也无任何内容,页面一片空白。 要求: 1. 阅读 loadKnowledgeList 方法和 getKnowledgeList API 调用。 2. 如果 API 正常但数据为空,请检查请求参数(type 字段的值是否正确:guide / article)。 3. 如果 API 失败,添加 catch 后的错误提示(uni.showToast)并确保 guideList / articleList 不被设为 undefined。 4. 在 onLoad 中,若没有 id 参数,应默认加载当前 tab 对应的列表(当前为 nutrients,应在 switchTab 后加载 guide/articles 列表)。 5. 修复详情页跳转:goToDetail 方法中,确保 knowledgeId 或 id 字段存在才跳转,否则提示「暂无详情」。 请先阅读文件,再执行修改。" log "[阶段2] 阶段2修复完成,进行局部验证..." run_partial_test "TC-B01|TC-B07" # ============================================================================= # 阶段 3:数据展示修复(~60 min) # BUG-003 食物列表 | BUG-008 营养统计 # ============================================================================= phase "阶段 3 — 数据展示修复(BUG-003, BUG-008)" run_agent "BUG-003" "修复 BUG-003: 文件:msh_single_uniapp/pages/tool/food-encyclopedia.vue 问题:食物百科全部列表页中,所有食物条目均未展示配图(.food-image)和营养简介(.nutrition-item)。 要求: 1. 阅读 food-encyclopedia.vue 的 template 中 .food-item 部分,找到 .food-image 的 :src 绑定和 .nutrition-item 的 v-for 数据来源。 2. 检查数据加载方法(如 loadFoodList 或 getFoodList API),确认响应数据结构,找出 image/imageUrl/img 字段和 nutrition/nutrients 字段。 3. 修复字段映射:确保 .food-image 的 :src 绑定到正确字段(如 item.imageUrl || item.image),且 .nutrition-item 遍历正确的数组(如 item.nutrients || item.nutritions)。 4. 若图片字段为空,显示一个默认占位图(可使用已有的本地资源或灰色背景)。 请先阅读文件和相关 API 文件(api/tool.js),再执行修改。" run_agent "BUG-008" "修复 BUG-008: 文件:msh_single_uniapp/pages/tool/post-detail.vue 问题:帖子详情页中,营养统计数据(.nutrition-stats-card)不显示,.stat-item 数组为空。 要求: 1. 阅读 post-detail.vue 的 data 中 postData.nutritionStats 的初始化和赋值逻辑。 2. 找到加载帖子详情的方法(如 loadPostDetail),确认后端响应中是否有 nutritionStats 字段。 3. 若后端无该字段,根据帖子关联的饮食打卡数据(如 dietaryData / mealData),计算蛋白质、热量、钾、磷等关键营养素,填充 nutritionStats 数组格式:[{label:'蛋白质', value:'56g'}, ...]。 4. 若后端有该字段但字段名不一致,修复映射。 5. 营养统计卡片的显示条件(v-if)应改为:nutritionStats.length > 0,而不是依赖后端字段存在性。 请先阅读文件,再执行修改。" log "[阶段3] 阶段3修复完成,进行局部验证..." run_partial_test "TC-B03|TC-B08" # ============================================================================= # 阶段 4:复杂修复(~90 min) # BUG-004 食物详情 | BUG-005 AI 回复 # ============================================================================= phase "阶段 4 — 复杂修复(BUG-004, BUG-005)" run_agent "BUG-004" "修复 BUG-004: 文件:msh_single_uniapp/pages/tool/food-detail.vue 问题:点击任意食物条目进入详情页,提示「数据加载失败」,页面无法正常展示食物名称、营养成分等信息。 要求: 1. 阅读 food-detail.vue 的 onLoad 和数据加载方法(如 loadFoodDetail)。 2. 打印或记录 API 请求参数(id、name 等),确认传参是否正确。 3. 如果 API 调用失败,catch 块中不要只 showToast,应同时: a. 将 loadError 置为具体错误信息(用于调试), b. 使用 defaultFoodData 填充页面,保证用户能看到基础界面而不是空白, c. 在页面显示「当前数据来自缓存,可能不是最新」提示。 4. 确认 .food-name-overlay / .nutrient-card / .nutrition-row 等元素在 defaultFoodData 状态下也能正常渲染(数据不为空数组/空字符串)。 5. 如果问题是 API 路径或参数有误(如 id 传了 name 字段),同时修复调用参数。 请先阅读文件和 api/tool.js,再执行修改。" run_agent "BUG-005" "修复 BUG-005(对话接口已改为 KieAI Gemini chat,若仍有固定回复再修): 文件:msh_single_uniapp/pages/tool/ai-nutritionist.vue、api/models-api.js 后端:msh_crmeb_22 的 /api/front/kieai/gemini/chat(ToolKieAIServiceImpl.geminiChat) 要求: 1. 文本对话必须走 KieAI Gemini 大模型:调用 POST /api/front/kieai/gemini/chat,请求体为 { messages: [{ role: 'user', content: 用户输入 }], stream: false }。 2. 前端从响应 data.choices[0].message.content 取回复并展示,不得使用 getAIResponse 等固定话术作为接口成功时的回复。 3. 若后端 ToolKieAIServiceImpl.geminiChat 或 buildGeminiRequestBody 中存在硬编码 prompt,改为使用 request.getMessages() 透传用户内容。 4. 不修改 UI 布局,仅保证数据流:用户输入 → kieai/gemini/chat → 展示模型返回内容。" log "[阶段4] 阶段4修复完成,进行局部验证..." run_partial_test "TC-B04|TC-B05" # ============================================================================= # 阶段 5:全量回归 + 报告 # ============================================================================= phase "阶段 5 — 全量回归测试 + 报告生成" run_full_regression END_TS=$(date +%s) ELAPSED=$(( END_TS - START_TS )) ELAPSED_MIN=$(( ELAPSED / 60 )) log "────────────────────────────────────────────────" log "全部阶段完成!总耗时:${ELAPSED_MIN} 分钟(${ELAPSED} 秒)" log "回归报告:$WORKSPACE/tests/e2e/reports/index.html" log "详细日志:$MAIN_LOG" log "────────────────────────────────────────────────"