Forráskód Böngészése

fix(price): product-detail 移除 memberPrice 价格回退

openhands 2 hónapja
szülő
commit
b664b88558
34 módosított fájl, 1925 hozzáadás és 528 törlés
  1. 35 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/wealth/InsurancePlanningController.java
  2. 48 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/InsuranceGapDTO.java
  3. 63 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/MemberCoverageDTO.java
  4. 52 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/PlanningReportDTO.java
  5. 529 0
      cfc-backend/src/main/java/com/etotem/cfc/service/InsurancePlanningService.java
  6. 2 1
      cfc-frontend/AGENTS.md
  7. 1 1
      cfc-frontend/components/FamilyRelationGraph.vue
  8. 1 1
      cfc-frontend/components/HealthTips.vue
  9. 4 3
      cfc-frontend/components/RecommendedFeed.vue
  10. 1 1
      cfc-frontend/components/WisdomTips.vue
  11. 167 122
      cfc-frontend/pages.json
  12. 1 1
      cfc-frontend/pages/body/index.vue
  13. 1 1
      cfc-frontend/pages/discover-detail/product-detail/product-detail.vue
  14. 1 1
      cfc-frontend/pages/discover/index.vue
  15. 19 9
      cfc-frontend/pages/health/gut-flora-detail.vue
  16. 22 14
      cfc-frontend/pages/health/gut-flora-risks-detail.vue
  17. 19 8
      cfc-frontend/pages/health/gut-flora-species-detail.vue
  18. 1 1
      cfc-frontend/pages/index/child-index.vue
  19. 1 1
      cfc-frontend/pages/index/parent-index.vue
  20. 2 2
      cfc-frontend/pages/mind-extra/articles.vue
  21. 2 2
      cfc-frontend/pages/mind/index.vue
  22. 116 116
      cfc-frontend/pages/profile-extra/children.vue
  23. 0 0
      cfc-frontend/pages/profile-extra/coupons.vue
  24. 231 231
      cfc-frontend/pages/profile-extra/create-child.vue
  25. 0 0
      cfc-frontend/pages/profile-extra/edit-child.vue
  26. 0 0
      cfc-frontend/pages/profile-extra/family-members.vue
  27. 0 0
      cfc-frontend/pages/profile-extra/onboarding.vue
  28. 1 1
      cfc-frontend/pages/profile/components/ProfileHeader.vue
  29. 2 2
      cfc-frontend/pages/profile/components/ProfileMenu.vue
  30. 584 0
      cfc-frontend/pages/wealth-sub/insurance-planning.vue
  31. 12 5
      cfc-frontend/pages/wealth/index.vue
  32. 2 2
      cfc-frontend/pages/wisdom/index.vue
  33. 1 1
      cfc-frontend/pages/wisdom/wisdom_temp/index.vue
  34. 4 1
      cfc-frontend/utils/api.js

+ 35 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/wealth/InsurancePlanningController.java

@@ -0,0 +1,35 @@
+package com.etotem.cfc.controller.wealth;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.dto.PlanningReportDTO;
+import com.etotem.cfc.service.InsurancePlanningService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * 家庭保险规划
+ * <p>
+ * 根据家庭成员的身/心/智维度评分 + 已有保单,
+ * 分析覆盖缺口,生成个性化保险规划建议。
+ */
+@Tag(name = "保险规划", description = "家庭保险规划分析")
+@RestController
+@RequestMapping("/api/insurance")
+public class InsurancePlanningController {
+
+    @Resource
+    private InsurancePlanningService insurancePlanningService;
+
+    @Operation(summary = "获取家庭保险规划报告")
+    @PostMapping("/planning")
+    public Result<PlanningReportDTO> getPlanningReport(@RequestAttribute("userId") Long userId) {
+        PlanningReportDTO report = insurancePlanningService.generatePlanningReport(userId);
+        return Result.success(report);
+    }
+}

+ 48 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/InsuranceGapDTO.java

@@ -0,0 +1,48 @@
+package com.etotem.cfc.dto;
+
+import lombok.Data;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 保险覆盖缺口
+ */
+@Data
+public class InsuranceGapDTO implements Serializable {
+
+    private Long memberId;
+
+    private String memberName;
+
+    private String familyRole;
+
+    /** 缺口维度: medical/critical_illness/education/accident/mental_health */
+    private String gapType;
+
+    /** 维度名称 */
+    private String gapTypeName;
+
+    /** 缺口等级: none/basic/adequate/comprehensive */
+    private String coverageLevel;
+
+    /** 相关五维维度 */
+    private String relatedDimension;
+
+    /** 缺口描述 */
+    private String gapDescription;
+
+    /** 建议险种列表 */
+    private List<String> recommendedTypes;
+
+    /** 优先级: high/medium/low */
+    private String priority;
+
+    /** 紧迫度评分 0-100 */
+    private Integer urgencyScore;
+
+    /** 建议保额范围(最小值,元) */
+    private Long recommendedMinCoverage;
+
+    /** 建议保额范围(最大值,元) */
+    private Long recommendedMaxCoverage;
+}

+ 63 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/MemberCoverageDTO.java

@@ -0,0 +1,63 @@
+package com.etotem.cfc.dto;
+
+import lombok.Data;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 成员保险覆盖状态
+ */
+@Data
+public class MemberCoverageDTO implements Serializable {
+
+    private Long memberId;
+
+    /** parent / child */
+    private String memberType;
+
+    /** 姓名 */
+    private String name;
+
+    /** 头像 */
+    private String avatar;
+
+    /** 家庭角色:爸爸/妈妈/爷爷/奶奶 或空 */
+    private String familyRole;
+
+    /** 年龄 */
+    private Integer age;
+
+    /** 性别 */
+    private String gender;
+
+    // ===== 维度评分(0-100) =====
+    private Integer bodyScore;   // 身
+    private Integer mindScore;   // 心
+    private Integer wisdomScore; // 智
+
+    // ===== 已有保单信息 =====
+    /** 已有保单数量 */
+    private Integer policyCount;
+
+    /** 已有保单类型列表 */
+    private List<String> existingPolicyTypes;
+
+    /** 已有保单总保额 */
+    private Long totalSumInsured;
+
+    // ===== 覆盖状态 =====
+    /** 医疗险覆盖状态: none/basic/adequate/comprehensive */
+    private String medicalCoverage;   // 身维度相关
+
+    /** 重疾险覆盖状态 */
+    private String criticalIllnessCoverage;
+
+    /** 教育金险覆盖状态(仅孩子) */
+    private String educationCoverage;
+
+    /** 意外险覆盖状态 */
+    private String accidentCoverage;
+
+    /** 心理健康险覆盖状态 */
+    private String mentalHealthCoverage;  // 心维度相关
+}

+ 52 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/PlanningReportDTO.java

@@ -0,0 +1,52 @@
+package com.etotem.cfc.dto;
+
+import lombok.Data;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 家庭保险规划报告
+ */
+@Data
+public class PlanningReportDTO implements Serializable {
+
+    /** 报告生成时间 */
+    private String generatedAt;
+
+    /** 家庭ID */
+    private Long familyId;
+
+    // ===== 家庭整体数据 =====
+    private Integer familyBodyScore;    // 家庭身维度均值
+    private Integer familyMindScore;     // 家庭心维度均值
+    private Integer familyWisdomScore;   // 家庭智维度均值
+    private Integer familyOverallScore;   // 家庭综合能量
+
+    /** 家庭成员数量 */
+    private Integer memberCount;
+
+    // ===== 成员覆盖列表 =====
+    private List<MemberCoverageDTO> memberCoverages;
+
+    // ===== 覆盖缺口列表 =====
+    private List<InsuranceGapDTO> gaps;
+
+    /** 高优先级缺口数量 */
+    private Integer highPriorityGapCount;
+
+    /** 中优先级缺口数量 */
+    private Integer mediumPriorityGapCount;
+
+    /** 低优先级缺口数量 */
+    private Integer lowPriorityGapCount;
+
+    // ===== 综合建议 =====
+    /** 保险配置评分 (0-100) */
+    private Integer coverageScore;
+
+    /** 整体评估:充足/基本/不足/严重不足 */
+    private String overallStatus;
+
+    /** 整体建议文本 */
+    private String overallRecommendation;
+}

+ 529 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/InsurancePlanningService.java

@@ -0,0 +1,529 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.dto.*;
+import com.etotem.cfc.entity.FamilyMember;
+import com.etotem.cfc.entity.InsurancePolicy;
+import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
+import com.etotem.cfc.mapper.InsurancePolicyMapper;
+import com.etotem.cfc.mapper.UserMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 家庭保险规划服务
+ * <p>
+ * 根据家庭成员的身/心/智维度评分 + 已有保单数据,
+ * 分析覆盖缺口,生成个性化保险规划建议。
+ */
+@Slf4j
+@Service
+public class InsurancePlanningService {
+
+    @Resource
+    private UserMapper userMapper;
+
+    @Resource
+    private EnergyService energyService;
+
+    @Resource
+    private InsurancePolicyMapper insurancePolicyMapper;
+
+    @Resource
+    private FamilyMemberMapper familyMemberMapper;
+
+    private static final DateTimeFormatter DF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    /**
+     * 生成家庭保险规划报告
+     */
+    public PlanningReportDTO generatePlanningReport(Long userId) {
+        User user = userMapper.selectById(userId);
+        if (user == null || user.getFamilyId() == null) {
+            return buildEmptyReport();
+        }
+
+        Long familyId = user.getFamilyId();
+
+        // 1. 获取家庭能量数据
+        EnergySandboxDTO sandbox = energyService.calculateFamilyEnergy(familyId);
+
+        // 2. 获取所有家庭成员的已有保单
+        List<User> familyUsers = userMapper.selectList(
+                new LambdaQueryWrapper<User>().eq(User::getFamilyId, familyId));
+        List<Long> userIds = familyUsers.stream()
+                .map(User::getId)
+                .collect(Collectors.toList());
+
+        List<InsurancePolicy> allPolicies = new ArrayList<>();
+        if (!userIds.isEmpty()) {
+            LambdaQueryWrapper<InsurancePolicy> policyWrapper = new LambdaQueryWrapper<InsurancePolicy>()
+                    .in(InsurancePolicy::getUserId, userIds)
+                    .or()
+                    .in(InsurancePolicy::getChildId,
+                            familyMemberMapper.selectList(
+                                    new LambdaQueryWrapper<FamilyMember>()
+                                            .eq(FamilyMember::getFamilyId, familyId))
+                                    .stream()
+                                    .map(m -> m.getId())
+                                    .collect(Collectors.toList()));
+            allPolicies = insurancePolicyMapper.selectList(policyWrapper);
+        }
+
+        // 3. 构建成员覆盖数据
+        List<MemberCoverageDTO> memberCoverages = buildMemberCoverages(sandbox.getMembers(), allPolicies);
+
+        // 4. 分析覆盖缺口
+        List<InsuranceGapDTO> gaps = analyzeGaps(memberCoverages);
+
+        // 5. 计算综合评分
+        int coverageScore = calcCoverageScore(memberCoverages, gaps);
+        String overallStatus = calcOverallStatus(coverageScore);
+        String overallRecommendation = buildOverallRecommendation(coverageScore, gaps);
+
+        // 6. 统计优先级
+        int high = 0, medium = 0, low = 0;
+        for (InsuranceGapDTO g : gaps) {
+            if ("high".equals(g.getPriority())) high++;
+            else if ("medium".equals(g.getPriority())) medium++;
+            else low++;
+        }
+
+        // 7. 构建报告
+        PlanningReportDTO report = new PlanningReportDTO();
+        report.setGeneratedAt(LocalDate.now().format(DF));
+        report.setFamilyId(familyId);
+        report.setFamilyBodyScore(sandbox.getBodyScore());
+        report.setFamilyMindScore(sandbox.getMindScore());
+        report.setFamilyWisdomScore(sandbox.getWisdomScore());
+        report.setFamilyOverallScore(sandbox.getOverallScore());
+        report.setMemberCount(memberCoverages.size());
+        report.setMemberCoverages(memberCoverages);
+        report.setGaps(gaps);
+        report.setHighPriorityGapCount(high);
+        report.setMediumPriorityGapCount(medium);
+        report.setLowPriorityGapCount(low);
+        report.setCoverageScore(coverageScore);
+        report.setOverallStatus(overallStatus);
+        report.setOverallRecommendation(overallRecommendation);
+
+        return report;
+    }
+
+    // ===== 构建成员覆盖数据 =====
+
+    private List<MemberCoverageDTO> buildMemberCoverages(List<MemberEnergyDTO> members, List<InsurancePolicy> policies) {
+        if (members == null || members.isEmpty()) return new ArrayList<>();
+
+        // 按 memberId 分组保单
+        Map<Long, List<InsurancePolicy>> policyMap = policies.stream()
+                .collect(Collectors.groupingBy(p -> {
+                    if (p.getChildId() != null) return p.getChildId();
+                    return p.getUserId() != null ? -p.getUserId() : 0L;
+                }));
+
+        List<MemberCoverageDTO> result = new ArrayList<>();
+        for (MemberEnergyDTO m : members) {
+            MemberCoverageDTO dto = new MemberCoverageDTO();
+            dto.setMemberId(m.getMemberId());
+            dto.setMemberType(m.getMemberType());
+            dto.setName(m.getName());
+            dto.setAvatar(m.getAvatar());
+            dto.setFamilyRole(m.getFamilyRole());
+            dto.setBodyScore(m.getBodyScore());
+            dto.setMindScore(m.getMindScore());
+            dto.setWisdomScore(m.getWisdomScore());
+
+            // 年龄:从 familyMember 获取
+            try {
+                FamilyMember fm = familyMemberMapper.selectById(m.getMemberId());
+                if (fm != null) {
+                    dto.setAge(fm.getAge());
+                    dto.setGender(fm.getGender());
+                }
+            } catch (Exception e) {
+                log.debug("获取成员年龄失败, memberId={}", m.getMemberId());
+            }
+
+            // 保单数据
+            List<InsurancePolicy> memberPolicies = policyMap.getOrDefault(m.getMemberId(), Collections.emptyList());
+            memberPolicies.addAll(policyMap.getOrDefault(-m.getMemberId(), Collections.emptyList()));
+
+            dto.setPolicyCount(memberPolicies.size());
+            dto.setExistingPolicyTypes(memberPolicies.stream()
+                    .map(InsurancePolicy::getPolicyType)
+                    .filter(t -> t != null && !t.isEmpty())
+                    .distinct()
+                    .collect(Collectors.toList()));
+            long totalSum = memberPolicies.stream()
+                    .filter(p -> p.getSumInsured() != null)
+                    .mapToLong(InsurancePolicy::getSumInsured)
+                    .sum();
+            dto.setTotalSumInsured(totalSum);
+
+            // 覆盖状态分析
+            dto.setMedicalCoverage(calcMedicalCoverage(memberPolicies, m.getBodyScore()));
+            dto.setCriticalIllnessCoverage(calcCriticalIllnessCoverage(memberPolicies, m.getBodyScore()));
+            dto.setEducationCoverage(calcEducationCoverage(memberPolicies, m.getWisdomScore(), "child".equals(m.getMemberType())));
+            dto.setAccidentCoverage(calcAccidentCoverage(memberPolicies));
+            dto.setMentalHealthCoverage(calcMentalHealthCoverage(memberPolicies, m.getMindScore()));
+
+            result.add(dto);
+        }
+        return result;
+    }
+
+    // ===== 覆盖状态计算 =====
+
+    /**
+     * 医疗险覆盖:
+     * - comprehensive: 有医疗险 + bodyScore >= 60
+     * - adequate: 有医疗险 + bodyScore 40-59
+     * - basic: 有医疗险 + bodyScore < 40
+     * - none: 无医疗险
+     */
+    private String calcMedicalCoverage(List<InsurancePolicy> policies, Integer bodyScore) {
+        boolean hasMedical = policies.stream()
+                .anyMatch(p -> "medical".equals(p.getPolicyType()));
+        if (!hasMedical) return "none";
+        if (bodyScore == null) return "adequate";
+        if (bodyScore >= 60) return "comprehensive";
+        if (bodyScore >= 40) return "adequate";
+        return "basic";
+    }
+
+    /**
+     * 重疾险覆盖:
+     * - comprehensive: 有重疾险 + bodyScore >= 50
+     * - adequate: 有重疾险
+     * - none: 无重疾险
+     */
+    private String calcCriticalIllnessCoverage(List<InsurancePolicy> policies, Integer bodyScore) {
+        boolean hasCI = policies.stream()
+                .anyMatch(p -> "critical_illness".equals(p.getPolicyType()) || "life".equals(p.getPolicyType()));
+        if (!hasCI) return "none";
+        if (bodyScore != null && bodyScore >= 50) return "comprehensive";
+        return "adequate";
+    }
+
+    /**
+     * 教育金险覆盖:
+     * - comprehensive: 有教育金险 + wisdomScore >= 60
+     * - adequate: 有教育金险
+     * - none: 无教育金险
+     * 只对 child 类型有意义
+     */
+    private String calcEducationCoverage(List<InsurancePolicy> policies, Integer wisdomScore, boolean isChild) {
+        if (!isChild) return "not_applicable";
+        boolean hasEdu = policies.stream()
+                .anyMatch(p -> "education".equals(p.getPolicyType()));
+        if (!hasEdu) return "none";
+        if (wisdomScore != null && wisdomScore >= 60) return "comprehensive";
+        return "adequate";
+    }
+
+    /**
+     * 意外险覆盖:
+     * - comprehensive: 有意外险
+     * - none: 无意外险
+     */
+    private String calcAccidentCoverage(List<InsurancePolicy> policies) {
+        boolean hasAccident = policies.stream()
+                .anyMatch(p -> "accident".equals(p.getPolicyType()));
+        return hasAccident ? "comprehensive" : "none";
+    }
+
+    /**
+     * 心理健康险覆盖:
+     * - adequate: 有相关险种或 mindScore >= 70
+     * - basic: mindScore 50-69
+     * - none: mindScore < 50 且无专门险种
+     */
+    private String calcMentalHealthCoverage(List<InsurancePolicy> policies, Integer mindScore) {
+        boolean hasMental = policies.stream()
+                .anyMatch(p -> "mental_health".equals(p.getPolicyType()) || "health".equals(p.getPolicyType()));
+        if (mindScore == null) mindScore = 0;
+        if (hasMental) return "adequate";
+        if (mindScore >= 70) return "adequate";
+        if (mindScore >= 50) return "basic";
+        return "none";
+    }
+
+    // ===== 缺口分析 =====
+
+    private List<InsuranceGapDTO> analyzeGaps(List<MemberCoverageDTO> members) {
+        List<InsuranceGapDTO> gaps = new ArrayList<>();
+
+        for (MemberCoverageDTO m : members) {
+            // 1. 医疗险缺口(身维度)
+            InsuranceGapDTO medicalGap = analyzeMedicalGap(m);
+            if (medicalGap != null) gaps.add(medicalGap);
+
+            // 2. 重疾险缺口(身维度严重时)
+            InsuranceGapDTO ciGap = analyzeCriticalIllnessGap(m);
+            if (ciGap != null) gaps.add(ciGap);
+
+            // 3. 心理健康险缺口(心维度)
+            InsuranceGapDTO mentalGap = analyzeMentalHealthGap(m);
+            if (mentalGap != null) gaps.add(mentalGap);
+
+            // 4. 教育金险缺口(仅孩子,智维度)
+            if ("child".equals(m.getMemberType())) {
+                InsuranceGapDTO eduGap = analyzeEducationGap(m);
+                if (eduGap != null) gaps.add(eduGap);
+            }
+
+            // 5. 意外险缺口
+            InsuranceGapDTO accidentGap = analyzeAccidentGap(m);
+            if (accidentGap != null) gaps.add(accidentGap);
+        }
+
+        // 按紧迫度降序排列
+        gaps.sort((a, b) -> b.getUrgencyScore().compareTo(a.getUrgencyScore()));
+        return gaps;
+    }
+
+    private InsuranceGapDTO analyzeMedicalGap(MemberCoverageDTO m) {
+        if ("comprehensive".equals(m.getMedicalCoverage()) || "adequate".equals(m.getMedicalCoverage())) {
+            return null;
+        }
+
+        InsuranceGapDTO gap = new InsuranceGapDTO();
+        gap.setMemberId(m.getMemberId());
+        gap.setMemberName(m.getName());
+        gap.setFamilyRole(m.getFamilyRole());
+        gap.setGapType("medical");
+        gap.setGapTypeName("医疗险");
+        gap.setRelatedDimension("body");
+        gap.setCoverageLevel(m.getMedicalCoverage());
+
+        int bodyScore = m.getBodyScore() != null ? m.getBodyScore() : 50;
+
+        if ("basic".equals(m.getMedicalCoverage())) {
+            gap.setGapDescription(String.format("%s健康评分%d分,已有基础医疗险,建议升级至综合医疗险以覆盖更多门诊和特效药费用",
+                    m.getName(), bodyScore));
+            gap.setPriority("medium");
+            gap.setUrgencyScore(60);
+        } else {
+            // none
+            gap.setGapDescription(String.format("%s健康评分%d分,暂无医疗险。身体是财富的基石,强烈建议尽快配置医疗险,避免高额医疗费用风险",
+                    m.getName(), bodyScore));
+            gap.setPriority("high");
+            gap.setUrgencyScore(80 + (50 - bodyScore) / 2);
+        }
+
+        gap.setRecommendedTypes(Arrays.asList("医疗险", "住院津贴险"));
+        gap.setRecommendedMinCoverage(500000L);
+        gap.setRecommendedMaxCoverage(2000000L);
+
+        return gap;
+    }
+
+    private InsuranceGapDTO analyzeCriticalIllnessGap(MemberCoverageDTO m) {
+        if ("comprehensive".equals(m.getCriticalIllnessCoverage()) || "adequate".equals(m.getCriticalIllnessCoverage())) {
+            return null;
+        }
+
+        if (m.getBodyScore() != null && m.getBodyScore() < 60) {
+            InsuranceGapDTO gap = new InsuranceGapDTO();
+            gap.setMemberId(m.getMemberId());
+            gap.setMemberName(m.getName());
+            gap.setFamilyRole(m.getFamilyRole());
+            gap.setGapType("critical_illness");
+            gap.setGapTypeName("重疾险");
+            gap.setRelatedDimension("body");
+            gap.setCoverageLevel(m.getCriticalIllnessCoverage());
+
+            int bodyScore = m.getBodyScore();
+            gap.setPriority("high");
+            gap.setUrgencyScore(70 + (60 - bodyScore));
+            gap.setGapDescription(String.format("%s健康评分%d分,身体底评分偏低,面临较高重疾风险。建议配置重疾险,一旦确诊即可获赔保险金",
+                    m.getName(), bodyScore));
+            gap.setRecommendedTypes(Arrays.asList("重疾险", "防癌险"));
+            gap.setRecommendedMinCoverage(300000L);
+            gap.setRecommendedMaxCoverage(1000000L);
+
+            return gap;
+        }
+        return null;
+    }
+
+    private InsuranceGapDTO analyzeMentalHealthGap(MemberCoverageDTO m) {
+        String level = m.getMentalHealthCoverage();
+        if ("adequate".equals(level)) return null;
+
+        int mindScore = m.getMindScore() != null ? m.getMindScore() : 50;
+
+        if ("basic".equals(level)) {
+            InsuranceGapDTO gap = new InsuranceGapDTO();
+            gap.setMemberId(m.getMemberId());
+            gap.setMemberName(m.getName());
+            gap.setFamilyRole(m.getFamilyRole());
+            gap.setGapType("mental_health");
+            gap.setGapTypeName("心理健康险");
+            gap.setRelatedDimension("mind");
+            gap.setCoverageLevel(level);
+            gap.setPriority("medium");
+            gap.setUrgencyScore(50);
+            gap.setGapDescription(String.format("%s心理评分%d分,建议关注情绪管理,可考虑附加心理咨询服务的健康险",
+                    m.getName(), mindScore));
+            gap.setRecommendedTypes(Arrays.asList("健康险(含心理咨询)", "EMI保险"));
+            gap.setRecommendedMinCoverage(100000L);
+            gap.setRecommendedMaxCoverage(500000L);
+            return gap;
+        }
+
+        // none
+        if (mindScore < 70) {
+            InsuranceGapDTO gap = new InsuranceGapDTO();
+            gap.setMemberId(m.getMemberId());
+            gap.setMemberName(m.getName());
+            gap.setFamilyRole(m.getFamilyRole());
+            gap.setGapType("mental_health");
+            gap.setGapTypeName("心理健康险");
+            gap.setRelatedDimension("mind");
+            gap.setCoverageLevel(level);
+            gap.setPriority("medium");
+            gap.setUrgencyScore(40 + (70 - mindScore) / 2);
+            gap.setGapDescription(String.format("%s心理评分%d分,情绪健康需要关注。建议配置心理健康相关保障,覆盖心理咨询和情绪疏导费用",
+                    m.getName(), mindScore));
+            gap.setRecommendedTypes(Arrays.asList("健康险(含心理咨询)", "EMI保险"));
+            gap.setRecommendedMinCoverage(50000L);
+            gap.setRecommendedMaxCoverage(300000L);
+            return gap;
+        }
+        return null;
+    }
+
+    private InsuranceGapDTO analyzeEducationGap(MemberCoverageDTO m) {
+        if ("comprehensive".equals(m.getEducationCoverage()) || "adequate".equals(m.getEducationCoverage())
+                || "not_applicable".equals(m.getEducationCoverage())) {
+            return null;
+        }
+
+        int wisdomScore = m.getWisdomScore() != null ? m.getWisdomScore() : 50;
+
+        InsuranceGapDTO gap = new InsuranceGapDTO();
+        gap.setMemberId(m.getMemberId());
+        gap.setMemberName(m.getName());
+        gap.setFamilyRole(m.getFamilyRole());
+        gap.setGapType("education");
+        gap.setGapTypeName("教育金险");
+        gap.setRelatedDimension("wisdom");
+        gap.setCoverageLevel(m.getEducationCoverage());
+        gap.setPriority("medium");
+        gap.setUrgencyScore(50 + (70 - wisdomScore) / 3);
+        gap.setGapDescription(String.format("%s智慧评分%d分,正处于成长关键期。教育金险可强制储备教育资金,专款专用",
+                m.getName(), wisdomScore));
+        gap.setRecommendedTypes(Arrays.asList("教育金险", "婚嫁金险", "创业金险"));
+        gap.setRecommendedMinCoverage(100000L);
+        gap.setRecommendedMaxCoverage(500000L);
+
+        return gap;
+    }
+
+    private InsuranceGapDTO analyzeAccidentGap(MemberCoverageDTO m) {
+        if ("comprehensive".equals(m.getAccidentCoverage())) {
+            return null;
+        }
+
+        InsuranceGapDTO gap = new InsuranceGapDTO();
+        gap.setMemberId(m.getMemberId());
+        gap.setMemberName(m.getName());
+        gap.setFamilyRole(m.getFamilyRole());
+        gap.setGapType("accident");
+        gap.setGapTypeName("意外险");
+        gap.setRelatedDimension("body");
+        gap.setCoverageLevel(m.getAccidentCoverage());
+        gap.setPriority("high");
+        gap.setUrgencyScore(85);
+        gap.setGapDescription(String.format("%s暂无意外险。意外风险无处不在,配置意外险保费低、保障高,是性价比最高的保障险种",
+                m.getName()));
+        gap.setRecommendedTypes(Arrays.asList("综合意外险", "交通意外险"));
+        gap.setRecommendedMinCoverage(200000L);
+        gap.setRecommendedMaxCoverage(1000000L);
+
+        return gap;
+    }
+
+    // ===== 综合评分 =====
+
+    private int calcCoverageScore(List<MemberCoverageDTO> members, List<InsuranceGapDTO> gaps) {
+        if (members.isEmpty()) return 0;
+
+        // 扣分逻辑:每个high优先级缺口-15,medium-8,low-3
+        int penalty = 0;
+        for (InsuranceGapDTO g : gaps) {
+            if ("high".equals(g.getPriority())) penalty += 15;
+            else if ("medium".equals(g.getPriority())) penalty += 8;
+            else penalty += 3;
+        }
+
+        // 基础分 + 有保单的加分
+        int baseScore = 80;
+        for (MemberCoverageDTO m : members) {
+            int memberBase = 20;
+            if (!"none".equals(m.getMedicalCoverage())) memberBase += 10;
+            if (!"none".equals(m.getCriticalIllnessCoverage())) memberBase += 10;
+            if (!"none".equals(m.getAccidentCoverage())) memberBase += 10;
+            if (!"none".equals(m.getMentalHealthCoverage()) && !"not_applicable".equals(m.getMentalHealthCoverage())) memberBase += 5;
+            if (!"none".equals(m.getEducationCoverage()) && !"not_applicable".equals(m.getEducationCoverage())) memberBase += 5;
+            baseScore += memberBase;
+        }
+
+        int score = baseScore - penalty;
+        // 每成员平均
+        int avgScore = score / members.size();
+        return Math.max(0, Math.min(100, avgScore));
+    }
+
+    private String calcOverallStatus(int score) {
+        if (score >= 80) return "充足";
+        if (score >= 60) return "基本";
+        if (score >= 40) return "不足";
+        return "严重不足";
+    }
+
+    private String buildOverallRecommendation(int score, List<InsuranceGapDTO> gaps) {
+        long highCount = gaps.stream().filter(g -> "high".equals(g.getPriority())).count();
+        long mediumCount = gaps.stream().filter(g -> "medium".equals(g.getPriority())).count();
+
+        if (score >= 80) {
+            return "您的家庭保险配置较为完善,继续保持。建议每年定期复审,根据家庭成员健康状况变化调整保额。";
+        }
+        if (score >= 60) {
+            return String.format("您的家庭保险基本覆盖了主要风险,但仍有 %d 个高优先级缺口和 %d 个中优先级缺口需要关注。", highCount, mediumCount);
+        }
+        if (score >= 40) {
+            return String.format("您的家庭保险配置存在明显不足,%d 个高优先级缺口需要尽快补全。意外险和医疗险是首要配置险种。", highCount);
+        }
+        return String.format("您的家庭保险严重不足,%d 个高优先级缺口急需补全。保险是家庭财富的防线,建议尽快为每位成员配置意外险和医疗险。", highCount);
+    }
+
+    private PlanningReportDTO buildEmptyReport() {
+        PlanningReportDTO report = new PlanningReportDTO();
+        report.setGeneratedAt(LocalDate.now().format(DF));
+        report.setFamilyBodyScore(0);
+        report.setFamilyMindScore(0);
+        report.setFamilyWisdomScore(0);
+        report.setFamilyOverallScore(0);
+        report.setMemberCount(0);
+        report.setMemberCoverages(new ArrayList<>());
+        report.setGaps(new ArrayList<>());
+        report.setHighPriorityGapCount(0);
+        report.setMediumPriorityGapCount(0);
+        report.setLowPriorityGapCount(0);
+        report.setCoverageScore(0);
+        report.setOverallStatus("无数据");
+        report.setOverallRecommendation("请先加入家庭后再使用保险规划功能");
+        return report;
+    }
+}

+ 2 - 1
cfc-frontend/AGENTS.md

@@ -54,7 +54,8 @@ pages.json 中定义 5 个 TabBar 页面(TabBar 文案,非五维维度名)
 - **NEVER** 跳过 JWT 认证直接调用需登录接口
 - **NEVER** 在本地存储中保存敏感用户信息
 - **NEVER** 在 WXML/Vue 模板中使用可选链 `?.`,微信小程序不支持 → 使用 `&&` 代替(如 `currentWish?.title` 改为 `currentWish && currentWish.title`)
-- **NEVER** 在 `:class` 绑定中调用方法(如 `:class="getStatusClass(item)"`),微信小程序模板编译器不支持带参数的方法调用 → 改用内联表达式(如 `:class="'status-' + item.status"`)或计算属性
+- **NEVER** 在 `:class` 绑定中调用方法(如 `:class="getStatusClass(item)"`),微信小程序模板编译器不支持带参数的方法调用 → 改用内联表达式(如 `:class="'status-' + item._cssClass"`)或计算属性
+- **NEVER** 在 CSS 类名、选择器中使用中文(如 `.val-偏高`、`.badge-低风险`),微信小程序 wxss 编译器不支持中文类名 → 使用英文(如 `.val-high`、`.badge-low`),数据中的中文状态通过 `_cssClass` 字段映射
 
 ## UNIQUE FEATURES
 

+ 1 - 1
cfc-frontend/components/FamilyRelationGraph.vue

@@ -672,7 +672,7 @@ export default {
     // 点击右上角小齿轮 → 跳转成员管理页
     onManageTap: function() {
       uni.navigateTo({
-        url: '/pages/profile/family-members'
+        url: '/pages/profile-extra/family-members'
       })
     },
 

+ 1 - 1
cfc-frontend/components/HealthTips.vue

@@ -59,7 +59,7 @@ export default {
       uni.navigateTo({ url: '/pages/mind/article-detail?id=' + tip.id })
     },
     goMore: function() {
-      uni.navigateTo({ url: '/pages/mind/articles?dimension=' + this.dimension })
+      uni.navigateTo({ url: '/pages/mind-extra/articles?dimension=' + this.dimension })
     }
   }
 }

+ 4 - 3
cfc-frontend/components/RecommendedFeed.vue

@@ -31,7 +31,9 @@
                 <text class="feed-type-badge product-badge">🛍️ 商品</text>
               </view>
               <text class="feed-title line-clamp-2">{{ item.name }}</text>
-              <text class="feed-price">{{ formatPrice(item) }}</text>
+              <text class="feed-price" v-if="item.priceLabel">{{ item.priceLabel }}</text>
+              <text class="feed-price fee-free" v-else-if="item.price == null || item.price === 0">免费</text>
+              <text class="feed-price" v-else>{{ formatPrice(item) }}</text>
             </view>
           </template>
           <!-- 活动卡片 -->
@@ -75,7 +77,6 @@ export default {
           coverImage: p.coverImage,
           price: p.price,
           priceLabel: p.priceLabel,
-          memberPrice: p.memberPrice,
           source: p
         }
       })
@@ -112,7 +113,7 @@ export default {
   },
   methods: {
     formatPrice: function(item) {
-      var price = item.memberPrice || item.price
+      var price = item.price
       if (price == null || price === 0) return '免费'
       return '¥' + (Number(price) / 100).toFixed(0)
     },

+ 1 - 1
cfc-frontend/components/WisdomTips.vue

@@ -61,7 +61,7 @@ export default {
       uni.navigateTo({ url: '/pages/mind/article-detail?id=' + tip.id })
     },
     goMore: function() {
-      uni.navigateTo({ url: '/pages/mind/articles?dimension=' + this.dimension })
+      uni.navigateTo({ url: '/pages/mind-extra/articles?dimension=' + this.dimension })
     }
   }
 }

+ 167 - 122
cfc-frontend/pages.json

@@ -62,134 +62,12 @@
         "navigationStyle": "custom"
       }
     },
-    {
-      "path": "pages/policy/agreement",
-      "style": {
-        "navigationBarTitleText": "用户协议"
-      }
-    },
-    {
-      "path": "pages/policy/privacy",
-      "style": {
-        "navigationBarTitleText": "隐私政策"
-      }
-    },
-    {
-      "path": "pages/user-edit/user-edit",
-      "style": {
-        "navigationBarTitleText": "完善信息"
-      }
-    },
-    {
-      "path": "pages/discover/index",
-      "style": {
-        "navigationBarTitleText": "发现"
-      }
-    },
-    {
-      "path": "pages/action/index",
-      "style": {
-        "navigationBarTitleText": "行动"
-      }
-    },
     {
       "path": "pages/index/member-home-detail",
       "style": {
         "navigationBarTitleText": "成员详情",
         "navigationStyle": "custom"
       }
-    },
-    {
-      "path": "pages/member-detail/member-detail",
-      "style": {
-        "navigationBarTitleText": "成员详情",
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      "path": "pages/profile/create-child",
-      "style": {
-        "navigationBarTitleText": "添加孩子"
-      }
-    },
-    {
-      "path": "pages/profile/children",
-      "style": {
-        "navigationBarTitleText": "孩子管理"
-      }
-    },
-    {
-      "path": "pages/profile/edit-child",
-      "style": {
-        "navigationBarTitleText": "编辑孩子"
-      }
-    },
-    {
-      "path": "pages/profile/family-members",
-      "style": {
-        "navigationBarTitleText": "家庭成员"
-      }
-    },
-    {
-      "path": "pages/profile/onboarding",
-      "style": {
-        "navigationBarTitleText": "新手任务"
-      }
-    },
-    {
-      "path": "pages/profile/coupons",
-      "style": {
-        "navigationBarTitleText": "我的优惠券"
-      }
-    },
-    {
-      "path": "pages/mind/articles",
-      "style": {
-        "navigationBarTitleText": "心智成长",
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      "path": "pages/tianpan/index",
-      "style": {
-        "navigationBarTitleText": "家庭天盘",
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      "path": "pages/tianpan/member-detail",
-      "style": {
-        "navigationBarTitleText": "成员详情",
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      "path": "pages/tianpan/relation-detail",
-      "style": {
-        "navigationBarTitleText": "关系详情",
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      "path": "pages/tianpan/annual-energy",
-      "style": {
-        "navigationBarTitleText": "年度能量",
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      "path": "pages/tianpan/daily-fortune",
-      "style": {
-        "navigationBarTitleText": "每日运势",
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      "path": "pages/tianpan/related-items",
-      "style": {
-        "navigationBarTitleText": "相关事项",
-        "navigationStyle": "custom"
-      }
     }
   ],
   "subPackages": [
@@ -826,6 +704,12 @@
           "style": {
             "navigationBarTitleText": "新增保单"
           }
+        },
+        {
+          "path": "insurance-planning",
+          "style": {
+            "navigationBarTitleText": "家庭保险规划"
+          }
         }
       ]
     },
@@ -1240,6 +1124,167 @@
           }
         }
       ]
+    },
+    {
+      "root": "pages/tianpan",
+      "pages": [
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "家庭天盘",
+            "navigationStyle": "custom"
+          }
+        },
+        {
+          "path": "member-detail",
+          "style": {
+            "navigationBarTitleText": "成员详情",
+            "navigationStyle": "custom"
+          }
+        },
+        {
+          "path": "relation-detail",
+          "style": {
+            "navigationBarTitleText": "关系详情",
+            "navigationStyle": "custom"
+          }
+        },
+        {
+          "path": "annual-energy",
+          "style": {
+            "navigationBarTitleText": "年度能量",
+            "navigationStyle": "custom"
+          }
+        },
+        {
+          "path": "daily-fortune",
+          "style": {
+            "navigationBarTitleText": "每日运势",
+            "navigationStyle": "custom"
+          }
+        },
+        {
+          "path": "related-items",
+          "style": {
+            "navigationBarTitleText": "相关事项",
+            "navigationStyle": "custom"
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/mind-extra",
+      "pages": [
+        {
+          "path": "articles",
+          "style": {
+            "navigationBarTitleText": "心智成长",
+            "navigationStyle": "custom"
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/discover",
+      "pages": [
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "发现"
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/action",
+      "pages": [
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "行动"
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/member-detail",
+      "pages": [
+        {
+          "path": "member-detail",
+          "style": {
+            "navigationBarTitleText": "成员详情"
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/user-edit",
+      "pages": [
+        {
+          "path": "user-edit",
+          "style": {
+            "navigationBarTitleText": "完善信息"
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/policy",
+      "pages": [
+        {
+          "path": "agreement",
+          "style": {
+            "navigationBarTitleText": "用户协议"
+          }
+        },
+        {
+          "path": "privacy",
+          "style": {
+            "navigationBarTitleText": "隐私政策"
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/profile-extra",
+      "pages": [
+        {
+          "path": "create-child",
+          "style": {
+            "navigationBarTitleText": "添加孩子"
+          }
+        },
+        {
+          "path": "children",
+          "style": {
+            "navigationBarTitleText": "孩子管理"
+          }
+        },
+        {
+          "path": "edit-child",
+          "style": {
+            "navigationBarTitleText": "编辑孩子"
+          }
+        },
+        {
+          "path": "family-members",
+          "style": {
+            "navigationBarTitleText": "家庭成员"
+          }
+        },
+        {
+          "path": "onboarding",
+          "style": {
+            "navigationBarTitleText": "新手任务"
+          }
+        },
+        {
+          "path": "coupons",
+          "style": {
+            "navigationBarTitleText": "我的优惠券"
+          }
+        }
+      ]
     }
   ],
   "globalStyle": {

+ 1 - 1
cfc-frontend/pages/body/index.vue

@@ -540,7 +540,7 @@ export default {
       })
     },
     goFamilyMembers: function() {
-      uni.navigateTo({ url: '/pages/profile/family-members' })
+      uni.navigateTo({ url: '/pages/profile-extra/family-members' })
     },
     sectionVisible: function(key) {
       return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1

+ 1 - 1
cfc-frontend/pages/discover-detail/product-detail/product-detail.vue

@@ -118,7 +118,7 @@ export default {
         return
       }
       // 跳转结算页面,传递商品信息
-      const price = this.product.memberPrice || this.product.price
+      const price = this.product.price
       const productData = {
         productId: this.product.id,
         productName: this.product.name,

+ 1 - 1
cfc-frontend/pages/discover/index.vue

@@ -219,7 +219,7 @@ export default {
       uni.navigateTo({ url: '/pages/mind/article-detail?id=' + id })
     },
     goKnowledgeCenter() {
-      uni.navigateTo({ url: '/pages/mind/articles' })
+      uni.navigateTo({ url: '/pages/mind-extra/articles' })
     }
   }
 }

+ 19 - 9
cfc-frontend/pages/health/gut-flora-detail.vue

@@ -76,7 +76,7 @@
                 <text class="arrow-warn" v-else-if="item.status === '异常'"> ⚠</text>
               </text>
               <view class="indicator-value" v-if="!isEditing">
-                <text :class="'val-text val-' + item.status">{{ item.indicatorValue || '--' }}</text>
+                <text :class="'val-text val-' + (item._cssClass || 'normal')">{{ item.indicatorValue || '--' }}</text>
               </view>
               <view class="indicator-edit" v-if="isEditing">
                 <input class="edit-input" type="text" v-model="item.indicatorValue" />
@@ -109,7 +109,7 @@
                   <text class="arrow-warn" v-else-if="item.status === '异常'"> ⚠</text>
                 </text>
                 <view class="indicator-value" v-if="!isEditing">
-                  <text :class="'val-text val-' + item.status">{{ item.indicatorValue || '--' }}</text>
+                  <text :class="'val-text val-' + (item._cssClass || 'normal')">{{ item.indicatorValue || '--' }}</text>
                 </view>
                 <view class="indicator-edit" v-if="isEditing">
                   <input class="edit-input" type="text" v-model="item.indicatorValue" />
@@ -184,6 +184,11 @@ export default {
   },
   methods: {
     goBack: function() { uni.navigateBack() },
+    /** 中文状态 → CSS安全英文 */
+    _cssForStatus: function(s) {
+      var map = { '偏高': 'high', '过多': 'excess', '偏低': 'low', '缺乏': 'deficient', '不足': 'insufficient', '异常': 'abnormal', '注意': 'warning' }
+      return map[s] || 'normal'
+    },
     loadData: function() {
       var self = this
       getReportDetail(this.reportId).then(function(res) {
@@ -223,6 +228,11 @@ export default {
     },
     buildIndicators: function(data) {
       var indicators = data.indicators || []
+      // 为每个指标添加_cssClass字段(wxss不支持中文类名)
+      var self = this
+      indicators.forEach(function(item) {
+        item._cssClass = self._cssForStatus(item.status)
+      })
       this.allIndicators = indicators
       var gutCategories = ['肠道屏障与代谢物', '短链脂肪酸', '神经递质与激素', '抗生素风险评估']
       this.gutIndicators = indicators.filter(function(item) {
@@ -359,13 +369,13 @@ export default {
 .arrow-warn { color: #F59E0B; font-size: 22rpx; }
 
 .val-text { font-size: 28rpx; font-weight: 600; color: #333; }
-.val-偏高 { color: #C62828; }
-.val-过多 { color: #C62828; }
-.val-偏低 { color: #E65100; }
-.val-缺乏 { color: #E65100; }
-.val-不足 { color: #E65100; }
-.val-异常 { color: #C62828; }
-.val-注意 { color: #F59E0B; }
+.val-high { color: #C62828; }
+.val-excess { color: #C62828; }
+.val-low { color: #E65100; }
+.val-deficient { color: #E65100; }
+.val-insufficient { color: #E65100; }
+.val-abnormal { color: #C62828; }
+.val-warning { color: #F59E0B; }
 
 .indicator-range { font-size: 22rpx; color: #bbb; display: block; margin-top: 4rpx; }
 

+ 22 - 14
cfc-frontend/pages/health/gut-flora-risks-detail.vue

@@ -10,11 +10,11 @@
 
     <scroll-view class="content" scroll-y>
       <view class="section-label">重要风险</view>
-      <view class="risk-card" v-for="(item, idx) in importantRisks" :key="'imp-' + idx"
-            :class="'risk-level-' + (item.riskLevel || 'low')">
+      <view class="risk-card" v-for="(item, idx) in importantRisks" :key="idx"
+            :class="'risk-level-' + (item._cssClass || 'low')">
         <view class="risk-header">
           <text class="risk-name" @tap="showKnowledge('disease', item.diseaseName)">{{ item.diseaseName }}</text>
-          <text class="risk-badge" :class="'badge-' + (item.riskLevel || 'low')">
+          <text class="risk-badge" :class="'badge-' + (item._cssClass || 'low')">
             {{ riskBadgeText(item.riskLevel) }}
           </text>
         </view>
@@ -33,11 +33,11 @@
       </view>
 
       <view class="section-label">其它风险</view>
-      <view class="risk-card" v-for="(item, idx) in normalRisks" :key="'norm-' + idx"
-            :class="'risk-level-' + (item.riskLevel || 'low')">
+      <view class="risk-card" v-for="(item, idx) in normalRisks" :key="idx"
+            :class="'risk-level-' + (item._cssClass || 'low')">
         <view class="risk-header">
           <text class="risk-name" @tap="showKnowledge('disease', item.diseaseName)">{{ item.diseaseName }}</text>
-          <text class="risk-badge" :class="'badge-' + (item.riskLevel || 'low')">
+          <text class="risk-badge" :class="'badge-' + (item._cssClass || 'low')">
             {{ riskBadgeText(item.riskLevel) }}
           </text>
         </view>
@@ -107,6 +107,11 @@ export default {
   },
   methods: {
     goBack: function() { uni.navigateBack() },
+    /** 中文风险等级 → CSS安全英文 */
+    _cssForRiskLevel: function(level) {
+      var map = { '低风险': 'low', '需注意': 'warning', '高风险': 'high', '异常': 'abnormal' }
+      return map[level] || 'low'
+    },
     getRisks: function() {
       return this.isEditing ? this.editRisks : this.risks
     },
@@ -115,7 +120,10 @@ export default {
       getReportDetail(this.reportId).then(function(res) {
         if (res.code === 200 && res.data) {
           self.reportData = res.data
-          self.risks = res.data.diseaseRisks || []
+          self.risks = (res.data.diseaseRisks || []).map(function(item) {
+            item._cssClass = self._cssForRiskLevel(item.riskLevel)
+            return item
+          })
         }
       })
     },
@@ -180,16 +188,16 @@ export default {
 .content { padding: 20rpx 30rpx; height: calc(100vh - 100rpx); }
 .section-label { font-size: 26rpx; color: #999; margin: 10rpx 0 16rpx; padding-left: 8rpx; }
 .risk-card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; border-left: 8rpx solid #10B981; }
-.risk-level-需注意 { border-left-color: #F59E0B; }
-.risk-level-高风险 { border-left-color: #C62828; }
-.risk-level-异常 { border-left-color: #C62828; }
+.risk-level-warning { border-left-color: #F59E0B; }
+.risk-level-high { border-left-color: #C62828; }
+.risk-level-abnormal { border-left-color: #C62828; }
 .risk-header { display: flex; align-items: center; margin-bottom: 12rpx; }
 .risk-name { font-size: 30rpx; font-weight: 600; color: #333; flex: 1; }
 .risk-badge { padding: 4rpx 16rpx; border-radius: 20rpx; font-size: 22rpx; }
-.badge-低风险 { background: #E8F5E9; color: #2E7D32; }
-.badge-需注意 { background: #FFF3E0; color: #E65100; }
-.badge-高风险 { background: #FFEBEE; color: #C62828; }
-.badge-异常 { background: #FFEBEE; color: #C62828; }
+.badge-low { background: #E8F5E9; color: #2E7D32; }
+.badge-warning { background: #FFF3E0; color: #E65100; }
+.badge-high { background: #FFEBEE; color: #C62828; }
+.badge-abnormal { background: #FFEBEE; color: #C62828; }
 .risk-detail-row { display: flex; gap: 24rpx; }
 .risk-value, .risk-level-text { font-size: 24rpx; color: #999; }
 .risk-edit-row { display: flex; align-items: center; gap: 12rpx; flex-wrap: wrap; }

+ 19 - 8
cfc-frontend/pages/health/gut-flora-species-detail.vue

@@ -19,7 +19,7 @@
     <scroll-view class="content" scroll-y>
       <view class="flora-card" v-for="(item, idx) in filteredFlora" :key="idx">
         <view class="flora-header">
-          <text class="flora-name" :class="'status-' + (item.status || '正常')"
+          <text class="flora-name" :class="'status-' + (item._cssClass || 'normal')"
                 @tap="showKnowledge('bacteria', item.bacteriaName)">
             {{ item.bacteriaName }}
             <text class="arrow-up" v-if="item.status === '偏高' || item.status === '过多'"> ↑</text>
@@ -109,13 +109,24 @@ export default {
   },
   methods: {
     goBack: function() { uni.navigateBack() },
+    /** 中文状态 → CSS安全英文 */
+    _cssForStatus: function(s) {
+      var map = { '偏高': 'high', '过多': 'excess', '偏低': 'low', '缺乏': 'deficient', '不足': 'insufficient' }
+      return map[s] || 'normal'
+    },
     loadData: function() {
       var self = this
       getReportDetail(this.reportId).then(function(res) {
         if (res.code === 200 && res.data) {
           self.reportData = res.data
-          self.gutFlora = res.data.gutFlora || []
-          self.probioticSpecies = res.data.probioticSpecies || []
+          self.gutFlora = (res.data.gutFlora || []).map(function(item) {
+            item._cssClass = self._cssForStatus(item.status)
+            return item
+          })
+          self.probioticSpecies = (res.data.probioticSpecies || []).map(function(item) {
+            item._cssClass = self._cssForStatus(item.status)
+            return item
+          })
         }
       })
     },
@@ -180,11 +191,11 @@ export default {
 .flora-card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; }
 .flora-header { display: flex; align-items: center; margin-bottom: 10rpx; }
 .flora-name { font-size: 28rpx; font-weight: 600; color: #333; flex: 1; }
-.flora-name.status-偏高 { color: #C62828; }
-.flora-name.status-过多 { color: #C62828; }
-.flora-name.status-偏低 { color: #E65100; }
-.flora-name.status-缺乏 { color: #E65100; }
-.flora-name.status-不足 { color: #E65100; }
+.flora-name.status-high { color: #C62828; }
+.flora-name.status-excess { color: #C62828; }
+.flora-name.status-low { color: #E65100; }
+.flora-name.status-deficient { color: #E65100; }
+.flora-name.status-insufficient { color: #E65100; }
 .arrow-up { color: #C62828; font-size: 22rpx; }
 .arrow-down { color: #E65100; font-size: 22rpx; }
 .flora-level-tag { padding: 2rpx 12rpx; background: #E3F2FD; border-radius: 16rpx; font-size: 20rpx; color: #4A9BD7; }

+ 1 - 1
cfc-frontend/pages/index/child-index.vue

@@ -632,7 +632,7 @@ export default {
     },
     goToAddChild() {
       this.$emit('navigate', { page: 'add-child' })
-      uni.navigateTo({ url: '/pages/profile/create-child' })
+      uni.navigateTo({ url: '/pages/profile-extra/create-child' })
     },
     showStreakDetail() {
       this.$emit('navigate', { page: 'streak-detail' })

+ 1 - 1
cfc-frontend/pages/index/parent-index.vue

@@ -786,7 +786,7 @@ export default {
       })
     },
     goToTaskList() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
-    manageChildren() { uni.navigateTo({ url: '/pages/profile/family-members' }) },
+    manageChildren() { uni.navigateTo({ url: '/pages/profile-extra/family-members' }) },
 
     loadContacts: function() {
       var self = this

+ 2 - 2
cfc-frontend/pages/mind/articles.vue → cfc-frontend/pages/mind-extra/articles.vue

@@ -311,14 +311,14 @@ export default {
       if (this.isLoggedIn) {
         uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id })
       } else {
-        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/mind/articles') })
+        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/mind-extra/articles') })
       }
     },
     goCreate() {
       if (this.isLoggedIn) {
         uni.navigateTo({ url: '/pages/article-center/article-edit' })
       } else {
-        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/mind/articles') })
+        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/mind-extra/articles') })
       }
     },
     onBack() {

+ 2 - 2
cfc-frontend/pages/mind/index.vue

@@ -355,7 +355,7 @@ export default {
 
       // 功能入口
       funcList: [
-        { icon: '\u{1F4D6}', label: '阅读', needLogin: false, page: '/pages/mind/articles' },
+        { icon: '\u{1F4D6}', label: '阅读', needLogin: false, page: '/pages/mind-extra/articles' },
         { icon: '\u{1F4CA}', label: '测评报告', needLogin: true, page: '/pages/mind/emotion-report' },
         { icon: '\u{1F4DD}', label: '成长档案', needLogin: true, page: '/pages/growth/index' },
         { icon: '\u{2728}', label: '情绪打卡', needLogin: true, page: '/pages/mind-detail/emotion-checkin' },
@@ -1070,7 +1070,7 @@ export default {
       })
     },
     goFamilyMembers: function() {
-      uni.navigateTo({ url: '/pages/profile/family-members' })
+      uni.navigateTo({ url: '/pages/profile-extra/family-members' })
     },
 
     goAssessment: function() { uni.navigateTo({ url: '/pages/assessment/apply' }) },

+ 116 - 116
cfc-frontend/pages/profile/children.vue → cfc-frontend/pages/profile-extra/children.vue

@@ -1,116 +1,116 @@
-<template>
-  <view class="container">
-    <view class="children-list">
-      <view class="child-item" v-for="child in children" :key="child.id">
-        <view class="child-avatar">{{ child.nickname ? child.nickname[0] : '?' }}</view>
-        <view class="child-info">
-          <text class="child-name">{{ child.nickname }}</text>
-          <text class="child-detail">年龄: {{ child.age }} | 积分: {{ child.totalPoints }}</text>
-        </view>
-        <view class="child-actions">
-          <button class="btn-invite" open-type="share" @click="prepareInvite(child)">邀请</button>
-          <text class="btn-edit" @click="editChild(child)">编辑</text>
-        </view>
-      </view>
-    </view>
-    <view class="empty" v-if="children.length === 0">
-      <text>暂无孩子,请点击下方按钮添加</text>
-    </view>
-    
-    <!-- 添加孩子按钮 -->
-    <view class="add-btn-wrapper">
-      <button class="btn-add" @click="addChild">
-        <text class="add-icon">+</text>
-        <text>添加孩子</text>
-      </button>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getChildren } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      children: [],
-      shareData: null
-    }
-  },
-  onLoad() {
-  },
-  onShow() {
-    this.loadChildren()
-  },
-  onShareAppMessage() {
-    if (this.shareData) {
-      return {
-        title: this.shareData.title,
-        path: this.shareData.path,
-        imageUrl: '/static/invite-card.png'
-      }
-    }
-  },
-  methods: {
-    async loadChildren() {
-      try {
-        const res = await getChildren()
-        this.children = res.data || []
-      } catch (e) {
-        console.error(e)
-      }
-    },
-    async prepareInvite(child) {
-      try {
-        const { generateInviteCard } = require('../../utils/api.js')
-        const res = await generateInviteCard('child', child.id)
-        const code = res.data.code
-        this.shareData = {
-          title: '邀请 ' + (child.nickname || '孩子') + ' 加入家庭',
-          path: '/pages/login/login?invite_code=' + code
-        }
-        uni.showToast({ title: '点击右上角转发给TA', icon: 'none' })
-      } catch (e) {
-        uni.showToast({ title: e.message || '生成邀请失败', icon: 'none' })
-      }
-    },
-    editChild(child) {
-      uni.navigateTo({
-        url: '/pages/profile/edit-child?childId=' + child.id
-      })
-    },
-    addChild() {
-      uni.navigateTo({
-        url: '/pages/profile/create-child'
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container { padding: 30rpx; padding-bottom: 150rpx; }
-.children-list { background: #fff; border-radius: 20rpx; }
-.child-item { display: flex; align-items: center; padding: 30rpx; border-bottom: 1rpx solid #f0f0f0; }
-.child-avatar { width: 100rpx; height: 100rpx; border-radius: 50%; background: linear-gradient(135deg, #667eea, #764ba2); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 40rpx; font-weight: bold; margin-right: 20rpx; }
-.child-info { flex: 1; }
-.child-name { font-size: 32rpx; font-weight: bold; color: #333; display: block; }
-.child-detail { font-size: 26rpx; color: #666; display: block; }
-.child-dan { font-size: 24rpx; color: #999; }
-.btn-edit { color: #F97316; font-size: 28rpx; }
-.btn-invite { background: #3B82F6; color: #fff; font-size: 24rpx; padding: 4rpx 16rpx; border-radius: 8rpx; margin-right: 10rpx; line-height: 1.8; display: inline-block; }
-.empty { text-align: center; padding: 100rpx; color: #999; }
-.add-btn-wrapper { position: fixed; bottom: 30rpx; left: 30rpx; right: 30rpx; }
-.btn-add { 
-  display: flex; 
-  align-items: center; 
-  justify-content: center; 
-  background: linear-gradient(135deg, #F97316, #FB923C); 
-  color: #fff; 
-  border-radius: 50rpx; 
-  padding: 24rpx 0;
-  font-size: 32rpx;
-  box-shadow: 0 4rpx 12rpx rgba(255, 107, 107, 0.3);
-}
-.add-icon { font-size: 40rpx; margin-right: 10rpx; font-weight: bold; }
-</style>
+<template>
+  <view class="container">
+    <view class="children-list">
+      <view class="child-item" v-for="child in children" :key="child.id">
+        <view class="child-avatar">{{ child.nickname ? child.nickname[0] : '?' }}</view>
+        <view class="child-info">
+          <text class="child-name">{{ child.nickname }}</text>
+          <text class="child-detail">年龄: {{ child.age }} | 积分: {{ child.totalPoints }}</text>
+        </view>
+        <view class="child-actions">
+          <button class="btn-invite" open-type="share" @click="prepareInvite(child)">邀请</button>
+          <text class="btn-edit" @click="editChild(child)">编辑</text>
+        </view>
+      </view>
+    </view>
+    <view class="empty" v-if="children.length === 0">
+      <text>暂无孩子,请点击下方按钮添加</text>
+    </view>
+    
+    <!-- 添加孩子按钮 -->
+    <view class="add-btn-wrapper">
+      <button class="btn-add" @click="addChild">
+        <text class="add-icon">+</text>
+        <text>添加孩子</text>
+      </button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getChildren } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      children: [],
+      shareData: null
+    }
+  },
+  onLoad() {
+  },
+  onShow() {
+    this.loadChildren()
+  },
+  onShareAppMessage() {
+    if (this.shareData) {
+      return {
+        title: this.shareData.title,
+        path: this.shareData.path,
+        imageUrl: '/static/invite-card.png'
+      }
+    }
+  },
+  methods: {
+    async loadChildren() {
+      try {
+        const res = await getChildren()
+        this.children = res.data || []
+      } catch (e) {
+        console.error(e)
+      }
+    },
+    async prepareInvite(child) {
+      try {
+        const { generateInviteCard } = require('../../utils/api.js')
+        const res = await generateInviteCard('child', child.id)
+        const code = res.data.code
+        this.shareData = {
+          title: '邀请 ' + (child.nickname || '孩子') + ' 加入家庭',
+          path: '/pages/login/login?invite_code=' + code
+        }
+        uni.showToast({ title: '点击右上角转发给TA', icon: 'none' })
+      } catch (e) {
+        uni.showToast({ title: e.message || '生成邀请失败', icon: 'none' })
+      }
+    },
+    editChild(child) {
+      uni.navigateTo({
+        url: '/pages/profile-extra/edit-child?childId=' + child.id
+      })
+    },
+    addChild() {
+      uni.navigateTo({
+        url: '/pages/profile-extra/create-child'
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container { padding: 30rpx; padding-bottom: 150rpx; }
+.children-list { background: #fff; border-radius: 20rpx; }
+.child-item { display: flex; align-items: center; padding: 30rpx; border-bottom: 1rpx solid #f0f0f0; }
+.child-avatar { width: 100rpx; height: 100rpx; border-radius: 50%; background: linear-gradient(135deg, #667eea, #764ba2); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 40rpx; font-weight: bold; margin-right: 20rpx; }
+.child-info { flex: 1; }
+.child-name { font-size: 32rpx; font-weight: bold; color: #333; display: block; }
+.child-detail { font-size: 26rpx; color: #666; display: block; }
+.child-dan { font-size: 24rpx; color: #999; }
+.btn-edit { color: #F97316; font-size: 28rpx; }
+.btn-invite { background: #3B82F6; color: #fff; font-size: 24rpx; padding: 4rpx 16rpx; border-radius: 8rpx; margin-right: 10rpx; line-height: 1.8; display: inline-block; }
+.empty { text-align: center; padding: 100rpx; color: #999; }
+.add-btn-wrapper { position: fixed; bottom: 30rpx; left: 30rpx; right: 30rpx; }
+.btn-add { 
+  display: flex; 
+  align-items: center; 
+  justify-content: center; 
+  background: linear-gradient(135deg, #F97316, #FB923C); 
+  color: #fff; 
+  border-radius: 50rpx; 
+  padding: 24rpx 0;
+  font-size: 32rpx;
+  box-shadow: 0 4rpx 12rpx rgba(255, 107, 107, 0.3);
+}
+.add-icon { font-size: 40rpx; margin-right: 10rpx; font-weight: bold; }
+</style>

+ 0 - 0
cfc-frontend/pages/profile/coupons.vue → cfc-frontend/pages/profile-extra/coupons.vue


+ 231 - 231
cfc-frontend/pages/profile/create-child.vue → cfc-frontend/pages/profile-extra/create-child.vue

@@ -1,231 +1,231 @@
-<template>
-  <view class="container">
-    <view class="form-container">
-      <view class="page-title">{{ isEditMode ? '编辑孩子信息' : '添加孩子' }}</view>
-
-      <!-- 姓名(必填) -->
-      <view class="form-item required">
-        <text class="label"><text class="required-mark">*</text>孩子姓名</text>
-        <input class="input" v-model="form.nickname" placeholder="请输入孩子姓名" />
-      </view>
-
-      <!-- 出生日期(必填) -->
-      <view class="form-item required">
-        <text class="label"><text class="required-mark">*</text>出生日期</text>
-        <picker mode="date" :value="form.birthday" @change="onBirthdayChange" :end="todayDate">
-          <view class="picker">
-            {{ form.birthday || '请选择出生日期' }}
-          </view>
-        </picker>
-      </view>
-
-      <!-- 性别(必填) -->
-      <view class="form-item required">
-        <text class="label"><text class="required-mark">*</text>性别</text>
-        <picker :range="genderOptions" range-key="label" @change="onGenderChange">
-          <view class="picker">
-            {{ genderLabel || '请选择性别' }}
-          </view>
-        </picker>
-      </view>
-
-      <!-- 年龄(根据出生日期自动计算) -->
-      <view class="form-item">
-        <text class="label">年龄</text>
-        <view class="age-display">{{ calculatedAge || '根据出生日期自动计算' }}</view>
-      </view>
-
-      <!-- 手机号(非必填) -->
-      <view class="form-item">
-        <text class="label">手机号(选填)</text>
-        <input class="input" type="number" v-model="form.phone" placeholder="请输入手机号" maxlength="11" />
-      </view>
-
-      <!-- 身份证号(非必填) -->
-      <view class="form-item">
-        <text class="label">身份证号(选填)</text>
-        <input class="input" v-model="form.idCard" placeholder="请输入身份证号" maxlength="18" />
-      </view>
-
-      <!-- 扣分开关 -->
-      <view class="form-item">
-        <text class="label">扣分开关</text>
-        <switch :checked="form.penaltyEnabled === 1" @change="onPenaltyChange" color="#F97316" />
-        <text class="hint">开启后,超时未完成任务将扣减积分</text>
-      </view>
-
-      <button class="btn-primary" :loading="submitting" @click="submit">{{ isEditMode ? '保存修改' : '保存' }}</button>
-    </view>
-  </view>
-</template>
-
-<script>
-import { createChild, getChildren, updateChild } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      childId: '',
-      isEditMode: false,
-      submitting: false,
-      form: {
-        nickname: '',
-        birthday: '',
-        gender: '',
-        age: '',
-        phone: '',
-        idCard: '',
-        penaltyEnabled: 0
-      },
-      genderOptions: [
-        { value: 'male', label: '男' },
-        { value: 'female', label: '女' }
-      ],
-      todayDate: ''
-    }
-  },
-  computed: {
-    genderLabel() {
-      const gender = this.genderOptions.find(g => g.value === this.form.gender)
-      return gender ? gender.label : ''
-    },
-    calculatedAge() {
-      if (!this.form.birthday) return ''
-      const birth = new Date(this.form.birthday)
-      const today = new Date()
-      let age = today.getFullYear() - birth.getFullYear()
-      const monthDiff = today.getMonth() - birth.getMonth()
-      if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
-        age--
-      }
-      return age >= 0 ? age + '岁' : ''
-    }
-  },
-  onLoad(options) {
-    // 设置今天的日期作为选择器结束日期
-    const today = new Date()
-    this.todayDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
-    
-    this.isEditMode = options.mode === 'edit'
-    this.childId = options.childId || ''
-
-    if (this.isEditMode && this.childId) {
-      uni.setNavigationBarTitle({ title: '编辑孩子' })
-      this.loadChildDetail()
-    } else {
-      uni.setNavigationBarTitle({ title: '添加孩子' })
-    }
-  },
-  methods: {
-    async loadChildDetail() {
-      try {
-        const res = await getChildren()
-        const children = res.data || []
-        const targetChild = children.find(child => String(child.id) === String(this.childId))
-
-        if (!targetChild) {
-          uni.showToast({ title: '未找到孩子信息', icon: 'none' })
-          setTimeout(() => uni.navigateBack(), 1200)
-          return
-        }
-
-        this.form.nickname = targetChild.nickname || ''
-        this.form.birthday = targetChild.birthday || ''
-        this.form.gender = targetChild.gender || ''
-        this.form.age = targetChild.age != null ? String(targetChild.age) : ''
-        this.form.phone = targetChild.phone || ''
-        this.form.idCard = targetChild.idCard || ''
-        this.form.penaltyEnabled = targetChild.penaltyEnabled != null ? targetChild.penaltyEnabled : 0
-      } catch (e) {
-        uni.showToast({ title: '加载孩子信息失败', icon: 'none' })
-      }
-    },
-    onBirthdayChange(e) {
-      this.form.birthday = e.detail.value
-    },
-    onGenderChange(e) {
-      this.form.gender = this.genderOptions[e.detail.value].value
-    },
-    onPenaltyChange(e) {
-      this.form.penaltyEnabled = e.detail.value ? 1 : 0
-    },
-    async submit() {
-      // 必填校验
-      if (!this.form.nickname || !this.form.nickname.trim()) {
-        uni.showToast({ title: '请输入孩子姓名', icon: 'none' })
-        return
-      }
-      if (!this.form.birthday) {
-        uni.showToast({ title: '请选择出生日期', icon: 'none' })
-        return
-      }
-      if (!this.form.gender) {
-        uni.showToast({ title: '请选择性别', icon: 'none' })
-        return
-      }
-
-      // 手机号格式校验(如果填写了)
-      if (this.form.phone && !/^1[3-9]\d{9}$/.test(this.form.phone)) {
-        uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
-        return
-      }
-
-      // 身份证格式校验(如果填写了)
-      if (this.form.idCard && !/^\d{15}$|^\d{17}[\dXx]$/.test(this.form.idCard)) {
-        uni.showToast({ title: '请输入正确的身份证号', icon: 'none' })
-        return
-      }
-
-      // 计算年龄
-      const birth = new Date(this.form.birthday)
-      const today = new Date()
-      let age = today.getFullYear() - birth.getFullYear()
-      const monthDiff = today.getMonth() - birth.getMonth()
-      if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
-        age--
-      }
-
-      try {
-        this.submitting = true
-        const payload = {
-          nickname: this.form.nickname.trim(),
-          birthday: this.form.birthday,
-          gender: this.form.gender,
-          age: age >= 0 ? age : 0,
-          phone: this.form.phone || null,
-          idCard: this.form.idCard || null,
-          penaltyEnabled: this.form.penaltyEnabled
-        }
-
-        if (this.isEditMode) {
-          await updateChild(this.childId, payload)
-        } else {
-          await createChild(payload)
-        }
-
-        uni.showToast({ title: this.isEditMode ? '修改成功' : '添加成功', icon: 'success' })
-        setTimeout(() => uni.navigateBack(), 1500)
-      } catch (e) {
-        uni.showToast({ title: this.isEditMode ? '修改失败' : '添加失败', icon: 'none' })
-      } finally {
-        this.submitting = false
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container { padding: 30rpx; }
-.form-container { background: #fff; border-radius: 20rpx; padding: 30rpx; }
-.page-title { font-size: 34rpx; font-weight: bold; color: #333; margin-bottom: 30rpx; }
-.form-item { margin-bottom: 30rpx; }
-.form-item.required .label { font-weight: bold; }
-.label { display: block; font-size: 28rpx; color: #333; margin-bottom: 10rpx; }
-.required-mark { color: #F97316; margin-right: 4rpx; }
-.input { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; font-size: 28rpx; }
-.picker { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; font-size: 28rpx; color: #666; }
-.age-display { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; font-size: 28rpx; color: #999; background: #f9f9f9; }
-.hint { font-size: 24rpx; color: #999; margin-left: 20rpx; }
-.btn-primary { background: #F97316; color: #fff; border-radius: 20rpx; margin-top: 40rpx; }
-</style>
+<template>
+  <view class="container">
+    <view class="form-container">
+      <view class="page-title">{{ isEditMode ? '编辑孩子信息' : '添加孩子' }}</view>
+
+      <!-- 姓名(必填) -->
+      <view class="form-item required">
+        <text class="label"><text class="required-mark">*</text>孩子姓名</text>
+        <input class="input" v-model="form.nickname" placeholder="请输入孩子姓名" />
+      </view>
+
+      <!-- 出生日期(必填) -->
+      <view class="form-item required">
+        <text class="label"><text class="required-mark">*</text>出生日期</text>
+        <picker mode="date" :value="form.birthday" @change="onBirthdayChange" :end="todayDate">
+          <view class="picker">
+            {{ form.birthday || '请选择出生日期' }}
+          </view>
+        </picker>
+      </view>
+
+      <!-- 性别(必填) -->
+      <view class="form-item required">
+        <text class="label"><text class="required-mark">*</text>性别</text>
+        <picker :range="genderOptions" range-key="label" @change="onGenderChange">
+          <view class="picker">
+            {{ genderLabel || '请选择性别' }}
+          </view>
+        </picker>
+      </view>
+
+      <!-- 年龄(根据出生日期自动计算) -->
+      <view class="form-item">
+        <text class="label">年龄</text>
+        <view class="age-display">{{ calculatedAge || '根据出生日期自动计算' }}</view>
+      </view>
+
+      <!-- 手机号(非必填) -->
+      <view class="form-item">
+        <text class="label">手机号(选填)</text>
+        <input class="input" type="number" v-model="form.phone" placeholder="请输入手机号" maxlength="11" />
+      </view>
+
+      <!-- 身份证号(非必填) -->
+      <view class="form-item">
+        <text class="label">身份证号(选填)</text>
+        <input class="input" v-model="form.idCard" placeholder="请输入身份证号" maxlength="18" />
+      </view>
+
+      <!-- 扣分开关 -->
+      <view class="form-item">
+        <text class="label">扣分开关</text>
+        <switch :checked="form.penaltyEnabled === 1" @change="onPenaltyChange" color="#F97316" />
+        <text class="hint">开启后,超时未完成任务将扣减积分</text>
+      </view>
+
+      <button class="btn-primary" :loading="submitting" @click="submit">{{ isEditMode ? '保存修改' : '保存' }}</button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { createChild, getChildren, updateChild } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      childId: '',
+      isEditMode: false,
+      submitting: false,
+      form: {
+        nickname: '',
+        birthday: '',
+        gender: '',
+        age: '',
+        phone: '',
+        idCard: '',
+        penaltyEnabled: 0
+      },
+      genderOptions: [
+        { value: 'male', label: '男' },
+        { value: 'female', label: '女' }
+      ],
+      todayDate: ''
+    }
+  },
+  computed: {
+    genderLabel() {
+      const gender = this.genderOptions.find(g => g.value === this.form.gender)
+      return gender ? gender.label : ''
+    },
+    calculatedAge() {
+      if (!this.form.birthday) return ''
+      const birth = new Date(this.form.birthday)
+      const today = new Date()
+      let age = today.getFullYear() - birth.getFullYear()
+      const monthDiff = today.getMonth() - birth.getMonth()
+      if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
+        age--
+      }
+      return age >= 0 ? age + '岁' : ''
+    }
+  },
+  onLoad(options) {
+    // 设置今天的日期作为选择器结束日期
+    const today = new Date()
+    this.todayDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
+    
+    this.isEditMode = options.mode === 'edit'
+    this.childId = options.childId || ''
+
+    if (this.isEditMode && this.childId) {
+      uni.setNavigationBarTitle({ title: '编辑孩子' })
+      this.loadChildDetail()
+    } else {
+      uni.setNavigationBarTitle({ title: '添加孩子' })
+    }
+  },
+  methods: {
+    async loadChildDetail() {
+      try {
+        const res = await getChildren()
+        const children = res.data || []
+        const targetChild = children.find(child => String(child.id) === String(this.childId))
+
+        if (!targetChild) {
+          uni.showToast({ title: '未找到孩子信息', icon: 'none' })
+          setTimeout(() => uni.navigateBack(), 1200)
+          return
+        }
+
+        this.form.nickname = targetChild.nickname || ''
+        this.form.birthday = targetChild.birthday || ''
+        this.form.gender = targetChild.gender || ''
+        this.form.age = targetChild.age != null ? String(targetChild.age) : ''
+        this.form.phone = targetChild.phone || ''
+        this.form.idCard = targetChild.idCard || ''
+        this.form.penaltyEnabled = targetChild.penaltyEnabled != null ? targetChild.penaltyEnabled : 0
+      } catch (e) {
+        uni.showToast({ title: '加载孩子信息失败', icon: 'none' })
+      }
+    },
+    onBirthdayChange(e) {
+      this.form.birthday = e.detail.value
+    },
+    onGenderChange(e) {
+      this.form.gender = this.genderOptions[e.detail.value].value
+    },
+    onPenaltyChange(e) {
+      this.form.penaltyEnabled = e.detail.value ? 1 : 0
+    },
+    async submit() {
+      // 必填校验
+      if (!this.form.nickname || !this.form.nickname.trim()) {
+        uni.showToast({ title: '请输入孩子姓名', icon: 'none' })
+        return
+      }
+      if (!this.form.birthday) {
+        uni.showToast({ title: '请选择出生日期', icon: 'none' })
+        return
+      }
+      if (!this.form.gender) {
+        uni.showToast({ title: '请选择性别', icon: 'none' })
+        return
+      }
+
+      // 手机号格式校验(如果填写了)
+      if (this.form.phone && !/^1[3-9]\d{9}$/.test(this.form.phone)) {
+        uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
+        return
+      }
+
+      // 身份证格式校验(如果填写了)
+      if (this.form.idCard && !/^\d{15}$|^\d{17}[\dXx]$/.test(this.form.idCard)) {
+        uni.showToast({ title: '请输入正确的身份证号', icon: 'none' })
+        return
+      }
+
+      // 计算年龄
+      const birth = new Date(this.form.birthday)
+      const today = new Date()
+      let age = today.getFullYear() - birth.getFullYear()
+      const monthDiff = today.getMonth() - birth.getMonth()
+      if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
+        age--
+      }
+
+      try {
+        this.submitting = true
+        const payload = {
+          nickname: this.form.nickname.trim(),
+          birthday: this.form.birthday,
+          gender: this.form.gender,
+          age: age >= 0 ? age : 0,
+          phone: this.form.phone || null,
+          idCard: this.form.idCard || null,
+          penaltyEnabled: this.form.penaltyEnabled
+        }
+
+        if (this.isEditMode) {
+          await updateChild(this.childId, payload)
+        } else {
+          await createChild(payload)
+        }
+
+        uni.showToast({ title: this.isEditMode ? '修改成功' : '添加成功', icon: 'success' })
+        setTimeout(() => uni.navigateBack(), 1500)
+      } catch (e) {
+        uni.showToast({ title: this.isEditMode ? '修改失败' : '添加失败', icon: 'none' })
+      } finally {
+        this.submitting = false
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container { padding: 30rpx; }
+.form-container { background: #fff; border-radius: 20rpx; padding: 30rpx; }
+.page-title { font-size: 34rpx; font-weight: bold; color: #333; margin-bottom: 30rpx; }
+.form-item { margin-bottom: 30rpx; }
+.form-item.required .label { font-weight: bold; }
+.label { display: block; font-size: 28rpx; color: #333; margin-bottom: 10rpx; }
+.required-mark { color: #F97316; margin-right: 4rpx; }
+.input { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; font-size: 28rpx; }
+.picker { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; font-size: 28rpx; color: #666; }
+.age-display { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; font-size: 28rpx; color: #999; background: #f9f9f9; }
+.hint { font-size: 24rpx; color: #999; margin-left: 20rpx; }
+.btn-primary { background: #F97316; color: #fff; border-radius: 20rpx; margin-top: 40rpx; }
+</style>

+ 0 - 0
cfc-frontend/pages/profile/edit-child.vue → cfc-frontend/pages/profile-extra/edit-child.vue


+ 0 - 0
cfc-frontend/pages/profile/family-members.vue → cfc-frontend/pages/profile-extra/family-members.vue


+ 0 - 0
cfc-frontend/pages/profile/onboarding.vue → cfc-frontend/pages/profile-extra/onboarding.vue


+ 1 - 1
cfc-frontend/pages/profile/components/ProfileHeader.vue

@@ -87,7 +87,7 @@ export default {
             content: '您还没有添加孩子,是否现在去添加?',
             success: (res) => {
               if (res.confirm) {
-                uni.navigateTo({ url: '/pages/profile/create-child' })
+                uni.navigateTo({ url: '/pages/profile-extra/create-child' })
               }
             }
           })

+ 2 - 2
cfc-frontend/pages/profile/components/ProfileMenu.vue

@@ -128,10 +128,10 @@ export default {
       } catch (e) {}
     },
     goToOnboarding() {
-      uni.navigateTo({ url: '/pages/profile/onboarding' })
+      uni.navigateTo({ url: '/pages/profile-extra/onboarding' })
     },
     goToFamilyMembers() {
-      uni.navigateTo({ url: '/pages/profile/family-members' })
+      uni.navigateTo({ url: '/pages/profile-extra/family-members' })
     },
     goToDailyTasks() {
       uni.navigateTo({ url: '/pages/tasks/daily-tasks' })

+ 584 - 0
cfc-frontend/pages/wealth-sub/insurance-planning.vue

@@ -0,0 +1,584 @@
+<template>
+  <scroll-view class="container" scroll-y>
+
+    <!-- 加载状态 -->
+    <view v-if="loading" class="loading-state">
+      <text class="loading-icon">🔍</text>
+      <text class="loading-text">正在分析家庭保险规划...</text>
+    </view>
+
+    <template v-else-if="report">
+
+      <!-- 综合评分卡 -->
+      <view class="score-card">
+        <view class="score-header">
+          <text class="score-title">🛡️ 保险规划评分</text>
+          <text class="score-status" :class="'status-' + report._cssStatus">{{ report.overallStatus }}</text>
+        </view>
+        <view class="score-body">
+          <view class="score-ring">
+            <view class="score-ring-inner">
+              <text class="score-value">{{ report.coverageScore }}</text>
+              <text class="score-max">/100</text>
+            </view>
+          </view>
+          <view class="score-dims">
+            <view class="dim-row">
+              <text class="dim-label">身</text>
+              <view class="dim-bar">
+                <view class="dim-fill body-fill" :style="'width:' + (report.familyBodyScore || 0) + '%'"></view>
+              </view>
+              <text class="dim-value">{{ report.familyBodyScore || 0 }}分</text>
+            </view>
+            <view class="dim-row">
+              <text class="dim-label">心</text>
+              <view class="dim-bar">
+                <view class="dim-fill mind-fill" :style="'width:' + (report.familyMindScore || 0) + '%'"></view>
+              </view>
+              <text class="dim-value">{{ report.familyMindScore || 0 }}分</text>
+            </view>
+            <view class="dim-row">
+              <text class="dim-label">智</text>
+              <view class="dim-bar">
+                <view class="dim-fill wisdom-fill" :style="'width:' + (report.familyWisdomScore || 0) + '%'"></view>
+              </view>
+              <text class="dim-value">{{ report.familyWisdomScore || 0 }}分</text>
+            </view>
+          </view>
+        </view>
+        <!-- 缺口统计 -->
+        <view class="gap-stats">
+          <view class="gap-stat high" v-if="report.highPriorityGapCount > 0">
+            <text class="gap-stat-num">{{ report.highPriorityGapCount }}</text>
+            <text class="gap-stat-label">高优先级</text>
+          </view>
+          <view class="gap-stat medium" v-if="report.mediumPriorityGapCount > 0">
+            <text class="gap-stat-num">{{ report.mediumPriorityGapCount }}</text>
+            <text class="gap-stat-label">中优先级</text>
+          </view>
+          <view class="gap-stat low" v-if="report.lowPriorityGapCount > 0">
+            <text class="gap-stat-num">{{ report.lowPriorityGapCount }}</text>
+            <text class="gap-stat-label">低优先级</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 整体建议 -->
+      <view class="section">
+        <view class="recommend-card">
+          <text class="recommend-title">💡 综合建议</text>
+          <text class="recommend-text">{{ report.overallRecommendation }}</text>
+        </view>
+      </view>
+
+      <!-- 成员覆盖卡片 -->
+      <view class="section" v-if="report.memberCoverages && report.memberCoverages.length > 0">
+        <view class="section-header">
+          <text class="section-title">👨‍👩‍👧‍👦 成员覆盖情况</text>
+          <text class="section-sub">{{ report.memberCount }} 位成员</text>
+        </view>
+        <view class="member-list">
+          <view class="member-card" v-for="m in coverageMembers" :key="m.memberId">
+            <view class="member-header">
+              <view class="member-avatar">{{ (m.name || '未知').substring(0, 1) }}</view>
+              <view class="member-info">
+                <text class="member-name">{{ m.name || '未知' }}</text>
+                <text class="member-role">{{ m.familyRole || (m.memberType === 'parent' ? '家长' : '孩子') }}</text>
+              </view>
+              <view class="member-policy-badge" v-if="m.policyCount > 0">
+                <text>{{ m.policyCount }}份保单</text>
+              </view>
+              <view class="member-policy-badge no-policy" v-else>
+                <text>暂无保单</text>
+              </view>
+            </view>
+            <!-- 维度得分 -->
+            <view class="member-dims" v-if="m.bodyScore || m.mindScore || m.wisdomScore">
+              <view class="member-dim-item">
+                <text class="mdim-label">身</text>
+                <text class="mdim-val">{{ m.bodyScore || 0 }}</text>
+              </view>
+              <view class="member-dim-item">
+                <text class="mdim-label">心</text>
+                <text class="mdim-val">{{ m.mindScore || 0 }}</text>
+              </view>
+              <view class="member-dim-item">
+                <text class="mdim-label">智</text>
+                <text class="mdim-val">{{ m.wisdomScore || 0 }}</text>
+              </view>
+            </view>
+            <!-- 覆盖状态 -->
+            <view class="coverage-chips">
+              <view class="coverage-chip" :class="'chip-' + m._cssMedical" @click="showCoverageTip(m.memberName, 'medical')">
+                <text>医疗险</text>
+                <text class="chip-status">{{ getCoverageLabel(m.medicalCoverage) }}</text>
+              </view>
+              <view class="coverage-chip" :class="'chip-' + m._cssCriticalIllness" @click="showCoverageTip(m.memberName, 'critical_illness')">
+                <text>重疾险</text>
+                <text class="chip-status">{{ getCoverageLabel(m.criticalIllnessCoverage) }}</text>
+              </view>
+              <view class="coverage-chip" :class="'chip-' + m._cssAccident" @click="showCoverageTip(m.memberName, 'accident')">
+                <text>意外险</text>
+                <text class="chip-status">{{ getCoverageLabel(m.accidentCoverage) }}</text>
+              </view>
+              <view class="coverage-chip" v-if="m.educationCoverage && m.educationCoverage !== 'not_applicable'" :class="'chip-' + m._cssEducation">
+                <text>教育金</text>
+                <text class="chip-status">{{ getCoverageLabel(m.educationCoverage) }}</text>
+              </view>
+              <view class="coverage-chip" v-if="m.mentalHealthCoverage && m.mentalHealthCoverage !== 'not_applicable'" :class="'chip-' + m._cssMental">
+                <text>心理险</text>
+                <text class="chip-status">{{ getCoverageLabel(m.mentalHealthCoverage) }}</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 缺口列表 -->
+      <view class="section" v-if="report.gaps && report.gaps.length > 0">
+        <view class="section-header">
+          <text class="section-title">⚠️ 保障缺口</text>
+          <text class="section-sub">共 {{ report.gaps.length }} 项</text>
+        </view>
+        <view class="gap-list">
+          <view class="gap-card" v-for="(g, idx) in report.gaps" :key="idx">
+            <view class="gap-header">
+              <view class="gap-priority" :class="'priority-' + g.priority">{{ getPriorityLabel(g.priority) }}</view>
+              <text class="gap-member">{{ g.memberName }}</text>
+              <text class="gap-type">{{ g.gapTypeName }}</text>
+            </view>
+            <text class="gap-desc">{{ g.gapDescription }}</text>
+            <view class="gap-products" v-if="g.recommendedTypes && g.recommendedTypes.length > 0">
+              <text class="gap-products-label">推荐险种:</text>
+              <text class="gap-products-list">{{ g.recommendedTypes.join(' / ') }}</text>
+            </view>
+            <view class="gap-actions">
+              <button class="gap-add-btn" @click="goToAddPolicy(g)">添加保单</button>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 无缺口提示 -->
+      <view class="section" v-if="report.gaps && report.gaps.length === 0">
+        <view class="all-covered-card">
+          <text class="all-covered-icon">✅</text>
+          <text class="all-covered-title">保险配置完善</text>
+          <text class="all-covered-desc">您的家庭保险已覆盖主要风险,请定期复审保单有效性。</text>
+        </view>
+      </view>
+
+      <!-- 保单管理入口 -->
+      <view class="section">
+        <view class="action-btn" @click="goToInsuranceList">
+          <text>📋 管理我的保单</text>
+          <text class="action-arrow">›</text>
+        </view>
+      </view>
+
+    </template>
+
+    <!-- 无数据 -->
+    <view v-else class="empty-state">
+      <text class="empty-icon">🛡️</text>
+      <text class="empty-title">暂无保险规划数据</text>
+      <text class="empty-desc">请先加入家庭后再使用此功能</text>
+    </view>
+
+  </scroll-view>
+</template>
+
+<script>
+import { getInsurancePlanning } from '../../utils/api'
+
+export default {
+  data() {
+    return {
+      loading: true,
+      report: null
+    }
+  },
+  computed: {
+    /** 成员覆盖数据:预计算CSS类名(wxss不支持中文类名,禁止:class方法调用) */
+    coverageMembers: function() {
+      var members = this.report && this.report.memberCoverages
+      if (!members) return []
+      var self = this
+      return members.map(function(m) {
+        m._cssMedical = self._coverageCss(m.medicalCoverage)
+        m._cssCriticalIllness = self._coverageCss(m.criticalIllnessCoverage)
+        m._cssAccident = self._coverageCss(m.accidentCoverage)
+        m._cssEducation = self._coverageCss(m.educationCoverage)
+        m._cssMental = self._coverageCss(m.mentalHealthCoverage)
+        return m
+      })
+    }
+  },
+  onShow() {
+    this.loadPlanning()
+  },
+  methods: {
+    async loadPlanning() {
+      this.loading = true
+      try {
+        const res = await getInsurancePlanning()
+        if (res && res.code === 200 && res.data) {
+          this.report = res.data
+          // 预计算CSS类名(wxss/wxml不支持中文和特殊字符)
+          var self = this
+          this.report._cssStatus = self._overallStatusCss(res.data.overallStatus)
+        } else {
+          this.report = null
+        }
+      } catch (e) {
+        console.error('获取保险规划失败', e)
+        this.report = null
+      } finally {
+        this.loading = false
+      }
+    },
+    /** 覆盖等级 → CSS类名 */
+    _overallStatusCss: function(s) {
+      var map = { '优秀': 'excellent', '良好': 'good', '一般': 'fair', '较差': 'poor', '差': 'bad' }
+      return map[s] || 'neutral'
+    },
+    _coverageCss: function(level) {
+      if (level === 'comprehensive' || level === 'adequate') return 'good'
+      if (level === 'basic') return 'warn'
+      if (level === 'none') return 'danger'
+      return 'neutral'
+    },
+    getCoverageLabel(level) {
+      if (level === 'comprehensive') return '完善'
+      if (level === 'adequate') return '充足'
+      if (level === 'basic') return '基础'
+      if (level === 'none') return '缺失'
+      if (level === 'not_applicable') return '不适用'
+      return level || '缺失'
+    },
+    getPriorityLabel(priority) {
+      if (priority === 'high') return '高'
+      if (priority === 'medium') return '中'
+      return '低'
+    },
+    showCoverageTip(name, type) {
+      var tips = {
+        medical: '医疗险:覆盖门诊和住院医疗费用,按实际花费报销',
+        critical_illness: '重疾险:确诊即赔,一次性给付保险金',
+        accident: '意外险:覆盖意外伤害医疗、身故和伤残'
+      }
+      uni.showModal({
+        title: '险种说明',
+        content: tips[type] || '',
+        showCancel: false
+      })
+    },
+    goToAddPolicy(gap) {
+      uni.navigateTo({
+        url: '/pages/wealth-sub/insurance-add?gapType=' + gap.gapType + '&memberId=' + gap.memberId
+      })
+    },
+    goToInsuranceList() {
+      uni.navigateTo({
+        url: '/pages/wealth-sub/insurance-list'
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding: 20rpx 30rpx 120rpx;
+}
+
+/* 加载 */
+.loading-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 120rpx 0;
+}
+.loading-icon { font-size: 64rpx; margin-bottom: 24rpx; }
+.loading-text { font-size: 28rpx; color: #999; }
+
+/* 综合评分卡 */
+.score-card {
+  background: linear-gradient(135deg, #1E3A5F 0%, #2D5A8A 100%);
+  border-radius: 24rpx;
+  padding: 32rpx;
+  margin-bottom: 24rpx;
+  color: #fff;
+}
+.score-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24rpx;
+}
+.score-title { font-size: 30rpx; font-weight: bold; }
+.score-status {
+  font-size: 26rpx;
+  padding: 6rpx 20rpx;
+  border-radius: 20rpx;
+  background: rgba(255,255,255,0.2);
+}
+.score-body {
+  display: flex;
+  align-items: center;
+  gap: 32rpx;
+}
+.score-ring {
+  width: 160rpx;
+  height: 160rpx;
+  border-radius: 50%;
+  border: 8rpx solid rgba(255,255,255,0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+.score-ring-inner { text-align: center; }
+.score-value { font-size: 56rpx; font-weight: bold; display: block; }
+.score-max { font-size: 24rpx; opacity: 0.7; }
+
+.score-dims { flex: 1; }
+.dim-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.dim-label {
+  font-size: 26rpx;
+  width: 36rpx;
+  color: rgba(255,255,255,0.8);
+}
+.dim-bar {
+  flex: 1;
+  height: 12rpx;
+  background: rgba(255,255,255,0.2);
+  border-radius: 6rpx;
+  margin: 0 12rpx;
+  overflow: hidden;
+}
+.dim-fill {
+  height: 100%;
+  border-radius: 6rpx;
+}
+.body-fill { background: #FF8C42; }
+.mind-fill { background: #FF6B9D; }
+.wisdom-fill { background: #6366F1; }
+.dim-value {
+  font-size: 24rpx;
+  width: 70rpx;
+  text-align: right;
+  color: rgba(255,255,255,0.9);
+}
+
+.gap-stats {
+  display: flex;
+  gap: 16rpx;
+  margin-top: 24rpx;
+  padding-top: 20rpx;
+  border-top: 1rpx solid rgba(255,255,255,0.15);
+}
+.gap-stat {
+  flex: 1;
+  background: rgba(255,255,255,0.12);
+  border-radius: 12rpx;
+  padding: 12rpx 0;
+  text-align: center;
+}
+.gap-stat.high { border-left: 4rpx solid #FF4D4F; }
+.gap-stat.medium { border-left: 4rpx solid #FFA940; }
+.gap-stat.low { border-left: 4rpx solid #52C41A; }
+.gap-stat-num {
+  font-size: 36rpx;
+  font-weight: bold;
+  display: block;
+  color: #fff;
+}
+.gap-stat-label { font-size: 22rpx; color: rgba(255,255,255,0.7); }
+
+/* 整体建议 */
+.recommend-card {
+  background: #FFFBEB;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  border-left: 6rpx solid #F59E0B;
+}
+.recommend-title { font-size: 28rpx; font-weight: bold; color: #92400E; display: block; margin-bottom: 12rpx; }
+.recommend-text { font-size: 26rpx; color: #78350F; line-height: 1.6; }
+
+/* Section */
+.section { margin-bottom: 24rpx; }
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.section-title { font-size: 30rpx; font-weight: bold; color: #333; }
+.section-sub { font-size: 24rpx; color: #999; }
+
+/* 成员卡片 */
+.member-list {}
+.member-card {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.member-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.member-avatar {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #667eea, #764ba2);
+  color: #fff;
+  font-size: 32rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16rpx;
+  flex-shrink: 0;
+}
+.member-info { flex: 1; }
+.member-name { font-size: 30rpx; font-weight: bold; color: #333; display: block; }
+.member-role { font-size: 24rpx; color: #999; }
+.member-policy-badge {
+  font-size: 22rpx;
+  background: #DCFCE7;
+  color: #16A34A;
+  padding: 6rpx 16rpx;
+  border-radius: 20rpx;
+}
+.member-policy-badge.no-policy {
+  background: #FEF2F2;
+  color: #DC2626;
+}
+
+.member-dims {
+  display: flex;
+  gap: 24rpx;
+  margin-bottom: 16rpx;
+  padding: 12rpx 0;
+  border-top: 1rpx solid #f5f5f5;
+  border-bottom: 1rpx solid #f5f5f5;
+}
+.member-dim-item { text-align: center; flex: 1; }
+.mdim-label { font-size: 22rpx; color: #999; display: block; }
+.mdim-val { font-size: 32rpx; font-weight: bold; color: #333; }
+
+/* 覆盖状态芯片 */
+.coverage-chips {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12rpx;
+}
+.coverage-chip {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+  padding: 8rpx 16rpx;
+  border-radius: 20rpx;
+  font-size: 24rpx;
+  background: #f0f0f0;
+  color: #666;
+}
+.coverage-chip.chip-good { background: #DCFCE7; color: #16A34A; }
+.coverage-chip.chip-warn { background: #FFF7E6; color: #D46B08; }
+.coverage-chip.chip-danger { background: #FFF1F0; color: #CF1322; }
+.chip-status { font-size: 22rpx; }
+
+/* 缺口列表 */
+.gap-list {}
+.gap-card {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+  border-left: 6rpx solid;
+}
+.gap-card[style*="priority-high"] { border-left-color: #FF4D4F; }
+.gap-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 12rpx;
+  gap: 12rpx;
+}
+.gap-priority {
+  font-size: 22rpx;
+  padding: 4rpx 12rpx;
+  border-radius: 8rpx;
+}
+.gap-priority.priority-high { background: #FFF1F0; color: #CF1322; }
+.gap-priority.priority-medium { background: #FFF7E6; color: #D46B08; }
+.gap-priority.priority-low { background: #F6FFED; color: #389E0D; }
+.gap-member { font-size: 28rpx; font-weight: bold; color: #333; }
+.gap-type { font-size: 26rpx; color: #666; }
+.gap-desc {
+  font-size: 26rpx;
+  color: #555;
+  line-height: 1.5;
+  display: block;
+  margin-bottom: 12rpx;
+}
+.gap-products { margin-bottom: 12rpx; }
+.gap-products-label { font-size: 24rpx; color: #999; }
+.gap-products-list { font-size: 24rpx; color: #F59E0B; font-weight: 500; }
+.gap-actions { display: flex; justify-content: flex-end; }
+.gap-add-btn {
+  font-size: 24rpx;
+  background: #F97316;
+  color: #fff;
+  padding: 10rpx 28rpx;
+  border-radius: 24rpx;
+  line-height: 1.5;
+}
+
+/* 全覆盖 */
+.all-covered-card {
+  background: #F6FFED;
+  border-radius: 16rpx;
+  padding: 40rpx;
+  text-align: center;
+  border: 2rpx solid #B7EB8F;
+}
+.all-covered-icon { font-size: 64rpx; display: block; margin-bottom: 16rpx; }
+.all-covered-title { font-size: 30rpx; font-weight: bold; color: #389E0D; display: block; margin-bottom: 12rpx; }
+.all-covered-desc { font-size: 26rpx; color: #52C41A; }
+
+/* 保单管理入口 */
+.action-btn {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 28rpx 30rpx;
+  font-size: 28rpx;
+  color: #333;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.action-arrow { color: #ccc; font-size: 32rpx; }
+
+/* 空状态 */
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 120rpx 0;
+}
+.empty-icon { font-size: 80rpx; margin-bottom: 24rpx; }
+.empty-title { font-size: 32rpx; font-weight: bold; color: #333; margin-bottom: 12rpx; }
+.empty-desc { font-size: 26rpx; color: #999; }
+</style>

+ 12 - 5
cfc-frontend/pages/wealth/index.vue

@@ -118,14 +118,18 @@
           <text class="service-icon">📝</text>
           <text class="service-label">记账打卡</text>
         </view>
-        <view class="service-item" @click="goToInsurance">
-          <text class="service-icon">🛡️</text>
-          <text class="service-label">保单管理</text>
+        <view class="service-item" @click="goToInsurancePlanning">
+          <text class="service-icon">📊</text>
+          <text class="service-label">保险规划</text>
         </view>
         <view class="service-item" @click="goToCreator">
           <text class="service-icon">🎨</text>
           <text class="service-label">创客中心</text>
         </view>
+        <view class="service-item" @click="goToInsuranceList">
+          <text class="service-icon">🛡️</text>
+          <text class="service-label">保单管理</text>
+        </view>
       </view>
     </view>
 
@@ -429,7 +433,7 @@ export default {
             content: '您还没有添加孩子,是否现在去添加?',
             success: function(res) {
               if (res.confirm) {
-                uni.navigateTo({ url: '/pages/profile/create-child' })
+                uni.navigateTo({ url: '/pages/profile-extra/create-child' })
               }
             }
           })
@@ -490,7 +494,10 @@ export default {
     goToCheckin() {
       uni.navigateTo({ url: '/pages/wealth-sub/checkin' })
     },
-    goToInsurance() {
+    goToInsurancePlanning() {
+      uni.navigateTo({ url: '/pages/wealth-sub/insurance-planning' })
+    },
+    goToInsuranceList() {
       uni.navigateTo({ url: '/pages/wealth-sub/insurance-list' })
     },
     goToCreator() {

+ 2 - 2
cfc-frontend/pages/wisdom/index.vue

@@ -203,7 +203,7 @@ export default {
         { icon: '\u{270F}\uFE0F', label: '自己出题', needLogin: true, page: '/pages/wisdom-detail/self-quiz' },
         { icon: '\u{1F9E0}', label: '训练中心', needLogin: true, page: '/pages/wisdom-detail/training-hub' },
         { icon: '\u{1F3AF}', label: '测评预约', needLogin: false, page: '/pages/assessment/apply' },
-        { icon: '\u{1F4D6}', label: '阅读天地', needLogin: false, page: '/pages/mind/articles' }
+        { icon: '\u{1F4D6}', label: '阅读天地', needLogin: false, page: '/pages/mind-extra/articles' }
       ]
     }
   },
@@ -414,7 +414,7 @@ export default {
       uni.navigateTo({ url: '/pages/member-detail/member-detail?memberId=' + memberId + '&entrySource=relation-graph&dimensionCode=wisdom' })
     },
     goFamilyMembers: function() {
-      uni.navigateTo({ url: '/pages/profile/family-members' })
+      uni.navigateTo({ url: '/pages/profile-extra/family-members' })
     },
     goCognitiveReport: function() {
       uni.navigateTo({ url: '/pages/wisdom-detail/cognitive-report' })

+ 1 - 1
cfc-frontend/pages/wisdom/wisdom_temp/index.vue

@@ -82,7 +82,7 @@ export default {
         { icon: '\u{1F4DA}', label: '学习任务', needLogin: true, page: '/pages/tasks/tasks' },
         { icon: '\u{1F3AE}', label: '小游戏', needLogin: false, page: '/pages/games/list' },
         { icon: '\u{1F9E9}', label: '测评中心', needLogin: false, page: '/pages/assessment/apply' },
-        { icon: '\u{1F4D6}', label: '阅读天地', needLogin: false, page: '/pages/mind/articles' },
+        { icon: '\u{1F4D6}', label: '阅读天地', needLogin: false, page: '/pages/mind-extra/articles' },
         { icon: '\u{1F4A1}', label: '知识挑战', needLogin: false, page: '' }
       ],
       hotActivities: [

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

@@ -1448,8 +1448,11 @@ export const deleteInsurance = (id) => {
   return request('/api/wealth/insurance/delete', 'POST', { id })
 }
 
+export const getInsurancePlanning = () => {
+  return request('/api/insurance/planning', 'POST', {})
+}
 
-// ===== 鍋ュ悍鎵撳崱 =====
+// ===== 鍋ュ悍鎵撳崱 =====
 export const healthCheckinList = (data) => request('/api/health/checkin/list', 'POST', data)
 export const healthCheckinCreate = (data) => request('/api/health/checkin/create', 'POST', data)
 export const healthCheckinDelete = (id) => request('/api/health/checkin/delete', 'POST', { id })