Переглянути джерело

chore: auto bump version and changelog [skip ci]

iwt 15 годин тому
батько
коміт
8d239444e6

+ 1 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -6305,6 +6305,7 @@ sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min,
         // 迁移134: products表添加sort_order列(商品按排序字段排序展示)
         ensureColumn("products", "sort_order", "INT DEFAULT 0 COMMENT '排序(值越小越靠前)'");
         ensureColumn("products", "recommendation_tags", "VARCHAR(500) DEFAULT NULL COMMENT '推荐标签'");
+        ensureColumn("products", "is_recommended", "TINYINT(1) DEFAULT 0 COMMENT '是否推荐商品 0否 1是'");
         ensureColumn("products", "repurchase_interval_days", "INT DEFAULT 30 COMMENT '复购间隔天数'");
 
         // 迁移134: 创建 category_ppoint 表(品类P点比例配置)

+ 2 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/ProductDTO.java

@@ -36,6 +36,7 @@ public class ProductDTO {
     private String minVisibleLevel;        // 最低可见会员级别编码,NULL=全部可见
     private String minDiscountLevel;       // 享受会员价所需的最低会员级别,NULL=所有会员均可
     private Boolean vendorVisible;         // 是否仅供应商体系内用户可见(列表接口生效)
+    private Integer isRecommended;         // 是否推荐商品: 0=否 1=是(推荐时同步至营养产品)
     private List<String> imageList;
     private Long energyDimensionId;
     private Integer effectivePpoint;     // 有效P点(基点,如1000=10%)
@@ -127,6 +128,7 @@ public class ProductDTO {
         d.minVisibleLevel = p.getMinVisibleLevel();
         d.minDiscountLevel = p.getMinDiscountLevel();
         d.vendorVisible = p.getVendorVisible();
+        d.isRecommended = p.getIsRecommended();
         d.createdAt = p.getCreatedAt();
         d.updatedAt = p.getUpdatedAt();
         // 会员体系重构:商品对所有用户统一定价(活动侧保留会员价,商品侧停用)

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/Product.java

@@ -63,6 +63,9 @@ public class Product implements Serializable {
     /** 是否仅供应商体系内用户可见(在列表接口生效,详情页不受影响,直接分享可 bypass) */
     private Boolean vendorVisible;
 
+    /** 是否推荐商品: 0=否 1=是(推荐时同步至营养产品表) */
+    private Integer isRecommended;
+
     // 佣金系统 - 平台利润率(千分比)
     private Integer profitRate;
 

+ 55 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/NutritionProductService.java

@@ -187,6 +187,61 @@ public class NutritionProductService {
         );
     }
 
+    /**
+     * 将推荐商品同步至营养产品表。
+     * isRecommended=1 时创建或更新 nutrition_product 记录;
+     * isRecommended=0 时将已有记录置为下架(不删除)。
+     */
+    public void syncFromProduct(Product product) {
+        if (product == null || product.getId() == null) return;
+        boolean recommended = product.getIsRecommended() != null && product.getIsRecommended() == 1;
+        NutritionProduct existing = nutritionProductMapper.selectOne(
+            new LambdaQueryWrapper<NutritionProduct>()
+                .eq(NutritionProduct::getLocalProductId, product.getId())
+                .last("LIMIT 1")
+        );
+        if (recommended) {
+            if (existing == null) {
+                existing = new NutritionProduct();
+                existing.setLocalProductId(product.getId());
+                existing.setName(product.getName());
+                existing.setImageUrl(product.getCoverImage());
+                existing.setProductImages(product.getImages());
+                existing.setDescription(product.getDescription());
+                existing.setPrice(convertFenToYuan(product.getPrice()));
+                if (product.getMemberPrice() != null && product.getMemberPrice() > 0) {
+                    existing.setOriginalPrice(convertFenToIntYuan(product.getMemberPrice()));
+                }
+                existing.setStatus(1);
+                nutritionProductMapper.insert(existing);
+            } else {
+                existing.setName(product.getName());
+                existing.setImageUrl(product.getCoverImage());
+                existing.setProductImages(product.getImages());
+                existing.setDescription(product.getDescription());
+                existing.setPrice(convertFenToYuan(product.getPrice()));
+                if (product.getMemberPrice() != null && product.getMemberPrice() > 0) {
+                    existing.setOriginalPrice(convertFenToIntYuan(product.getMemberPrice()));
+                }
+                existing.setStatus(1);
+                nutritionProductMapper.updateById(existing);
+            }
+        } else if (existing != null) {
+            existing.setStatus(0);
+            nutritionProductMapper.updateById(existing);
+        }
+    }
+
+    private BigDecimal convertFenToYuan(Integer fen) {
+        if (fen == null) return BigDecimal.ZERO;
+        return BigDecimal.valueOf(fen).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
+    }
+
+    private Integer convertFenToIntYuan(Integer fen) {
+        if (fen == null) return 0;
+        return fen / 100;
+    }
+
     // ---- 评分私有方法 ----
 
     private double calcIngredientScore(NutritionProduct product, NutritionRecommendDTO.RecommendQuery query) {

+ 4 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ProductService.java

@@ -170,6 +170,7 @@ wrapper.in(Product::getId, productIds)
                 }
             });
         }
+        nutritionProductService.syncFromProduct(product);
         // 供应商可见过滤:仅对非供应商体系用户生效,供应商体系内用户可查看全部商品
         boolean isVendor = isVendorUser(userId, role);
         if (!isVendor && !isGuest(userId)) {
@@ -250,6 +251,7 @@ wrapper.in(Product::getId, productIds)
                 ProductDTO.BundleItemVO vo = new ProductDTO.BundleItemVO();
                 vo.setChildProductId(child.getId());
                 vo.setChildProductName(child.getName());
+        nutritionProductService.syncFromProduct(product);
                 vo.setCoverImage(child.getCoverImage());
                 vo.setChildSkuId(bi.getChildSkuId());
                 vo.setQuantity(bi.getQuantity());
@@ -329,6 +331,7 @@ wrapper.in(Product::getId, productIds)
         product.setStatus("pending");
         product.setUpdatedAt(new Date());
         productMapper.updateById(product);
+        nutritionProductService.syncFromProduct(product);
         return Result.success(ProductDTO.from(productMapper.selectById(product.getId())));
     }
 
@@ -562,6 +565,7 @@ wrapper.in(Product::getId, productIds)
         // 避免管理端 el-select 清空提交 '' 导致商品对所有用户不可见
         boolean clearMinVisibleLevel = normalizeMinVisibleLevel(product);
         productMapper.updateById(product);
+        nutritionProductService.syncFromProduct(product);
         // updateById 默认忽略 null 字段,清空时需显式将 min_visible_level 置 NULL
         if (clearMinVisibleLevel) {
             productMapper.update(null, new LambdaUpdateWrapper<Product>()

+ 16 - 15
cfc-backend/src/main/resources/schema.sql

@@ -1190,6 +1190,7 @@ CREATE TABLE IF NOT EXISTS products (
     visible_roles JSON DEFAULT NULL COMMENT '鍙��瑙掕壊: ["parent","child","teacher","admin"]锛孨ULL=鍏ㄩ儴鍙��',
     min_visible_level VARCHAR(50) DEFAULT NULL COMMENT '鏈€浣庡彲瑙佷細鍛樼骇鍒�紪鐮�(FREE/FAMILY/PROVIDER/PREMIUM/LIFETIME锛孨ULL=鍏ㄩ儴鍙��)',
     min_discount_level VARCHAR(50) DEFAULT NULL COMMENT '浜�彈浼氬憳浠锋墍闇€鐨勬渶浣庝細鍛樼骇鍒�紪鐮�(FREE/FAMILY/PREMIUM/LIFETIME锛孨ULL=鎵€鏈変細鍛樺潎鍙�)',
+    is_recommended TINYINT(1) DEFAULT 0 COMMENT '鏄�惁鎺ㄨ崘鍟嗗搧 0鍚� 1鏄�',
     created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿'
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鍟嗗搧琛�';
@@ -6275,26 +6276,26 @@ CREATE TABLE IF NOT EXISTS indicator_correlations (
     INDEX idx_def_b (definition_id_b)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鎸囨爣鍏宠仈鍏崇郴(浜哄伐瀹氫箟+鏁版嵁鏍″噯)';
 
--- 迁移363: 创建采购任务表(商品组合推荐与采购任务生命周期管理)
+-- 迁锟斤拷363: 锟斤拷锟斤拷锟缴癸拷锟斤拷锟斤拷锟斤拷锟斤拷锟狡凤拷锟斤拷锟狡硷拷锟斤拷晒锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷诠锟斤拷锟斤拷锟�
 CREATE TABLE IF NOT EXISTS procurement_tasks (
     id BIGINT AUTO_INCREMENT PRIMARY KEY,
-    task_id VARCHAR(64) NOT NULL COMMENT '任务唯一标识',
-    user_id BIGINT NOT NULL COMMENT '所属用户ID',
-    family_member_id BIGINT NOT NULL COMMENT '所属家庭成员ID',
-    combo_tier VARCHAR(20) NOT NULL COMMENT '选择的套餐等级: high|medium|low',
-    original_combo_tier VARCHAR(20) DEFAULT NULL COMMENT '原始套餐等级(用于自定义追踪)',
-    selected_product_sku_ids TEXT COMMENT '最终选中的产品SKU列表(JSON数组)',
-    custom_addition_sku_ids TEXT COMMENT '用户自新增的产品SKU列表(JSON数组)',
-    custom_removal_sku_ids TEXT COMMENT '用户移除的产品SKU列表(JSON数组)',
-    status VARCHAR(32) NOT NULL COMMENT '任务状态: SELECTED|PROCURING|PROCURED|EXECUTING|COMPLETED|CANCELLED',
-    create_time DATETIME NOT NULL COMMENT '创建时间',
-    complete_time DATETIME DEFAULT NULL COMMENT '完成时间',
-    message_reminder_sent TINYINT DEFAULT 0 COMMENT '是否已发送消息提醒',
-    execution_task_id VARCHAR(64) DEFAULT NULL COMMENT '关联的执行任务ID',
+    task_id VARCHAR(64) NOT NULL COMMENT '锟斤拷锟斤拷唯一锟斤拷识',
+    user_id BIGINT NOT NULL COMMENT '锟斤拷锟斤拷锟矫伙拷ID',
+    family_member_id BIGINT NOT NULL COMMENT '锟斤拷锟斤拷锟斤拷庭锟斤拷员ID',
+    combo_tier VARCHAR(20) NOT NULL COMMENT '选锟斤拷锟斤拷撞偷燃锟�: high|medium|low',
+    original_combo_tier VARCHAR(20) DEFAULT NULL COMMENT '原始锟阶餐等硷拷锟斤拷锟斤拷锟斤拷锟皆讹拷锟斤拷追锟劫o拷',
+    selected_product_sku_ids TEXT COMMENT '锟斤拷锟斤拷选锟叫的诧拷品SKU锟叫憋拷锟斤拷JSON锟斤拷锟介)',
+    custom_addition_sku_ids TEXT COMMENT '锟矫伙拷锟斤拷锟斤拷锟斤拷锟侥诧拷品SKU锟叫憋拷锟斤拷JSON锟斤拷锟介)',
+    custom_removal_sku_ids TEXT COMMENT '锟矫伙拷锟狡筹拷锟侥诧拷品SKU锟叫憋拷锟斤拷JSON锟斤拷锟介)',
+    status VARCHAR(32) NOT NULL COMMENT '锟斤拷锟斤拷状态: SELECTED|PROCURING|PROCURED|EXECUTING|COMPLETED|CANCELLED',
+    create_time DATETIME NOT NULL COMMENT '锟斤拷锟斤拷时锟斤拷',
+    complete_time DATETIME DEFAULT NULL COMMENT '锟斤拷锟绞憋拷锟�',
+    message_reminder_sent TINYINT DEFAULT 0 COMMENT '锟角凤拷锟窖凤拷锟斤拷锟斤拷息锟斤拷锟斤拷',
+    execution_task_id VARCHAR(64) DEFAULT NULL COMMENT '锟斤拷锟斤拷锟斤拷执锟斤拷锟斤拷锟斤拷ID',
     update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     UNIQUE KEY uk_task_id (task_id),
     INDEX idx_user_id (user_id),
     INDEX idx_family_member_id (family_member_id),
     INDEX idx_status (status),
     INDEX idx_create_time (create_time)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='采购任务表(商品组合推荐与采购任务生命周期管理)';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='锟缴癸拷锟斤拷锟斤拷锟斤拷锟斤拷锟狡凤拷锟斤拷锟狡硷拷锟斤拷晒锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷诠锟斤拷锟斤拷锟�';

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-89adf93c555227a41ef8c0fa5ed6811aa3845647
+fd2455983f841de070a9ec390b0cab87e42a407a

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1584",
+  "version": "1.0.1585",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1584",
+      "version": "1.0.1585",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1585",
+  "version": "1.0.1586",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 108 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,114 @@
 
 ---
 
+## v1.0.1586 (2026-10-12)
+
+### Bug 修复
+- 修复微信扫码登录 405、移除令牌输入框、悬停暂停活动与精细化动画
+- schema.sql 建表语句提取支持引号内分号
+- 修复编译错误(补充scan-login.json,移除main.js中不存在的login-sheet引用)
+- 修复创建入库单页表单内容显示不全无法滚动问题
+- reminder-config :key 修复 v-for index 未定义
+- 修复 reminder-config 页 :key 表达式导致非 h5 编译失败
+- 京东云存储下 LangGraph 改读共享卷缓存路径
+
+### 其他
+- - models.rs/commands.rs: 兼容旧版 qrconnect 后端(仅 {state,url} 无小程序码),给出明确「需部署最新后端」提示
+- - settings.html: 移除 JWT 令牌输入框及相关读写(保留 hasToken 探测)
+- - locomotion.rs: 新增 cursor_over_pet() 悬停检测,鼠标落在宠物身上时立即暂停走/跳/飞并保持静止(新增单测)
+- - index.html/animations.css/pet-pose.js: 姿态动画精细化(方向镜像 --face、走/跳/飞/站手更丰富关键帧、新增呼吸待机 pose-idle),并加固宠物 cursor:pointer
+- 
+- 遇到 COMMENT 字符串字面量内的分号(task_reminder_config.offset_minutes
+- 注释"截止前N分钟; after_timeout...")会提前截断,语句不完整导致
+- 每次启动告警且该表无法由 schema.sql 创建。
+- 改为按字符扫描、跳过单引号/反引号内分号,完整切分 338 条语句(与旧实现同集)。
+- 
+- - HealthReportController: 去掉 2 处 startsWith(http) 直传 OSS URL 的捷径,统一经 getFilePath 下载
+- - 代上传(proxy-parse): 改用 storageService 存 OSS 再经 getFilePath 传给 LangGraph
+- 
+- ### 1. BASIC 会员恢复(f86dd164b 反向还原)
+- - 迁移360 restoreBasicMembershipLevel() 恢复 membership_levels BASIC 行 + membership_benefits 4 条 LITE 权益
+- - backfillMembershipLevelOrder 修正 level_order:FREE=1 BASIC=2 FAMILY=3 PROVIDER=4 PREMIUM=5 LIFETIME=6
+- - BasicQuotaService 恢复 refreshMonthlyQuota/isActiveBasicMember/getExpireTime
+- - MembershipService 恢复 BASIC canUseFeature/getMemberLevel 到期判断/listActive/配额初始化
+- - 新增 FOOD_RECOGNIZE 配额(3次/月),控制器层校验 FREE→403
+- - schema.sql 同步种子数据 + 注释
+- ### 2. 饮食打卡分享
+- - diet-checkin.vue / records.vue / record-detail.vue 接入 share-mixin
+- - 分享链接自动带 refCode,新用户注册后自动绑定推荐人
+- ### 3. cfclub 分支合并(~300+ 文件)
+- - 新模块:Indicator(指标曲线/相关性/映射)、FatDiet/FatExercise/FatGoal/FatWeight(减脂)、GutHealth(菌群健康)、SsoTicket(单点登录)、AdminTaskReminderConfig
+- - 前端:ContentSharePoster.vue、TrendChart.vue、indicator-curve/quicklook 页面、self-check-ai、indicator-change/sso utils
+- - LangGraph:indicator_classify、question_generate、report_parse 更新
+- - Web 管理端:IndicatorCorrelations/IndicatorMappings、api/indicator.js、utils/indicatorSeeds.js
+- - xiaofat-miniprogram(独立减脂小程序 Demo)纳入版本库
+- - docs/superpowers/plans/ & specs/ 补充完整
+- ## 主提交
+- - DatabaseInitializer.java、schema.sql、BasicQuotaService.java、MembershipService.java
+- - DietRecordController.java、diet-checkin.vue、records.vue、record-detail.vue
+- - docs/superpowers/specs/2026-10-11-restore-basic-and-diet-share-design.md
+- - 全部 cfclub 分支新增/修改文件
+- 
+- ### 1. 恢复 BASIC 会员等级
+- - 迁移360 restoreBasicMembershipLevel() 重新插入 membership_levels 的 BASIC 行(level_order=2,月费990,max_children=3,max_tasks_per_day=3)和 membership_benefits 的 4 条 LITE 权益(AI_CHAT_LITE 3次/月,REPORT_READING_LITE 1次/月,GROWTH_PLAN_LITE,ASSESSMENT_QUOTA_LITE 1次)
+- - 修正 backfillMembershipLevelOrder 的 level_order 映射:FREE=1 BASIC=2 FAMILY=3 PROVIDER=4 PREMIUM=5 LIFETIME=6(BASIC 插回 FREE 与 FAMILY 之间)
+- - 同步 schema.sql:BASIC 种子数据 + level_order 与 quota_type 字段注释
+- ### 2. 图片解析(食材识别)配额校验
+- - BasicQuotaService.QUOTA_LIMITS 增加 FOOD_RECOGNIZE=3
+- - BasicQuotaService 恢复 isActiveBasicMember / getExpireTime / refreshMonthlyQuota 真实实现
+- - 新增 consumeFoodRecognizeQuota(userId):FAMILY+ 不限次、BASIC 走月度配额、FREE 403 升级提示
+- - DietRecordController./recognize 注入 BasicQuotaService,取 userId 后校验配额,失败返回 Result.error(403, toast)
+- ### 3. 饮食打卡分享(复用既有推荐链路)
+- - diet-checkin.vue / records.vue / record-detail.vue 接入 share-mixin(mixins + setShareInfo + onShareAppMessage)
+- - 分享链接自动带 refCode,他人点击注册后通过 App.vue / login.vue autoBindInviteCode 绑定推荐人
+- - 新用户即可自行饮食打卡
+- ### 4. 设计文档
+- - 新增 docs/superpowers/specs/2026-10-11-restore-basic-and-diet-share-design.md
+- ## 主文件变更
+- - DatabaseInitializer.java:新增迁移360 + backfill CASE 修正
+- - schema.sql:BASIC 种子 + 注释更新
+- - BasicQuotaService.java:恢复 3 方法 + FOOD_RECOGNIZE
+- - MembershipService.java:BASIC 分支 / 到期判断 / listActive / 激活配额初始化
+- - DietRecordController.java:/recognize 会员配额校验
+- - docs/superpowers/specs/2026-10-11-restore-basic-and-diet-share-design.md:设计说明
+- ## 注意
+- - level_order 仅相对顺序调整(插入 BASIC),代码层 level_code 匹配逻辑不受影响
+- - 不恢复 consumeQuota 内原有的 isActiveBasicMember 前置校验,避免误伤 FREE/FAMILY 的 AI 对话
+- - 图片解析配额在控制器层校验,FAILED 返回 403 并提示升级
+- - 生产库历史 BASIC 用户已降级为 FREE,本迁移不自动回溯
+- 
+- - AiGateway 新增 generateSelfCheckQuestions(Map) 方法,调 /api/v1/question-generate
+- - FiveDimensionSelfCheckController 新增 ai-generate-questions 路由,返回 Result<List<SelfCheckQuestionVO>>
+- - questions 列表的 options 字段暂不映射,待后续补全
+- 解除了本地 mvn clean compile 因 LangGraphService 导致的构建中断
+- 
+
+### 新功能
+- 玄凤鹦鹉精细化动作(走/跳着走/飞/站手跟随)
+- implement product recommendation & procurement workflow with 5-dimension tiers, customization, and task lifecycle with message reminders
+- 待退款审核合并入审核中心(新增待退款审核 tab + 待审计数)
+- 离线自主活动与本地点击互动
+- 微信登录改为展示小程序码,不再打开系统浏览器
+- 新增扫码登录落地页 scan-login 与 wechatWebScan 接口封装
+- 桌面端扫码登录改用小程序码(getwxacodeunlimit),新增 POST /scan 确认接口,移除 GET /callback 与网站应用凭据
+- 文章支持推荐排序值编辑;编辑页消除横向滚动条
+- 同步 cfclub 分支 + 恢复 BASIC 会员 + 饮食分享 + 新增指标/减脂/Sso 模块
+- restore BASIC 体验会员 9.9/月 + 饮食打卡分享 + 图片解析配额
+- 通过 AiGateway 接入智能出题能力,新增 POST /api/family/self-check/ai-generate-questions
+- 收货人保存后回填用户真实姓名
+- 商城地址保存后回填用户真实姓名
+- 新增 RealNameBackfillService 真实姓名回填组件
+
+### 文档
+- 玄凤鹦鹉精细化动作设计文档
+- 地址联系人回填真实姓名实现计划(含文档索引同步)
+- 地址联系人回填真实姓名设计(自检修正+文档索引同步)
+- 地址联系人回填真实姓名设计
+
+### 测试
+- 同步 ActivityControllerTest 到 roles 形参签名,修复 testCompile 失败
+
+
 ## v1.0.1585 (2026-10-12)
 
 ### Bug 修复

+ 109 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1585
+> 当前版本: v1.0.1586
 
 ## 历史版本
 
@@ -8,6 +8,114 @@
 
 ---
 
+## v1.0.1586 (2026-10-12)
+
+### Bug 修复
+- 修复微信扫码登录 405、移除令牌输入框、悬停暂停活动与精细化动画
+- schema.sql 建表语句提取支持引号内分号
+- 修复编译错误(补充scan-login.json,移除main.js中不存在的login-sheet引用)
+- 修复创建入库单页表单内容显示不全无法滚动问题
+- reminder-config :key 修复 v-for index 未定义
+- 修复 reminder-config 页 :key 表达式导致非 h5 编译失败
+- 京东云存储下 LangGraph 改读共享卷缓存路径
+
+### 其他
+- - models.rs/commands.rs: 兼容旧版 qrconnect 后端(仅 {state,url} 无小程序码),给出明确「需部署最新后端」提示
+- - settings.html: 移除 JWT 令牌输入框及相关读写(保留 hasToken 探测)
+- - locomotion.rs: 新增 cursor_over_pet() 悬停检测,鼠标落在宠物身上时立即暂停走/跳/飞并保持静止(新增单测)
+- - index.html/animations.css/pet-pose.js: 姿态动画精细化(方向镜像 --face、走/跳/飞/站手更丰富关键帧、新增呼吸待机 pose-idle),并加固宠物 cursor:pointer
+- 
+- 遇到 COMMENT 字符串字面量内的分号(task_reminder_config.offset_minutes
+- 注释"截止前N分钟; after_timeout...")会提前截断,语句不完整导致
+- 每次启动告警且该表无法由 schema.sql 创建。
+- 改为按字符扫描、跳过单引号/反引号内分号,完整切分 338 条语句(与旧实现同集)。
+- 
+- - HealthReportController: 去掉 2 处 startsWith(http) 直传 OSS URL 的捷径,统一经 getFilePath 下载
+- - 代上传(proxy-parse): 改用 storageService 存 OSS 再经 getFilePath 传给 LangGraph
+- 
+- ### 1. BASIC 会员恢复(f86dd164b 反向还原)
+- - 迁移360 restoreBasicMembershipLevel() 恢复 membership_levels BASIC 行 + membership_benefits 4 条 LITE 权益
+- - backfillMembershipLevelOrder 修正 level_order:FREE=1 BASIC=2 FAMILY=3 PROVIDER=4 PREMIUM=5 LIFETIME=6
+- - BasicQuotaService 恢复 refreshMonthlyQuota/isActiveBasicMember/getExpireTime
+- - MembershipService 恢复 BASIC canUseFeature/getMemberLevel 到期判断/listActive/配额初始化
+- - 新增 FOOD_RECOGNIZE 配额(3次/月),控制器层校验 FREE→403
+- - schema.sql 同步种子数据 + 注释
+- ### 2. 饮食打卡分享
+- - diet-checkin.vue / records.vue / record-detail.vue 接入 share-mixin
+- - 分享链接自动带 refCode,新用户注册后自动绑定推荐人
+- ### 3. cfclub 分支合并(~300+ 文件)
+- - 新模块:Indicator(指标曲线/相关性/映射)、FatDiet/FatExercise/FatGoal/FatWeight(减脂)、GutHealth(菌群健康)、SsoTicket(单点登录)、AdminTaskReminderConfig
+- - 前端:ContentSharePoster.vue、TrendChart.vue、indicator-curve/quicklook 页面、self-check-ai、indicator-change/sso utils
+- - LangGraph:indicator_classify、question_generate、report_parse 更新
+- - Web 管理端:IndicatorCorrelations/IndicatorMappings、api/indicator.js、utils/indicatorSeeds.js
+- - xiaofat-miniprogram(独立减脂小程序 Demo)纳入版本库
+- - docs/superpowers/plans/ & specs/ 补充完整
+- ## 主提交
+- - DatabaseInitializer.java、schema.sql、BasicQuotaService.java、MembershipService.java
+- - DietRecordController.java、diet-checkin.vue、records.vue、record-detail.vue
+- - docs/superpowers/specs/2026-10-11-restore-basic-and-diet-share-design.md
+- - 全部 cfclub 分支新增/修改文件
+- 
+- ### 1. 恢复 BASIC 会员等级
+- - 迁移360 restoreBasicMembershipLevel() 重新插入 membership_levels 的 BASIC 行(level_order=2,月费990,max_children=3,max_tasks_per_day=3)和 membership_benefits 的 4 条 LITE 权益(AI_CHAT_LITE 3次/月,REPORT_READING_LITE 1次/月,GROWTH_PLAN_LITE,ASSESSMENT_QUOTA_LITE 1次)
+- - 修正 backfillMembershipLevelOrder 的 level_order 映射:FREE=1 BASIC=2 FAMILY=3 PROVIDER=4 PREMIUM=5 LIFETIME=6(BASIC 插回 FREE 与 FAMILY 之间)
+- - 同步 schema.sql:BASIC 种子数据 + level_order 与 quota_type 字段注释
+- ### 2. 图片解析(食材识别)配额校验
+- - BasicQuotaService.QUOTA_LIMITS 增加 FOOD_RECOGNIZE=3
+- - BasicQuotaService 恢复 isActiveBasicMember / getExpireTime / refreshMonthlyQuota 真实实现
+- - 新增 consumeFoodRecognizeQuota(userId):FAMILY+ 不限次、BASIC 走月度配额、FREE 403 升级提示
+- - DietRecordController./recognize 注入 BasicQuotaService,取 userId 后校验配额,失败返回 Result.error(403, toast)
+- ### 3. 饮食打卡分享(复用既有推荐链路)
+- - diet-checkin.vue / records.vue / record-detail.vue 接入 share-mixin(mixins + setShareInfo + onShareAppMessage)
+- - 分享链接自动带 refCode,他人点击注册后通过 App.vue / login.vue autoBindInviteCode 绑定推荐人
+- - 新用户即可自行饮食打卡
+- ### 4. 设计文档
+- - 新增 docs/superpowers/specs/2026-10-11-restore-basic-and-diet-share-design.md
+- ## 主文件变更
+- - DatabaseInitializer.java:新增迁移360 + backfill CASE 修正
+- - schema.sql:BASIC 种子 + 注释更新
+- - BasicQuotaService.java:恢复 3 方法 + FOOD_RECOGNIZE
+- - MembershipService.java:BASIC 分支 / 到期判断 / listActive / 激活配额初始化
+- - DietRecordController.java:/recognize 会员配额校验
+- - docs/superpowers/specs/2026-10-11-restore-basic-and-diet-share-design.md:设计说明
+- ## 注意
+- - level_order 仅相对顺序调整(插入 BASIC),代码层 level_code 匹配逻辑不受影响
+- - 不恢复 consumeQuota 内原有的 isActiveBasicMember 前置校验,避免误伤 FREE/FAMILY 的 AI 对话
+- - 图片解析配额在控制器层校验,FAILED 返回 403 并提示升级
+- - 生产库历史 BASIC 用户已降级为 FREE,本迁移不自动回溯
+- 
+- - AiGateway 新增 generateSelfCheckQuestions(Map) 方法,调 /api/v1/question-generate
+- - FiveDimensionSelfCheckController 新增 ai-generate-questions 路由,返回 Result<List<SelfCheckQuestionVO>>
+- - questions 列表的 options 字段暂不映射,待后续补全
+- 解除了本地 mvn clean compile 因 LangGraphService 导致的构建中断
+- 
+
+### 新功能
+- 玄凤鹦鹉精细化动作(走/跳着走/飞/站手跟随)
+- implement product recommendation & procurement workflow with 5-dimension tiers, customization, and task lifecycle with message reminders
+- 待退款审核合并入审核中心(新增待退款审核 tab + 待审计数)
+- 离线自主活动与本地点击互动
+- 微信登录改为展示小程序码,不再打开系统浏览器
+- 新增扫码登录落地页 scan-login 与 wechatWebScan 接口封装
+- 桌面端扫码登录改用小程序码(getwxacodeunlimit),新增 POST /scan 确认接口,移除 GET /callback 与网站应用凭据
+- 文章支持推荐排序值编辑;编辑页消除横向滚动条
+- 同步 cfclub 分支 + 恢复 BASIC 会员 + 饮食分享 + 新增指标/减脂/Sso 模块
+- restore BASIC 体验会员 9.9/月 + 饮食打卡分享 + 图片解析配额
+- 通过 AiGateway 接入智能出题能力,新增 POST /api/family/self-check/ai-generate-questions
+- 收货人保存后回填用户真实姓名
+- 商城地址保存后回填用户真实姓名
+- 新增 RealNameBackfillService 真实姓名回填组件
+
+### 文档
+- 玄凤鹦鹉精细化动作设计文档
+- 地址联系人回填真实姓名实现计划(含文档索引同步)
+- 地址联系人回填真实姓名设计(自检修正+文档索引同步)
+- 地址联系人回填真实姓名设计
+
+### 测试
+- 同步 ActivityControllerTest 到 roles 形参签名,修复 testCompile 失败
+
+
 ## v1.0.1585 (2026-10-12)
 
 ### Bug 修复

+ 8 - 0
cfc-web/src/views/admin/ProductEdit.vue

@@ -25,6 +25,14 @@
           </el-select>
         </el-form-item>
 
+        <el-form-item label="推荐商品">
+          <el-radio-group v-model="form.isRecommended">
+            <el-radio :label="1">是</el-radio>
+            <el-radio :label="0">否</el-radio>
+          </el-radio-group>
+          <span style="margin-left: 10px; font-size: 12px; color: #999;">开启后自动同步商品信息至营养产品,小程序营养产品详情可跳转购买</span>
+        </el-form-item>
+
         <el-form-item label="商品分类">
           <el-select v-model="form.categoryId" placeholder="请选择分类(用于前台商城筛选)" clearable filterable style="width: 300px;">
             <el-option-group v-for="group in categoryGroups" :key="group.label" :label="group.label">

+ 92 - 0
docs/superpowers/specs/2026-10-12-product-nutrition-sync-design.md

@@ -0,0 +1,92 @@
+# 商品推荐同步至营养产品功能设计
+
+## 1. 需求背景
+在商品编辑页面(ProductEdit.vue)增加"推荐商品"选项(单选:是/否)。当商品被标记为推荐时,将商品的核心信息同步/更新至 `nutrition_product` 表(通过 `local_product_id` 关联)。商品后续变更时,自动同步更新营养产品对应记录。
+
+## 2. 数据模型变更
+
+### 2.1 products 表新增字段
+| 字段 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `is_recommended` | TINYINT(1) | 0 | 是否推荐商品:0=否,1=是 |
+
+### 2.2 nutrition_product 表关联字段(已存在)
+| 字段 | 说明 |
+|------|------|
+| `local_product_id` | 关联本平台商品ID (products.id) |
+| `name` | 产品名称 |
+| `description` | 产品描述 |
+| `image_url` | 产品图/封面图 |
+| `price` | 当前售价 |
+| `original_price` | 原价 |
+| `product_images` | 商品图片URL(JSON数组) |
+| `spec` | 规格 |
+
+## 3. 同步字段映射
+| Product 字段 | → NutritionProduct 字段 | 备注 |
+|--------------|------------------------|------|
+| `id` | `local_product_id` | 关联键 |
+| `name` | `name` | - |
+| `description` | `description` | - |
+| `intro` | 可选,补充描述 | 不直接映射 |
+| `coverImage` | `image_url` | 封面图 |
+| `images` | `product_images` | JSON数组 |
+| `price` (分) | `price` (元) | 需除以100 |
+| `memberPrice` (分) | `original_price` (元) | 会员价作为原价,需除以100 |
+| `productType` | - | 不同步 |
+| `stock` | - | 不同步,营养产品有自己的库存逻辑 |
+
+## 4. 同步逻辑
+
+### 4.1 触发时机
+- 管理员创建商品时(adminCreate)
+- 管理员更新商品时(adminUpdate)
+- 管理员复制商品时(adminCopy)——新商品默认不推荐
+
+### 4.2 同步规则
+1. **商品设为推荐 (is_recommended=1)**:
+   - 若 nutrition_product 表无对应记录 → **创建**新记录
+   - 若已有记录 → **更新**字段
+2. **商品取消推荐 (is_recommended=0)**:
+   - 保留 nutrition_product 记录,但将 `status=0`(下架),不删除
+3. **商品未变更推荐状态**:
+   - 若已推荐 → 同步更新营养产品字段
+   - 未推荐 → 不操作
+
+### 4.3 实现位置
+- `ProductService.adminCreate()` / `adminUpdate()` 内部调用 `syncToNutritionProduct(product)`
+- `ProductService.syncToNutritionProduct(product)` 处理创建/更新逻辑
+- 依赖 `NutritionProductService` 注入
+
+## 5. 前端变更
+### 5.1 ProductEdit.vue
+- 在"商品类型"下方或"关联成长档案"上方增加"推荐商品"单选项
+- 使用 `el-radio-group`:是/否
+- 绑定 `form.isRecommended` (Boolean)
+
+### 5.2 后端 DTO
+- `ProductCreateRequest` 新增 `isRecommended` 字段
+
+## 6. 数据库迁移
+在 `DatabaseInitializer.runMigrations()` 中添加迁移:
+```java
+ensureColumn("products", "is_recommended", "TINYINT(1) DEFAULT 0 COMMENT '是否推荐商品 0否 1是'");
+```
+
+同时更新 `schema.sql` 的 `products` 表建表语句。
+
+## 7. 实现步骤
+1. 数据库迁移 + schema.sql 同步
+2. Product 实体类新增 `isRecommended` 字段
+3. ProductCreateRequest DTO 新增 `isRecommended`
+4. ProductService 新增 `syncToNutritionProduct()` 方法
+5. AdminProductController create/update 透传字段
+6. ProductEdit.vue 前端表单增加单选项
+7. 编译验证
+
+## 8. 验收标准
+- 商品编辑页可见"推荐商品"单选项
+- 勾选"是"保存后,nutrition_product 表有对应记录(local_product_id = product.id)
+- 修改商品名称/价格/描述/图片后保存,nutrition_product 对应字段同步更新
+- 取消推荐后,nutrition_product.status 变为 0,记录保留
+- 复制商品时,新商品 is_recommended 默认 0