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

merge: 合入远程 cfclub——第1章'为什么是算法'主轴论述 + 书名定位升级'一词三通'(解决成书稿3处冲突:保留序章定位声明与'守住每个人/终身课题'章节标题)

asus 1 місяць тому
батько
коміт
55eecae8e1
31 змінених файлів з 1158 додано та 830 видалено
  1. 21 0
      book/主动健康-书籍策划方案.md
  2. 58 4
      book/成书稿.txt
  3. 34 1
      book/竞品调研分析.md
  4. 56 0
      book/第一章-我们到底在为什么焦虑.md
  5. 2 2
      book/第二章-幸福三支柱.md
  6. 43 2
      cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
  7. 1 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/ActivityController.java
  8. 21 14
      cfc-backend/src/main/java/com/etotem/cfc/controller/CircleController.java
  9. 3 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/InviteController.java
  10. 5 1
      cfc-backend/src/main/java/com/etotem/cfc/service/ActivityRegistrationService.java
  11. 3 103
      cfc-frontend/components/AIFloatingAvatar.vue
  12. 2 2
      cfc-frontend/components/tab-transition.vue
  13. 12 28
      cfc-frontend/pages/activity/activity-detail/activity-detail.vue
  14. 1 0
      cfc-frontend/pages/activity/index.vue
  15. 21 173
      cfc-frontend/pages/article-center/article-detail.vue
  16. 4 8
      cfc-frontend/pages/body-detail/health-report.vue
  17. 1 1
      cfc-frontend/pages/body/index.vue
  18. 2 6
      cfc-frontend/pages/health/gut-flora-detail.vue
  19. 4 8
      cfc-frontend/pages/health/physical-exam-detail.vue
  20. 5 4
      cfc-frontend/pages/health/report-survey.vue
  21. 110 426
      cfc-frontend/pages/profile-extra/family-members.vue
  22. 27 31
      cfc-frontend/pages/wealth/index.vue
  23. 1 1
      cfc-web/.last_build_commit
  24. 2 2
      cfc-web/package-lock.json
  25. 1 1
      cfc-web/package.json
  26. 104 0
      cfc-web/public/CHANGELOG-v1.0.md
  27. 105 1
      cfc-web/public/CHANGELOG.md
  28. 8 8
      cfc-web/src/components/DimensionWeightPicker.vue
  29. 6 2
      cfc-web/src/views/admin/ActivityRegistrationReview.vue
  30. 142 0
      docs/superpowers/specs/2026-07-31-family-members-redesign.md
  31. 353 1
      web/articles/plastic-health.html

+ 21 - 0
book/主动健康-书籍策划方案.md

@@ -295,6 +295,27 @@
 **副标题:五维能量让幸福有迹可循**
 
 > 书名解析:"家的算法"以"算法"这一现代概念比喻家庭幸福的系统方法论,暗示幸福不是靠运气、不是随缘,而是有方法、可复现、可经营的。有技术感但不冰冷,有差异化且易于传播。
+
+### 书名内核:「一词三通」——算法串联宇宙因果 × 传统文化 × 人工智能
+
+"算法"是全书的枢纽词,一词同时打通三个维度:
+
+| 维度 | 内涵 | 书中落地 |
+|:----:|------|---------|
+| **宇宙因果**(本体论) | 万物运行遵循因果律,种什么因、得什么果 | 五行相生相克、动态平衡 = 宇宙因果律在家庭中的映射;"补上游而非死磕短板" |
+| **传统文化**(认识论) | 五行相生相克不是迷信,而是古人对宇宙因果的系统建模——相生=正反馈回路,相克=负反馈调节 | 五维幸福矩阵、五行相生找短板上游、家庭雷达图 |
+| **人工智能**(方法论) | 算法是 AI 的母语:输入→处理→输出;可执行、可重复、可优化 | 第三篇六步行为算法、每章算法类比、编程式复盘 |
+
+**"家"是三者交汇的最小单元**:宇宙因果太大,AI 太抽象,只有"家"是每个人都拥有的系统实验场。修身→齐家→治国→平天下——齐家本就是因果链上的第一步。
+
+**叙事翻转(时代情绪解药)**:现代人被推荐算法、消费算法、鸡娃算法支配。本书翻转视角:不是教你逃离算法,而是教你**成为自己家的算法设计师**——从被动受害者到主动设计者。
+
+**Slogan 升级方向**:
+
+> 算法不是让你更快,是让你回家。
+> 宇宙的因果,祖先的智慧,AI 时代的答案。
+
+> 正文落地:已在第1章末尾新增"为什么是算法"主轴论述(约1000字,打通三层,含金句"算法不是让你更快,是让你回家"),第2章"为什么叫算法"节同步精简呼应;已重新生成 `book/成书稿.txt`。
 >
 > 作者计算机专业背景贯穿全书:第三篇六步法本身是一套"行为算法",每章结尾设「编程式复盘」专栏——用代码思维回看一个家庭场景,让有技术背景的读者会心一笑,也让没有技术背景的读者觉得新鲜有趣。
 

+ 58 - 4
book/成书稿.txt

@@ -182,7 +182,6 @@
 **从父母身上,验证孩子的未来。** 你现在怎么对待父母,你的孩子都看在眼里——十几年后,他会用同样的方式对待你。你父母现在的状态:身体如何、心态如何、和你的关系如何——就是你的孩子未来面对你时的"预览版"。你今天不关心父母,孩子明天就不会关心你;你今天善待父母,孩子后天就懂得善待你。
 
 所以,不管你在家庭中扮演什么角色——父母、子女、伴侣、还是你自己——这张地图,都是你的。而书里每一次出现孩子,都不是在教你如何教育他,而是在提醒你:**先看见自己。**
-
 ====================
 [第一章-我们到底在为什么焦虑]
 ====================
@@ -324,6 +323,62 @@
 
 ---
 
+## 为什么是"算法"
+
+写这本书之前,我一直在想一个问题:给一部讲家庭幸福的书起名字,为什么偏偏是"算法"?
+
+这个词太现代了。它让人想到代码、想到 AI、想到大数据——跟"家"似乎隔着十万八千里。
+
+但后来我想明白了一件事:算法这个词,恰好是三个时代在同一句话里的交汇点。它同时装着宇宙的因果、祖先的智慧,和 AI 时代的答案。
+
+### 第一层:宇宙的因果
+
+你爷爷可能不识字,但他一定说过一句话:种瓜得瓜,种豆得豆。
+
+这是中国人对这个世界最朴素的信念——世界是有规律的。你今天种下什么,明天就会收获什么。身体是养出来的,关系是处出来的,孩子是带出来的,家是一天一天过出来的。没有什么"突然"的幸福,也没有无缘无故的崩塌。每一件正在发生的好事,往前追,都能追到一个具体的动作。
+
+这就是因果。它是宇宙运行的第一条算法。
+
+### 第二层:祖先的智慧
+
+有人可能会说:因果?这不是迷信吗?
+
+恰恰相反。我们的祖先,早在两千多年前,就把这套因果律做成了模型——它叫五行。
+
+五行不是玄学。相生,说的是事物之间互相滋养的链条(土生金、金生水、水生木、木生火、火生土);相克,说的是系统防止某一环过度膨胀的刹车(身克富、智克行、富克心、行克身、心克智)。用今天的话说:相生是正反馈回路,相克是负反馈调节。
+
+我们以为这些是现代系统论才有的概念。可祖先用五个字,就把一个家庭、一个组织、甚至一个王朝的运行规律讲完了。这不是迷信,这是中国版的"系统动力学"——只是它诞生得太早,早到我们忘了它有多超前。
+
+### 第三层:AI 时代的答案
+
+今天,全世界都在谈论算法。推荐算法知道你看什么,消费算法知道你想买什么,教育算法知道你的孩子"应该"学什么。
+
+我们一边被算法安排得明明白白,一边焦虑——因为那些算法,没有一个是为你家写的。
+
+但"算法"这个词,本来的意思朴素得很,只有三个特点:**可执行、可重复、可优化**。它不是高高在上的东西,它就是"把一件事做对,然后重复,然后做得更好"。
+
+如果 AI 有母语,那就是算法。而算法最早的母语,是我们的祖先用五行写下的因果。
+
+### 三层交汇,落在"家"
+
+现在你明白为什么是"算法"了——
+
+**宇宙因果**是本体论:世界有规律,幸福有迹可循。
+
+**五行**是认识论:祖先替我们给这套规律建了模。
+
+**人工智能**是方法论:我们终于有了一个现代词,来描述这套可执行、可重复、可优化的系统。
+
+而三者交汇的地方,不在实验室,不在大厂,就在你家。
+
+修身、齐家、治国、平天下——齐家从来不是小事,它是这条因果链上的第一步,也是每个人唯一能亲自上手的系统实验场。宇宙太大,AI 太远,只有家,是你今天晚饭前就能开始运行的最小系统。
+
+所以这本书不教你逃离算法。它教你**成为自己家的算法设计师**——读懂宇宙的因果,接上祖先的智慧,写出属于你家的那套算法。
+
+> **金句:算法不是让你更快,是让你回家。**
+
+---
+
 ## 本章小结
 
 - 中国家庭的焦虑可以归纳为五个维度:健康、成长、财富、关系、意义
@@ -509,9 +564,9 @@
 
 ## 为什么叫"算法"
 
-你可能会问:这本书叫《家的算法》,跟算法有什么关系?
+上一章,我们已经回答过这个问题:这本书为什么叫《家的算法》——因为"算法"这个词,装着宇宙的因果、祖先的智慧(五行),和 AI 时代的答案。
 
-算法的核心不是代码,是**解决问题的步骤**。一个算法有三个特点:可执行、可重复、可优化。
+这里只补充一个角度:算法的核心不是代码,是**解决问题的步骤**。一个算法有三个特点:可执行、可重复、可优化。
 
 可执行——每一步都是具体的、能做出来的动作。不是"要对孩子好一点",是"每天晚饭时问一句'今天有什么让你开心的事吗'"。
 
@@ -4525,4 +4580,3 @@ _________________________________
 ---
 
 *工具包到此结束。需要时可单独打印使用。*
-

+ 34 - 1
book/竞品调研分析.md

@@ -82,6 +82,37 @@
 
 ---
 
+### 2.5 算法/逻辑方法类
+
+| 书名 | 作者 | 出版信息 | 核心内容 | 与本书差别 |
+|:----|:----|:--------|---------|:----|
+| 《人生算法》 | 喻颖正 | 中信出版社 2020 | 概率思维、认知闭环、贝叶斯更新,用"算法"提升个人决策质量 | 个人成功学,对象是自己,不涉及家庭系统 |
+| 《底层逻辑》 | 刘润 | 机械工业出版社 | 方法论=底层逻辑+环境变量;因果链、增强回路、调节回路 | 商业方法论,案例全在商业,无"家"的落点 |
+
+**分析**:
+- 市面上"算法/逻辑"类书不少,但全部指向"个人如何变强 / 商业如何成功"
+- **没有一本把"算法"指向"家"**——而"家"恰恰是每个人都拥有的最小系统实验场
+- **我们的差异化**:《家的算法》是唯一把"算法"落到家庭单位的书;且本书"算法"一词三通(宇宙因果×传统文化×AI),哲学纵深上竞品无法复制
+
+---
+
+### 2.6 AI×家庭类(2026 新兴赛道)
+
+| 书名 | 作者 | 出版信息 | 核心内容 | 与本书差别 |
+|:----|:----|:--------|---------|:----|
+| 《豆包AI:你的家庭全能助手》 | - | 2026.04 | AI 工具实操教程 | 工具书,无哲学深度 |
+| 《如何创建一个家教智能体》 | - | 2026.03 | AI 家教智能体搭建 | 工具书,单点场景 |
+| 《AI时代,做不焦虑的父母》 | 熊丙奇 | 机械工业出版社 2026.07 | AI 时代育儿焦虑应对 | 教育单维度视角 |
+| Human Raised(海外) | Dana Suskind | - | 亲子语言互动研究 | 海外视角,无文化根基 |
+| Artificial Intimacy(海外) | Sherry Turkle | - | 警惕 AI 侵蚀共情能力 | 负面警告向 |
+
+**分析**:
+- AI×家庭赛道被两类书占据:**AI 工具教程书**(教用工具,无深度)和 **AI 育儿焦虑书**(应对焦虑,教育单维度)
+- **没有任何一本书回答"AI 时代的家庭该如何自处"**——更没有人提出"与其被算法支配,不如成为自己家的算法设计师"的翻转叙事
+- **我们的差异化**:把 AI 时代最大的情绪痛点(被算法支配的无力感)转化为主动身份(算法设计师),且以传统文化为根,给出不同于"工具/焦虑"的第三条路
+
+---
+
 ## 三、竞争优势总结
 
 ### 3.1 市场空白定位图
@@ -131,7 +162,8 @@
 2. **正面语境**——不做"问题书",做"构建书",与武志红等形成差异化互补
 3. **五行哲学壁垒**——中国传统文化根基,引进书无法复制
 4. **工具化落地**——能量地图、家庭角色分工、行动卡,竞品无此配套
-5. **书名即定位**——"算法"带科技感、方法论感,与其他书名形成区隔
+5. **书名即定位**——"算法"一词三通(宇宙因果×传统文化×AI):算法类书(《人生算法》《底层逻辑》)均指向个人/商业,**无一家**落到"家"
+6. **AI 时代立场**——AI×家庭赛道只有工具教程与焦虑书,本书给出第三条路:成为自己家的算法设计师
 
 ---
 
@@ -153,6 +185,7 @@
 1. 没有一本书以"五维全景"视角覆盖家庭全部维度
 2. 没有一本书将中国五行哲学与现代家庭管理结合
 3. 没有一本书同时具备"系统性理论框架"和"可量化工具"
+4. 没有一本书用"算法"串联宇宙因果、传统文化与人工智能,并把落点放到"家"——算法书=个人/商业,AI书=工具/焦虑,家庭书=疗愈/单维,三块空白交汇处正是本书
 
 **核心Slogan方向:**
 

+ 56 - 0
book/第一章-我们到底在为什么焦虑.md

@@ -136,6 +136,62 @@
 
 ---
 
+## 为什么是"算法"
+
+写这本书之前,我一直在想一个问题:给一部讲家庭幸福的书起名字,为什么偏偏是"算法"?
+
+这个词太现代了。它让人想到代码、想到 AI、想到大数据——跟"家"似乎隔着十万八千里。
+
+但后来我想明白了一件事:算法这个词,恰好是三个时代在同一句话里的交汇点。它同时装着宇宙的因果、祖先的智慧,和 AI 时代的答案。
+
+### 第一层:宇宙的因果
+
+你爷爷可能不识字,但他一定说过一句话:种瓜得瓜,种豆得豆。
+
+这是中国人对这个世界最朴素的信念——世界是有规律的。你今天种下什么,明天就会收获什么。身体是养出来的,关系是处出来的,孩子是带出来的,家是一天一天过出来的。没有什么"突然"的幸福,也没有无缘无故的崩塌。每一件正在发生的好事,往前追,都能追到一个具体的动作。
+
+这就是因果。它是宇宙运行的第一条算法。
+
+### 第二层:祖先的智慧
+
+有人可能会说:因果?这不是迷信吗?
+
+恰恰相反。我们的祖先,早在两千多年前,就把这套因果律做成了模型——它叫五行。
+
+五行不是玄学。相生,说的是事物之间互相滋养的链条(土生金、金生水、水生木、木生火、火生土);相克,说的是系统防止某一环过度膨胀的刹车(身克富、智克行、富克心、行克身、心克智)。用今天的话说:相生是正反馈回路,相克是负反馈调节。
+
+我们以为这些是现代系统论才有的概念。可祖先用五个字,就把一个家庭、一个组织、甚至一个王朝的运行规律讲完了。这不是迷信,这是中国版的"系统动力学"——只是它诞生得太早,早到我们忘了它有多超前。
+
+### 第三层:AI 时代的答案
+
+今天,全世界都在谈论算法。推荐算法知道你看什么,消费算法知道你想买什么,教育算法知道你的孩子"应该"学什么。
+
+我们一边被算法安排得明明白白,一边焦虑——因为那些算法,没有一个是为你家写的。
+
+但"算法"这个词,本来的意思朴素得很,只有三个特点:**可执行、可重复、可优化**。它不是高高在上的东西,它就是"把一件事做对,然后重复,然后做得更好"。
+
+如果 AI 有母语,那就是算法。而算法最早的母语,是我们的祖先用五行写下的因果。
+
+### 三层交汇,落在"家"
+
+现在你明白为什么是"算法"了——
+
+**宇宙因果**是本体论:世界有规律,幸福有迹可循。
+
+**五行**是认识论:祖先替我们给这套规律建了模。
+
+**人工智能**是方法论:我们终于有了一个现代词,来描述这套可执行、可重复、可优化的系统。
+
+而三者交汇的地方,不在实验室,不在大厂,就在你家。
+
+修身、齐家、治国、平天下——齐家从来不是小事,它是这条因果链上的第一步,也是每个人唯一能亲自上手的系统实验场。宇宙太大,AI 太远,只有家,是你今天晚饭前就能开始运行的最小系统。
+
+所以这本书不教你逃离算法。它教你**成为自己家的算法设计师**——读懂宇宙的因果,接上祖先的智慧,写出属于你家的那套算法。
+
+> **金句:算法不是让你更快,是让你回家。**
+
+---
+
 ## 本章小结
 
 - 中国家庭的焦虑可以归纳为五个维度:健康、成长、财富、关系、意义

+ 2 - 2
book/第二章-幸福三支柱.md

@@ -162,9 +162,9 @@
 
 ## 为什么叫"算法"
 
-你可能会问:这本书叫《家的算法》,跟算法有什么关系?
+上一章,我们已经回答过这个问题:这本书为什么叫《家的算法》——因为"算法"这个词,装着宇宙的因果、祖先的智慧(五行),和 AI 时代的答案。
 
-算法的核心不是代码,是**解决问题的步骤**。一个算法有三个特点:可执行、可重复、可优化。
+这里只补充一个角度:算法的核心不是代码,是**解决问题的步骤**。一个算法有三个特点:可执行、可重复、可优化。
 
 可执行——每一步都是具体的、能做出来的动作。不是"要对孩子好一点",是"每天晚饭时问一句'今天有什么让你开心的事吗'"。
 

+ 43 - 2
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -2961,6 +2961,7 @@ log.info("已添加template_id列到tasks表");
         migrateHealthDimensionsTable();
         seedHealthNorms();
         runLateMigrations();
+        migration102();
     }
 
     private void migrateHealthDimensionsTable() {
@@ -6147,6 +6148,45 @@ try {
 
     // 迁移140: activities表添加family_scope列(推荐系统-家庭参与去重,原migration102内从未被调用)
     ensureColumn("activities", "family_scope", "TINYINT DEFAULT 1 COMMENT '是否面向家庭: 1=家庭成员参与过即排除, 0=仅按个人参与排除'");
+
+    // 迁移141: 修正五维权重维度code体系(后台DimensionWeightPicker曾用mind=智/heart=心,标准体系为wisdom=智/mind=心)
+    // 旧体系写入的数据导致小程序端按wisdom/mind查询JSON_EXTRACT匹配不到,新活动显示不出来
+    try {
+        // 1. dimension_weights 表:仅处理含 heart 的目标(旧体系写入的),mind(智)→wisdom,heart(心)→mind
+        int dwHeartCount = jdbcTemplate.queryForObject(
+            "SELECT COUNT(*) FROM dimension_weights WHERE dimension = 'heart'", Integer.class);
+        if (dwHeartCount > 0) {
+            jdbcTemplate.update("UPDATE dimension_weights dw " +
+                "JOIN (SELECT DISTINCT target_type, target_id FROM dimension_weights WHERE dimension = 'heart') h " +
+                "ON dw.target_type = h.target_type AND dw.target_id = h.target_id " +
+                "SET dw.dimension = 'wisdom' WHERE dw.dimension = 'mind'");
+            jdbcTemplate.update("UPDATE dimension_weights SET dimension = 'mind' WHERE dimension = 'heart'");
+            log.info("已修正dimension_weights表维度code: {}条heart记录", dwHeartCount);
+        }
+    } catch (Exception e) {
+        log.warn("修正dimension_weights表维度code失败: {}", e.getMessage());
+    }
+
+    try {
+        // 2. 主表 dimension_weights JSON 列:含 heart 键的记录,mind键→wisdom键,heart键→mind键
+        String[] jsonTables = {"activities", "products", "articles"};
+        for (String table : jsonTables) {
+            int jsonCount = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM " + table + " WHERE dimension_weights IS NOT NULL " +
+                "AND JSON_CONTAINS_PATH(dimension_weights, 'one', '$.heart')", Integer.class);
+            if (jsonCount > 0) {
+                jdbcTemplate.update("UPDATE " + table + " SET dimension_weights = JSON_SET(" +
+                    "JSON_REMOVE(JSON_REMOVE(dimension_weights, '$.mind'), '$.heart'), " +
+                    "'$.wisdom', JSON_EXTRACT(dimension_weights, '$.mind'), " +
+                    "'$.mind', JSON_EXTRACT(dimension_weights, '$.heart')) " +
+                    "WHERE dimension_weights IS NOT NULL " +
+                    "AND JSON_CONTAINS_PATH(dimension_weights, 'one', '$.heart')");
+                log.info("已修正{}表dimension_weights JSON: {}条", table, jsonCount);
+            }
+        }
+    } catch (Exception e) {
+        log.warn("修正主表dimension_weights JSON失败: {}", e.getMessage());
+    }
     }
 
     private void runMigration82() {
@@ -7167,11 +7207,12 @@ private void runMigration100() {
 		}
 
 		// 迁移112: users表添加article_admin_status字段(文章管理员状态)
+		log.info("迁移112: 开始添加article_admin_status列到users表");
 		try {
 			jdbcTemplate.execute("ALTER TABLE users ADD COLUMN article_admin_status VARCHAR(20) DEFAULT NULL COMMENT '文章管理员状态: pending/approved/rejected'");
-			log.info("已添加article_admin_status列到users表");
+			log.info("迁移112: 已添加article_admin_status列到users表");
 		} catch (Exception e) {
-			// 列已存在,忽略错误
+			log.error("迁移112: 添加article_admin_status列失败: {}", e.getMessage());
 		}
 
 		// 迁移113: users表添加activity_provider_status字段(活动提供商状态)

+ 1 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/ActivityController.java

@@ -7,6 +7,7 @@ import com.etotem.cfc.service.ActivityRegistrationService;
 import com.etotem.cfc.service.ActivityService;
 import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
+import java.util.List;
 import java.util.Map;
 
 @RestController

+ 21 - 14
cfc-backend/src/main/java/com/etotem/cfc/controller/CircleController.java

@@ -41,8 +41,9 @@ public class CircleController {
      */
     @PostMapping("/discover")
     public Result<List<Map<String, Object>>> discover(@RequestBody Map<String, Object> params) {
-        Long childId = params.get("childId") != null
-                ? Long.valueOf(params.get("childId").toString()) : null;
+        Object childIdObj = params.get("childId");
+        Long childId = childIdObj != null && !childIdObj.toString().trim().isEmpty()
+                ? Long.valueOf(childIdObj.toString()) : null;
         if (childId == null) {
             return Result.error("childId不能为空");
         }
@@ -55,10 +56,12 @@ public class CircleController {
      */
     @PostMapping("/join")
     public Result<String> join(@RequestBody Map<String, Object> params) {
-        Long circleId = params.get("circleId") != null
-                ? Long.valueOf(params.get("circleId").toString()) : null;
-        Long memberId = params.get("memberId") != null
-                ? Long.valueOf(params.get("memberId").toString()) : null;
+        Object circleIdObj = params.get("circleId");
+        Long circleId = circleIdObj != null && !circleIdObj.toString().trim().isEmpty()
+                ? Long.valueOf(circleIdObj.toString()) : null;
+        Object memberIdObj = params.get("memberId");
+        Long memberId = memberIdObj != null && !memberIdObj.toString().trim().isEmpty()
+                ? Long.valueOf(memberIdObj.toString()) : null;
         String memberType = params.get("memberType") != null
                 ? params.get("memberType").toString() : "child";
 
@@ -74,10 +77,12 @@ public class CircleController {
      */
     @PostMapping("/leave")
     public Result<String> leave(@RequestBody Map<String, Object> params) {
-        Long circleId = params.get("circleId") != null
-                ? Long.valueOf(params.get("circleId").toString()) : null;
-        Long memberId = params.get("memberId") != null
-                ? Long.valueOf(params.get("memberId").toString()) : null;
+        Object circleIdObj = params.get("circleId");
+        Long circleId = circleIdObj != null && !circleIdObj.toString().trim().isEmpty()
+                ? Long.valueOf(circleIdObj.toString()) : null;
+        Object memberIdObj = params.get("memberId");
+        Long memberId = memberIdObj != null && !memberIdObj.toString().trim().isEmpty()
+                ? Long.valueOf(memberIdObj.toString()) : null;
         String memberType = params.get("memberType") != null
                 ? params.get("memberType").toString() : "child";
 
@@ -97,10 +102,12 @@ public class CircleController {
         String name = params.get("name") != null ? params.get("name").toString() : null;
         String type = params.get("type") != null ? params.get("type").toString() : null;
         String matchSource = params.get("matchSource") != null ? params.get("matchSource").toString() : null;
-        Long sourceId = params.get("sourceId") != null
-                ? Long.valueOf(params.get("sourceId").toString()) : null;
-        Long creatorId = params.get("creatorId") != null
-                ? Long.valueOf(params.get("creatorId").toString()) : null;
+        Object sourceIdObj = params.get("sourceId");
+        Long sourceId = sourceIdObj != null && !sourceIdObj.toString().trim().isEmpty()
+                ? Long.valueOf(sourceIdObj.toString()) : null;
+        Object creatorIdObj = params.get("creatorId");
+        Long creatorId = creatorIdObj != null && !creatorIdObj.toString().trim().isEmpty()
+                ? Long.valueOf(creatorIdObj.toString()) : null;
         String creatorType = params.get("creatorType") != null
                 ? params.get("creatorType").toString() : "child";
 

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/InviteController.java

@@ -26,6 +26,9 @@ public class InviteController {
 
     @PostMapping("/code")
     public Result<String> getCode(@RequestAttribute("userId") Long userId) {
+        if (userId == null || userId <= 0) {
+            return Result.error("请先登录");
+        }
         String code = commissionService.getOrCreateReferralCode(userId);
         return Result.success(code);
     }

+ 5 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/ActivityRegistrationService.java

@@ -115,7 +115,11 @@ public class ActivityRegistrationService {
         if (size == null || size < 1) size = 10;
         LambdaQueryWrapper<ActivityRegistration> wrapper = new LambdaQueryWrapper<>();
         if (userId != null) wrapper.eq(ActivityRegistration::getUserId, userId);
-        if (childId != null) wrapper.eq(ActivityRegistration::getChildId, childId);
+        if (childId != null) {
+            // 手动录入的报名 child_id 为 NULL,也属于该用户,需一并返回
+            wrapper.and(w -> w.eq(ActivityRegistration::getChildId, childId)
+                .or().isNull(ActivityRegistration::getChildId));
+        }
         wrapper.orderByDesc(ActivityRegistration::getRegisteredAt);
         Page<ActivityRegistration> pageResult = registrationMapper.selectPage(new Page<>(page, size), wrapper);
         List<Map<String, Object>> records = pageResult.getRecords().stream().map(reg -> {

+ 3 - 103
cfc-frontend/components/AIFloatingAvatar.vue

@@ -1,109 +1,9 @@
 <template>
-  <view class="ai-float-wrapper" @click="goToChat">
-    <view class="ai-float-avatar" :class="mascotCode">
-      <text class="ai-float-icon">{{ mascotIcon }}</text>
-    </view>
-  </view>
+  <!-- AI问答功能已屏蔽 -->
 </template>
 
 <script>
 export default {
-  name: 'AIFloatingAvatar',
-  data() {
-    return {
-      mascotCode: 'xibao',
-      mascotIcon: '🌟'
-    }
-  },
-  mounted() {
-    this.loadMascot()
-  },
-  methods: {
-    loadMascot() {
-      var userInfo = uni.getStorageSync('userInfo')
-      if (userInfo && userInfo.mascot) {
-        if (userInfo.mascot === 'xibao') {
-          this.mascotCode = 'xibao'
-          this.mascotIcon = '🌟'
-        } else if (userInfo.mascot === 'fubao') {
-          this.mascotCode = 'fubao'
-          this.mascotIcon = '💪'
-        }
-      } else {
-        // 默认浠宝
-        this.mascotCode = 'xibao'
-        this.mascotIcon = '🌟'
-      }
-    },
-    goToChat() {
-      // 获取当前页面路径作为来源标识
-      var pages = getCurrentPages()
-      var currentPage = pages[pages.length - 1]
-      var fromPage = ''
-      if (currentPage) {
-        fromPage = currentPage.route || ''
-      }
-      var url = '/pages/ai/chat?fromPage=' + encodeURIComponent(fromPage)
-      // 健康维度页面(身/智/心)自动切换为健康教练模式
-      if (fromPage === 'pages/body/index' || fromPage === 'pages/wisdom/index' || fromPage === 'pages/mind/index') {
-        url += '&mode=health'
-      }
-      uni.navigateTo({ url: url })
-    }
-  }
+  name: 'AIFloatingAvatar'
 }
-</script>
-
-<style scoped>
-.ai-float-wrapper {
-  position: fixed;
-  right: 32rpx;
-  bottom: calc(160rpx + constant(safe-area-inset-bottom));
-  bottom: calc(160rpx + env(safe-area-inset-bottom));
-  z-index: 999;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  width: 112rpx;
-  height: 112rpx;
-}
-
-.ai-float-avatar {
-  width: 96rpx;
-  height: 96rpx;
-  border-radius: 50%;
-  background: #fff;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15), 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
-  animation: floatBounce 3s ease-in-out infinite;
-  position: relative;
-  z-index: 2;
-  transition: transform 0.2s;
-}
-
-.ai-float-avatar:active {
-  transform: scale(0.92);
-}
-
-.ai-float-avatar.xibao {
-  background: linear-gradient(135deg, #FFF7ED 0%, #FFE4CC 100%);
-  border: 3rpx solid #FF8C42;
-}
-
-.ai-float-avatar.fubao {
-  background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%);
-  border: 3rpx solid #F59E0B;
-}
-
-.ai-float-icon {
-  font-size: 48rpx;
-  line-height: 1;
-}
-
-@keyframes floatBounce {
-  0%, 100% { transform: translateY(0); }
-  50% { transform: translateY(-8rpx); }
-}
-</style>
+</script>

+ 2 - 2
cfc-frontend/components/tab-transition.vue

@@ -52,10 +52,10 @@ export default {
   mounted() {
     this.mountedAt = Date.now()
     var self = this
-    // 最多 3 秒强制关闭
+    // 最多 1.5 秒强制关闭
     setTimeout(function() {
       self.$emit('close')
-    }, 3000)
+    }, 1500)
   },
   methods: {
     // 父组件数据加载完成后调用,过渡页至少停留 800ms

+ 12 - 28
cfc-frontend/pages/activity/activity-detail/activity-detail.vue

@@ -87,13 +87,9 @@
           <button
             v-if="activity.requireRegistration === 1 && !registrationStatus"
             class="btn-register"
-            :class="{ 'btn-register-loading': registrationLoading || paying }"
-            :disabled="registrationLoading || paying"
             @click="onRegister"
           >
-            <text v-if="registrationLoading">报名中...</text>
-            <text v-else-if="paying">支付中...</text>
-            <text v-else-if="activity.price > 0">¥{{ activity.price }} 立即报名</text>
+            <text v-if="activity.price > 0">¥{{ activity.price }} 立即报名</text>
             <text v-else>立即报名</text>
           </button>
 
@@ -270,7 +266,6 @@ export default {
       checkinPoints: 0,
       registrationStatus: null,
       registrationLoading: false,
-      paying: false,
       showPenaltyModal: false,
       penaltyAmount: 0,
       showFeedbackModal: false,
@@ -414,27 +409,6 @@ export default {
       self.registrants = []
       self.loadFamilyMembers()
     },
-    doRegister: function(childId) {
-      var self = this
-      self.registrationLoading = true
-      if (self.activity && self.activity.price > 0) {
-        self.paying = true
-      }
-      registerActivity({ id: self.activity.id, childId: childId }).then(function(res) {
-        self.registrationLoading = false
-        self.paying = false
-        if (res && res.code === 200) {
-          self.registrationStatus = 'pending'
-          uni.showToast({ title: '报名成功,请等待审核', icon: 'success' })
-        } else {
-          uni.showToast({ title: (res && res.message) || '报名失败', icon: 'none' })
-        }
-      }).catch(function(err) {
-        self.registrationLoading = false
-        self.paying = false
-        uni.showToast({ title: '网络异常', icon: 'none' })
-      })
-    },
     onCancelRegistration: function() {
       var self = this
       uni.showModal({
@@ -611,10 +585,20 @@ export default {
         return
       }
       for (var i = 0; i < self.registrants.length; i++) {
-        if (!self.registrants[i].name || self.registrants[i].name.trim() === '') {
+        var entry = self.registrants[i]
+        if (!entry.name || entry.name.trim() === '') {
           uni.showToast({ title: '请输入第 ' + (i + 1) + ' 个报名人的姓名', icon: 'none' })
           return
         }
+        var phone = (entry.phone || '').trim()
+        if (phone === '') {
+          uni.showToast({ title: '请输入第 ' + (i + 1) + ' 个报名人的手机号', icon: 'none' })
+          return
+        }
+        if (!/^1[3-9]\d{9}$/.test(phone)) {
+          uni.showToast({ title: '请输入第 ' + (i + 1) + ' 个报名人的正确手机号', icon: 'none' })
+          return
+        }
       }
 
       // 付费活动 → 先确认支付

+ 1 - 0
cfc-frontend/pages/activity/index.vue

@@ -62,6 +62,7 @@
             />
             <view class="card-info">
               <text class="card-title">{{ act.title }}</text>
+              <text class="card-meta" v-if="act.registrantName">报名人:{{ act.registrantName }}</text>
               <text class="card-meta">{{ act.startTime }}</text>
               <text class="card-meta" v-if="act.location">📍 {{ act.location }}</text>
               <view class="card-bottom">

+ 21 - 173
cfc-frontend/pages/article-center/article-detail.vue

@@ -34,58 +34,7 @@
         <view class="divider"></view>
         <view class="detail-body"><rich-text :nodes="article.content"></rich-text></view>
 
-        <!-- 评论区 -->
-        <view class="comment-section">
-          <view class="comment-header">
-            <text class="comment-title">评论 ({{ comments.length }})</text>
-          </view>
-          <view class="comment-input-row">
-            <input class="comment-input" v-model="commentText" :placeholder="commentPlaceholder" confirm-type="send" @confirm="submitComment" />
-            <button class="comment-submit" @click="submitComment">发送</button>
-          </view>
-          <view v-if="comments.length === 0" class="comment-empty">暂无评论,来写第一条吧</view>
-          <view v-for="(c, i) in comments" :key="c.id || i" class="comment-item">
-            <view class="comment-user">
-              <text class="comment-avatar">{{ (c.userName || '?').charAt(0) }}</text>
-              <text class="comment-name">{{ c.userName || '匿名' }}</text>
-              <text class="comment-time">{{ formatDate(c.createdAt) }}</text>
-            </view>
-            <view class="comment-content">{{ c.content }}</view>
-            <view class="comment-actions">
-              <text class="comment-reply-btn" @click="startReply(c)">回复</text>
-            </view>
-            <!-- 回复列表 -->
-            <view class="reply-list" v-if="c.replies && c.replies.length > 0">
-              <view v-for="(r, ri) in c.replies" :key="ri" class="reply-item">
-                <view class="reply-user">
-                  <text class="reply-avatar">{{ (r.userName || '?').charAt(0) }}</text>
-                  <text class="reply-name">{{ r.userName || '匿名' }}</text>
-                  <text class="reply-time">{{ formatDate(r.createdAt) }}</text>
-                  <text class="reply-to" v-if="r.replyToId">回复</text>
-                </view>
-                <view class="reply-content">{{ r.content }}</view>
-              </view>
-            </view>
-          </view>
-        </view>
-        <!-- 作者审核区 -->
-        <view class="review-section" v-if="isArticleAuthor && hasPendingComments">
-          <view class="review-header">
-            <text class="review-title">待审核评论 ({{ pendingComments.length }})</text>
-          </view>
-          <view v-for="(c, i) in pendingComments" :key="'pending-' + i" class="review-item">
-            <view class="review-user">
-              <text class="review-avatar">{{ (c.userName || '?').charAt(0) }}</text>
-              <text class="review-name">{{ c.userName || '匿名' }}</text>
-              <text class="review-time">{{ formatDate(c.createdAt) }}</text>
-            </view>
-            <view class="review-content">{{ c.content }}</view>
-            <view class="review-actions">
-              <button class="review-btn approve" @click="approveComment(c.id)">通过</button>
-              <button class="review-btn reject" @click="rejectComment(c.id)">驳回</button>
-            </view>
-          </view>
-        </view>
+        <!-- 评论区(已屏蔽) -->
         <view style="height: 200rpx;"></view>
       </scroll-view>
 
@@ -102,10 +51,10 @@
       </view>
     </template>
 
-    <ContentSharePoster :show="showPoster" :title="article && article.title" :coverImage="article && article.coverImage" :qrCodeBase64="posterQrCode" typeLabel="好文推荐" @close="showPoster = false" />
+    <ContentSharePoster :show="showPoster" :title="article && article.title" :coverImage="article && article.coverImage" :qrCodeBase64="posterQrCode" typeLabel="好文推荐" @close="closePoster" />
 
     <!-- 浠宝答题弹窗 -->
-    <view class="quiz-mask" v-if="showQuiz" @click="showQuiz = false">
+    <view class="quiz-mask" v-if="showQuiz" @click="closeQuiz">
       <view class="quiz-dialog" @click.stop>
         <view class="quiz-mascot">
           <text class="mascot-icon">{{ mascotIcon }}</text>
@@ -115,7 +64,7 @@
         <view class="quiz-question" v-for="(q, qi) in quizQuestions" :key="qi">
           <text class="q-title">问题{{ qi+1 }}: {{ q.question }}</text>
           <view class="q-options">
-            <text v-for="(opt, oi) in q.options" :key="oi" class="q-option" :class="{ selected: quizAnswers[qi] === String.fromCharCode(65 + oi) }" @click="quizAnswers[qi] = String.fromCharCode(65 + oi)">{{ opt }}</text>
+            <text v-for="(opt, oi) in q.options" :key="oi" class="q-option" :class="{ selected: quizAnswers[qi] === String.fromCharCode(65 + oi) }" @click="selectQuizAnswer(qi, oi)">{{ opt }}</text>
           </view>
         </view>
         <button class="quiz-submit" @click="submitQuiz">提交答案</button>
@@ -128,14 +77,14 @@
         <text class="result-icon">{{ resultIcon }}</text>
         <text class="result-text">答对 {{ quizResult.correctCount }}/{{ quizResult.totalQuestions }} 题</text>
         <text class="result-energy">获得 {{ quizResult.earnedEnergy }} 能量 ⚡</text>
-        <button class="result-btn" @click="showResult = false">知道了</button>
+        <button class="result-btn" @click="closeResult">知道了</button>
       </view>
     </view>
   </view>
 </template>
 
 <script>
-import { getArticleDetail, reportReadingTime, getShareQrCode, completeArticleRead, getArticleComments, createArticleComment, generateQuiz, submitQuiz, getPendingComments, auditArticleComment } from '@/utils/api.js'
+import { getArticleDetail, reportReadingTime, getShareQrCode, completeArticleRead, generateQuiz, submitQuiz } from '@/utils/api.js'
 import shareMixin from '../../components/share-mixin.js'
 import ContentSharePoster from '@/components/ContentSharePoster.vue'
 
@@ -148,62 +97,17 @@ export default {
       readingSeconds: 0, lastReportedSeconds: 0, readingCompleted: false,
       isTimerRunning: false, timerHandle: null, syncHandle: null,
       showPoster: false, posterQrCode: '',
-      // 评论
-      comments: [], commentText: '', replyTarget: null,
-      // 作者审核
-      pendingComments: [], currentUserId: null, currentRole: null,
-    // 作者审核
-    async loadPendingComments() {
-      try {
-        var res = await getPendingComments({ articleId: this.articleId })
-        if (res.code === 200) { this.pendingComments = res.data || [] }
-      } catch (e) {}
-    },
-    async approveComment(commentId) {
-      try {
-        var res = await auditArticleComment({ commentId: commentId, status: 'approved' })
-        if (res.code === 200) {
-          uni.showToast({ title: '已通过', icon: 'success' })
-          this.pendingComments = this.pendingComments.filter(function(c) { return c.id !== commentId })
-        } else {
-          uni.showToast({ title: res.message || '操作失败', icon: 'none' })
-        }
-      } catch (e) { uni.showToast({ title: '网络错误', icon: 'none' }) }
-    },
-    async rejectComment(commentId) {
-      try {
-        var res = await auditArticleComment({ commentId: commentId, status: 'rejected' })
-        if (res.code === 200) {
-          uni.showToast({ title: '已驳回', icon: 'success' })
-          this.pendingComments = this.pendingComments.filter(function(c) { return c.id !== commentId })
-        } else {
-          uni.showToast({ title: res.message || '操作失败', icon: 'none' })
-        }
-      } catch (e) { uni.showToast({ title: '网络错误', icon: 'none' }) }
-    },
-    // 答题
+      // 答题
       showQuiz: false, quizQuestions: [], quizAnswers: [], showResult: false, quizResult: {},
       mascotList: [{ icon: '🐶', name: '浠宝' }, { icon: '🐼', name: '福宝' }],
       mascotIcon: '🐶', mascotName: '浠宝'
     }
   },
-  computed: {
-    commentPlaceholder: function() {
-      return this.replyTarget ? '回复 ' + (this.replyTarget.userName || '') + ':' : '写下你的评论...'
-    },
-    isArticleAuthor: function() {
-      return this.article && this.currentUserId && this.article.createdBy === this.currentUserId
-    },
-    hasPendingComments: function() {
-      return this.pendingComments && this.pendingComments.length > 0
-    }
-  },
+  computed: {},
   onLoad(options) {
     this.mascotIcon = this.mascotList[Math.floor(Math.random() * 2)].icon
     this.mascotName = this.mascotList[Math.floor(Math.random() * 2)].name
-    this.currentUserId = parseInt(uni.getStorageSync('userId') || 0)
-    this.currentRole = uni.getStorageSync('currentRole') || ''
-    if (options && options.id) { this.articleId = options.id; this.loadDetail(options.id); this.loadComments() }
+    if (options && options.id) { this.articleId = options.id; this.loadDetail(options.id) }
     else { this.error = true; this.errorMsg = '参数错误'; this.loading = false }
   },
   onShow() { if (this.article && !this.error) this.startReadingTimer() },
@@ -218,18 +122,10 @@ export default {
           this.article = res.data
           this.setShareInfo('推荐阅读: ' + (res.data.title || ''), '/pages/article-center/article-detail?id=' + id)
           this.startReadingTimer()
-          // 如果当前用户是文章作者,加载待审核评论
-          if (this.currentUserId && res.data.createdBy === this.currentUserId) {
-            this.loadPendingComments()
-          }
         } else { this.error = true; this.errorMsg = '文章不存在或无权限查看' }
       } catch (e) { this.error = true; this.errorMsg = '加载失败' }
       finally { this.loading = false }
     },
-    async loadComments() {
-      try { var res = await getArticleComments({ articleId: this.articleId }); if (res.code === 200) this.comments = res.data || [] }
-      catch (e) {}
-    },
     formatDate(d) { return d ? d.slice(0, 10) : '' },
     parseDimensions: function(article) {
       var dimMap = { body: { code: 'body', color: '#FF8C42', name: '身' }, mind: { code: 'mind', color: '#FF6B9D', name: '心' }, wisdom: { code: 'wisdom', color: '#6366F1', name: '智' }, action: { code: 'action', color: '#10B981', name: '行' }, wealth: { code: 'wealth', color: '#F59E0B', name: '富' } }
@@ -290,25 +186,6 @@ export default {
       } catch (e) { uni.showToast({ title: '生成失败', icon: 'none' }) }
       finally { uni.hideLoading() }
     },
-    // 评论
-    startReply: function(comment) {
-      this.replyTarget = comment
-      this.commentText = ''
-      uni.pageScrollTo({ selector: '.comment-input-row', duration: 300 })
-    },
-    async submitComment() {
-      var text = this.commentText.trim()
-      if (!text) return
-      try {
-        var params = { articleId: this.articleId, content: text }
-        if (this.replyTarget) { params.parentId = this.replyTarget.id; params.replyToId = this.replyTarget.id }
-        var res = await createArticleComment(params)
-        if (res.code === 200) {
-          uni.showToast({ title: '评论已提交,等待审核', icon: 'success' })
-          this.commentText = ''; this.replyTarget = null
-        } else { uni.showToast({ title: res.message || '评论失败', icon: 'none' }) }
-      } catch (e) { uni.showToast({ title: '网络错误', icon: 'none' }) }
-    },
     // 答题
     async startQuiz() {
       try {
@@ -334,6 +211,18 @@ export default {
           this.showResult = true
         }
       } catch (e) {}
+    },
+    closePoster: function() {
+      this.showPoster = false
+    },
+    closeQuiz: function() {
+      this.showQuiz = false
+    },
+    closeResult: function() {
+      this.showResult = false
+    },
+    selectQuizAnswer: function(qi, oi) {
+      this.quizAnswers[qi] = String.fromCharCode(65 + oi)
     }
   }
 }
@@ -366,47 +255,6 @@ export default {
 .divider { height: 1rpx; background: #eee; margin: 24rpx 30rpx; }
 .detail-body { padding: 0 30rpx; font-size: 28rpx; color: #444; line-height: 1.8; }
 
-/* 评论区 */
-.comment-section { padding: 30rpx; border-top: 16rpx solid #F5F7FA; }
-.comment-header { margin-bottom: 20rpx; }
-.comment-title { font-size: 30rpx; font-weight: 700; color: #333; }
-.comment-input-row { display: flex; gap: 12rpx; margin-bottom: 24rpx; }
-.comment-input { flex: 1; border: 2rpx solid #E5E7EB; border-radius: 12rpx; padding: 16rpx 20rpx; font-size: 26rpx; height: 72rpx; box-sizing: border-box; }
-.comment-submit { width: 120rpx; height: 72rpx; line-height: 72rpx; background: #5B9BD5; color: #fff; font-size: 26rpx; border-radius: 12rpx; text-align: center; border: none; }
-.comment-empty { text-align: center; padding: 40rpx 0; color: #999; font-size: 26rpx; }
-.comment-item { padding: 20rpx 0; border-bottom: 2rpx solid #F5F5F5; }
-.comment-user { display: flex; align-items: center; margin-bottom: 8rpx; }
-.comment-avatar { width: 40rpx; height: 40rpx; border-radius: 50%; background: #5B9BD5; color: #fff; font-size: 20rpx; text-align: center; line-height: 40rpx; margin-right: 12rpx; }
-.comment-name { font-size: 26rpx; color: #333; font-weight: 500; }
-.comment-time { font-size: 20rpx; color: #999; margin-left: 12rpx; }
-.comment-content { font-size: 26rpx; color: #444; line-height: 1.5; padding-left: 52rpx; }
-.comment-actions { padding-left: 52rpx; margin-top: 8rpx; }
-.comment-reply-btn { font-size: 22rpx; color: #5B9BD5; }
-.reply-list { padding-left: 52rpx; margin-top: 12rpx; background: #FAFAFA; border-radius: 8rpx; padding: 12rpx; }
-.reply-item { padding: 8rpx 0; }
-.reply-user { display: flex; align-items: center; }
-.reply-avatar { width: 32rpx; height: 32rpx; border-radius: 50%; background: #94A3B8; color: #fff; font-size: 16rpx; text-align: center; line-height: 32rpx; margin-right: 8rpx; }
-.reply-name { font-size: 24rpx; color: #333; }
-.reply-time { font-size: 18rpx; color: #999; margin-left: 8rpx; }
-.reply-to { font-size: 18rpx; color: #5B9BD5; margin-left: 8rpx; }
-.reply-content { font-size: 24rpx; color: #444; padding-left: 40rpx; margin-top: 4rpx; }
-
-/* 作者审核区 */
-.review-section { padding: 30rpx; border-top: 16rpx solid #FFF3E0; background: #FFFBF5; }
-.review-header { margin-bottom: 16rpx; }
-.review-title { font-size: 28rpx; font-weight: 700; color: #E65100; }
-.review-item { padding: 16rpx 0; border-bottom: 2rpx solid #F5F5F5; }
-.review-user { display: flex; align-items: center; margin-bottom: 8rpx; }
-.review-avatar { width: 40rpx; height: 40rpx; border-radius: 50%; background: #E65100; color: #fff; font-size: 20rpx; text-align: center; line-height: 40rpx; margin-right: 12rpx; }
-.review-name { font-size: 26rpx; color: #333; font-weight: 500; }
-.review-time { font-size: 20rpx; color: #999; margin-left: 12rpx; }
-.review-content { font-size: 26rpx; color: #444; line-height: 1.5; padding-left: 52rpx; }
-.review-actions { display: flex; gap: 16rpx; padding-left: 52rpx; margin-top: 8rpx; }
-.review-btn { height: 56rpx; line-height: 56rpx; font-size: 24rpx; border-radius: 28rpx; padding: 0 24rpx; border: none; min-width: 100rpx; text-align: center; }
-.review-btn.approve { background: #10B981; color: #fff; }
-.review-btn.reject { background: #EF4444; color: #fff; }
-.review-btn::after { border: none; }
-
 .detail-footer { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 20rpx 30rpx; display: flex; align-items: center; gap: 16rpx; box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.06); z-index: 10; }
 .share-btn { height: 72rpx; line-height: 72rpx; background: #f5f5f5; color: #666; font-size: 24rpx; border-radius: 36rpx; padding: 0 24rpx; display: flex; align-items: center; border: none; }
 .share-btn::after { border: none; }

+ 4 - 8
cfc-frontend/pages/body-detail/health-report.vue

@@ -177,11 +177,11 @@
     <!-- 底部占位 -->
     <view class="bottom-spacer"></view>
 
-    <!-- 营养分析入口按钮 -->
+    <!-- 营养分析入口按钮(已屏蔽)
     <view class="nutrition-fab" @click="goNutritionChat" v-if="report">
       <text class="fab-icon">🥗</text>
       <text class="fab-text">精准营养分析</text>
-    </view>
+    </view> -->
   </view>
 </template>
 
@@ -404,12 +404,8 @@ export default {
     goBack: function() {
       uni.navigateBack()
     },
-    goNutritionChat: function() {
-      if (!this.reportId) return
-      uni.navigateTo({
-        url: '/pages/ai/chat?mode=nutrition&reportId=' + this.reportId + '&firstMessage=' + encodeURIComponent('请根据这份菌群检测报告,分析我孩子的营养状况并提出建议')
-      })
-    },
+    // AI问答功能已屏蔽
+    goNutritionChat: function() {},
     loadDeficiencyRecords: function() {
       if (!this.childId) return
       var self = this

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

@@ -183,7 +183,7 @@ export default {
         { icon: '\u{1F634}', label: '作息', needLogin: true, page: '/pages/body/sleep-record' },
         { icon: '\u{1F9D8}', label: '冥想', needLogin: true, page: '/pages/health/meditation-index' },
         { icon: '\u{1F4CA}', label: '舌诊', needLogin: true, page: '/pages/health/report-upload?reportType=tongue' },
-        { icon: '\u{1F3E5}', label: '健康管家', needLogin: true, page: '/pages/ai/chat?mode=butler&fromPage=pages/body/index' },
+        // AI问答功能已屏蔽: { icon: '\u{1F3E5}', label: '健康管家', needLogin: true, page: '/pages/ai/chat?mode=butler&fromPage=pages/body/index' },
         { icon: '\u{1F9EC}', label: '健康维度', needLogin: true, page: 'health-dimensions' }
       ]
     }

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

@@ -389,12 +389,8 @@ export default {
     goToIndicators: function() {
       this.accordionOpen.nutri = true
     },
-    goAIChat: function() {
-      if (!this.reportId) return
-      uni.navigateTo({
-        url: '/pages/ai/chat?mode=nutrition&reportId=' + this.reportId + '&firstMessage=' + encodeURIComponent('请根据这份肠道菌群检测报告,分析我孩子的肠道健康状况并提出建议')
-      })
-    }
+    // AI问答功能已屏蔽
+    goAIChat: function() {}
   }
 }
 </script>

+ 4 - 8
cfc-frontend/pages/health/physical-exam-detail.vue

@@ -71,11 +71,11 @@
 
 <view class="bottom-spacer"></view>
 
-<!-- AI 解读入口 -->
+<!-- AI 解读入口(已屏蔽)
 <view class="ai-fab" @click="goAIChat" v-if="report && reportId">
   <text class="fab-icon">🤖</text>
   <text class="fab-text">AI 解读报告</text>
-</view>
+</view> -->
 </view>
 </template>
 
@@ -200,12 +200,8 @@ export default {
       }
       return ''
     },
-goAIChat: function() {
-  if (!this.reportId) return
-  uni.navigateTo({
-    url: '/pages/ai/chat?mode=nutrition&reportId=' + this.reportId + '&firstMessage=' + encodeURIComponent('请根据这份体检报告,分析我孩子的健康状况并提出建议')
-  })
-},
+// AI问答功能已屏蔽
+goAIChat: function() {},
 goBack: function() {
   uni.navigateBack()
 }

+ 5 - 4
cfc-frontend/pages/health/report-survey.vue

@@ -622,10 +622,11 @@ export default {
           uni.hideLoading()
           if (res.code === 200) {
             uni.showToast({ title: '提交成功', icon: 'success' })
-            setTimeout(function() {
-              var params = 'reportId=' + self.reportId + '&surveyId=' + self.surveyId + '&firstMessage=' + encodeURIComponent('请根据报告和问卷信息,提供个性化的健康建议')
-              uni.redirectTo({ url: '/pages/ai/chat?' + params })
-            }, 500)
+            // AI问答功能已屏蔽
+            // setTimeout(function() {
+            //   var params = 'reportId=' + self.reportId + '&surveyId=' + self.surveyId + '&firstMessage=' + encodeURIComponent('请根据报告和问卷信息,提供个性化的健康建议')
+            //   uni.redirectTo({ url: '/pages/ai/chat?' + params })
+            // }, 500)
           } else {
             uni.showToast({ title: res.message || '提交失败', icon: 'none' })
             self.submitting = false

+ 110 - 426
cfc-frontend/pages/profile-extra/family-members.vue

@@ -1,123 +1,34 @@
 <template>
   <view class="container">
-    <!-- ===== 家庭信息头部 (带齿轮入口) ===== -->
+    <!-- ===== 家庭信息头部 ===== -->
     <view class="family-header">
       <view class="family-header-content">
-        <view class="family-header-icon">🏠</view>
+        <text class="family-header-icon">🏠</text>
         <view class="family-header-text">
           <text class="family-name">{{ familyName || '我的家庭' }}</text>
           <text class="family-member-count">{{ members.length }} 位成员</text>
         </view>
-        <view class="header-settings-btn" @click="showSettings = true">
-          <text class="settings-gear-icon">⚙️</text>
-        </view>
       </view>
     </view>
 
-    <!-- ===== 关系标签筛选项 ===== -->
-    <view class="filter-tabs">
-      <view class="filter-tab" :class="{active: filterType === 'all'}" @click="filterType = 'all'">全部</view>
-      <view class="filter-tab" :class="{active: filterType === 'parent'}" @click="filterType = 'parent'">家长</view>
-      <view class="filter-tab" :class="{active: filterType === 'child'}" @click="filterType = 'child'">孩子</view>
-    </view>
-
-    <!-- ===== 成员列表(点击行→详情弹窗) ===== -->
-    <view class="member-list">
-      <view class="member-item" v-for="m in filteredMembers" :key="m.id" @click="showMemberDetail(m)">
+    <!-- ===== 成员卡片列表 ===== -->
+    <view class="member-list" v-if="members.length > 0">
+      <view class="member-card" v-for="m in members" :key="m.id" @click="showMemberDetail(m)">
         <image class="member-avatar" :src="m.avatar || '/static/default-avatar.png'" mode="aspectFill"></image>
-        <view class="member-detail">
+        <view class="member-info">
           <text class="member-name">{{ m.nickname || '未设置' }}</text>
-          <view class="member-tags">
-            <text class="member-role-tag">{{ m.relativeLabel || m.roleLabel || (m.effectiveRole === 'parent' ? '家长' : '孩子') }}</text>
-            <text class="member-source-tag" v-if="m.source === 'family_member'">手动添加</text>
-            <text class="member-extra" v-if="m.age"> {{ m.age }}岁</text>
-            <text class="member-extra" v-if="m.gender"> · {{ m.gender === 'male' ? '男' : '女' }}</text>
-          </view>
-          <!-- 三维评分进度条 -->
-          <view class="member-quality-scores" v-if="memberScores[m.id]">
-            <view class="score-item">
-              <text class="score-label">信任</text>
-              <view class="score-bar-bg">
-                <view class="score-bar-fill trust" :style="{width: (memberScores[m.id].trustScore || 0) + '%'}"></view>
-              </view>
-              <text class="score-value">{{ Math.round(memberScores[m.id].trustScore || 0) }}</text>
-            </view>
-            <view class="score-item">
-              <text class="score-label">亲密</text>
-              <view class="score-bar-bg">
-                <view class="score-bar-fill intimacy" :style="{width: (memberScores[m.id].intimacyScore || 0) + '%'}"></view>
-              </view>
-              <text class="score-value">{{ Math.round(memberScores[m.id].intimacyScore || 0) }}</text>
-            </view>
-            <view class="score-item">
-              <text class="score-label">沟通</text>
-              <view class="score-bar-bg">
-                <view class="score-bar-fill communication" :style="{width: (memberScores[m.id].communicationScore || 0) + '%'}"></view>
-              </view>
-              <text class="score-value">{{ Math.round(memberScores[m.id].communicationScore || 0) }}</text>
-            </view>
-          </view>
+          <text class="member-role-tag">{{ m.relativeLabel || m.roleLabel || (m.effectiveRole === 'parent' ? '家长' : '孩子') }}</text>
         </view>
         <text class="member-arrow">›</text>
       </view>
-      <!-- 空状态:还没有家庭成员 -->
-      <view v-if="members.length === 0" class="empty-state-card">
-        <text class="empty-icon">👨‍👩‍👧‍👦</text>
-        <text class="empty-title">还没有家庭成员</text>
-        <view class="empty-actions">
-          <view class="empty-action-btn add-btn" @click="onAddMember">+ 添加</view>
-          <view class="empty-action-btn invite-btn" @click="onInvite">📨 邀请</view>
-        </view>
-      </view>
-      <view v-else-if="filteredMembers.length === 0" class="empty-row">
-        <text class="empty-text">暂无{{ filterType === 'parent' ? '家长' : '孩子' }}成员</text>
-      </view>
     </view>
 
-    <!-- ===== Settings 弹窗(齿轮入口) ===== -->
-    <view class="modal-mask" v-if="showSettings" @click="showSettings = false">
-      <view class="settings-card" @click.stop>
-        <view class="settings-header">
-          <text class="settings-title">成员管理</text>
-          <text class="settings-close" @click="showSettings = false">×</text>
-        </view>
-        <view class="settings-body">
-          <view class="settings-item" @click="onAddMember">
-            <text class="settings-item-icon">+</text>
-            <text class="settings-item-text">添加成员</text>
-          </view>
-          <view class="settings-item" @click="onInvite">
-            <text class="settings-item-icon">📨</text>
-            <text class="settings-item-text">邀请成员</text>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <!-- ===== 邀请卡片弹窗 ===== -->
-    <view class="modal-mask" v-if="showInviteCard" @click="showInviteCard = false">
-      <view class="invite-card" @click.stop>
-        <text class="invite-card-title">邀请家庭成员</text>
-        <text class="invite-card-family">{{ familyName }}</text>
-        <view class="invite-qr-wrap" v-if="inviteQrCode">
-          <image class="invite-qr" :src="inviteQrCode" mode="widthFix" />
-        </view>
-        <view class="invite-qr-wrap loading-qr" v-else>
-          <text class="loading-text">生成二维码中...</text>
-        </view>
-        <view class="invite-code-section">
-          <text class="invite-code-label">邀请码</text>
-          <view class="invite-code-row">
-            <text class="invite-code">{{ inviteCode }}</text>
-            <button class="copy-btn" @click="onCopyCode">复制</button>
-          </view>
-        </view>
-        <text class="invite-tip">让好友在「我的」页面输入邀请码加入家庭</text>
-        <view class="invite-actions">
-          <button class="share-card-btn" @click="shareInviteCard">📤 分享邀请卡</button>
-          <button class="close-btn" @click="showInviteCard = false">关闭</button>
-        </view>
-      </view>
+    <!-- ===== 空状态 ===== -->
+    <view v-if="members.length === 0" class="empty-state">
+      <text class="empty-icon">👨‍👩‍👧‍👦</text>
+      <text class="empty-title">还没有家庭成员</text>
+      <text class="empty-desc">邀请家人加入,一起记录成长</text>
+      <button class="empty-invite-btn" @click="onInvite">📨 邀请家人</button>
     </view>
 
     <!-- ===== 分享邀请卡海报 ===== -->
@@ -179,6 +90,12 @@
       </view>
     </view>
 
+    <!-- ===== 底部操作栏 ===== -->
+    <view class="bottom-bar">
+      <button class="bottom-btn btn-add" @click="onAddMember">+ 添加成员</button>
+      <button class="bottom-btn btn-invite" @click="onInvite">📨 邀请成员</button>
+    </view>
+
     <!-- ===== 加载状态 ===== -->
     <view v-if="loading" class="loading-wrap">
       <text class="loading-text">加载中...</text>
@@ -211,9 +128,6 @@ export default {
       members: [],
       isAdmin: false,
       loading: false,
-      filterType: 'all',
-      showSettings: false,
-      showInviteCard: false,
       showPoster: false,
       showMemberDetailModal: false,
       detailMember: null,
@@ -224,11 +138,7 @@ export default {
   },
   computed: {
     filteredMembers() {
-      var self = this
-      if (this.filterType === 'all') return this.members
-      return this.members.filter(function(m) {
-        return m.effectiveRole === self.filterType
-      })
+      return this.members
     },
     detailMemberScores() {
       if (!this.detailMember) return {}
@@ -292,12 +202,10 @@ export default {
       this.showMemberDetailModal = true
     },
     canEditMember(member) {
-      // 可编辑规则:非登录用户(无userId关联)
       if (member.userId) return false
       return member.source === 'family_member'
     },
     onAddMember() {
-      this.showSettings = false
       uni.navigateTo({ url: '/pages/family/add-member' })
     },
     async onSwitchMember(member) {
@@ -347,36 +255,26 @@ export default {
       }
     },
     async onInvite() {
-      this.showSettings = false
-      this.showInviteCard = true
       this.inviteQrCode = ''
+      this.inviteCode = ''
+      uni.showLoading({ title: '生成邀请...' })
       try {
         var res = await generateFamilyInviteQrCode()
         if (res.code === 200 && res.data) {
           this.inviteCode = res.data.inviteCode
           this.inviteQrCode = res.data.qrCodeBase64
+          this.showPoster = true
         } else {
           uni.showToast({ title: res.message || '生成失败', icon: 'none' })
         }
       } catch (e) {
         uni.showToast({ title: '生成失败', icon: 'none' })
+      } finally {
+        uni.hideLoading()
       }
     },
-    onCopyCode() {
-      uni.setClipboardData({
-        data: this.inviteCode,
-        success: function() {
-          uni.showToast({ title: '已复制邀请码', icon: 'success' })
-        }
-      })
-    },
-    shareInviteCard() {
-      this.showInviteCard = false
-      this.showPoster = true
-    },
     onEditRelation(member) {
       this.showMemberDetailModal = false
-      // 已整合到家庭成员管理,跳转到添加/编辑成员页面
       var params = 'memberId=' + member.id + '&nickname=' + encodeURIComponent(member.nickname || '')
       if (member.gender) params += '&gender=' + member.gender
       if (member.phone) params += '&phone=' + member.phone
@@ -397,32 +295,35 @@ export default {
 
 <style>
 .container {
-  padding: 30rpx;
+  padding: 30rpx 30rpx 140rpx;
   min-height: 100vh;
-  background: #f5f7fa;
+  background: #F5F0EB;
 }
 
-/* ===== 家庭信息头部(带齿轮入口) ===== */
+/* ===== 家庭信息头部 ===== */
 .family-header {
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  background: linear-gradient(135deg, #F97316, #EA580C);
   border-radius: 20rpx;
-  padding: 30rpx 30rpx;
-  margin-bottom: 20rpx;
+  padding: 36rpx 30rpx;
+  margin-bottom: 24rpx;
   color: #fff;
+  text-align: center;
 }
 .family-header-content {
   display: flex;
   align-items: center;
+  justify-content: center;
+  flex-direction: column;
 }
 .family-header-icon {
   font-size: 48rpx;
-  margin-right: 16rpx;
+  margin-bottom: 8rpx;
 }
 .family-header-text {
-  flex: 1;
+  text-align: center;
 }
 .family-name {
-  font-size: 34rpx;
+  font-size: 36rpx;
   font-weight: bold;
   display: block;
 }
@@ -432,107 +333,49 @@ export default {
   margin-top: 4rpx;
   display: block;
 }
-.header-settings-btn {
-  width: 64rpx;
-  height: 64rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  border-radius: 50%;
-  background: rgba(255,255,255,0.2);
-}
-.header-settings-btn:active {
-  background: rgba(255,255,255,0.35);
-}
-.settings-gear-icon {
-  font-size: 36rpx;
-}
 
-/* ===== 筛选标签 ===== */
-.filter-tabs {
-  display: flex;
-  gap: 12rpx;
-  margin-bottom: 20rpx;
-}
-.filter-tab {
-  padding: 8rpx 28rpx;
-  background: #fff;
-  border-radius: 30rpx;
-  font-size: 26rpx;
-  color: #666;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
-}
-.filter-tab.active {
-  background: #667eea;
-  color: #fff;
-  font-weight: 500;
-}
-.filter-tab:active {
-  opacity: 0.8;
-}
-
-/* ===== 成员列表 ===== */
+/* ===== 成员卡片 ===== */
 .member-list {
-  background: #fff;
-  border-radius: 16rpx;
-  overflow: hidden;
-  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
+  padding-bottom: 20rpx;
 }
-.member-item {
+.member-card {
   display: flex;
   align-items: center;
-  padding: 24rpx 30rpx;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-.member-item:last-child {
-  border-bottom: none;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx 28rpx;
+  margin-bottom: 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
 }
-.member-item:active {
-  background: #f8f8ff;
+.member-card:active {
+  background: #FFFCF5;
 }
 .member-avatar {
-  width: 72rpx;
-  height: 72rpx;
+  width: 80rpx;
+  height: 80rpx;
   border-radius: 50%;
   margin-right: 20rpx;
   background: #f0f0f0;
   flex-shrink: 0;
 }
-.member-detail {
+.member-info {
   flex: 1;
   min-width: 0;
 }
 .member-name {
   font-size: 30rpx;
-  font-weight: 500;
+  font-weight: 600;
   display: block;
-}
-.member-tags {
-  display: flex;
-  align-items: center;
-  margin-top: 6rpx;
-  gap: 8rpx;
-  flex-wrap: wrap;
+  color: #333;
 }
 .member-role-tag {
-  font-size: 22rpx;
-  color: #667eea;
-  background: #eef0ff;
-  padding: 2rpx 12rpx;
-  border-radius: 6rpx;
-}
-.member-source-tag {
+  display: inline-block;
   font-size: 22rpx;
   color: #F97316;
-  background: #fff7ed;
-  padding: 2rpx 12rpx;
-  border-radius: 6rpx;
-}
-.member-extra {
-  font-size: 24rpx;
-  color: #999;
-  margin-top: 4rpx;
-  display: inline;
+  background: #FFF7ED;
+  padding: 4rpx 14rpx;
+  border-radius: 8rpx;
+  margin-top: 6rpx;
 }
 .member-arrow {
   font-size: 36rpx;
@@ -540,93 +383,76 @@ export default {
   margin-left: 12rpx;
   flex-shrink: 0;
 }
-.empty-row {
-  padding: 40rpx 0;
-  text-align: center;
-}
-.empty-text {
-  font-size: 26rpx;
-  color: #bbb;
-}
 
-/* ===== 空状态卡片(无家庭成员时显示) ===== */
-.empty-state-card {
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 60rpx 30rpx 40rpx;
+/* ===== 空状态 ===== */
+.empty-state {
   text-align: center;
-  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
+  padding: 100rpx 30rpx;
 }
 .empty-icon {
-  font-size: 80rpx;
+  font-size: 100rpx;
   display: block;
-  margin-bottom: 16rpx;
+  margin-bottom: 20rpx;
 }
 .empty-title {
-  font-size: 30rpx;
+  font-size: 32rpx;
   color: #666;
   display: block;
-  margin-bottom: 32rpx;
-}
-.empty-actions {
-  display: flex;
-  justify-content: center;
-  gap: 20rpx;
-}
-.empty-action-btn {
-  padding: 16rpx 40rpx;
-  border-radius: 12rpx;
-  font-size: 28rpx;
-  font-weight: 500;
+  margin-bottom: 12rpx;
 }
-.empty-action-btn:active {
-  opacity: 0.8;
+.empty-desc {
+  font-size: 26rpx;
+  color: #999;
+  display: block;
+  margin-bottom: 40rpx;
 }
-.add-btn {
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+.empty-invite-btn {
+  background: linear-gradient(135deg, #F97316, #EA580C);
   color: #fff;
+  border: none;
+  border-radius: 40rpx;
+  padding: 20rpx 50rpx;
+  font-size: 28rpx;
+  line-height: 1.4;
 }
-.invite-btn {
-  background: #f0f9ff;
-  color: #667eea;
+.empty-invite-btn:active {
+  opacity: 0.85;
 }
 
-/* ===== 三维评分进度条(列表行内) ===== */
-.member-quality-scores {
-  display: flex;
-  flex-direction: column;
-  gap: 6rpx;
-  padding: 8rpx 0;
-}
-.score-item {
+/* ===== 底部操作栏 ===== */
+.bottom-bar {
+  position: fixed;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  padding: 16rpx 30rpx calc(16rpx + constant(safe-area-inset-bottom));
+  padding: 16rpx 30rpx calc(16rpx + env(safe-area-inset-bottom));
+  background: #fff;
   display: flex;
-  align-items: center;
-  gap: 8rpx;
-}
-.score-label {
-  width: 56rpx;
-  font-size: 20rpx;
-  color: #999;
+  gap: 20rpx;
+  box-shadow: 0 -2rpx 12rpx rgba(0,0,0,0.06);
+  z-index: 50;
 }
-.score-bar-bg {
+.bottom-btn {
   flex: 1;
-  height: 10rpx;
-  background: #f0f0f0;
-  border-radius: 5rpx;
-  overflow: hidden;
+  height: 80rpx;
+  line-height: 80rpx;
+  border-radius: 40rpx;
+  font-size: 28rpx;
+  text-align: center;
+  border: none;
 }
-.score-bar-fill {
-  height: 100%;
-  border-radius: 5rpx;
+.bottom-btn:active {
+  opacity: 0.85;
 }
-.score-bar-fill.trust { background: #10B981; }
-.score-bar-fill.intimacy { background: #FF6B9D; }
-.score-bar-fill.communication { background: #6366F1; }
-.score-value {
-  width: 44rpx;
-  font-size: 18rpx;
-  color: #999;
-  text-align: right;
+.btn-add {
+  background: #fff;
+  color: #F97316;
+  border: 2rpx solid #F97316;
+}
+.btn-invite {
+  background: linear-gradient(135deg, #F97316, #EA580C);
+  color: #fff;
 }
 
 /* ===== 弹窗遮罩 ===== */
@@ -643,148 +469,6 @@ export default {
   z-index: 100;
 }
 
-/* ===== Settings 弹窗 ===== */
-.settings-card {
-  background: #fff;
-  border-radius: 24rpx;
-  width: 500rpx;
-  overflow: hidden;
-}
-.settings-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 30rpx 30rpx 0;
-}
-.settings-title {
-  font-size: 32rpx;
-  font-weight: bold;
-}
-.settings-close {
-  font-size: 44rpx;
-  color: #999;
-  padding: 4rpx 8rpx;
-}
-.settings-body {
-  padding: 20rpx 30rpx 30rpx;
-}
-.settings-item {
-  display: flex;
-  align-items: center;
-  padding: 22rpx 16rpx;
-  border-radius: 12rpx;
-  margin-bottom: 8rpx;
-}
-.settings-item:active {
-  background: #f5f5f5;
-}
-.settings-item-icon {
-  font-size: 32rpx;
-  margin-right: 16rpx;
-  width: 40rpx;
-  text-align: center;
-}
-.settings-item-text {
-  font-size: 28rpx;
-  color: #333;
-}
-
-/* ===== 邀请卡片 ===== */
-.invite-card {
-  background: #fff;
-  border-radius: 24rpx;
-  padding: 40rpx;
-  width: 580rpx;
-  text-align: center;
-}
-.invite-card-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  display: block;
-}
-.invite-card-family {
-  font-size: 28rpx;
-  color: #667eea;
-  display: block;
-  margin-top: 8rpx;
-}
-.invite-qr-wrap {
-  margin: 30rpx auto;
-  width: 320rpx;
-  height: 320rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-.invite-qr {
-  width: 320rpx;
-  height: 320rpx;
-}
-.loading-qr {
-  background: #f5f5f5;
-  border-radius: 16rpx;
-}
-.invite-code-section {
-  margin: 20rpx 0;
-}
-.invite-code-label {
-  font-size: 24rpx;
-  color: #999;
-  display: block;
-  margin-bottom: 8rpx;
-}
-.invite-code-row {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  gap: 16rpx;
-}
-.invite-code {
-  font-size: 48rpx;
-  font-weight: bold;
-  letter-spacing: 6rpx;
-  color: #333;
-}
-.copy-btn {
-  font-size: 24rpx;
-  color: #667eea;
-  background: #eef0ff;
-  border: none;
-  padding: 8rpx 20rpx;
-  border-radius: 8rpx;
-  line-height: 1.5;
-}
-.invite-tip {
-  font-size: 24rpx;
-  color: #94A3B8;
-  text-align: center;
-  margin: 20rpx 0;
-}
-.invite-actions {
-  display: flex;
-  flex-direction: column;
-  gap: 12rpx;
-  padding: 0 30rpx 20rpx;
-}
-.share-card-btn {
-  background: #F97316;
-  color: #FFFFFF;
-  border: none;
-  border-radius: 12rpx;
-  padding: 20rpx;
-  font-size: 28rpx;
-  text-align: center;
-}
-.close-btn {
-  width: 100%;
-  background: #f5f5f5;
-  border: none;
-  border-radius: 12rpx;
-  padding: 18rpx 0;
-  font-size: 28rpx;
-  color: #666;
-}
-
 /* ===== 成员详情弹窗 ===== */
 .member-detail-card {
   background: #fff;
@@ -818,7 +502,7 @@ export default {
 }
 .detail-relation {
   font-size: 26rpx;
-  color: #667eea;
+  color: #F97316;
   margin-top: 4rpx;
   display: block;
 }
@@ -853,7 +537,7 @@ export default {
   display: block;
   margin-bottom: 12rpx;
   padding-left: 12rpx;
-  border-left: 4rpx solid #667eea;
+  border-left: 4rpx solid #F97316;
 }
 .detail-score-item {
   display: flex;
@@ -939,4 +623,4 @@ export default {
   font-size: 28rpx;
   color: #999;
 }
-</style>
+</style>

+ 27 - 31
cfc-frontend/pages/wealth/index.vue

@@ -3,13 +3,23 @@
     <!-- TabTransition overlay -->
     <tab-transition v-if="showTabTransition" dimCode="wealth" ref="tabTransition" @close="showTabTransition = false" />
 
-    <!-- 未登录 -->
-    <view v-if="!isLoggedIn" class="login-prompt">
-      <view class="prompt-icon">👤</view>
-      <text class="prompt-title">登录浠艾福</text>
-      <text class="prompt-desc">登录后查看财富能量详情</text>
-      <button class="login-btn" @click="goLogin">登录 / 注册</button>
-    </view>
+    <!-- 未登录时显示精简内容 -->
+    <template v-if="!isLoggedIn">
+      <PageBanner theme="wealth"
+        tagline="利他创富 · 价值共享"
+        quote="富足源于利他与坚持" />
+
+      <!-- 商品推荐 -->
+      <DimensionProductList
+        dimensionCode="wealth"
+        title="为你推荐" />
+
+      <!-- 登录引导 -->
+      <view class="login-guide-section">
+        <text class="login-guide-text">登录后查看财富能量、推广收益、邀请返利等更多内容</text>
+        <button class="login-btn" @click="goLogin">登录 / 注册</button>
+      </view>
+    </template>
 
     <template v-else>
       <!-- 品牌头部 -->
@@ -151,7 +161,6 @@
 
       <!-- 商品推荐 -->
       <DimensionProductList
-        v-if="isLoggedIn"
         dimensionCode="wealth"
         :familyId="activeChildId"
         title="为你推荐" />
@@ -713,37 +722,24 @@ export default {
 }
 
 /* 未登录 */
-.login-prompt {
+.login-guide-section {
   display: flex;
   flex-direction: column;
   align-items: center;
-  justify-content: center;
-  min-height: 80vh;
-  padding: 60rpx;
-}
-.prompt-icon {
-  font-size: 120rpx;
-  margin-bottom: 30rpx;
-  width: 160rpx;
-  height: 160rpx;
-  line-height: 160rpx;
-  text-align: center;
-  background: #f5f5f5;
-  border-radius: 50%;
-}
-.prompt-title {
-  font-size: 36rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 16rpx;
+  padding: 60rpx 40rpx;
+  margin: 30rpx;
+  background: #fff;
+  border-radius: 20rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
 }
-.prompt-desc {
+.login-guide-text {
   font-size: 26rpx;
   color: #999;
   text-align: center;
-  margin-bottom: 40rpx;
+  margin-bottom: 30rpx;
+  line-height: 1.6;
 }
-.login-prompt .login-btn {
+.login-guide-section .login-btn {
   width: 60%;
   height: 80rpx;
   line-height: 80rpx;

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-48b9018fdfe9dd63ce356e19c9e67600a8340f78
+4d77b01fc5a652e4c7c7e25b93b1c5c21a2a999d

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

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

+ 1 - 1
cfc-web/package.json

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

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

@@ -4,6 +4,110 @@
 
 ---
 
+## v1.0.682 (2026-07-31)
+
+### Bug 修复
+- add missing List import in ActivityController and DB migrations for dimension_code/family_scope columns
+
+### 其他
+- - DatabaseInitializer: add migrations 138-140 for tasks.dimension_code,
+-   products.family_scope, activities.family_scope (were dead code in migration102)
+- 
+
+
+## v1.0.681 (2026-07-31)
+
+### Bug 修复
+- add missing List import in ActivityController and DB migrations for dimension_code/family_scope columns
+
+### 其他
+- - DatabaseInitializer: add migrations 138-140 for tasks.dimension_code,
+-   products.family_scope, activities.family_scope (were dead code in migration102)
+- 
+
+
+## v1.0.680 (2026-07-31)
+
+### Bug 修复
+- circle接口空字符串解析Long报NumberFormatException
+
+### 其他
+- Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+
+## v1.0.679 (2026-07-31)
+
+### 其他
+- book: 合入「为什么是算法」主轴论述(第1章)并重新生成成书稿
+- - 收束金句:算法不是让你更快,是让你回家
+- - 第2章「为什么叫算法」节精简呼应,避免重复
+- - 成书稿.txt 同步重新生成(16章结构不变)
+- 
+- book: 书名定位升级为「一词三通」(宇宙因果×传统文化×AI)
+- - 竞品调研新增算法/逻辑方法类(人生算法、底层逻辑)与 AI×家庭类(2026 新兴赛道)
+- - 应对策略与结论同步更新:三块空白交汇处正是本书
+- 
+
+
+## v1.0.678 (2026-07-31)
+
+### Bug 修复
+- 富页登录按钮因重复CSS规则min-height:80vh位置偏高
+
+### 其他
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+
+## v1.0.677 (2026-07-31)
+
+### Bug 修复
+- article-detail auto-merge遗留重复方法
+
+### 其他
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+
+## v1.0.676 (2026-07-31)
+
+### Bug 修复
+- article-detail内联表达式导致data-event-opts编译错误
+
+### 其他
+- - @close/click内联赋值 → closePoster/closeQuiz/closeResult
+- - quizAnswers[qi]=... → selectQuizAnswer(qi,oi)
+- Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+
+## v1.0.675 (2026-07-31)
+
+### Bug 修复
+- 未登录可浏览商品/活动/文章详情 + 富页面预登录内容
+
+### 其他
+- wealth/index.vue: 移除全屏登录墙,未登录显示商品推荐+登录引导,登录后显示完整内容
+- 
+
+
+## v1.0.674 (2026-07-31)
+
+### 文档
+- 文章评论审核系统方案与实现计划
+
+### 新功能
+- 文章评论审核前端——RadarChart/article-detail/order-list/article.js/CommentReview
+- 文章评论审核功能——AdminArticleController/ArticleCommentController/ArticleCommentService
+
+### 其他
+- book: 生成成书稿txt(序章→第十三章→终章→附录 全16章拼接)
+- book: 全书一致性修订——补全预告链/数据来源说明,规范引号与格式
+
+
 ## v1.0.673 (2026-07-31)
 
 ### 新功能

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

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.673
+> 当前版本: v1.0.682
 
 ## 历史版本
 
@@ -8,6 +8,110 @@
 
 ---
 
+## v1.0.682 (2026-07-31)
+
+### Bug 修复
+- add missing List import in ActivityController and DB migrations for dimension_code/family_scope columns
+
+### 其他
+- - DatabaseInitializer: add migrations 138-140 for tasks.dimension_code,
+-   products.family_scope, activities.family_scope (were dead code in migration102)
+- 
+
+
+## v1.0.681 (2026-07-31)
+
+### Bug 修复
+- add missing List import in ActivityController and DB migrations for dimension_code/family_scope columns
+
+### 其他
+- - DatabaseInitializer: add migrations 138-140 for tasks.dimension_code,
+-   products.family_scope, activities.family_scope (were dead code in migration102)
+- 
+
+
+## v1.0.680 (2026-07-31)
+
+### Bug 修复
+- circle接口空字符串解析Long报NumberFormatException
+
+### 其他
+- Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+
+## v1.0.679 (2026-07-31)
+
+### 其他
+- book: 合入「为什么是算法」主轴论述(第1章)并重新生成成书稿
+- - 收束金句:算法不是让你更快,是让你回家
+- - 第2章「为什么叫算法」节精简呼应,避免重复
+- - 成书稿.txt 同步重新生成(16章结构不变)
+- 
+- book: 书名定位升级为「一词三通」(宇宙因果×传统文化×AI)
+- - 竞品调研新增算法/逻辑方法类(人生算法、底层逻辑)与 AI×家庭类(2026 新兴赛道)
+- - 应对策略与结论同步更新:三块空白交汇处正是本书
+- 
+
+
+## v1.0.678 (2026-07-31)
+
+### Bug 修复
+- 富页登录按钮因重复CSS规则min-height:80vh位置偏高
+
+### 其他
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+
+## v1.0.677 (2026-07-31)
+
+### Bug 修复
+- article-detail auto-merge遗留重复方法
+
+### 其他
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+
+## v1.0.676 (2026-07-31)
+
+### Bug 修复
+- article-detail内联表达式导致data-event-opts编译错误
+
+### 其他
+- - @close/click内联赋值 → closePoster/closeQuiz/closeResult
+- - quizAnswers[qi]=... → selectQuizAnswer(qi,oi)
+- Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+
+## v1.0.675 (2026-07-31)
+
+### Bug 修复
+- 未登录可浏览商品/活动/文章详情 + 富页面预登录内容
+
+### 其他
+- wealth/index.vue: 移除全屏登录墙,未登录显示商品推荐+登录引导,登录后显示完整内容
+- 
+
+
+## v1.0.674 (2026-07-31)
+
+### 文档
+- 文章评论审核系统方案与实现计划
+
+### 新功能
+- 文章评论审核前端——RadarChart/article-detail/order-list/article.js/CommentReview
+- 文章评论审核功能——AdminArticleController/ArticleCommentController/ArticleCommentService
+
+### 其他
+- book: 生成成书稿txt(序章→第十三章→终章→附录 全16章拼接)
+- book: 全书一致性修订——补全预告链/数据来源说明,规范引号与格式
+
+
 ## v1.0.673 (2026-07-31)
 
 ### 新功能

+ 8 - 8
cfc-web/src/components/DimensionWeightPicker.vue

@@ -54,18 +54,18 @@ export default {
     return {
       dimensions: [
         { code: 'body', name: '身·土', color: '#FF8C42', currentValue: 20 },
-        { code: 'mind', name: '智·金', color: '#6366F1', currentValue: 20 },
+        { code: 'wisdom', name: '智·金', color: '#6366F1', currentValue: 20 },
         { code: 'action', name: '行·木', color: '#10B981', currentValue: 20 },
         { code: 'wealth', name: '富·水', color: '#F59E0B', currentValue: 20 },
-        { code: 'heart', name: '心·火', color: '#FF6B9D', currentValue: 20 }
+        { code: 'mind', name: '心·火', color: '#FF6B9D', currentValue: 20 }
       ],
       presets: [
-        { label: '均衡', weights: { body: 20, mind: 20, action: 20, wealth: 20, heart: 20 } },
-        { label: '偏身', weights: { body: 40, mind: 15, action: 15, wealth: 15, heart: 15 } },
-        { label: '偏智', weights: { body: 15, mind: 40, action: 15, wealth: 15, heart: 15 } },
-        { label: '偏行', weights: { body: 15, mind: 15, action: 40, wealth: 15, heart: 15 } },
-        { label: '偏富', weights: { body: 15, mind: 15, action: 15, wealth: 40, heart: 15 } },
-        { label: '偏心', weights: { body: 15, mind: 15, action: 15, wealth: 15, heart: 40 } }
+        { label: '均衡', weights: { body: 20, wisdom: 20, action: 20, wealth: 20, mind: 20 } },
+        { label: '偏身', weights: { body: 40, wisdom: 15, action: 15, wealth: 15, mind: 15 } },
+        { label: '偏智', weights: { body: 15, wisdom: 40, action: 15, wealth: 15, mind: 15 } },
+        { label: '偏行', weights: { body: 15, wisdom: 15, action: 40, wealth: 15, mind: 15 } },
+        { label: '偏富', weights: { body: 15, wisdom: 15, action: 15, wealth: 40, mind: 15 } },
+        { label: '偏心', weights: { body: 15, wisdom: 15, action: 15, wealth: 15, mind: 40 } }
       ]
     }
   },

+ 6 - 2
cfc-web/src/views/admin/ActivityRegistrationReview.vue

@@ -6,7 +6,7 @@
         <el-select v-model="activityId" placeholder="选择活动" @change="loadList" style="width: 200px;" clearable>
           <el-option v-for="a in activityList" :key="a.id" :label="a.title" :value="a.id" />
         </el-select>
-         <el-input v-model="keyword" placeholder="搜索用户/孩子ID..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="loadList" @clear="loadList" />
+         <el-input v-model="keyword" placeholder="搜索报名人/手机号/用户ID..." prefix-icon="el-icon-search" style="width:220px" clearable @keyup.enter.native="loadList" @clear="loadList" />
          <el-select v-model="statusFilter" placeholder="状态筛选" @change="loadList" style="width: 130px;" clearable>
           <el-option label="全部状态" value="" />
           <el-option label="待审核" value="pending" />
@@ -21,6 +21,8 @@
       <el-table-column prop="id" label="ID" width="60" />
       <el-table-column prop="userId" label="用户ID" width="70" />
       <el-table-column prop="childId" label="孩子ID" width="70" />
+      <el-table-column prop="registrantName" label="报名人" width="100" />
+      <el-table-column prop="registrantPhone" label="手机号" width="120" />
       <el-table-column prop="status" label="状态" width="90">
         <template slot-scope="{ row }">
           <el-tag :type="row.status === 'approved' ? 'success' : row.status === 'rejected' ? 'danger' : 'warning'">
@@ -81,7 +83,9 @@ export default {
       var kw = this.keyword.toLowerCase()
       return this.list.filter(function(item) {
         return (item.userId && String(item.userId).includes(kw)) ||
-               (item.childId && String(item.childId).includes(kw))
+               (item.childId && String(item.childId).includes(kw)) ||
+               (item.registrantName && String(item.registrantName).toLowerCase().includes(kw)) ||
+               (item.registrantPhone && String(item.registrantPhone).includes(kw))
       })
     }
   },

+ 142 - 0
docs/superpowers/specs/2026-07-31-family-members-redesign.md

@@ -0,0 +1,142 @@
+# 家庭成员页面重新设计
+
+## 概述
+
+重新设计小程序家庭成员管理页面,从丑旧的行式列表改为温暖家庭风的卡片式布局,并将"添加成员"和"邀请成员"提升为底部固定按钮,邀请直接弹出海报。
+
+## 当前问题
+
+- 页面视觉老旧(紫色渐变头部 + 简单行式列表)
+- 添加/邀请功能藏在齿轮设置弹窗中,需要额外点击
+- 邀请流程需经过中间邀请码卡片,步骤多
+- 成员行显示三维评分条,信息过重
+
+## 设计方案
+
+### 视觉风格:温暖家庭风
+
+- 主色:暖橙 `#F97316` / 米白 `#FFFCF5` / 暖灰 `#F5F0EB`
+- 卡片圆角 + 柔和阴影,家庭相册感
+- 去掉紫色渐变,使用暖橙渐变头部
+
+### 页面结构
+
+```
+┌─────────────────────────────┐
+│  🏠 幸福之家 · 5位成员        │  ← 暖橙渐变头部
+├─────────────────────────────┤
+│                             │
+│  ┌─────┐                    │
+│  │ 🧑  │ 爸爸 · 家长        │  ← 卡片式成员条目
+│  └─────┘                    │
+│  ┌─────┐                    │
+│  │ 👩  │ 妈妈 · 家长        │
+│  └─────┘                    │
+│  ┌─────┐                    │
+│  │ 👦  │ 小明 · 孩子        │
+│  └─────┘                    │
+│                             │
+│  [空状态: 邀请家人加入引导]     │
+│                             │
+├─────────────────────────────┤
+│  [+ 添加成员] [📨 邀请成员]  │  ← 底部固定栏
+└─────────────────────────────┘
+```
+
+### 组件详细设计
+
+#### 1. 页面头部
+
+- 暖橙渐变背景 `linear-gradient(135deg, #F97316, #EA580C)`
+- 居中布局:🏠 家庭图标 + 家庭名称 + 成员数
+- 白色文字
+- 圆角底部
+
+#### 2. 成员列表
+
+- 白底圆角卡片,柔和阴影
+- 每行包含:
+  - 左侧:头像 80rpx 圆形
+  - 中间:昵称(粗体 30rpx)+ 关系标签(彩色小标签)
+  - 右侧:箭头 `›`
+- 无三维评分条
+- 无筛选标签
+- 点击 → 成员详情弹窗
+- 卡片间间距 16rpx
+
+#### 3. 空状态
+
+- 当成员数为 0 时显示
+- 大图标 👨‍👩‍👧‍👦
+- 文案:"还没有家庭成员"
+- 副文案:"邀请家人加入,一起记录成长"
+- 按钮:「邀请家人」(暖橙填充)
+
+#### 4. 底部固定栏
+
+- 固定在页面底部,安全区适配
+- 两个按钮并排,间距 20rpx
+- 「+ 添加成员」:白色背景 + 暖橙边框/文字,flex:1
+- 「📨 邀请成员」:暖橙渐变填充,白色文字,flex:1
+- 添加 → 跳转 `/pages/family/add-member`
+- 邀请 → 加载 QR 码 → 直接弹出 SharePoster
+
+#### 5. 成员详情弹窗
+
+- 保留现有功能,视觉改造为暖色风格
+- 顶部:头像 + 昵称 + 关系标签
+- 信息区:性别 · 年龄 · 手机号
+- 操作按钮 2 列网格:切换身份 / 编辑资料 / 关系问卷 / 记录互动 / 移出家庭
+
+#### 6. 邀请流程
+
+简化前:
+```
+点击齿轮 → 邀请成员 → 显示邀请码卡片 → 点击分享邀请卡 → 弹出海报
+```
+
+简化后:
+```
+点击「邀请成员」→ 加载 QR 码 → 直接弹出海报
+```
+
+### 移除内容
+
+| 移除项 | 原因 |
+|--------|------|
+| 紫色渐变头部 | 改为暖橙家庭风 |
+| 齿轮设置弹窗 | 功能合并到底部栏 |
+| 筛选标签(全部/家长/孩子) | 精简需求 |
+| 行内三维评分条 | 精简需求 |
+| 中间邀请码卡片 | 直接弹出海报 |
+
+### 保留内容
+
+| 保留项 | 说明 |
+|--------|------|
+| 成员详情弹窗 | 操作入口,保留全部功能 |
+| SharePoster 组件 | 邀请海报,直接触发 |
+| 添加成员页面 | `/pages/family/add-member`,保持不变 |
+| 切换身份/编辑资料/移出家庭等操作 | 在详情弹窗中保留 |
+
+### 数据流
+
+```
+页面加载 → listFamilyMembers() → 渲染成员列表
+点击成员 → showMemberDetail(m) → 详情弹窗
+点击添加 → uni.navigateTo(/pages/family/add-member)
+点击邀请 → generateFamilyInviteQrCode() → 获取 qrCodeBase64 → showPoster = true
+```
+
+### 文件变更清单
+
+| 文件 | 变更类型 | 说明 |
+|------|----------|------|
+| `pages/profile-extra/family-members.vue` | 重写 | 模板/样式/脚本全部重新设计 |
+| `components/SharePoster.vue` | 无变更 | 保持现有,直接调用 |
+
+### 非功能性要求
+
+- 遵循小程序限制:禁止可选链 `?.`、禁止 CSS Grid、禁止 `:key` 表达式
+- 使用 Vue 2 Options API
+- 匹配现有品牌色系

+ 353 - 1
web/articles/plastic-health.html

@@ -531,6 +531,127 @@
     .compare-table tr:last-child td { border-bottom: none; }
     .compare-table .highlight-cell { background: var(--green-light); font-weight: 600; }
 
+    /* ===== Comparison Carousel (Mobile) ===== */
+    .compare-carousel { display: none; }
+    .carousel-track {
+      display: flex;
+      gap: 1rem;
+      overflow-x: auto;
+      scroll-snap-type: x mandatory;
+      -webkit-overflow-scrolling: touch;
+      scrollbar-width: none;
+      padding: 0.5rem 0 1rem;
+    }
+    .carousel-track::-webkit-scrollbar { display: none; }
+    .carousel-card {
+      flex: 0 0 calc(100% - 2rem);
+      scroll-snap-align: start;
+      background: white;
+      border-radius: var(--radius);
+      box-shadow: var(--shadow-card);
+      padding: 1.5rem;
+      border-top: 4px solid #CBD5E0;
+      position: relative;
+    }
+    .carousel-card .card-badge {
+      display: inline-block;
+      font-size: 0.7rem;
+      font-weight: 700;
+      padding: 0.25rem 0.75rem;
+      border-radius: 999px;
+      margin-bottom: 0.75rem;
+      letter-spacing: 0.02em;
+    }
+    .carousel-card .card-badge.danger { background: #FFF5F5; color: #C53030; }
+    .carousel-card .card-badge.success { background: #F0FFF4; color: #276749; }
+    .carousel-card .card-badge.warning { background: #FFFFF0; color: #975A16; }
+    .carousel-card .card-badge.primary { background: #EBF8FF; color: #2B6CB0; }
+    .carousel-card.border-danger { border-top-color: #E53E3E; }
+    .carousel-card.border-success { border-top-color: #38A169; }
+    .carousel-card.border-warning { border-top-color: #DD6B20; }
+    .carousel-card.border-primary { border-top-color: #3182CE; }
+    .carousel-card .card-title {
+      font-size: 1.1rem;
+      font-weight: 700;
+      margin-bottom: 1rem;
+      padding-bottom: 0.75rem;
+      border-bottom: 1px solid #E2E8F0;
+    }
+    .carousel-card .criteria-list { list-style: none; }
+    .carousel-card .criteria-list li {
+      display: flex;
+      justify-content: space-between;
+      align-items: flex-start;
+      padding: 0.6rem 0;
+      border-bottom: 1px dashed #EDF2F7;
+      gap: 0.5rem;
+    }
+    .carousel-card .criteria-list li:last-child { border-bottom: none; }
+    .carousel-card .criterion-label {
+      flex-shrink: 0;
+      font-size: 0.8rem;
+      font-weight: 600;
+      color: var(--text-primary);
+      width: 5.5rem;
+    }
+    .carousel-card .criterion-value {
+      font-size: 0.8rem;
+      color: var(--text-secondary);
+      text-align: right;
+      line-height: 1.5;
+    }
+    .carousel-card .criterion-value .highlight-cell { background: transparent; font-weight: 600; }
+    .carousel-card .card-footer {
+      margin-top: 0.75rem;
+      padding-top: 0.75rem;
+      border-top: 1px solid #E2E8F0;
+      font-size: 0.85rem;
+      font-weight: 700;
+      text-align: center;
+    }
+    .carousel-card .card-footer.danger { color: #C53030; }
+    .carousel-card .card-footer.success { color: #276749; }
+    .carousel-card .card-footer.warning { color: #975A16; }
+    .carousel-card .card-footer.primary { color: #2B6CB0; }
+    .carousel-nav {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 1rem;
+      margin-top: 0.75rem;
+    }
+    .carousel-arrow {
+      width: 36px; height: 36px;
+      border-radius: 50%;
+      border: 1px solid #E2E8F0;
+      background: white;
+      color: var(--text-secondary);
+      font-size: 1.3rem;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      transition: all 0.2s;
+      box-shadow: 0 1px 4px rgba(0,0,0,0.06);
+    }
+    .carousel-arrow:hover { background: var(--bg-alt); color: var(--text-primary); }
+    .carousel-dots {
+      display: flex;
+      gap: 0.5rem;
+    }
+    .carousel-dot {
+      width: 8px; height: 8px;
+      border-radius: 50%;
+      background: #CBD5E0;
+      cursor: pointer;
+      transition: all 0.2s;
+    }
+    .carousel-dot.active {
+      background: var(--blue);
+      width: 24px;
+      border-radius: 4px;
+    }
+
     /* ===== Responsive ===== */
     @media (max-width: 640px) {
       .hero { padding: 4rem 1rem 3rem; min-height: auto; }
@@ -545,6 +666,8 @@
       section { padding: 2.5rem 0; }
       h2 { font-size: 1.4rem; }
       .highlight-box { padding: 1.25rem; }
+      .compare-table-wrapper { display: none; }
+      .compare-carousel { display: block; }
       .compare-table { font-size: 0.8rem; }
       .compare-table th, .compare-table td { padding: 0.5rem 0.75rem; }
     }
@@ -958,7 +1081,7 @@
     <h2>📊 四大饮水方案全面对比</h2>
     <p>基于以上风险分析,我们将办公室常见的四种饮水方案放在同一张表中清晰对比:</p>
 
-    <div style="overflow-x:auto;">
+    <div class="compare-table-wrapper" style="overflow-x:auto;">
     <table class="compare-table">
       <thead>
         <tr>
@@ -1037,6 +1160,186 @@
     </table>
     </div>
 
+    <!-- ===== Mobile Carousel ===== -->
+    <div class="compare-carousel" id="compareCarousel">
+      <div class="carousel-track">
+        <!-- Card 1: 桶装水/瓶装水 -->
+        <div class="carousel-card border-danger" data-index="0">
+          <span class="card-badge danger">❌ 风险最高</span>
+          <div class="card-title">📦 桶装水/瓶装水</div>
+          <ul class="criteria-list">
+            <li>
+              <span class="criterion-label">微塑料</span>
+              <span class="criterion-value">620万颗粒/升,随时间+温度递增<sup><a href="#ref-3">[3]</a></sup></span>
+            </li>
+            <li>
+              <span class="criterion-label">抗生素/污染物</span>
+              <span class="criterion-value">出厂检测有限,难以保证去除</span>
+            </li>
+            <li>
+              <span class="criterion-label">矿物质</span>
+              <span class="criterion-value">含天然矿物质(但伴塑料污染)</span>
+            </li>
+            <li>
+              <span class="criterion-label">BPA/塑化剂</span>
+              <span class="criterion-value">高温暴晒释放BPA、锑、邻苯二甲酸酯</span>
+            </li>
+            <li>
+              <span class="criterion-label">二次污染</span>
+              <span class="criterion-value">饮水机+桶身细菌滋生,密封垫磨损</span>
+            </li>
+            <li>
+              <span class="criterion-label">健康增益</span>
+              <span class="criterion-value">无</span>
+            </li>
+            <li>
+              <span class="criterion-label">致肥/焦虑风险</span>
+              <span class="criterion-value">⚠️ BPA/塑化剂关联致肥+焦虑<sup><a href="#ref-14">[14]</a></sup></span>
+            </li>
+            <li>
+              <span class="criterion-label">综合成本</span>
+              <span class="criterion-value">高:每桶12~22元+饮水机折旧</span>
+            </li>
+          </ul>
+          <div class="card-footer danger">❌ 风险最高</div>
+        </div>
+
+        <!-- Card 2: 自来水(烧开后) -->
+        <div class="carousel-card border-success" data-index="1">
+          <span class="card-badge success">✅ 安全经济之选</span>
+          <div class="card-title">🚰 自来水(烧开后)</div>
+          <ul class="criteria-list">
+            <li>
+              <span class="criterion-label">微塑料</span>
+              <span class="criterion-value">~200万/升(原水),烧开去除84%+<sup><a href="#ref-4">[4]</a></sup></span>
+            </li>
+            <li>
+              <span class="criterion-label">抗生素/污染物</span>
+              <span class="criterion-value" style="color:#C53030;">❌ 烧开无法去除抗生素、农药、避孕药等</span>
+            </li>
+            <li>
+              <span class="criterion-label">矿物质</span>
+              <span class="criterion-value" style="color:#276749;font-weight:600;">✅ 完整保留钙镁等</span>
+            </li>
+            <li>
+              <span class="criterion-label">BPA/塑化剂</span>
+              <span class="criterion-value">无额外污染</span>
+            </li>
+            <li>
+              <span class="criterion-label">二次污染</span>
+              <span class="criterion-value">低(即烧即饮)</span>
+            </li>
+            <li>
+              <span class="criterion-label">健康增益</span>
+              <span class="criterion-value">无(仅为安全饮水)</span>
+            </li>
+            <li>
+              <span class="criterion-label">致肥/焦虑风险</span>
+              <span class="criterion-value" style="color:#276749;font-weight:600;">❌ 无</span>
+            </li>
+            <li>
+              <span class="criterion-label">综合成本</span>
+              <span class="criterion-value" style="color:#276749;font-weight:600;">✅ 极低</span>
+            </li>
+          </ul>
+          <div class="card-footer success">✅ 安全经济之选</div>
+        </div>
+
+        <!-- Card 3: RO反渗透纯水机 -->
+        <div class="carousel-card border-warning" data-index="2">
+          <span class="card-badge warning">⚠️ 缺矿物质</span>
+          <div class="card-title">💧 RO反渗透纯水机</div>
+          <ul class="criteria-list">
+            <li>
+              <span class="criterion-label">微塑料</span>
+              <span class="criterion-value">RO膜孔径0.0001μm,可截留<sup><a href="#ref-16">[16]</a></sup></span>
+            </li>
+            <li>
+              <span class="criterion-label">抗生素/污染物</span>
+              <span class="criterion-value">RO膜可截留大部分大分子有机物</span>
+            </li>
+            <li>
+              <span class="criterion-label">矿物质</span>
+              <span class="criterion-value" style="color:#C53030;">❌ 完全去除所有矿物<sup><a href="#ref-16">[16]</a></sup></span>
+            </li>
+            <li>
+              <span class="criterion-label">BPA/塑化剂</span>
+              <span class="criterion-value">小分子有机物部分可通过RO膜</span>
+            </li>
+            <li>
+              <span class="criterion-label">二次污染</span>
+              <span class="criterion-value">中高:滤芯久不换滋生细菌<sup><a href="#ref-18">[18]</a></sup></span>
+            </li>
+            <li>
+              <span class="criterion-label">健康增益</span>
+              <span class="criterion-value">无(纯水呈弱酸性)</span>
+            </li>
+            <li>
+              <span class="criterion-label">致肥/焦虑风险</span>
+              <span class="criterion-value">不确定(无矿物水可能代谢负担)<sup><a href="#ref-17">[17]</a></sup></span>
+            </li>
+            <li>
+              <span class="criterion-label">综合成本</span>
+              <span class="criterion-value">中高:设备+换芯+废水(1:3~7)</span>
+            </li>
+          </ul>
+          <div class="card-footer warning">⚠️ 缺矿物质</div>
+        </div>
+
+        <!-- Card 4: 电解还原水机 -->
+        <div class="carousel-card border-primary" data-index="3">
+          <span class="card-badge primary">🥇 综合最优解</span>
+          <div class="card-title">⚡ 电解还原水机</div>
+          <ul class="criteria-list">
+            <li>
+              <span class="criterion-label">微塑料</span>
+              <span class="criterion-value" style="color:#276749;font-weight:600;">多层过滤可有效截留</span>
+            </li>
+            <li>
+              <span class="criterion-label">抗生素/污染物</span>
+              <span class="criterion-value" style="color:#276749;font-weight:600;">活性炭+UF过滤有效去除余氯、农残、激素</span>
+            </li>
+            <li>
+              <span class="criterion-label">矿物质</span>
+              <span class="criterion-value" style="color:#276749;font-weight:600;">✅ 完整保留,生物利用度更优</span>
+            </li>
+            <li>
+              <span class="criterion-label">BPA/塑化剂</span>
+              <span class="criterion-value" style="color:#276749;font-weight:600;">有效过滤(活性炭+UF膜吸附)</span>
+            </li>
+            <li>
+              <span class="criterion-label">二次污染</span>
+              <span class="criterion-value">低(定期换芯即可)</span>
+            </li>
+            <li>
+              <span class="criterion-label">健康增益</span>
+              <span class="criterion-value" style="color:#2B6CB0;font-weight:600;">🏆 溶解氢抗氧化(ORP -250~-500mV)<sup><a href="#ref-19">[19]</a></sup></span>
+            </li>
+            <li>
+              <span class="criterion-label">致肥/焦虑风险</span>
+              <span class="criterion-value" style="color:#276749;font-weight:600;">❌ 无</span>
+            </li>
+            <li>
+              <span class="criterion-label">综合成本</span>
+              <span class="criterion-value">中高:设备+换芯</span>
+            </li>
+          </ul>
+          <div class="card-footer primary">🥇 综合最优解</div>
+        </div>
+      </div>
+
+      <div class="carousel-nav">
+        <button class="carousel-arrow carousel-prev" aria-label="上一项">‹</button>
+        <div class="carousel-dots">
+          <span class="carousel-dot active" data-index="0"></span>
+          <span class="carousel-dot" data-index="1"></span>
+          <span class="carousel-dot" data-index="2"></span>
+          <span class="carousel-dot" data-index="3"></span>
+        </div>
+        <button class="carousel-arrow carousel-next" aria-label="下一项">›</button>
+      </div>
+    </div>
+
     <div class="two-col-grid" style="margin-top:1.5rem;">
       <div class="highlight-box green" style="margin:0;">
         <p><strong>自来水烧开</strong>——在微塑料层面是最安全经济的方案,但需注意:<strong>烧开无法去除抗生素、农药残留、避孕药等化学污染物</strong>。若水源存在此类风险,建议搭配活性炭滤水壶或选择过滤净水方案。结论:<strong>预算有限时的最优入门方案,但非完美方案</strong>。</p>
@@ -1297,5 +1600,54 @@
 <!-- Footer (shared) -->
 <div id="footer-placeholder"></div>
 <script src="js/footer.js"></script>
+<script>
+(function(){
+  var track = document.getElementById('compareCarousel');
+  if (!track) return;
+  track = track.querySelector('.carousel-track');
+  var dots = document.querySelectorAll('.carousel-dot');
+  var prev = document.querySelector('.carousel-prev');
+  var next = document.querySelector('.carousel-next');
+  var cards = track.querySelectorAll('.carousel-card');
+  if (!cards.length) return;
+
+  var gap = 16; // 1rem in px
+  var updateDots = function(){
+    var scrollLeft = track.scrollLeft;
+    var cardWidth = cards[0].offsetWidth + gap;
+    var activeIndex = Math.round(scrollLeft / cardWidth);
+    if (activeIndex < 0) activeIndex = 0;
+    if (activeIndex >= dots.length) activeIndex = dots.length - 1;
+    dots.forEach(function(d, i){
+      d.classList.toggle('active', i === activeIndex);
+    });
+  };
+
+  track.addEventListener('scroll', updateDots);
+
+  dots.forEach(function(dot){
+    dot.addEventListener('click', function(){
+      var index = parseInt(this.getAttribute('data-index'));
+      if (cards[index]) {
+        cards[index].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
+      }
+    });
+  });
+
+  if (prev) {
+    prev.addEventListener('click', function(){
+      var cardWidth = cards[0].offsetWidth + gap;
+      track.scrollBy({ left: -cardWidth, behavior: 'smooth' });
+    });
+  }
+
+  if (next) {
+    next.addEventListener('click', function(){
+      var cardWidth = cards[0].offsetWidth + gap;
+      track.scrollBy({ left: cardWidth, behavior: 'smooth' });
+    });
+  }
+})();
+</script>
 </body>
 </html>