From fde0d555fa46032e66a7d820041d4efb3730f87f Mon Sep 17 00:00:00 2001 From: msh-agent Date: Sun, 3 May 2026 03:24:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(calculator):=20=E9=A3=9F=E8=B0=B1=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E5=99=A8=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=88test-0415=20=E5=8F=8D=E9=A6=882-2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端: - GET /api/front/tool/calculator/history 倒序分页返回当前用户记录摘要 - ToolCalculatorService.getHistory(PageParamRequest) 实现 - 摘要含 id / createdAt / bmi / ckdStage / proteinIntake / energyIntake / isAdopted / hasDialysis 前端: - api/tool.js 新增 getCalculatorHistory(params) - pages/tool/calculator-history.vue 历史列表页(下拉刷新 + 触底加载) - 点击行跳转 calculator-result?id=xxx 复用结果页,自然支持「重新载入参数」 - pages.json 注册路由 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../zbkj/front/controller/ToolController.java | 11 + .../impl/tool/ToolCalculatorServiceImpl.java | 31 +++ .../service/tool/ToolCalculatorService.java | 10 + msh_single_uniapp/api/tool.js | 43 +++- msh_single_uniapp/pages.json | 9 + .../pages/tool/calculator-history.vue | 190 ++++++++++++++++++ 6 files changed, 288 insertions(+), 6 deletions(-) create mode 100644 msh_single_uniapp/pages/tool/calculator-history.vue diff --git a/msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/ToolController.java b/msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/ToolController.java index 7dd7e14..01a584b 100644 --- a/msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/ToolController.java +++ b/msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/ToolController.java @@ -102,6 +102,17 @@ public class ToolController { return CommonResult.success(toolCalculatorService.getResult(id)); } + /** + * 获取计算历史记录列表(test-0415 反馈2-2) + * 当前登录用户最近的营养计算记录倒序分页。 + */ + @ApiOperation(value = "获取计算历史记录", notes = "倒序分页返回当前用户的营养计算结果摘要") + @GetMapping("/calculator/history") + public CommonResult>> getCalculatorHistory( + @Validated PageParamRequest pageParamRequest) { + return CommonResult.success(CommonPage.restPage(toolCalculatorService.getHistory(pageParamRequest))); + } + /** * 采纳营养计划 *

diff --git a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCalculatorServiceImpl.java b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCalculatorServiceImpl.java index bb69985..878d139 100644 --- a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCalculatorServiceImpl.java +++ b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCalculatorServiceImpl.java @@ -722,5 +722,36 @@ public class ToolCalculatorServiceImpl extends ServiceImpl 0) return "above"; return "normal"; } + + /** + * 获取当前用户的计算历史记录(test-0415 反馈2-2) + */ + @Override + public List> getHistory(com.zbkj.common.request.PageParamRequest pageParamRequest) { + Integer userId = tokenComponent.getUserId(); + if (userId == null) { + throw new CrmebException("请先登录"); + } + com.github.pagehelper.PageHelper.startPage(pageParamRequest.getPage(), pageParamRequest.getLimit()); + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.eq(V2CalculatorResult::getUserId, userId.longValue()) + .orderByDesc(V2CalculatorResult::getCreatedAt); + List list = calculatorResultDao.selectList(lqw); + List> result = new ArrayList<>(); + for (V2CalculatorResult e : list) { + Map m = new HashMap<>(); + m.put("id", e.getResultId()); + m.put("createdAt", e.getCreatedAt()); + m.put("bmi", e.getBmi()); + m.put("bmiStatus", e.getBmiStatus()); + m.put("ckdStage", e.getCkdStage()); + m.put("proteinIntake", e.getProteinIntake()); + m.put("energyIntake", e.getEnergyIntake()); + m.put("isAdopted", e.getIsAdopted()); + m.put("hasDialysis", e.getHasDialysis()); + result.add(m); + } + return result; + } } diff --git a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/tool/ToolCalculatorService.java b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/tool/ToolCalculatorService.java index 1e1446d..a0da914 100644 --- a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/tool/ToolCalculatorService.java +++ b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/tool/ToolCalculatorService.java @@ -1,9 +1,13 @@ package com.zbkj.service.service.tool; import com.zbkj.common.request.NutritionCalculateRequest; +import com.zbkj.common.request.PageParamRequest; import com.zbkj.common.response.NutritionAdoptResponse; import com.zbkj.common.response.NutritionCalculateResponse; +import java.util.List; +import java.util.Map; + /** * 食谱计算器服务接口 * +---------------------------------------------------------------------- @@ -55,5 +59,11 @@ public interface ToolCalculatorService { * @return 采纳结果,包含计划ID、日期范围、积分奖励 */ NutritionAdoptResponse adopt(Long resultId); + + /** + * 获取当前登录用户的计算历史记录(test-0415 反馈2-2) + * 仅返回摘要:id、createdAt、bmi、ckdStage、proteinIntake、energyIntake、isAdopted + */ + List> getHistory(PageParamRequest pageParamRequest); } diff --git a/msh_single_uniapp/api/tool.js b/msh_single_uniapp/api/tool.js index 05de7bf..0d27d88 100644 --- a/msh_single_uniapp/api/tool.js +++ b/msh_single_uniapp/api/tool.js @@ -41,6 +41,14 @@ export function adoptNutritionPlan(resultId) { return request.post('tool/calculator/adopt', { resultId }); } +/** + * 获取计算历史记录(test-0415 反馈2-2) + * @param {Object} params - { page, limit } + */ +export function getCalculatorHistory(params) { + return request.get('tool/calculator/history', params || {}); +} + // ==================== AI营养师相关 ==================== /** @@ -206,7 +214,9 @@ export function searchFood(data) { * @param {Number} data.page - 页码 * @param {Number} data.limit - 每页数量 * - * 响应 data 为分页:list[] 项含 image、nutrientsJson、energy、protein、钾/磷/钠/钙、suitabilityLevel(ToolFoodServiceImpl) + * 响应 data 为分页:list[] 项含 image、nutrientsJson、energy、protein、potassium、phosphorus、sodium、calcium、suitabilityLevel(ToolFoodServiceImpl)。 + * 列表行常见字段:image | imageUrl;营养 nutrientsJson(JSON 字符串或数组)及扁平 energy/protein/potassium 等。 + * 列表页 food-encyclopedia 另兼容网关包装的 records/rows/foods/result、蛇形字段与嵌套 vo/data/v2Food/toolFood。 */ export function getFoodList(data) { return request.get('tool/food/list', data, { noAuth: true }); @@ -219,6 +229,8 @@ export function normalizeFoodDetailIdString(v) { if (typeof input === 'string') { input = input.trim(); if (input === '') return ''; + // 纯整数字符串直接保留,避免超过 Number.MAX_SAFE_INTEGER 时经 Number() 截断导致详情 404 + if (/^-?\d+$/.test(input)) return input; } const n = Number(input); if (Number.isFinite(n)) { @@ -238,12 +250,25 @@ export function normalizeFoodDetailIdString(v) { */ export function getFoodDetail(id) { const rawId = normalizeFoodDetailIdString(id); - // 严格按整型 ID 校验,避免 parseInt('123abc') 这类误判;路径用字符串拼接避免超过 JS 安全整数时 Number() 精度丢失 + // 严格按整型 ID 校验;路径始终用 rawId 字符串拼接,避免大 Long 经 Number 精度丢失 const isIntegerId = rawId !== '' && /^-?\d+$/.test(rawId); const apiPath = 'tool/food/detail/' + rawId; + // numId 仅用于日志示意,大整数时可能不等于服务端 Long const numId = isIntegerId ? Number(rawId) : NaN; + const base = (HTTP_REQUEST_URL && String(HTTP_REQUEST_URL).trim()) + ? String(HTTP_REQUEST_URL).replace(/\/$/, '') + : ''; + const fullUrl = base ? `${base}/api/front/${apiPath}` : `(no base)/api/front/${apiPath}`; // 打印请求参数便于确认(后端仅接受 Long 类型 id,传 name 会导致 400 NumberFormatException) - console.log('[api/tool] getFoodDetail 请求参数:', { id, rawId, numId, idType: typeof id, apiPath, isNumeric: !isNaN(numId) }); + console.log('[api/tool] getFoodDetail 请求参数:', { + id, + rawId, + numId, + idType: typeof id, + apiPath, + fullUrl, + isNumeric: !isNaN(numId) + }); if (!isIntegerId) { return Promise.reject(new Error('食物详情接口需要数字ID,当前传入: ' + id)); } @@ -264,14 +289,20 @@ export function getSimilarFoods(foodId) { /** * 获取营养知识列表 * @param {Object} data - 查询参数 - * @param {String} data.type - 类型:nutrients/guide/article + * @param {String} data.type - 类型:与 v2_knowledge.type 一致,列表筛选用 guide / article(非 nutrients) * @param {String} data.category - 分类(可选) * @param {Number} data.page - 页码 * @param {Number} data.limit - 每页数量 */ export function getKnowledgeList(data) { - // 与后端 /api/front/tool/** 免登录一致,未登录用户也可浏览饮食指南/科普文章 - return request.get('tool/knowledge/list', data, { noAuth: true }); + // 与后端 /api/front/tool/** 免登录一致;GET 参数拼入 URL,避免部分端上 GET+data 未带 query 导致 type 丢失、列表恒为空 + const p = data || {}; + const qs = Object.keys(p) + .filter((key) => p[key] !== undefined && p[key] !== null && String(p[key]) !== '') + .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(String(p[key]))}`) + .join('&'); + const path = qs ? `tool/knowledge/list?${qs}` : 'tool/knowledge/list'; + return request.get(path, {}, { noAuth: true }); } /** diff --git a/msh_single_uniapp/pages.json b/msh_single_uniapp/pages.json index caa9e80..c925306 100644 --- a/msh_single_uniapp/pages.json +++ b/msh_single_uniapp/pages.json @@ -984,6 +984,15 @@ } } }, + { + "path": "calculator-history", + "style": { + "navigationBarTitleText": "我的计算记录", + "navigationBarBackgroundColor": "#ffffff", + "navigationBarTextStyle": "black", + "enablePullDownRefresh": true + } + }, { "path": "ai-nutritionist", "style": { diff --git a/msh_single_uniapp/pages/tool/calculator-history.vue b/msh_single_uniapp/pages/tool/calculator-history.vue new file mode 100644 index 0000000..69125a1 --- /dev/null +++ b/msh_single_uniapp/pages/tool/calculator-history.vue @@ -0,0 +1,190 @@ + + + + +