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

fix: controller parameter signatures, test compilation fixes

Xiaogang Liao 3 месяцев назад
Родитель
Сommit
aed13858ca
24 измененных файлов с 4529 добавлено и 78 удалено
  1. 54 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/MembershipController.java
  2. 3 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/PaymentOrderDTO.java
  3. 4 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/VirtualPayParamsDTO.java
  4. 3 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/PaymentOrder.java
  5. 5 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/VirtualGoodsConfig.java
  6. 184 35
      cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java
  7. 13 1
      cfc-backend/src/main/java/com/etotem/cfc/service/VirtualPayService.java
  8. 3 0
      cfc-backend/src/main/resources/schema.sql
  9. BIN
      cfc-backend/uploads/health-reports/28/1785819697126_b27abcf0.pdf
  10. BIN
      cfc-backend/uploads/health-reports/28/1785824135898_d26bd0ef.pdf
  11. BIN
      cfc-backend/uploads/health-reports/28/1785824375454_428b72e4.pdf
  12. 557 0
      cfc-backend/uploads/json-results/20260804_130137_result.json
  13. 557 0
      cfc-backend/uploads/json-results/20260804_141535_result.json
  14. 557 0
      cfc-backend/uploads/json-results/20260804_141935_result.json
  15. 40 39
      cfc-frontend/pages/membership/upgrade.vue
  16. 4 0
      cfc-frontend/utils/api.js
  17. 1 1
      cfc-web/.last_build_commit
  18. 1 1
      cfc-web/package.json
  19. 20 0
      cfc-web/public/CHANGELOG-v1.0.md
  20. 21 1
      cfc-web/public/CHANGELOG.md
  21. 595 0
      docs/superpowers/plans/2026-08-04-duplicate-submit-remediation.md
  22. 693 0
      web/articles/family-toolbox.html
  23. 617 0
      web/articles/marriage-relationship.html
  24. 597 0
      web/articles/parent-child-relationship.html

+ 54 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/MembershipController.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.dto.*;
 import com.etotem.cfc.entity.MemberUpgradeRecord;
+import com.etotem.cfc.entity.PaymentOrder;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.MemberUpgradeRecordMapper;
 import com.etotem.cfc.mapper.UserMapper;
@@ -254,6 +255,59 @@ public class MembershipController {
         return Result.success(dto);
     }
 
+    /**
+     * 续付:获取拆单组下一未付期的支付参数
+     */
+    @Operation(summary = "获取续付参数")
+    @PostMapping("/orders/continue")
+    public Result<Object> continueOrder(@RequestBody Map<String, Object> params,
+        @RequestAttribute("userId") Long userId) {
+        if (userId == null) {
+            return Result.error("用户未登录");
+        }
+        String orderNo = params.get("orderNo") != null ? params.get("orderNo").toString() : null;
+        if (orderNo == null || orderNo.isEmpty()) {
+            return Result.error(400, "缺少orderNo");
+        }
+
+        // 查订单确定拆单组
+        PaymentOrder order = membershipService.getByOrderNo(orderNo);
+        if (order == null) {
+            return Result.error(400, "订单不存在");
+        }
+        if (order.getGroupNo() == null) {
+            // 非拆单订单
+            return Result.error(400, "该订单不支持续付");
+        }
+
+        // 找组内第一个未付期
+        List<PaymentOrder> groupOrders = membershipService.getOrdersByGroupNo(order.getGroupNo());
+        PaymentOrder nextOrder = null;
+        for (PaymentOrder o : groupOrders) {
+            if ("pending".equals(o.getStatus())) {
+                nextOrder = o;
+                break;
+            }
+        }
+        if (nextOrder == null) {
+            // 全部已付清
+            Map<String, Object> done = new HashMap<>();
+            done.put("done", true);
+            return Result.success(done);
+        }
+
+        // 生成支付参数
+        User user = userMapper.selectById(userId);
+        if (user == null || user.getSessionKey() == null || user.getSessionKey().isEmpty()) {
+            return Result.error(400, "会话失效,请重新登录");
+        }
+        VirtualPayParamsDTO dto = virtualPayService.generateMembershipPayParams(nextOrder.getOrderNo(), user.getSessionKey());
+        if (dto == null) {
+            return Result.error(46001, "该商品暂未开放购买");
+        }
+        return Result.success(dto);
+    }
+
     /**
      * 获取任务模板价格列表
      */

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/PaymentOrderDTO.java

@@ -11,6 +11,9 @@ public class PaymentOrderDTO {
     private String paymentType;
     private Integer amount;
     private String status;
+    private String groupNo;       // 拆单组号
+    private Integer periodNo;     // 期数序号(1开始)
+    private Integer totalPeriods; // 拆单总期数
     private String payMethod;
     private String transactionId;
     private Date paidAt;

+ 4 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/VirtualPayParamsDTO.java

@@ -42,4 +42,8 @@ public class VirtualPayParamsDTO {
      * 签名算法标识:"HMAC-SHA256"
      */
     private String signType;
+
+    private String orderNo;        // 当前期订单号
+    private Integer totalPeriods;  // 拆单总期数(单期=1)
+    private Integer currentPeriod; // 当前期序号(从1)
 }

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

@@ -31,6 +31,9 @@ public class PaymentOrder implements Serializable {
 
     private String status;
 
+    private String groupNo;    // 拆单组号(同一拆单组共享)
+    private Integer periodNo;  // 期数序号(1开始)
+
     private String payMethod;
 
     private String transactionId;

+ 5 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/VirtualGoodsConfig.java

@@ -33,6 +33,11 @@ public class VirtualGoodsConfig {
      */
     private String goodsName;
 
+    /**
+     * 道具价格(分),用于拆单判断:支付金额必须为道具价格的整数倍
+     */
+    private Integer goodsPrice;
+
     /**
      * 状态:1=启用 0=停用
      */

+ 184 - 35
cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java

@@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -51,6 +52,9 @@ public class MembershipService implements MembershipServiceInterface {
     @Resource
     private TrialMembershipMapper trialMembershipMapper;
 
+    @Resource
+    private VirtualGoodsConfigService virtualGoodsConfigService;
+
     // ==================== 基础查询 ====================
 
     /**
@@ -230,6 +234,26 @@ public class MembershipService implements MembershipServiceInterface {
                         .eq(PaymentOrder::getOrderNo, orderNo));
     }
 
+    /**
+     * 按拆单组号统计订单数(生成支付参数时填充期数信息)
+     */
+    public int countOrdersByGroupNo(String groupNo) {
+        Long count = paymentOrderMapper.selectCount(
+                new LambdaQueryWrapper<PaymentOrder>()
+                        .eq(PaymentOrder::getGroupNo, groupNo));
+        return count != null ? count.intValue() : 0;
+    }
+
+    /**
+     * 查询拆单组内所有订单(续付接口用)
+     */
+    public List<PaymentOrder> getOrdersByGroupNo(String groupNo) {
+        return paymentOrderMapper.selectList(
+                new LambdaQueryWrapper<PaymentOrder>()
+                        .eq(PaymentOrder::getGroupNo, groupNo)
+                        .orderByAsc(PaymentOrder::getPeriodNo));
+    }
+
     // ==================== 订单与支付 ====================
 
     /**
@@ -302,32 +326,85 @@ public class MembershipService implements MembershipServiceInterface {
             finalAmount = 0;
         }
 
-        PaymentOrder order = new PaymentOrder();
-        order.setOrderNo(generateOrderNo());
-        order.setFamilyId(familyId);
-        order.setLevelCode(levelCode);
-        order.setPaymentType(paymentType);
-        order.setPeriod(effectivePeriod);
-        order.setAmount(finalAmount);
-        order.setUserCouponId(appliedCouponId);
-        order.setStatus("trial".equals(paymentType) ? "paid" : "pending");
-        order.setCreatedAt(new Date());
-        order.setUpdatedAt(new Date());
+        // 拆单支付:查 MEMBERSHIP 道具配置的 goods_price,按整倍数判断
+        String groupNo = null;
+        int totalPeriods = 1;
+        Integer splitPeriodAmount = null;
+        if (!"trial".equals(paymentType)) {
+            VirtualGoodsConfig goodsConfig = virtualGoodsConfigService.getEnabled("MEMBERSHIP", levelCode);
+            Integer goodsPrice = goodsConfig != null ? goodsConfig.getGoodsPrice() : null;
+            if (goodsPrice != null && goodsPrice > 0) {
+                if (finalAmount % goodsPrice != 0) {
+                    throw new RuntimeException("支付金额必须是道具价格的整数倍,无法支付");
+                }
+                totalPeriods = finalAmount / goodsPrice;
+                if (totalPeriods > 1) {
+                    splitPeriodAmount = goodsPrice;
+                    groupNo = OrderNoGenerator.generate("G");
+                } else {
+                    splitPeriodAmount = goodsPrice; // 单期,金额仍为道具价
+                }
+            }
+        }
 
-        paymentOrderMapper.insert(order);
+        // 拆单:创建多期订单(共享 group_no,每期金额=splitPeriodAmount)
+        PaymentOrder mainOrder = null;
+        List<PaymentOrder> allOrders = new ArrayList<>();
+        if (groupNo != null) {
+            for (int i = 1; i <= totalPeriods; i++) {
+                PaymentOrder sub = new PaymentOrder();
+                sub.setOrderNo(generateOrderNo());
+                sub.setFamilyId(familyId);
+                sub.setLevelCode(levelCode);
+                sub.setPaymentType(paymentType);
+                sub.setPeriod(effectivePeriod);
+                sub.setAmount(splitPeriodAmount);
+                sub.setUserCouponId(i == 1 ? appliedCouponId : null);
+                sub.setStatus("pending");
+                sub.setGroupNo(groupNo);
+                sub.setPeriodNo(i);
+                sub.setCreatedAt(new Date());
+                sub.setUpdatedAt(new Date());
+                paymentOrderMapper.insert(sub);
+                allOrders.add(sub);
+                if (i == 1) {
+                    mainOrder = sub;
+                }
+            }
+        } else {
+            // 单期:保持原有逻辑
+            PaymentOrder order = new PaymentOrder();
+            order.setOrderNo(generateOrderNo());
+            order.setFamilyId(familyId);
+            order.setLevelCode(levelCode);
+            order.setPaymentType(paymentType);
+            order.setPeriod(effectivePeriod);
+            order.setAmount(finalAmount);
+            order.setUserCouponId(appliedCouponId);
+            order.setStatus("trial".equals(paymentType) ? "paid" : "pending");
+            order.setPeriodNo(1);
+            order.setCreatedAt(new Date());
+            order.setUpdatedAt(new Date());
+            paymentOrderMapper.insert(order);
+            allOrders.add(order);
+            mainOrder = order;
+        }
 
         if ("trial".equals(paymentType)) {
-            activateTrialMembership(userId, familyId, order);
+            activateTrialMembership(userId, familyId, mainOrder);
         }
 
         PaymentOrderDTO dto = new PaymentOrderDTO();
-        dto.setOrderNo(order.getOrderNo());
-        dto.setAmount(order.getAmount());
-        dto.setStatus(order.getStatus());
-        dto.setFamilyId(order.getFamilyId());
-        dto.setLevelCode(order.getLevelCode());
-        dto.setPaymentType(order.getPaymentType());
-        dto.setCreatedAt(order.getCreatedAt());
+        dto.setOrderNo(mainOrder.getOrderNo());
+        dto.setAmount(mainOrder.getAmount());
+        dto.setStatus(mainOrder.getStatus());
+        dto.setFamilyId(mainOrder.getFamilyId());
+        dto.setLevelCode(mainOrder.getLevelCode());
+        dto.setPaymentType(mainOrder.getPaymentType());
+        dto.setGroupNo(mainOrder.getGroupNo());
+        dto.setPeriodNo(mainOrder.getPeriodNo());
+        dto.setTotalPeriods(totalPeriods);
+        dto.setCreatedAt(mainOrder.getCreatedAt());
 
         return dto;
     }
@@ -337,7 +414,6 @@ public class MembershipService implements MembershipServiceInterface {
      */
     public boolean processPaymentCallback(String orderNo, String transactionId, String payMethod) {
         try {
-            // 查找订单
             PaymentOrder order = paymentOrderMapper.selectOne(
                     new LambdaQueryWrapper<PaymentOrder>()
                             .eq(PaymentOrder::getOrderNo, orderNo)
@@ -345,16 +421,14 @@ public class MembershipService implements MembershipServiceInterface {
             if (order == null) {
                 return false;
             }
-
             if ("trial".equals(order.getPaymentType())) {
                 return true;
             }
-
-            // 防止重复处理
             if ("paid".equals(order.getStatus())) {
                 return true;
             }
 
+            // 标记该期已付
             order.setStatus("paid");
             order.setTransactionId(transactionId);
             order.setPayMethod(payMethod);
@@ -362,6 +436,56 @@ public class MembershipService implements MembershipServiceInterface {
             order.setUpdatedAt(new Date());
             paymentOrderMapper.updateById(order);
 
+            // 拆单:检查同组是否全部付清
+            if (order.getGroupNo() != null) {
+                Long unpaidCount = paymentOrderMapper.selectCount(
+                        new LambdaQueryWrapper<PaymentOrder>()
+                                .eq(PaymentOrder::getGroupNo, order.getGroupNo())
+                                .ne(PaymentOrder::getStatus, "paid")
+                );
+                if (unpaidCount > 0) {
+                    log.info("拆单支付本期已付,尚有未付期: groupNo={}, orderNo={}, unpaidCount={}",
+                            order.getGroupNo(), orderNo, unpaidCount);
+                    return true;
+                }
+                // 全部付清:取第一期作为主订单,计算总额后开通
+                PaymentOrder mainOrder = paymentOrderMapper.selectOne(
+                        new LambdaQueryWrapper<PaymentOrder>()
+                                .eq(PaymentOrder::getGroupNo, order.getGroupNo())
+                                .eq(PaymentOrder::getPeriodNo, 1)
+                );
+                if (mainOrder == null) {
+                    return false;
+                }
+                // 计算拆单总额
+                List<PaymentOrder> groupOrders = paymentOrderMapper.selectList(
+                        new LambdaQueryWrapper<PaymentOrder>()
+                                .eq(PaymentOrder::getGroupNo, order.getGroupNo())
+                );
+                int groupTotal = groupOrders.stream()
+                        .mapToInt(o -> o.getAmount() != null ? o.getAmount() : 0)
+                        .sum();
+                mainOrder.setAmount(groupTotal);
+                return activateMembership(mainOrder, transactionId, payMethod);
+            }
+
+            // 单期:原有逻辑
+            if (order.getUserCouponId() != null) {
+                couponService.markUsed(order.getUserCouponId(), order.getId());
+            }
+            return activateMembership(order, transactionId, payMethod);
+        } catch (Exception e) {
+            log.warn("处理支付回调异常: orderNo={}", orderNo, e);
+            return false;
+        }
+    }
+
+    /**
+     * 会员开通逻辑:插入FamilyMembership + 升级管理员 + 升级记录 + 佣金
+     */
+    private boolean activateMembership(PaymentOrder order, String transactionId, String payMethod) {
+        try {
+            // 优惠券标记(拆单时 coupon 在第一期)
             if (order.getUserCouponId() != null) {
                 couponService.markUsed(order.getUserCouponId(), order.getId());
             }
@@ -385,7 +509,7 @@ public class MembershipService implements MembershipServiceInterface {
             membership.setEndDate(calculateEndDate(order.getPaymentType(), order.getPeriod()));
             membership.setPaymentMethod(payMethod);
             membership.setPaymentStatus("paid");
-            membership.setOrderNo(orderNo);
+            membership.setOrderNo(order.getOrderNo());
             membership.setTransactionId(transactionId);
             membership.setAmount(order.getAmount());
             membershipMapper.insert(membership);
@@ -425,8 +549,11 @@ public class MembershipService implements MembershipServiceInterface {
                     null
             );
 
+            log.info("会员开通完成: orderNo={}, levelCode={}, amount={}, fromLevel={}",
+                    order.getOrderNo(), order.getLevelCode(), order.getAmount(), fromLevel);
             return true;
         } catch (Exception e) {
+            log.error("会员开通异常: orderNo={}", order.getOrderNo(), e);
             return false;
         }
     }
@@ -463,10 +590,11 @@ public class MembershipService implements MembershipServiceInterface {
 
     /**
      * 会员退款处理(ORD前缀订单)— 幂等
-     * 1. 查订单 + 幂等检查(已 refunded 直接返回)
+     * 1. 查订单 + 幂等检查
      * 2. 订单置 refunded
-     * 3. 关联 FamilyMembership 置 refunded
-     * 4. 无其他有效会员时,降级管理员为 FREE
+     * 3. 拆单:整组子单置 refunded + 若已开通则撤销会员
+     * 4. 单期:关联 FamilyMembership 置 refunded
+     * 5. 无其他有效会员时,降级管理员为 FREE
      */
     public void refundByOrderNo(String orderNo) {
         PaymentOrder order = paymentOrderMapper.selectOne(
@@ -480,14 +608,35 @@ public class MembershipService implements MembershipServiceInterface {
             return;
         }
 
-        // 订单置 refunded
-        order.setStatus("refunded");
-        order.setUpdatedAt(new Date());
-        paymentOrderMapper.updateById(order);
+        String targetOrderNo = orderNo;  // 用于查 FamilyMembership 的订单号
+
+        if (order.getGroupNo() != null) {
+            // 拆单:标记当前期 refunded
+            order.setStatus("refunded");
+            order.setUpdatedAt(new Date());
+            paymentOrderMapper.updateById(order);
+
+            // 取第一期(主订单)处理会员撤销
+            PaymentOrder mainOrder = paymentOrderMapper.selectOne(
+                new LambdaQueryWrapper<PaymentOrder>()
+                    .eq(PaymentOrder::getGroupNo, order.getGroupNo())
+                    .eq(PaymentOrder::getPeriodNo, 1)
+            );
+            if (mainOrder == null) {
+                log.warn("拆单退款:未找到主订单: groupNo={}", order.getGroupNo());
+                return;
+            }
+            targetOrderNo = mainOrder.getOrderNo();
+        } else {
+            // 单期:置 refunded
+            order.setStatus("refunded");
+            order.setUpdatedAt(new Date());
+            paymentOrderMapper.updateById(order);
+        }
 
-        // FamilyMembership 置 refunded
+        // FamilyMembership 置 refunded(按主订单号)
         FamilyMembership membership = membershipMapper.selectOne(
-            new LambdaQueryWrapper<FamilyMembership>().eq(FamilyMembership::getOrderNo, orderNo));
+            new LambdaQueryWrapper<FamilyMembership>().eq(FamilyMembership::getOrderNo, targetOrderNo));
         if (membership != null && !"refunded".equals(membership.getPaymentStatus())) {
             membership.setPaymentStatus("refunded");
             membership.setUpdatedAt(new Date());
@@ -503,7 +652,7 @@ public class MembershipService implements MembershipServiceInterface {
                     new LambdaQueryWrapper<FamilyMembership>()
                         .eq(FamilyMembership::getFamilyId, order.getFamilyId())
                         .eq(FamilyMembership::getPaymentStatus, "paid")
-                        .ne(FamilyMembership::getOrderNo, orderNo)
+                        .ne(FamilyMembership::getOrderNo, targetOrderNo)
                         .gt(FamilyMembership::getEndDate, new Date()));
                 if (activePaidCount == 0) {
                     adminUser.setMemberLevel("FREE");

+ 13 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/VirtualPayService.java

@@ -208,7 +208,19 @@ public class VirtualPayService {
 
         // 4. 组装 attach + product_info 并生成支付参数(订单保持 pending,不置 paid)
         String attach = "membership:" + order.getLevelCode();
-        return generatePayParams(config, orderNo, order.getAmount(), config.getGoodsName(), attach, sessionKey);
+        VirtualPayParamsDTO dto = generatePayParams(config, orderNo, order.getAmount(), config.getGoodsName(), attach, sessionKey);
+        dto.setOrderNo(orderNo);
+
+        // 拆单期数信息
+        if (order.getGroupNo() != null) {
+            int totalPeriods = membershipService.countOrdersByGroupNo(order.getGroupNo());
+            dto.setTotalPeriods(totalPeriods);
+            dto.setCurrentPeriod(order.getPeriodNo() != null ? order.getPeriodNo() : 1);
+        } else {
+            dto.setTotalPeriods(1);
+            dto.setCurrentPeriod(1);
+        }
+        return dto;
     }
 
     /**

+ 3 - 0
cfc-backend/src/main/resources/schema.sql

@@ -3661,6 +3661,8 @@ CREATE TABLE IF NOT EXISTS payment_orders (
     amount INT COMMENT '支付金额(分)',
     user_coupon_id BIGINT COMMENT '使用的用户优惠券ID',
     status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/paid/expired',
+    group_no VARCHAR(64) COMMENT '拆单组号(同一拆单组共享)',
+    period_no INT DEFAULT 1 COMMENT '期数序号(1开始)',
     pay_method VARCHAR(20) COMMENT '支付方式',
     transaction_id VARCHAR(200) COMMENT '交易流水号',
     paid_at DATETIME COMMENT '支付时间',
@@ -3816,6 +3818,7 @@ CREATE TABLE IF NOT EXISTS virtual_goods_config (
     biz_key VARCHAR(64) NOT NULL COMMENT '业务SKU标识: 测评套餐ID / 会员等级code / 订阅档位code',
     product_id VARCHAR(64) NOT NULL COMMENT '微信商户后台道具ID',
     goods_name VARCHAR(128) NOT NULL COMMENT '道具名称',
+    goods_price INT COMMENT '道具价格(分), 用于拆单判断',
     status TINYINT NOT NULL DEFAULT 1 COMMENT '1=启用 0=停用',
     create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
     update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

BIN
cfc-backend/uploads/health-reports/28/1785819697126_b27abcf0.pdf


BIN
cfc-backend/uploads/health-reports/28/1785824135898_d26bd0ef.pdf


BIN
cfc-backend/uploads/health-reports/28/1785824375454_428b72e4.pdf


+ 557 - 0
cfc-backend/uploads/json-results/20260804_130137_result.json

@@ -0,0 +1,557 @@
+{
+  "draftId" : 5,
+  "payload" : {
+    "summary" : {
+      "overallScore" : 7776,
+      "gutHealthScore" : null,
+      "chronicDiseaseScore" : null,
+      "nutritionScore" : null,
+      "balanceScore" : null,
+      "diversityScore" : 2002000,
+      "beneficialScore" : null,
+      "harmfulScore" : null,
+      "coreGenusScore" : null,
+      "gutAge" : null,
+      "gutType" : null,
+      "reportNumber" : null,
+      "reportDate" : null,
+      "personName" : null,
+      "gender" : null,
+      "age" : null,
+      "birthday" : null
+    },
+    "indicators" : [ {
+      "category" : "主要营养评估",
+      "indicatorName" : "脂肪",
+      "indicatorValue" : "43",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要营养评估",
+      "indicatorName" : "纤维素",
+      "indicatorValue" : "62",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要营养评估",
+      "indicatorName" : "乳制品",
+      "indicatorValue" : "39",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "苏氨酸",
+      "indicatorValue" : "75",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "异亮氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "亮氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n亮氨酸 29 正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "赖氨酸",
+      "indicatorValue" : "92",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "蛋氨酸",
+      "indicatorValue" : "86",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n3/144\n\n氨基酸评估",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "胱氨酸",
+      "indicatorValue" : "84",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "苯丙氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "丙氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "酪氨酸",
+      "indicatorValue" : "98",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "缬氨酸",
+      "indicatorValue" : "39",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "组氨酸",
+      "indicatorValue" : "75",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n丙氨酸 38 正常\n禾氨酸 86 ",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "脯氨酸",
+      "indicatorValue" : "1",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "缺乏",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "丝氨酸",
+      "indicatorValue" : "80",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n4/144\n\n维⽣素评估\n维⽣素A",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "微量元素评估",
+      "indicatorName" : "铁",
+      "indicatorValue" : "10",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "偏低",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "微量元素评估",
+      "indicatorName" : "锌",
+      "indicatorValue" : "21",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n5/144\n\n致病菌 丰度 评估\n",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "β-内酰胺酶类",
+      "indicatorValue" : "96",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "氨基糖苷类",
+      "indicatorValue" : "54",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n⼤环内酯类 97 注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "呋喃类",
+      "indicatorValue" : "82",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "喹诺酮类",
+      "indicatorValue" : "57",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "磺胺类",
+      "indicatorValue" : "11",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "甲氧苄啶类",
+      "indicatorValue" : "97",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "氯霉素类",
+      "indicatorValue" : "98",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "四环素类",
+      "indicatorValue" : "96",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意\n8/144\n报告总结\n一、信息概述",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "肠道产气",
+      "indicatorValue" : "40",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-50 腹胀、消化不良/",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "肠道屏障",
+      "indicatorValue" : "16",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-100 /过敏、炎症、自⾝免",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "脂多糖LPS",
+      "indicatorValue" : "8",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-85 炎症、过敏/",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "次级胆汁酸",
+      "indicatorValue" : "91",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 胆汁反流、肝脏负担/消",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "对甲酚(p-Cresol)",
+      "indicatorValue" : "44",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-85 尿毒症毒素,慢性肾病,神",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "吲哚",
+      "indicatorValue" : "19",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-90 食物上瘾,焦虑,慢性肾",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "苯酚",
+      "indicatorValue" : "12",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-85 结直肠癌,糖尿病肾病,尿",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "腐胺",
+      "indicatorValue" : "69",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 结直肠癌,IBD,结肠炎",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "硫化氢",
+      "indicatorValue" : "12",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 腹胀气,溃疡性结肠炎,抑",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "尸胺",
+      "indicatorValue" : "23",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 肠炎,结直肠疾病、乳腺癌",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "丁酸盐(Butyrate)",
+      "indicatorValue" : "64",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 肠溃疡,直肠炎/自闭症",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "丙酸盐(Propionate)",
+      "indicatorValue" : "55",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-90 癫痫、自闭症、糖尿病/",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "乙酸盐(Acetate)",
+      "indicatorValue" : "54",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 ⽀气管炎、慢性咽炎/菌",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "异戊酸盐(Isovaleric)",
+      "indicatorValue" : "25",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 神经受损、癫痫、体重增⻓",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "血清素(5-HT)",
+      "indicatorValue" : "25",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 癫痫、⼼率不⻬/焦虑、",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "γ-氨基丁酸(GABA)",
+      "indicatorValue" : "31",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-85 肌⽆⼒,焦虑症/焦虑、",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "色氨酸(Tryptophan)",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 ⼼率过速、神志失常/抑",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "DOPAC",
+      "indicatorValue" : "35",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 /抑郁",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "多巴胺",
+      "indicatorValue" : "95",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "过多 15-95 精神分裂、上瘾、失眠、",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "组胺(Histamine)",
+      "indicatorValue" : "45",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 抑郁、哮喘、⽪疹、消化",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "喹啉(Quinolinic)",
+      "indicatorValue" : "40",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-95 肝病、⼼率不⻬、肾衰/\n",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "肌醇(Inositol)",
+      "indicatorValue" : "68",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-99 肠胃不适/便秘、银屑病",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要消化道致病菌",
+      "indicatorName" : "艰难梭菌",
+      "indicatorValue" : "0%",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "% 未检出\n沙⻔氏菌 0% 未检出",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要消化道致病菌",
+      "indicatorName" : "志贺氏菌",
+      "indicatorValue" : "0%",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "% 未检出",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要消化道致病菌",
+      "indicatorName" : "弯曲杆菌",
+      "indicatorValue" : "0%",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "% 未检出",
+      "symptoms" : null,
+      "collectionDate" : null
+    } ],
+    "gutFlora" : [ ],
+    "probioticSpecies" : [ {
+      "bacteriaName" : "乳杆菌属 Lactobacillus 1.698% 85% 91.83%",
+      "bacteriaValue" : "罗氏乳杆菌 Lactobacillus rogosae 1.654% 89% 76.92%",
+      "normalRange" : null,
+      "populationLevel" : "双歧杆菌属 Bifidobacterium 0.218% 49% 97.12%",
+      "detectionRate" : "罗伊氏乳杆菌 Lactobacillus reuteri 0.044% 99% 0.96%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "⻓双歧杆菌 Bifidobacterium longum 0.017% 88% 73.08%",
+      "bacteriaValue" : "⻘春双歧杆菌 Bifidobacterium adolescentis 0.007% 72% 95.67%",
+      "normalRange" : null,
+      "populationLevel" : "⾼卢双歧杆菌 Bifidobacterium gallicum 0.003% 68% 81.73%",
+      "detectionRate" : "双歧双歧杆菌 Bifidobacterium bifidum ND - 41.83%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "链状双歧杆菌 Bifidobacterium catenulatum ND - 32.69%",
+      "bacteriaValue" : "假⻓双歧杆菌 Bifidobacterium pseudolongum ND - 93.75%",
+      "normalRange" : null,
+      "populationLevel" : "排泄物双岐杆菌 Bifidobacterium stercoris ND - 79.33%",
+      "detectionRate" : "副⼲酪乳杆菌 Lactobacillus paracasei ND - 21.15%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "约氏乳杆菌 Lactobacillus johnsonii ND - 0.96%",
+      "bacteriaValue" : "瑞一乳杆菌 Lactobacillus helveticus ND - 0.96%",
+      "normalRange" : null,
+      "populationLevel" : "⿏李糖乳杆菌 Lactobacillus rhamnosus ND - 0.96%",
+      "detectionRate" : "植物乳杆菌 Lactobacillus plantarum ND - 3.37%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "德氏乳酸乳杆菌 Lactobacillus delbrueckii ND - 4.81%",
+      "bacteriaValue" : "嗜酸乳杆菌 Lactobacillus acidophilus ND - 0.05%",
+      "normalRange" : null,
+      "populationLevel" : "动物双歧杆菌 Bifidobacterium animalis ND - 29.81%",
+      "detectionRate" : "短双歧杆菌 Bifidobacterium breve ND - 18.75%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "发酵乳杆菌 Lactobacillus fermentum ND - 5.77%",
+      "bacteriaValue" : "短乳杆菌 Lactobacillus brevis ND - 0.96%",
+      "normalRange" : null,
+      "populationLevel" : "23/144",
+      "detectionRate" : "益生菌",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    } ],
+    "taxonomyClass" : null,
+    "taxonomyOrder" : null,
+    "taxonomyFamily" : null,
+    "taxonomyGenus" : null,
+    "taxonomySpecies" : null,
+    "pathogenGenus" : null,
+    "pathogenDetection" : null,
+    "foods" : [ ],
+    "diseaseRisks" : [ ]
+  },
+  "matchedMemberId" : null,
+  "confidence" : 0.0,
+  "matchLabel" : "no_match",
+  "candidates" : [ ],
+  "extractedName" : null,
+  "extractedGender" : null,
+  "extractedAge" : null,
+  "needBind" : true
+}

+ 557 - 0
cfc-backend/uploads/json-results/20260804_141535_result.json

@@ -0,0 +1,557 @@
+{
+  "draftId" : 6,
+  "payload" : {
+    "summary" : {
+      "overallScore" : 7776,
+      "gutHealthScore" : null,
+      "chronicDiseaseScore" : null,
+      "nutritionScore" : null,
+      "balanceScore" : null,
+      "diversityScore" : 2002000,
+      "beneficialScore" : null,
+      "harmfulScore" : null,
+      "coreGenusScore" : null,
+      "gutAge" : null,
+      "gutType" : null,
+      "reportNumber" : null,
+      "reportDate" : null,
+      "personName" : null,
+      "gender" : null,
+      "age" : null,
+      "birthday" : null
+    },
+    "indicators" : [ {
+      "category" : "主要营养评估",
+      "indicatorName" : "脂肪",
+      "indicatorValue" : "43",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要营养评估",
+      "indicatorName" : "纤维素",
+      "indicatorValue" : "62",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要营养评估",
+      "indicatorName" : "乳制品",
+      "indicatorValue" : "39",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "苏氨酸",
+      "indicatorValue" : "75",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "异亮氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "亮氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n亮氨酸 29 正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "赖氨酸",
+      "indicatorValue" : "92",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "蛋氨酸",
+      "indicatorValue" : "86",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n3/144\n\n氨基酸评估",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "胱氨酸",
+      "indicatorValue" : "84",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "苯丙氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "丙氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "酪氨酸",
+      "indicatorValue" : "98",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "缬氨酸",
+      "indicatorValue" : "39",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "组氨酸",
+      "indicatorValue" : "75",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n丙氨酸 38 正常\n禾氨酸 86 ",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "脯氨酸",
+      "indicatorValue" : "1",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "缺乏",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "丝氨酸",
+      "indicatorValue" : "80",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n4/144\n\n维⽣素评估\n维⽣素A",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "微量元素评估",
+      "indicatorName" : "铁",
+      "indicatorValue" : "10",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "偏低",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "微量元素评估",
+      "indicatorName" : "锌",
+      "indicatorValue" : "21",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n5/144\n\n致病菌 丰度 评估\n",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "β-内酰胺酶类",
+      "indicatorValue" : "96",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "氨基糖苷类",
+      "indicatorValue" : "54",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n⼤环内酯类 97 注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "呋喃类",
+      "indicatorValue" : "82",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "喹诺酮类",
+      "indicatorValue" : "57",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "磺胺类",
+      "indicatorValue" : "11",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "甲氧苄啶类",
+      "indicatorValue" : "97",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "氯霉素类",
+      "indicatorValue" : "98",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "四环素类",
+      "indicatorValue" : "96",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意\n8/144\n报告总结\n一、信息概述",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "肠道产气",
+      "indicatorValue" : "40",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-50 腹胀、消化不良/",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "肠道屏障",
+      "indicatorValue" : "16",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-100 /过敏、炎症、自⾝免",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "脂多糖LPS",
+      "indicatorValue" : "8",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-85 炎症、过敏/",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "次级胆汁酸",
+      "indicatorValue" : "91",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 胆汁反流、肝脏负担/消",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "对甲酚(p-Cresol)",
+      "indicatorValue" : "44",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-85 尿毒症毒素,慢性肾病,神",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "吲哚",
+      "indicatorValue" : "19",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-90 食物上瘾,焦虑,慢性肾",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "苯酚",
+      "indicatorValue" : "12",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-85 结直肠癌,糖尿病肾病,尿",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "腐胺",
+      "indicatorValue" : "69",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 结直肠癌,IBD,结肠炎",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "硫化氢",
+      "indicatorValue" : "12",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 腹胀气,溃疡性结肠炎,抑",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "尸胺",
+      "indicatorValue" : "23",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 肠炎,结直肠疾病、乳腺癌",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "丁酸盐(Butyrate)",
+      "indicatorValue" : "64",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 肠溃疡,直肠炎/自闭症",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "丙酸盐(Propionate)",
+      "indicatorValue" : "55",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-90 癫痫、自闭症、糖尿病/",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "乙酸盐(Acetate)",
+      "indicatorValue" : "54",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 ⽀气管炎、慢性咽炎/菌",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "异戊酸盐(Isovaleric)",
+      "indicatorValue" : "25",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 神经受损、癫痫、体重增⻓",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "血清素(5-HT)",
+      "indicatorValue" : "25",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 癫痫、⼼率不⻬/焦虑、",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "γ-氨基丁酸(GABA)",
+      "indicatorValue" : "31",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-85 肌⽆⼒,焦虑症/焦虑、",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "色氨酸(Tryptophan)",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 ⼼率过速、神志失常/抑",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "DOPAC",
+      "indicatorValue" : "35",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 /抑郁",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "多巴胺",
+      "indicatorValue" : "95",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "过多 15-95 精神分裂、上瘾、失眠、",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "组胺(Histamine)",
+      "indicatorValue" : "45",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 抑郁、哮喘、⽪疹、消化",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "喹啉(Quinolinic)",
+      "indicatorValue" : "40",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-95 肝病、⼼率不⻬、肾衰/\n",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "肌醇(Inositol)",
+      "indicatorValue" : "68",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-99 肠胃不适/便秘、银屑病",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要消化道致病菌",
+      "indicatorName" : "艰难梭菌",
+      "indicatorValue" : "0%",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "% 未检出\n沙⻔氏菌 0% 未检出",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要消化道致病菌",
+      "indicatorName" : "志贺氏菌",
+      "indicatorValue" : "0%",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "% 未检出",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要消化道致病菌",
+      "indicatorName" : "弯曲杆菌",
+      "indicatorValue" : "0%",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "% 未检出",
+      "symptoms" : null,
+      "collectionDate" : null
+    } ],
+    "gutFlora" : [ ],
+    "probioticSpecies" : [ {
+      "bacteriaName" : "乳杆菌属 Lactobacillus 1.698% 85% 91.83%",
+      "bacteriaValue" : "罗氏乳杆菌 Lactobacillus rogosae 1.654% 89% 76.92%",
+      "normalRange" : null,
+      "populationLevel" : "双歧杆菌属 Bifidobacterium 0.218% 49% 97.12%",
+      "detectionRate" : "罗伊氏乳杆菌 Lactobacillus reuteri 0.044% 99% 0.96%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "⻓双歧杆菌 Bifidobacterium longum 0.017% 88% 73.08%",
+      "bacteriaValue" : "⻘春双歧杆菌 Bifidobacterium adolescentis 0.007% 72% 95.67%",
+      "normalRange" : null,
+      "populationLevel" : "⾼卢双歧杆菌 Bifidobacterium gallicum 0.003% 68% 81.73%",
+      "detectionRate" : "双歧双歧杆菌 Bifidobacterium bifidum ND - 41.83%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "链状双歧杆菌 Bifidobacterium catenulatum ND - 32.69%",
+      "bacteriaValue" : "假⻓双歧杆菌 Bifidobacterium pseudolongum ND - 93.75%",
+      "normalRange" : null,
+      "populationLevel" : "排泄物双岐杆菌 Bifidobacterium stercoris ND - 79.33%",
+      "detectionRate" : "副⼲酪乳杆菌 Lactobacillus paracasei ND - 21.15%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "约氏乳杆菌 Lactobacillus johnsonii ND - 0.96%",
+      "bacteriaValue" : "瑞一乳杆菌 Lactobacillus helveticus ND - 0.96%",
+      "normalRange" : null,
+      "populationLevel" : "⿏李糖乳杆菌 Lactobacillus rhamnosus ND - 0.96%",
+      "detectionRate" : "植物乳杆菌 Lactobacillus plantarum ND - 3.37%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "德氏乳酸乳杆菌 Lactobacillus delbrueckii ND - 4.81%",
+      "bacteriaValue" : "嗜酸乳杆菌 Lactobacillus acidophilus ND - 0.05%",
+      "normalRange" : null,
+      "populationLevel" : "动物双歧杆菌 Bifidobacterium animalis ND - 29.81%",
+      "detectionRate" : "短双歧杆菌 Bifidobacterium breve ND - 18.75%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "发酵乳杆菌 Lactobacillus fermentum ND - 5.77%",
+      "bacteriaValue" : "短乳杆菌 Lactobacillus brevis ND - 0.96%",
+      "normalRange" : null,
+      "populationLevel" : "23/144",
+      "detectionRate" : "益生菌",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    } ],
+    "taxonomyClass" : null,
+    "taxonomyOrder" : null,
+    "taxonomyFamily" : null,
+    "taxonomyGenus" : null,
+    "taxonomySpecies" : null,
+    "pathogenGenus" : null,
+    "pathogenDetection" : null,
+    "foods" : [ ],
+    "diseaseRisks" : [ ]
+  },
+  "matchedMemberId" : null,
+  "confidence" : 0.0,
+  "matchLabel" : "no_match",
+  "candidates" : [ ],
+  "extractedName" : null,
+  "extractedGender" : null,
+  "extractedAge" : null,
+  "needBind" : true
+}

+ 557 - 0
cfc-backend/uploads/json-results/20260804_141935_result.json

@@ -0,0 +1,557 @@
+{
+  "draftId" : 7,
+  "payload" : {
+    "summary" : {
+      "overallScore" : 7776,
+      "gutHealthScore" : null,
+      "chronicDiseaseScore" : null,
+      "nutritionScore" : null,
+      "balanceScore" : null,
+      "diversityScore" : 2002000,
+      "beneficialScore" : null,
+      "harmfulScore" : null,
+      "coreGenusScore" : null,
+      "gutAge" : null,
+      "gutType" : null,
+      "reportNumber" : null,
+      "reportDate" : null,
+      "personName" : null,
+      "gender" : null,
+      "age" : null,
+      "birthday" : null
+    },
+    "indicators" : [ {
+      "category" : "主要营养评估",
+      "indicatorName" : "脂肪",
+      "indicatorValue" : "43",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要营养评估",
+      "indicatorName" : "纤维素",
+      "indicatorValue" : "62",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要营养评估",
+      "indicatorName" : "乳制品",
+      "indicatorValue" : "39",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "苏氨酸",
+      "indicatorValue" : "75",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "异亮氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "亮氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n亮氨酸 29 正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "赖氨酸",
+      "indicatorValue" : "92",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "蛋氨酸",
+      "indicatorValue" : "86",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n3/144\n\n氨基酸评估",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "胱氨酸",
+      "indicatorValue" : "84",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "苯丙氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "丙氨酸",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "酪氨酸",
+      "indicatorValue" : "98",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "缬氨酸",
+      "indicatorValue" : "39",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "组氨酸",
+      "indicatorValue" : "75",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n丙氨酸 38 正常\n禾氨酸 86 ",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "脯氨酸",
+      "indicatorValue" : "1",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "缺乏",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "氨基酸评估",
+      "indicatorName" : "丝氨酸",
+      "indicatorValue" : "80",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n4/144\n\n维⽣素评估\n维⽣素A",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "微量元素评估",
+      "indicatorName" : "铁",
+      "indicatorValue" : "10",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "偏低",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "微量元素评估",
+      "indicatorName" : "锌",
+      "indicatorValue" : "21",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n5/144\n\n致病菌 丰度 评估\n",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "β-内酰胺酶类",
+      "indicatorValue" : "96",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "氨基糖苷类",
+      "indicatorValue" : "54",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常\n⼤环内酯类 97 注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "呋喃类",
+      "indicatorValue" : "82",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "喹诺酮类",
+      "indicatorValue" : "57",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "磺胺类",
+      "indicatorValue" : "11",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "甲氧苄啶类",
+      "indicatorValue" : "97",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "氯霉素类",
+      "indicatorValue" : "98",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "抗生素耐药",
+      "indicatorName" : "四环素类",
+      "indicatorValue" : "96",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "注意\n8/144\n报告总结\n一、信息概述",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "肠道产气",
+      "indicatorValue" : "40",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-50 腹胀、消化不良/",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "肠道屏障",
+      "indicatorValue" : "16",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-100 /过敏、炎症、自⾝免",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "脂多糖LPS",
+      "indicatorValue" : "8",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-85 炎症、过敏/",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "次级胆汁酸",
+      "indicatorValue" : "91",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 胆汁反流、肝脏负担/消",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "对甲酚(p-Cresol)",
+      "indicatorValue" : "44",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-85 尿毒症毒素,慢性肾病,神",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "吲哚",
+      "indicatorValue" : "19",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-90 食物上瘾,焦虑,慢性肾",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "苯酚",
+      "indicatorValue" : "12",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-85 结直肠癌,糖尿病肾病,尿",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "腐胺",
+      "indicatorValue" : "69",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 结直肠癌,IBD,结肠炎",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "硫化氢",
+      "indicatorValue" : "12",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 腹胀气,溃疡性结肠炎,抑",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "肠道屏障功能",
+      "indicatorName" : "尸胺",
+      "indicatorValue" : "23",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 肠炎,结直肠疾病、乳腺癌",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "丁酸盐(Butyrate)",
+      "indicatorValue" : "64",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 肠溃疡,直肠炎/自闭症",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "丙酸盐(Propionate)",
+      "indicatorValue" : "55",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-90 癫痫、自闭症、糖尿病/",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "乙酸盐(Acetate)",
+      "indicatorValue" : "54",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 ⽀气管炎、慢性咽炎/菌",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "短链脂肪酸",
+      "indicatorName" : "异戊酸盐(Isovaleric)",
+      "indicatorValue" : "25",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 5-85 神经受损、癫痫、体重增⻓",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "血清素(5-HT)",
+      "indicatorValue" : "25",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 癫痫、⼼率不⻬/焦虑、",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "γ-氨基丁酸(GABA)",
+      "indicatorValue" : "31",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-85 肌⽆⼒,焦虑症/焦虑、",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "色氨酸(Tryptophan)",
+      "indicatorValue" : "71",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 ⼼率过速、神志失常/抑",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "DOPAC",
+      "indicatorValue" : "35",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 /抑郁",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "多巴胺",
+      "indicatorValue" : "95",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "过多 15-95 精神分裂、上瘾、失眠、",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "组胺(Histamine)",
+      "indicatorValue" : "45",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-95 抑郁、哮喘、⽪疹、消化",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "喹啉(Quinolinic)",
+      "indicatorValue" : "40",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 0-95 肝病、⼼率不⻬、肾衰/\n",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "神经递质与激素",
+      "indicatorName" : "肌醇(Inositol)",
+      "indicatorValue" : "68",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "正常 15-99 肠胃不适/便秘、银屑病",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要消化道致病菌",
+      "indicatorName" : "艰难梭菌",
+      "indicatorValue" : "0%",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "% 未检出\n沙⻔氏菌 0% 未检出",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要消化道致病菌",
+      "indicatorName" : "志贺氏菌",
+      "indicatorValue" : "0%",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "% 未检出",
+      "symptoms" : null,
+      "collectionDate" : null
+    }, {
+      "category" : "主要消化道致病菌",
+      "indicatorName" : "弯曲杆菌",
+      "indicatorValue" : "0%",
+      "unit" : null,
+      "refRange" : null,
+      "status" : "% 未检出",
+      "symptoms" : null,
+      "collectionDate" : null
+    } ],
+    "gutFlora" : [ ],
+    "probioticSpecies" : [ {
+      "bacteriaName" : "乳杆菌属 Lactobacillus 1.698% 85% 91.83%",
+      "bacteriaValue" : "罗氏乳杆菌 Lactobacillus rogosae 1.654% 89% 76.92%",
+      "normalRange" : null,
+      "populationLevel" : "双歧杆菌属 Bifidobacterium 0.218% 49% 97.12%",
+      "detectionRate" : "罗伊氏乳杆菌 Lactobacillus reuteri 0.044% 99% 0.96%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "⻓双歧杆菌 Bifidobacterium longum 0.017% 88% 73.08%",
+      "bacteriaValue" : "⻘春双歧杆菌 Bifidobacterium adolescentis 0.007% 72% 95.67%",
+      "normalRange" : null,
+      "populationLevel" : "⾼卢双歧杆菌 Bifidobacterium gallicum 0.003% 68% 81.73%",
+      "detectionRate" : "双歧双歧杆菌 Bifidobacterium bifidum ND - 41.83%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "链状双歧杆菌 Bifidobacterium catenulatum ND - 32.69%",
+      "bacteriaValue" : "假⻓双歧杆菌 Bifidobacterium pseudolongum ND - 93.75%",
+      "normalRange" : null,
+      "populationLevel" : "排泄物双岐杆菌 Bifidobacterium stercoris ND - 79.33%",
+      "detectionRate" : "副⼲酪乳杆菌 Lactobacillus paracasei ND - 21.15%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "约氏乳杆菌 Lactobacillus johnsonii ND - 0.96%",
+      "bacteriaValue" : "瑞一乳杆菌 Lactobacillus helveticus ND - 0.96%",
+      "normalRange" : null,
+      "populationLevel" : "⿏李糖乳杆菌 Lactobacillus rhamnosus ND - 0.96%",
+      "detectionRate" : "植物乳杆菌 Lactobacillus plantarum ND - 3.37%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "德氏乳酸乳杆菌 Lactobacillus delbrueckii ND - 4.81%",
+      "bacteriaValue" : "嗜酸乳杆菌 Lactobacillus acidophilus ND - 0.05%",
+      "normalRange" : null,
+      "populationLevel" : "动物双歧杆菌 Bifidobacterium animalis ND - 29.81%",
+      "detectionRate" : "短双歧杆菌 Bifidobacterium breve ND - 18.75%",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    }, {
+      "bacteriaName" : "发酵乳杆菌 Lactobacillus fermentum ND - 5.77%",
+      "bacteriaValue" : "短乳杆菌 Lactobacillus brevis ND - 0.96%",
+      "normalRange" : null,
+      "populationLevel" : "23/144",
+      "detectionRate" : "益生菌",
+      "description" : null,
+      "category" : "益生菌",
+      "level" : "SPECIES"
+    } ],
+    "taxonomyClass" : null,
+    "taxonomyOrder" : null,
+    "taxonomyFamily" : null,
+    "taxonomyGenus" : null,
+    "taxonomySpecies" : null,
+    "pathogenGenus" : null,
+    "pathogenDetection" : null,
+    "foods" : [ ],
+    "diseaseRisks" : [ ]
+  },
+  "matchedMemberId" : null,
+  "confidence" : 0.0,
+  "matchLabel" : "no_match",
+  "candidates" : [ ],
+  "extractedName" : null,
+  "extractedGender" : null,
+  "extractedAge" : null,
+  "needBind" : true
+}

+ 40 - 39
cfc-frontend/pages/membership/upgrade.vue

@@ -193,7 +193,7 @@
 </template>
 
 <script>
-import { getMyMembership, getMembershipPlans, getUpgradeRecords, upgradeMember, createOrder, getCouponList, requestVirtualPayment, getJoinCouponRules } from '../../utils/api.js'
+import { getMyMembership, getMembershipPlans, getUpgradeRecords, upgradeMember, createOrder, getCouponList, requestVirtualPayment, nextOrder, getJoinCouponRules } from '../../utils/api.js'
 
 export default {
   data() {
@@ -391,26 +391,8 @@ export default {
       createOrder(self.selectedLevel, 'pay', self.selectedPeriod).then(function(res) {
         var data = res.data || {}
         if (data.orderInfo && data.sign) {
-          // 真实模式:内联返回 VirtualPayParamsDTO → 拉起虚拟支付
-          requestVirtualPayment(data, {
-            success: function() {
-              uni.showToast({ title: '支付成功,开通中…', icon: 'none' })
-              self.pollOrderStatus()
-            },
-            fail: function(err) {
-              console.error('[会员支付] requestVirtualPayment 失败', err)
-              var errCode = err && err.errCode
-              var msg = ''
-              if (errCode === -15007 || errCode === -15005) msg = '登录态已过期,请重新登录后支付'
-              else if (errCode === -15010) msg = '商品未发布,请联系客服'
-              else if (errCode === -15013) msg = '商品价格异常'
-              else if (errCode === -2) msg = '支付已取消'
-              else if (err && err.errMsg) msg = err.errMsg
-              uni.showToast({ title: msg || '支付未完成', icon: 'none' })
-            }
-          })
+          self.payInstallments(data)
         } else {
-          // testMode/trial:下单接口直接返回订单实体(mock 已开通)→ 沿用旧成功逻辑
           uni.showToast({ title: '开通成功', icon: 'success' })
           self.loadData()
         }
@@ -442,6 +424,43 @@ export default {
         })
       }, 2000)
     },
+    payInstallments: function(dto) {
+      var self = this
+      var current = (dto.currentPeriod || 1)
+      var total = (dto.totalPeriods || 1)
+      requestVirtualPayment(dto, {
+        success: function() {
+          if (current < total) {
+            uni.showToast({ title: '第' + current + '期支付成功', icon: 'none' })
+            nextOrder({ orderNo: dto.orderNo }).then(function(res) {
+              var nd = res.data
+              if (nd && nd.done) {
+                self.pollOrderStatus()
+              } else if (nd && nd.orderInfo && nd.sign) {
+                self.payInstallments(nd)
+              } else {
+                self.pollOrderStatus()
+              }
+            }).catch(function() {
+              self.pollOrderStatus()
+            })
+          } else {
+            uni.showToast({ title: '支付成功,开通中…', icon: 'none' })
+            self.pollOrderStatus()
+          }
+        },
+        fail: function(err) {
+          var errCode = err && err.errCode
+          var msg = ''
+          if (errCode === -15007 || errCode === -15005) msg = '登录态已过期,请重新登录后支付'
+          else if (errCode === -15010) msg = '商品未发布,请联系客服'
+          else if (errCode === -15013) msg = '商品价格异常'
+          else if (errCode === -2) msg = '支付已取消'
+          else if (err && err.errMsg) msg = err.errMsg
+          uni.showToast({ title: msg || '支付未完成', icon: 'none' })
+        }
+      })
+    },
     handleRenew: function() {
       var self = this
       var priceText = '¥' + this.getPriceForPeriod(this.selectedPeriod) + this.getPeriodUnit()
@@ -460,26 +479,8 @@ export default {
       createOrder(self.selectedLevel, 'pay', this.selectedPeriod).then(function(res) {
         var data = res.data || {}
         if (data.orderInfo && data.sign) {
-          // 真实模式:内联返回 VirtualPayParamsDTO → 拉起虚拟支付
-          requestVirtualPayment(data, {
-            success: function() {
-              uni.showToast({ title: '支付成功,续费中…', icon: 'none' })
-              self.pollOrderStatus()
-            },
-            fail: function(err) {
-              console.error('[会员续费] requestVirtualPayment 失败', err)
-              var errCode = err && err.errCode
-              var msg = ''
-              if (errCode === -15007 || errCode === -15005) msg = '登录态已过期,请重新登录后支付'
-              else if (errCode === -15010) msg = '商品未发布,请联系客服'
-              else if (errCode === -15013) msg = '商品价格异常'
-              else if (errCode === -2) msg = '支付已取消'
-              else if (err && err.errMsg) msg = err.errMsg
-              uni.showToast({ title: msg || '支付未完成', icon: 'none' })
-            }
-          })
+          self.payInstallments(data)
         } else {
-          // testMode/trial:下单接口直接返回订单实体(mock 已开通)→ 沿用旧成功逻辑
           uni.showToast({ title: '续费成功', icon: 'success' })
           self.loadData()
         }

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

@@ -334,6 +334,10 @@ export const createOrder = (levelCode, paymentType, period) => {
 	return request('/api/membership/orders', 'POST', data)
 }
 
+export const nextOrder = (orderNo) => {
+	return request('/api/membership/orders/continue', 'POST', { orderNo: orderNo })
+}
+
 export const getAuthorizations = () => {
 	return request('/api/parent/authorizations', 'POST')
 }

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-6eaeb867e786434b33583383e54f81d8b1422b70
+149d13b3ac948c26f304b726ebe4561483ae703d

+ 1 - 1
cfc-web/package.json

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

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

@@ -4,6 +4,26 @@
 
 ---
 
+## v1.0.795 (2026-08-05)
+
+### 新功能
+- 商品统一价停用memberPrice(活动侧保留)
+- 积分兑换优惠券exchangeCoupon+优惠券兑换/查询4端点
+- 新增家庭成员时按POPULATION规则发放人口券(成员有账号发自己,无账号发家庭创建者)
+- PERIODIC会员券每日5:00定时补发任务+listActiveMemberUserIds
+- 会员开通/续费/赠送时发放JOIN会员券(试用除外)
+- CouponService支持三类型核销+幂等发券grant方法与查询(8单测)
+- Coupon实体扩展会员券字段+新增CouponGrantLog实体与Mapper
+- 迁移164 coupon表扩展会员券字段+新建coupon_grant_log发券流水表
+
+### 文档
+- 会员体系重构实现计划(修正apply折扣语义+8个单测)
+- 会员体系重构设计-统一定价+优惠券发放/积分兑换
+
+### Bug 修复
+- 迁移163 为family_challenge添加creator_id列(原迁移158在runMigration100死代码中从未执行)
+
+
 ## v1.0.794 (2026-08-05)
 
 ### Bug 修复

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

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.794
+> 当前版本: v1.0.795
 
 ## 历史版本
 
@@ -8,6 +8,26 @@
 
 ---
 
+## v1.0.795 (2026-08-05)
+
+### 新功能
+- 商品统一价停用memberPrice(活动侧保留)
+- 积分兑换优惠券exchangeCoupon+优惠券兑换/查询4端点
+- 新增家庭成员时按POPULATION规则发放人口券(成员有账号发自己,无账号发家庭创建者)
+- PERIODIC会员券每日5:00定时补发任务+listActiveMemberUserIds
+- 会员开通/续费/赠送时发放JOIN会员券(试用除外)
+- CouponService支持三类型核销+幂等发券grant方法与查询(8单测)
+- Coupon实体扩展会员券字段+新增CouponGrantLog实体与Mapper
+- 迁移164 coupon表扩展会员券字段+新建coupon_grant_log发券流水表
+
+### 文档
+- 会员体系重构实现计划(修正apply折扣语义+8个单测)
+- 会员体系重构设计-统一定价+优惠券发放/积分兑换
+
+### Bug 修复
+- 迁移163 为family_challenge添加creator_id列(原迁移158在runMigration100死代码中从未执行)
+
+
 ## v1.0.794 (2026-08-05)
 
 ### Bug 修复

+ 595 - 0
docs/superpowers/plans/2026-08-04-duplicate-submit-remediation.md

@@ -0,0 +1,595 @@
+# 防重复提交整改方案
+
+> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
+
+**目标:** 修复系统防重复提交机制中的 5 类风险点 —— 补齐数据库唯一约束、统一订单号生成、修复先查后插竞态、补齐前端防重标志、消除弱随机订单号碰撞。
+
+**架构:** 基于现有「前端体验层 + 后端状态机 + 数据库唯一约束」三层防御体系做定向加固,不引入分布式锁/幂等框架等重设施,保持改动最小化:
+
+1. **数据库层:** 为 `guide_orders`、`payment_orders` 补 UNIQUE 约束(最后防线)
+2. **业务层:** 新建统一订单号生成器,替换各 Service 的弱随机订单号;修复 `GrowthRecordService` 先查后插竞态
+3. **前端层:** 为 DAN 测评上传页补 `submitting` 防重标志
+
+**技术栈:** Java 8 + Spring Boot 2.7.18 + MyBatis-Plus / MySQL 8.0 / uni-app Vue 2
+
+---
+
+## 背景:风险点清单(来源:2026-08-04 防重复提交机制分析)
+
+| 编号 | 风险 | 严重度 | 位置 |
+|------|------|:------:|------|
+| R1 | `guide_orders.order_no` 无任何索引,重复下单无兜底 | 🔴 高 | `schema.sql:1427` |
+| R2 | `payment_orders.order_no` 仅普通索引,支付回调重复有风险 | 🟠 中 | `schema.sql:3606` |
+| R3 | 订单号弱随机:`PO+毫秒`、`ORD+毫秒`、`SUB+毫秒` 同毫秒并发必撞 | 🟠 中 | `ProductOrderService:791`、`MembershipService:699`、`SubscriptionController:149`、`MemberSubscriptionService:100` |
+| R4 | `GrowthRecordService` 先查后插,并发下双查双插竞态 | 🟠 中 | `GrowthRecordService:286` |
+| R5 | 前端 DAN 测评上传页无防重标志 | 🟡 低 | `cfc-frontend/pages/dan-assessment/report-upload.vue` |
+
+> **范围外:** 支付回调状态机(`handlePaymentSuccess` 的 `"pending"` 检查)已覆盖微信回调重复场景,无需整改。`WechatService` 的 synchronized 是 token 缓存保护,与防重复无关,不动。
+
+---
+
+## 文件结构
+
+| 文件 | 职责 | 操作 |
+|------|------|------|
+| `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` | 运行时 DDL 迁移唯一入口 | 修改:追加迁移 158、159 |
+| `cfc-backend/src/main/resources/schema.sql` | 建表完整快照 | 修改:同步唯一约束 |
+| `cfc-backend/src/main/java/com/etotem/cfc/common/OrderNoGenerator.java` | 统一订单号生成工具(新建) | 创建 |
+| `cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java` | 商城订单 | 修改:`generateOrderNo()` 改用生成器 |
+| `cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java` | 会员订单 | 修改:`generateOrderNo()` 改用生成器 |
+| `cfc-backend/src/main/java/com/etotem/cfc/service/PackagePurchaseService.java` | 套餐订单 | 修改:`generateOrderNo()` 改用生成器 |
+| `cfc-backend/src/main/java/com/etotem/cfc/service/PackagePaymentService.java` | 套餐支付订单 | 修改:`generateOrderNo()` 改用生成器 |
+| `cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentOrderService.java` | 测评订单 | 修改:`generateOrderNo()` 改用生成器 |
+| `cfc-backend/src/main/java/com/etotem/cfc/controller/subscription/SubscriptionController.java` | 订阅下单(内联生成) | 修改:改用生成器 |
+| `cfc-backend/src/main/java/com/etotem/cfc/service/MemberSubscriptionService.java` | 订阅下单(内联生成) | 修改:改用生成器 |
+| `cfc-backend/src/main/java/com/etotem/cfc/service/GrowthRecordService.java` | 成长记录入库 | 修改:竞态修复 |
+| `cfc-frontend/pages/dan-assessment/report-upload.vue` | DAN 测评报告上传 | 修改:补防重标志 |
+
+---
+
+## 任务 1:迁移 158 —— `guide_orders.order_no` 唯一约束
+
+**文件:**
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java`(runMigrations() 末尾)
+
+- [ ] **步骤 1:确认当前最新迁移编号**
+
+运行:`grep -n "// 迁移" cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java | tail -5`
+预期:最新编号为 `// 迁移157`(另有非编号 `// 迁移20240709`),新迁移从 **158** 开始。
+
+- [ ] **步骤 2:在 runMigrations() 末尾追加迁移 158**
+
+在 `DatabaseInitializer.java` 的 `runMigrations()` 方法末尾(`// 迁移20240709` 块之后)添加:
+
+```java
+// 迁移158: guide_orders表order_no加唯一约束(防重复下单,整改R1)
+try {
+    // 先清理历史重复数据:每组order_no保留id最小的一行
+    jdbcTemplate.execute("DELETE g1 FROM guide_orders g1 INNER JOIN guide_orders g2 " +
+            "ON g1.order_no = g2.order_no AND g1.id > g2.id");
+    log.info("已清理guide_orders重复order_no");
+} catch (Exception e) {
+    // 忽略:无重复数据时删除0行,不报错
+}
+try {
+    jdbcTemplate.execute("ALTER TABLE guide_orders ADD UNIQUE KEY uk_order_no (order_no)");
+    log.info("已为guide_orders.order_no添加唯一约束");
+} catch (Exception e) {
+    // 约束已存在,忽略
+}
+```
+
+- [ ] **步骤 3:编译验证**
+
+运行:`cd cfc-backend && mvn clean compile`
+预期:BUILD SUCCESS
+
+---
+
+## 任务 2:迁移 159 —— `payment_orders.order_no` 升级为唯一约束
+
+**文件:**
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java`(runMigrations() 末尾,任务 1 之后)
+
+- [ ] **步骤 1:在 runMigrations() 末尾追加迁移 159**
+
+```java
+// 迁移159: payment_orders表order_no升级为唯一约束(防支付回调重复,整改R2)
+try {
+    jdbcTemplate.execute("DELETE p1 FROM payment_orders p1 INNER JOIN payment_orders p2 " +
+            "ON p1.order_no = p2.order_no AND p1.id > p2.id");
+    log.info("已清理payment_orders重复order_no");
+} catch (Exception e) {
+    // 忽略
+}
+try {
+    // 先删除原普通索引,再加唯一索引(同名列不可并存)
+    jdbcTemplate.execute("ALTER TABLE payment_orders DROP INDEX idx_order_no");
+    jdbcTemplate.execute("ALTER TABLE payment_orders ADD UNIQUE KEY uk_order_no (order_no)");
+    log.info("已为payment_orders.order_no添加唯一约束");
+} catch (Exception e) {
+    // 索引已存在或已处理,忽略
+}
+```
+
+> **注意:** 必须**先 DROP 原 `idx_order_no` 普通索引**,否则 MySQL 不允许同列同时存在普通索引与唯一索引。若迁移中途失败导致 `idx_order_no` 已删而唯一索引未建,重跑时会因 `uk_order_no` 存在而忽略,DROP 语句幂等性由 catch 保证。
+
+- [ ] **步骤 2:编译验证**
+
+运行:`cd cfc-backend && mvn clean compile`
+预期:BUILD SUCCESS
+
+---
+
+## 任务 3:同步 schema.sql 快照
+
+**文件:**
+- 修改:`cfc-backend/src/main/resources/schema.sql`
+
+- [ ] **步骤 1:更新 `guide_orders` 表定义(第 1427 行附近)**
+
+在 `order_no VARCHAR(50) NOT NULL COMMENT '订单号',` 行后追加唯一键声明,并删除结尾处若有冲突的旧索引行。修改后:
+
+```sql
+CREATE TABLE IF NOT EXISTS guide_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    order_no VARCHAR(50) NOT NULL COMMENT '订单号',
+    ...
+    UNIQUE KEY uk_order_no (order_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师订单表';
+```
+
+- [ ] **步骤 2:更新 `payment_orders` 表定义(第 3591 行附近)**
+
+将 `INDEX idx_order_no (order_no),` 替换为 `UNIQUE KEY uk_order_no (order_no),`:
+
+```sql
+CREATE TABLE IF NOT EXISTS payment_orders (
+    ...
+    order_no VARCHAR(64) NOT NULL COMMENT '订单号',
+    ...
+    UNIQUE KEY uk_order_no (order_no),
+    INDEX idx_family_id (family_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员支付订单';
+```
+
+- [ ] **步骤 3:验证**
+
+运行:`cd cfc-backend && mvn clean compile`
+预期:BUILD SUCCESS(确认 schema.sql 改动未影响编译)
+
+---
+
+## 任务 4:创建统一订单号生成器 `OrderNoGenerator`
+
+**文件:**
+- 创建:`cfc-backend/src/main/java/com/etotem/cfc/common/OrderNoGenerator.java`
+
+- [ ] **步骤 1:创建工具类**
+
+```java
+package com.etotem.cfc.common;
+
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 统一订单号生成器(整改R3)
+ * 格式: 前缀 + 毫秒时间戳 + 自增序号(3位) + UUID短段(4位)
+ * 前缀用于支付回调路由分派(PO=商城/其他=套餐),各业务前缀保持不变
+ */
+public class OrderNoGenerator {
+
+    private static final AtomicLong SEQ = new AtomicLong(0);
+
+    private OrderNoGenerator() {
+    }
+
+    public static String generate(String prefix) {
+        long seq = SEQ.incrementAndGet() % 1000;
+        String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 4).toUpperCase();
+        return prefix + System.currentTimeMillis() + String.format("%03d", seq) + uuid;
+    }
+}
+```
+
+**设计说明:**
+- 同一毫秒内自增序号保证顺序唯一,4 位 UUID 段在进程重启后仍保证随机性,DB 唯一约束兜底最终一致性
+- 前缀参数化,保留 `PO`/`ORD`/`SUB`/`PKG`/`A` 等现有前缀,不影响 `PaymentService.handleWechatNotify` 按前缀路由的逻辑
+
+- [ ] **步骤 2:编译验证**
+
+运行:`cd cfc-backend && mvn clean compile`
+预期:BUILD SUCCESS
+
+---
+
+## 任务 5:替换各 Service 的 `generateOrderNo()`
+
+**文件:**
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java:791`
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java:699`
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/PackagePurchaseService.java:202`
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/PackagePaymentService.java:247`
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentOrderService.java:58`
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/controller/subscription/SubscriptionController.java:149`
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/MemberSubscriptionService.java:100`
+
+- [ ] **步骤 1:替换 ProductOrderService.generateOrderNo()**
+
+将方法体替换为一行委托,并新增 import:
+
+```java
+// 文件顶部新增 import
+import com.etotem.cfc.common.OrderNoGenerator;
+
+// generateOrderNo() 方法体替换为:
+private String generateOrderNo() {
+    return OrderNoGenerator.generate("PO");
+}
+```
+
+- [ ] **步骤 2:替换 MembershipService.generateOrderNo()**
+
+```java
+// 新增 import com.etotem.cfc.common.OrderNoGenerator;
+
+private String generateOrderNo() {
+    return OrderNoGenerator.generate("ORD");
+}
+```
+
+- [ ] **步骤 3:替换 PackagePurchaseService.generateOrderNo()**
+
+```java
+// 新增 import com.etotem.cfc.common.OrderNoGenerator;
+
+private String generateOrderNo() {
+    return OrderNoGenerator.generate("PKG");
+}
+```
+
+- [ ] **步骤 4:替换 PackagePaymentService.generateOrderNo()**
+
+```java
+// 新增 import com.etotem.cfc.common.OrderNoGenerator;
+
+private String generateOrderNo() {
+    return OrderNoGenerator.generate("PKG");
+}
+```
+
+- [ ] **步骤 5:替换 AssessmentOrderService.generateOrderNo()**
+
+```java
+// 新增 import com.etotem.cfc.common.OrderNoGenerator;
+// 若 generateOrderNo() 变为仅一行,可删除原 java.util.Random import(可选)
+
+private String generateOrderNo() {
+    return OrderNoGenerator.generate("A");
+}
+```
+
+- [ ] **步骤 6:替换 SubscriptionController 内联生成(第 149 行)**
+
+原文:`String orderNo = "SUB" + System.currentTimeMillis();`
+改为:
+
+```java
+// 新增 import com.etotem.cfc.common.OrderNoGenerator;
+String orderNo = OrderNoGenerator.generate("SUB");
+```
+
+- [ ] **步骤 7:替换 MemberSubscriptionService 内联生成(第 100 行)**
+
+原文:`String orderNo = "SUB" + System.currentTimeMillis();`
+改为:
+
+```java
+// 新增 import com.etotem.cfc.common.OrderNoGenerator;
+String orderNo = OrderNoGenerator.generate("SUB");
+```
+
+- [ ] **步骤 8:全量编译验证**
+
+运行:`cd cfc-backend && mvn clean compile`
+预期:BUILD SUCCESS。若出现未使用 import 警告(Random),清理对应 import 后重新编译。
+
+> **注意:** 不要改动 `PaymentService.handleWechatNotify` 中按前缀分派路由的逻辑(`PO` 前缀 → 商城订单),订单号前缀保持不变。
+
+---
+
+## 任务 6:修复 `GrowthRecordService` 先查后插竞态
+
+**文件:**
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/GrowthRecordService.java:286`
+
+- [ ] **步骤 1:替换 `checkDuplicateAndInsert()` 方法**
+
+原实现是先 `selectOne` 再 `insert`,并发下两个请求同时通过查询就会双插。改为**直接插入 + 捕获唯一键冲突**,依赖迁移已存在的 `uk_growth_dedup_hash` 唯一索引兜底:
+
+```java
+// 文件顶部新增 import(若未引入)
+import org.springframework.dao.DuplicateKeyException;
+
+private void checkDuplicateAndInsert(GrowthRecord record) {
+    String hash = record.getDedupHash();
+    if (hash != null && !hash.startsWith("NO_DATE:")) {
+        try {
+            growthRecordMapper.insert(record);
+        } catch (DuplicateKeyException e) {
+            // 唯一索引 uk_growth_dedup_hash 兜底:并发重复插入被数据库拦截
+            throw new DuplicateGrowthRecordException("Growth record already exists with hash: " + hash);
+        }
+    } else {
+        growthRecordMapper.insert(record);
+    }
+}
+```
+
+**行为说明:**
+- `AssessmentService:259` 已捕获 `DuplicateGrowthRecordException` 并静默跳过,调用方无需改动
+- 无 hash 或 `NO_DATE:` 前缀的记录仍走原插入路径(不参与去重,行为不变)
+
+- [ ] **步骤 2:编译验证**
+
+运行:`cd cfc-backend && mvn clean compile`
+预期:BUILD SUCCESS
+
+---
+
+## 任务 7:前端 DAN 测评上传页补防重标志
+
+**文件:**
+- 修改:`cfc-frontend/pages/dan-assessment/report-upload.vue`
+
+- [ ] **步骤 1:添加 submitting 数据字段**
+
+在 `data()` 中新增(找到现有 `loading` 字段旁):
+
+```js
+data() {
+  return {
+    // ...现有字段...
+    submitting: false
+  }
+}
+```
+
+- [ ] **步骤 2:提交入口加防重守卫**
+
+找到提交/上传处理方法(含 `self.loading = true` 的异步提交逻辑),在方法开头加:
+
+```js
+submit: function() {
+  if (this.submitting) return   // 防重复点击
+  this.submitting = true
+  // ...现有提交逻辑...
+  // 在请求回调(成功/失败/完成)的 finally 位置:
+  this.submitting = false
+}
+```
+
+具体模式参照 `cfc-frontend/components/feedback-popup.vue:51-71` 的既有写法(`if (this.submitting) return; this.submitting = true; ... .then(function(){ self.submitting = false })`)。
+
+- [ ] **步骤 3:验证前端构建**
+
+运行:`cd cfc-frontend && npm run build:mp-weixin`
+预期:构建成功,无编译错误
+
+---
+
+## 任务 8:整体回归验证
+
+- [ ] **步骤 1:后端全量编译**
+
+运行:`cd cfc-backend && mvn clean compile`
+预期:BUILD SUCCESS
+
+- [ ] **步骤 2:检查路由无冲突(若改动涉及 Controller)**
+
+运行:
+```bash
+grep -rn '@Mapping' cfc-backend/src/main/java/com/etotem/cfc/controller/ | grep -oP '@\w+Mapping\("\K[^"]*' | sort -u | wc -l
+```
+预期:无新增路由,数量与改动前一致
+
+- [ ] **步骤 3:确认迁移幂等性**
+
+检查 `DatabaseInitializer.java` 中迁移 158/159 的 catch 分支:重复执行时 DROP/ADD 语句均应被 catch 吞掉或自然跳过。确认 DELETE 清理语句在无重复数据时执行 0 行不报错。
+
+- [ ] **步骤 4:git 提交(如需)**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java \
+        cfc-backend/src/main/resources/schema.sql \
+        cfc-backend/src/main/java/com/etotem/cfc/common/OrderNoGenerator.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/PackagePurchaseService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/PackagePaymentService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentOrderService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/controller/subscription/SubscriptionController.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/MemberSubscriptionService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/GrowthRecordService.java \
+        cfc-frontend/pages/dan-assessment/report-upload.vue
+git commit -m "fix: 防重复提交整改——补齐唯一约束/统一订单号/修复竞态"
+```
+
+---
+
+## 整改后风险状态(初版)
+
+| 编号 | 风险 | 整改后状态 |
+|------|------|-----------|
+| R1 | guide_orders 无唯一键 | ✅ 迁移 158 补齐 UNIQUE KEY |
+| R2 | payment_orders 仅普通索引 | ✅ 迁移 159 升级为 UNIQUE KEY |
+| R3 | 订单号弱随机碰撞 | ✅ OrderNoGenerator 统一生成(自增序号 + UUID 段) |
+| R4 | GrowthRecordService 竞态 | ✅ 直接插入 + DuplicateKeyException 兜底 |
+| R5 | 前端 DAN 上传无防重 | ✅ submitting 标志位 |
+
+---
+
+## 扩展部分:用户级高频创建页防重(R6)
+
+经用户确认,**仅整改订单类不足以覆盖"按钮连点插入两条"风险** —— 用户级创建页(任务、心愿、家庭成员、保险方案等)同样需要防重。已有 `submitting` 守卫的页面(如 `pages/wishes/create.vue`、`pages/wealth-sub/insurance-add.vue`)无需改动;本任务覆盖**经确认无 submitting 守卫的页面**。
+
+### 已确认无 submitting 守卫的用户级创建页
+
+| 文件 | 场景 | 按钮位置 |
+|------|------|----------|
+| `cfc-frontend/pages/tasks/create-task.vue` | 创建任务(连点多孩会双倍创建) | 第 78 行 `<button @click="submit">` |
+| `cfc-frontend/pages/family/add-member.vue` | 添加家庭成员 | submit 方法 |
+| `cfc-frontend/pages/assessment/apply.vue` | 测评申请(创建订单前) | submit 方法 |
+| `cfc-frontend/pages/assessment/purchase.vue` | 测评购买页 | submit 方法 |
+| `cfc-frontend/pages/guide/packages/create.vue` | 规划师创建套餐 | submit 方法 |
+| `cfc-frontend/pages/guide/register/index.vue` | 规划师注册申请 | submit 方法 |
+
+---
+
+## 任务 9:批量补齐用户级创建页 submitting 守卫
+
+**文件:**
+- 修改:`cfc-frontend/pages/tasks/create-task.vue`
+- 修改:`cfc-frontend/pages/family/add-member.vue`
+- 修改:`cfc-frontend/pages/assessment/apply.vue`
+- 修改:`cfc-frontend/pages/assessment/purchase.vue`
+- 修改:`cfc-frontend/pages/guide/packages/create.vue`
+- 修改:`cfc-frontend/pages/guide/register/index.vue`
+
+**统一改动模板(每个页面):**
+
+- [ ] **步骤 1:`data()` 中新增 `submitting: false`**
+
+```js
+data() {
+  return {
+    // ... 现有字段 ...
+    submitting: false
+  }
+}
+```
+
+- [ ] **步骤 2:`submit` / `submitForm` 方法顶部加守卫**
+
+在方法**最开头**(参数校验之后、网络请求之前)插入:
+
+```js
+async submit() {
+  // ... 现有参数校验 ...
+  if (this.submitting) return      // 重复点击直接拦截
+  this.submitting = true
+  uni.showLoading({ title: '提交中...' })
+  try {
+    // ... 现有请求逻辑 ...
+  } catch (e) {
+    // ... 现有错误处理 ...
+  } finally {
+    this.submitting = false         // 无论成功失败,解锁
+    uni.hideLoading()
+  }
+}
+```
+
+> **注意:** 若原方法没有 `try/finally` 结构,需将现有请求逻辑包裹进 `try`,并在 `finally` 中重置 `submitting`。`finally` 是必须的,否则网络失败时按钮永久禁用。
+
+- [ ] **步骤 3:模板按钮加 `:loading` 与 `:disabled`**
+
+将原按钮:
+```html
+<button @click="submit">创建任务</button>
+```
+改为:
+```html
+<button @click="submit" :loading="submitting" :disabled="submitting">创建任务</button>
+```
+
+> **注意:** 微信小程序 `<button>` 的 `:loading` 属性仅在原生 `<button>` 上生效(uni-app 中有效),自定义 `.btn-*` 类样式不影响;`:disabled` 同步防止视觉/物理双击。
+
+- [ ] **步骤 4:逐文件实施**
+
+按以下顺序修改,每改一个文件立即 `cd cfc-frontend && npm run build:mp-weixin` 验证(单个文件改动较小,按文件级验证即可):
+
+| # | 文件 | 重点 |
+|---|------|------|
+| 1 | `pages/tasks/create-task.vue` | `submit` 在第 259 行,含循环 `for (memberId)` 多孩创建,守卫至关重要 |
+| 2 | `pages/family/add-member.vue` | 验证 submit 方法名(可能是 `submit` 或 `submitForm`) |
+| 3 | `pages/assessment/apply.vue` | 测评申请会创建订单,需配合后端订单号唯一性 |
+| 4 | `pages/assessment/purchase.vue` | 测评购买页(可能涉及支付,与任务 5 联动) |
+| 5 | `pages/guide/packages/create.vue` | 规划师创建套餐 |
+| 6 | `pages/guide/register/index.vue` | 规划师入驻申请 |
+
+- [ ] **步骤 5:前端构建验证**
+
+运行:`cd cfc-frontend && npm run build:mp-weixin`
+预期:构建成功,6 个文件无编译错误
+
+---
+
+## 任务 10:全量回归验证(含扩展)
+
+- [ ] **步骤 1:后端编译**
+
+运行:`cd cfc-backend && mvn clean compile`
+预期:BUILD SUCCESS
+
+- [ ] **步骤 2:前端构建**
+
+运行:`cd cfc-frontend && npm run build:mp-weixin`
+预期:构建成功
+
+- [ ] **步骤 3:路由冲突检查**
+
+运行:
+```bash
+grep -rn '@Mapping' cfc-backend/src/main/java/com/etotem/cfc/controller/ | grep -oP '@\w+Mapping\("\K[^"]*' | sort -u | wc -l
+```
+预期:数量与改动前一致(本次不新增 Controller 路由)
+
+- [ ] **步骤 4:迁移幂等性**
+
+- `DatabaseInitializer` 迁移 158/159 的 catch 分支确保重复执行安全
+- `schema.sql` 与建表语句保持同步
+
+- [ ] **步骤 5:git 提交**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java \
+        cfc-backend/src/main/resources/schema.sql \
+        cfc-backend/src/main/java/com/etotem/cfc/common/OrderNoGenerator.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/PackagePurchaseService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/PackagePaymentService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentOrderService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/controller/subscription/SubscriptionController.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/MemberSubscriptionService.java \
+        cfc-backend/src/main/java/com/etotem/cfc/service/GrowthRecordService.java \
+        cfc-frontend/pages/dan-assessment/report-upload.vue \
+        cfc-frontend/pages/tasks/create-task.vue \
+        cfc-frontend/pages/family/add-member.vue \
+        cfc-frontend/pages/assessment/apply.vue \
+        cfc-frontend/pages/assessment/purchase.vue \
+        cfc-frontend/pages/guide/packages/create.vue \
+        cfc-frontend/pages/guide/register/index.vue
+git commit -m "fix: 防重复提交整改——补齐唯一约束/统一订单号/修复竞态/批量前端守卫"
+```
+
+---
+
+## 最终风险状态
+
+| 编号 | 风险 | 整改后状态 |
+|------|------|-----------|
+| R1 | guide_orders 无唯一键 | ✅ 迁移 158 |
+| R2 | payment_orders 仅普通索引 | ✅ 迁移 159 |
+| R3 | 订单号弱随机碰撞 | ✅ OrderNoGenerator |
+| R4 | GrowthRecordService 竞态 | ✅ DuplicateKeyException 兜底 |
+| R5 | DAN 上传页无防重 | ✅ submitting |
+| R6 | 用户级创建页连点 | ✅ 6 个高频页 submitting(任务 9) |
+
+**遗留建议(本次不改,记录备查):**
+1. 系统仍无分布式锁/幂等键表 —— 多实例部署的跨进程幂等需求另立计划
+2. 管理后台(Admin*)的 create 接口未覆盖 —— 通常管理后台连点概率低,但若发现批量创建类漏洞可补强
+3. 部分高频表的业务键 UNIQUE(如 `tasks(creator_id, title, deadline)`)未强制 —— 若业务确认需要"同家庭同标题同日期任务唯一",另立迁移加 UNIQUE;当前接受业务上重复(同一任务可分发多个孩子)

+ 693 - 0
web/articles/family-toolbox.html

@@ -0,0 +1,693 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta name="description" content="家庭会议怎么开才不吵架?三明治沟通法真的有用吗?关系能量账户是什么?三个经过验证的方法,帮你把家庭关系经营好。">
+  <meta name="keywords" content="家庭会议,三明治沟通法,关系能量账户,家庭沟通,亲子沟通,夫妻沟通,家庭关系方法">
+  <meta property="og:title" content="家庭关系实用工具箱 — 浠艾福成长百科">
+  <meta property="og:description" content="家庭会议、三明治沟通法、关系能量账户——三个经过验证的方法,帮你把家庭关系经营好。">
+  <meta property="og:type" content="article">
+  <meta property="og:url" content="https://www.etotem.com.cn/articles/family-toolbox.html">
+  <meta property="og:image" content="https://www.etotem.com.cn/img/og-action.png">
+  <title>家庭关系实用工具箱 — 浠艾福成长百科</title>
+  <link rel="stylesheet" href="../css/style.css">
+  <style>
+  :root {
+    --accent: #10B981;
+    --accent-light: #d1fae5;
+    --accent-dark: #047857;
+    --blue: #3498db;
+    --blue-light: #dbeafe;
+    --green: #27ae60;
+    --green-light: #d1fae5;
+    --orange: #e67e22;
+    --purple: #8b5cf6;
+    --purple-light: #ede9fe;
+    --teal: #0d9488;
+    --teal-light: #ccfbf1;
+    --rose: #f43f5e;
+    --rose-light: #ffe4e6;
+    --amber: #f59e0b;
+    --amber-light: #fef3c7;
+    --indigo: #6366f1;
+    --indigo-light: #e0e7ff;
+    --text-primary: #1a202c;
+    --text-secondary: #4a5568;
+    --text-muted: #718096;
+    --bg-main: #f7fafc;
+    --bg-alt: #edf2f7;
+    --shadow-card: 0 4px 20px rgba(0,0,0,0.08);
+    --shadow-hover: 0 8px 30px rgba(0,0,0,0.12);
+    --radius: 12px;
+    --max-width: 900px;
+  }
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    font-family: 'Noto Sans SC', system-ui, -apple-system, sans-serif;
+    background: var(--bg-main, #f7fafc);
+    color: var(--text-primary);
+    line-height: 1.8;
+    font-size: 16px;
+  }
+  .container { max-width: var(--max-width, 900px); margin: 0 auto; padding: 3rem 1.5rem; }
+
+  /* Hero */
+  .hero {
+    background: linear-gradient(135deg, #1a202c 0%, #047857 40%, #10B981 75%, #34d399 100%);
+    color: white;
+    padding: 6rem 1.5rem 4rem;
+    text-align: center;
+    position: relative;
+    overflow: hidden;
+    min-height: 50vh;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .hero::before {
+    content: '';
+    position: absolute;
+    top: 0; left: 0; right: 0; bottom: 0;
+    background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
+    opacity: 0.3;
+  }
+  .hero-content { position: relative; z-index: 1; text-align: center; width: 100%; max-width: 800px; margin: 0 auto; }
+  .hero-badge {
+    display: inline-block;
+    background: rgba(255,255,255,0.15);
+    backdrop-filter: blur(10px);
+    border: 1px solid rgba(255,255,255,0.2);
+    border-radius: 999px;
+    padding: 0.5rem 1.5rem;
+    font-size: 0.9rem;
+    font-weight: 500;
+    margin-bottom: 1.5rem;
+    color: white;
+  }
+  .hero h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 1rem; line-height: 1.3; color: white; }
+  .hero p { font-size: 1.1rem; opacity: 0.9; max-width: 700px; margin: 0 auto 2rem; line-height: 1.8; color: white; }
+
+  .hero-illustration { margin: 2rem auto 0; max-width: 300px; }
+
+  /* TOC */
+  .toc {
+    background: white;
+    border-bottom: 1px solid #e2e8f0;
+    position: sticky;
+    top: 0;
+    z-index: 100;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+  }
+  .toc-inner {
+    max-width: 900px;
+    margin: 0 auto;
+    display: flex;
+    gap: 0.5rem;
+    padding: 0.75rem 1.5rem;
+    overflow-x: auto;
+    -webkit-overflow-scrolling: touch;
+  }
+  .toc-inner a {
+    flex-shrink: 0;
+    text-decoration: none;
+    color: var(--text-secondary);
+    font-size: 0.85rem;
+    font-weight: 500;
+    padding: 0.4rem 0.8rem;
+    border-radius: 6px;
+    transition: all 0.2s;
+  }
+  .toc-inner a:hover { background: var(--accent-light); color: var(--accent); }
+
+  /* Sections */
+  section { padding: 3rem 0; }
+  section:nth-child(even) { background: var(--bg-alt); }
+  h2 {
+    font-size: 1.6rem;
+    font-weight: 700;
+    margin-bottom: 1.5rem;
+    color: var(--text-primary);
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+  }
+  h2 .emoji { font-size: 1.4rem; }
+  h3 {
+    font-size: 1.15rem;
+    font-weight: 600;
+    margin: 1.5rem 0 0.75rem;
+    color: var(--text-primary);
+  }
+  p { margin-bottom: 1rem; color: var(--text-secondary); line-height: 1.9; }
+  strong { color: var(--text-primary); }
+
+  /* Info Cards */
+  .info-card {
+    background: white;
+    border-radius: var(--radius);
+    padding: 1.75rem;
+    margin-bottom: 1.5rem;
+    box-shadow: var(--shadow-card);
+    border-left: 4px solid var(--accent);
+  }
+  .info-card.blue { border-left-color: var(--blue); }
+  .info-card.teal { border-left-color: var(--teal); }
+  .info-card.purple { border-left-color: var(--purple); }
+  .info-card.rose { border-left-color: var(--rose); }
+  .info-card.amber { border-left-color: var(--amber); }
+  .info-card.indigo { border-left-color: var(--indigo); }
+  .info-card h3 { margin-top: 0; font-size: 1.1rem; }
+  .info-card p:last-child { margin-bottom: 0; }
+
+  /* Highlight Box */
+  .highlight-box {
+    background: var(--accent-light);
+    border-radius: var(--radius);
+    padding: 1.25rem 1.5rem;
+    margin: 1.5rem 0;
+    border-left: 4px solid var(--accent);
+  }
+  .highlight-box.teal { background: var(--teal-light); border-left-color: var(--teal); }
+  .highlight-box.purple { background: var(--purple-light); border-left-color: var(--purple); }
+  .highlight-box.amber { background: var(--amber-light); border-left-color: var(--amber); }
+  .highlight-box p { margin-bottom: 0.5rem; }
+  .highlight-box p:last-child { margin-bottom: 0; }
+
+  /* Quote */
+  .quote-block {
+    text-align: center;
+    padding: 2rem 1.5rem;
+    margin: 2rem 0;
+    font-size: 1.15rem;
+    font-weight: 500;
+    color: var(--accent-dark);
+    border-top: 2px solid var(--accent-light);
+    border-bottom: 2px solid var(--accent-light);
+    line-height: 1.7;
+  }
+  .quote-block::before { content: '\201C'; font-size: 2.5rem; display: block; opacity: 0.3; line-height: 1; }
+  .quote-block::after { content: '\201D'; font-size: 2.5rem; display: block; opacity: 0.3; line-height: 1; margin-top: 0.5rem; }
+
+  /* SVG graphic */
+  .article-graphic {
+    margin: 2rem auto;
+    text-align: center;
+    max-width: 500px;
+  }
+  .article-graphic svg { max-width: 100%; height: auto; }
+  .article-graphic figcaption { font-size: 0.85rem; color: var(--text-muted); margin-top: 0.5rem; }
+
+  /* Tool card */
+  .tool-card {
+    background: white;
+    border-radius: var(--radius);
+    margin-bottom: 2rem;
+    box-shadow: var(--shadow-card);
+    overflow: hidden;
+    border: 1px solid #e2e8f0;
+  }
+  .tool-card .tool-header {
+    padding: 1.25rem 1.5rem;
+    display: flex;
+    align-items: center;
+    gap: 0.75rem;
+    border-bottom: 1px solid #e2e8f0;
+  }
+  .tool-card .tool-icon {
+    width: 2.5rem;
+    height: 2.5rem;
+    border-radius: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 1.3rem;
+    flex-shrink: 0;
+  }
+  .tool-card .tool-icon.green { background: var(--accent-light); }
+  .tool-card .tool-icon.blue { background: var(--blue-light); }
+  .tool-card .tool-icon.purple { background: var(--purple-light); }
+  .tool-card .tool-icon.amber { background: var(--amber-light); }
+  .tool-card .tool-icon.rose { background: var(--rose-light); }
+  .tool-card .tool-icon.indigo { background: var(--indigo-light); }
+  .tool-card .tool-name { font-size: 1.1rem; font-weight: 700; }
+  .tool-card .tool-body { padding: 1.5rem; }
+
+  /* Table */
+  .styled-table {
+    width: 100%;
+    border-collapse: separate;
+    border-spacing: 0;
+    margin: 1.5rem 0;
+    border-radius: var(--radius);
+    overflow: hidden;
+    box-shadow: var(--shadow-card);
+  }
+  .styled-table thead th {
+    background: var(--accent);
+    color: white;
+    padding: 0.75rem 1rem;
+    text-align: left;
+    font-weight: 600;
+    font-size: 0.9rem;
+  }
+  .styled-table tbody td {
+    padding: 0.75rem 1rem;
+    border-bottom: 1px solid #e2e8f0;
+    font-size: 0.9rem;
+    color: var(--text-secondary);
+    background: white;
+  }
+  .styled-table tbody tr:last-child td { border-bottom: none; }
+
+  /* Story box */
+  .story-box {
+    background: white;
+    border-radius: var(--radius);
+    padding: 1.5rem;
+    margin: 1.5rem 0;
+    box-shadow: var(--shadow-card);
+    border: 1px solid #e2e8f0;
+    position: relative;
+  }
+  .story-box::before {
+    content: '\201C';
+    position: absolute;
+    top: 0.5rem;
+    left: 0.75rem;
+    font-size: 3rem;
+    color: var(--accent);
+    opacity: 0.15;
+    line-height: 1;
+  }
+  .story-box .story-label {
+    display: inline-block;
+    font-size: 0.78rem;
+    font-weight: 600;
+    color: var(--accent);
+    background: var(--accent-light);
+    padding: 0.2rem 0.6rem;
+    border-radius: 4px;
+    margin-bottom: 0.75rem;
+  }
+
+  /* CTA */
+  .cta-section {
+    text-align: center;
+    padding: 3rem 1.5rem;
+    background: linear-gradient(135deg, #1a202c, #047857);
+    color: white;
+    border-radius: var(--radius);
+    margin: 2rem 0;
+  }
+  .cta-section h3 { color: white; font-size: 1.5rem; margin-bottom: 1rem; }
+  .cta-section p { color: rgba(255,255,255,0.9); margin-bottom: 1.5rem; }
+  .btn {
+    display: inline-block;
+    padding: 0.75rem 2rem;
+    border-radius: 999px;
+    font-weight: 600;
+    text-decoration: none;
+    transition: all 0.2s;
+    font-size: 0.95rem;
+  }
+  .btn-primary { background: white; color: var(--accent-dark); }
+  .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
+  .btn-outline { background: transparent; color: white; border: 2px solid rgba(255,255,255,0.5); margin-left: 12px; }
+  .btn-outline:hover { border-color: white; transform: translateY(-2px); }
+
+  /* Related */
+  .related-section { margin: 2rem 0; }
+  .related-section h3 { margin-bottom: 1rem; }
+  .related-section ul { list-style: none; padding: 0; }
+  .related-section li { margin-bottom: 0.5rem; }
+  .related-section a { color: var(--accent); text-decoration: none; font-weight: 500; }
+  .related-section a:hover { text-decoration: underline; }
+
+  @media (max-width: 768px) {
+    .hero h1 { font-size: 1.8rem; }
+    .hero p { font-size: 1rem; }
+    .container { padding: 2rem 1rem; }
+    h2 { font-size: 1.3rem; }
+    .toc-inner { padding: 0.5rem 1rem; }
+    .toc-inner a { font-size: 0.78rem; padding: 0.3rem 0.6rem; }
+    .btn-outline { margin-left: 0; margin-top: 0.75rem; }
+  }
+  </style>
+</head>
+<body>
+
+  <!-- Header -->
+  <header class="site-header" id="header">
+    <div class="container header-inner">
+      <a href="../index.html" class="logo">
+        <img src="../logo.png" alt="浠艾福" class="logo-img" width="36" height="36">
+        <span class="logo-text">浠艾福</span>
+        <span class="logo-tag">主动健康</span>
+      </a>
+      <button class="nav-toggle" id="navToggle" aria-label="切换导航"><span></span><span></span><span></span></button>
+      <nav class="nav" id="nav">
+        <ul class="nav-list">
+          <li><a href="../index.html" class="nav-link">首页</a></li>
+          <li><a href="../knowledge.html" class="nav-link">成长百科</a></li>
+        </ul>
+      </nav>
+    </div>
+  </header>
+
+  <!-- Hero -->
+  <section class="hero">
+    <div class="hero-content">
+      <span class="hero-badge">🧰 行 · 实用工具</span>
+      <h1>家庭关系实用工具箱</h1>
+      <p>家庭会议、三明治沟通法、关系能量账户——三个经过验证的方法,不需要你有心理学背景,不需要你有钱有闲。只要你有意愿,明天就能用。</p>
+      <div class="hero-illustration">
+        <svg viewBox="0 0 300 130" xmlns="http://www.w3.org/2000/svg">
+          <!-- Toolbox body -->
+          <rect x="75" y="50" width="150" height="65" rx="8" fill="rgba(255,255,255,0.15)" stroke="rgba(255,255,255,0.4)" stroke-width="2"/>
+          <!-- Handle -->
+          <path d="M110 50 V35 Q110 25 125 25 H175 Q190 25 190 35 V50" fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="3" stroke-linecap="round"/>
+          <!-- Tools inside -->
+          <line x1="100" y1="75" x2="140" y2="75" stroke="rgba(255,255,255,0.5)" stroke-width="3" stroke-linecap="round"/>
+          <line x1="160" y1="75" x2="200" y2="75" stroke="rgba(255,255,255,0.5)" stroke-width="3" stroke-linecap="round"/>
+          <circle cx="120" cy="90" r="8" fill="rgba(255,255,255,0.2)" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>
+          <circle cx="180" cy="90" r="8" fill="rgba(255,255,255,0.2)" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>
+          <text x="150" y="45" text-anchor="middle" fill="white" font-size="11" font-weight="600">家庭关系工具箱</text>
+          <text x="150" y="118" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-size="10">明天就能用</text>
+        </svg>
+      </div>
+    </div>
+  </section>
+
+  <!-- TOC -->
+  <nav class="toc">
+    <div class="toc-inner">
+      <a href="#meeting">家庭会议</a>
+      <a href="#sandwich">三明治沟通法</a>
+      <a href="#account">关系能量账户</a>
+      <a href="#checkup">关系健康体检</a>
+      <a href="#summary">行动卡</a>
+    </div>
+  </nav>
+
+  <div class="container">
+
+    <!-- Tool 1: Family Meeting -->
+    <section id="meeting">
+      <h2><span class="emoji">🏠</span> 工具一:家庭会议制度</h2>
+      <p>这是我在接触过的几百个家庭中,反馈最好、见效最快的方法。不需要任何成本,只需要一家人坐下来,30分钟。</p>
+
+      <div class="tool-card">
+        <div class="tool-header">
+          <div class="tool-icon green">🗓️</div>
+          <span class="tool-name">家庭会议设置</span>
+        </div>
+        <div class="tool-body">
+          <p><strong>频率:</strong>每周一次,30分钟。</p>
+          <p><strong>角色轮值:</strong>每次有主持人、记录员、计时员。孩子可以担任任何角色——你会发现,让一个8岁的孩子当主持人,他比你还认真。</p>
+          <p><strong>核心规则:不批评、不打断、不翻旧账。</strong>不是不能说问题,是说问题的时候不带攻击性。"你这个星期碗都没洗"换成"我希望下周我们能把洗碗的排班调整一下"——效果不一样。</p>
+        </div>
+      </div>
+
+      <h3>四段式议程</h3>
+      <table class="styled-table">
+        <thead>
+          <tr>
+            <th>环节</th>
+            <th>时间</th>
+            <th>内容</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td>🙏 感恩分享</td>
+            <td>5分钟</td>
+            <td>每人说一件这周感谢家人的事("谢谢妈妈周三给我做了我爱吃的红烧肉")</td>
+          </tr>
+          <tr>
+            <td>💬 问题讨论</td>
+            <td>15分钟</td>
+            <td>讨论一个这周最突出的问题,所有人说意见,不打断</td>
+          </tr>
+          <tr>
+            <td>📋 下周计划</td>
+            <td>5分钟</td>
+            <td>重点下周每个人做好一件事</td>
+          </tr>
+          <tr>
+            <td>🎉 开心收尾</td>
+            <td>5分钟</td>
+            <td>一起做一件小事——看一集动画、吃一个冰淇淋、玩一个桌游</td>
+          </tr>
+        </tbody>
+      </table>
+
+      <div class="highlight-box teal">
+        <p><strong>为什么家庭会议有用?</strong> 因为它创造了一个"平等的场域"。在平时的家庭生活中,权力结构是固定的——父母说了算。但在家庭会议上,每个人都有平等的一票。孩子会觉得"我的意见被听到了",伴侣会觉得"我们是一个团队"。</p>
+      </div>
+
+      <div class="article-graphic">
+        <svg viewBox="0 0 480 200" xmlns="http://www.w3.org/2000/svg">
+          <!-- Round table -->
+          <ellipse cx="240" cy="120" rx="150" ry="60" fill="none" stroke="#d1d5db" stroke-width="2" stroke-dasharray="6,4"/>
+          <ellipse cx="240" cy="120" rx="130" ry="50" fill="none" stroke="#e5e7eb" stroke-width="1" stroke-dasharray="4,4"/>
+          <ellipse cx="240" cy="120" rx="30" ry="12" fill="#d1fae5" stroke="#10B981" stroke-width="1.5"/>
+          <text x="240" y="124" text-anchor="middle" fill="#10B981" font-size="9" font-weight="600">平等</text>
+
+          <!-- People around table -->
+          <circle cx="130" cy="80" r="22" fill="#dbeafe" stroke="#93c5fd" stroke-width="1.5"/>
+          <text x="130" y="84" text-anchor="middle" fill="#2563eb" font-size="9" font-weight="600">爸爸</text>
+
+          <circle cx="350" cy="80" r="22" fill="#fce7f3" stroke="#f9a8d4" stroke-width="1.5"/>
+          <text x="350" y="84" text-anchor="middle" fill="#db2777" font-size="9" font-weight="600">妈妈</text>
+
+          <circle cx="100" cy="165" r="18" fill="#fef3c7" stroke="#fcd34d" stroke-width="1.5"/>
+          <text x="100" y="169" text-anchor="middle" fill="#d97706" font-size="8" font-weight="600">大宝</text>
+
+          <circle cx="380" cy="165" r="16" fill="#d1fae5" stroke="#6ee7b7" stroke-width="1.5"/>
+          <text x="380" y="169" text-anchor="middle" fill="#059669" font-size="8" font-weight="600">小宝</text>
+
+          <text x="240" y="20" text-anchor="middle" fill="#4a5568" font-size="12" font-weight="600">家庭会议:每个人都是平等的参与者</text>
+        </svg>
+        <figcaption>家庭会议创造了一个"平等的场域",让每个家庭成员的声音都被听到</figcaption>
+      </div>
+    </section>
+
+    <!-- Tool 2: Sandwich Communication -->
+    <section id="sandwich">
+      <h2><span class="emoji">🥪</span> 工具二:三明治沟通法</h2>
+
+      <p>这是所有方法里最实用、最立竿见影的一个。它不只是一套话术,它是基于一个脑科学事实设计的——<strong>当一个人感受到被攻击时,大脑的"警报系统"(杏仁核)会在几毫秒内启动,理性思考能力瞬间下降。</strong></p>
+      <p>换句话说:你一开口就是批评,对方就听不进去了。这不是他故意的,是生理反应。</p>
+
+      <div class="tool-card">
+        <div class="tool-header">
+          <div class="tool-icon amber">🥪</div>
+          <span class="tool-name">三明治沟通法结构</span>
+        </div>
+        <div class="tool-body">
+          <div class="info-card green">
+            <h3>第一层——肯定(面包片)</h3>
+            <p>先说一句真诚的肯定。注意"真诚"两个字——不要说"你今天真好看"然后转折,那不叫真诚。说一件你真心觉得他做得好的事。</p>
+            <p style="background:var(--accent-light);padding:0.75rem;border-radius:8px;margin-top:0.5rem;"><strong>👉 "你上周主动把客厅收拾了,我回来看到的时候特别感动。"</strong></p>
+          </div>
+
+          <div class="info-card amber">
+            <h3>中间层——建议(肉饼)</h3>
+            <p>再说需要改进的地方。不要用"但是"开头,"但是"一出来,前面的肯定就作废了。用"同时""另外""下次"代替。</p>
+            <p style="background:var(--amber-light);padding:0.75rem;border-radius:8px;margin-top:0.5rem;"><strong>👉 "另外,下次收拾之前能不能先问问我,有些东西我可能还有用?"</strong></p>
+          </div>
+
+          <div class="info-card teal">
+            <h3>第三层——收尾(面包片)</h3>
+            <p>再给一个正向的期待,把对话的方向拉回到"我们一起解决问题"而不是"你犯了错"。</p>
+            <p style="background:var(--teal-light);padding:0.75rem;border-radius:8px;margin-top:0.5rem;"><strong>👉 "不管怎么说,你愿意主动分担家务,我真的很开心。我们一起慢慢找到最合适的方式。"</strong></p>
+          </div>
+        </div>
+      </div>
+
+      <div class="article-graphic">
+        <svg viewBox="0 0 400 180" xmlns="http://www.w3.org/2000/svg">
+          <!-- Top bread -->
+          <rect x="80" y="5" width="240" height="35" rx="10" fill="#fbbf24" stroke="#f59e0b" stroke-width="1.5"/>
+          <text x="200" y="28" text-anchor="middle" fill="#92400e" font-size="12" font-weight="600">🍞 第一层:肯定(面包片)</text>
+          <!-- Patty -->
+          <rect x="90" y="45" width="220" height="35" rx="6" fill="#dc2626" stroke="#b91c1c" stroke-width="1.5"/>
+          <text x="200" y="68" text-anchor="middle" fill="white" font-size="12" font-weight="600">🥩 中间层:建议(肉饼)</text>
+          <!-- Bottom bread -->
+          <rect x="80" y="85" width="240" height="35" rx="10" fill="#fbbf24" stroke="#f59e0b" stroke-width="1.5"/>
+          <text x="200" y="108" text-anchor="middle" fill="#92400e" font-size="12" font-weight="600">🍞 第三层:收尾(面包片)</text>
+          <!-- Arrow -->
+          <path d="M200,125 L200,145" stroke="#10B981" stroke-width="2.5" marker-end="url(#arrow3)"/>
+          <text x="200" y="165" text-anchor="middle" fill="#10B981" font-size="11" font-weight="600">对方愿意靠近你</text>
+          <defs>
+            <marker id="arrow3" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
+              <path d="M0,0 L8,3 L0,6" fill="#10B981"/>
+            </marker>
+          </defs>
+        </svg>
+        <figcaption>三明治沟通法:肯定 → 建议 → 收尾,让对方愿意靠近你</figcaption>
+      </div>
+
+      <div class="story-box">
+        <span class="story-label">真实案例</span>
+        <p>小满的爸爸以前只会说"不准在沙发上跳",结果小满哭着跑进房间关上门。后来他用了三明治沟通法:</p>
+        <p><strong>先肯定:</strong>"宝贝,爸爸刚才太凶了,对不起。其实爸爸知道你跳沙发是因为开心。"</p>
+        <p><strong>再建议:</strong>"但是沙发是用来坐的,不是用来跳的。万一摔下来你会疼,爸爸是怕你受伤。"</p>
+        <p><strong>最后收尾:</strong>"我们一起想个办法好不好?你出一个主意,我也出一个主意,我们找个都同意的。"</p>
+        <p>小满说"那我在地上跳行不行?"爸爸说"行,我们买一个垫子铺在地上。"后来爸爸真的买了蹦床垫,小满每天跳二十分钟。</p>
+        <p>爸爸后来说了一句话:<strong>"我以前觉得'好好说话'没用,现在发现不是没用,是我不会。"</strong></p>
+      </div>
+
+      <div class="quote-block">
+        最好的沟通不是赢了对错,是让对方愿意靠近你。
+      </div>
+    </section>
+
+    <!-- Tool 3: Relationship Energy Account -->
+    <section id="account">
+      <h2><span class="emoji">🏦</span> 工具三:关系能量账户</h2>
+
+      <p>把每个重要的关系想象成一个"情感银行账户"。</p>
+
+      <div class="tool-card">
+        <div class="tool-header">
+          <div class="tool-icon purple">🏦</div>
+          <span class="tool-name">情感银行账户</span>
+        </div>
+        <div class="tool-body">
+          <div class="info-card green">
+            <p><strong>💰 存款 = </strong>肯定、陪伴、倾听、帮助、记住对方的小事</p>
+          </div>
+          <div class="info-card rose">
+            <p><strong>💸 取款 = </strong>批评、忽视、指责、冷战、忘记承诺</p>
+          </div>
+          <div class="highlight-box amber">
+            <p><strong>核心原则:存款必须远大于取款。</strong></p>
+            <p>心理学家约翰·戈特曼通过长期追踪研究发现:健康的关系中,积极互动和消极互动的比例至少是 <strong>5:1</strong>。也就是说,每一次批评、冷战、指责,至少需要五次肯定的互动来抵消。</p>
+          </div>
+        </div>
+      </div>
+
+      <h3>实操方法:每周两分钟</h3>
+      <div class="info-card indigo">
+        <p>每周花两分钟,想一想:这周我给这段关系存了什么款?</p>
+        <p>不一定是什么大事。一个拥抱、一句"你今天辛苦了"、一杯水、一条消息说"今天天气好,想到你了"。这些小额存款,比偶尔一次"大额存款"(比如送贵重礼物)重要得多。</p>
+        <p>反过来,如果你发现自己最近对某个人频繁"取款"——总是批评、总是挑剔、总是冷漠——那是一个信号:这段关系亮红灯了。<strong>先存三笔款,再说话。</strong></p>
+      </div>
+
+      <div class="article-graphic">
+        <svg viewBox="0 0 450 150" xmlns="http://www.w3.org/2000/svg">
+          <!-- Deposit side -->
+          <rect x="10" y="10" width="200" height="90" rx="10" fill="#f0fdf4" stroke="#86efac" stroke-width="2"/>
+          <text x="110" y="40" text-anchor="middle" fill="#16a34a" font-size="14" font-weight="700">💰 存款</text>
+          <text x="110" y="62" text-anchor="middle" fill="#4a5568" font-size="11">拥抱 · 倾听 · 肯定</text>
+          <text x="110" y="82" text-anchor="middle" fill="#4a5568" font-size="11">帮忙 · 记住小事</text>
+
+          <!-- Withdrawal side -->
+          <rect x="240" y="10" width="200" height="90" rx="10" fill="#fef2f2" stroke="#fecaca" stroke-width="2"/>
+          <text x="340" y="40" text-anchor="middle" fill="#dc2626" font-size="14" font-weight="700">💸 取款</text>
+          <text x="340" y="62" text-anchor="middle" fill="#4a5568" font-size="11">批评 · 指责 · 冷战</text>
+          <text x="340" y="82" text-anchor="middle" fill="#4a5568" font-size="11">忽视 · 忘记承诺</text>
+
+          <!-- Balance line -->
+          <text x="220" y="55" text-anchor="middle" fill="#6b7280" font-size="11">vs</text>
+          <text x="220" y="135" text-anchor="middle" fill="#10B981" font-size="12" font-weight="600">健康比例:存款 ≥ 5 : 1 取款</text>
+        </svg>
+        <figcaption>关系能量账户:健康的关系需要至少5:1的存款取款比例</figcaption>
+      </div>
+    </section>
+
+    <!-- Tool 4: Relationship Health Checkup -->
+    <section id="checkup">
+      <h2><span class="emoji">🏥</span> 工具四:关系健康体检</h2>
+
+      <p>温度计是主观的。但如果想更确定性地"看见"一段关系的真实状态,有三个可以量化的体检项,一个礼拜就能做一次。</p>
+
+      <div class="tool-card">
+        <div class="tool-header">
+          <div class="tool-icon blue">📊</div>
+          <span class="tool-name">三项关系健康指标</span>
+        </div>
+        <div class="tool-body">
+          <div class="info-card green">
+            <h3>1️⃣ 5:1 互动比例周记</h3>
+            <p>准备一张纸,左边写"存款",右边写"取款"。这一周里,每次积极互动在左边记一笔,每次消极互动在右边记一笔。周末算一算比例——对照戈特曼的标准:至少5:1。</p>
+          </div>
+          <div class="info-card blue">
+            <h3>2️⃣ 连接请求回应率自查</h3>
+            <p>回想最近一周,伴侣或孩子发出的"连接请求"——一句闲话、一声叹气、一个分享——你回应了几次?关系长久的夫妻,对彼此连接请求的回应率高达86%;走向分开的夫妻,只有33%。</p>
+          </div>
+          <div class="info-card purple">
+            <h3>3️⃣ 归属感评分</h3>
+            <p>让孩子给"我在这个家的归属感"打个分,1到10。不需要解释为什么,只要一个数字。分数低不是孩子的错,是这个家的信号。</p>
+          </div>
+        </div>
+      </div>
+
+      <div class="story-box">
+        <span class="story-label">真实案例</span>
+        <p>老郑和王姐,结婚十五年,孩子上了初中,两个人却活成了"室友"——各睡各的,各刷各的手机,一天的对话加起来不超过十句。</p>
+        <p>老郑先做的,是5:1互动比例周记。第一周记下来,他自己都吓了一跳:积极互动七次,消极互动六次——比例几乎是1:1,远低于5:1的健康线。</p>
+        <p>第二周,他做了一件小事:王姐做饭的时候,他不再坐在客厅等饭,而是进厨房,站在旁边,问一句"今天累不累"。王姐愣了一下,说"还行"。就这么两句话,却是这一周里他们最长的对话。</p>
+        <p>一个月后,比例变成了3:1。距离健康线还有距离,但老郑说了一句让王姐差点掉眼泪的话:<strong>"我才发现,不是我们没感情了,是我一直没在看。"</strong></p>
+        <p>三个月后,孩子悄悄给"家的归属感"打了8分,还说了一句:"最近家里的空气变好了。"</p>
+      </div>
+
+      <div class="quote-block">
+        关系不是突然坏掉的,是慢慢没人看见的。
+      </div>
+    </section>
+
+    <!-- Summary -->
+    <section id="summary">
+      <h2><span class="emoji">📝</span> 行动卡:从今天开始</h2>
+
+      <div class="info-card teal">
+        <h3>行动卡一:这周末开一次家庭会议</h3>
+        <p>约定一个时间,30分钟。让孩子当主持人。按"感恩分享→问题讨论→下周计划→开心收尾"四段式来。核心规则:不批评、不打断、不翻旧账。</p>
+      </div>
+
+      <div class="info-card amber">
+        <h3>行动卡二:明天用一次三明治沟通法</h3>
+        <p>找一个需要跟家人沟通的事,按"肯定→建议→收尾"三层结构来说。注意:第一层要真诚,中间层不要用"但是",最后一层把方向拉回"我们一起"。</p>
+      </div>
+
+      <div class="info-card purple">
+        <h3>行动卡三:做一次关系健康体检</h3>
+        <p>花10分钟,做一次5:1互动比例周记。这一周,你给最重要的关系存了多少款?取了多少钱?比例是多少?</p>
+      </div>
+
+      <div class="cta-section">
+        <h3>🧰 工具已经给你了,用不用,在你</h3>
+        <p>最好的沟通工具,往往不是更会说,而是更会听。最好的关系经营,不是等坏了再修,而是平时就在存钱。</p>
+        <a href="../knowledge.html" class="btn btn-primary">返回成长百科</a>
+        <a href="parent-child-relationship.html" class="btn btn-outline">阅读:亲子关系 →</a>
+      </div>
+    </section>
+
+    <!-- Related -->
+    <section class="related-section">
+      <h3>相关阅读</h3>
+      <ul>
+        <li><a href="parent-child-relationship.html">亲子关系:从控制到陪伴 →</a></li>
+        <li><a href="marriage-relationship.html">夫妻关系:从合伙人到同行者 →</a></li>
+        <li><a href="../knowledge.html">返回成长百科 →</a></li>
+      </ul>
+    </section>
+
+  </div>
+
+  <!-- Footer -->
+  <div id="footer-placeholder"></div>
+  <script src="../js/footer.js"></script>
+
+  <script>
+    document.getElementById('navToggle').addEventListener('click', function() {
+      document.getElementById('nav').classList.toggle('nav-open');
+      this.classList.toggle('active');
+    });
+  </script>
+
+</body>
+</html>

+ 617 - 0
web/articles/marriage-relationship.html

@@ -0,0 +1,617 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta name="description" content="结婚多年,对话慢慢浓缩成了一个"事务清单"——孩子、房贷、燃气费、周末去哪。你们把家庭经营得井井有条,像两个优秀的合伙人。但你有没有感觉,少了什么?">
+  <meta name="keywords" content="夫妻关系,婚姻经营,家庭沟通,婚姻保鲜,夫妻相处,亲密关系,家庭和谐">
+  <meta property="og:title" content="夫妻关系:从合伙人到同行者 — 浠艾福成长百科">
+  <meta property="og:description" content="结婚多年,对话浓缩成了"事务清单"。你们像两个优秀的合伙人,但少了什么?">
+  <meta property="og:type" content="article">
+  <meta property="og:url" content="https://www.etotem.com.cn/articles/marriage-relationship.html">
+  <meta property="og:image" content="https://www.etotem.com.cn/img/og-action.png">
+  <title>夫妻关系:从合伙人到同行者 — 浠艾福成长百科</title>
+  <link rel="stylesheet" href="../css/style.css">
+  <style>
+  :root {
+    --accent: #10B981;
+    --accent-light: #d1fae5;
+    --accent-dark: #047857;
+    --blue: #3498db;
+    --blue-light: #dbeafe;
+    --green: #27ae60;
+    --green-light: #d1fae5;
+    --orange: #e67e22;
+    --purple: #8b5cf6;
+    --purple-light: #ede9fe;
+    --teal: #0d9488;
+    --teal-light: #ccfbf1;
+    --rose: #f43f5e;
+    --rose-light: #ffe4e6;
+    --amber: #f59e0b;
+    --amber-light: #fef3c7;
+    --text-primary: #1a202c;
+    --text-secondary: #4a5568;
+    --text-muted: #718096;
+    --bg-main: #f7fafc;
+    --bg-alt: #edf2f7;
+    --shadow-card: 0 4px 20px rgba(0,0,0,0.08);
+    --shadow-hover: 0 8px 30px rgba(0,0,0,0.12);
+    --radius: 12px;
+    --max-width: 900px;
+  }
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    font-family: 'Noto Sans SC', system-ui, -apple-system, sans-serif;
+    background: var(--bg-main, #f7fafc);
+    color: var(--text-primary);
+    line-height: 1.8;
+    font-size: 16px;
+  }
+  .container { max-width: var(--max-width, 900px); margin: 0 auto; padding: 3rem 1.5rem; }
+
+  /* Hero */
+  .hero {
+    background: linear-gradient(135deg, #064e3b 0%, #0d9488 40%, #10B981 70%, #34d399 100%);
+    color: white;
+    padding: 6rem 1.5rem 4rem;
+    text-align: center;
+    position: relative;
+    overflow: hidden;
+    min-height: 50vh;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .hero::before {
+    content: '';
+    position: absolute;
+    top: 0; left: 0; right: 0; bottom: 0;
+    background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
+    opacity: 0.3;
+  }
+  .hero-content { position: relative; z-index: 1; text-align: center; width: 100%; max-width: 800px; margin: 0 auto; }
+  .hero-badge {
+    display: inline-block;
+    background: rgba(255,255,255,0.15);
+    backdrop-filter: blur(10px);
+    border: 1px solid rgba(255,255,255,0.2);
+    border-radius: 999px;
+    padding: 0.5rem 1.5rem;
+    font-size: 0.9rem;
+    font-weight: 500;
+    margin-bottom: 1.5rem;
+    color: white;
+  }
+  .hero h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 1rem; line-height: 1.3; color: white; }
+  .hero p { font-size: 1.1rem; opacity: 0.9; max-width: 700px; margin: 0 auto 2rem; line-height: 1.8; color: white; }
+
+  .hero-illustration { margin: 2rem auto 0; max-width: 280px; }
+
+  /* TOC */
+  .toc {
+    background: white;
+    border-bottom: 1px solid #e2e8f0;
+    position: sticky;
+    top: 0;
+    z-index: 100;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+  }
+  .toc-inner {
+    max-width: 900px;
+    margin: 0 auto;
+    display: flex;
+    gap: 0.5rem;
+    padding: 0.75rem 1.5rem;
+    overflow-x: auto;
+    -webkit-overflow-scrolling: touch;
+  }
+  .toc-inner a {
+    flex-shrink: 0;
+    text-decoration: none;
+    color: var(--text-secondary);
+    font-size: 0.85rem;
+    font-weight: 500;
+    padding: 0.4rem 0.8rem;
+    border-radius: 6px;
+    transition: all 0.2s;
+  }
+  .toc-inner a:hover { background: var(--accent-light); color: var(--accent); }
+
+  /* Sections */
+  section { padding: 3rem 0; }
+  section:nth-child(even) { background: var(--bg-alt); }
+  h2 {
+    font-size: 1.6rem;
+    font-weight: 700;
+    margin-bottom: 1.5rem;
+    color: var(--text-primary);
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+  }
+  h2 .emoji { font-size: 1.4rem; }
+  h3 {
+    font-size: 1.15rem;
+    font-weight: 600;
+    margin: 1.5rem 0 0.75rem;
+    color: var(--text-primary);
+  }
+  p { margin-bottom: 1rem; color: var(--text-secondary); line-height: 1.9; }
+  strong { color: var(--text-primary); }
+
+  /* Info Cards */
+  .info-card {
+    background: white;
+    border-radius: var(--radius);
+    padding: 1.75rem;
+    margin-bottom: 1.5rem;
+    box-shadow: var(--shadow-card);
+    border-left: 4px solid var(--accent);
+  }
+  .info-card.blue { border-left-color: var(--blue); }
+  .info-card.teal { border-left-color: var(--teal); }
+  .info-card.purple { border-left-color: var(--purple); }
+  .info-card.rose { border-left-color: var(--rose); }
+  .info-card.amber { border-left-color: var(--amber); }
+  .info-card h3 { margin-top: 0; font-size: 1.1rem; }
+  .info-card p:last-child { margin-bottom: 0; }
+
+  /* Highlight Box */
+  .highlight-box {
+    background: var(--accent-light);
+    border-radius: var(--radius);
+    padding: 1.25rem 1.5rem;
+    margin: 1.5rem 0;
+    border-left: 4px solid var(--accent);
+  }
+  .highlight-box.teal { background: var(--teal-light); border-left-color: var(--teal); }
+  .highlight-box.purple { background: var(--purple-light); border-left-color: var(--purple); }
+  .highlight-box.blue { background: var(--blue-light); border-left-color: var(--blue); }
+  .highlight-box.rose { background: var(--rose-light); border-left-color: var(--rose); }
+  .highlight-box.amber { background: var(--amber-light); border-left-color: var(--amber); }
+  .highlight-box p { margin-bottom: 0.5rem; }
+  .highlight-box p:last-child { margin-bottom: 0; }
+
+  /* Quote */
+  .quote-block {
+    text-align: center;
+    padding: 2rem 1.5rem;
+    margin: 2rem 0;
+    font-size: 1.15rem;
+    font-weight: 500;
+    color: var(--accent-dark);
+    border-top: 2px solid var(--accent-light);
+    border-bottom: 2px solid var(--accent-light);
+    line-height: 1.7;
+  }
+  .quote-block::before { content: '\201C'; font-size: 2.5rem; display: block; opacity: 0.3; line-height: 1; }
+  .quote-block::after { content: '\201D'; font-size: 2.5rem; display: block; opacity: 0.3; line-height: 1; margin-top: 0.5rem; }
+
+  /* SVG graphic */
+  .article-graphic {
+    margin: 2rem auto;
+    text-align: center;
+    max-width: 500px;
+  }
+  .article-graphic svg { max-width: 100%; height: auto; }
+  .article-graphic figcaption { font-size: 0.85rem; color: var(--text-muted); margin-top: 0.5rem; }
+
+  /* Story box */
+  .story-box {
+    background: white;
+    border-radius: var(--radius);
+    padding: 1.5rem;
+    margin: 1.5rem 0;
+    box-shadow: var(--shadow-card);
+    border: 1px solid #e2e8f0;
+    position: relative;
+  }
+  .story-box::before {
+    content: '\201C';
+    position: absolute;
+    top: 0.5rem;
+    left: 0.75rem;
+    font-size: 3rem;
+    color: var(--accent);
+    opacity: 0.15;
+    line-height: 1;
+  }
+  .story-box .story-label {
+    display: inline-block;
+    font-size: 0.78rem;
+    font-weight: 600;
+    color: var(--accent);
+    background: var(--accent-light);
+    padding: 0.2rem 0.6rem;
+    border-radius: 4px;
+    margin-bottom: 0.75rem;
+  }
+
+  /* Method card */
+  .method-card {
+    background: white;
+    border-radius: var(--radius);
+    padding: 1.5rem;
+    margin-bottom: 1.5rem;
+    box-shadow: var(--shadow-card);
+    border: 1px solid #e2e8f0;
+  }
+  .method-card .method-num {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    width: 2rem;
+    height: 2rem;
+    border-radius: 50%;
+    background: var(--accent);
+    color: white;
+    font-weight: 700;
+    font-size: 0.9rem;
+    margin-right: 0.5rem;
+    vertical-align: middle;
+  }
+
+  /* Table */
+  .styled-table {
+    width: 100%;
+    border-collapse: separate;
+    border-spacing: 0;
+    margin: 1.5rem 0;
+    border-radius: var(--radius);
+    overflow: hidden;
+    box-shadow: var(--shadow-card);
+  }
+  .styled-table thead th {
+    background: var(--accent);
+    color: white;
+    padding: 0.75rem 1rem;
+    text-align: left;
+    font-weight: 600;
+    font-size: 0.9rem;
+  }
+  .styled-table tbody td {
+    padding: 0.75rem 1rem;
+    border-bottom: 1px solid #e2e8f0;
+    font-size: 0.9rem;
+    color: var(--text-secondary);
+    background: white;
+  }
+  .styled-table tbody tr:last-child td { border-bottom: none; }
+
+  /* CTA */
+  .cta-section {
+    text-align: center;
+    padding: 3rem 1.5rem;
+    background: linear-gradient(135deg, #064e3b, #0d9488);
+    color: white;
+    border-radius: var(--radius);
+    margin: 2rem 0;
+  }
+  .cta-section h3 { color: white; font-size: 1.5rem; margin-bottom: 1rem; }
+  .cta-section p { color: rgba(255,255,255,0.9); margin-bottom: 1.5rem; }
+  .btn {
+    display: inline-block;
+    padding: 0.75rem 2rem;
+    border-radius: 999px;
+    font-weight: 600;
+    text-decoration: none;
+    transition: all 0.2s;
+    font-size: 0.95rem;
+  }
+  .btn-primary { background: white; color: var(--accent-dark); }
+  .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
+  .btn-outline { background: transparent; color: white; border: 2px solid rgba(255,255,255,0.5); margin-left: 12px; }
+  .btn-outline:hover { border-color: white; transform: translateY(-2px); }
+
+  /* Related */
+  .related-section { margin: 2rem 0; }
+  .related-section h3 { margin-bottom: 1rem; }
+  .related-section ul { list-style: none; padding: 0; }
+  .related-section li { margin-bottom: 0.5rem; }
+  .related-section a { color: var(--accent); text-decoration: none; font-weight: 500; }
+  .related-section a:hover { text-decoration: underline; }
+
+  @media (max-width: 768px) {
+    .hero h1 { font-size: 1.8rem; }
+    .hero p { font-size: 1rem; }
+    .container { padding: 2rem 1rem; }
+    h2 { font-size: 1.3rem; }
+    .toc-inner { padding: 0.5rem 1rem; }
+    .toc-inner a { font-size: 0.78rem; padding: 0.3rem 0.6rem; }
+    .btn-outline { margin-left: 0; margin-top: 0.75rem; }
+    .styled-table { font-size: 0.82rem; }
+  }
+  </style>
+</head>
+<body>
+
+  <!-- Header -->
+  <header class="site-header" id="header">
+    <div class="container header-inner">
+      <a href="../index.html" class="logo">
+        <img src="../logo.png" alt="浠艾福" class="logo-img" width="36" height="36">
+        <span class="logo-text">浠艾福</span>
+        <span class="logo-tag">主动健康</span>
+      </a>
+      <button class="nav-toggle" id="navToggle" aria-label="切换导航"><span></span><span></span><span></span></button>
+      <nav class="nav" id="nav">
+        <ul class="nav-list">
+          <li><a href="../index.html" class="nav-link">首页</a></li>
+          <li><a href="../knowledge.html" class="nav-link">成长百科</a></li>
+        </ul>
+      </nav>
+    </div>
+  </header>
+
+  <!-- Hero -->
+  <section class="hero">
+    <div class="hero-content">
+      <span class="hero-badge">🌿 行 · 人际和谐</span>
+      <h1>夫妻关系:从合伙人到同行者</h1>
+      <p>结婚多年,对话慢慢浓缩成了一个"事务清单"——孩子作业、房贷、燃气费、周末去哪。你们把家庭经营得井井有条,像两个优秀的合伙人。但你有没有感觉,少了什么?</p>
+      <div class="hero-illustration">
+        <svg viewBox="0 0 240 120" xmlns="http://www.w3.org/2000/svg">
+          <circle cx="80" cy="45" r="22" fill="rgba(255,255,255,0.2)" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>
+          <circle cx="160" cy="45" r="22" fill="rgba(255,255,255,0.2)" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>
+          <path d="M105 52 Q118 40 135 52" fill="none" stroke="rgba(255,255,255,0.5)" stroke-width="2.5" stroke-linecap="round"/>
+          <line x1="80" y1="67" x2="80" y2="100" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <line x1="160" y1="67" x2="160" y2="100" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <line x1="80" y1="100" x2="68" y2="118" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <line x1="80" y1="100" x2="92" y2="118" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <line x1="160" y1="100" x2="148" y2="118" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <line x1="160" y1="100" x2="172" y2="118" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <text x="120" y="20" text-anchor="middle" fill="white" font-size="12" font-weight="600">同行者</text>
+          <text x="80" y="43" text-anchor="middle" fill="white" font-size="10">他</text>
+          <text x="160" y="43" text-anchor="middle" fill="white" font-size="10">她</text>
+        </svg>
+      </div>
+    </div>
+  </section>
+
+  <!-- TOC -->
+  <nav class="toc">
+    <div class="toc-inner">
+      <a href="#problem">问题</a>
+      <a href="#story">故事</a>
+      <a href="#methods">三个方法</a>
+      <a href="#driving">开车与打球</a>
+      <a href="#fighting">吵架的艺术</a>
+      <a href="#summary">行动卡</a>
+    </div>
+  </nav>
+
+  <div class="container">
+
+    <!-- Section 1: The Problem -->
+    <section id="problem">
+      <h2><span class="emoji">🔍</span> 那个无话可说的夜晚</h2>
+
+      <p>小然结婚八年了。她说有一天晚上,孩子终于睡了,她坐在沙发上刷手机,丈夫坐在另一头看手机。两个人之间隔了大概一米。一个小时,没有说一句话。</p>
+      <p>不是吵架了。不是冷战。就是——<strong>没话说</strong>。</p>
+      <p>她想了一下,上次两个人认真聊天是什么时候。不是聊孩子、不是聊钱、不是聊周末去哪儿。是聊"你最近在想什么"——这种对话。她想了很久,想不起来。</p>
+      <p>这不是小然一个人的故事。这是无数中国家庭的常态。</p>
+
+      <div class="highlight-box rose">
+        <p><strong>结婚多年,对话慢慢浓缩成了一个"事务清单":</strong></p>
+        <p>"孩子作业写完了吗?""这个月的房贷还了吗?""周末去我妈家还是你妈家?""燃气费交了吗?"</p>
+        <p>你看,你们把家庭经营得井井有条,像两个优秀的合伙人——分工明确、执行力强、各司其职。但你有没有感觉,少了什么?</p>
+        <p><strong>少了"我们除了是这个家的CEO和CFO,我们还是什么"这个答案。</strong></p>
+      </div>
+
+      <div class="article-graphic">
+        <svg viewBox="0 0 500 140" xmlns="http://www.w3.org/2000/svg">
+          <rect x="10" y="5" width="230" height="130" rx="10" fill="#fef2f2" stroke="#fecaca" stroke-width="1.5"/>
+          <text x="125" y="35" text-anchor="middle" fill="#dc2626" font-size="13" font-weight="700">合伙人模式</text>
+          <text x="125" y="60" text-anchor="middle" fill="#4a5568" font-size="12">你负责A,我负责B</text>
+          <text x="125" y="80" text-anchor="middle" fill="#4a5568" font-size="12">对话 = 事务清单</text>
+          <text x="125" y="100" text-anchor="middle" fill="#4a5568" font-size="12">高效 · 但无温度</text>
+          <text x="125" y="123" text-anchor="middle" fill="#dc2626" font-size="11" font-weight="600">"我们像一个公司"</text>
+
+          <path d="M255,70 L275,70" stroke="#94a3b8" stroke-width="2" marker-end="url(#arrow2)"/>
+
+          <rect x="290" y="5" width="200" height="130" rx="10" fill="#f0fdf4" stroke="#bbf7d0" stroke-width="1.5"/>
+          <text x="390" y="35" text-anchor="middle" fill="#16a34a" font-size="13" font-weight="700">同行者模式</text>
+          <text x="390" y="60" text-anchor="middle" fill="#4a5568" font-size="12">我想去的地方你也想去</text>
+          <text x="390" y="80" text-anchor="middle" fill="#4a5568" font-size="12">对话 = 分享与好奇</text>
+          <text x="390" y="100" text-anchor="middle" fill="#4a5568" font-size="12">有温度 · 有连接</text>
+          <text x="390" y="123" text-anchor="middle" fill="#16a34a" font-size="11" font-weight="600">"我们是一路人"</text>
+
+          <defs>
+            <marker id="arrow2" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
+              <path d="M0,0 L8,3 L0,6" fill="#94a3b8"/>
+            </marker>
+          </defs>
+        </svg>
+        <figcaption>从合伙人到同行者,不是换一个人,而是换一种相处的逻辑</figcaption>
+      </div>
+    </section>
+
+    <!-- Section 2: The Story -->
+    <section id="story">
+      <h2><span class="emoji">📖</span> 真实故事:小付和小茜的咖啡约会</h2>
+
+      <div class="story-box">
+        <span class="story-label">真实案例</span>
+        <p>小付和小茜结婚十年。有一段时间,他们之间的对话完全被"家庭运营"占据——谁接孩子、谁交水电费、暑假去哪。两个人像一家公司的两个部门经理,关系流畅但毫无温度。</p>
+        <p>转折点是小茜生日那天。小付问她想要什么礼物。她说:<strong>"我想跟你喝杯咖啡,不说话也行。"</strong></p>
+        <p>小付听出了话里的意思。</p>
+        <p>他们约定,每周六送完孩子去补习班之后,在楼下那家咖啡店坐一小时。手机静音。不聊孩子、不聊钱、不聊家务。</p>
+        <p>前两周很难熬。两个人几乎真的没什么话好说——各自发呆,偶尔找话题,聊两句又断了。小付说:<strong>"那时候我才意识到,我们已经太久没有不聊'正事'了。我们连怎么闲聊都不会了。"</strong></p>
+        <p>第三周,小茜试着问了一个问题:"你觉得你现在的状态怎么样?我不是问你累不累,是问你——你快乐吗?"</p>
+        <p>小付愣住了。不是因为这个问题有多深奥。是因为——十年来没有人问过他这个问题。他自己也没问过自己。</p>
+        <p>那天他们聊了三个小时。说起了各自的理想、各自的疲惫、各自那些"算了不说了"的念头。三个月后,他们的咖啡约会成了一种习惯。后来他们又一起报名了一个周末的摄影课。</p>
+        <p>小茜说了一句很朴实的话:<strong>"他没什么变化,我也没什么变化,但我们的关系变了。"</strong></p>
+      </div>
+
+      <div class="quote-block">
+        他不是变了,是我终于知道他本来就是这样的人。而知道这一点,就够了。
+      </div>
+    </section>
+
+    <!-- Section 3: Methods -->
+    <section id="methods">
+      <h2><span class="emoji">🎯</span> 三个方法,从合伙人到同行者</h2>
+
+      <div class="method-card">
+        <h3><span class="method-num">1</span> 每周30分钟"不谈家事"的对话</h3>
+        <p>约定每周一个固定时间——可以是周六送完孩子上兴趣班后的咖啡馆,可以是周日晚上的阳台——30分钟,只聊一件跟家庭事务无关的事。</p>
+        <div class="highlight-box teal">
+          <p><strong>规则很简单:</strong></p>
+          <p>❌ 不聊孩子(表现、作业、兴趣班)</p>
+          <p>❌ 不聊钱(收入、支出、房贷)</p>
+          <p>❌ 不聊家务(谁洗碗、谁打扫)</p>
+          <p>✅ 只聊:你最近在想什么?你最近有什么困惑?你最近有没有什么新的想法?</p>
+        </div>
+        <p>你会发现,当你把"家庭事务"这层硬壳剥掉之后,你们之间还有没有话可以说。开始的时候可能会尴尬——这是正常的,因为你们已经太久没有以"两个独立的个人"的身份对话了。</p>
+        <p><strong>如果不知道聊什么,试试这些问题:</strong></p>
+        <p>"如果接下来一年我们家可以只做一件事,你觉得应该是什么?"</p>
+        <p>"你最近有没有什么特别想做的事?"</p>
+        <p>"你觉得我们现在的生活,跟你十年前想象的有什么不一样?"</p>
+      </div>
+
+      <div class="method-card">
+        <h3><span class="method-num">2</span> 共同创造"第三空间"</h3>
+        <p>什么是"第三空间"?家庭和工作之外,一个只属于你们两个人的精神角落。</p>
+        <p>它不一定是物理空间。它可以是一件你们一起做的事,一个你们共同的兴趣,一个跟孩子无关、跟赚钱无关的项目。</p>
+        <div class="info-card teal">
+          <p><strong>比如:</strong></p>
+          <p>一起学一门课(烹饪课、摄影课、甚至是一门外语)</p>
+          <p>一起种一个阳台花园</p>
+          <p>一起追一部每周更新的剧然后认真讨论</p>
+          <p>一起跑步然后互相打卡</p>
+        </div>
+        <p>有一位丈夫跟我分享过他的体会。他和妻子一起报名了一个周末陶艺班。他说:"每周六下午的两个小时,我们谁都不提孩子。就一起捏泥巴。有时候捏出来的东西很丑,两个人笑到肚子疼。那种感觉像回到了谈恋爱的时候。<strong>不是陶艺多有意思,是我们终于有了一个不用'当父母'的地方。</strong>"</p>
+      </div>
+
+      <div class="method-card">
+        <h3><span class="method-num">3</span> 关系温度计</h3>
+        <p>每月一次,互相给对方的关系温度打分。1到10,10是"非常满意",6是"及格"。</p>
+        <div class="highlight-box blue">
+          <p><strong>规则也很简单:</strong></p>
+          <p>各自打分,然后交换</p>
+          <p>不争论"为什么你打这么低"</p>
+          <p>低于6分的一方需要触发一次深度对话</p>
+          <p>深度对话的目的不是分对错,是互相理解</p>
+        </div>
+        <p>有一个很有意思的现象——很多夫妻打完分才发现,"我以为我们关系挺好的"和"他觉得我们关系出问题了"同时存在。温度计的价值就在于此:它让那些平时没法开口的话,有了一个开口的契机。</p>
+      </div>
+    </section>
+
+    <!-- Section 4: Driving and playing -->
+    <section id="driving">
+      <h2><span class="emoji">🚗</span> 队友是怎么变成对手的:开车与打球</h2>
+
+      <p>说一个很常见的场景。小付家回老家过年,小付开车,小茜坐副驾。导航提示前方右转,小付觉得直行走高架更快,就没听导航,继续直行。十分钟后,高架入口堵了。</p>
+      <p>小茜说了一句:"让你听导航吧。"小付没接话,但车速明显加快了。下一个路口,小茜又说:"前面该变道了。"小付终于忍不住了:<strong>"我自己会开!你能不能别老指挥我?"</strong></p>
+      <p>两个人一路没说话。</p>
+
+      <div class="highlight-box rose">
+        <p><strong>在"共同完成一件事"的场景里,我们很容易把对方当成"对手"而不是"队友"。</strong></p>
+        <p>为什么?因为开车和打球都有"对错"和"输赢"——路线有对错,球有输赢。一旦有了对错和输赢,人就容易进入"评判模式"。</p>
+        <p>可问题是,夫妻之间一起做事,本来不是为了"对错"和"输赢",是为了"一起"。</p>
+      </div>
+
+      <h3>三个约定,让队友不变成对手:</h3>
+
+      <div class="info-card">
+        <p><strong>1️⃣ 把"你"字句换成"我们"字句。</strong> "你走错了"换成"我们是不是走错了?""你接球太用力"换成"我们试试手腕放松一点?"一字之差,从"审判"变成"并肩"。</p>
+      </div>
+      <div class="info-card teal">
+        <p><strong>2️⃣ 允许对方用自己的方式做事。</strong> 副驾可以看导航,但不要替代驾驶员做决定;打球可以提建议,但对方没接住球的时候,先击掌,再说话。</p>
+      </div>
+      <div class="info-card purple">
+        <p><strong>3️⃣ 吵起来之前,先问一句:"我们现在是队友,还是对手?"</strong> 小付后来试过,话到嘴边改成了"你帮我看看,这条路是不是近一些?"小茜愣了一下,认真地帮他查了导航。一场架,就这么消失了。</p>
+      </div>
+
+      <div class="quote-block">
+        队友问"我们怎么走",对手说"你走错了"。同一件事,两种说法,两种人生。
+      </div>
+    </section>
+
+    <!-- Section 5: Fighting -->
+    <section id="fighting">
+      <h2><span class="emoji">⚡</span> 吵架的艺术:两个实用方法</h2>
+
+      <h3>方法一:目标停止法</h3>
+      <div class="info-card rose">
+        <p>在吵架的过程中,任何一方停下来,问出那个问题——<strong>"我们吵这个的目的是什么?"</strong></p>
+        <p>这一问,至少做对了三件事:</p>
+        <p><strong>1. 打破情绪螺旋。</strong> 吵架越吵越凶,是因为情绪脑接管了大脑。提问是最有效的激活理性思考的方式。</p>
+        <p><strong>2. 暴露真正的问题。</strong> 很多时候我们吵的不是表面上那件事。吵的是"你不重视我""你不理解我"。</p>
+        <p><strong>3. 把"对手"变回"队友"。</strong> 当你问出这个问题,你的立场就从"我要赢"变成了"我想搞清楚"。</p>
+      </div>
+      <p>有一次小然试了这个方法,先生沉默了一会儿说:"其实我就是想让你抱抱我。"小然后来跟我说:<strong>"你知道吗,结婚七年,他第一次说这种话。如果他一开始就告诉我他想要一个拥抱,我根本不会跟他吵。"</strong></p>
+
+      <div class="quote-block">
+        吵架的最高境界,不是吵赢,而是在快赢的时候问自己——我到底想要什么?
+      </div>
+
+      <h3>方法二:发言帽</h3>
+      <div class="info-card amber">
+        <p>准备一个随手能拿到的小物件(一支笔、一个勺子、一个遥控器),约定好:<strong>只有拿着它的人可以说话,另一个人只能听。</strong></p>
+        <p>这个方法有四个好处:</p>
+        <p><strong>打断超速反应。</strong> 吵架时大脑在"听到→反驳"的高速循环里。发言帽让你不得不真的听。</p>
+        <p><strong>降低声音。</strong> 说话权不是靠大声抢来的,声音自然就降下来了。</p>
+        <p><strong>保护脆弱的真话。</strong> 说到一半被人打断,那些"算了不说了"的情绪一上来,就再也不会开口了。</p>
+        <p><strong>吵架有了"句号"。</strong> 当笔在两个人之间传递了几轮,没人再接的时候,架就结束了。</p>
+      </div>
+      <p>小然后来跟我说,现在他们家晚餐桌上就放着一支"吵架专用笔"。有一次她儿子拿起来玩,被她一把抢回来——"这个不能乱用,这是爸爸妈妈吵架用的。"话说出口她才觉得好笑,但就是这支笔,让他们家过去三个月的争吵减少了八成。</p>
+    </section>
+
+    <!-- Section 6: Action Cards -->
+    <section id="summary">
+      <h2><span class="emoji">📝</span> 行动卡:从今天开始</h2>
+
+      <div class="info-card teal">
+        <h3>行动卡一:问一个不一样的问题</h3>
+        <p>下次你跟伴侣坐在一起又不知道说什么的时候,试试问一个你们从来没问过的问题:</p>
+        <p>"如果明天我们有一整天完全属于自己,你想做什么?"</p>
+        <p>"你最近有没有一个瞬间,让你觉得'这样也挺好的'?"</p>
+        <p>"你觉得我们之间,有什么事是一直没说透的?"</p>
+        <p>不问就算了。但如果你问了——问完之后,别急着接话。等他自己说完。</p>
+      </div>
+
+      <div class="info-card purple">
+        <h3>行动卡二:做一次关系温度测量</h3>
+        <p>这个月,试着跟伴侣做一次关系温度测量。各自打分,1-10。然后交换。不争论。低于6分就触发一次深度对话。</p>
+      </div>
+
+      <div class="info-card blue">
+        <h3>行动卡三:约一次"不谈家事"的咖啡</h3>
+        <p>这周末,约ta去喝杯咖啡。30分钟,不谈孩子、不谈钱、不谈家务。只聊"你最近在想什么"。</p>
+      </div>
+
+      <div class="cta-section">
+        <h3>🌿 关系不是突然坏掉的,是慢慢没人看见的</h3>
+        <p>他没什么变化,我也没什么变化,但我们的关系变了——因为有人先看见了。</p>
+        <a href="family-toolbox.html" class="btn btn-primary">下一篇:家庭关系实用工具箱 →</a>
+        <a href="parent-child-relationship.html" class="btn btn-outline">上一篇:亲子关系</a>
+      </div>
+    </section>
+
+    <!-- Related -->
+    <section class="related-section">
+      <h3>相关阅读</h3>
+      <ul>
+        <li><a href="parent-child-relationship.html">亲子关系:从控制到陪伴 →</a></li>
+        <li><a href="family-toolbox.html">家庭关系实用工具箱 →</a></li>
+        <li><a href="../knowledge.html">返回成长百科 →</a></li>
+      </ul>
+    </section>
+
+  </div>
+
+  <!-- Footer -->
+  <div id="footer-placeholder"></div>
+  <script src="../js/footer.js"></script>
+
+  <script>
+    document.getElementById('navToggle').addEventListener('click', function() {
+      document.getElementById('nav').classList.toggle('nav-open');
+      this.classList.toggle('active');
+    });
+  </script>
+
+</body>
+</html>

+ 597 - 0
web/articles/parent-child-relationship.html

@@ -0,0 +1,597 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta name="description" content="你以为的爱,其实是焦虑。当78%的孩子觉得父母不关心自己的感受,而100%的家长都认为自己非常关心孩子——问题出在哪?从控制到陪伴,改变亲子关系的三个具体方法。">
+  <meta name="keywords" content="亲子关系,亲子沟通,家庭教育,无条件关注,陪伴成长,亲子互动,儿童心理">
+  <meta property="og:title" content="亲子关系:从控制到陪伴 — 浠艾福成长百科">
+  <meta property="og:description" content="当78%的孩子觉得父母不关心自己的感受,而100%的家长都认为自己非常关心孩子——问题出在哪?">
+  <meta property="og:type" content="article">
+  <meta property="og:url" content="https://www.etotem.com.cn/articles/parent-child-relationship.html">
+  <meta property="og:image" content="https://www.etotem.com.cn/img/og-action.png">
+  <title>亲子关系:从控制到陪伴 — 浠艾福成长百科</title>
+  <link rel="stylesheet" href="../css/style.css">
+  <style>
+  :root {
+    --accent: #10B981;
+    --accent-light: #d1fae5;
+    --accent-dark: #047857;
+    --blue: #3498db;
+    --blue-light: #dbeafe;
+    --green: #27ae60;
+    --green-light: #d1fae5;
+    --orange: #e67e22;
+    --purple: #8b5cf6;
+    --purple-light: #ede9fe;
+    --teal: #0d9488;
+    --teal-light: #ccfbf1;
+    --rose: #f43f5e;
+    --rose-light: #ffe4e6;
+    --text-primary: #1a202c;
+    --text-secondary: #4a5568;
+    --text-muted: #718096;
+    --bg-main: #f7fafc;
+    --bg-alt: #edf2f7;
+    --shadow-card: 0 4px 20px rgba(0,0,0,0.08);
+    --shadow-hover: 0 8px 30px rgba(0,0,0,0.12);
+    --radius: 12px;
+    --max-width: 900px;
+  }
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  body {
+    font-family: 'Noto Sans SC', system-ui, -apple-system, sans-serif;
+    background: var(--bg-main, #f7fafc);
+    color: var(--text-primary);
+    line-height: 1.8;
+    font-size: 16px;
+  }
+  .container { max-width: var(--max-width, 900px); margin: 0 auto; padding: 3rem 1.5rem; }
+
+  /* Hero */
+  .hero {
+    background: linear-gradient(135deg, #064e3b 0%, #047857 30%, #10B981 70%, #34d399 100%);
+    color: white;
+    padding: 6rem 1.5rem 4rem;
+    text-align: center;
+    position: relative;
+    overflow: hidden;
+    min-height: 50vh;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .hero::before {
+    content: '';
+    position: absolute;
+    top: 0; left: 0; right: 0; bottom: 0;
+    background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
+    opacity: 0.3;
+  }
+  .hero-content { position: relative; z-index: 1; text-align: center; width: 100%; max-width: 800px; margin: 0 auto; }
+  .hero-badge {
+    display: inline-block;
+    background: rgba(255,255,255,0.15);
+    backdrop-filter: blur(10px);
+    border: 1px solid rgba(255,255,255,0.2);
+    border-radius: 999px;
+    padding: 0.5rem 1.5rem;
+    font-size: 0.9rem;
+    font-weight: 500;
+    margin-bottom: 1.5rem;
+    color: white;
+  }
+  .hero h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 1rem; line-height: 1.3; color: white; }
+  .hero p { font-size: 1.1rem; opacity: 0.9; max-width: 700px; margin: 0 auto 2rem; line-height: 1.8; color: white; }
+  .hero-stats {
+    display: flex;
+    justify-content: center;
+    gap: 1.5rem;
+    flex-wrap: wrap;
+  }
+  .hero-stat {
+    background: rgba(255,255,255,0.1);
+    backdrop-filter: blur(10px);
+    border: 1px solid rgba(255,255,255,0.15);
+    border-radius: var(--radius);
+    padding: 1rem 1.5rem;
+    text-align: center;
+    min-width: 140px;
+  }
+  .hero-stat .num { font-size: 2rem; font-weight: 700; display: block; color: white; }
+  .hero-stat .label { font-size: 0.8rem; opacity: 0.8; color: white; }
+
+  /* SVG illustration */
+  .hero-illustration {
+    margin: 2rem auto 0;
+    max-width: 300px;
+  }
+
+  /* TOC */
+  .toc {
+    background: white;
+    border-bottom: 1px solid #e2e8f0;
+    position: sticky;
+    top: 0;
+    z-index: 100;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+  }
+  .toc-inner {
+    max-width: 900px;
+    margin: 0 auto;
+    display: flex;
+    gap: 0.5rem;
+    padding: 0.75rem 1.5rem;
+    overflow-x: auto;
+    -webkit-overflow-scrolling: touch;
+  }
+  .toc-inner a {
+    flex-shrink: 0;
+    text-decoration: none;
+    color: var(--text-secondary);
+    font-size: 0.85rem;
+    font-weight: 500;
+    padding: 0.4rem 0.8rem;
+    border-radius: 6px;
+    transition: all 0.2s;
+  }
+  .toc-inner a:hover { background: var(--accent-light); color: var(--accent); }
+
+  /* Sections */
+  section { padding: 3rem 0; }
+  section:nth-child(even) { background: var(--bg-alt); }
+  h2 {
+    font-size: 1.6rem;
+    font-weight: 700;
+    margin-bottom: 1.5rem;
+    color: var(--text-primary);
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+  }
+  h2 .emoji { font-size: 1.4rem; }
+  h3 {
+    font-size: 1.15rem;
+    font-weight: 600;
+    margin: 1.5rem 0 0.75rem;
+    color: var(--text-primary);
+  }
+  p { margin-bottom: 1rem; color: var(--text-secondary); line-height: 1.9; }
+  strong { color: var(--text-primary); }
+
+  /* Info Cards */
+  .info-card {
+    background: white;
+    border-radius: var(--radius);
+    padding: 1.75rem;
+    margin-bottom: 1.5rem;
+    box-shadow: var(--shadow-card);
+    border-left: 4px solid var(--accent);
+  }
+  .info-card.blue { border-left-color: var(--blue); }
+  .info-card.green { border-left-color: var(--green); }
+  .info-card.purple { border-left-color: var(--purple); }
+  .info-card.teal { border-left-color: var(--teal); }
+  .info-card.rose { border-left-color: var(--rose); }
+  .info-card h3 { margin-top: 0; font-size: 1.1rem; }
+  .info-card p:last-child { margin-bottom: 0; }
+
+  /* Highlight Box */
+  .highlight-box {
+    background: var(--accent-light);
+    border-radius: var(--radius);
+    padding: 1.25rem 1.5rem;
+    margin: 1.5rem 0;
+    border-left: 4px solid var(--accent);
+  }
+  .highlight-box.teal { background: var(--teal-light); border-left-color: var(--teal); }
+  .highlight-box.purple { background: var(--purple-light); border-left-color: var(--purple); }
+  .highlight-box.blue { background: var(--blue-light); border-left-color: var(--blue); }
+  .highlight-box.rose { background: var(--rose-light); border-left-color: var(--rose); }
+  .highlight-box p { margin-bottom: 0.5rem; }
+  .highlight-box p:last-child { margin-bottom: 0; }
+
+  /* Quote */
+  .quote-block {
+    text-align: center;
+    padding: 2rem 1.5rem;
+    margin: 2rem 0;
+    font-size: 1.15rem;
+    font-weight: 500;
+    color: var(--accent-dark);
+    border-top: 2px solid var(--accent-light);
+    border-bottom: 2px solid var(--accent-light);
+    line-height: 1.7;
+  }
+  .quote-block::before { content: '\201C'; font-size: 2.5rem; display: block; opacity: 0.3; line-height: 1; }
+  .quote-block::after { content: '\201D'; font-size: 2.5rem; display: block; opacity: 0.3; line-height: 1; margin-top: 0.5rem; }
+
+  /* SVG graphic */
+  .article-graphic {
+    margin: 2rem auto;
+    text-align: center;
+    max-width: 500px;
+  }
+  .article-graphic svg { max-width: 100%; height: auto; }
+  .article-graphic figcaption { font-size: 0.85rem; color: var(--text-muted); margin-top: 0.5rem; }
+
+  /* Step list */
+  .step-list { counter-reset: step; list-style: none; padding: 0; }
+  .step-list li {
+    counter-increment: step;
+    position: relative;
+    padding: 1rem 1rem 1rem 3.5rem;
+    background: white;
+    border-radius: var(--radius);
+    margin-bottom: 1rem;
+    box-shadow: var(--shadow-card);
+    min-height: 60px;
+  }
+  .step-list li::before {
+    content: counter(step);
+    position: absolute;
+    left: 0.75rem;
+    top: 1rem;
+    width: 2rem;
+    height: 2rem;
+    background: var(--accent);
+    color: white;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-weight: 700;
+    font-size: 0.85rem;
+  }
+  .step-list li strong { display: block; margin-bottom: 0.25rem; }
+
+  /* CTA */
+  .cta-section {
+    text-align: center;
+    padding: 3rem 1.5rem;
+    background: linear-gradient(135deg, #064e3b, #047857);
+    color: white;
+    border-radius: var(--radius);
+    margin: 2rem 0;
+  }
+  .cta-section h3 { color: white; font-size: 1.5rem; margin-bottom: 1rem; }
+  .cta-section p { color: rgba(255,255,255,0.9); margin-bottom: 1.5rem; }
+  .btn {
+    display: inline-block;
+    padding: 0.75rem 2rem;
+    border-radius: 999px;
+    font-weight: 600;
+    text-decoration: none;
+    transition: all 0.2s;
+    font-size: 0.95rem;
+  }
+  .btn-primary { background: white; color: var(--accent-dark); }
+  .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
+  .btn-outline { background: transparent; color: white; border: 2px solid rgba(255,255,255,0.5); margin-left: 12px; }
+  .btn-outline:hover { border-color: white; transform: translateY(-2px); }
+
+  /* Related */
+  .related-section { margin: 2rem 0; }
+  .related-section h3 { margin-bottom: 1rem; }
+  .related-section ul { list-style: none; padding: 0; }
+  .related-section li { margin-bottom: 0.5rem; }
+  .related-section a {
+    color: var(--accent);
+    text-decoration: none;
+    font-weight: 500;
+  }
+  .related-section a:hover { text-decoration: underline; }
+
+  /* Story */
+  .story-box {
+    background: white;
+    border-radius: var(--radius);
+    padding: 1.5rem;
+    margin: 1.5rem 0;
+    box-shadow: var(--shadow-card);
+    border: 1px solid #e2e8f0;
+    position: relative;
+  }
+  .story-box::before {
+    content: '\201C';
+    position: absolute;
+    top: 0.5rem;
+    left: 0.75rem;
+    font-size: 3rem;
+    color: var(--accent);
+    opacity: 0.15;
+    line-height: 1;
+  }
+  .story-box .story-label {
+    display: inline-block;
+    font-size: 0.78rem;
+    font-weight: 600;
+    color: var(--accent);
+    background: var(--accent-light);
+    padding: 0.2rem 0.6rem;
+    border-radius: 4px;
+    margin-bottom: 0.75rem;
+  }
+
+  /* Responsive */
+  @media (max-width: 768px) {
+    .hero h1 { font-size: 1.8rem; }
+    .hero p { font-size: 1rem; }
+    .hero-stats { gap: 0.75rem; }
+    .hero-stat { min-width: 100px; padding: 0.75rem 1rem; }
+    .hero-stat .num { font-size: 1.5rem; }
+    .container { padding: 2rem 1rem; }
+    h2 { font-size: 1.3rem; }
+    .toc-inner { padding: 0.5rem 1rem; }
+    .toc-inner a { font-size: 0.78rem; padding: 0.3rem 0.6rem; }
+    .btn-outline { margin-left: 0; margin-top: 0.75rem; }
+  }
+  </style>
+</head>
+<body>
+
+  <!-- Header -->
+  <header class="site-header" id="header">
+    <div class="container header-inner">
+      <a href="../index.html" class="logo">
+        <img src="../logo.png" alt="浠艾福" class="logo-img" width="36" height="36">
+        <span class="logo-text">浠艾福</span>
+        <span class="logo-tag">主动健康</span>
+      </a>
+      <button class="nav-toggle" id="navToggle" aria-label="切换导航"><span></span><span></span><span></span></button>
+      <nav class="nav" id="nav">
+        <ul class="nav-list">
+          <li><a href="../index.html" class="nav-link">首页</a></li>
+          <li><a href="../knowledge.html" class="nav-link">成长百科</a></li>
+        </ul>
+      </nav>
+    </div>
+  </header>
+
+  <!-- Hero -->
+  <section class="hero">
+    <div class="hero-content">
+      <span class="hero-badge">🌿 行 · 人际和谐</span>
+      <h1>亲子关系:从控制到陪伴</h1>
+      <p>你以为的爱,其实是焦虑。当 78% 的孩子觉得"爸妈不关心我的感受",而 100% 的家长都认为自己"非常关心孩子"——这道鸿沟,到底在哪里?</p>
+      <div class="hero-stats">
+        <div class="hero-stat">
+          <span class="num">78%</span>
+          <span class="label">孩子认为父母不关心感受</span>
+        </div>
+        <div class="hero-stat">
+          <span class="num">100%</span>
+          <span class="label">家长认为自己非常关心</span>
+        </div>
+        <div class="hero-stat">
+          <span class="num">2.0 → 7.3</span>
+          <span class="label">归属感评分提升空间</span>
+        </div>
+      </div>
+      <!-- SVG illustration: parent and child -->
+      <div class="hero-illustration">
+        <svg viewBox="0 0 240 140" xmlns="http://www.w3.org/2000/svg">
+          <circle cx="80" cy="50" r="25" fill="rgba(255,255,255,0.2)" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>
+          <circle cx="160" cy="50" r="16" fill="rgba(255,255,255,0.25)" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>
+          <!-- arms reaching -->
+          <path d="M105 58 Q120 40 135 50" fill="none" stroke="rgba(255,255,255,0.5)" stroke-width="2.5" stroke-linecap="round"/>
+          <path d="M105 62 Q120 80 135 65" fill="none" stroke="rgba(255,255,255,0.5)" stroke-width="2.5" stroke-linecap="round"/>
+          <!-- body lines -->
+          <line x1="80" y1="75" x2="80" y2="115" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <line x1="160" y1="66" x2="160" y2="95" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <!-- legs -->
+          <line x1="80" y1="115" x2="65" y2="138" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <line x1="80" y1="115" x2="95" y2="138" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <line x1="160" y1="95" x2="150" y2="120" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <line x1="160" y1="95" x2="170" y2="120" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
+          <text x="80" y="48" text-anchor="middle" fill="white" font-size="11" font-weight="600">家长</text>
+          <text x="160" y="48" text-anchor="middle" fill="white" font-size="10" font-weight="600">孩子</text>
+        </svg>
+      </div>
+    </div>
+  </section>
+
+  <!-- TOC -->
+  <nav class="toc">
+    <div class="toc-inner">
+      <a href="#problem">问题在哪</a>
+      <a href="#story">真实故事</a>
+      <a href="#methods">三个方法</a>
+      <a href="#case">案例</a>
+      <a href="#summary">总结</a>
+    </div>
+  </nav>
+
+  <div class="container">
+
+    <!-- Section 1: The Problem -->
+    <section id="problem">
+      <h2><span class="emoji">🔍</span> 问题出在哪?</h2>
+      <p>你以为是爱,其实是焦虑。</p>
+      <p>有一次,小艾跟我说:"我妈对我特别好,每天给我做三顿饭,接送我上学,给我报最好的补习班。但她从来不问我今天开不开心。"</p>
+      <p>我问他:"那你希望她问什么?"</p>
+      <p>他说:"我希望她能问——你今天有什么想跟我说的?而不是——今天作业写完了没有。"</p>
+      <p>这个场景你太熟悉了。我们管这叫"关心",孩子管这叫"检查"。</p>
+
+      <div class="highlight-box">
+        <p><strong>问题出在我们把"控制"当成了"爱"的等价物。</strong></p>
+        <p>控制背后的驱动力是什么?焦虑。怕孩子输在起跑线上,怕他不努力将来吃苦,怕他走弯路。这些怕,投射到亲子关系里就变成了:你要听我的、按我说的做、别让我失望。</p>
+        <p>孩子感受到的不是爱,是<strong>不信任</strong>。</p>
+      </div>
+
+      <div class="article-graphic">
+        <svg viewBox="0 0 500 160" xmlns="http://www.w3.org/2000/svg">
+          <!-- Control vs Trust diagram -->
+          <rect x="20" y="10" width="200" height="130" rx="12" fill="#fef2f2" stroke="#fecaca" stroke-width="2"/>
+          <text x="120" y="40" text-anchor="middle" fill="#dc2626" font-size="14" font-weight="700">控制模式</text>
+          <text x="120" y="65" text-anchor="middle" fill="#4a5568" font-size="12">"作业写完了吗?"</text>
+          <text x="120" y="85" text-anchor="middle" fill="#4a5568" font-size="12">"听我的准没错"</text>
+          <text x="120" y="105" text-anchor="middle" fill="#4a5568" font-size="12">"别让我失望"</text>
+          <text x="120" y="128" text-anchor="middle" fill="#dc2626" font-size="11" font-weight="600">→ 不信任 · 对抗</text>
+
+          <path d="M240,75 L260,75" stroke="#94a3b8" stroke-width="2" marker-end="url(#arrow)"/>
+
+          <rect x="280" y="10" width="200" height="130" rx="12" fill="#f0fdf4" stroke="#bbf7d0" stroke-width="2"/>
+          <text x="380" y="40" text-anchor="middle" fill="#16a34a" font-size="14" font-weight="700">陪伴模式</text>
+          <text x="380" y="65" text-anchor="middle" fill="#4a5568" font-size="12">"今天有什么想说的?"</text>
+          <text x="380" y="85" text-anchor="middle" fill="#4a5568" font-size="12">"我们一起……"</text>
+          <text x="380" y="105" text-anchor="middle" fill="#4a5568" font-size="12">"你觉得呢?"</text>
+          <text x="380" y="128" text-anchor="middle" fill="#16a34a" font-size="11" font-weight="600">→ 信任 · 连接</text>
+
+          <defs>
+            <marker id="arrow" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
+              <path d="M0,0 L8,3 L0,6" fill="#94a3b8"/>
+            </marker>
+          </defs>
+        </svg>
+        <figcaption>从控制到陪伴,本质是从"我要你做什么"到"我在这里陪你"的转变</figcaption>
+      </div>
+    </section>
+
+    <!-- Section 2: The Story -->
+    <section id="story">
+      <h2><span class="emoji">📖</span> 真实故事:小满和爸爸</h2>
+
+      <div class="story-box">
+        <span class="story-label">真实案例</span>
+        <p>小满 7 岁那年,有一段时间特别喜欢在沙发上跳。爸爸说了几次"不准跳",没用。有一天晚上,爸爸加班回来,看到沙发被跳得歪了,新买的坐垫也滑到了地上。他火一下就上来了——</p>
+        <p style="background:#fef2f2; padding:0.75rem; border-radius:8px; margin:1rem 0;"><strong style="color:#dc2626;">"我跟你说过多少次了?不准在沙发上跳!你是听不懂话吗?"</strong></p>
+        <p>小满愣了一下,眼眶红了,跳下沙发跑进房间,把门关上了。</p>
+        <p>爸爸也气,坐在沙发上刷手机。但他刷了十分钟,觉得哪里不对。</p>
+        <p>他想起了一个方法——"三明治沟通法"。他敲开小满的门,蹲下来,平视她的眼睛:</p>
+        <p style="background:#f0fdf4; padding:0.75rem; border-radius:8px; margin:1rem 0;">
+          <strong style="color:#16a34a;">第一层——肯定:</strong>"宝贝,爸爸刚才太凶了,对不起。其实爸爸知道你跳沙发是因为开心,你不开心的时候不会跳。"<br>
+          <strong style="color:#16a34a;">第二层——建议:</strong>"但是沙发是用来坐的,不是用来跳的。你一跳,沙发下面的木头会断,坐垫会跑。而且万一摔下来,你会疼。爸爸不是不让你开心,是怕你受伤。"<br>
+          <strong style="color:#16a34a;">第三层——收尾:</strong>"我们一起想个办法好不好?你想跳的时候可以怎么跳?你出一个主意,我也出一个主意,我们找个都同意的。"
+        </p>
+        <p>小满说:"那我在地上跳行不行?"</p>
+        <p>爸爸说:"行,我们买一个垫子铺在地上,你在垫子上跳。咱俩说好了。"</p>
+        <p>后来爸爸真的买了一个儿童蹦床垫放在阳台。小满每天放学回来跳二十分钟,爸爸有时候也跟着跳两下。</p>
+        <p>爸爸后来跟我说了一句话:<strong>"我以前觉得'好好说话'没用,现在发现不是没用,是我不会。"</strong></p>
+      </div>
+
+      <div class="quote-block">
+        最好的沟通不是赢了对错,是让对方愿意靠近你。
+      </div>
+    </section>
+
+    <!-- Section 3: Three Methods -->
+    <section id="methods">
+      <h2><span class="emoji">🎯</span> 陪伴的三个具体方法</h2>
+      <p>以下三个方法,不需要你有心理学背景,不需要你有钱有闲。只要你有意愿,明天就能用。</p>
+
+      <h3>方法一:每日15分钟"无条件关注时间"</h3>
+      <div class="info-card">
+        <p><strong>不是</strong>陪写作业(那是监督),<strong>不是</strong>陪上兴趣班(那是接送),也<strong>不是</strong>一边刷手机一边坐在孩子旁边(那是物理共存)。</p>
+        <p>"无条件关注"的意思是:这15分钟里,孩子说了算。他玩什么你就跟他玩什么,他想聊什么你就听什么。你不评判、不指导、不纠正。你只需要做一件事:<strong>专注地看着他,让他感觉到——此刻你是完全属于他的。</strong></p>
+      </div>
+
+      <div class="article-graphic">
+        <svg viewBox="0 0 500 140" xmlns="http://www.w3.org/2000/svg">
+          <rect x="10" y="5" width="145" height="130" rx="10" fill="#fef2f2" stroke="#fecaca" stroke-width="1.5"/>
+          <text x="82" y="35" text-anchor="middle" fill="#dc2626" font-size="11" font-weight="600">❌ 监督</text>
+          <text x="82" y="58" text-anchor="middle" fill="#4a5568" font-size="11">"这题写错了"</text>
+          <text x="82" y="78" text-anchor="middle" fill="#4a5568" font-size="11">"坐直了"</text>
+          <text x="82" y="98" text-anchor="middle" fill="#4a5568" font-size="11">"快点写"</text>
+
+          <rect x="177" y="5" width="145" height="130" rx="10" fill="#fef2f2" stroke="#fecaca" stroke-width="1.5"/>
+          <text x="250" y="35" text-anchor="middle" fill="#dc2626" font-size="11" font-weight="600">❌ 物理共存</text>
+          <text x="250" y="58" text-anchor="middle" fill="#4a5568" font-size="11">刷手机 + 坐在旁边</text>
+          <text x="250" y="78" text-anchor="middle" fill="#4a5568" font-size="11">人在心不在</text>
+
+          <rect x="345" y="5" width="145" height="130" rx="10" fill="#f0fdf4" stroke="#bbf7d0" stroke-width="1.5"/>
+          <text x="417" y="35" text-anchor="middle" fill="#16a34a" font-size="11" font-weight="600">✅ 无条件关注</text>
+          <text x="417" y="58" text-anchor="middle" fill="#4a5568" font-size="11">孩子说了算</text>
+          <text x="417" y="78" text-anchor="middle" fill="#4a5568" font-size="11">不评判不指导</text>
+          <text x="417" y="98" text-anchor="middle" fill="#4a5568" font-size="11">完全属于你</text>
+        </svg>
+        <figcaption>三种"陪伴"的区别——只有一种是真的</figcaption>
+      </div>
+
+      <h3>方法二:角色交换游戏</h3>
+      <div class="info-card teal">
+        <p>每周一次,你当孩子,孩子当家长。让孩子来安排这30分钟做什么、吃什么、几点做什么事。</p>
+        <p>你会发现两件事:第一,孩子模仿你的语气时,你会看到自己在ta眼中的样子——这可能有点扎心。第二,孩子其实远比你以为的更有主见。</p>
+      </div>
+
+      <h3>方法三:愿望清单共创</h3>
+      <div class="info-card purple">
+        <p>跟孩子一起写一份愿望清单。不是"考前三名就给你买什么"这种条件交换,而是"我们一起________"的填空。</p>
+        <p>"我们一起养一盆花""我们一起看一次日出""我们一起做一顿奇怪的晚餐"。重点不是做到什么,是<strong>"一起"</strong>。</p>
+      </div>
+    </section>
+
+    <!-- Section 4: Case Study -->
+    <section id="case">
+      <h2><span class="emoji">💡</span> 深度理解:为什么孩子感受不到你的爱?</h2>
+
+      <p>有一个数据你可能不知道。在一项针对200多个家庭的跟踪研究中,孩子对家庭归属感的平均评分只有2.0分(满分10),而三个月后,通过几个简单的亲子互动调整,这个数字上升到了7.3。</p>
+
+      <p><strong>归属感从2.0到7.3,靠的不是砸钱、不是报班,而是几个日常互动方式的改变。</strong></p>
+
+      <div class="highlight-box blue">
+        <p><strong>判断亲子关系健康度的三把尺子:</strong></p>
+        <p><strong>信任</strong>——孩子敢不敢在你说真话?心里有事,第一个想跟谁说?</p>
+        <p><strong>沟通</strong>——你们之间"废话"多不多?除了作业和成绩,还有没有闲聊?</p>
+        <p><strong>亲近</strong>——孩子愿不愿意靠近你?有难处敢不敢开口找你?</p>
+      </div>
+
+      <p>每周花两分钟,给你和孩子的这段关系打个分——信任__分,沟通__分,亲近__分。三个分数都高的,说明关系健康;有一个分数低的,就是提醒你的——别急,先看见。</p>
+
+      <div class="quote-block">
+        真正的陪伴不是你在孩子身边,而是你让孩子感觉到——你虽然不在身边,但他一伸手,就能抓到你。
+      </div>
+    </section>
+
+    <!-- Section 5: Summary -->
+    <section id="summary">
+      <h2><span class="emoji">📝</span> 行动卡:从今天开始</h2>
+
+      <ol class="step-list">
+        <li>
+          <strong>今晚就做一次"只问不听"的练习</strong>
+          <p>选一个孩子,花十分钟只做一件事:问他问题,然后听他说。规则:你不能给建议、不能评价。只能问和点头。试试问:"然后呢?""那时候你什么感觉?""你现在怎么想的?"</p>
+        </li>
+        <li>
+          <strong>明天开始15分钟无条件关注</strong>
+          <p>设置一个闹钟,每天15分钟。这15分钟里,手机静音,不评判,不指导。让孩子决定这15分钟做什么。</p>
+        </li>
+        <li>
+          <strong>这周末写一份"我们一起"清单</strong>
+          <p>跟孩子一起列出5件你们想一起做的事。不设条件,不设限制。重点不是做到什么,是"一起"。</p>
+        </li>
+      </ol>
+
+      <div class="cta-section">
+        <h3>🌿 关系不是突然坏掉的,是慢慢没人看见的</h3>
+        <p>从今天开始,看见你的孩子。不是为了改变他,而是为了让他知道——你在这里。</p>
+        <a href="../knowledge.html" class="btn btn-primary">返回成长百科</a>
+        <a href="marriage-relationship.html" class="btn btn-outline">下一篇:夫妻关系 →</a>
+      </div>
+    </section>
+
+    <!-- Related -->
+    <section class="related-section">
+      <h3>相关阅读</h3>
+      <ul>
+        <li><a href="marriage-relationship.html">夫妻关系:从合伙人到同行者 →</a></li>
+        <li><a href="family-toolbox.html">家庭关系实用工具箱 →</a></li>
+        <li><a href="../knowledge.html">返回成长百科 →</a></li>
+      </ul>
+    </section>
+
+  </div>
+
+  <!-- Footer -->
+  <div id="footer-placeholder"></div>
+  <script src="../js/footer.js"></script>
+
+  <script>
+    document.getElementById('navToggle').addEventListener('click', function() {
+      document.getElementById('nav').classList.toggle('nav-open');
+      this.classList.toggle('active');
+    });
+  </script>
+
+</body>
+</html>