Преглед изворни кода

feat(backend+frontend): 菌群功能分析——功能维度聚合+P/B比值+饮食模式+风险提示

后端:
- BeijingNutritionService.analyzeGutFlora: 按功能分类聚合菌属
  (丁酸盐生产者/黏液屏障/蛋白质发酵者/SCFA/产气菌/益生菌等)
- 计算 Prevotella/Bacteroides 比值推断饮食模式
- 组合风险模型(IBD/代谢/肠易激/神经-肠轴)
- 益生菌补充指导
- POST /api/nutrition/beijing/analysis

前端:
- gut-flora-detail.vue 新增分析卡片(功能维度评估/PB比值/饮食模式/风险/益生菌)
- api.js 添加 getGutFloraAnalysis
asus пре 2 месеци
родитељ
комит
468db3f23a

+ 10 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/nutrition/BeijingNutritionController.java

@@ -116,4 +116,14 @@ public class BeijingNutritionController {
                 .collect(Collectors.toList()));
         return Result.success(info);
     }
+
+    @Operation(summary = "菌群功能分析——功能维度聚合、饮食模式、风险提示")
+    @PostMapping("/analysis")
+    public Result<Map<String, Object>> analyzeGutFlora(@RequestBody Map<String, Object> params) {
+        if (params.get("reportId") == null) {
+            return Result.error("reportId 不能为空");
+        }
+        Long reportId = Long.valueOf(params.get("reportId").toString());
+        return Result.success(beijingNutritionService.analyzeGutFlora(reportId));
+    }
 }

+ 309 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/BeijingNutritionService.java

@@ -237,6 +237,315 @@ public class BeijingNutritionService {
         return list;
     }
 
+    // ============================================================
+    // 菌群功能分析引擎
+    // ============================================================
+
+    /**
+     * 分析菌群报告,给出功能维度聚合、饮食模式推断、风险提示等额外分析结论
+     *
+     * @param reportId 菌群报告ID
+     * @return 分析结果
+     */
+    public Map<String, Object> analyzeGutFlora(Long reportId) {
+        Map<String, Object> result = new LinkedHashMap<>();
+
+        // 1. 获取报告菌群数据
+        List<HealthGutFlora> floraList = healthGutFloraMapper.selectList(
+                new LambdaQueryWrapper<HealthGutFlora>()
+                        .eq(HealthGutFlora::getReportId, reportId)
+        );
+        if (floraList.isEmpty()) {
+            result.put("error", "该报告无菌群数据");
+            return result;
+        }
+
+        // 构建菌名→状态映射
+        Map<String, HealthGutFlora> floraByName = new HashMap<>();
+        for (HealthGutFlora f : floraList) {
+            if (f.getBacteriaName() != null) {
+                floraByName.put(f.getBacteriaName().toLowerCase(), f);
+            }
+        }
+
+        // 2. 功能维度聚合
+        // 功能分类定义:菌名(小写) → 功能分类
+        Map<String, String> funcClassMap = new HashMap<>();
+        funcClassMap.put("faecalibacterium", "丁酸盐生产者");
+        funcClassMap.put("roseburia", "丁酸盐生产者");
+        funcClassMap.put("eubacterium", "丁酸盐生产者");
+        funcClassMap.put("coprococcus", "丁酸盐生产者");
+        funcClassMap.put("oscillospira", "丁酸盐生产者");
+        funcClassMap.put("akkermansia", "黏液屏障");
+        funcClassMap.put("alistipes", "蛋白质发酵者");
+        funcClassMap.put("sutterella", "蛋白质发酵者");
+        funcClassMap.put("parabacteroides", "蛋白质发酵者");
+        funcClassMap.put("blautia", "短链脂肪酸生产者");
+        funcClassMap.put("lachnospira", "短链脂肪酸生产者");
+        funcClassMap.put("lachnoclostridium", "短链脂肪酸生产者");
+        funcClassMap.put("phascolarctobacterium", "短链脂肪酸生产者");
+        funcClassMap.put("dorea", "产气菌");
+        funcClassMap.put("bifidobacterium", "益生菌");
+        funcClassMap.put("lactobacillus", "益生菌");
+        funcClassMap.put("ruminococcus", "纤维分解者");
+        funcClassMap.put("prevotella", "纤维分解者");
+        funcClassMap.put("megamonas", "条件致病菌");
+        funcClassMap.put("clostridium", "条件致病菌");
+        funcClassMap.put("bacteroides", "碳水化合物分解者");
+        funcClassMap.put("dialister", "琥珀酸生产者");
+
+        // 按功能分类聚合
+        Map<String, List<Map<String, Object>>> funcGroups = new LinkedHashMap<>();
+        for (Map.Entry<String, String> e : funcClassMap.entrySet()) {
+            String funcName = e.getValue();
+            funcGroups.putIfAbsent(funcName, new ArrayList<>());
+            HealthGutFlora flora = floraByName.get(e.getKey());
+            if (flora != null) {
+                Map<String, Object> item = new HashMap<>();
+                item.put("bacteriaName", flora.getBacteriaName());
+                item.put("status", flora.getStatus());
+                item.put("bacteriaValue", flora.getBacteriaValue());
+                item.put("normalRange", flora.getNormalRange());
+                funcGroups.get(funcName).add(item);
+            }
+        }
+
+        // 计算每个功能维度的健康状态
+        List<Map<String, Object>> functionalAnalysis = new ArrayList<>();
+        for (Map.Entry<String, List<Map<String, Object>>> entry : funcGroups.entrySet()) {
+            if (entry.getValue().isEmpty()) continue;
+            Map<String, Object> dim = new LinkedHashMap<>();
+            dim.put("name", entry.getKey());
+            dim.put("items", entry.getValue());
+
+            // 统计异常菌属数量
+            long abnormalCount = entry.getValue().stream()
+                    .filter(m -> {
+                        String s = (String) m.get("status");
+                        return s != null && !"正常".equals(s);
+                    }).count();
+            dim.put("abnormalCount", abnormalCount);
+            dim.put("totalCount", entry.getValue().size());
+
+            // 评估状态
+            if (abnormalCount == 0) {
+                dim.put("status", "良好");
+                dim.put("level", 85);
+            } else if (abnormalCount <= entry.getValue().size() / 2) {
+                dim.put("status", "注意");
+                dim.put("level", 60);
+            } else {
+                dim.put("status", "需关注");
+                dim.put("level", 35);
+            }
+
+            // 根据功能分类给出摘要
+            dim.put("summary", getFuncSummary(entry.getKey(), dim));
+            functionalAnalysis.add(dim);
+        }
+        result.put("functionalAnalysis", functionalAnalysis);
+
+        // 3. P/B 比值(Prevotella / Bacteroides)
+        Map<String, Object> pbRatio = new LinkedHashMap<>();
+        HealthGutFlora prevotella = floraByName.get("prevotella");
+        HealthGutFlora bacteroides = floraByName.get("bacteroides");
+        if (prevotella != null && bacteroides != null) {
+            double pv = parseDouble(prevotella.getBacteriaValue(), 0);
+            double bv = parseDouble(bacteroides.getBacteriaValue(), 0);
+            double ratio = bv > 0 ? pv / bv : 0;
+            pbRatio.put("prevotellaValue", pv);
+            pbRatio.put("bacteroidesValue", bv);
+            pbRatio.put("ratio", Math.round(ratio * 1000.0) / 1000.0);
+            pbRatio.put("prevotellaStatus", prevotella.getStatus());
+            pbRatio.put("bacteroidesStatus", bacteroides.getStatus());
+            if (ratio > 0.5) {
+                pbRatio.put("interpretation", "植物性饮食倾向(高 Prevotella/Bacteroides 比值)");
+            } else if (ratio > 0.1) {
+                pbRatio.put("interpretation", "均衡饮食模式");
+            } else {
+                pbRatio.put("interpretation", "西式高脂高蛋白饮食倾向(低 Prevotella/Bacteroides 比值)");
+            }
+        }
+        result.put("pbRatio", pbRatio);
+
+        // 4. 饮食模式推断
+        Map<String, Object> dietaryPattern = inferDietaryPattern(floraByName);
+        result.put("dietaryPattern", dietaryPattern);
+
+        // 5. 风险提示组合
+        List<Map<String, Object>> riskIndicators = computeRiskIndicators(floraByName);
+        result.put("riskIndicators", riskIndicators);
+
+        // 6. 益生菌指导
+        Map<String, Object> probioticAdvice = new LinkedHashMap<>();
+        HealthGutFlora bifido = floraByName.get("bifidobacterium");
+        HealthGutFlora lacto = floraByName.get("lactobacillus");
+        if (bifido != null) {
+            Map<String, Object> b = new LinkedHashMap<>();
+            b.put("status", bifido.getStatus());
+            b.put("advice", "偏低".equals(bifido.getStatus()) ? "建议增加益生元摄入(洋葱、大蒜、香蕉、燕麦)"
+                    : "偏高".equals(bifido.getStatus()) ? "偏高通常无害,严重偏高时暂停益生菌补充剂"
+                    : "正常水平,继续保持");
+            probioticAdvice.put("bifidobacterium", b);
+        }
+        if (lacto != null) {
+            Map<String, Object> l = new LinkedHashMap<>();
+            l.put("status", lacto.getStatus());
+            l.put("advice", "偏低".equals(lacto.getStatus()) ? "建议补充发酵食品(无糖酸奶、开菲尔、泡菜)"
+                    : "偏高".equals(lacto.getStatus()) ? "偏高通常无害,严重偏高时暂停益生菌补充剂"
+                    : "正常水平,继续保持");
+            probioticAdvice.put("lactobacillus", l);
+        }
+        result.put("probioticAdvice", probioticAdvice);
+
+        return result;
+    }
+
+    /** 功能维度摘要 */
+    private String getFuncSummary(String funcName, Map<String, Object> dim) {
+        String status = (String) dim.get("status");
+        long total = (long) dim.get("totalCount");
+        long abnormal = (long) dim.get("abnormalCount");
+
+        switch (funcName) {
+            case "丁酸盐生产者":
+                return status.equals("良好") ? "丁酸盐生产者菌群正常,肠道屏障修复能力良好"
+                        : abnormal + "/" + total + "种丁酸菌异常,可能影响肠黏膜修复和抗炎能力";
+            case "黏液屏障":
+                return status.equals("良好") ? "阿克曼菌正常,肠黏液屏障完整"
+                        : "阿克曼菌偏低,提示肠道屏障功能可能受损,建议增加多酚类食物";
+            case "蛋白质发酵者":
+                return status.equals("良好") ? "蛋白质发酵菌群正常"
+                        : abnormal + "/" + total + "种异常,提示高动物蛋白饮食或潜在肠道炎症";
+            case "短链脂肪酸生产者":
+                return status.equals("良好") ? "短链脂肪酸生产者正常,代谢健康良好"
+                        : abnormal + "/" + total + "种SCFA菌异常,可能影响代谢和免疫调节";
+            case "产气菌":
+                return "偏高".equals(dim.get("status")) ? "Dorea偏高,提示腹胀/肠易激风险"
+                        : "产气菌属正常,无腹胀风险";
+            case "益生菌":
+                return status.equals("良好") ? "益生菌水平正常"
+                        : abnormal + "/" + total + "种益生菌异常,请参考补充建议";
+            case "纤维分解者":
+                return status.equals("良好") ? "纤维分解菌群正常,膳食纤维消化能力良好"
+                        : abnormal + "/" + total + "种纤维分解菌异常,可能影响膳食纤维消化";
+            case "条件致病菌":
+                return status.equals("良好") ? "条件致病菌在正常范围内"
+                        : abnormal + "/" + total + "种条件致病菌异常,建议关注";
+            case "碳水化合物分解者":
+                return status.equals("良好") ? "碳水化合物分解菌正常"
+                        : "拟杆菌属异常,可能受高脂高蛋白饮食影响";
+            case "琥珀酸生产者":
+                return status.equals("良好") ? "Dialister正常"
+                        : "Dialister异常,可能与情绪/压力相关";
+            default:
+                return abnormal + "/" + total + "种菌属异常";
+        }
+    }
+
+    /** 饮食模式推断 */
+    private Map<String, Object> inferDietaryPattern(Map<String, HealthGutFlora> floraByName) {
+        Map<String, Object> pattern = new LinkedHashMap<>();
+        int highProtein = 0, highFiber = 0, highSugar = 0;
+
+        HealthGutFlora alistipes = floraByName.get("alistipes");
+        if (alistipes != null && "偏高".equals(alistipes.getStatus())) highProtein++;
+
+        HealthGutFlora sutterella = floraByName.get("sutterella");
+        if (sutterella != null && "偏高".equals(sutterella.getStatus())) highProtein++;
+
+        HealthGutFlora parabacteroides = floraByName.get("parabacteroides");
+        if (parabacteroides != null && "偏高".equals(parabacteroides.getStatus())) highProtein++;
+
+        HealthGutFlora prevotella = floraByName.get("prevotella");
+        if (prevotella != null && "偏高".equals(prevotella.getStatus())) highFiber++;
+
+        HealthGutFlora megamonas = floraByName.get("megamonas");
+        if (megamonas != null && "偏高".equals(megamonas.getStatus())) highSugar++;
+
+        HealthGutFlora dorea = floraByName.get("dorea");
+        if (dorea != null && "偏高".equals(dorea.getStatus())) highSugar++;
+
+        if (highProtein >= 2 && highFiber == 0) {
+            pattern.put("pattern", "高动物蛋白饮食");
+            pattern.put("detail", "蛋白质发酵菌(Alistipes/Sutterella/Parabacteroides)偏高,提示近期动物蛋白摄入较多,建议增加蔬菜水果比例");
+        } else if (highFiber >= 1 && highProtein == 0) {
+            pattern.put("pattern", "植物性饮食");
+            pattern.put("detail", "Prevotella 偏高,提示植物性食物摄入充足,建议继续保持均衡");
+        } else if (highSugar >= 2) {
+            pattern.put("pattern", "高糖/精制碳水饮食");
+            pattern.put("detail", "Megamonas/Dorea 偏高,提示精制碳水和简单糖摄入较多,建议减少含糖饮料和糕点");
+        } else {
+            pattern.put("pattern", "均衡饮食");
+            pattern.put("detail", "菌群特征未显示明显偏食模式,现有饮食结构较为均衡");
+        }
+        return pattern;
+    }
+
+    /** 风险组合模型 */
+    private List<Map<String, Object>> computeRiskIndicators(Map<String, HealthGutFlora> floraByName) {
+        List<Map<String, Object>> risks = new ArrayList<>();
+
+        // IBD 倾向
+        Map<String, Object> ibd = new LinkedHashMap<>();
+        ibd.put("name", "肠道炎症倾向");
+        List<String> ibdBacteria = new ArrayList<>();
+        addIfAbnormal(floraByName, "megamonas", ibdBacteria);
+        addIfAbnormal(floraByName, "ruminococcus", ibdBacteria);
+        ibd.put("relatedBacteria", ibdBacteria);
+        ibd.put("level", ibdBacteria.size() >= 2 ? "关注" : ibdBacteria.size() == 1 ? "注意" : "正常");
+        risks.add(ibd);
+
+        // 代谢综合征
+        Map<String, Object> metabolic = new LinkedHashMap<>();
+        metabolic.put("name", "代谢健康提示");
+        List<String> metaBacteria = new ArrayList<>();
+        addIfAbnormal(floraByName, "blautia", metaBacteria);
+        addIfAbnormal(floraByName, "roseburia", metaBacteria);
+        metabolic.put("relatedBacteria", metaBacteria);
+        metabolic.put("level", metaBacteria.size() >= 2 ? "关注" : metaBacteria.size() == 1 ? "注意" : "正常");
+        risks.add(metabolic);
+
+        // IBS/肠易激
+        Map<String, Object> ibs = new LinkedHashMap<>();
+        ibs.put("name", "肠易激/腹胀风险");
+        List<String> ibsBacteria = new ArrayList<>();
+        addIfAbnormal(floraByName, "dorea", ibsBacteria);
+        addIfAbnormal(floraByName, "ruminococcus", ibsBacteria);
+        ibs.put("relatedBacteria", ibsBacteria);
+        ibs.put("level", ibsBacteria.size() >= 2 ? "关注" : ibsBacteria.size() == 1 ? "注意" : "正常");
+        risks.add(ibs);
+
+        // 神经-肠轴
+        Map<String, Object> neuro = new LinkedHashMap<>();
+        neuro.put("name", "神经-肠轴提示");
+        List<String> neuroBacteria = new ArrayList<>();
+        addIfAbnormal(floraByName, "dialister", neuroBacteria);
+        addIfAbnormal(floraByName, "phascolarctobacterium", neuroBacteria);
+        addIfAbnormal(floraByName, "sutterella", neuroBacteria);
+        neuro.put("relatedBacteria", neuroBacteria);
+        neuro.put("level", neuroBacteria.size() >= 2 ? "关注" : neuroBacteria.size() == 1 ? "注意" : "正常");
+        risks.add(neuro);
+
+        return risks;
+    }
+
+    private void addIfAbnormal(Map<String, HealthGutFlora> floraByName, String name, List<String> list) {
+        HealthGutFlora f = floraByName.get(name);
+        if (f != null && f.getStatus() != null && !"正常".equals(f.getStatus())) {
+            list.add(f.getBacteriaName() + "(" + f.getStatus() + ")");
+        }
+    }
+
+    private double parseDouble(String value, double defaultValue) {
+        if (value == null) return defaultValue;
+        try {
+            return Double.parseDouble(value);
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+
     // ============================================================
     // 饮食模块扩展
     // ============================================================

+ 235 - 2
cfc-frontend/pages/health/gut-flora-detail.vue

@@ -74,6 +74,69 @@
         </view>
       </view>
 
+      <!-- 菌群功能分析 -->
+      <view class="analysis-section" v-if="analysisData">
+        <view class="section-title-bar">
+          <text class="analysis-title">🧬 菌群功能分析</text>
+          <text class="analysis-subtitle">基于知识库的功能维度解读</text>
+        </view>
+
+        <!-- 功能维度聚合 -->
+        <view class="analysis-card">
+          <text class="card-label">功能维度评估</text>
+          <view class="func-dim" v-for="(dim, di) in analysisData.functionalAnalysis" :key="di">
+            <view class="func-dim-head">
+              <text class="func-dim-name">{{ dim.name }}</text>
+              <text :class="'func-badge func-' + statusKey(dim.status)">{{ dim.status }}</text>
+            </view>
+            <view class="func-bar">
+              <view class="func-bar-fill" :class="'fill-' + statusKey(dim.status)" :style="'width:' + dim.level + '%'"></view>
+            </view>
+            <text class="func-dim-summary">{{ dim.summary }}</text>
+          </view>
+        </view>
+
+        <!-- P/B 比值 -->
+        <view class="analysis-card" v-if="analysisData.pbRatio && analysisData.pbRatio.ratio !== undefined">
+          <text class="card-label">饮食模式(P/B 比值)</text>
+          <view class="pb-row">
+            <text class="pb-label">Prevotella: {{ analysisData.pbRatio.prevotellaValue }}</text>
+            <text class="pb-label">Bacteroides: {{ analysisData.pbRatio.bacteroidesValue }}</text>
+            <text class="pb-ratio">比值 {{ analysisData.pbRatio.ratio }}</text>
+          </view>
+          <text class="func-dim-summary">{{ analysisData.pbRatio.interpretation }}</text>
+        </view>
+
+        <!-- 饮食模式推断 -->
+        <view class="analysis-card" v-if="analysisData.dietaryPattern">
+          <text class="card-label">饮食模式推断</text>
+          <text class="pattern-name">{{ analysisData.dietaryPattern.pattern }}</text>
+          <text class="func-dim-summary">{{ analysisData.dietaryPattern.detail }}</text>
+        </view>
+
+        <!-- 风险提示 -->
+        <view class="analysis-card">
+          <text class="card-label">风险提示</text>
+          <view class="risk-item" v-for="(risk, ri) in analysisData.riskIndicators" :key="ri">
+            <text class="risk-name">{{ risk.name }}</text>
+            <text :class="'risk-level risk-' + statusKey(risk.level)">{{ risk.level }}</text>
+            <text class="risk-bacteria" v-if="risk.relatedBacteria && risk.relatedBacteria.length > 0">
+              {{ risk.relatedBacteria.join('、') }}
+            </text>
+          </view>
+        </view>
+
+        <!-- 益生菌指导 -->
+        <view class="analysis-card" v-if="analysisData.probioticAdvice">
+          <text class="card-label">益生菌指导</text>
+          <view class="probiotic-item" v-for="(advice, key) in analysisData.probioticAdvice" :key="key">
+            <text class="probiotic-name">{{ key === 'bifidobacterium' ? '双歧杆菌' : '乳杆菌' }}</text>
+            <text :class="'func-badge func-' + statusKey(advice.status)">{{ advice.status }}</text>
+            <text class="probiotic-advice">{{ advice.advice }}</text>
+          </view>
+        </view>
+      </view>
+
       <!-- 肠道功能手风琴 -->
       <view class="accordion-section">
         <view class="accordion-header" @tap="toggleAccordion('gut')">
@@ -163,7 +226,7 @@
 </template>
 
 <script>
-import { getReportDetail, editHealthReport, queryKnowledgeBase, queryIndicatorKnowledge } from '../../utils/api.js'
+import { getReportDetail, editHealthReport, queryKnowledgeBase, queryIndicatorKnowledge, getGutFloraAnalysis } from '../../utils/api.js'
 import HealthKnowledgePopup from '../../components/health-knowledge-popup.vue'
 import FeedbackPopup from '../../components/feedback-popup.vue'
 
@@ -199,7 +262,8 @@ export default {
       knowledgeVisible: false,
       knowledgeData: {},
       feedbackVisible: false,
-      indStatuses: ['正常', '偏低', '偏高', '缺乏', '不足', '过多', '异常']
+      indStatuses: ['正常', '偏低', '偏高', '缺乏', '不足', '过多', '异常'],
+      analysisData: null
     }
   },
   onLoad: function(options) {
@@ -215,6 +279,10 @@ export default {
       var map = { '偏高': 'high', '过多': 'excess', '偏低': 'low', '缺乏': 'deficient', '不足': 'insufficient', '异常': 'abnormal', '注意': 'warning' }
       return map[s] || 'normal'
     },
+    statusKey: function(s) {
+      var map = { '良好': 'good', '注意': 'warning', '需关注': 'danger', '关注': 'danger', '正常': 'normal', '偏高': 'high', '偏低': 'low' }
+      return map[s] || 'normal'
+    },
     loadData: function() {
       var self = this
       getReportDetail(this.reportId).then(function(res) {
@@ -235,6 +303,16 @@ export default {
           }).length
         }
       })
+      // 加载菌群功能分析
+      this.loadAnalysis()
+    },
+    loadAnalysis: function() {
+      var self = this
+      getGutFloraAnalysis(this.reportId).then(function(res) {
+        if (res.code === 200 && res.data && !res.data.error) {
+          self.analysisData = res.data
+        }
+      }).catch(function() {})
     },
     buildScores: function(data) {
       var r = data.report || data
@@ -503,4 +581,159 @@ export default {
 }
 .ai-fab .fab-icon { font-size: 32rpx; margin-right: 12rpx; }
 .ai-fab .fab-text { font-size: 26rpx; color: #fff; font-weight: 500; }
+
+/* ===== 菌群功能分析 ===== */
+.analysis-section {
+  margin: 20rpx 30rpx;
+}
+.section-title-bar {
+  margin-bottom: 16rpx;
+}
+.analysis-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+}
+.analysis-subtitle {
+  font-size: 22rpx;
+  color: #999;
+  display: block;
+  margin-top: 4rpx;
+}
+.analysis-card {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.card-label {
+  font-size: 26rpx;
+  font-weight: 600;
+  color: #333;
+  display: block;
+  margin-bottom: 16rpx;
+}
+.func-dim {
+  margin-bottom: 20rpx;
+}
+.func-dim:last-child { margin-bottom: 0; }
+.func-dim-head {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8rpx;
+}
+.func-dim-name {
+  font-size: 24rpx;
+  color: #333;
+  font-weight: 500;
+}
+.func-badge {
+  font-size: 20rpx;
+  padding: 2rpx 14rpx;
+  border-radius: 20rpx;
+  color: #fff;
+}
+.func-good { background: #10B981; }
+.func-warning { background: #F59E0B; }
+.func-danger { background: #EF4444; }
+.func-normal { background: #6B7280; }
+.func-high { background: #EF4444; }
+.func-low { background: #F59E0B; }
+.func-bar {
+  height: 8rpx;
+  background: #f0f0f0;
+  border-radius: 4rpx;
+  overflow: hidden;
+  margin-bottom: 6rpx;
+}
+.func-bar-fill {
+  height: 100%;
+  border-radius: 4rpx;
+  transition: width 0.5s ease;
+}
+.fill-good { background: #10B981; }
+.fill-warning { background: #F59E0B; }
+.fill-danger { background: #EF4444; }
+.fill-normal { background: #6B7280; }
+.func-dim-summary {
+  font-size: 22rpx;
+  color: #888;
+  line-height: 1.5;
+}
+.pb-row {
+  display: flex;
+  gap: 20rpx;
+  align-items: center;
+  margin-bottom: 8rpx;
+}
+.pb-label {
+  font-size: 24rpx;
+  color: #666;
+}
+.pb-ratio {
+  font-size: 24rpx;
+  color: #6366F1;
+  font-weight: bold;
+}
+.pattern-name {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #FF8C42;
+  display: block;
+  margin-bottom: 8rpx;
+}
+.risk-item {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 12rpx;
+  padding: 12rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.risk-item:last-child { border-bottom: none; }
+.risk-name {
+  font-size: 24rpx;
+  color: #333;
+  font-weight: 500;
+}
+.risk-level {
+  font-size: 20rpx;
+  padding: 2rpx 14rpx;
+  border-radius: 20rpx;
+  color: #fff;
+}
+.risk-normal { background: #10B981; }
+.risk-warning { background: #F59E0B; }
+.risk-danger { background: #EF4444; }
+.risk-bacteria {
+  font-size: 22rpx;
+  color: #999;
+  width: 100%;
+  margin-top: 4rpx;
+}
+.probiotic-item {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 12rpx;
+  padding: 12rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.probiotic-item:last-child { border-bottom: none; }
+.probiotic-name {
+  font-size: 24rpx;
+  color: #333;
+  font-weight: 500;
+  width: 120rpx;
+}
+.probiotic-advice {
+  font-size: 22rpx;
+  color: #888;
+  width: 100%;
+  margin-top: 4rpx;
+  line-height: 1.5;
+}
 </style>

+ 3 - 0
cfc-frontend/utils/api.js

@@ -2596,3 +2596,6 @@ export const getDietDailyRecords = (data) => request('/api/diet/record/daily', '
 export const getDietWeeklyTrend = (data) => request('/api/diet/record/weekly', 'POST', data)
 export const getMealConfig = (data) => request('/api/diet/meals/config', 'POST', data)
 export const saveMealConfig = (data) => request('/api/diet/meals/config/save', 'POST', data)
+
+// ===== 北京菌群报告 — 菌群功能分析 =====
+export const getGutFloraAnalysis = (reportId) => request('/api/nutrition/beijing/analysis', 'POST', { reportId })