Просмотр исходного кода

fix: 推荐接口移除 growth_category 过滤,直接取精选内容

iwt 1 месяц назад
Родитель
Сommit
095edee28f

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

@@ -30,8 +30,6 @@ public class GrowthRecommendationService {
         LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<Article>()
                 .eq(Article::getStatus, "published")
                 .eq(Article::getIsFeatured, 1)
-                .eq(growthCategory != null, Article::getGrowthCategory, growthCategory)
-                .isNotNull(growthCategory != null, Article::getGrowthCategory)
                 .orderByDesc(Article::getPublishedAt)
                 .last("LIMIT " + limit);
         List<Article> articles = articleMapper.selectList(wrapper);
@@ -52,8 +50,6 @@ public class GrowthRecommendationService {
     public List<Map<String, Object>> getRecommendedProducts(String growthCategory, int limit) {
         LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<Product>()
                 .eq(Product::getStatus, "on_shelf")
-                .eq(growthCategory != null, Product::getGrowthCategory, growthCategory)
-                .isNotNull(growthCategory != null, Product::getGrowthCategory)
                 .orderByAsc(Product::getSortOrder)
                 .orderByDesc(Product::getCreatedAt)
                 .last("LIMIT " + limit);

+ 0 - 300
cfc-frontend/pages/health/gut-flora-caution-detail.vue

@@ -1,300 +0,0 @@
-<template>
-  <view class="page">
-    <view class="nav-bar">
-      <view class="nav-back" @tap="goBack">
-        <text class="back-text">‹ 返回</text>
-      </view>
-      <text class="nav-title">食材戒备匹配</text>
-      <view class="nav-placeholder"></view>
-    </view>
-
-    <view class="warning-banner">
-      <text class="banner-icon">⚠️</text>
-      <text class="banner-text">以下食材推荐指数低于 0,建议谨慎食用或避免</text>
-    </view>
-
-    <scroll-view class="content" scroll-y :refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
-      <view class="food-card" v-for="(item, idx) in foods" :key="idx">
-        <view class="food-header">
-          <text class="food-name">{{ item.foodName }}</text>
-          <view class="caution-badge">戒备</view>
-        </view>
-        <view class="food-meta">
-          <text class="meta-item" v-if="item.category">{{ item.category }}</text>
-        </view>
-        <view class="food-score-row">
-          <view class="score-bar-wrap">
-            <view class="score-bar-bg">
-              <view class="score-bar-fill danger" :style="'width:' + Math.min(Math.abs(item.indexScore) / 50 * 100, 100) + '%'"></view>
-            </view>
-            <text class="score-value danger">{{ item.indexScore }}</text>
-          </view>
-          <text class="score-label">推荐指数</text>
-        </view>
-        <view class="food-nutrition" v-if="item.nutrition">
-          <text class="nut-item">蛋白 {{ item.nutrition.protein }}g</text>
-          <text class="nut-item">脂肪 {{ item.nutrition.fat }}g</text>
-          <text class="nut-item">碳水 {{ item.nutrition.carbs }}g</text>
-        </view>
-      </view>
-
-      <view class="empty-state" v-if="!loading && foods.length === 0">
-        <text class="empty-icon">✅</text>
-        <text class="empty-text">暂无戒备食材</text>
-        <text class="empty-hint">您的菌群报告未显示需要戒备的食材</text>
-      </view>
-
-      <view class="loading-state" v-if="loading">
-        <text class="loading-text">加载中...</text>
-      </view>
-
-      <view class="bottom-spacer"></view>
-    </scroll-view>
-  </view>
-</template>
-
-<script>
-import { getFoodCautionList } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      foods: [],
-      loading: true,
-      refreshing: false,
-      nutritionCache: {}
-    }
-  },
-  onLoad() {
-    this.loadFoods()
-  },
-  methods: {
-    goBack() {
-      uni.navigateBack()
-    },
-    async loadFoods() {
-      this.loading = true
-      try {
-        var res = await getFoodCautionList()
-        if (res && res.code === 200 && res.data) {
-          this.foods = res.data
-          // 批量加载营养数据
-          this.loadNutrition()
-        } else {
-          this.foods = []
-        }
-      } catch (e) {
-        console.error('加载戒备食材失败', e)
-        this.foods = []
-      } finally {
-        this.loading = false
-        this.refreshing = false
-      }
-    },
-    async loadNutrition() {
-      if (!this.foods.length) return
-      try {
-        var kbRes = await this.getKnowledgeSection('食物库')
-        if (kbRes && kbRes.data && kbRes.data['数据']) {
-          var kbFoods = kbRes.data['数据']
-          var cache = {}
-          for (var i = 0; i < kbFoods.length; i++) {
-            var f = kbFoods[i]
-            cache[f['名称']] = {
-              protein: f['蛋白g'] || null,
-              fat: f['脂肪g'] || null,
-              carbs: f['碳水化合物g'] || null,
-              fiber: f['总膳食纤维g'] || null,
-              energy: f['能量KJ'] || null,
-              category: f['分类'] || ''
-            }
-          }
-          this.nutritionCache = cache
-          this.foods = this.foods.map(function(item) {
-            var n = cache[item.foodName] || {}
-            return Object.assign({}, item, {
-              nutrition: n,
-              category: item.category || n.category || ''
-            })
-          })
-          this.$forceUpdate()
-        }
-      } catch (e) {
-        console.warn('加载营养数据失败', e)
-      }
-    },
-    getKnowledgeSection(key) {
-      return new Promise(function(resolve) {
-        uni.request({
-          url: getApp().globalData.baseApi + '/api/kb/section',
-          method: 'POST',
-          header: {
-            'Content-Type': 'application/json',
-            'Authorization': 'Bearer ' + (uni.getStorageSync('token') || '')
-          },
-          data: { key: key },
-          success: function(res) { resolve(res.data) },
-          fail: function() { resolve({}) }
-        })
-      })
-    },
-    onRefresh() {
-      this.refreshing = true
-      this.loadFoods()
-    }
-  }
-}
-</script>
-
-<style scoped>
-.page {
-  min-height: 100vh;
-  background: #f5f6fa;
-}
-.nav-bar {
-  display: flex;
-  align-items: center;
-  padding: 20rpx 30rpx;
-  background: #fff;
-  position: relative;
-}
-.nav-back { padding: 10rpx 0; }
-.back-text { font-size: 28rpx; color: #4A9BD7; }
-.nav-title { flex: 1; text-align: center; font-size: 32rpx; font-weight: 600; }
-.nav-placeholder { width: 80rpx; }
-
-.warning-banner {
-  display: flex;
-  align-items: center;
-  gap: 12rpx;
-  padding: 20rpx 30rpx;
-  background: #FFF3CD;
-  border-bottom: 1rpx solid #FFECB5;
-}
-.banner-icon { font-size: 32rpx; }
-.banner-text { font-size: 24rpx; color: #856404; flex: 1; }
-
-.content {
-  padding: 20rpx 30rpx;
-  height: calc(100vh - 160rpx);
-}
-
-.food-card {
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 24rpx;
-  margin-bottom: 16rpx;
-  border-left: 6rpx solid #EF4444;
-  box-shadow: 0 2rpx 8rpx rgba(239, 68, 68, 0.08);
-}
-.food-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 8rpx;
-}
-.food-name {
-  font-size: 32rpx;
-  font-weight: 600;
-  color: #1a1a1a;
-}
-.caution-badge {
-  padding: 4rpx 16rpx;
-  background: #EF4444;
-  color: #fff;
-  border-radius: 20rpx;
-  font-size: 22rpx;
-  font-weight: 500;
-}
-.food-meta {
-  margin-bottom: 16rpx;
-}
-.meta-item {
-  font-size: 24rpx;
-  color: #888;
-  background: #f5f5f5;
-  padding: 4rpx 12rpx;
-  border-radius: 8rpx;
-}
-.food-score-row {
-  display: flex;
-  align-items: center;
-  gap: 16rpx;
-  margin-bottom: 12rpx;
-}
-.score-bar-wrap {
-  flex: 1;
-  display: flex;
-  align-items: center;
-  gap: 12rpx;
-}
-.score-bar-bg {
-  flex: 1;
-  height: 12rpx;
-  background: #fee2e2;
-  border-radius: 6rpx;
-  overflow: hidden;
-}
-.score-bar-fill {
-  height: 100%;
-  border-radius: 6rpx;
-  transition: width 0.3s ease;
-}
-.score-bar-fill.danger {
-  background: linear-gradient(90deg, #EF4444, #DC2626);
-}
-.score-value {
-  font-size: 28rpx;
-  font-weight: 700;
-  color: #EF4444;
-  min-width: 60rpx;
-  text-align: right;
-}
-.score-label {
-  font-size: 24rpx;
-  color: #999;
-  white-space: nowrap;
-}
-.food-nutrition {
-  display: flex;
-  gap: 20rpx;
-  flex-wrap: wrap;
-}
-.nut-item {
-  font-size: 22rpx;
-  color: #999;
-}
-
-.empty-state {
-  text-align: center;
-  padding: 100rpx 40rpx;
-}
-.empty-icon {
-  font-size: 80rpx;
-  display: block;
-  margin-bottom: 20rpx;
-}
-.empty-text {
-  font-size: 30rpx;
-  color: #333;
-  font-weight: 500;
-  display: block;
-  margin-bottom: 12rpx;
-}
-.empty-hint {
-  font-size: 24rpx;
-  color: #999;
-  display: block;
-}
-
-.loading-state {
-  text-align: center;
-  padding: 80rpx 0;
-}
-.loading-text {
-  font-size: 26rpx;
-  color: #999;
-}
-
-.bottom-spacer { height: 40rpx; }
-</style>

+ 0 - 201
cfc-frontend/pages/health/gut-flora-foods-detail.vue

@@ -1,201 +0,0 @@
-<template>
-  <view class="page">
-    <view class="nav-bar">
-      <view class="nav-back" @tap="goBack">
-        <text class="back-text">‹ 返回</text>
-      </view>
-      <text class="nav-title">饮食推荐</text>
-      <text class="nav-edit" @tap="toggleEdit">{{ isEditing ? '完成' : '编辑' }}</text>
-    </view>
-
-    <scroll-view class="filter-bar" scroll-x>
-      <view class="filter-item" v-for="(cat, idx) in categories" :key="idx"
-            :class="'filter-' + (selectedCategory === cat ? 'active' : '')"
-            @tap="selectedCategory = cat">
-        <text>{{ cat }}</text>
-      </view>
-    </scroll-view>
-
-    <view class="sort-bar">
-      <text class="sort-text" @tap="sortDesc = !sortDesc">排序: {{ sortDesc ? '推荐↓' : '推荐↑' }}</text>
-      <text class="sort-text" @tap="showRecommendedOnly = !showRecommendedOnly">{{ showRecommendedOnly ? '全部' : '仅推荐' }}</text>
-    </view>
-
-    <scroll-view class="content" scroll-y>
-      <view class="food-card" v-for="(item, idx) in filteredFoods" :key="idx">
-        <view class="food-header">
-          <text class="food-name">{{ item.foodName || item.name }}</text>
-          <view class="food-cat-tag">
-            <text>{{ item.category }}</text>
-          </view>
-        </view>
-        <view class="food-detail-row" v-if="!isEditing">
-          <text class="food-nutrition">蛋白{{ item.protein || '--' }} | 脂肪{{ item.fat || '--' }} | 碳水{{ item.carb || '--' }} | 纤维{{ item.fiber || '--' }}</text>
-        </view>
-        <view class="food-detail-row" v-if="!isEditing && item.energy">
-          <text class="food-nutrition">能量 {{ item.energy }}KJ</text>
-        </view>
-        <view class="food-edit-row" v-if="isEditing">
-          <text class="edit-label">推荐指数:</text>
-          <input class="edit-input" type="number" v-model="item.score" />
-        </view>
-        <view class="food-score" :class="(item.score || 0) >= 5 ? 'score-good' : 'score-normal'">
-          <text>推荐: {{ item.score }}</text>
-        </view>
-      </view>
-      <view class="empty-hint" v-if="filteredFoods.length === 0">暂无数据</view>
-      <view class="bottom-spacer"></view>
-    </scroll-view>
-
-    <view class="bottom-bar" v-if="isEditing">
-      <view class="btn-cancel" @tap="cancelEdit">取消</view>
-      <view class="btn-save" @tap="saveEdit">保存</view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getReportDetail, editHealthReport, getKnowledgeSection } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      reportId: null,
-      reportData: null,
-      foods: [],
-      isEditing: false,
-      editFoods: [],
-      selectedCategory: '全部',
-      sortDesc: true,
-      showRecommendedOnly: false,
-      categories: ['全部', '主食', '蔬菜', '水果', '肉类', '其它']
-    }
-  },
-  computed: {
-    filteredFoods: function() {
-      var list = this.isEditing ? this.editFoods : this.foods
-      if (this.selectedCategory !== '全部') {
-        list = list.filter(function(item) {
-          return (item.category || '') === this.selectedCategory
-        }.bind(this))
-      }
-      if (this.showRecommendedOnly) {
-        list = list.filter(function(item) {
-          return (item.score || 0) >= 5
-        })
-      }
-      list = [].concat(list)
-      list.sort(function(a, b) {
-        var sa = a.score || 0
-        var sb = b.score || 0
-        return this.sortDesc ? sb - sa : sa - sb
-      }.bind(this))
-      return list
-    }
-  },
-  onLoad: function(options) {
-    this.reportId = options.reportId ? parseInt(options.reportId) : null
-    if (this.reportId) {
-      this.loadData()
-    }
-  },
-  methods: {
-    goBack: function() {
-      uni.navigateBack()
-    },
-    loadData: function() {
-      var self = this
-      Promise.all([
-        getReportDetail(this.reportId),
-        getKnowledgeSection('食物库')
-      ]).then(function(results) {
-        var reportRes = results[0]
-        var kbRes = results[1]
-
-        // 构建 KB 食物 map
-        var kbFoodMap = {}
-        if (kbRes.code === 200 && kbRes.data && kbRes.data['数据']) {
-          var kbFoods = kbRes.data['数据']
-          for (var i = 0; i < kbFoods.length; i++) {
-            var f = kbFoods[i]
-            kbFoodMap[f['名称']] = f
-          }
-        }
-
-        if (reportRes.code === 200 && reportRes.data) {
-          self.reportData = reportRes.data
-          var reportFoods = reportRes.data.foods || []
-          // 合并 KB 营养数据(个人 JSON 只保存名称和推荐指数,营养数据从 KB 查)
-          self.foods = reportFoods.map(function(item) {
-            var name = item.foodName || item.name
-            var kb = kbFoodMap[name] || {}
-            return {
-              foodName: name,
-              name: name,
-              score: item.score,
-              category: item.category || kb['分类'] || '其它',
-              protein: kb['蛋白g'] || item.protein,
-              fat: kb['脂肪g'] || item.fat,
-              carb: kb['碳水化合物g'] || item.carb,
-              fiber: kb['总膳食纤维g'] || item.fiber,
-              energy: kb['能量KJ'] || item.energy
-            }
-          })
-        }
-      })
-    },
-    toggleEdit: function() {
-      this.isEditing = true
-      this.editFoods = JSON.parse(JSON.stringify(this.foods))
-    },
-    cancelEdit: function() {
-      this.isEditing = false
-      this.editFoods = []
-    },
-    saveEdit: function() {
-      var self = this
-      editHealthReport(self.reportId, { foods: self.editFoods }, 0).then(function(res) {
-        if (res.code === 200) {
-          uni.showToast({ title: '已保存', icon: 'success' })
-          self.isEditing = false
-          self.loadData()
-        } else {
-          uni.showToast({ title: res.message || '保存失败', icon: 'none' })
-        }
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.page { min-height: 100vh; background: #f8f8f8; }
-.nav-bar { display: flex; align-items: center; padding: 20rpx 30rpx; background: #fff; position: relative; }
-.nav-back { padding: 10rpx 0; }
-.back-text { font-size: 28rpx; color: #4A9BD7; }
-.nav-title { flex: 1; text-align: center; font-size: 32rpx; font-weight: 600; }
-.nav-edit { font-size: 28rpx; color: #4A9BD7; padding: 10rpx 0; }
-.filter-bar { background: #fff; padding: 10rpx 20rpx; white-space: nowrap; }
-.filter-item { display: inline-block; padding: 12rpx 28rpx; margin: 0 10rpx; border-radius: 30rpx; font-size: 26rpx; background: #f0f0f0; color: #666; }
-.filter-active { background: #4A9BD7; color: #fff; }
-.sort-bar { display: flex; justify-content: space-between; padding: 16rpx 30rpx; background: #fff; border-top: 1rpx solid #eee; }
-.sort-text { font-size: 24rpx; color: #999; }
-.content { padding: 20rpx 30rpx; height: calc(100vh - 200rpx); }
-.food-card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; }
-.food-header { display: flex; align-items: center; margin-bottom: 12rpx; }
-.food-name { font-size: 30rpx; font-weight: 600; color: #333; flex: 1; }
-.food-cat-tag { padding: 4rpx 16rpx; background: #f0f0f0; border-radius: 20rpx; font-size: 22rpx; color: #666; }
-.food-detail-row { margin-bottom: 6rpx; }
-.food-nutrition { font-size: 24rpx; color: #999; }
-.food-edit-row { display: flex; align-items: center; margin-bottom: 6rpx; }
-.edit-label { font-size: 26rpx; color: #666; margin-right: 12rpx; }
-.edit-input { border: 1rpx solid #ddd; border-radius: 8rpx; padding: 8rpx 12rpx; font-size: 26rpx; width: 120rpx; }
-.food-score { margin-top: 8rpx; font-size: 24rpx; }
-.score-good { color: #10B981; }
-.score-normal { color: #999; }
-.empty-hint { text-align: center; color: #ccc; padding: 60rpx 0; font-size: 28rpx; }
-.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; padding: 20rpx 30rpx; background: #fff; border-top: 1rpx solid #eee; gap: 20rpx; }
-.btn-cancel { flex: 1; padding: 20rpx; border: 1rpx solid #ddd; border-radius: 12rpx; text-align: center; font-size: 28rpx; color: #666; }
-.btn-save { flex: 1; padding: 20rpx; background: #4A9BD7; border-radius: 12rpx; text-align: center; font-size: 28rpx; color: #fff; }
-.bottom-spacer { height: 40rpx; }
-</style>

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-c991b876cff41beab4e089a89539776d407108fb
+ccb6224b55f2e8f1582153c659816ca483ce79a1

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

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.982",
+  "version": "1.0.996",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.982",
+      "version": "1.0.996",
       "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.982",
+  "version": "1.0.996",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

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

@@ -4,6 +4,145 @@
 
 ---
 
+## v1.0.996 (2026-08-12)
+
+### 新功能
+- 成长页新增推荐文章+推荐商品板块
+
+### 其他
+- - 后端: AI自动分类接口 /api/growth/articles/products/ai-classify
+- - 前端: growth-main 新增推荐文章和推荐商品卡片
+- - 前端: api.js 新增 getGrowthRecommendations()
+- 
+
+
+## v1.0.995 (2026-08-12)
+
+### Bug 修复
+- GrowthRecommendationService 修复 getCategory 调用错误
+
+
+## v1.0.994 (2026-08-12)
+
+### 新功能
+- 成长板块推荐文章/商品 + AI自动分类
+
+### 其他
+- - 后端: GrowthRecommendationService 推荐查询 + AI分类
+- - 后端: GrowthController /api/growth/recommendations + /ai-classify
+- - 前端: growth-main 新增推荐文章+推荐商品板块
+- 
+
+
+## v1.0.993 (2026-08-12)
+
+### 重构
+- 成长页面调整 — 戒备/饮食推荐从健康移至成长
+
+### 其他
+- - gut-flora-caution-detail → pages/growth/caution-detail
+- - gut-flora-foods-detail → pages/growth/foods-detail
+- - growth-main 新增戒备/饮食推荐行动卡片(戒备有数据时显示)
+- - 更新 gut-flora-detail.vue 跳转链接
+- 
+
+
+## v1.0.992 (2026-08-12)
+
+### 新功能
+- 我的页面改为电商风格 - 订单五宫格/购物车/收货地址, 移除功能网格
+
+### 其他
+- - profile-main: 新增购物车(角标99+封顶)与收货地址入口
+- - profile-main: 移除功能入口网格, 设置改为菜单项放退出登录上方
+- - profile-main: loadOrderCounts 修正字段映射(pending/paid/shipped/refunding)
+- - profile-main: 补充 loadCartCount 方法定义
+- - order-list: onLoad 支持 status 参数, 我的订单五宫格点击直达对应状态列表
+- Ultraworked with [Sisyphus](https://github.com/oh-my-open-code/oh-my-open-code)
+- Co-authored-by: Sisyphus <sisyphus@ohmyopencode.ai>
+- 
+
+
+## v1.0.991 (2026-08-12)
+
+### Bug 修复
+- 报告详情行距优化、富维度商品封面图URL修复、智维度功能区间距调整
+- 修复成就页分包注册路径,健康/成长页移除家庭成员选择条
+
+
+## v1.0.990 (2026-08-12)
+
+### 新功能
+- 通关解锁管理端 - 关卡配置 CRUD/排序/启停/家庭进度管理
+
+
+## v1.0.989 (2026-08-12)
+
+### 文档
+- 我的页面电商风格改造设计
+
+### 其他
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- /data/cfc-uploads/ 文件,导致解析时报「文件不存在」。
+- 修复:将 langgraph-data:/data 拆分为两层挂载:
+- - /data/cfc-uploads → bind mount 宿主机路径(共享上传文件)
+- - langgraph-data:/data/chroma_db → 独立命名卷(chroma 向量数据持久化)
+- 
+
+### Bug 修复
+- LangGraph 容器挂载宿主机上传目录替代命名卷
+
+
+## v1.0.988 (2026-08-12)
+
+### 文档
+- 通关解锁计划状态更新为后端完成+前端核心完成
+
+
+## v1.0.987 (2026-08-11)
+
+### Bug 修复
+- getFamilyEnergySandbox 兼容新返回格式,避免16个旧页面崩溃
+
+
+## v1.0.986 (2026-08-11)
+
+### 新功能
+- 前端组件+页面改造 - WuxingSandbox切换开关/gate-lock/gate-guide-bar/关卡中心页/parent-index+child-index门禁
+
+
+## v1.0.985 (2026-08-11)
+
+### 新功能
+- 报告详情页统一重构为完整页面,修复上传后跳转 routeDone 报错
+
+### 其他
+- - report-confirm.vue 去除 onLoad 重复的 draftId/memberId 赋值,draftId 模式自动跳转详情页
+- - report-upload.vue 上传草稿后跳转详情页
+- - 新增健康报告编辑日志(HealthReportEditLog + Mapper + 建表迁移)
+- - 报告服务/区块组装/排序工具相关后端增强
+- 
+
+
+## v1.0.984 (2026-08-11)
+
+### Bug 修复
+- 图片路径替换为京东云OSS URL
+
+
+## v1.0.983 (2026-08-11)
+
+### 新功能
+- 食材戒备匹配功能
+
+### 其他
+- - 前端: 新建 gut-flora-caution-detail.vue 页面,倒序展示 indexScore < 0 的食材
+- - 前端: gut-flora-detail.vue 新增'食材戒备'入口卡片(有戒备食材时显示)
+- - 前端: api.js 新增 getFoodCautionList()
+- 
+
+
 ## v1.0.982 (2026-08-11)
 
 ### 新功能

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

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.982
+> 当前版本: v1.0.996
 
 ## 历史版本
 
@@ -8,6 +8,145 @@
 
 ---
 
+## v1.0.996 (2026-08-12)
+
+### 新功能
+- 成长页新增推荐文章+推荐商品板块
+
+### 其他
+- - 后端: AI自动分类接口 /api/growth/articles/products/ai-classify
+- - 前端: growth-main 新增推荐文章和推荐商品卡片
+- - 前端: api.js 新增 getGrowthRecommendations()
+- 
+
+
+## v1.0.995 (2026-08-12)
+
+### Bug 修复
+- GrowthRecommendationService 修复 getCategory 调用错误
+
+
+## v1.0.994 (2026-08-12)
+
+### 新功能
+- 成长板块推荐文章/商品 + AI自动分类
+
+### 其他
+- - 后端: GrowthRecommendationService 推荐查询 + AI分类
+- - 后端: GrowthController /api/growth/recommendations + /ai-classify
+- - 前端: growth-main 新增推荐文章+推荐商品板块
+- 
+
+
+## v1.0.993 (2026-08-12)
+
+### 重构
+- 成长页面调整 — 戒备/饮食推荐从健康移至成长
+
+### 其他
+- - gut-flora-caution-detail → pages/growth/caution-detail
+- - gut-flora-foods-detail → pages/growth/foods-detail
+- - growth-main 新增戒备/饮食推荐行动卡片(戒备有数据时显示)
+- - 更新 gut-flora-detail.vue 跳转链接
+- 
+
+
+## v1.0.992 (2026-08-12)
+
+### 新功能
+- 我的页面改为电商风格 - 订单五宫格/购物车/收货地址, 移除功能网格
+
+### 其他
+- - profile-main: 新增购物车(角标99+封顶)与收货地址入口
+- - profile-main: 移除功能入口网格, 设置改为菜单项放退出登录上方
+- - profile-main: loadOrderCounts 修正字段映射(pending/paid/shipped/refunding)
+- - profile-main: 补充 loadCartCount 方法定义
+- - order-list: onLoad 支持 status 参数, 我的订单五宫格点击直达对应状态列表
+- Ultraworked with [Sisyphus](https://github.com/oh-my-open-code/oh-my-open-code)
+- Co-authored-by: Sisyphus <sisyphus@ohmyopencode.ai>
+- 
+
+
+## v1.0.991 (2026-08-12)
+
+### Bug 修复
+- 报告详情行距优化、富维度商品封面图URL修复、智维度功能区间距调整
+- 修复成就页分包注册路径,健康/成长页移除家庭成员选择条
+
+
+## v1.0.990 (2026-08-12)
+
+### 新功能
+- 通关解锁管理端 - 关卡配置 CRUD/排序/启停/家庭进度管理
+
+
+## v1.0.989 (2026-08-12)
+
+### 文档
+- 我的页面电商风格改造设计
+
+### 其他
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- /data/cfc-uploads/ 文件,导致解析时报「文件不存在」。
+- 修复:将 langgraph-data:/data 拆分为两层挂载:
+- - /data/cfc-uploads → bind mount 宿主机路径(共享上传文件)
+- - langgraph-data:/data/chroma_db → 独立命名卷(chroma 向量数据持久化)
+- 
+
+### Bug 修复
+- LangGraph 容器挂载宿主机上传目录替代命名卷
+
+
+## v1.0.988 (2026-08-12)
+
+### 文档
+- 通关解锁计划状态更新为后端完成+前端核心完成
+
+
+## v1.0.987 (2026-08-11)
+
+### Bug 修复
+- getFamilyEnergySandbox 兼容新返回格式,避免16个旧页面崩溃
+
+
+## v1.0.986 (2026-08-11)
+
+### 新功能
+- 前端组件+页面改造 - WuxingSandbox切换开关/gate-lock/gate-guide-bar/关卡中心页/parent-index+child-index门禁
+
+
+## v1.0.985 (2026-08-11)
+
+### 新功能
+- 报告详情页统一重构为完整页面,修复上传后跳转 routeDone 报错
+
+### 其他
+- - report-confirm.vue 去除 onLoad 重复的 draftId/memberId 赋值,draftId 模式自动跳转详情页
+- - report-upload.vue 上传草稿后跳转详情页
+- - 新增健康报告编辑日志(HealthReportEditLog + Mapper + 建表迁移)
+- - 报告服务/区块组装/排序工具相关后端增强
+- 
+
+
+## v1.0.984 (2026-08-11)
+
+### Bug 修复
+- 图片路径替换为京东云OSS URL
+
+
+## v1.0.983 (2026-08-11)
+
+### 新功能
+- 食材戒备匹配功能
+
+### 其他
+- - 前端: 新建 gut-flora-caution-detail.vue 页面,倒序展示 indexScore < 0 的食材
+- - 前端: gut-flora-detail.vue 新增'食材戒备'入口卡片(有戒备食材时显示)
+- - 前端: api.js 新增 getFoodCautionList()
+- 
+
+
 ## v1.0.982 (2026-08-11)
 
 ### 新功能