Explorar el Código

Merge branch 'cfclub' of http://git.iwintrue.com/liaoxg/cfc into cfclub

E2E Test Bot hace 6 días
padre
commit
7e19245054
Se han modificado 100 ficheros con 2231 adiciones y 1365 borrados
  1. 7 0
      cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
  2. 2 0
      cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java
  3. 4 0
      cfc-backend/src/main/java/com/etotem/cfc/config/WebConfig.java
  4. 12 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/HealthKnowledgeBaseController.java
  5. 0 37
      cfc-backend/src/main/java/com/etotem/cfc/controller/MicrobiomeArticleController.java
  6. 8 3
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminActivityController.java
  7. 20 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminArticleController.java
  8. 34 2
      cfc-backend/src/main/java/com/etotem/cfc/controller/content/ArticleController.java
  9. 43 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/product/ProductController.java
  10. 6 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/ActivityDTO.java
  11. 4 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/ProductDTO.java
  12. 6 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/Activity.java
  13. 0 16
      cfc-backend/src/main/java/com/etotem/cfc/entity/MicrobiomeArticle.java
  14. 0 4
      cfc-backend/src/main/java/com/etotem/cfc/mapper/MicrobiomeArticleMapper.java
  15. 111 5
      cfc-backend/src/main/java/com/etotem/cfc/service/ActivityAdminService.java
  16. 76 7
      cfc-backend/src/main/java/com/etotem/cfc/service/ArticleService.java
  17. 14 0
      cfc-backend/src/main/java/com/etotem/cfc/service/HealthKnowledgeBaseService.java
  18. 48 13
      cfc-backend/src/main/java/com/etotem/cfc/service/ProductRecommendationService.java
  19. 16 5
      cfc-backend/src/main/java/com/etotem/cfc/service/ProductService.java
  20. 3 0
      cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java
  21. 2 0
      cfc-backend/src/main/resources/schema.sql
  22. 169 0
      cfc-backend/src/test/java/com/etotem/cfc/unit/ActivityAuditTest.java
  23. 6 6
      cfc-backend/src/test/java/com/etotem/cfc/unit/ArticleRelatedServiceTest.java
  24. 29 0
      cfc-backend/src/test/java/com/etotem/cfc/unit/ProductDTOMappingTest.java
  25. 99 0
      cfc-backend/src/test/java/com/etotem/cfc/unit/ProductUpdateClearTest.java
  26. 5 5
      cfc-frontend/App.vue
  27. 9 8
      cfc-frontend/components/ArticleBookshelf.vue
  28. 5 5
      cfc-frontend/components/BadgeGrid.vue
  29. 7 7
      cfc-frontend/components/BaseBadge.vue
  30. 3 3
      cfc-frontend/components/BaseLoading.vue
  31. 7 7
      cfc-frontend/components/ConfigurableGuide.vue
  32. 27 26
      cfc-frontend/components/ContentSharePoster.vue
  33. 21 21
      cfc-frontend/components/DimensionActivities.vue
  34. 9 9
      cfc-frontend/components/DimensionArticles.vue
  35. 6 6
      cfc-frontend/components/DimensionIntroCard.vue
  36. 9 9
      cfc-frontend/components/DimensionProducts.vue
  37. 12 12
      cfc-frontend/components/DimensionTasks.vue
  38. 9 9
      cfc-frontend/components/DimensionVisitorCard.vue
  39. 32 32
      cfc-frontend/components/FamilyChallengeCard.vue
  40. 2 2
      cfc-frontend/components/FamilyEmptyState/FamilyEmptyState.vue
  41. 11 11
      cfc-frontend/components/FamilyEnergyBar.vue
  42. 17 17
      cfc-frontend/components/FamilyFeed.vue
  43. 12 12
      cfc-frontend/components/FamilyMemberCard.vue
  44. 24 24
      cfc-frontend/components/FamilyMemberStrip.vue
  45. 7 7
      cfc-frontend/components/FamilyRelationGraph.vue
  46. 12 12
      cfc-frontend/components/GenerationPicker.vue
  47. 9 9
      cfc-frontend/components/HealthBadge.vue
  48. 9 9
      cfc-frontend/components/HealthTimeline.vue
  49. 6 6
      cfc-frontend/components/LoginGuideCard.vue
  50. 11 11
      cfc-frontend/components/OnboardingGuide.vue
  51. 7 7
      cfc-frontend/components/OnboardingProgressBar.vue
  52. 7 6
      cfc-frontend/components/PageBanner.vue
  53. 17 17
      cfc-frontend/components/PlayfulButton.vue
  54. 13 13
      cfc-frontend/components/PlayfulCard.vue
  55. 6 6
      cfc-frontend/components/ProfileBadges.vue
  56. 7 7
      cfc-frontend/components/ProfileGrowth.vue
  57. 3 3
      cfc-frontend/components/RadarChart.vue
  58. 19 19
      cfc-frontend/components/RecommendedFeed.vue
  59. 3 3
      cfc-frontend/components/TrendChart.vue
  60. 22 22
      cfc-frontend/components/address-picker.vue
  61. 11 11
      cfc-frontend/components/daily-task-card.vue
  62. 2 2
      cfc-frontend/components/gate-lock.vue
  63. 13 13
      cfc-frontend/components/hand-signature/hand-signature.vue
  64. 22 22
      cfc-frontend/components/login-sheet.vue
  65. 3 3
      cfc-frontend/components/report-upload-card.vue
  66. 9 9
      cfc-frontend/components/wuxing-sandbox.vue
  67. 8 8
      cfc-frontend/custom-tab-bar/index.vue
  68. 17 17
      cfc-frontend/pages/achievement/index.vue
  69. 13 12
      cfc-frontend/pages/action-detail/components/PearlDiagram.vue
  70. 73 73
      cfc-frontend/pages/activity/activity-detail/activity-detail.vue
  71. 215 63
      cfc-frontend/pages/activity/create.vue
  72. 67 67
      cfc-frontend/pages/activity/index.vue
  73. 16 16
      cfc-frontend/pages/activity/order-pay/order-pay.vue
  74. 51 51
      cfc-frontend/pages/ai/chat.vue
  75. 12 12
      cfc-frontend/pages/ai/components/AITaskCard.vue
  76. 4 4
      cfc-frontend/pages/ai/components/QuotaProgressBar.vue
  77. 10 10
      cfc-frontend/pages/ai/components/UpgradeGuideModal.vue
  78. 154 64
      cfc-frontend/pages/article-center/article-detail.vue
  79. 20 20
      cfc-frontend/pages/article-center/article-edit.vue
  80. 36 36
      cfc-frontend/pages/article-center/index.vue
  81. 14 14
      cfc-frontend/pages/article-center/my-posts.vue
  82. 15 15
      cfc-frontend/pages/assessment-plan/detail.vue
  83. 14 14
      cfc-frontend/pages/assessment-plan/list.vue
  84. 27 27
      cfc-frontend/pages/assessment/apply.vue
  85. 37 37
      cfc-frontend/pages/assessment/execution.vue
  86. 34 34
      cfc-frontend/pages/assessment/purchase.vue
  87. 22 22
      cfc-frontend/pages/assessment/quota.vue
  88. 17 17
      cfc-frontend/pages/assessment/rating.vue
  89. 30 30
      cfc-frontend/pages/assessment/results.vue
  90. 18 18
      cfc-frontend/pages/assessment/upload-report.vue
  91. 14 14
      cfc-frontend/pages/butler/apply.vue
  92. 16 16
      cfc-frontend/pages/butler/select.vue
  93. 10 10
      cfc-frontend/pages/child/rewards.vue
  94. 24 24
      cfc-frontend/pages/child/tasks.vue
  95. 2 2
      cfc-frontend/pages/cognitive-hall.vue
  96. 14 14
      cfc-frontend/pages/cognitive-test/language-test.vue
  97. 22 22
      cfc-frontend/pages/cognitive-test/logic-test.vue
  98. 18 18
      cfc-frontend/pages/cognitive-test/memory-test.vue
  99. 6 6
      cfc-frontend/pages/cognitive-test/perception-test.vue
  100. 17 17
      cfc-frontend/pages/cognitive-test/spatial-test.vue

+ 7 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -3101,6 +3101,9 @@ insertSysConfigSeed("gift_membership_pay_price", "198000", "送会员商品达
         // 迁移387: 下线独立“服务角色申请审核”菜单(申请按角色分流合并进审核中心)
         hideServiceRoleApplicationMenu();
 
+        // 迁移388: activities表添加intro列(活动简介,纯文本短摘要,用于活动列表卡片展示)
+        ensureColumn("activities", "intro", "VARCHAR(200) DEFAULT NULL COMMENT '活动简介(列表卡片展示)'");
+
         // 迁移382: health_status 添加 signature 列(用户手签名,base64 PNG data URL,用于问卷回答确认)
         ensureColumn("health_status", "signature", "MEDIUMTEXT DEFAULT NULL COMMENT '用户手签名(base64 PNG data URL),确认问卷回答准确性'");
 
@@ -3115,6 +3118,10 @@ insertSysConfigSeed("gift_membership_pay_price", "198000", "送会员商品达
         //      平台赠送券(JOIN/POPULATION/MANUAL)无对应兑换记录,退款时不退 CF。
         ensureColumn("family_platform_exchange_record", "family_coupon_id",
                 "BIGINT DEFAULT NULL COMMENT '兑换发放的优惠券实例ID(family_coupon.id), 退款时据此退还CF值'");
+
+        // 迁移388: activities 添加 poster_template_id 列(小程序端创建活动时选择海报模板并保存,详情页生成海报时默认使用)
+        ensureColumn("activities", "poster_template_id",
+                "BIGINT NULL COMMENT '创建时选择的海报模板ID(activity_poster_templates.id, 小程序端海报模板选择)'");
     }
 
     private void migrateHealthDimensionsTable() {

+ 2 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java

@@ -48,6 +48,7 @@ private static final String[] PUBLIC_PATHS = {
          "/api/product/list",
          "/api/product/detail",
          "/api/product/type-list",
+         "/api/product/related-products",
          "/api/mini-game/list",
          "/api/admin-auth/send-code",
          "/api/admin-auth/login",
@@ -60,6 +61,7 @@ private static final String[] PUBLIC_PATHS = {
          "/api/articles/record-read",
          "/api/articles/related-products",
         "/api/articles/related",
+        "/api/articles/related-by-product",
          "/api/growth/**",
          "/api/activity/list",
          "/api/activity/detail",

+ 4 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/WebConfig.java

@@ -111,6 +111,7 @@ public class WebConfig implements WebMvcConfigurer {
                 "/api/product/list",
                 "/api/product/detail",
                 "/api/product/type-list",
+                "/api/product/related-products",
                 "/api/mini-game/list",
                 "/api/articles/list",
                 "/api/articles/detail",
@@ -119,6 +120,7 @@ public class WebConfig implements WebMvcConfigurer {
                 "/api/articles/record-read",
                 "/api/articles/related-products",
                 "/api/articles/related",
+                "/api/articles/related-by-product",
                 "/api/growth/**",
                 "/api/articles/updated-since",
                 "/api/activity/list",
@@ -158,6 +160,7 @@ public class WebConfig implements WebMvcConfigurer {
                 "/api/product/list",
                 "/api/product/detail",
                 "/api/product/type-list",
+                "/api/product/related-products",
                 "/api/mini-game/list",
                 "/api/articles/list",
                 "/api/articles/detail",
@@ -166,6 +169,7 @@ public class WebConfig implements WebMvcConfigurer {
                 "/api/articles/record-read",
                 "/api/articles/related-products",
                 "/api/articles/related",
+                "/api/articles/related-by-product",
                 "/api/growth/**",
                 "/api/activity/list",
                 "/api/activity/detail",

+ 12 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthKnowledgeBaseController.java

@@ -1,5 +1,6 @@
 package com.etotem.cfc.controller;
 
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.entity.HealthKnowledgeBase;
 import com.etotem.cfc.service.HealthKnowledgeBaseService;
@@ -66,6 +67,17 @@ public class HealthKnowledgeBaseController {
         return Result.success(healthKnowledgeBaseService.listAll());
     }
 
+    @Operation(summary = "菌群文章分页列表(item_type=article,供 langgraph 向量库同步,替代 microbiome_article)")
+    @PostMapping("/article/list")
+    public Result<Page<HealthKnowledgeBase>> articleList(@RequestBody(required = false) Map<String, Object> params) {
+        int page = (int) ParamUtils.getLong(params == null ? null : params.get("page"), 1L);
+        int size = (int) ParamUtils.getLong(params == null ? null : params.get("size"), 50L);
+        if (size <= 0 || size > 500) {
+            size = 50;
+        }
+        return Result.success(healthKnowledgeBaseService.pageArticles(page, size));
+    }
+
     @Operation(summary = "新增知识库条目")
     @PostMapping("/add")
     public Result add(@RequestBody HealthKnowledgeBase entity) {

+ 0 - 37
cfc-backend/src/main/java/com/etotem/cfc/controller/MicrobiomeArticleController.java

@@ -1,37 +0,0 @@
-package com.etotem.cfc.controller;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.etotem.cfc.common.Result;
-import com.etotem.cfc.entity.MicrobiomeArticle;
-import com.etotem.cfc.mapper.MicrobiomeArticleMapper;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.web.bind.annotation.*;
-import javax.annotation.Resource;
-import java.util.Map;
-@Tag(name = "菌群知识库文章")
-@RestController
-@RequestMapping("/api/microbiome/article")
-public class MicrobiomeArticleController {
-    @Resource
-    private MicrobiomeArticleMapper microbiomeArticleMapper;
-    @Operation(summary = "获取菌群知识库文章列表(分页)")
-    @PostMapping("/list")
-    public Result<Page<MicrobiomeArticle>> list(@RequestBody Map<String, Object> params) {
-        int page = params.get("page") != null ? Integer.parseInt(params.get("page").toString()) : 1;
-        int size = params.get("size") != null ? Integer.parseInt(params.get("size").toString()) : 1000;
-        LambdaQueryWrapper<MicrobiomeArticle> wrapper = new LambdaQueryWrapper<MicrobiomeArticle>()
-                .eq(MicrobiomeArticle::getStatus, 1);
-        if (params != null) {
-            Object category = params.get("category");
-            if (category != null && !category.toString().isEmpty())
-                wrapper.eq(MicrobiomeArticle::getCategory, category.toString());
-            Object keyword = params.get("keyword");
-            if (keyword != null && !keyword.toString().isEmpty())
-                wrapper.and(w -> w.like(MicrobiomeArticle::getTitle, keyword.toString())
-                        .or().like(MicrobiomeArticle::getContent, keyword.toString()));
-        }
-        wrapper.orderByDesc(MicrobiomeArticle::getCreatedAt);
-        return Result.success(microbiomeArticleMapper.selectPage(new Page<>(page, size), wrapper));
-    }
-}

+ 8 - 3
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminActivityController.java

@@ -6,6 +6,7 @@ import com.etotem.cfc.service.ActivityAdminService;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.util.List;
 import java.util.Map;
 import com.etotem.cfc.util.ParamUtils;
 
@@ -40,8 +41,8 @@ public class AdminActivityController {
     }
 
     /**
-     * 审核活动
-     * @param params action: "publish" | "reject", id: Long
+     * 审核活动(旧入口:ActivityReview.vue 使用)
+     * @param params action: "publish" | "reject", id: Long, dimensionCode: String(发布时必填)
      */
     @PostMapping("/review")
     public Result<String> review(@RequestBody Map<String, Object> params) {
@@ -74,8 +75,12 @@ public class AdminActivityController {
         Long id = ParamUtils.getLong(params.get("id"));
         String action = (String) params.get("action");
         String rejectReason = (String) params.get("rejectReason");
+        String dimensionCode = (String) params.get("dimensionCode");
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> dimensionWeights =
+                (List<Map<String, Object>>) params.get("dimensionWeights");
         if (id == null || action == null) return Result.error("id和action不能为空");
-        activityAdminService.auditActivity(id, action, rejectReason, adminId);
+        activityAdminService.auditActivity(id, action, rejectReason, adminId, dimensionCode, dimensionWeights);
         return Result.success("已审核");
     }
 

+ 20 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminArticleController.java

@@ -18,6 +18,7 @@ import javax.annotation.Resource;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import com.etotem.cfc.util.SortUtil;
@@ -94,10 +95,17 @@ public class AdminArticleController {
         if (!checkTextSafe(content)) {
             return Result.error("文章内容包含敏感信息,发布失败");
         }
+        if (!checkTextSafe((String) body.get("audioText"))) {
+            return Result.error("朗读文本包含敏感信息,发布失败");
+        }
 
         Article article = new Article();
         article.setTitle((String) body.get("title"));
         article.setContent((String) body.get("content"));
+        // 朗读文本(TTS 专用;留空则自动从正文生成)
+        if (body.get("audioText") != null) {
+            article.setAudioText(body.get("audioText").toString());
+        }
         if (body.get("categoryId") != null && !body.get("categoryId").toString().trim().isEmpty()) {
             article.setCategoryId(ParamUtils.getLong(body.get("categoryId")));
         }
@@ -169,10 +177,14 @@ public class AdminArticleController {
         if (!checkTextSafe(content)) {
             return Result.error("文章内容包含敏感信息,更新失败");
         }
+        if (!checkTextSafe((String) body.get("audioText"))) {
+            return Result.error("朗读文本包含敏感信息,更新失败");
+        }
 
         Long articleId = ParamUtils.getLong(body.get("id"));
+        Article existing = null;
         if (articleId != null) {
-            Article existing = articleService.getById(articleId);
+            existing = articleService.getById(articleId);
             if (existing != null && !"draft".equals(existing.getStatus())
                     && !"published".equals(existing.getStatus())
                     && !"withdrawn".equals(existing.getStatus())) {
@@ -226,6 +238,13 @@ public class AdminArticleController {
         } else if (articleTypeVal instanceof String) {
             article.setArticleType((String) articleTypeVal);
         }
+        // 朗读文本(TTS 专用;留空则从正文生成)。变更时作废已生成音频,下次「听文章」重新合成
+        if (body.get("audioText") != null) {
+            article.setAudioText(body.get("audioText").toString());
+            if (existing != null && !Objects.equals(existing.getAudioText(), article.getAudioText())) {
+                article.setAudioUrl("");
+            }
+        }
         articleService.update(article);
 
         return Result.success("更新成功");

+ 34 - 2
cfc-backend/src/main/java/com/etotem/cfc/controller/content/ArticleController.java

@@ -73,7 +73,8 @@ public class ArticleController {
     }
 
     /**
-     * 相关文章:按标签关联(共享标签数优先,发布先后次之),默认 3 篇;无匹配返回空数组
+     * 相关文章:按标签关联(共享标签数优先,发布先后次之),默认 3 篇/页;无匹配返回空数组。
+     * 支持分页:page 从 1 开始,每页 size 篇。
      */
     @PostMapping("/related")
     public Result<List<Article>> related(@RequestBody Map<String, Object> body,
@@ -85,6 +86,37 @@ public class ArticleController {
         }
         int size = body.get("size") != null ? Integer.parseInt(body.get("size").toString()) : 3;
         if (size < 1) size = 1;
+        int page = body.get("page") != null ? Math.max(1, Integer.parseInt(body.get("page").toString())) : 1;
+
+        Long familyId = null;
+        String vendorType = null;
+        if (userId != null) {
+            User user = userMapper.selectById(userId);
+            if (user != null) {
+                familyId = user.getFamilyId();
+                vendorType = user.getVendorType();
+            }
+        }
+
+        List<Article> list = articleService.getRelatedArticles(articleId, size, page, userId, familyId, vendorType);
+        return Result.success(list);
+    }
+
+    /**
+     * 商品详情页相关文章:按商品标签匹配文章(共享标签数优先),支持分页;无匹配返回空数组。
+     * 游客可访问(白名单),未登录无家庭可见性加成。
+     */
+    @PostMapping("/related-by-product")
+    public Result<List<Article>> relatedByProduct(@RequestBody Map<String, Object> body,
+                                                   @RequestAttribute(value = "userId", required = false) Long userId,
+                                                   @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId) {
+        Long productId = ParamUtils.getLong(body.get("productId"));
+        if (productId == null) {
+            return Result.error("productId不能为空");
+        }
+        int size = body.get("size") != null ? Integer.parseInt(body.get("size").toString()) : 3;
+        if (size < 1) size = 1;
+        int page = body.get("page") != null ? Math.max(1, Integer.parseInt(body.get("page").toString())) : 1;
 
         Long familyId = null;
         String vendorType = null;
@@ -96,7 +128,7 @@ public class ArticleController {
             }
         }
 
-        List<Article> list = articleService.getRelatedArticles(articleId, size, userId, familyId, vendorType);
+        List<Article> list = articleService.getRelatedArticlesByProduct(productId, size, page, userId, familyId, vendorType);
         return Result.success(list);
     }
 

+ 43 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/product/ProductController.java

@@ -5,7 +5,10 @@ import com.etotem.cfc.dto.ProductDTO;
 import com.etotem.cfc.dto.ProductListQueryDTO;
 import com.etotem.cfc.entity.Product;
 import com.etotem.cfc.entity.ProductSku;
+import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.PpointService;
+import com.etotem.cfc.service.ProductRecommendationService;
 import com.etotem.cfc.service.ProductService;
 import com.etotem.cfc.service.ProductSkuService;
 import com.alibaba.fastjson.JSON;
@@ -20,6 +23,7 @@ import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -40,6 +44,12 @@ public class ProductController {
     @Resource
     private PpointService ppointService;
 
+    @Resource
+    private ProductRecommendationService productRecommendationService;
+
+    @Resource
+    private UserMapper userMapper;
+
     @PostMapping("/list")
     public Result<Map<String, Object>> list(@RequestBody ProductListQueryDTO query,
                                              @RequestAttribute(value = "userId", required = false) Long userId,
@@ -208,6 +218,39 @@ public class ProductController {
         return Result.success(result);
     }
 
+    /**
+     * 商品详情页「相关商品」:按当前商品标签匹配其他商品(排除自身),
+     * 按 标签匹配数 > 用户权重 > 家庭权重 > 销量 排序;支持分页(默认 4 个/页)。
+     * 登录/匿名均可返回(白名单),未登录无权重加成。
+     */
+    @PostMapping("/related-products")
+    public Result<List<Map<String, Object>>> relatedProducts(@RequestBody Map<String, Object> params,
+                                                              @RequestAttribute(value = "userId", required = false) Long userId) {
+        Long productId = ParamUtils.getLong(params.get("productId"));
+        if (productId == null) {
+            return Result.error("productId不能为空");
+        }
+        int size = params.get("size") != null ? Integer.parseInt(params.get("size").toString()) : 4;
+        if (size < 1) size = 4;
+        int page = params.get("page") != null ? Math.max(1, Integer.parseInt(params.get("page").toString())) : 1;
+
+        Long familyId = null;
+        if (userId != null) {
+            User user = userMapper.selectById(userId);
+            if (user != null) {
+                familyId = user.getFamilyId();
+            }
+        }
+
+        // 全量候选 → 按页切片(与 /api/articles/related-products 分页语义一致,保证翻页不重复、不遗漏)
+        int fetchLimit = page * size;
+        List<Map<String, Object>> all = productRecommendationService.getRelatedProductsByProductFull(productId, userId, familyId, fetchLimit);
+        int from = (page - 1) * size;
+        int to = Math.min(from + size, all.size());
+        List<Map<String, Object>> result = from >= all.size() ? Collections.emptyList() : all.subList(from, to);
+        return Result.success(result);
+    }
+
     private List<Map<String, String>> parseSpecs(String specsStr) {
         String trimmed = specsStr.trim();
         List<Map<String, String>> result = new ArrayList<>();

+ 6 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/ActivityDTO.java

@@ -10,6 +10,7 @@ public class ActivityDTO {
     private Long id;
     private String title;
     private String description;
+    private String intro;
     private String coverImage;
     private String dimensionCode;
     /** JSON: {"body":0,"mind":0,"wisdom":0,"action":0,"wealth":0} */
@@ -64,6 +65,9 @@ public class ActivityDTO {
     /** 评价维度列表JSON(如 ["组织形式","会场气氛","主持人","专家"]) */
     private String feedbackDimensions;
 
+    /** 创建时选择的海报模板ID(小程序端海报模板选择) */
+    private Long posterTemplateId;
+
     public static ActivityDTO from(Activity activity) {
         return from(activity, false, null);
     }
@@ -77,6 +81,7 @@ public class ActivityDTO {
         dto.setId(activity.getId());
         dto.setTitle(activity.getTitle());
         dto.setDescription(activity.getDescription());
+        dto.setIntro(activity.getIntro());
         dto.setCoverImage(activity.getCoverImage());
         dto.setDimensionCode(activity.getDimensionCode());
         dto.setDimensionWeights(activity.getDimensionWeights());
@@ -98,6 +103,7 @@ public class ActivityDTO {
         dto.setParentMemberFee(activity.getParentMemberFee());
         dto.setCreatorId(activity.getCreatorId());
         dto.setFeedbackDimensions(activity.getFeedbackDimensions());
+        dto.setPosterTemplateId(activity.getPosterTemplateId());
         // Member-aware pricing: resolve final display price based on membership level
         // Guests (no memberLevel) see base price; members see memberPrice if applicable
         Integer resolvedPrice = resolveMemberPrice(activity.getPrice(), activity.getMemberPrice(), memberLevel);

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

@@ -45,6 +45,8 @@ public class ProductDTO {
     private String ppointSource;         // config=管理员配置,default=商品默认
     private Integer profitRate;         // 商品默认利润率(千分比,备用)
     private Integer sortOrder;           // 排序(值越小越靠前)
+    private Long categoryId;               // 商品类目ID
+    private Long distributionSystemId;     // 销售体系ID
 
     // 测评商品扩展信息
     private String assessmentType;
@@ -120,6 +122,8 @@ public class ProductDTO {
         d.rejectReason = p.getRejectReason();
         d.deliveryMethod = p.getDeliveryMethod();
         d.sortOrder = p.getSortOrder();
+        d.categoryId = p.getCategoryId();
+        d.distributionSystemId = p.getDistributionSystemId();
         if (p.getVisibleRoles() != null && !p.getVisibleRoles().isEmpty()) {
             try {
                 d.visibleRoles = JSON.parseArray(p.getVisibleRoles(), String.class);

+ 6 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/Activity.java

@@ -19,6 +19,9 @@ public class Activity implements Serializable {
 
     private String description;
 
+    /** 活动简介(纯文本短摘要,用于活动列表卡片展示) */
+    private String intro;
+
     private String coverImage;
 
     /** body/mind/wisdom/action/wealth */
@@ -109,6 +112,9 @@ public class Activity implements Serializable {
     /** 评价维度列表JSON(如 ["组织形式","会场气氛","主持人","专家"]) */
     private String feedbackDimensions;
 
+    /** 创建时选择的海报模板ID(小程序端海报模板选择,迁移388) */
+    private Long posterTemplateId;
+
     private Date createdAt;
 
     private Date updatedAt;

+ 0 - 16
cfc-backend/src/main/java/com/etotem/cfc/entity/MicrobiomeArticle.java

@@ -1,16 +0,0 @@
-package com.etotem.cfc.entity;
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-import java.io.Serializable;
-import java.util.Date;
-@Data
-@TableName("microbiome_article")
-public class MicrobiomeArticle implements Serializable {
-    @TableId(type = IdType.AUTO)
-    private Long id; private String title; private String content;
-    private String category; private String tags; private String sourceUrl;
-    private String pubDate; private Integer status;
-    private Date createdAt; private Date updatedAt;
-}

+ 0 - 4
cfc-backend/src/main/java/com/etotem/cfc/mapper/MicrobiomeArticleMapper.java

@@ -1,4 +0,0 @@
-package com.etotem.cfc.mapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.etotem.cfc.entity.MicrobiomeArticle;
-public interface MicrobiomeArticleMapper extends BaseMapper<MicrobiomeArticle> {}

+ 111 - 5
cfc-backend/src/main/java/com/etotem/cfc/service/ActivityAdminService.java

@@ -4,13 +4,17 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.entity.Activity;
+import com.etotem.cfc.entity.DimensionWeight;
 import com.etotem.cfc.mapper.ActivityMapper;
 import org.springframework.stereotype.Service;
 
 import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import com.etotem.cfc.util.SortUtil;
 import com.etotem.cfc.util.ParamUtils;
@@ -26,6 +30,9 @@ public class ActivityAdminService {
     @Resource
     private ActivityMapper activityMapper;
 
+    @Resource
+    private DimensionWeightService dimensionWeightService;
+
     /**
      * 分页查询活动列表(包含草稿)
      * 支持按 status / dimensionCode / auditStatus 过滤
@@ -83,8 +90,8 @@ public class ActivityAdminService {
     }
 
     /**
-     * 审核活动
-     * @param params action: "publish" | "reject", id: Long
+     * 审核活动(旧入口:ActivityReview.vue 使用)
+     * @param params action: "publish" | "reject", id: Long, dimensionCode: String(publish 时必填)
      */
     public Result<String> review(Map<String, Object> params) {
         String action = (String) params.get("action");
@@ -99,7 +106,13 @@ public class ActivityAdminService {
             if (!"draft".equals(activity.getStatus())) {
                 return Result.error("仅草稿状态可发布");
             }
+            // 审核通过时维度为必填
+            String dimensionCode = (String) params.get("dimensionCode");
+            if (dimensionCode == null || dimensionCode.trim().isEmpty()) {
+                return Result.error("审核通过时必须选择所属维度");
+            }
             activity.setStatus("published");
+            activity.setDimensionCode(dimensionCode.trim());
             activityMapper.updateById(activity);
             return Result.success("发布成功");
         } else if ("reject".equals(action)) {
@@ -125,8 +138,13 @@ public class ActivityAdminService {
         activityMapper.updateById(activity);
     }
 
+    /**
+     * 审核活动(审核中心:ActivityAudit.vue 使用)
+     * @param params action: "approved" | "rejected", id: Long, dimensionCode: String(approved 时必填), rejectReason: String
+     */
     @Transactional
-    public void auditActivity(Long id, String action, String auditReason, Long adminId) {
+    public void auditActivity(Long id, String action, String auditReason, Long adminId, String dimensionCode,
+                              List<Map<String, Object>> dimensionWeights) {
         Activity activity = activityMapper.selectById(id);
         if (activity == null) {
             throw new IllegalStateException("活动不存在");
@@ -134,12 +152,21 @@ public class ActivityAdminService {
         if (!"pending".equals(activity.getStatus())) {
             throw new IllegalStateException("仅待审核状态可审核");
         }
-        if (!"approved".equals(action) && !"rejected".equals(action)) {
+        String normalizedAction = normalizeAuditAction(action);
+        if (normalizedAction == null) {
             throw new IllegalStateException("审核操作无效");
         }
-        if ("approved".equals(action)) {
+        List<DimensionWeight> weights = null;
+        if ("approved".equals(normalizedAction)) {
+            if (dimensionCode == null || dimensionCode.trim().isEmpty()) {
+                throw new IllegalStateException("审核通过时必须选择所属维度");
+            }
+            weights = parseAndValidateDimensionWeights(dimensionWeights);
             activity.setStatus("published");
             activity.setAuditStatus("approved");
+            activity.setDimensionCode(dimensionCode.trim());
+            activity.setAuditorId(adminId);
+            activity.setAuditedAt(new Date());
         } else {
             activity.setStatus("rejected");
             activity.setAuditStatus("rejected");
@@ -148,6 +175,85 @@ public class ActivityAdminService {
             activity.setAuditedAt(new Date());
         }
         activityMapper.updateById(activity);
+        if (weights != null) {
+            // 同时写入 dimension_weights 表与 activities.dimension_weights JSON 列
+            dimensionWeightService.saveWeights("activity", id, weights);
+        }
+    }
+
+    private static final List<String> REQUIRED_DIMENSIONS =
+            Arrays.asList("body", "mind", "wisdom", "action", "wealth");
+
+    /**
+     * 校验并转换审核时填写的五维权重:五个维度必须齐全,合计必须为 100%。
+     */
+    private List<DimensionWeight> parseAndValidateDimensionWeights(List<Map<String, Object>> rawWeights) {
+        if (rawWeights == null || rawWeights.isEmpty()) {
+            throw new IllegalStateException("审核通过时必须填写五维权重");
+        }
+        Map<String, Integer> weightByDimension = new HashMap<>();
+        for (Map<String, Object> item : rawWeights) {
+            if (item == null || item.get("dimension") == null || item.get("weight") == null) {
+                continue;
+            }
+            String dimension = item.get("dimension").toString().trim().toLowerCase();
+            if (!REQUIRED_DIMENSIONS.contains(dimension)) {
+                continue;
+            }
+            int weight;
+            Object weightObj = item.get("weight");
+            if (weightObj instanceof Number) {
+                weight = ((Number) weightObj).intValue();
+            } else {
+                try {
+                    weight = Integer.parseInt(weightObj.toString().trim());
+                } catch (NumberFormatException e) {
+                    throw new IllegalStateException("五维权重格式不正确");
+                }
+            }
+            if (weight < 0 || weight > 100) {
+                throw new IllegalStateException("五维权重必须在0-100之间");
+            }
+            weightByDimension.put(dimension, weight);
+        }
+
+        List<DimensionWeight> weights = new ArrayList<>();
+        int sum = 0;
+        for (String dimension : REQUIRED_DIMENSIONS) {
+            Integer weight = weightByDimension.get(dimension);
+            if (weight == null) {
+                throw new IllegalStateException("五维权重必须包含身/心/智/行/富五个维度");
+            }
+            sum += weight;
+            DimensionWeight w = new DimensionWeight();
+            w.setDimension(dimension);
+            w.setWeight(weight);
+            w.setEnabled(weight > 0);
+            weights.add(w);
+        }
+        if (sum != 100) {
+            throw new IllegalStateException("五维权重合计必须为100%");
+        }
+        return weights;
+    }
+
+    /**
+     * 归一化审核动作:兼容审核中心历史提交的 publish/reject 与标准 approved/rejected。
+     *
+     * @return approved / rejected;无法识别时返回 null
+     */
+    private String normalizeAuditAction(String action) {
+        if (action == null) {
+            return null;
+        }
+        String normalized = action.trim().toLowerCase();
+        if ("approved".equals(normalized) || "approve".equals(normalized) || "publish".equals(normalized)) {
+            return "approved";
+        }
+        if ("rejected".equals(normalized) || "reject".equals(normalized)) {
+            return "rejected";
+        }
+        return null;
     }
 
     @Transactional

+ 76 - 7
cfc-backend/src/main/java/com/etotem/cfc/service/ArticleService.java

@@ -9,9 +9,11 @@ import com.etotem.cfc.entity.ArticleQuizRecord;
 import com.etotem.cfc.entity.ArticleReadingRecord;
 import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsLog;
+import com.etotem.cfc.entity.Product;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.ArticleMapper;
+import com.etotem.cfc.mapper.ProductMapper;
 import com.etotem.cfc.mapper.ArticleQuizRecordMapper;
 import com.etotem.cfc.mapper.ArticleReadingRecordMapper;
 import com.etotem.cfc.mapper.FamilyMemberMapper;
@@ -40,6 +42,9 @@ public class ArticleService {
     @Resource
     private ArticleMapper articleMapper;
 
+    @Resource
+    private ProductMapper productMapper;
+
     @Resource
     private ArticlePermissionService articlePermissionService;
 
@@ -197,9 +202,10 @@ public class ArticleService {
 
     /**
      * 相关文章:按标签匹配(FIND_IN_SET 精确匹配整标签),共享标签数降序 → 发布时间降序,
-     * 排除自身,仅取已发布,经可见性过滤后截取前 size 篇。无标签/无匹配返回空。
+     * 排除自身,仅取已发布,经可见性过滤后按 (page-1)*size ~ page*size 分页窗口截取。
+     * 无标签/无匹配返回空。
      */
-    public List<Article> getRelatedArticles(Long articleId, int size, Long userId, Long familyId, String vendorType) {
+    public List<Article> getRelatedArticles(Long articleId, int size, int page, Long userId, Long familyId, String vendorType) {
         Article current = articleMapper.selectById(articleId);
         if (current == null || current.getTags() == null || current.getTags().trim().isEmpty()) {
             return Collections.emptyList();
@@ -211,13 +217,39 @@ public class ArticleService {
                 tags.add(tag);
             }
         }
-        if (tags.isEmpty()) {
+        return matchArticlesByTags(tags, articleId, size, page, userId, familyId, vendorType);
+    }
+
+    /**
+     * 商品详情页「相关文章」:按商品标签匹配文章(共享标签数降序 → 发布时间降序),
+     * 支持分页窗口截取。商品标签为 JSON 数组或逗号分隔字符串。
+     */
+    public List<Article> getRelatedArticlesByProduct(Long productId, int size, int page, Long userId, Long familyId, String vendorType) {
+        if (productId == null) return Collections.emptyList();
+        Product product = productMapper.selectById(productId);
+        if (product == null || product.getTags() == null || product.getTags().trim().isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<String> tags = parseCommaOrJsonTags(product.getTags());
+        // 商品来源无需排除文章自身(productId 与 articleId 分属不同表),excludeArticleId 传 null
+        return matchArticlesByTags(tags, null, size, page, userId, familyId, vendorType);
+    }
+
+    /**
+     * 标签匹配文章核心逻辑:FIND_IN_SET 候选 → 共享标签计数 → 排序 → 可见性过滤 → 分页窗口截取。
+     */
+    private List<Article> matchArticlesByTags(List<String> tags, Long excludeArticleId, int size, int page,
+                                              Long userId, Long familyId, String vendorType) {
+        if (tags == null || tags.isEmpty() || size < 1) {
             return Collections.emptyList();
         }
+        int p = page < 1 ? 1 : page;
 
         LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<Article>()
-                .eq(Article::getStatus, "published")
-                .ne(Article::getId, articleId);
+                .eq(Article::getStatus, "published");
+        if (excludeArticleId != null) {
+            wrapper.ne(Article::getId, excludeArticleId);
+        }
         wrapper.and(w -> {
             boolean first = true;
             for (String tag : tags) {
@@ -235,7 +267,7 @@ public class ArticleService {
         Map<Long, Integer> score = new HashMap<>();
         List<Article> matched = new ArrayList<>();
         for (Article a : candidates) {
-            if (a.getId() != null && a.getId().equals(articleId)) {
+            if (excludeArticleId != null && a.getId() != null && a.getId().equals(excludeArticleId)) {
                 continue;
             }
             int s = 0;
@@ -268,7 +300,44 @@ public class ArticleService {
         if (visible == null) {
             visible = Collections.emptyList();
         }
-        return visible.size() > size ? new ArrayList<>(visible.subList(0, size)) : visible;
+        // 分页窗口截取:兼容旧调用方(size 截断)与分页调用方
+        int from = (p - 1) * size;
+        if (from >= visible.size()) {
+            return Collections.emptyList();
+        }
+        int to = Math.min(from + size, visible.size());
+        return new ArrayList<>(visible.subList(from, to));
+    }
+
+    /** 商品标签解析:兼容 JSON 数组字符串(["标签1","标签2"])与逗号分隔(标签1,标签2) */
+    private List<String> parseCommaOrJsonTags(String raw) {
+        if (raw == null || raw.trim().isEmpty()) return Collections.emptyList();
+        String trimmed = raw.trim();
+        if (trimmed.startsWith("[")) {
+            try {
+                com.alibaba.fastjson.JSONArray arr = com.alibaba.fastjson.JSON.parseArray(trimmed);
+                if (arr == null) return Collections.emptyList();
+                List<String> tags = new ArrayList<>();
+                for (int i = 0; i < arr.size(); i++) {
+                    Object o = arr.get(i);
+                    if (o != null && !o.toString().trim().isEmpty()) {
+                        tags.add(o.toString().trim());
+                    }
+                }
+                return tags;
+            } catch (Exception e) {
+                log.warn("解析商品标签失败: {}", e.getMessage());
+                return Collections.emptyList();
+            }
+        }
+        List<String> tags = new ArrayList<>();
+        for (String t : trimmed.split(",")) {
+            String tag = t.trim();
+            if (!tag.isEmpty() && !tags.contains(tag)) {
+                tags.add(tag);
+            }
+        }
+        return tags;
     }
 
     public Article getDetail(Long id, Long userId, Long familyId, String vendorType) {

+ 14 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/HealthKnowledgeBaseService.java

@@ -1,6 +1,7 @@
 package com.etotem.cfc.service;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.entity.HealthKnowledgeBase;
 import com.etotem.cfc.mapper.HealthKnowledgeBaseMapper;
 import lombok.extern.slf4j.Slf4j;
@@ -61,6 +62,19 @@ public class HealthKnowledgeBaseService {
         return healthKnowledgeBaseMapper.selectList(null);
     }
 
+    /**
+     * 菌群文章分页列表(item_type='article',按 id 升序保证分页稳定)。
+     * 作为 langgraph 向量库的统一数据源,替代已废弃的 microbiome_article 表。
+     */
+    public Page<HealthKnowledgeBase> pageArticles(int page, int size) {
+        return healthKnowledgeBaseMapper.selectPage(
+                new Page<>(page, size),
+                new LambdaQueryWrapper<HealthKnowledgeBase>()
+                        .eq(HealthKnowledgeBase::getItemType, "article")
+                        .orderByAsc(HealthKnowledgeBase::getId)
+        );
+    }
+
     public void save(HealthKnowledgeBase entry) {
         if (entry.getId() != null) {
             healthKnowledgeBaseMapper.updateById(entry);

+ 48 - 13
cfc-backend/src/main/java/com/etotem/cfc/service/ProductRecommendationService.java

@@ -870,23 +870,57 @@ public class ProductRecommendationService {
     }
 
     /**
-     * 文章标签匹配商品推荐:通过文章标签与商品标签匹配,按 标签匹配数 > 用户权重 > 家庭权重 > 销量 排序
-     *
-     * @param articleId 文章ID
-     * @param userId    当前用户ID(可为空,用于用户权重)
-     * @param familyId  家庭ID(可为空,用于家庭权重)
-     * @param limit     返回数量
-     * @return 关联商品列表(无匹配时返回空列表,由调用方决定是否兜底)
+     * 文章标签匹配商品推荐(包装方法):通过文章标签与商品标签匹配,按 标签匹配数 > 用户权重 > 家庭权重 > 销量 排序
      */
     private List<Map<String, Object>> getArticleTagMatchedProducts(Long articleId, Long userId, Long familyId, int limit) {
-        // 1. 获取文章标签
         Article article = articleMapper.selectById(articleId);
         if (article == null || article.getTags() == null || article.getTags().trim().isEmpty()) {
             return Collections.emptyList();
         }
         List<String> articleTags = parseTagList(article.getTags());
-        if (articleTags.isEmpty()) return Collections.emptyList();
-        Set<String> articleTagSet = new HashSet<>(articleTags);
+        // 文章场景无需排除商品(文章与商品主键分属不同表),excludeProductId 传 null
+        return getTagMatchedProducts(articleTags, null, userId, familyId, limit, "文章", "article_tag");
+    }
+
+    /**
+     * 商品详情页「相关商品」完整候选列表:按商品标签匹配商品(排除自身),排序与分页语义与文章关联商品一致。
+     * 调用方自行切片实现分页。
+     *
+     * @param productId 商品ID
+     * @param userId    当前用户ID(可为空,用于用户权重)
+     * @param familyId  家庭ID(可为空,用于家庭权重)
+     * @param limit     候选上限(0 表示不限制,由调用方决定分页窗口)
+     * @return 关联商品完整列表(无匹配时返回空列表)
+     */
+    public List<Map<String, Object>> getRelatedProductsByProductFull(Long productId, Long userId, Long familyId, int limit) {
+        if (productId == null) return Collections.emptyList();
+        Product product = productMapper.selectById(productId);
+        if (product == null || product.getTags() == null || product.getTags().trim().isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<String> tags = parseTagList(product.getTags());
+        return getTagMatchedProducts(tags, productId, userId, familyId, limit, "商品", "product_tag");
+    }
+
+    /**
+     * 标签匹配商品推荐核心逻辑:通过源标签(文章/商品)与商品标签匹配,按 标签匹配数 > 用户权重 > 家庭权重 > 销量 排序
+     *
+     * @param sourceTags       源标签集合(来自文章或商品)
+     * @param excludeProductId 需排除的商品ID(商品关联商品时排除自身;文章关联商品时为 null)
+     * @param userId           当前用户ID(可为空,用于用户权重)
+     * @param familyId         家庭ID(可为空,用于家庭权重)
+     * @param limit            返回数量
+     * @param sourceDesc       来源描述("文章"/"商品",用于 reason 文案)
+     * @param sourceCode       来源编码(article_tag/product_tag,写入 source 字段)
+     * @return 关联商品列表(无匹配时返回空列表,由调用方决定是否兜底)
+     */
+    private List<Map<String, Object>> getTagMatchedProducts(List<String> sourceTags, Long excludeProductId,
+                                                            Long userId, Long familyId, int limit,
+                                                            String sourceDesc, String sourceCode) {
+        if (sourceTags == null || sourceTags.isEmpty() || limit <= 0) {
+            return Collections.emptyList();
+        }
+        Set<String> sourceTagSet = new HashSet<>(sourceTags);
 
         // 2. 查询所有在售且有库存的商品
         List<Product> products = productMapper.selectList(
@@ -934,13 +968,14 @@ public class ProductRecommendationService {
         // 5. 计算标签匹配数与权重,构建候选列表
         List<Map<String, Object>> candidates = new ArrayList<>();
         for (Product product : products) {
+            if (excludeProductId != null && excludeProductId.equals(product.getId())) continue;
             if (product.getTags() == null || product.getTags().trim().isEmpty()) continue;
             List<String> productTags = parseTagList(product.getTags());
             if (productTags.isEmpty()) continue;
 
             int matchCount = 0;
             for (String tag : productTags) {
-                if (articleTagSet.contains(tag)) matchCount++;
+                if (sourceTagSet.contains(tag)) matchCount++;
             }
             if (matchCount == 0) continue;
 
@@ -958,8 +993,8 @@ public class ProductRecommendationService {
             item.put("tagMatchCount", matchCount);
             item.put("userWeight", userWeight);
             item.put("familyWeight", familyWeight);
-            item.put("reason", "与文章标签匹配 " + matchCount + " 个标签");
-            item.put("source", "article_tag");
+            item.put("reason", "与" + sourceDesc + "标签匹配 " + matchCount + " 个标签");
+            item.put("source", sourceCode);
             item.put("url", "/pages/discover-detail/product-detail/product-detail?id=" + product.getId());
             candidates.add(item);
         }

+ 16 - 5
cfc-backend/src/main/java/com/etotem/cfc/service/ProductService.java

@@ -585,13 +585,24 @@ wrapper.in(Product::getId, productIds)
         // 最低可见级别:空串规整为 NULL(NULL=所有用户含未登录可见),
         // 避免管理端 el-select 清空提交 '' 导致商品对所有用户不可见
         boolean clearMinVisibleLevel = normalizeMinVisibleLevel(product);
+        // updateById 默认忽略 null 字段:clearable 下拉清空后需显式置 NULL
+        boolean clearCategory = product.getCategoryId() == null;
+        boolean clearDistributionSystem = product.getDistributionSystemId() == null;
         productMapper.updateById(product);
         nutritionProductService.syncFromProduct(product);
-        // updateById 默认忽略 null 字段,清空时需显式将 min_visible_level 置 NULL
-        if (clearMinVisibleLevel) {
-            productMapper.update(null, new LambdaUpdateWrapper<Product>()
-                    .eq(Product::getId, product.getId())
-                    .set(Product::getMinVisibleLevel, null));
+        if (clearMinVisibleLevel || clearCategory || clearDistributionSystem) {
+            LambdaUpdateWrapper<Product> clearWrapper = new LambdaUpdateWrapper<Product>()
+                    .eq(Product::getId, product.getId());
+            if (clearMinVisibleLevel) {
+                clearWrapper.set(Product::getMinVisibleLevel, null);
+            }
+            if (clearCategory) {
+                clearWrapper.set(Product::getCategoryId, null);
+            }
+            if (clearDistributionSystem) {
+                clearWrapper.set(Product::getDistributionSystemId, null);
+            }
+            productMapper.update(null, clearWrapper);
         }
         return Result.success(ProductDTO.from(productMapper.selectById(product.getId())));
     }

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java

@@ -379,6 +379,9 @@ public List<Task> getTodayTasks(Long memberId, String dimensionCode) {
 
         SortUtil.applySort(wrapper);
         List<Task> all = taskMapper.selectList(wrapper);
+        // 过滤掉 latestEnd 已过期的任务(有执行时间窗口的任务:过了结束时间不再显示为待完成)
+        Date now = new Date();
+        all.removeIf(t -> t.getLatestEnd() != null && t.getLatestEnd().before(now) && "pending".equals(t.getStatus()));
         fillProgress(all, memberId);
         return filterLockedChildren(all);
     }

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

@@ -2108,6 +2108,7 @@ CREATE TABLE IF NOT EXISTS activities (
     id               BIGINT AUTO_INCREMENT PRIMARY KEY,
     title            VARCHAR(100) NOT NULL COMMENT '活动标题',
     description      TEXT COMMENT '活动描述',
+    intro            VARCHAR(200) DEFAULT NULL COMMENT '活动简介(列表卡片展示)',
     cover_image      VARCHAR(500) COMMENT '封面图片URL',
     dimension_code   VARCHAR(20) COMMENT '维度代码: body/mind/wisdom/action/wealth',
     dimension_weights JSON DEFAULT NULL COMMENT '五维权重百分比: {"body":0,"mind":0,"wisdom":0,"action":0,"wealth":0}',
@@ -2127,6 +2128,7 @@ CREATE TABLE IF NOT EXISTS activities (
     platform_points INT DEFAULT 0 COMMENT '活动奖励CF值(0=不发)',
     visibility VARCHAR(20) DEFAULT 'public' COMMENT '浏览权限:public/login/private',
     visible_to TEXT COMMENT '私密指定人群JSON',
+    poster_template_id BIGINT NULL COMMENT '创建时选择的海报模板ID(小程序端海报模板选择,迁移388)',
     created_at       DATETIME DEFAULT CURRENT_TIMESTAMP,
     updated_at       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     INDEX idx_dimension (dimension_code),

+ 169 - 0
cfc-backend/src/test/java/com/etotem/cfc/unit/ActivityAuditTest.java

@@ -0,0 +1,169 @@
+package com.etotem.cfc.unit;
+
+import com.etotem.cfc.entity.Activity;
+import com.etotem.cfc.entity.DimensionWeight;
+import com.etotem.cfc.mapper.ActivityMapper;
+import com.etotem.cfc.service.ActivityAdminService;
+import com.etotem.cfc.service.DimensionWeightService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+/**
+ * 审核中心活动审核:动作兼容 publish/reject,审核通过时必须补充五维权重。
+ */
+class ActivityAuditTest {
+
+    private ActivityAdminService service;
+    private ActivityMapper activityMapper;
+    private DimensionWeightService dimensionWeightService;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        service = new ActivityAdminService();
+        activityMapper = mock(ActivityMapper.class);
+        dimensionWeightService = mock(DimensionWeightService.class);
+        set("activityMapper", activityMapper);
+        set("dimensionWeightService", dimensionWeightService);
+    }
+
+    private void set(String name, Object value) throws Exception {
+        Field f = ActivityAdminService.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(service, value);
+    }
+
+    private Activity pending() {
+        Activity a = new Activity();
+        a.setId(9L);
+        a.setTitle("测试活动");
+        a.setStatus("pending");
+        a.setAuditStatus("pending");
+        return a;
+    }
+
+    private List<Map<String, Object>> weights(int body, int mind, int wisdom, int action, int wealth) {
+        List<Map<String, Object>> list = new ArrayList<>();
+        String[] dims = {"body", "mind", "wisdom", "action", "wealth"};
+        int[] vals = {body, mind, wisdom, action, wealth};
+        for (int i = 0; i < dims.length; i++) {
+            Map<String, Object> m = new HashMap<>();
+            m.put("dimension", dims[i]);
+            m.put("weight", vals[i]);
+            m.put("enabled", vals[i] > 0);
+            list.add(m);
+        }
+        return list;
+    }
+
+    @Test
+    void audit_审核中心提交publish动作_按通过处理() {
+        Activity activity = pending();
+        when(activityMapper.selectById(9L)).thenReturn(activity);
+        when(activityMapper.updateById(any(Activity.class))).thenReturn(1);
+
+        service.auditActivity(9L, "publish", null, 1L, "body", weights(20, 20, 20, 20, 20));
+
+        assertEquals("published", activity.getStatus());
+        assertEquals("approved", activity.getAuditStatus());
+        assertEquals("body", activity.getDimensionCode());
+        assertEquals(Long.valueOf(1L), activity.getAuditorId());
+        assertNotNull(activity.getAuditedAt());
+    }
+
+    @Test
+    void audit_通过时保存五维权重() {
+        Activity activity = pending();
+        when(activityMapper.selectById(9L)).thenReturn(activity);
+        when(activityMapper.updateById(any(Activity.class))).thenReturn(1);
+
+        service.auditActivity(9L, "approved", null, 1L, "middle", weights(40, 15, 15, 15, 15));
+
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<List<DimensionWeight>> captor = ArgumentCaptor.forClass(List.class);
+        verify(dimensionWeightService).saveWeights(eq("activity"), eq(9L), captor.capture());
+
+        List<DimensionWeight> saved = captor.getValue();
+        assertEquals(5, saved.size());
+        assertEquals(100, saved.stream().mapToInt(DimensionWeight::getWeight).sum());
+        DimensionWeight body = saved.stream().filter(w -> "body".equals(w.getDimension())).findFirst().orElse(null);
+        assertNotNull(body);
+        assertEquals(40, body.getWeight());
+        assertTrue(body.getEnabled());
+    }
+
+    @Test
+    void audit_通过时缺少五维权重_报错且不落库() {
+        Activity activity = pending();
+        when(activityMapper.selectById(9L)).thenReturn(activity);
+
+        IllegalStateException e = assertThrows(IllegalStateException.class,
+                () -> service.auditActivity(9L, "approved", null, 1L, "body", null));
+
+        assertTrue(e.getMessage().contains("五维权重"), e.getMessage());
+        verify(activityMapper, never()).updateById(any(Activity.class));
+        verify(dimensionWeightService, never()).saveWeights(any(), any(), any());
+    }
+
+    @Test
+    void audit_通过时权重合计不为100_报错() {
+        Activity activity = pending();
+        when(activityMapper.selectById(9L)).thenReturn(activity);
+
+        IllegalStateException e = assertThrows(IllegalStateException.class,
+                () -> service.auditActivity(9L, "approved", null, 1L, "body", weights(20, 20, 20, 20, 19)));
+
+        assertTrue(e.getMessage().contains("100"), e.getMessage());
+        verify(activityMapper, never()).updateById(any(Activity.class));
+    }
+
+    @Test
+    void audit_通过时维度不完整_报错() {
+        Activity activity = pending();
+        when(activityMapper.selectById(9L)).thenReturn(activity);
+
+        List<Map<String, Object>> onlyFour = weights(25, 25, 25, 25, 0);
+        onlyFour.removeIf(m -> "wealth".equals(m.get("dimension")));
+
+        IllegalStateException e = assertThrows(IllegalStateException.class,
+                () -> service.auditActivity(9L, "approved", null, 1L, "body", onlyFour));
+
+        assertTrue(e.getMessage().contains("五个维度"), e.getMessage());
+    }
+
+    @Test
+    void audit_驳回不要求五维权重() {
+        Activity activity = pending();
+        when(activityMapper.selectById(9L)).thenReturn(activity);
+        when(activityMapper.updateById(any(Activity.class))).thenReturn(1);
+
+        service.auditActivity(9L, "reject", "材料不全", 1L, null, null);
+
+        assertEquals("rejected", activity.getStatus());
+        assertEquals("rejected", activity.getAuditStatus());
+        assertEquals("材料不全", activity.getAuditReason());
+        verify(dimensionWeightService, never()).saveWeights(any(), any(), any());
+    }
+
+    @Test
+    void audit_无效动作_报审核操作无效() {
+        Activity activity = pending();
+        when(activityMapper.selectById(9L)).thenReturn(activity);
+
+        IllegalStateException e = assertThrows(IllegalStateException.class,
+                () -> service.auditActivity(9L, "unknown", null, 1L, "body", weights(20, 20, 20, 20, 20)));
+
+        assertTrue(e.getMessage().contains("审核操作无效"), e.getMessage());
+    }
+}

+ 6 - 6
cfc-backend/src/test/java/com/etotem/cfc/unit/ArticleRelatedServiceTest.java

@@ -55,7 +55,7 @@ class ArticleRelatedServiceTest {
     @Test
     void getRelatedArticles_currentNull_returnsEmpty() {
         when(articleMapper.selectById(1L)).thenReturn(null);
-        List<Article> r = service.getRelatedArticles(1L, 3, null, null, null);
+        List<Article> r = service.getRelatedArticles(1L, 3, 1, null, null, null);
         assertTrue(r.isEmpty());
         verify(articleMapper, never()).selectList(any());
     }
@@ -63,7 +63,7 @@ class ArticleRelatedServiceTest {
     @Test
     void getRelatedArticles_emptyTags_returnsEmpty() {
         when(articleMapper.selectById(1L)).thenReturn(a(1L, null, new Date()));
-        List<Article> r = service.getRelatedArticles(1L, 3, null, null, null);
+        List<Article> r = service.getRelatedArticles(1L, 3, 1, null, null, null);
         assertTrue(r.isEmpty());
         verify(articleMapper, never()).selectList(any());
     }
@@ -75,7 +75,7 @@ class ArticleRelatedServiceTest {
                 Collections.singletonList(a(2L, "阅读,亲子", new Date())));
         when(permissionService.filterVisible(any(), any(), any(), any())).thenReturn(Collections.emptyList());
 
-        List<Article> r = service.getRelatedArticles(1L, 3, null, null, null);
+        List<Article> r = service.getRelatedArticles(1L, 3, 1, null, null, null);
         assertTrue(r.isEmpty());
     }
 
@@ -90,7 +90,7 @@ class ArticleRelatedServiceTest {
         when(articleMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(candidates);
         when(permissionService.filterVisible(any(), any(), any(), any())).thenAnswer(inv -> inv.getArgument(0));
 
-        List<Article> r = service.getRelatedArticles(1L, 3, null, null, null);
+        List<Article> r = service.getRelatedArticles(1L, 3, 1, null, null, null);
 
         // order: id=2 (2 shared) > id=3 (1 shared) > id=4 filtered out by count=0 but still queried
         assertEquals(2, r.size()); // size=3 but only 2 match
@@ -106,7 +106,7 @@ class ArticleRelatedServiceTest {
         when(articleMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(Collections.singletonList(dup));
         when(permissionService.filterVisible(any(), any(), any(), any())).thenAnswer(inv -> inv.getArgument(0));
 
-        List<Article> r = service.getRelatedArticles(1L, 3, null, null, null);
+        List<Article> r = service.getRelatedArticles(1L, 3, 1, null, null, null);
         assertTrue(r.isEmpty());
     }
 
@@ -120,7 +120,7 @@ class ArticleRelatedServiceTest {
         when(articleMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(cands);
         when(permissionService.filterVisible(any(), any(), any(), any())).thenAnswer(inv -> inv.getArgument(0));
 
-        List<Article> r = service.getRelatedArticles(1L, 2, null, null, null);
+        List<Article> r = service.getRelatedArticles(1L, 2, 1, null, null, null);
 
         assertEquals(2, r.size());
     }

+ 29 - 0
cfc-backend/src/test/java/com/etotem/cfc/unit/ProductDTOMappingTest.java

@@ -0,0 +1,29 @@
+package com.etotem.cfc.unit;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.etotem.cfc.dto.ProductDTO;
+import com.etotem.cfc.entity.Product;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * 商品详情 DTO 映射:管理端编辑页依赖详情接口回显「商品分类」和「所属体系」。
+ */
+class ProductDTOMappingTest {
+
+    @Test
+    void from_保留商品分类与所属体系() {
+        Product p = new Product();
+        p.setId(7L);
+        p.setName("还原水机滤芯");
+        p.setCategoryId(1L);
+        p.setDistributionSystemId(3L);
+
+        JSONObject json = JSON.parseObject(JSON.toJSONString(ProductDTO.from(p)));
+
+        assertEquals(1L, json.getLong("categoryId"));
+        assertEquals(3L, json.getLong("distributionSystemId"));
+    }
+}

+ 99 - 0
cfc-backend/src/test/java/com/etotem/cfc/unit/ProductUpdateClearTest.java

@@ -0,0 +1,99 @@
+package com.etotem.cfc.unit;
+
+import com.baomidou.mybatisplus.core.MybatisConfiguration;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.etotem.cfc.entity.Product;
+import com.etotem.cfc.mapper.ProductMapper;
+import com.etotem.cfc.service.NutritionProductService;
+import com.etotem.cfc.service.ProductService;
+import org.apache.ibatis.builder.MapperBuilderAssistant;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import java.lang.reflect.Field;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.*;
+
+/**
+ * 商品编辑:分类/所属体系为 clearable 下拉,清空后必须真正写入 NULL,
+ * 不能因为 MyBatis-Plus updateById 忽略 null 字段而被静默保留。
+ */
+class ProductUpdateClearTest {
+
+    private ProductService service;
+    private ProductMapper productMapper;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        // LambdaUpdateWrapper 解析列名依赖 MyBatis-Plus 的 TableInfo 缓存
+        MapperBuilderAssistant assistant =
+                new MapperBuilderAssistant(new MybatisConfiguration(), "");
+        TableInfoHelper.initTableInfo(assistant, Product.class);
+
+        service = new ProductService();
+        productMapper = mock(ProductMapper.class);
+        set("productMapper", productMapper);
+        set("nutritionProductService", mock(NutritionProductService.class));
+    }
+
+    private void set(String name, Object value) throws Exception {
+        Field f = ProductService.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(service, value);
+    }
+
+    private Product existing() {
+        Product p = new Product();
+        p.setId(7L);
+        p.setName("还原水机滤芯");
+        p.setStatus("on_shelf");
+        p.setCategoryId(1L);
+        p.setDistributionSystemId(3L);
+        return p;
+    }
+
+    @Test
+    void adminUpdate_分类与体系为空时显式置NULL() {
+        Product existing = existing();
+        when(productMapper.selectById(7L)).thenReturn(existing);
+        when(productMapper.updateById(any(Product.class))).thenReturn(1);
+        when(productMapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
+
+        Product form = new Product();
+        form.setId(7L);
+        form.setName("还原水机滤芯");
+        form.setCategoryId(null);
+        form.setDistributionSystemId(null);
+
+        assertEquals(200, service.adminUpdate(form).getCode());
+
+        ArgumentCaptor<Wrapper> captor = ArgumentCaptor.forClass(Wrapper.class);
+        verify(productMapper).update(isNull(), captor.capture());
+        String sqlSet = ((LambdaUpdateWrapper<Product>) captor.getValue()).getSqlSet();
+        assertTrue(sqlSet.contains("category_id"), sqlSet);
+        assertTrue(sqlSet.contains("distribution_system_id"), sqlSet);
+    }
+
+    @Test
+    void adminUpdate_选择了分类与体系时不额外置NULL() {
+        Product existing = existing();
+        when(productMapper.selectById(7L)).thenReturn(existing);
+        when(productMapper.updateById(any(Product.class))).thenReturn(1);
+        when(productMapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
+
+        Product form = new Product();
+        form.setId(7L);
+        form.setName("还原水机滤芯");
+        form.setCategoryId(2L);
+        form.setDistributionSystemId(4L);
+
+        assertEquals(200, service.adminUpdate(form).getCode());
+
+        verify(productMapper, never()).update(isNull(), any(Wrapper.class));
+    }
+}

+ 5 - 5
cfc-frontend/App.vue

@@ -230,15 +230,15 @@ bindReferralIfNotSelf(code).then(function(res) {
 }
 </script>
 
-<style>
+<style lang="scss">
 @import url('./common/uni.css');
 
 page {
-  background-color: var(--bg, #F5F9FC);
+  background-color: var(--bg, $uni-bg-color);
   font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
     'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
     'Helvetica Neue', Helvetica, Arial, sans-serif;
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
   font-size: 28rpx;
   line-height: 1.6;
   -webkit-font-smoothing: antialiased;
@@ -261,7 +261,7 @@ page {
   font-weight: 700;
   font-size: 32rpx;
   margin: 32rpx 0 16rpx;
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
   display: flex;
   align-items: center;
   justify-content: space-between;
@@ -270,7 +270,7 @@ page {
 }
 .section-header__more {
   font-size: 24rpx;
-  color: var(--muted, #94A3B8);
+  color: var(--muted, $app-text-subtle);
   font-weight: 400;
 }
 

+ 9 - 8
cfc-frontend/components/ArticleBookshelf.vue

@@ -34,7 +34,7 @@
             class="bs-cover-img"
             :src="article.coverSrc"
             mode="aspectFill"
-          />
+           lazy-load />
 
           <view v-else class="bs-cover-text">
             <text
@@ -72,7 +72,7 @@ var FALLBACK_GRADIENTS = [
   { bg: 'linear-gradient(180deg, #06B6D4 0%, #0891B2 100%)', text: '#ECFEFF' },
   { bg: 'linear-gradient(180deg, #EC4899 0%, #DB2777 100%)', text: '#FDF2F8' },
   { bg: 'linear-gradient(180deg, #14B8A6 0%, #0D9488 100%)', text: '#F0FDFA' },
-  { bg: 'linear-gradient(180deg, #F97316 0%, #EA580C 100%)', text: '#FFF7ED' }
+  { bg: 'linear-gradient(180deg, #F97316 0%, #EA580C 100%)', text: 'BrandColors.primaryPale' }
 ]
 
 export default {
@@ -161,8 +161,9 @@ export default {
   }
 }
 </script>
+import { primaryPale } from '@/utils/brand-colors.js'
 
-<style scoped>
+<style scoped lang="scss">
 /* ======================================
    ArticleBookshelf — 四列网格推荐阅读组件
    浠艾福 (Care Family) 设计系统
@@ -182,7 +183,7 @@ export default {
 }
 .bs-empty-text {
   font-size: 24rpx;
-  color: #9CA3AF;
+  color: $facade-text-gray-400;
 }
 
 /* ---- Skeleton ---- */
@@ -207,7 +208,7 @@ export default {
   height: 0;
   padding-top: 133.33%; /* 3:4 书封比例 */
   border-radius: 12rpx;
-  background: linear-gradient(180deg, #e8e8e8 0%, #f0f0f0 50%, #e8e8e8 100%);
+  background: linear-gradient(180deg, $facade-fill-4 0%, $facade-fill 50%, $facade-fill-4 100%);
   background-size: 200% 100%;
   animation: skeleton-wave 1.4s ease-in-out infinite;
 }
@@ -215,7 +216,7 @@ export default {
   height: 24rpx;
   margin-top: 10rpx;
   border-radius: 6rpx;
-  background: linear-gradient(180deg, #e8e8e8 0%, #f0f0f0 50%, #e8e8e8 100%);
+  background: linear-gradient(180deg, $facade-fill-4 0%, $facade-fill 50%, $facade-fill-4 100%);
   background-size: 200% 100%;
   animation: skeleton-wave 1.4s ease-in-out infinite;
 }
@@ -308,7 +309,7 @@ export default {
   font-size: 18rpx;
   font-weight: 700;
   line-height: 1.4;
-  color: #FFFFFF;
+  color: $app-surface;
   text-align: center;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -324,7 +325,7 @@ export default {
   display: block;
   margin-top: 8rpx;
   font-size: 22rpx;
-  color: #333;
+  color: $facade-text-strong;
   line-height: 1.4;
   overflow: hidden;
   text-overflow: ellipsis;

+ 5 - 5
cfc-frontend/components/BadgeGrid.vue

@@ -20,9 +20,9 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .badge-grid {
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 24rpx;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
@@ -44,13 +44,13 @@ export default {
   width: 80rpx;
   height: 80rpx;
   border-radius: 50%;
-  background: linear-gradient(135deg, #FFD700, #FFA500);
+  background: linear-gradient(135deg, $accent-gold, #FFA500);
   display: flex;
   align-items: center;
   justify-content: center;
   margin-bottom: 8rpx;
 }
-.badge-icon-wrap--locked { background: #E2E8F0; }
+.badge-icon-wrap--locked { background: $uni-border-color-light; }
 .badge-icon { font-size: 36rpx; }
-.badge-name { font-size: 22rpx; color: #333; text-align: center; }
+.badge-name { font-size: 22rpx; color: $facade-text-strong; text-align: center; }
 </style>

+ 7 - 7
cfc-frontend/components/BaseBadge.vue

@@ -19,7 +19,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .base-badge {
   display: inline-flex;
   align-items: center;
@@ -36,26 +36,26 @@ export default {
 /* Variants */
 .base-badge--primary {
   background: rgba(249, 115, 22, 0.12);
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, $brand-primary);
 }
 .base-badge--success {
   background: rgba(34, 197, 94, 0.12);
-  color: var(--color-success, #22C55E);
+  color: var(--color-success, $status-success-2);
 }
 .base-badge--warning {
   background: rgba(245, 158, 11, 0.12);
-  color: var(--color-warning, #F59E0B);
+  color: var(--color-warning, $dim-wealth);
 }
 .base-badge--error {
   background: rgba(239, 68, 68, 0.12);
-  color: var(--color-error, #EF4444);
+  color: var(--color-error, $status-danger);
 }
 .base-badge--accent {
   background: rgba(14, 165, 233, 0.12);
-  color: var(--color-accent, #0EA5E9);
+  color: var(--color-accent, $uni-info);
 }
 .base-badge--muted {
   background: rgba(148, 163, 184, 0.12);
-  color: var(--muted, #94A3B8);
+  color: var(--muted, $app-text-subtle);
 }
 </style>

+ 3 - 3
cfc-frontend/components/BaseLoading.vue

@@ -20,7 +20,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .base-loading {
   display: flex;
   flex-direction: column;
@@ -50,7 +50,7 @@ export default {
   width: 16rpx;
   height: 16rpx;
   border-radius: 50%;
-  background: var(--color-primary, #F97316);
+  background: var(--color-primary, $brand-primary);
   animation: dot-bounce 1.2s ease-in-out infinite;
 }
 .base-loading__dot:nth-child(2) { animation-delay: 0.2s; }
@@ -58,7 +58,7 @@ export default {
 
 .base-loading__text {
   font-size: 24rpx;
-  color: var(--muted, #94A3B8);
+  color: var(--muted, $app-text-subtle);
 }
 </style>
 

+ 7 - 7
cfc-frontend/components/ConfigurableGuide.vue

@@ -102,7 +102,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .cg-overlay {
   position: fixed;
   top: 0;
@@ -118,7 +118,7 @@ export default {
 .cg-card {
   width: 640rpx;
   min-height: 560rpx;
-  background: #FFFFFF;
+  background: $app-surface;
   border-radius: 32rpx;
   display: flex;
   flex-direction: column;
@@ -139,7 +139,7 @@ export default {
 .cg-title {
   font-size: 36rpx;
   font-weight: 700;
-  color: #1E293B;
+  color: $uni-text-color;
   text-align: center;
   margin-bottom: 20rpx;
   display: block;
@@ -148,21 +148,21 @@ export default {
 .cg-title--intro { font-size: 32rpx; font-weight: 800; }
 .cg-desc {
   font-size: 28rpx;
-  color: #64748B;
+  color: $uni-text-color-secondary;
   text-align: center;
   line-height: 1.6;
   display: block;
   padding: 0 16rpx;
 }
 .cg-dots { display: flex; align-items: center; justify-content: center; padding: 0 56rpx 32rpx; }
-.cg-dot { width: 14rpx; height: 14rpx; border-radius: 50%; background: #CBD5E1; margin: 0 8rpx; }
-.cg-dot--active { background: #F97316; transform: scale(1.3); }
+.cg-dot { width: 14rpx; height: 14rpx; border-radius: 50%; background: $uni-border-color; margin: 0 8rpx; }
+.cg-dot--active { background: $brand-primary; transform: scale(1.3); }
 .cg-action { width: 100%; padding: 0 56rpx 48rpx; box-sizing: border-box; }
 .cg-btn {
   width: 100%;
   height: 88rpx;
   border-radius: 999rpx;
-  color: #FFFFFF;
+  color: $app-surface;
   font-size: 32rpx;
   font-weight: 700;
   display: flex;

+ 27 - 26
cfc-frontend/components/ContentSharePoster.vue

@@ -130,7 +130,7 @@ export default {
       // 选中的管理端模板 id(未使用管理端模板时为 null)
       activeTemplateId: null,
       // 可选的背景色(渐变模板下展示)
-      bgColors: ['#FF8C42', '#6366F1', '#F59E0B', '#10B981', '#FF6B9D', '#1E293B', '#FFFFFF']
+      bgColors: ['BrandColors.body', 'BrandColors.wisdom', '#F59E0B', 'BrandColors.success', '#FF6B9D', '#1E293B', 'BrandColors.white']
     }
   },
   methods: {
@@ -179,19 +179,19 @@ export default {
       var tpl = this.getActiveTemplate()
       if (tpl) {
         return {
-          main: tpl.mainColor || '#F97316',
+          main: tpl.mainColor || 'BrandColors.primary',
           dark: tpl.darkColor || '#EA580C',
-          light: tpl.lightColor || '#FFF7ED'
+          light: tpl.lightColor || 'BrandColors.primaryPale'
         }
       }
       var map = {
-        body: { main: '#FF8C42', dark: '#EA580C', light: '#FFF7ED' },
+        body: { main: 'BrandColors.body', dark: '#EA580C', light: 'BrandColors.primaryPale' },
         mind: { main: '#FF6B9D', dark: '#DB2777', light: '#FDF2F8' },
-        wisdom: { main: '#6366F1', dark: '#4F46E5', light: '#EEF2FF' },
-        action: { main: '#10B981', dark: '#059669', light: '#ECFDF5' },
+        wisdom: { main: 'BrandColors.wisdom', dark: '#4F46E5', light: '#EEF2FF' },
+        action: { main: 'BrandColors.success', dark: '#059669', light: '#ECFDF5' },
         wealth: { main: '#F59E0B', dark: '#D97706', light: '#FFFBEB' }
       }
-      return map[this.dimensionCode] || { main: '#F97316', dark: '#EA580C', light: '#FFF7ED' }
+      return map[this.dimensionCode] || { main: 'BrandColors.primary', dark: '#EA580C', light: 'BrandColors.primaryPale' }
     },
     // 海报模板配色
     getTemplateConfig() {
@@ -209,7 +209,7 @@ export default {
         return { template: 'minimal', bgColor: dark, main: colors.main, dark: colors.dark, light: colors.light }
       }
       // 卡片模板:浅色背景+彩色顶栏
-      return { template: 'card', bgColor: bgColor || '#FFFFFF', main: colors.main, dark: colors.dark, light: colors.light }
+      return { template: 'card', bgColor: bgColor || 'BrandColors.white', main: colors.main, dark: colors.dark, light: colors.light }
     },
     // 获取当前模板的背景样式
     getBgStyle() {
@@ -221,7 +221,7 @@ export default {
       if (cfg.template === 'minimal') {
         return 'linear-gradient(135deg, ' + cfg.bgColor + ', ' + cfg.dark + ')'
       }
-      return 'linear-gradient(135deg, ' + cfg.main + ', ' + (cfg.bgColor || '#FFFFFF') + ')'
+      return 'linear-gradient(135deg, ' + cfg.main + ', ' + (cfg.bgColor || 'BrandColors.white') + ')'
     },
     drawPoster() {
       var self = this
@@ -267,7 +267,7 @@ export default {
       }
       // 【模板3 · card】白色背景
       else {
-        ctx.setFillStyle('#FFFFFF')
+        ctx.setFillStyle('BrandColors.white')
         ctx.fillRect(0, 0, w, h)
       }
 
@@ -342,7 +342,7 @@ export default {
 
     _drawTopDecoration(ctx, w, colors) {
       var centerX = w / 2
-      var dotColors = ['#FF8C42', '#6366F1', '#F59E0B', '#10B981', '#FF6B9D']
+      var dotColors = ['BrandColors.body', 'BrandColors.wisdom', '#F59E0B', 'BrandColors.success', '#FF6B9D']
       var dotY = 28
       var dotR = 5
       var dotSpacing = 26
@@ -357,7 +357,7 @@ export default {
         ctx.fill()
       }
 
-      ctx.setFillStyle('#ffffff')
+      ctx.setFillStyle('BrandColors.white')
       ctx.setFontSize(this._fs(20))
       ctx.setTextAlign('center')
       ctx.setTextBaseline('top')
@@ -390,7 +390,7 @@ export default {
       this._roundRect(ctx, x, y, w, h, 8)
       ctx.fill()
 
-      ctx.setFillStyle('#F97316')
+      ctx.setFillStyle('BrandColors.primary')
       ctx.setFontSize(this._fs(16))
       ctx.setTextAlign('center')
       ctx.setTextBaseline('middle')
@@ -402,7 +402,7 @@ export default {
       var data = this.energyData || {}
       var dims = data.dims || []
 
-      ctx.setFillStyle('#FFFFFF')
+      ctx.setFillStyle('BrandColors.white')
       this._roundRect(ctx, x, y, w, h, 8)
       ctx.fill()
 
@@ -446,7 +446,7 @@ export default {
         ctx.arc(cx, y + 94, 17, 0, 2 * Math.PI)
         ctx.fill()
 
-        ctx.setFillStyle('#FFFFFF')
+        ctx.setFillStyle('BrandColors.white')
         ctx.setFontSize(this._fs(15))
         ctx.setTextAlign('center')
         ctx.setTextBaseline('middle')
@@ -487,7 +487,7 @@ export default {
       this._roundRect(ctx, x, y, w, h, 8)
       ctx.fill()
 
-      var dotColors = ['#FF8C42', '#6366F1', '#F59E0B', '#10B981', '#FF6B9D']
+      var dotColors = ['BrandColors.body', 'BrandColors.wisdom', '#F59E0B', 'BrandColors.success', '#FF6B9D']
       var centerX = x + w / 2
       for (var i = 0; i < 5; i++) {
         ctx.setFillStyle(dotColors[i])
@@ -496,7 +496,7 @@ export default {
         ctx.fill()
       }
 
-      ctx.setFillStyle('#FFFFFF')
+      ctx.setFillStyle('BrandColors.white')
       ctx.setFontSize(this._fs(24))
       ctx.setTextAlign('center')
       ctx.setTextBaseline('top')
@@ -579,7 +579,7 @@ export default {
           data: base64Data,
           encoding: 'base64',
           success: function() {
-            ctx.setFillStyle('#ffffff')
+            ctx.setFillStyle('BrandColors.white')
             self._roundRect(ctx, qrX - 10, qrY - 10, qrSize + 20, qrSize + 20, 8)
             ctx.fill()
 
@@ -637,7 +637,7 @@ export default {
     },
 
     _drawQrPlaceholder(ctx, x, y, size) {
-      ctx.setFillStyle('#ffffff')
+      ctx.setFillStyle('BrandColors.white')
       this._roundRect(ctx, x - 10, y - 10, size + 20, size + 20, 8)
       ctx.fill()
 
@@ -680,7 +680,7 @@ export default {
       ctx.setTextBaseline('top')
       ctx.fillText(this.ctaText || '长按识别二维码 · 查看详情', centerX, bottomY)
 
-      var dotColors = ['#FF8C42', '#6366F1', '#F59E0B', '#10B981', '#FF6B9D']
+      var dotColors = ['BrandColors.body', 'BrandColors.wisdom', '#F59E0B', 'BrandColors.success', '#FF6B9D']
       var dotY = h - 22
       var dotR = 4
       var dotSpacing = 20
@@ -765,8 +765,9 @@ export default {
   }
 }
 </script>
+import { body, primary, primaryPale, success, white, wisdom } from '@/utils/brand-colors.js'
 
-<style scoped>
+<style scoped lang="scss">
 .poster-overlay {
   position: fixed;
   top: 0;
@@ -806,8 +807,8 @@ export default {
 }
 .poster-btn::after { border: none; }
 .poster-btn.save {
-  background: #ffffff;
-  color: #F97316;
+  background: $app-surface;
+  color: $brand-primary;
   font-weight: 600;
 }
 .poster-btn.save:active { opacity: 0.85; }
@@ -819,7 +820,7 @@ export default {
 }
 .tpl-label {
   margin-right: 20rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 .tpl-options {
   display: flex;
@@ -843,8 +844,8 @@ export default {
 .tpl-text {
   font-size: 14rpx;
   text-align: center;
-  color: #666;
+  color: $facade-text-body;
 }
-.tpl-active .tpl-text { color: #F97316; }
+.tpl-active .tpl-text { color: $brand-primary; }
 
 </style>

+ 21 - 21
cfc-frontend/components/DimensionActivities.vue

@@ -39,7 +39,7 @@
               class="card-cover-img"
               :src="getImageUrl(act.coverImage) || '/static/default-activity.png'"
               mode="aspectFill"
-            />
+             lazy-load />
             <!-- 右侧信息区 -->
             <view class="card-info">
               <text class="card-title line-clamp-1">{{ act.title }}</text>
@@ -219,7 +219,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .section {
   margin: 16rpx 20rpx;
 }
@@ -253,7 +253,7 @@ export default {
 .section-title {
   font-size: 32rpx;
   font-weight: 700;
-  color: #1E293B;
+  color: $uni-text-color;
   letter-spacing: 1rpx;
 }
 .section-more {
@@ -261,17 +261,17 @@ export default {
   flex-direction: row;
   align-items: center;
   padding: 8rpx 20rpx;
-  background: #F1F5F9;
+  background: $facade-fill-slate;
   border-radius: 24rpx;
 }
 .section-more-text {
   font-size: 24rpx;
-  color: #64748B;
+  color: $uni-text-color-secondary;
   margin-right: 4rpx;
 }
 .section-more-arrow {
   font-size: 20rpx;
-  color: #94A3B8;
+  color: $app-text-subtle;
 }
 
 /* ===== 空状态 ===== */
@@ -287,7 +287,7 @@ export default {
 }
 .empty-tip {
   font-size: 26rpx;
-  color: #94A3B8;
+  color: $app-text-subtle;
 }
 
 /* ===== 活动轮播 ===== */
@@ -326,12 +326,12 @@ export default {
 .dot.active {
   width: 32rpx;
   border-radius: 4rpx;
-  background: #F97316;
+  background: $brand-primary;
 }
 
 /* ===== 活动卡片(行布局) ===== */
 .activity-card {
-  background: #FFFFFF;
+  background: $app-surface;
   border-radius: 16rpx;
   overflow: hidden;
   box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
@@ -348,7 +348,7 @@ export default {
   width: 33.33%;
   height: 100%;
   flex-shrink: 0;
-  background: #F1F5F9;
+  background: $facade-fill-slate;
 }
 
 /* 右侧信息区 — 文字右对齐 */
@@ -367,7 +367,7 @@ export default {
 .card-title {
   font-size: 28rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 8rpx;
 }
 .line-clamp-1 {
@@ -399,7 +399,7 @@ export default {
 
 .meta-text {
   font-size: 24rpx;
-  color: #64748B;
+  color: $uni-text-color-secondary;
 }
 
 .card-badges {
@@ -438,32 +438,32 @@ export default {
   font-size: 20rpx;
   padding: 2rpx 10rpx;
   border-radius: 8rpx;
-  background: #E8F5E9;
-  color: #2E7D32;
+  background: $mat-green-tint;
+  color: $mat-green-strong;
 }
 .card-status.status-upcoming {
-  background: #E3F2FD;
-  color: #1565C0;
+  background: $mat-blue-tint;
+  color: $mat-blue-strong;
 }
 .card-status.status-ended {
-  background: #F5F5F5;
-  color: #999;
+  background: $facade-fill-2;
+  color: $facade-text-muted;
 }
 
 .card-price {
   font-size: 24rpx;
   font-weight: bold;
-  color: #F97316;
+  color: $brand-primary;
 }
 
 .card-price.member-teaser {
-  color: #F97316;
+  color: $brand-primary;
   text-decoration: underline;
 }
 
 .card-price-free {
   font-size: 24rpx;
   font-weight: 600;
-  color: #22C55E;
+  color: $status-success-2;
 }
 </style>

+ 9 - 9
cfc-frontend/components/DimensionArticles.vue

@@ -139,7 +139,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .section {
   margin: 20rpx 20rpx;
 }
@@ -152,18 +152,18 @@ export default {
 .section-title {
   font-size: 30rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 .section-more {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .article-list {
   display: flex;
   flex-direction: column;
 }
 .article-card {
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 28rpx 24rpx;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
@@ -180,13 +180,13 @@ export default {
 }
 .article-category-text {
   font-size: 20rpx;
-  color: #fff;
+  color: $app-surface;
   font-weight: 500;
 }
 .article-title {
   font-size: 28rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 10rpx;
 }
@@ -205,7 +205,7 @@ export default {
 }
 .article-summary {
   font-size: 24rpx;
-  color: #888;
+  color: $facade-text-faint;
   line-height: 1.5;
   display: block;
   margin-bottom: 20rpx;
@@ -227,11 +227,11 @@ export default {
 }
 .meta-text {
   font-size: 22rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .article-date {
   font-size: 22rpx;
-  color: #bbb;
+  color: $facade-text-ghost-light;
   margin-left: auto;
 }
 </style>

+ 6 - 6
cfc-frontend/components/DimensionIntroCard.vue

@@ -65,7 +65,7 @@ export default {
 }
 </script>
 
-<style>
+<style lang="scss">
 .dim-intro-card {
   margin: 16rpx 30rpx 0;
   padding: 24rpx 28rpx;
@@ -88,12 +88,12 @@ export default {
 .dim-intro-title {
   font-size: 28rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
 }
 .dim-intro-subtitle {
   font-size: 22rpx;
-  color: #666;
+  color: $facade-text-body;
   margin-top: 4rpx;
   display: block;
 }
@@ -108,18 +108,18 @@ export default {
 }
 .dim-intro-bullet {
   font-size: 16rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-right: 8rpx;
 }
 .dim-intro-feature-text {
   font-size: 24rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 .dim-intro-footer {
   text-align: right;
 }
 .dim-intro-link {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 </style>

+ 9 - 9
cfc-frontend/components/DimensionProducts.vue

@@ -5,7 +5,7 @@
     </view>
     <view v-else :class="['product-list', layout === 'mall' ? 'product-list-mall' : '']">
       <view :class="['product-card', layout === 'mall' ? 'product-card-mall' : '']" v-for="prod in displayProducts" :key="prod.id" @click="onProductClick(prod)">
-        <image class="product-img" :class="layout === 'mall' ? 'product-img-mall' : ''" :src="getImageUrl(prod.coverImage) || '/static/default-product.png'" mode="aspectFill" />
+        <image class="product-img" :class="layout === 'mall' ? 'product-img-mall' : ''" :src="getImageUrl(prod.coverImage) || '/static/default-product.png'" mode="aspectFill"  lazy-load />
         <view class="product-body" :class="layout === 'mall' ? 'product-body-mall' : ''">
           <text class="product-name" :class="layout === 'mall' ? 'product-name-mall' : ''">{{ prod.name }}</text>
           <view class="price-row">
@@ -160,14 +160,14 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .product-list {
   display: flex;
   flex-wrap: wrap;
 }
 .product-card {
   width: calc(25% - 12rpx);
-  background: #fff;
+  background: $app-surface;
   border-radius: 12rpx;
   overflow: hidden;
   box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
@@ -180,7 +180,7 @@ export default {
 .product-img {
   width: 100%;
   height: 160rpx;
-  background: #f0f0f0;
+  background: $facade-fill;
 }
 .product-body {
   padding: 8rpx 10rpx 12rpx;
@@ -188,7 +188,7 @@ export default {
 .product-name {
   display: block;
   font-size: 22rpx;
-  color: #333;
+  color: $facade-text-strong;
   line-height: 1.4;
   margin-bottom: 4rpx;
   overflow: hidden;
@@ -201,7 +201,7 @@ export default {
 }
 .product-price {
   font-size: 24rpx;
-  color: #F97316;
+  color: $brand-primary;
   font-weight: bold;
 }
 /* ===== 商城样式(首页 layout="mall")===== */
@@ -239,7 +239,7 @@ export default {
 }
 .empty-tip {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .loading-state {
   display: flex;
@@ -255,7 +255,7 @@ export default {
 }
 .loading-text {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .no-more {
   display: flex;
@@ -265,6 +265,6 @@ export default {
 }
 .no-more-text {
   font-size: 22rpx;
-  color: #ccc;
+  color: $facade-text-ghost;
 }
 </style>

+ 12 - 12
cfc-frontend/components/DimensionTasks.vue

@@ -60,7 +60,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .section {
   margin: 20rpx 20rpx;
 }
@@ -73,11 +73,11 @@ export default {
 .section-title {
   font-size: 30rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 .section-more {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .task-list {
   display: flex;
@@ -87,7 +87,7 @@ export default {
   display: flex;
   flex-direction: row;
   align-items: center;
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
   padding: 24rpx 20rpx;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
@@ -101,19 +101,19 @@ export default {
   width: 44rpx;
   height: 44rpx;
   border-radius: 50%;
-  border: 3rpx solid #ddd;
+  border: 3rpx solid $facade-border-soft;
   display: flex;
   align-items: center;
   justify-content: center;
   background: #f9f9f9;
 }
 .task-check.checked {
-  background: #22C55E;
-  border-color: #22C55E;
+  background: $status-success-2;
+  border-color: $status-success-2;
 }
 .task-check-icon {
   font-size: 24rpx;
-  color: #fff;
+  color: $app-surface;
   font-weight: bold;
 }
 .task-content {
@@ -125,11 +125,11 @@ export default {
 }
 .task-name {
   font-size: 28rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 .task-name.done {
   text-decoration: line-through;
-  color: #999;
+  color: $facade-text-muted;
 }
 .task-meta {
   display: flex;
@@ -138,11 +138,11 @@ export default {
 }
 .task-points {
   font-size: 24rpx;
-  color: #22C55E;
+  color: $status-success-2;
   margin-right: 12rpx;
 }
 .task-penalty {
   font-size: 24rpx;
-  color: #EF4444;
+  color: $status-danger;
 }
 </style>

+ 9 - 9
cfc-frontend/components/DimensionVisitorCard.vue

@@ -99,7 +99,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .dvc {
   margin: 12rpx 30rpx 0;
 }
@@ -115,13 +115,13 @@ export default {
 .dvc-title {
   font-size: 30rpx;
   font-weight: 600;
-  color: #1f2937;
+  color: $facade-text-gray-800;
   display: block;
   line-height: 1.4;
 }
 .dvc-sub {
   font-size: 22rpx;
-  color: #999999;
+  color: $facade-text-muted;
   margin-top: 6rpx;
   display: block;
 }
@@ -144,7 +144,7 @@ export default {
 }
 .dvc-benefit-text {
   font-size: 26rpx;
-  color: #333333;
+  color: $facade-text-strong;
   line-height: 1.5;
 }
 
@@ -158,7 +158,7 @@ export default {
   height: 88rpx;
   line-height: 88rpx;
   border-radius: 44rpx;
-  color: #ffffff;
+  color: $app-surface;
   font-size: 28rpx;
   font-weight: 600;
   border: none;
@@ -170,7 +170,7 @@ export default {
   height: 88rpx;
   line-height: 88rpx;
   border-radius: 44rpx;
-  background: #ffffff;
+  background: $app-surface;
   font-size: 28rpx;
   font-weight: 500;
   border: 2rpx solid;
@@ -180,8 +180,8 @@ export default {
 
 .dvc-preview {
   margin-top: 24rpx;
-  background: #ffffff;
-  border: 1rpx dashed #e5e7eb;
+  background: $app-surface;
+  border: 1rpx dashed $facade-gray-200;
   border-radius: 20rpx;
   padding: 40rpx 28rpx;
   display: flex;
@@ -194,7 +194,7 @@ export default {
 }
 .dvc-preview-text {
   font-size: 24rpx;
-  color: #999999;
+  color: $facade-text-muted;
   text-align: center;
   line-height: 1.6;
 }

+ 32 - 32
cfc-frontend/components/FamilyChallengeCard.vue

@@ -179,13 +179,13 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .challenge-card {
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
   margin: 0 20rpx 12rpx 20rpx;
   padding: 14rpx 16rpx;
-  border-left: 6rpx solid #10B981;
+  border-left: 6rpx solid $status-success;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
 }
 .card-header {
@@ -197,19 +197,19 @@ export default {
 .card-title {
   font-size: 26rpx;
   font-weight: 700;
-  color: #1E293B;
+  color: $uni-text-color;
 }
 .card-status {
   font-size: 20rpx;
   padding: 2rpx 10rpx;
   border-radius: 999rpx;
 }
-.status-active { background: #DCFCE7; color: #16A34A; }
-.status-completed { background: #F1F5F9; color: #64748B; }
-.status-cancelled { background: #FEE2E2; color: #DC2626; }
+.status-active { background: $status-success-soft-2; color: $status-success-strong-2; }
+.status-completed { background: $facade-fill-slate; color: $uni-text-color-secondary; }
+.status-cancelled { background: $status-danger-soft; color: $status-danger-deep; }
 .card-desc {
   font-size: 22rpx;
-  color: #64748B;
+  color: $uni-text-color-secondary;
   line-height: 1.4;
   margin-bottom: 8rpx;
   display: block;
@@ -217,7 +217,7 @@ export default {
 
 /* 待同意提示区 */
 .pending-section {
-  background: #FEF3C7;
+  background: $status-warning-soft;
   border-radius: 10rpx;
   padding: 10rpx 12rpx;
   margin-bottom: 6rpx;
@@ -241,13 +241,13 @@ export default {
   border-radius: 999rpx;
 }
 .member-rejected {
-  background: #FEE2E2;
-  color: #DC2626;
+  background: $status-danger-soft;
+  color: $status-danger-deep;
 }
 
 /* 目标区 */
 .target-section {
-  background: #F8FAFC;
+  background: $facade-fill-slate-50;
   border-radius: 10rpx;
   padding: 10rpx 12rpx;
   margin-bottom: 6rpx;
@@ -258,8 +258,8 @@ export default {
   align-items: center;
   margin-bottom: 6rpx;
 }
-.target-label { font-size: 22rpx; color: #475569; font-weight: 600; }
-.target-value { font-size: 26rpx; color: #10B981; font-weight: 700; }
+.target-label { font-size: 22rpx; color: $facade-text-slate-deep; font-weight: 600; }
+.target-value { font-size: 26rpx; color: $status-success; font-weight: 700; }
 
 .card-progress {
   display: flex;
@@ -270,19 +270,19 @@ export default {
 .progress-bar-bg {
   flex: 1;
   height: 10rpx;
-  background: #E2E8F0;
+  background: $uni-border-color-light;
   border-radius: 999rpx;
   overflow: hidden;
 }
 .progress-bar-fill {
   height: 100%;
-  background: linear-gradient(90deg, #10B981, #34D399);
+  background: linear-gradient(90deg, $status-success, $status-success-light-3);
   border-radius: 999rpx;
   transition: width 0.5s ease;
 }
 .progress-text {
   font-size: 20rpx;
-  color: #10B981;
+  color: $status-success;
   font-weight: 600;
   min-width: 90rpx;
   text-align: right;
@@ -300,7 +300,7 @@ export default {
 }
 .member-name {
   font-size: 20rpx;
-  color: #475569;
+  color: $facade-text-slate-deep;
   width: 90rpx;
   overflow: hidden;
   white-space: nowrap;
@@ -309,24 +309,24 @@ export default {
 .member-bar-wrap { flex: 1; }
 .member-bar-bg {
   height: 8rpx;
-  background: #E2E8F0;
+  background: $uni-border-color-light;
   border-radius: 999rpx;
   overflow: hidden;
 }
 .member-bar-fill {
   height: 100%;
-  background: linear-gradient(90deg, #6366F1, #8B5CF6);
+  background: linear-gradient(90deg, $dim-wisdom, $facade-violet);
   border-radius: 999rpx;
 }
 .member-val {
   font-size: 18rpx;
-  color: #64748B;
+  color: $uni-text-color-secondary;
   min-width: 80rpx;
   text-align: right;
 }
 .member-done {
   font-size: 22rpx;
-  color: #10B981;
+  color: $status-success;
   font-weight: 700;
 }
 
@@ -338,10 +338,10 @@ export default {
   margin-top: 6rpx;
   flex-wrap: wrap;
 }
-.detail-label { font-size: 20rpx; color: #94A3B8; }
-.detail-val { font-size: 20rpx; color: #334155; }
-.detail-val.reward { color: #F97316; font-weight: 600; }
-.detail-val.badge { color: #6366F1; }
+.detail-label { font-size: 20rpx; color: $app-text-subtle; }
+.detail-val { font-size: 20rpx; color: $facade-text-slate-700; }
+.detail-val.reward { color: $brand-primary; font-weight: 600; }
+.detail-val.badge { color: $dim-wisdom; }
 
 .card-action {
   margin-top: 8rpx;
@@ -351,8 +351,8 @@ export default {
 .checkin-btn {
   padding: 10rpx 18rpx;
   border-radius: 999rpx;
-  background: #10B981;
-  color: #FFFFFF;
+  background: $status-success;
+  color: $app-surface;
   font-size: 22rpx;
   font-weight: 600;
 }
@@ -364,12 +364,12 @@ export default {
   margin-left: 12rpx;
 }
 .respond-agree {
-  background: #10B981;
-  color: #FFFFFF;
+  background: $status-success;
+  color: $app-surface;
 }
 .respond-reject {
-  background: #FFFFFF;
-  color: #DC2626;
+  background: $app-surface;
+  color: $status-danger-deep;
   border: 2rpx solid #FCA5A5;
 }
 </style>

+ 2 - 2
cfc-frontend/components/FamilyEmptyState/FamilyEmptyState.vue

@@ -33,6 +33,6 @@ export default {
 .family-empty-block { padding: 60rpx 24rpx; }
 .family-empty-list { padding: 40rpx 16rpx; }
 .empty-icon { font-size: 48rpx; margin-bottom: 12rpx; }
-.empty-title { font-size: 28rpx; color: #333; margin-bottom: 8rpx; }
-.empty-desc { font-size: 24rpx; color: #999; margin-bottom: 16rpx; }
+.empty-title { font-size: 28rpx; color: $facade-text-strong; margin-bottom: 8rpx; }
+.empty-desc { font-size: 24rpx; color: $facade-text-muted; margin-bottom: 16rpx; }
 </style>

+ 11 - 11
cfc-frontend/components/FamilyEnergyBar.vue

@@ -153,10 +153,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .family-energy-bar {
   margin: 20rpx 30rpx;
-  background: #fff;
+  background: $app-surface;
   border-radius: 24rpx;
   padding: 28rpx 24rpx;
   box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
@@ -168,11 +168,11 @@ export default {
   margin-bottom: 20rpx;
 }
 .bar-icon { font-size: 32rpx; margin-right: 8rpx; }
-.bar-title { font-size: 28rpx; font-weight: bold; color: #333; flex: 1; }
-.bar-total-score { font-size: 40rpx; font-weight: bold; color: #F97316; margin-right: 12rpx; }
+.bar-title { font-size: 28rpx; font-weight: bold; color: $facade-text-strong; flex: 1; }
+.bar-total-score { font-size: 40rpx; font-weight: bold; color: $brand-primary; margin-right: 12rpx; }
 .bar-expand-icon {
   font-size: 20rpx;
-  color: #bbb;
+  color: $facade-text-ghost-light;
   transition: transform 0.3s ease;
 }
 .bar-row {
@@ -186,14 +186,14 @@ export default {
 .bar-row-member:active { opacity: 0.6; }
 .bar-label {
   font-size: 24rpx;
-  color: #666;
+  color: $facade-text-body;
   width: 80rpx;
   flex-shrink: 0;
 }
 .bar-track {
   flex: 1;
   height: 24rpx;
-  background: #f0f0f0;
+  background: $facade-fill;
   border-radius: 12rpx;
   overflow: hidden;
   margin: 0 12rpx;
@@ -206,18 +206,18 @@ export default {
 .bar-value {
   font-size: 26rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   width: 60rpx;
   text-align: right;
   flex-shrink: 0;
 }
 .bar-arrow {
   font-size: 28rpx;
-  color: #ccc;
+  color: $facade-text-ghost;
   margin-left: 8rpx;
 }
 .bar-row-family .bar-fill {
-  background: linear-gradient(90deg, #F97316, #FB923C);
+  background: linear-gradient(90deg, $brand-primary, $brand-primary-light);
 }
 
 /* ===== 堆叠模式 ===== */
@@ -253,6 +253,6 @@ export default {
 }
 .legend-label {
   font-size: 22rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 </style>

+ 17 - 17
cfc-frontend/components/FamilyFeed.vue

@@ -226,10 +226,10 @@ export default {
 }
 </script>
 
-<style>
+<style lang="scss">
 .family-feed {
   margin: 12rpx 30rpx 0;
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
   padding: 10rpx 16rpx;
   box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
@@ -254,7 +254,7 @@ export default {
   margin-right: 12rpx;
 }
 
-.feed-title { font-size: 24rpx; font-weight: bold; color: #333; }
+.feed-title { font-size: 24rpx; font-weight: bold; color: $facade-text-strong; }
 .feed-icon  { font-size: 20rpx; }
 
 @keyframes spin {
@@ -313,19 +313,19 @@ export default {
 
 .feed-item-text {
   flex: 1; min-width: 0;
-  font-size: 24rpx; color: #666;
+  font-size: 24rpx; color: $facade-text-body;
   white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
 }
 
 .feed-item-dot {
   width: 12rpx; height: 12rpx;
-  border-radius: 50%; background: #EF4444;
+  border-radius: 50%; background: $status-danger;
   margin-left: 8rpx; flex-shrink: 0;
 }
 
 .feed-count {
   flex-shrink: 0; margin-left: 12rpx;
-  font-size: 20rpx; color: #F97316;
+  font-size: 20rpx; color: $brand-primary;
   background: rgba(249,115,22,0.1);
   border-radius: 999rpx; padding: 2rpx 12rpx; line-height: 1.4;
 }
@@ -333,7 +333,7 @@ export default {
 .feed-empty {
   height: 48rpx; line-height: 48rpx; text-align: center;
 }
-.feed-empty-text { font-size: 24rpx; color: #ccc; }
+.feed-empty-text { font-size: 24rpx; color: $facade-text-ghost; }
 
 /* ===== 消息详情弹层 ===== */
 .feed-detail-mask {
@@ -345,7 +345,7 @@ export default {
 
 .feed-detail-card {
   width: 100%; max-height: 80vh; overflow-y: auto;
-  background: #fff; border-radius: 24rpx;
+  background: $app-surface; border-radius: 24rpx;
   padding: 32rpx 28rpx 24rpx;
   animation: feedDetailIn 0.25s ease;
 }
@@ -366,27 +366,27 @@ export default {
 .feed-detail-icon { font-size: 34rpx; }
 
 .feed-detail-head-texts { flex: 1; min-width: 0; display: flex; flex-direction: column; }
-.feed-detail-type { font-size: 26rpx; font-weight: bold; color: #333; }
-.feed-detail-time { font-size: 20rpx; color: #999; margin-top: 4rpx; }
-.feed-detail-close { font-size: 40rpx; color: #bbb; line-height: 1; padding: 0 8rpx; flex-shrink: 0; }
+.feed-detail-type { font-size: 26rpx; font-weight: bold; color: $facade-text-strong; }
+.feed-detail-time { font-size: 20rpx; color: $facade-text-muted; margin-top: 4rpx; }
+.feed-detail-close { font-size: 40rpx; color: $facade-text-ghost-light; line-height: 1; padding: 0 8rpx; flex-shrink: 0; }
 
 .feed-detail-title {
-  display: block; font-size: 30rpx; font-weight: bold; color: #333;
+  display: block; font-size: 30rpx; font-weight: bold; color: $facade-text-strong;
   line-height: 1.4; margin-top: 24rpx;
 }
 .feed-detail-content {
-  display: block; font-size: 26rpx; color: #666; line-height: 1.6; margin-top: 12rpx;
+  display: block; font-size: 26rpx; color: $facade-text-body; line-height: 1.6; margin-top: 12rpx;
 }
 .feed-detail-amount {
   display: inline-block; margin-top: 16rpx;
-  background: linear-gradient(135deg, #F97316, #FB923C);
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
   border-radius: 999rpx; padding: 6rpx 20rpx;
 }
-.feed-detail-amount-text { font-size: 24rpx; color: #fff; font-weight: bold; }
+.feed-detail-amount-text { font-size: 24rpx; color: $app-surface; font-weight: bold; }
 .feed-detail-btn {
   margin-top: 28rpx; height: 76rpx; border-radius: 38rpx;
-  background: linear-gradient(135deg, #F97316, #FB923C);
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
   display: flex; align-items: center; justify-content: center;
 }
-.feed-detail-btn-text { font-size: 28rpx; color: #fff; font-weight: bold; }
+.feed-detail-btn-text { font-size: 28rpx; color: $app-surface; font-weight: bold; }
 </style>

+ 12 - 12
cfc-frontend/components/FamilyMemberCard.vue

@@ -100,7 +100,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .family-member-card {
   display: flex;
   flex-direction: column;
@@ -108,13 +108,13 @@ export default {
   width: 110rpx;
   padding: 10rpx 8rpx;
   border-radius: 16rpx;
-  background: #FFFFFF;
+  background: $app-surface;
   border: 2rpx solid transparent;
   position: relative;
   flex-shrink: 0;
 }
 .family-member-card.selected {
-  border-color: #F97316;
+  border-color: $brand-primary;
   box-shadow: 0 0 0 2rpx rgba(249, 115, 22, 0.15);
 }
 .card-avatar-wrap {
@@ -127,7 +127,7 @@ export default {
   width: 64rpx;
   height: 64rpx;
   border-radius: 50%;
-  background: #F3F4F6;
+  background: $facade-fill-gray-100;
 }
 .card-relation-badge {
   position: absolute;
@@ -141,7 +141,7 @@ export default {
 }
 .card-relation-text {
   font-size: 16rpx;
-  color: #FFFFFF;
+  color: $app-surface;
   font-weight: 500;
   line-height: 1.4;
 }
@@ -173,7 +173,7 @@ export default {
 }
 .card-nickname {
   font-size: 22rpx;
-  color: #374151;
+  color: $facade-text-gray-700;
   font-weight: 600;
   text-align: center;
   line-height: 1.3;
@@ -187,7 +187,7 @@ export default {
 }
 .card-generation-text {
   font-size: 14rpx;
-  color: #9CA3AF;
+  color: $facade-text-gray-400;
 }
 .card-contact-row {
   display: flex;
@@ -200,7 +200,7 @@ export default {
 }
 .contact-phone {
   font-size: 16rpx;
-  color: #3B82F6;
+  color: $status-info;
   margin-left: 2rpx;
 }
 .card-energy {
@@ -211,12 +211,12 @@ export default {
 }
 .energy-value {
   font-size: 20rpx;
-  color: #F97316;
+  color: $brand-primary;
   font-weight: bold;
 }
 .energy-unit {
   font-size: 16rpx;
-  color: #9CA3AF;
+  color: $facade-text-gray-400;
   margin-left: 2rpx;
 }
 .card-selected-mark {
@@ -226,14 +226,14 @@ export default {
   width: 24rpx;
   height: 24rpx;
   border-radius: 50%;
-  background: #F97316;
+  background: $brand-primary;
   display: flex;
   align-items: center;
   justify-content: center;
 }
 .mark-icon {
   font-size: 16rpx;
-  color: #FFFFFF;
+  color: $app-surface;
   font-weight: bold;
 }
 </style>

+ 24 - 24
cfc-frontend/components/FamilyMemberStrip.vue

@@ -356,7 +356,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .family-member-strip {
   margin: 16rpx 0;
   padding: 0 30rpx;
@@ -392,7 +392,7 @@ export default {
   bottom: 0;
   left: 0;
   right: 0;
-  background: #FFFFFF;
+  background: $app-surface;
   border-radius: 24rpx 24rpx 0 0;
   z-index: 1000;
   padding: 20rpx 0;
@@ -401,21 +401,21 @@ export default {
 .action-title {
   font-size: 30rpx;
   font-weight: 600;
-  color: #1F2937;
+  color: $facade-text-gray-800;
 }
 .action-relation {
   font-size: 24rpx;
-  color: #9CA3AF;
+  color: $facade-text-gray-400;
   margin-top: 4rpx;
 }
 .action-item {
   display: flex;
   align-items: center;
   padding: 24rpx 30rpx;
-  border-bottom: 1rpx solid #F9FAFB;
+  border-bottom: 1rpx solid $facade-fill-gray-50;
 }
 .action-item:active {
-  background: #F3F4F6;
+  background: $facade-fill-gray-100;
 }
 .action-item-icon {
   font-size: 32rpx;
@@ -423,10 +423,10 @@ export default {
 }
 .action-item-text {
   font-size: 28rpx;
-  color: #374151;
+  color: $facade-text-gray-700;
 }
 .action-danger .action-item-text {
-  color: #EF4444;
+  color: $status-danger;
 }
 .action-cancel {
   justify-content: center;
@@ -434,18 +434,18 @@ export default {
   border-bottom: none;
 }
 .action-cancel .action-item-text {
-  color: #6B7280;
+  color: $facade-text-slate;
 }
 .action-time {
   font-size: 22rpx;
-  color: #9CA3AF;
+  color: $facade-text-gray-400;
   margin-left: 4rpx;
 }
 /* 空状态 */
 .family-strip-empty {
   margin: 16rpx 0;
   padding: 40rpx 30rpx;
-  background: #FFFFFF;
+  background: $app-surface;
   border-radius: 24rpx;
   display: flex;
   justify-content: center;
@@ -456,13 +456,13 @@ export default {
   align-items: center;
 }
 .empty-icon { font-size: 56rpx; }
-.empty-title { font-size: 30rpx; font-weight: 600; color: #1F2937; margin-top: 12rpx; }
-.empty-desc { font-size: 24rpx; color: #9CA3AF; margin-top: 8rpx; }
+.empty-title { font-size: 30rpx; font-weight: 600; color: $facade-text-gray-800; margin-top: 12rpx; }
+.empty-desc { font-size: 24rpx; color: $facade-text-gray-400; margin-top: 8rpx; }
 .empty-btn {
   margin-top: 24rpx;
   padding: 16rpx 48rpx;
-  background: #F97316;
-  color: #FFFFFF;
+  background: $brand-primary;
+  color: $app-surface;
   font-size: 28rpx;
   border-radius: 40rpx;
 }
@@ -472,14 +472,14 @@ export default {
   flex-direction: row;
   align-items: center;
   padding: 24rpx 30rpx;
-  border-bottom: 1rpx solid #F3F4F6;
+  border-bottom: 1rpx solid $facade-fill-gray-100;
 }
 .action-avatar {
   width: 88rpx;
   height: 88rpx;
   border-radius: 50%;
   margin-right: 20rpx;
-  background: #F3F4F6;
+  background: $facade-fill-gray-100;
 }
 .action-header-info {
   display: flex;
@@ -498,12 +498,12 @@ export default {
 .score-label {
   width: 110rpx;
   font-size: 24rpx;
-  color: #6B7280;
+  color: $facade-text-slate;
 }
 .score-bar-track {
   flex: 1;
   height: 14rpx;
-  background: #F3F4F6;
+  background: $facade-fill-gray-100;
   border-radius: 7rpx;
   overflow: hidden;
 }
@@ -511,19 +511,19 @@ export default {
   height: 100%;
   border-radius: 7rpx;
 }
-.score-bar-fill.trust { background: #10B981; }
-.score-bar-fill.intimacy { background: #3B82F6; }
-.score-bar-fill.communication { background: #F59E0B; }
+.score-bar-fill.trust { background: $status-success; }
+.score-bar-fill.intimacy { background: $status-info; }
+.score-bar-fill.communication { background: $dim-wealth; }
 .score-value {
   width: 70rpx;
   text-align: right;
   font-size: 24rpx;
-  color: #1F2937;
+  color: $facade-text-gray-800;
   font-weight: 600;
 }
 .action-divider {
   height: 1rpx;
-  background: #F3F4F6;
+  background: $facade-fill-gray-100;
   margin: 0 30rpx;
 }
 </style>

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

@@ -914,14 +914,14 @@ simulationTick: function() {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .family-graph-wrapper {
   position: relative;
   width: 100%;
   min-height: 200px;
   height: auto;
   margin: 10rpx 0;
-  background: #FFFFFF;
+  background: $app-surface;
   border-radius: 24rpx;
   overflow: hidden;
 }
@@ -948,7 +948,7 @@ simulationTick: function() {
 }
 .gear-text {
   font-size: 32rpx;
-  color: #94A3B8;
+  color: $app-text-subtle;
 }
 /* ⚙ 维护页提示 */
 .add-hint {
@@ -957,7 +957,7 @@ simulationTick: function() {
   width: 40rpx;
   height: 40rpx;
   border-radius: 50%;
-  background: #10B981;
+  background: $status-success;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -972,7 +972,7 @@ simulationTick: function() {
 }
 .add-hint-text {
   font-size: 28rpx;
-  color: #FFFFFF;
+  color: $app-surface;
   font-weight: bold;
   line-height: 1;
 }
@@ -981,12 +981,12 @@ simulationTick: function() {
   align-items: center;
   justify-content: center;
   padding: 60rpx 0;
-  background: #FFFFFF;
+  background: $app-surface;
   border-radius: 24rpx;
   margin: 10rpx 0;
 }
 .empty-text {
   font-size: 28rpx;
-  color: #94A3B8;
+  color: $app-text-subtle;
 }
 </style>

+ 12 - 12
cfc-frontend/components/GenerationPicker.vue

@@ -95,21 +95,21 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .generation-picker-wrap {
   width: 100%;
 }
 .picker {
-  border: 1rpx solid #ddd;
+  border: 1rpx solid $facade-border-soft;
   border-radius: 10rpx;
   padding: 20rpx;
   font-size: 28rpx;
-  color: #666;
-  background: #fff;
+  color: $facade-text-body;
+  background: $app-surface;
 }
 .picker-disabled {
-  background: #f5f5f5;
-  color: #999;
+  background: $facade-fill-2;
+  color: $facade-text-muted;
 }
 .form-item {
   margin-bottom: 10rpx;
@@ -117,12 +117,12 @@ export default {
 .form-item .label {
   display: block;
   font-size: 28rpx;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 10rpx;
   font-weight: bold;
 }
 .required-mark {
-  color: #F97316;
+  color: $brand-primary;
   margin-right: 4rpx;
 }
 .peer-type-wrap {
@@ -144,7 +144,7 @@ export default {
 .radio-icon {
   width: 40rpx;
   height: 40rpx;
-  border: 2rpx solid #ddd;
+  border: 2rpx solid $facade-border-soft;
   border-radius: 50%;
   margin-right: 10rpx;
   display: flex;
@@ -152,16 +152,16 @@ export default {
   justify-content: center;
 }
 .radio-icon.active {
-  border-color: #F97316;
+  border-color: $brand-primary;
 }
 .radio-inner {
   width: 20rpx;
   height: 20rpx;
-  background: #F97316;
+  background: $brand-primary;
   border-radius: 50%;
 }
 .radio-label {
   font-size: 28rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 </style>

+ 9 - 9
cfc-frontend/components/HealthBadge.vue

@@ -34,13 +34,13 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .health-badge {
   display: flex;
   align-items: center;
   gap: 16rpx;
   padding: 20rpx 24rpx;
-  background: linear-gradient(135deg, #F0FDF4, #ECFDF5);
+  background: linear-gradient(135deg, $status-success-tint-2, #ECFDF5);
   border-radius: 20rpx;
   margin: 0 20rpx 16rpx 20rpx;
 }
@@ -53,10 +53,10 @@ export default {
   justify-content: center;
   flex-shrink: 0;
 }
-.badge-lv-1 { background: #D1FAE5; }
+.badge-lv-1 { background: $status-success-soft; }
 .badge-lv-2 { background: #A7F3D0; }
-.badge-lv-3 { background: #FEF3C7; }
-.badge-lv-4 { background: #FED7AA; }
+.badge-lv-3 { background: $status-warning-soft; }
+.badge-lv-4 { background: $brand-primary-light-2; }
 .badge-lv-5 { background: #FECDD3; }
 .badge-icon { font-size: 40rpx; }
 .badge-info { flex: 1; min-width: 0; }
@@ -78,24 +78,24 @@ export default {
 }
 .badge-score {
   font-size: 22rpx;
-  color: #6B7280;
+  color: $facade-text-slate;
 }
 .badge-progress-bar {
   height: 8rpx;
-  background: #D1FAE5;
+  background: $status-success-soft;
   border-radius: 999rpx;
   overflow: hidden;
   margin-top: 8rpx;
 }
 .badge-progress-fill {
   height: 100%;
-  background: linear-gradient(90deg, #10B981, #34D399);
+  background: linear-gradient(90deg, $status-success, $status-success-light-3);
   border-radius: 999rpx;
   transition: width 0.5s ease;
 }
 .badge-next {
   font-size: 20rpx;
-  color: #6B7280;
+  color: $facade-text-slate;
   margin-top: 4rpx;
   display: block;
 }

+ 9 - 9
cfc-frontend/components/HealthTimeline.vue

@@ -28,7 +28,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .timeline {
   padding: 0 20rpx;
   margin-bottom: 32rpx;
@@ -36,7 +36,7 @@ export default {
 .timeline-title {
   font-size: 28rpx;
   font-weight: 700;
-  color: #1E293B;
+  color: $uni-text-color;
   margin-bottom: 16rpx;
 }
 .timeline-list { position: relative; padding-left: 20rpx; }
@@ -55,10 +55,10 @@ export default {
   margin-top: 6rpx;
   z-index: 1;
 }
-.dot-0 { background: #10B981; }
-.dot-1 { background: #F59E0B; }
-.dot-2 { background: #6366F1; }
-.dot-3 { background: #F97316; }
+.dot-0 { background: $status-success; }
+.dot-1 { background: $dim-wealth; }
+.dot-2 { background: $dim-wisdom; }
+.dot-3 { background: $brand-primary; }
 .dot-4 { background: #FF6B9D; }
 .timeline-line {
   position: absolute;
@@ -66,17 +66,17 @@ export default {
   top: 26rpx;
   bottom: 0;
   width: 2rpx;
-  background: #E2E8F0;
+  background: $uni-border-color-light;
 }
 .timeline-content { flex: 1; min-width: 0; }
 .timeline-text {
   font-size: 26rpx;
-  color: #334155;
+  color: $facade-text-slate-700;
   line-height: 1.5;
 }
 .timeline-date {
   font-size: 20rpx;
-  color: #94A3B8;
+  color: $app-text-subtle;
   margin-top: 4rpx;
   display: block;
 }

+ 6 - 6
cfc-frontend/components/LoginGuideCard.vue

@@ -34,10 +34,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .login-guide-card {
   margin: 20rpx 30rpx;
-  background: linear-gradient(135deg, #FFF7ED, #FFE4D6);
+  background: linear-gradient(135deg, $uni-primary-pale, #FFE4D6);
   border-radius: 24rpx;
   padding: 40rpx 32rpx;
   text-align: center;
@@ -46,14 +46,14 @@ export default {
 .guide-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   margin: 16rpx 0;
   display: block;
 }
 .guide-desc { margin-bottom: 24rpx; }
 .guide-desc-item {
   font-size: 26rpx;
-  color: #666;
+  color: $facade-text-body;
   line-height: 1.8;
   display: block;
 }
@@ -69,8 +69,8 @@ export default {
   font-weight: 500;
 }
 .guide-btn.primary {
-  background: linear-gradient(135deg, #F97316, #FB923C);
-  color: #fff;
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
+  color: $app-surface;
 }
 .guide-btn:active { opacity: 0.8; }
 </style>

+ 11 - 11
cfc-frontend/components/OnboardingGuide.vue

@@ -140,7 +140,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 /* ============================================================
    OnboardingGuide — 首次登录引导浮层
    设计体系:统一使用 uni.scss design tokens
@@ -163,7 +163,7 @@ export default {
 .onboarding-card {
   width: 640rpx;
   min-height: 680rpx;
-  background: var(--surface, #FFFFFF);
+  background: var(--surface, $app-surface);
   border-radius: var(--radius-xl, 32rpx);
   display: flex;
   flex-direction: column;
@@ -177,7 +177,7 @@ export default {
 .onboarding-topbar {
   width: 100%;
   height: 12rpx;
-  background: var(--color-primary, #F97316);
+  background: var(--color-primary, $brand-primary);
   flex-shrink: 0;
 }
 
@@ -203,7 +203,7 @@ export default {
 .onboarding-title {
   font-size: 36rpx;
   font-weight: 700;
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
   text-align: center;
   margin-bottom: 20rpx;
   display: block;
@@ -212,7 +212,7 @@ export default {
 
 .onboarding-desc {
   font-size: 28rpx;
-  color: var(--text-secondary, #64748B);
+  color: var(--text-secondary, $uni-text-color-secondary);
   text-align: center;
   line-height: 1.6;
   display: block;
@@ -229,7 +229,7 @@ export default {
 .onboarding-title--intro {
   font-size: 32rpx;
   font-weight: 800;
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, $brand-primary);
   line-height: 1.4;
 }
 
@@ -249,12 +249,12 @@ export default {
   width: 14rpx;
   height: 14rpx;
   border-radius: 50%;
-  background: var(--border, #CBD5E1);
+  background: var(--border, $uni-border-color);
   transition: background 0.3s ease, transform 0.3s ease;
 }
 
 .dot.active {
-  background: var(--color-primary, #F97316);
+  background: var(--color-primary, $brand-primary);
   transform: scale(1.3);
 }
 
@@ -272,8 +272,8 @@ export default {
   width: 100%;
   height: 88rpx;
   border-radius: 999rpx;
-  background: var(--color-primary, #F97316);
-  color: #FFFFFF;
+  background: var(--color-primary, $brand-primary);
+  color: $app-surface;
   font-size: 32rpx;
   font-weight: 700;
   display: flex;
@@ -290,7 +290,7 @@ export default {
 
 /* 最后一步:暖珊瑚色按钮 */
 .onboarding-btn--last {
-  background: var(--color-primary, #F97316);
+  background: var(--color-primary, $brand-primary);
   box-shadow: 0 4rpx 16rpx rgba(249, 115, 22, 0.30);
 }
 </style>

+ 7 - 7
cfc-frontend/components/OnboardingProgressBar.vue

@@ -71,12 +71,12 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .opb-card {
   margin: 20rpx 24rpx;
   padding: 24rpx 28rpx;
-  background: linear-gradient(135deg, #FFF7ED, #FFEDD5);
-  border: 2rpx solid #FED7AA;
+  background: linear-gradient(135deg, $uni-primary-pale, #FFEDD5);
+  border: 2rpx solid $brand-primary-light-2;
   border-radius: 20rpx;
 }
 .opb-head {
@@ -92,22 +92,22 @@ export default {
 }
 .opb-count {
   font-size: 24rpx;
-  color: #C2410C;
+  color: $brand-primary-deep;
 }
 .opb-bar {
   height: 16rpx;
-  background: #FED7AA;
+  background: $brand-primary-light-2;
   border-radius: 8rpx;
   overflow: hidden;
 }
 .opb-bar-fill {
   height: 100%;
-  background: linear-gradient(90deg, #F97316, #FF8C42);
+  background: linear-gradient(90deg, $brand-primary, $dim-body);
   border-radius: 8rpx;
 }
 .opb-tip {
   margin-top: 12rpx;
   font-size: 24rpx;
-  color: #C2410C;
+  color: $brand-primary-deep;
 }
 </style>

+ 7 - 6
cfc-frontend/components/PageBanner.vue

@@ -26,15 +26,15 @@
 var THEME_STYLES = {
   home: {
     background: 'linear-gradient(135deg, #FF8A65 0%, #FFB74D 100%)',
-    color: '#FFFFFF'
+    color: 'BrandColors.white'
   },
   body: {
     background: 'linear-gradient(135deg, #8D6E63 0%, #A1887F 100%)',
-    color: '#FFFFFF'
+    color: 'BrandColors.white'
   },
   mind: {
     background: 'linear-gradient(135deg, #FF6B35 0%, #E65100 100%)',
-    color: '#FFFFFF'
+    color: 'BrandColors.white'
   },
   wisdom: {
     background: 'linear-gradient(135deg, #FFD700 0%, #FFA000 100%)',
@@ -42,15 +42,15 @@ var THEME_STYLES = {
   },
   'mind-wisdom': {
     background: 'linear-gradient(135deg, #FF6B35 0%, #FFD700 100%)',
-    color: '#FFFFFF'
+    color: 'BrandColors.white'
   },
   action: {
     background: 'linear-gradient(135deg, #4CAF50 0%, #66BB6A 100%)',
-    color: '#FFFFFF'
+    color: 'BrandColors.white'
   },
   wealth: {
     background: 'linear-gradient(135deg, #F59E0B 0%, #FBBF24 100%)',
-    color: '#FFFFFF'
+    color: 'BrandColors.white'
   }
 }
 
@@ -83,6 +83,7 @@ export default {
   }
 }
 </script>
+import { white } from '@/utils/brand-colors.js'
 
 <style scoped>
 .page-banner {

+ 17 - 17
cfc-frontend/components/PlayfulButton.vue

@@ -33,7 +33,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .playful-btn {
   display: inline-flex;
   align-items: center;
@@ -51,53 +51,53 @@ export default {
 
 /* ---- Primary ---- */
 .playful-btn--primary {
-  background: var(--color-primary, #F97316);
-  color: #fff;
+  background: var(--color-primary, $brand-primary);
+  color: $app-surface;
   box-shadow: 0 2px 8px rgba(249,115,22,0.3);
 }
 
 /* ---- Secondary ---- */
 .playful-btn--secondary {
-  background: var(--surface, #ffffff);
-  color: var(--text, #1E293B);
-  border: 2rpx solid var(--border, #CBD5E1);
+  background: var(--surface, $app-surface);
+  color: var(--text, $uni-text-color);
+  border: 2rpx solid var(--border, $uni-border-color);
   box-shadow: 0 2px 6px rgba(249,115,22,0.08);
 }
 
 /* ---- Success ---- */
 .playful-btn--success {
-  background: var(--color-success, #22C55E);
-  color: #fff;
+  background: var(--color-success, $status-success-2);
+  color: $app-surface;
   box-shadow: 0 2px 8px rgba(34, 197, 94, 0.3);
 }
 
 /* ---- Warning ---- */
 .playful-btn--warning {
-  background: var(--color-warning, #F59E0B);
-  color: #fff;
+  background: var(--color-warning, $dim-wealth);
+  color: $app-surface;
   box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
 }
 
 /* ---- Error ---- */
 .playful-btn--error {
-  background: var(--color-error, #EF4444);
-  color: #fff;
+  background: var(--color-error, $status-danger);
+  color: $app-surface;
   box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
 }
 
 /* ---- Clay (孩子端玩具风) ---- */
 .playful-btn--clay {
-  background: var(--color-primary, #F97316);
-  color: #fff;
-  border: 2rpx solid var(--border, #CBD5E1);
+  background: var(--color-primary, $brand-primary);
+  color: $app-surface;
+  border: 2rpx solid var(--border, $uni-border-color);
   box-shadow: 0 2px 8px rgba(249,115,22,0.15);
 }
 
 /* ---- Outline ---- */
 .playful-btn--outline {
   background: transparent;
-  color: var(--color-primary, #F97316);
-  border: 2rpx solid var(--color-primary, #F97316);
+  color: var(--color-primary, $brand-primary);
+  border: 2rpx solid var(--color-primary, $brand-primary);
 }
 
 /* ---- Sizes ---- */

+ 13 - 13
cfc-frontend/components/PlayfulCard.vue

@@ -53,32 +53,32 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .playful-card {
-  background: var(--surface, #ffffff);
+  background: var(--surface, $app-surface);
   border-radius: 20rpx;
   padding: 24rpx;
   transition: transform .15s ease, box-shadow .2s ease;
 }
 .playful-card--flat {
-  border: 1px solid var(--border, #CBD5E1);
+  border: 1px solid var(--border, $uni-border-color);
   box-shadow: var(--shadow-md);
 }
 .playful-card--clay {
-  border: 1px solid var(--border-light, #E2E8F0);
-  background: var(--surface, #FFFFFF);
+  border: 1px solid var(--border-light, $uni-border-color-light);
+  background: var(--surface, $app-surface);
   box-shadow: var(--shadow-md);
 }
 .playful-card--bordered {
-  border: 1px solid var(--border-light, #E2E8F0);
+  border: 1px solid var(--border-light, $uni-border-color-light);
 }
 .playful-card--teacher {
-  border-left: 4px solid var(--color-primary, #F97316);
-  border-top: 1px solid var(--border-light, #E2E8F0);
-  border-right: 1px solid var(--border-light, #E2E8F0);
-  border-bottom: 1px solid var(--border-light, #E2E8F0);
+  border-left: 4px solid var(--color-primary, $brand-primary);
+  border-top: 1px solid var(--border-light, $uni-border-color-light);
+  border-right: 1px solid var(--border-light, $uni-border-color-light);
+  border-bottom: 1px solid var(--border-light, $uni-border-color-light);
   box-shadow: var(--shadow-sm);
-  background: var(--surface, #ffffff);
+  background: var(--surface, $app-surface);
 }
 .playful-card--shadow-none { box-shadow: none; }
 .playful-card--shadow-sm { box-shadow: var(--shadow-sm); }
@@ -91,10 +91,10 @@ export default {
   font-weight: 700;
   font-size: 32rpx;
   margin-bottom: 16rpx;
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
 }
 .playful-card__content {
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
   line-height: 1.6;
   font-size: 28rpx;
 }

+ 6 - 6
cfc-frontend/components/ProfileBadges.vue

@@ -111,7 +111,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .section {
   margin: 20rpx 30rpx;
 }
@@ -124,18 +124,18 @@ export default {
 .section-title {
   font-size: 30rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 .section-more {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .badge-grid {
   display: flex;
   flex-direction: row;
   flex-wrap: wrap;
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 24rpx 16rpx;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
@@ -162,12 +162,12 @@ export default {
   box-shadow: 0 4rpx 16rpx rgba(253,160,91,0.3);
 }
 .badge-icon-wrap.locked {
-  background: #f0f0f0;
+  background: $facade-fill;
   opacity: 0.5;
 }
 .badge-name {
   font-size: 20rpx;
-  color: #999;
+  color: $facade-text-muted;
   text-align: center;
 }
 </style>

+ 7 - 7
cfc-frontend/components/ProfileGrowth.vue

@@ -82,7 +82,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .section {
   margin: 20rpx 30rpx;
 }
@@ -95,15 +95,15 @@ export default {
 .section-title {
   font-size: 30rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 .section-more {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .report-card {
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 30rpx;
   display: flex;
@@ -119,16 +119,16 @@ export default {
 .report-stat {
   font-size: 40rpx;
   font-weight: bold;
-  color: #667eea;
+  color: $accent-indigo;
 }
 .report-label {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-top: 8rpx;
 }
 .report-divider {
   width: 1rpx;
   height: 60rpx;
-  background: #f0f0f0;
+  background: $facade-fill;
 }
 </style>

+ 3 - 3
cfc-frontend/components/RadarChart.vue

@@ -848,10 +848,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .radar-chart-wrap {
   margin: 20rpx 30rpx;
-  background: #FFFFFF;
+  background: $app-surface;
   border-radius: 20rpx;
   box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
   overflow: hidden;
@@ -914,7 +914,7 @@ export default {
 
 .legend-text {
   font-size: 22rpx;
-  color: #64748B;
+  color: $uni-text-color-secondary;
   font-weight: 500;
 }
 </style>

+ 19 - 19
cfc-frontend/components/RecommendedFeed.vue

@@ -25,7 +25,7 @@
         >
           <!-- 商品卡片 -->
           <template v-if="item.type === 'product'">
-            <image class="feed-img" :src="getImageUrl(item.coverImage) || '/static/default-product.png'" mode="aspectFill" />
+            <image class="feed-img" :src="getImageUrl(item.coverImage) || '/static/default-product.png'" mode="aspectFill"  lazy-load />
             <view class="feed-info">
               <view class="feed-tag-row">
                 <text class="feed-type-badge product-badge">🛍️ 商品</text>
@@ -38,7 +38,7 @@
           </template>
           <!-- 活动卡片 -->
           <template v-else-if="item.type === 'activity'">
-            <image class="feed-img" :src="getImageUrl(item.coverImage) || '/static/default-activity.png'" mode="aspectFill" />
+            <image class="feed-img" :src="getImageUrl(item.coverImage) || '/static/default-activity.png'" mode="aspectFill"  lazy-load />
             <view class="feed-info">
               <view class="feed-tag-row">
                 <text class="feed-type-badge activity-badge">🔥 活动</text>
@@ -138,7 +138,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .section {
   margin: 20rpx 20rpx;
 }
@@ -151,11 +151,11 @@ export default {
 .section-title {
   font-size: 30rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 .section-more {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .feed-list {
   display: flex;
@@ -165,7 +165,7 @@ export default {
 }
 .feed-card {
   width: calc(50% - 8rpx);
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
   padding: 16rpx;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
@@ -178,7 +178,7 @@ export default {
   width: 100%;
   height: 200rpx;
   border-radius: 12rpx;
-  background: #f0f0f0;
+  background: $facade-fill;
   display: block;
   margin-bottom: 12rpx;
 }
@@ -186,7 +186,7 @@ export default {
   width: 100%;
   height: 200rpx;
   border-radius: 12rpx;
-  background: linear-gradient(135deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
+  background: linear-gradient(135deg, $facade-fill 25%, $facade-fill-4 50%, $facade-fill 75%);
   background-size: 200% 100%;
   animation: shimmer 1.5s infinite;
   margin-bottom: 12rpx;
@@ -213,23 +213,23 @@ export default {
 }
 .product-badge {
   background: #FFF0E6;
-  color: #F97316;
+  color: $brand-primary;
 }
 .activity-badge {
   background: #FEF2E6;
-  color: #EA580C;
+  color: $brand-primary-dark;
 }
 .feed-status-badge {
   font-size: 18rpx;
   padding: 2rpx 8rpx;
   border-radius: 6rpx;
 }
-.status-upcoming { background: #DBEAFE; color: #2563EB; }
-.status-ongoing { background: #D1FAE5; color: #16A34A; }
-.status-ended { background: #F3F4F6; color: #9CA3AF; }
+.status-upcoming { background: $status-info-soft; color: #2563EB; }
+.status-ongoing { background: $status-success-soft; color: $status-success-strong-2; }
+.status-ended { background: $facade-fill-gray-100; color: $facade-text-gray-400; }
 .feed-title {
   font-size: 26rpx;
-  color: #333;
+  color: $facade-text-strong;
   font-weight: 500;
   display: block;
   margin-bottom: 6rpx;
@@ -238,18 +238,18 @@ export default {
 }
 .feed-title-placeholder {
   font-size: 26rpx;
-  color: #ccc;
+  color: $facade-text-ghost;
   display: block;
   margin-bottom: 6rpx;
   min-height: 36rpx;
 }
 .feed-price {
   font-size: 24rpx;
-  color: #F97316;
+  color: $brand-primary;
   font-weight: 600;
 }
 .fee-free {
-  color: #22C55E;
+  color: $status-success-2;
 }
 .feed-meta-row {
   display: flex;
@@ -259,11 +259,11 @@ export default {
 }
 .feed-date {
   font-size: 20rpx;
-  color: #bbb;
+  color: $facade-text-ghost-light;
 }
 .feed-sub-placeholder {
   font-size: 24rpx;
-  color: #ccc;
+  color: $facade-text-ghost;
 }
 .line-clamp-2 {
   overflow: hidden;

+ 3 - 3
cfc-frontend/components/TrendChart.vue

@@ -259,7 +259,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .trend-chart-wrap {
   display: flex;
   flex-direction: column;
@@ -292,11 +292,11 @@ export default {
 }
 .legend-line {
   width: 32rpx;
-  border-top: 4rpx solid #0EA5E9;
+  border-top: 4rpx solid $uni-info;
   margin-right: 8rpx;
 }
 .legend-text {
   font-size: 22rpx;
-  color: #475569;
+  color: $facade-text-slate-deep;
 }
 </style>

+ 22 - 22
cfc-frontend/components/address-picker.vue

@@ -367,7 +367,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .address-picker {
   width: 100%;
 }
@@ -377,20 +377,20 @@ export default {
   align-items: center;
   justify-content: space-between;
   padding: 24rpx 20rpx;
-  background: #f8f8f8;
+  background: $facade-fill-page;
   border-radius: 12rpx;
-  border: 2rpx solid #eee;
+  border: 2rpx solid $facade-hairline-2;
 }
 
 .address-text {
   font-size: 28rpx;
-  color: #333;
+  color: $facade-text-strong;
   flex: 1;
 }
 
 .address-arrow {
   font-size: 32rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-left: 10rpx;
 }
 
@@ -409,7 +409,7 @@ export default {
 
 .picker-panel {
   width: 100%;
-  background: #fff;
+  background: $app-surface;
   border-radius: 24rpx 24rpx 0 0;
   max-height: 70vh;
   display: flex;
@@ -422,24 +422,24 @@ export default {
   align-items: center;
   justify-content: space-between;
   padding: 30rpx 30rpx 20rpx;
-  border-bottom: 1px solid #eee;
+  border-bottom: 1px solid $facade-hairline-2;
 }
 
 .picker-btn {
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
   padding: 10rpx 20rpx;
 }
 
 .picker-confirm {
-  color: #f97316;
+  color: $brand-primary;
   font-weight: bold;
 }
 
 .picker-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 /* 面包屑导航 */
@@ -447,27 +447,27 @@ export default {
   display: flex;
   align-items: center;
   padding: 20rpx 30rpx;
-  background: #fafafa;
-  border-bottom: 1px solid #eee;
+  background: $facade-fill-ghost;
+  border-bottom: 1px solid $facade-hairline-2;
   flex-wrap: wrap;
 }
 
 .crumb {
   font-size: 26rpx;
-  color: #666;
+  color: $facade-text-body;
   padding: 8rpx 16rpx;
-  background: #eee;
+  background: $facade-hairline-2;
   border-radius: 20rpx;
 }
 
 .crumb-active {
-  background: #f97316;
-  color: #fff;
+  background: $brand-primary;
+  color: $app-surface;
 }
 
 .crumb-arrow {
   font-size: 28rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin: 0 8rpx;
 }
 
@@ -484,29 +484,29 @@ export default {
   align-items: center;
   justify-content: space-between;
   padding: 28rpx 40rpx;
-  border-bottom: 1px solid #f5f5f5;
+  border-bottom: 1px solid $facade-fill-2;
 }
 
 .item-text {
   font-size: 30rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .item-check {
   font-size: 32rpx;
-  color: #f97316;
+  color: $brand-primary;
   font-weight: bold;
 }
 
 .picker-item-selected .item-text {
-  color: #f97316;
+  color: $brand-primary;
   font-weight: bold;
 }
 
 .picker-loading {
   padding: 40rpx;
   text-align: center;
-  color: #999;
+  color: $facade-text-muted;
   font-size: 28rpx;
 }
 </style>

+ 11 - 11
cfc-frontend/components/daily-task-card.vue

@@ -122,12 +122,12 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .daily-task-card {
   margin: 24rpx 24rpx 0;
   padding: 28rpx;
   border-radius: 24rpx;
-  background: #FFFFFF;
+  background: $app-surface;
   box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
 }
 .daily-task-head {
@@ -140,11 +140,11 @@ export default {
 .daily-task-title {
   font-size: 32rpx;
   font-weight: 600;
-  color: #1F2937;
+  color: $facade-text-gray-800;
 }
 .daily-task-date {
   font-size: 24rpx;
-  color: #9CA3AF;
+  color: $facade-text-gray-400;
 }
 .daily-task-state {
   display: flex;
@@ -155,7 +155,7 @@ export default {
 }
 .daily-task-state-text {
   font-size: 26rpx;
-  color: #9CA3AF;
+  color: $facade-text-gray-400;
 }
 .daily-task-group {
   display: flex;
@@ -164,7 +164,7 @@ export default {
 }
 .daily-task-group-title {
   font-size: 24rpx;
-  color: #9CA3AF;
+  color: $facade-text-gray-400;
   margin-bottom: 8rpx;
 }
 .daily-task-item {
@@ -173,23 +173,23 @@ export default {
   justify-content: space-between;
   align-items: center;
   padding: 18rpx 0;
-  border-bottom: 1rpx solid #F3F4F6;
+  border-bottom: 1rpx solid $facade-fill-gray-100;
 }
 .daily-task-item:last-child {
   border-bottom: none;
 }
 .daily-task-item-label {
   font-size: 28rpx;
-  color: #374151;
+  color: $facade-text-gray-700;
 }
 .daily-task-go {
   font-size: 26rpx;
-  color: #F97316;
+  color: $brand-primary;
   font-weight: 500;
 }
 .daily-task-done {
   font-size: 26rpx;
-  color: #10B981;
+  color: $status-success;
   font-weight: 600;
 }
 .daily-task-all {
@@ -201,7 +201,7 @@ export default {
 }
 .daily-task-all-text {
   font-size: 28rpx;
-  color: #10B981;
+  color: $status-success;
   font-weight: 500;
 }
 </style>

+ 2 - 2
cfc-frontend/components/gate-lock.vue

@@ -131,7 +131,7 @@ export default {
   border-radius: 16rpx;
 }
 .reward-points {
-  color: #FFD700;
+  color: $accent-gold;
   background: rgba(255,215,0,0.12);
 }
 .reward-energy {
@@ -143,7 +143,7 @@ export default {
   height: 80rpx;
   line-height: 80rpx;
   font-size: 28rpx;
-  color: #fff;
+  color: $app-surface;
   background: linear-gradient(135deg, $brand-primary, $brand-primary-dark);
   border: none;
   border-radius: 40rpx;

+ 13 - 13
cfc-frontend/components/hand-signature/hand-signature.vue

@@ -259,7 +259,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .hand-signature {
   width: 100%;
 }
@@ -277,37 +277,37 @@ export default {
   margin-top: 2rpx;
   border: 2rpx solid #c8c9cc;
   border-radius: 6rpx;
-  background: #fff;
+  background: $app-surface;
   display: flex;
   align-items: center;
   justify-content: center;
 }
 .agree-box-on {
-  background: #667eea;
-  border-color: #667eea;
+  background: $accent-indigo;
+  border-color: $accent-indigo;
 }
 .agree-tick {
   font-size: 22rpx;
-  color: #fff;
+  color: $app-surface;
   line-height: 1;
 }
 .agree-text {
   flex: 1;
   font-size: 24rpx;
-  color: #666;
+  color: $facade-text-body;
   line-height: 1.5;
 }
 .signature-canvas-wrap {
   position: relative;
   width: 100%;
-  border: 2rpx dashed #ccc;
+  border: 2rpx dashed $facade-text-ghost;
   border-radius: 12rpx;
-  background: #fff;
+  background: $app-surface;
   overflow: hidden;
 }
 .signature-canvas {
   display: block;
-  background: #fff;
+  background: $app-surface;
 }
 .signature-preview {
   position: absolute;
@@ -328,8 +328,8 @@ export default {
   line-height: 72rpx;
   margin-right: 20rpx;
   font-size: 26rpx;
-  color: #666;
-  background: #f5f5f5;
+  color: $facade-text-body;
+  background: $facade-fill-2;
   border: none;
   border-radius: 36rpx;
 }
@@ -341,8 +341,8 @@ export default {
   height: 72rpx;
   line-height: 72rpx;
   font-size: 26rpx;
-  color: #fff;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: $app-surface;
+  background: linear-gradient(135deg, $accent-indigo 0%, $accent-indigo-deep 100%);
   border: none;
   border-radius: 36rpx;
 }

+ 22 - 22
cfc-frontend/components/login-sheet.vue

@@ -227,7 +227,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 /* ===== 浮层遮罩 ===== */
 .login-sheet-overlay {
   position: fixed;
@@ -274,7 +274,7 @@ export default {
 .login-sheet-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 /* ===== 滚动区域 ===== */
@@ -305,17 +305,17 @@ export default {
 .app-name {
   font-size: 44rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   margin-top: 16rpx;
 }
 .identity {
   font-size: 26rpx;
-  color: #f97316;
+  color: $brand-primary;
   margin-top: 8rpx;
 }
 .slogan {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-top: 12rpx;
   text-align: center;
 }
@@ -329,11 +329,11 @@ export default {
 }
 .concept-desc {
   font-size: 26rpx;
-  color: #555;
+  color: $facade-text-deep;
   line-height: 1.6;
 }
 .concept-highlight {
-  color: #f97316;
+  color: $brand-primary;
   font-weight: 600;
 }
 .loop-row {
@@ -355,17 +355,17 @@ export default {
   flex-shrink: 0;  /* 防止被压缩导致文本换行 */
   white-space: nowrap;
 }
-.loop-pill-indigo { background: rgba(99, 102, 241, 0.12); color: #6366f1; }
-.loop-pill-green { background: rgba(16, 185, 129, 0.12); color: #10b981; }
-.loop-pill-amber { background: rgba(245, 158, 11, 0.12); color: #f59e0b; }
-.loop-pill-red { background: rgba(239, 68, 68, 0.12); color: #ef4444; }
+.loop-pill-indigo { background: rgba(99, 102, 241, 0.12); color: $dim-wisdom; }
+.loop-pill-green { background: rgba(16, 185, 129, 0.12); color: $status-success; }
+.loop-pill-amber { background: rgba(245, 158, 11, 0.12); color: $dim-wealth; }
+.loop-pill-red { background: rgba(239, 68, 68, 0.12); color: $status-danger; }
 .loop-pill-text {
   margin-left: 4rpx;
   white-space: nowrap;  /* 禁止四字步骤文本换行 */
 }
 .loop-arrow {
   font-size: 24rpx;
-  color: #bbb;
+  color: $facade-text-ghost-light;
   padding: 0 2rpx;
   flex-shrink: 0;  /* 箭头不被压缩 */
 }
@@ -388,15 +388,15 @@ export default {
   border-radius: 999rpx;
   padding: 8rpx 14rpx;
   font-size: 22rpx;
-  color: #fff;
+  color: $app-surface;
   margin: 0 8rpx;  /* 使用 margin 替代 gap,微信小程序不支持 gap */
   white-space: nowrap;
 }
-.dim-tag-body { background: #10b981; }
-.dim-tag-wisdom { background: #6366f1; }
-.dim-tag-wealth { background: #f59e0b; }
+.dim-tag-body { background: $status-success; }
+.dim-tag-wisdom { background: $dim-wisdom; }
+.dim-tag-wealth { background: $dim-wealth; }
 .dim-tag-action { background: #14b8a6; }
-.dim-tag-mind { background: #ef4444; }
+.dim-tag-mind { background: $status-danger; }
 
 /* ===== 登录表单 ===== */
 .login-form {
@@ -406,8 +406,8 @@ export default {
   width: 100%;
   height: 96rpx;
   border-radius: 48rpx;
-  background: linear-gradient(135deg, #f97316, #fb923c);
-  color: #fff;
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
+  color: $app-surface;
   font-size: 32rpx;
   font-weight: 600;
   display: flex;
@@ -435,7 +435,7 @@ export default {
 }
 .skip-btn {
   font-size: 26rpx;
-  color: #999;
+  color: $facade-text-muted;
   text-decoration: underline;
 }
 
@@ -448,9 +448,9 @@ export default {
   flex-wrap: wrap;
   margin-top: 24rpx;
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .link {
-  color: #f97316;
+  color: $brand-primary;
 }
 </style>

+ 3 - 3
cfc-frontend/components/report-upload-card.vue

@@ -31,12 +31,12 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .report-upload-card {
   display: flex;
   flex-direction: row;
   align-items: center;
-  background: linear-gradient(135deg, #E8F5E9, #F1F8E9);
+  background: linear-gradient(135deg, $mat-green-tint, #F1F8E9);
   border-radius: 20rpx;
   padding: 28rpx 24rpx;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
@@ -61,7 +61,7 @@ export default {
 .report-upload-title {
   font-size: 30rpx;
   font-weight: bold;
-  color: #2E7D32;
+  color: $mat-green-strong;
   display: block;
   margin-bottom: 6rpx;
 }

+ 9 - 9
cfc-frontend/components/wuxing-sandbox.vue

@@ -651,8 +651,8 @@ export default {
   align-items: center;
   gap: 6rpx;
   font-size: 22rpx;
-  color: #fff;
-  background: linear-gradient(135deg, #667eea, #764ba2);
+  color: $app-surface;
+  background: linear-gradient(135deg, $accent-indigo, $accent-indigo-deep);
   padding: 10rpx 20rpx;
   border-radius: 28rpx;
   margin-left: auto;
@@ -719,11 +719,11 @@ export default {
   display: flex; align-items: center; justify-content: center;
   box-shadow: 0 0 24rpx rgba(255,255,255,0.25);
 }
-.point-dot--fire { background: linear-gradient(135deg, #FF6B35, #E65100); }
-.point-dot--water { background: linear-gradient(135deg, #42A5F5, #1565C0); }
+.point-dot--fire { background: linear-gradient(135deg, #FF6B35, $mat-orange-strong); }
+.point-dot--water { background: linear-gradient(135deg, #42A5F5, $mat-blue-strong); }
 .point-dot--earth { background: linear-gradient(135deg, #8D6E63, #5D4037); }
-.point-dot--metal { background: linear-gradient(135deg, #FFD700, #FF8F00); }
-.point-dot--wood { background: linear-gradient(135deg, #4CAF50, #2E7D32); }
+.point-dot--metal { background: linear-gradient(135deg, $accent-gold, #FF8F00); }
+.point-dot--wood { background: linear-gradient(135deg, $mat-green, $mat-green-strong); }
 
 .point-icon { font-size: 26rpx; line-height: 1; }
 
@@ -768,10 +768,10 @@ export default {
 .dim-item {
   font-size: 20rpx; padding: 4rpx 12rpx; border-radius: 10rpx; white-space: nowrap;
 }
-.dim-item--wood { color: #4CAF50; background: rgba(76,175,80,0.12); }
+.dim-item--wood { color: $mat-green; background: rgba(76,175,80,0.12); }
 .dim-item--fire { color: #FF6B35; background: rgba(255,107,53,0.12); }
 .dim-item--earth { color: #8D6E63; background: rgba(141,110,99,0.12); }
-.dim-item--metal { color: #FFD700; background: rgba(255,215,0,0.12); }
+.dim-item--metal { color: $accent-gold; background: rgba(255,215,0,0.12); }
 .dim-item--water { color: #42A5F5; background: rgba(66,165,245,0.12); }
 
 /* ============================================================
@@ -803,7 +803,7 @@ export default {
   border-left: none;
 }
 .toggle-item--active {
-  color: #fff;
+  color: $app-surface;
   background: linear-gradient(135deg, $brand-primary, $brand-primary-dark);
   border-color: $brand-primary;
 }

+ 8 - 8
cfc-frontend/custom-tab-bar/index.vue

@@ -163,7 +163,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .tab-bar-wrap {
   position: fixed;
   bottom: 0;
@@ -175,9 +175,9 @@ export default {
 .tab-bar {
   display: flex;
   height: 100rpx;
-  background: #fff;
+  background: $app-surface;
   padding-bottom: env(safe-area-inset-bottom);
-  border-top: 1rpx solid #eee;
+  border-top: 1rpx solid $facade-hairline-2;
   position: relative;
 }
 
@@ -200,11 +200,11 @@ export default {
 
 .tab-text {
   font-size: 22rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .tab-item.active .tab-text {
-  color: #F97316;
+  color: $brand-primary;
 }
 
 /* 中间五维按钮(占 20% 槽位) */
@@ -224,7 +224,7 @@ export default {
   width: 88rpx;
   height: 88rpx;
   border-radius: 50%;
-  background: linear-gradient(135deg, #F97316, #FB923C);
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
   display: flex;
   align-items: center;
   justify-content: center;
@@ -239,7 +239,7 @@ export default {
 }
 .center-btn-label {
   font-size: 20rpx;
-  color: #999;
+  color: $facade-text-muted;
   text-align: center;
   margin-top: 2rpx;
 }
@@ -298,7 +298,7 @@ export default {
 }
 .dim-fan-label {
   font-size: 22rpx;
-  color: #fff;
+  color: $app-surface;
   font-weight: 600;
   margin-top: 8rpx;
   text-align: center;

+ 17 - 17
cfc-frontend/pages/achievement/index.vue

@@ -106,17 +106,17 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   min-height: 100vh;
-  background: #f5f7fa;
+  background: $app-bg;
 }
 .nav-placeholder {
   width: 60rpx;
 }
 .stats-card {
   display: flex;
-  background: #fff;
+  background: $app-surface;
   padding: 40rpx 0;
   margin-bottom: 20rpx;
 }
@@ -128,16 +128,16 @@ export default {
   display: block;
   font-size: 48rpx;
   font-weight: 700;
-  color: #F97316;
+  color: $brand-primary;
 }
 .stat-label {
   display: block;
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-top: 8rpx;
 }
 .badge-section, .goals-section {
-  background: #fff;
+  background: $app-surface;
   margin: 0 30rpx 20rpx;
   border-radius: 20rpx;
   padding: 30rpx;
@@ -145,7 +145,7 @@ export default {
 .section-title {
   font-size: 30rpx;
   font-weight: 600;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 24rpx;
 }
 .badge-grid {
@@ -167,10 +167,10 @@ export default {
   margin: 0 auto 12rpx;
 }
 .badge-icon-wrap.unlocked {
-  background: linear-gradient(135deg, #FFD700, #FFA500);
+  background: linear-gradient(135deg, $accent-gold, #FFA500);
 }
 .badge-icon-wrap.locked {
-  background: #e8e8e8;
+  background: $facade-fill-4;
 }
 .badge-icon {
   font-size: 40rpx;
@@ -178,13 +178,13 @@ export default {
 .badge-name {
   display: block;
   font-size: 22rpx;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 4rpx;
 }
 .badge-desc {
   display: block;
   font-size: 18rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .empty-state {
   text-align: center;
@@ -198,13 +198,13 @@ export default {
 .empty-text {
   display: block;
   font-size: 28rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-bottom: 12rpx;
 }
 .empty-hint {
   display: block;
   font-size: 24rpx;
-  color: #ccc;
+  color: $facade-text-ghost;
 }
 .goal-item {
   margin-bottom: 24rpx;
@@ -219,21 +219,21 @@ export default {
 }
 .goal-name {
   font-size: 26rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 .goal-progress {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .progress-bar {
   height: 12rpx;
-  background: #f0f0f0;
+  background: $facade-fill;
   border-radius: 6rpx;
   overflow: hidden;
 }
 .progress-fill {
   height: 100%;
-  background: linear-gradient(90deg, #F97316, #FB923C);
+  background: linear-gradient(90deg, $brand-primary, $brand-primary-light);
   border-radius: 6rpx;
   transition: width 0.3s;
 }

+ 13 - 12
cfc-frontend/pages/action-detail/components/PearlDiagram.vue

@@ -127,8 +127,8 @@ export default {
       // 颜色盘:内圈偏暖(红橙),外圈偏冷(蓝紫绿)
       groupConfigs: [
         // ===== 内圈:必备 12 组(radiusPct 0.28)=====
-        { type: 'MEDICAL', typeName: '医疗', color: '#EF4444', radiusPct: 0.28, ring: 'inner' },
-        { type: 'EDUCATION', typeName: '教育', color: '#F97316', radiusPct: 0.28, ring: 'inner' },
+        { type: 'MEDICAL', typeName: '医疗', color: 'BrandColors.danger', radiusPct: 0.28, ring: 'inner' },
+        { type: 'EDUCATION', typeName: '教育', color: 'BrandColors.primary', radiusPct: 0.28, ring: 'inner' },
         { type: 'PUBLIC_SECURITY', typeName: '公安', color: '#DC2626', radiusPct: 0.28, ring: 'inner' },
         { type: 'AUTO', typeName: '汽车相关', color: '#EA580C', radiusPct: 0.28, ring: 'inner' },
         { type: 'REAL_ESTATE', typeName: '房产服务', color: '#D97706', radiusPct: 0.28, ring: 'inner' },
@@ -148,7 +148,7 @@ export default {
         { type: 'TRAVEL', typeName: '旅游', color: '#0EA5E9', radiusPct: 0.55, ring: 'outer' },
         { type: 'LEADING_ENTERPRISE', typeName: '领军企业', color: '#0891B2', radiusPct: 0.55, ring: 'outer' },
         { type: 'INDUSTRY_BENCHMARK', typeName: '行业标杆', color: '#059669', radiusPct: 0.55, ring: 'outer' },
-        { type: 'OVERSEAS', typeName: '国外', color: '#10B981', radiusPct: 0.55, ring: 'outer' },
+        { type: 'OVERSEAS', typeName: '国外', color: 'BrandColors.success', radiusPct: 0.55, ring: 'outer' },
         { type: 'UNIVERSITY', typeName: '高校', color: '#16A34A', radiusPct: 0.55, ring: 'outer' },
         { type: 'FINANCE', typeName: '金融', color: '#84CC16', radiusPct: 0.55, ring: 'outer' },
         { type: 'SENIOR_LOCAL', typeName: '资深土著', color: '#65A30D', radiusPct: 0.55, ring: 'outer' },
@@ -464,21 +464,21 @@ export default {
       // 外圈光晕
       ctx.beginPath()
       ctx.arc(cx, cy, r + 4, 0, Math.PI * 2)
-      ctx.fillStyle = this._hexToRgba('#10B981', 0.15)
+      ctx.fillStyle = this._hexToRgba('BrandColors.success', 0.15)
       ctx.fill()
 
       // 实心圆
       ctx.beginPath()
       ctx.arc(cx, cy, r, 0, Math.PI * 2)
-      ctx.fillStyle = '#10B981'
+      ctx.fillStyle = 'BrandColors.success'
       ctx.fill()
-      ctx.strokeStyle = '#FFFFFF'
+      ctx.strokeStyle = 'BrandColors.white'
       ctx.lineWidth = 3
       ctx.stroke()
 
       // "我" 字
       ctx.font = 'bold 18px PingFang SC, sans-serif'
-      ctx.fillStyle = '#FFFFFF'
+      ctx.fillStyle = 'BrandColors.white'
       ctx.textAlign = 'center'
       ctx.textBaseline = 'middle'
       ctx.fillText('我', cx, cy + 1)
@@ -508,7 +508,7 @@ export default {
       ctx.fill()
 
       // 白边
-      ctx.strokeStyle = '#FFFFFF'
+      ctx.strokeStyle = 'BrandColors.white'
       ctx.lineWidth = 2
       ctx.stroke()
 
@@ -517,13 +517,13 @@ export default {
         ctx.beginPath()
         ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2)
         ctx.lineWidth = 2
-        ctx.strokeStyle = '#FFFFFF'
+        ctx.strokeStyle = 'BrandColors.white'
         ctx.stroke()
       }
 
       // 首字
       ctx.font = 'bold ' + Math.max(12, r * 0.65) + 'px PingFang SC, sans-serif'
-      ctx.fillStyle = '#FFFFFF'
+      ctx.fillStyle = 'BrandColors.white'
       ctx.textAlign = 'center'
       ctx.textBaseline = 'middle'
       ctx.fillText(node.displayName, x, y + 1)
@@ -583,9 +583,9 @@ export default {
         // 红底白字小徽标
         ctx.beginPath()
         ctx.arc(bx, by, 9, 0, Math.PI * 2)
-        ctx.fillStyle = '#EF4444'
+        ctx.fillStyle = 'BrandColors.danger'
         ctx.fill()
-        ctx.fillStyle = '#FFFFFF'
+        ctx.fillStyle = 'BrandColors.white'
         ctx.font = 'bold 10px sans-serif'
         ctx.textAlign = 'center'
         ctx.textBaseline = 'middle'
@@ -851,6 +851,7 @@ export default {
   }
 }
 </script>
+import { danger, primary, success, white } from '@/utils/brand-colors.js'
 
 <style scoped lang="scss">
 .pearl-graph-wrapper {

+ 73 - 73
cfc-frontend/pages/activity/activity-detail/activity-detail.vue

@@ -23,8 +23,8 @@
         class="cover"
         :src="getImageUrl(activity.coverImage)"
         mode="widthFix"
-      />
-      <image v-else class="cover placeholder-cover" src="/static/default-activity.png" mode="aspectFill" />
+       lazy-load />
+      <image v-else class="cover placeholder-cover" src="/static/default-activity.png" mode="aspectFill"  lazy-load />
 
       <view class="info-section">
         <text class="activity-title">{{ activity.title }}</text>
@@ -918,10 +918,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   min-height: 100vh;
-  background: #FFF7ED;
+  background: $uni-primary-pale;
 }
 
 /* 加载 / 错误 / 空状态 */
@@ -938,7 +938,7 @@ export default {
   width: 60rpx;
   height: 60rpx;
   border: 4rpx solid #FDE68A;
-  border-top-color: #F97316;
+  border-top-color: $brand-primary;
   border-radius: 50%;
   animation: spin 0.8s linear infinite;
   margin-bottom: 20rpx;
@@ -950,7 +950,7 @@ export default {
 .error-text,
 .empty-text {
   font-size: 28rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-bottom: 20rpx;
 }
 .error-icon {
@@ -958,8 +958,8 @@ export default {
   margin-bottom: 16rpx;
 }
 .retry-btn {
-  background: #F97316;
-  color: #fff;
+  background: $brand-primary;
+  color: $app-surface;
   border: none;
   border-radius: 40rpx;
   padding: 12rpx 40rpx;
@@ -978,7 +978,7 @@ export default {
 }
 .placeholder-cover {
   height: 400rpx;
-  background: #f0f0f0;
+  background: $facade-fill;
 }
 
 /* 信息区 */
@@ -988,7 +988,7 @@ export default {
 .activity-title {
   font-size: calc(36rpx * var(--font-scale, 1));
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   line-height: 1.4;
 }
 .status-row {
@@ -1000,15 +1000,15 @@ export default {
   padding: 4rpx 16rpx;
   border-radius: 8rpx;
 }
-.status-draft { background: #F5F5F5; color: #999; }
-.status-published { background: #F0FDF4; color: #22C55E; }
-.status-ended { background: #F5F5F5; color: #999; }
-.status-upcoming { background: #FFF7ED; color: #F97316; }
+.status-draft { background: $facade-fill-2; color: $facade-text-muted; }
+.status-published { background: $status-success-tint-2; color: $status-success-2; }
+.status-ended { background: $facade-fill-2; color: $facade-text-muted; }
+.status-upcoming { background: $uni-primary-pale; color: $brand-primary; }
 
 /* 元信息列表 */
 .meta-list {
   margin-top: 24rpx;
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
   padding: 24rpx;
 }
@@ -1037,7 +1037,7 @@ export default {
   display: block;
 }
 .member-fee-link {
-  color: #F97316;
+  color: $brand-primary;
   font-weight: 500;
   text-decoration: underline;
   margin-top: 4rpx;
@@ -1046,14 +1046,14 @@ export default {
 /* 活动介绍 */
 .desc-section {
   margin-top: 30rpx;
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
   padding: 24rpx;
 }
 .section-title {
   font-size: calc(28rpx * var(--font-scale, 1));
   font-weight: 600;
-  color: #1f2937;
+  color: $facade-text-gray-800;
   display: block;
   margin-bottom: 12rpx;
 }
@@ -1084,7 +1084,7 @@ export default {
 }
 .refund-notice-text {
   font-size: calc(24rpx * var(--font-scale, 1));
-  color: #DC2626;
+  color: $status-danger-deep;
   line-height: 1.4;
 }
 
@@ -1094,7 +1094,7 @@ export default {
   left: 0;
   right: 0;
   bottom: 0;
-  background: #fff;
+  background: $app-surface;
   padding: 20rpx 30rpx;
   padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
   display: flex;
@@ -1109,11 +1109,11 @@ export default {
 }
 .bottom-type {
   font-size: 22rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .bottom-vendor {
   font-size: 22rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-top: 4rpx;
 }
 .action-btns {
@@ -1122,8 +1122,8 @@ export default {
   gap: 16rpx;
 }
 .btn-checkin {
-  background: linear-gradient(135deg, #F97316, #FB923C);
-  color: #fff;
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
+  color: $app-surface;
   border: none;
   border-radius: 40rpx;
   padding: 16rpx 48rpx;
@@ -1137,7 +1137,7 @@ export default {
   opacity: 0.85;
 }
 .btn-checkin-done {
-  background: #22C55E;
+  background: $status-success-2;
 }
 .btn-checkin-loading {
   opacity: 0.7;
@@ -1145,8 +1145,8 @@ export default {
 
 /* 报名按钮 */
 .btn-register {
-  background: linear-gradient(135deg, #10B981, #34D399);
-  color: #fff;
+  background: linear-gradient(135deg, $status-success, $status-success-light-3);
+  color: $app-surface;
   border: none;
   border-radius: 40rpx;
   padding: 16rpx 32rpx;
@@ -1159,8 +1159,8 @@ export default {
 .btn-register:active { opacity: 0.85; }
 .btn-register-loading { opacity: 0.7; }
 .btn-register-done {
-  background: #F5F5F5;
-  color: #999;
+  background: $facade-fill-2;
+  color: $facade-text-muted;
   border: none;
   border-radius: 40rpx;
   padding: 16rpx 32rpx;
@@ -1170,9 +1170,9 @@ export default {
   text-align: center;
 }
 .btn-cancel {
-  background: #fff;
-  color: #999;
-  border: 2rpx solid #ddd;
+  background: $app-surface;
+  color: $facade-text-muted;
+  border: 2rpx solid $facade-border-soft;
   border-radius: 40rpx;
   padding: 14rpx 28rpx;
   font-size: 24rpx;
@@ -1196,7 +1196,7 @@ export default {
   z-index: 999;
 }
 .penalty-content {
-  background: #fff;
+  background: $app-surface;
   border-radius: 24rpx;
   padding: 48rpx 40rpx;
   width: 560rpx;
@@ -1205,20 +1205,20 @@ export default {
 .penalty-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #EF4444;
+  color: $status-danger;
   display: block;
   margin-bottom: 20rpx;
 }
 .penalty-desc {
   font-size: 26rpx;
-  color: #666;
+  color: $facade-text-body;
   display: block;
   margin-bottom: 40rpx;
   line-height: 1.6;
 }
 .penalty-btn {
-  background: #F97316;
-  color: #fff;
+  background: $brand-primary;
+  color: $app-surface;
   border: none;
   border-radius: 40rpx;
   padding: 16rpx 60rpx;
@@ -1229,23 +1229,23 @@ export default {
 .feedback-section {
   padding: 30rpx;
   margin: 20rpx;
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
 }
 .feedback-header { margin-bottom: 20rpx; }
-.feedback-title { font-size: calc(32rpx * var(--font-scale, 1)); font-weight: bold; color: #333; }
+.feedback-title { font-size: calc(32rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; }
 .feedback-btn {
-  background: #F97316; color: #fff; border: none;
+  background: $brand-primary; color: $app-surface; border: none;
   border-radius: 40rpx; padding: 20rpx 40rpx; font-size: 28rpx;
   text-align: center;
 }
 .feedback-submitted { padding: 10rpx 0; }
 .feedback-ratings-display { margin-bottom: 16rpx; }
 .fr-row { display: flex; justify-content: space-between; padding: 8rpx 0; }
-.fr-label { font-size: 28rpx; color: #333; }
-.fr-stars { font-size: 28rpx; color: #F97316; }
-.feedback-comment-text { font-size: calc(26rpx * var(--font-scale, 1)); color: #666; margin: 10rpx 0; line-height: 1.5; }
-.feedback-thanks { font-size: 24rpx; color: #999; text-align: center; margin-top: 16rpx; }
+.fr-label { font-size: 28rpx; color: $facade-text-strong; }
+.fr-stars { font-size: 28rpx; color: $brand-primary; }
+.feedback-comment-text { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-body; margin: 10rpx 0; line-height: 1.5; }
+.feedback-thanks { font-size: 24rpx; color: $facade-text-muted; text-align: center; margin-top: 16rpx; }
 .feedback-modal-overlay {
   position: fixed;
   top: 0;
@@ -1259,40 +1259,40 @@ export default {
   z-index: 999;
 }
 .feedback-modal {
-  background: #fff; border-radius: 20rpx; padding: 40rpx;
+  background: $app-surface; border-radius: 20rpx; padding: 40rpx;
   width: 80%; max-width: 600rpx;
 }
 .feedback-modal-title {
-  font-size: 34rpx; font-weight: bold; color: #333;
+  font-size: 34rpx; font-weight: bold; color: $facade-text-strong;
   text-align: center; margin-bottom: 30rpx;
 }
 .rating-row {
   display: flex; justify-content: space-between; align-items: center;
   padding: 16rpx 0;
 }
-.rating-label { font-size: 28rpx; color: #333; min-width: 120rpx; }
+.rating-label { font-size: 28rpx; color: $facade-text-strong; min-width: 120rpx; }
 .stars { display: flex; }
-.star-icon { font-size: 40rpx; color: #ddd; padding: 0 6rpx; }
-.star-active { color: #F97316; }
+.star-icon { font-size: 40rpx; color: $facade-border-soft; padding: 0 6rpx; }
+.star-active { color: $brand-primary; }
 .feedback-textarea {
   width: 100%; height: 160rpx; margin-top: 20rpx;
-  border: 1rpx solid #eee; border-radius: 12rpx;
+  border: 1rpx solid $facade-hairline-2; border-radius: 12rpx;
   padding: 16rpx; font-size: 26rpx; box-sizing: border-box;
 }
 .feedback-modal-actions {
   display: flex; justify-content: space-between; margin-top: 30rpx;
 }
 .feedback-cancel-btn {
-  flex: 1; background: #f5f5f5; color: #666; border: none;
+  flex: 1; background: $facade-fill-2; color: $facade-text-body; border: none;
   border-radius: 40rpx; padding: 20rpx; font-size: 28rpx;
   text-align: center; margin-right: 20rpx;
 }
 .feedback-submit-btn {
-  flex: 1; background: #F97316; color: #fff; border: none;
+  flex: 1; background: $brand-primary; color: $app-surface; border: none;
   border-radius: 40rpx; padding: 20rpx; font-size: 28rpx;
   text-align: center;
 }
-.share-btn { height: 64rpx; line-height: 64rpx; background: #fff; border: 2rpx solid #F97316; color: #F97316; font-size: 24rpx; border-radius: 32rpx; padding: 0 24rpx; display: flex; align-items: center; flex-shrink: 0; }
+.share-btn { height: 64rpx; line-height: 64rpx; background: $app-surface; border: 2rpx solid $brand-primary; color: $brand-primary; font-size: 24rpx; border-radius: 32rpx; padding: 0 24rpx; display: flex; align-items: center; flex-shrink: 0; }
 .share-btn::after { border: none; }
 .share-btn-icon { font-size: 28rpx; margin-right: 6rpx; }
 .share-btn-text { font-size: 24rpx; font-weight: 600; }
@@ -1311,7 +1311,7 @@ export default {
   z-index: 999;
 }
 .reg-modal {
-  background: #fff;
+  background: $app-surface;
   border-radius: 24rpx;
   padding: 40rpx;
   width: 85%;
@@ -1321,7 +1321,7 @@ export default {
 .reg-modal-title {
   font-size: 34rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   text-align: center;
   display: block;
   margin-bottom: 30rpx;
@@ -1329,7 +1329,7 @@ export default {
 .reg-section-label {
   font-size: 28rpx;
   font-weight: 600;
-  color: #555;
+  color: $facade-text-deep;
   display: block;
   margin-bottom: 16rpx;
 }
@@ -1351,8 +1351,8 @@ export default {
   margin-bottom: 10rpx;
 }
 .reg-family-item-selected {
-  background: #FFF7ED;
-  border: 2rpx solid #F97316;
+  background: $uni-primary-pale;
+  border: 2rpx solid $brand-primary;
 }
 .reg-family-info {
   display: flex;
@@ -1361,18 +1361,18 @@ export default {
 }
 .reg-family-name {
   font-size: 28rpx;
-  color: #333;
+  color: $facade-text-strong;
   margin-right: 12rpx;
 }
 .reg-family-relation {
   font-size: 22rpx;
-  color: #999;
-  background: #eee;
+  color: $facade-text-muted;
+  background: $facade-hairline-2;
   padding: 2rpx 12rpx;
   border-radius: 8rpx;
 }
 .reg-checkmark {
-  color: #F97316;
+  color: $brand-primary;
   font-size: 32rpx;
   font-weight: bold;
 }
@@ -1388,12 +1388,12 @@ export default {
 }
 .reg-input {
   flex: 1;
-  background: #f5f5f5;
+  background: $facade-fill-2;
   border: none;
   border-radius: 12rpx;
   padding: 16rpx 20rpx;
   font-size: 26rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 .reg-input-name {
   flex: 2;
@@ -1402,18 +1402,18 @@ export default {
   flex: 3;
 }
 .reg-remove {
-  color: #EF4444;
+  color: $status-danger;
   font-size: 28rpx;
   padding: 10rpx;
   flex-shrink: 0;
 }
 .reg-add-btn {
   background: none;
-  border: 2rpx dashed #ddd;
+  border: 2rpx dashed $facade-border-soft;
   border-radius: 12rpx;
   padding: 16rpx;
   font-size: 26rpx;
-  color: #F97316;
+  color: $brand-primary;
   text-align: center;
   width: 100%;
   margin-top: 6rpx;
@@ -1422,10 +1422,10 @@ export default {
   text-align: center;
   font-size: 30rpx;
   font-weight: bold;
-  color: #F97316;
+  color: $brand-primary;
   margin-bottom: 24rpx;
   padding: 16rpx;
-  background: #FFF7ED;
+  background: $uni-primary-pale;
   border-radius: 12rpx;
 }
 .reg-actions {
@@ -1436,8 +1436,8 @@ export default {
 }
 .reg-cancel {
   flex: 1;
-  background: #f5f5f5;
-  color: #666;
+  background: $facade-fill-2;
+  color: $facade-text-body;
   border: none;
   border-radius: 40rpx;
   padding: 20rpx;
@@ -1446,8 +1446,8 @@ export default {
 }
 .reg-submit {
   flex: 1;
-  background: linear-gradient(135deg, #10B981, #34D399);
-  color: #fff;
+  background: linear-gradient(135deg, $status-success, $status-success-light-3);
+  color: $app-surface;
   border: none;
   border-radius: 40rpx;
   padding: 20rpx;

+ 215 - 63
cfc-frontend/pages/activity/create.vue

@@ -1,22 +1,12 @@
 <template>
   <view class="container">
     <view class="form-card">
-      <view class="form-title">{{ isNew ? '创建活动' : '编辑活动' }}</view>
-
       <!-- 活动标题 -->
       <view class="form-item">
         <text class="form-label">活动标题 *</text>
         <input class="form-input" v-model="form.title" placeholder="请输入活动标题" maxlength="50" />
       </view>
 
-      <!-- 活动维度 -->
-      <view class="form-item">
-        <text class="form-label">所属维度 *</text>
-        <picker class="form-picker" :value="dimIdx" :range="dimensionOptions" range-key="label" @change="onDimChange">
-          <text class="picker-text">{{ form.dimensionCode ? dimLabel(form.dimensionCode) : '请选择维度' }}</text>
-        </picker>
-      </view>
-
       <!-- 活动类型 -->
       <view class="form-item">
         <text class="form-label">活动类型 *</text>
@@ -92,12 +82,25 @@
         <input class="form-input" type="digit" v-model="parentMemberFeeYuan" placeholder="0=免费" />
       </view>
 
+      <!-- 主办方 -->
+      <view class="form-item">
+        <text class="form-label">主办方</text>
+        <input class="form-input" v-model="form.vendorName" placeholder="请输入主办方名称(可选)" maxlength="50" />
+      </view>
+
       <!-- 封面图片 -->
       <view class="form-item">
         <text class="form-label">封面图片</text>
-        <input class="form-input" v-model="form.coverImage" placeholder="输入图片URL(可选)" />
-        <view class="cover-preview" v-if="form.coverImage">
-          <image class="preview-img" :src="form.coverImage" mode="aspectFill" @error="onImgError" />
+        <view class="cover-upload" @click="chooseImage">
+          <image v-if="form.coverImage" class="preview-img" :src="getCoverUrl(form.coverImage)" mode="aspectFill" @click.stop  lazy-load />
+          <view v-else class="upload-placeholder">
+            <text class="upload-icon">📷</text>
+            <text class="upload-text">点击上传封面图</text>
+          </view>
+        </view>
+        <view class="cover-actions" v-if="form.coverImage">
+          <text class="cover-action" @click="chooseImage">重新选择</text>
+          <text class="cover-action-danger" @click="removeCover">移除</text>
         </view>
       </view>
 
@@ -119,6 +122,46 @@
       </view>
     </view>
 
+    <!-- 海报模板选择 + 预览 -->
+    <view class="form-card" v-if="posterTemplates.length > 0">
+      <view class="form-title">活动海报模板(可选)</view>
+      <view class="poster-tpl-list">
+        <view
+          v-for="tpl in posterTemplates"
+          :key="tpl.id"
+          class="poster-tpl-card"
+          :class="{ 'poster-tpl-active': form.posterTemplateId === tpl.id }"
+          @click="selectPosterTemplate(tpl)"
+        >
+          <view class="tpl-colors">
+            <view class="tpl-dot tpl-main" :style="{ background: tpl.mainColor || '#F97316' }"></view>
+            <view class="tpl-dot tpl-dark" :style="{ background: tpl.darkColor || '#EA580C' }"></view>
+            <view class="tpl-dot tpl-light" :style="{ background: tpl.lightColor || '#FFF7ED' }"></view>
+          </view>
+          <text class="tpl-name">{{ tpl.name }}</text>
+        </view>
+      </view>
+      <view class="poster-preview-row" v-if="form.posterTemplateId">
+        <text class="poster-preview-hint">已选择海报模板,点击「预览」查看效果</text>
+        <view class="btn-preview" @click="openPosterPreview">预览</view>
+      </view>
+    </view>
+
+    <!-- 海报预览弹层 -->
+    <content-share-poster
+      v-if="showPosterPreview"
+      :show="showPosterPreview"
+      :title="form.title || '活动海报'"
+      :cover-image="form.coverImage"
+      :dimension-code="form.dimensionCode"
+      :template="previewTemplateKey"
+      :templates="posterTemplates"
+      :poster-qr-code="''"
+      @close="showPosterPreview = false"
+      @update:template="previewTemplateKey = $event"
+      @update:bgColor="previewBgColor = $event"
+    ></content-share-poster>
+
     <view class="loading" v-if="loading">
       <view class="loading-spinner"></view>
       <text>加载中...</text>
@@ -127,15 +170,17 @@
 </template>
 
 <script>
-import { adminCreateActivity, adminUpdateActivity, adminPublishActivity, submitActivityForReview, getActivityDetail } from '../../utils/api.js'
+import { adminCreateActivity, adminUpdateActivity, adminPublishActivity, submitActivityForReview, getActivityDetail, getPosterTemplates } from '../../utils/api.js'
 import config from '../../config.js'
 import { parseDate } from '../../utils/format.js'
 import { hasActivityManageRole } from '../../utils/activity-role.js'
 import { pickImage } from '../../utils/image-picker.js'
+import ContentSharePoster from '../../components/ContentSharePoster.vue'
 
 var BASE_URL = config.api('')
 
 export default {
+  components: { ContentSharePoster },
   computed: {
     // 后台活动管理员可直接发布;服务商账号只能保存后提交审核
     isAdmin: function() {
@@ -180,20 +225,18 @@ export default {
       endTimePart: '',
       regDatePart: '',
       regTimePart: '',
-      dimIdx: 0,
       typeIdx: 0,
-      dimensionOptions: [
-        { key: 'body', label: '身 · 主动健康' },
-        { key: 'mind', label: '心 · 情感丰盈' },
-        { key: 'wisdom', label: '智 · 赋能未来' },
-        { key: 'action', label: '行 · 知行合一' },
-        { key: 'wealth', label: '富 · 物质精神' }
-      ],
       typeOptions: [
         { key: 'offline', label: '线下活动' },
         { key: 'online', label: '线上活动' },
         { key: 'campaign', label: '营销活动' }
-      ]
+      ],
+      // 海报模板
+      posterTemplates: [],
+      selectedPosterTemplate: null,
+      showPosterPreview: false,
+      previewTemplateKey: 'gradient',
+      previewBgColor: ''
     }
   },
   onLoad(options) {
@@ -205,6 +248,8 @@ export default {
     } else {
       uni.setNavigationBarTitle({ title: '创建活动' })
     }
+    // 加载海报模板列表(未登录也会返回空列表,不阻塞创建)
+    this.loadPosterTemplates()
   },
   methods: {
     loadDetail() {
@@ -217,6 +262,8 @@ export default {
           self.form.title = d.title || ''
           self.form.description = d.description || ''
           self.form.coverImage = d.coverImage || ''
+          self.form.vendorName = d.vendorName || ''
+          self.form.posterTemplateId = d.posterTemplateId || null
           self.form.dimensionCode = d.dimensionCode || ''
           self.form.activityType = d.activityType || ''
           self.form.location = d.location || ''
@@ -256,20 +303,13 @@ export default {
               self.form.registrationDeadline = rd.toISOString()
             }
           }
-          // sync pickers
-          for (var i = 0; i < self.dimensionOptions.length; i++) {
-            if (self.dimensionOptions[i].key === self.form.dimensionCode) self.dimIdx = i
-          }
+          // sync type picker
           for (var j = 0; j < self.typeOptions.length; j++) {
             if (self.typeOptions[j].key === self.form.activityType) self.typeIdx = j
           }
         }
       }).catch(function() { self.loading = false })
     },
-    onDimChange(e) {
-      this.dimIdx = e.detail.value
-      this.form.dimensionCode = this.dimensionOptions[this.dimIdx].key
-    },
     onTypeChange(e) {
       this.typeIdx = e.detail.value
       this.form.activityType = this.typeOptions[this.typeIdx].key
@@ -354,15 +394,44 @@ export default {
     onImgError() {
       this.form.coverImage = ''
     },
+    getCoverUrl(path) {
+      if (!path) return ''
+      var s = String(path).trim()
+      if (!s || s === 'null' || s === '/null') return ''
+      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+      return BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+    },
+    removeCover() {
+      this.form.coverImage = ''
+    },
+    async loadPosterTemplates() {
+      try {
+        var res = await getPosterTemplates()
+        if (res && res.code === 200) {
+          this.posterTemplates = res.data || []
+        }
+      } catch (e) { /* 模板拉取失败不影响创建 */ }
+    },
+    selectPosterTemplate(tpl) {
+      this.form.posterTemplateId = tpl.id
+      this.selectedPosterTemplate = tpl
+      this.previewTemplateKey = tpl.templateKey || 'gradient'
+      // 默认背景色取模板主色
+      this.previewBgColor = tpl.mainColor || ''
+      uni.showToast({ title: '已选择:' + tpl.name, icon: 'none' })
+    },
+    openPosterPreview() {
+      if (!this.form.posterTemplateId) {
+        uni.showToast({ title: '请先选择海报模板', icon: 'none' })
+        return
+      }
+      this.showPosterPreview = true
+    },
     validate() {
       if (!this.form.title) {
         uni.showToast({ title: '请输入活动标题', icon: 'none' })
         return false
       }
-      if (!this.form.dimensionCode) {
-        uni.showToast({ title: '请选择所属维度', icon: 'none' })
-        return false
-      }
       if (!this.form.activityType) {
         uni.showToast({ title: '请选择活动类型', icon: 'none' })
         return false
@@ -381,6 +450,11 @@ export default {
       payload.parentFee = Math.round((parseFloat(this.parentFeeYuan) || 0) * 100)
       payload.parentMemberFee = Math.round((parseFloat(this.parentMemberFeeYuan) || 0) * 100)
       if (this.activityId) payload.id = this.activityId
+      // 维度由审核时设置,创建/编辑不提交空维度,避免误清空已有维度
+      if (!payload.dimensionCode) delete payload.dimensionCode
+      // 主办方和海报模板仅非空时提交
+      if (!payload.vendorName) delete payload.vendorName
+      if (!payload.posterTemplateId) delete payload.posterTemplateId
       return payload
     },
     handleSave() {
@@ -470,12 +544,6 @@ export default {
         })
       }
     },
-    dimLabel(key) {
-      for (var i = 0; i < this.dimensionOptions.length; i++) {
-        if (this.dimensionOptions[i].key === key) return this.dimensionOptions[i].label
-      }
-      return '请选择维度'
-    },
     typeLabel(key) {
       for (var i = 0; i < this.typeOptions.length; i++) {
         if (this.typeOptions[i].key === key) return this.typeOptions[i].label
@@ -497,15 +565,15 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   min-height: 100vh;
-  background: #F5F7FA;
+  background: $app-bg;
   padding: 24rpx;
 }
 
 .form-card {
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 32rpx 28rpx;
   box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
@@ -514,7 +582,7 @@ export default {
 .form-title {
   font-size: 36rpx;
   font-weight: bold;
-  color: #1E293B;
+  color: $uni-text-color;
   margin-bottom: 24rpx;
 }
 
@@ -535,7 +603,7 @@ export default {
 
 .form-label {
   font-size: 26rpx;
-  color: #475569;
+  color: $facade-text-slate-deep;
   font-weight: 600;
   margin-bottom: 12rpx;
   display: block;
@@ -544,22 +612,22 @@ export default {
 .form-input {
   width: 100%;
   height: 80rpx;
-  border: 1rpx solid #E2E8F0;
+  border: 1rpx solid $uni-border-color-light;
   border-radius: 12rpx;
   padding: 0 20rpx;
   font-size: 28rpx;
-  color: #1E293B;
+  color: $uni-text-color;
   box-sizing: border-box;
 }
 
 .form-picker {
   width: 100%;
   height: 80rpx;
-  border: 1rpx solid #E2E8F0;
+  border: 1rpx solid $uni-border-color-light;
   border-radius: 12rpx;
   padding: 0 20rpx;
   font-size: 28rpx;
-  color: #1E293B;
+  color: $uni-text-color;
   box-sizing: border-box;
   display: flex;
   align-items: center;
@@ -567,24 +635,24 @@ export default {
 
 .picker-text {
   font-size: 28rpx;
-  color: #1E293B;
+  color: $uni-text-color;
 }
 
 .form-textarea {
   width: 100%;
   min-height: 160rpx;
-  border: 1rpx solid #E2E8F0;
+  border: 1rpx solid $uni-border-color-light;
   border-radius: 12rpx;
   padding: 16rpx 20rpx;
   font-size: 28rpx;
-  color: #1E293B;
+  color: $uni-text-color;
   box-sizing: border-box;
 }
 
 .cover-upload {
   width: 100%;
   height: 320rpx;
-  border: 2rpx dashed #CBD5E1;
+  border: 2rpx dashed $uni-border-color;
   border-radius: 12rpx;
   display: flex;
   align-items: center;
@@ -605,15 +673,99 @@ export default {
 
 .upload-icon {
   font-size: 64rpx;
-  color: #94A3B8;
+  color: $app-text-subtle;
 }
 
 .upload-text {
   font-size: 24rpx;
-  color: #94A3B8;
+  color: $app-text-subtle;
   margin-top: 8rpx;
 }
 
+.cover-actions {
+  display: flex;
+  flex-direction: row;
+  gap: 24rpx;
+  margin-top: 12rpx;
+}
+
+.cover-action {
+  font-size: 24rpx;
+  color: $facade-text-slate-deep;
+  text-decoration: underline;
+}
+
+.cover-action-danger {
+  font-size: 24rpx;
+  color: $status-danger;
+  text-decoration: underline;
+}
+
+/* 海报模板选择 */
+.poster-tpl-list {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  gap: 16rpx;
+  margin-top: 20rpx;
+}
+
+.poster-tpl-card {
+  width: 200rpx;
+  border: 2rpx solid $uni-border-color-light;
+  border-radius: 12rpx;
+  padding: 20rpx 16rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  background: $app-surface;
+}
+
+.poster-tpl-active {
+  border-color: $brand-primary;
+  background: $uni-primary-pale;
+}
+
+.tpl-colors {
+  display: flex;
+  flex-direction: row;
+  gap: 8rpx;
+  margin-bottom: 12rpx;
+}
+
+.tpl-dot {
+  width: 32rpx;
+  height: 32rpx;
+  border-radius: 50%;
+}
+
+.tpl-name {
+  font-size: 24rpx;
+  color: $uni-text-color;
+  text-align: center;
+}
+
+.poster-preview-row {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 20rpx;
+}
+
+.poster-preview-hint {
+  font-size: 24rpx;
+  color: $uni-text-color-secondary;
+}
+
+.btn-preview {
+  padding: 12rpx 32rpx;
+  border-radius: 32rpx;
+  background: $brand-primary;
+  color: $app-surface;
+  font-size: 26rpx;
+}
+
 .form-actions {
   display: flex;
   flex-direction: row;
@@ -634,14 +786,14 @@ export default {
 }
 
 .btn-save {
-  background: #fff;
-  color: #F97316;
-  border: 2rpx solid #F97316;
+  background: $app-surface;
+  color: $brand-primary;
+  border: 2rpx solid $brand-primary;
 }
 
 .btn-publish {
-  background: linear-gradient(135deg, #F97316, #FB923C);
-  color: #fff;
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
+  color: $app-surface;
 }
 
 .loading {
@@ -661,8 +813,8 @@ export default {
 .loading-spinner {
   width: 60rpx;
   height: 60rpx;
-  border: 4rpx solid #FED7AA;
-  border-top: 4rpx solid #F97316;
+  border: 4rpx solid $brand-primary-light-2;
+  border-top: 4rpx solid $brand-primary;
   border-radius: 50%;
   animation: spin 0.8s linear infinite;
   margin-bottom: 16rpx;

+ 67 - 67
cfc-frontend/pages/activity/index.vue

@@ -53,7 +53,7 @@
               class="card-cover"
               :src="getImageUrl(act.coverImage) || '/static/default-activity.png'"
               mode="aspectFill"
-            />
+             lazy-load />
             <view class="card-info">
               <text class="card-title">{{ act.title }}</text>
               <text class="card-meta" v-if="act.registrantName">报名人:{{ act.registrantName }}</text>
@@ -127,11 +127,11 @@
               class="card-cover"
               :src="getImageUrl(act.coverImage) || '/static/default-activity.png'"
               mode="aspectFill"
-            />
+             lazy-load />
             <view class="card-info">
               <text class="card-title">{{ act.title }}</text>
               <text class="card-meta">开始 {{ formatDateTime(act.startTime) }}{{ act.endTime ? ' 结束 ' + formatDateTime(act.endTime) : '' }}</text>
-              <text class="card-desc" v-if="act.description">{{ act.description }}</text>
+              <text class="card-desc" v-if="act.intro || act.description">{{ act.intro || act.description }}</text>
               <text class="card-meta" v-if="act.location">📍 {{ act.location }}</text>
               <view class="card-bottom">
                 <text class="card-status" :class="'status-' + (act.displayStatus || 'open')">
@@ -192,7 +192,7 @@
               class="card-cover"
               :src="getImageUrl(act.coverImage) || '/static/default-activity.png'"
               mode="aspectFill"
-            />
+             lazy-load />
             <view class="card-info">
               <text class="card-title">{{ act.title }}</text>
               <text class="card-meta">开始 {{ formatDateTime(act.startTime) }}{{ act.endTime ? ' 结束 ' + formatDateTime(act.endTime) : '' }}</text>
@@ -270,7 +270,7 @@
               class="card-cover"
               :src="getImageUrl(act.coverImage) || '/static/default-activity.png'"
               mode="aspectFill"
-            />
+             lazy-load />
             <view class="card-info">
               <text class="card-title">{{ act.title }}</text>
               <text class="card-meta">{{ act.startTime }}</text>
@@ -889,19 +889,19 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .activity-container {
   min-height: 100vh;
-  background: #F5F5F5;
+  background: $facade-fill-2;
   display: flex;
   flex-direction: column;
 }
 
 .tab-bar {
-  background: #fff;
+  background: $app-surface;
   display: flex;
   flex-direction: row;
-  border-bottom: 1rpx solid #eee;
+  border-bottom: 1rpx solid $facade-hairline-2;
 }
 
 .tab-item {
@@ -912,16 +912,16 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 }
 
 .tab-item.active {
-  border-bottom-color: #F97316;
+  border-bottom-color: $brand-primary;
 }
 
 .tab-label {
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 
 .tab-item.active .tab-label {
-  color: #F97316;
+  color: $brand-primary;
   font-weight: 600;
 }
 
@@ -932,7 +932,7 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 
 .filter-scroll {
   padding: 20rpx 24rpx;
-  background: #fff;
+  background: $app-surface;
 }
 
 .filter-chips {
@@ -944,23 +944,23 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 .filter-chip {
   padding: 12rpx 24rpx;
   border-radius: 16px;
-  background: #fff;
-  border: 1rpx solid #ddd;
+  background: $app-surface;
+  border: 1rpx solid $facade-border-soft;
   flex-shrink: 0;
 }
 
 .filter-chip.active {
-  background: #FFF7ED;
-  border-color: #F97316;
+  background: $uni-primary-pale;
+  border-color: $brand-primary;
 }
 
 .filter-label {
   font-size: 24rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 
 .filter-chip.active .filter-label {
-  color: #F97316;
+  color: $brand-primary;
   font-weight: 500;
 }
 
@@ -972,7 +972,7 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 }
 
 .activity-card {
-  background: #fff;
+  background: $app-surface;
   border-radius: 12rpx;
   padding: 20rpx;
   display: flex;
@@ -984,7 +984,7 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
   height: 144rpx;
   border-radius: 8rpx;
   flex-shrink: 0;
-  background: #f0f0f0;
+  background: $facade-fill;
 }
 
 .card-info {
@@ -997,7 +997,7 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 .card-title {
   font-size: calc(28rpx * var(--font-scale, 1));
   font-weight: 600;
-  color: #333;
+  color: $facade-text-strong;
   line-height: 1.4;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -1006,7 +1006,7 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 
 .card-meta {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-top: 8rpx;
 }
 
@@ -1026,58 +1026,58 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 }
 
 .status-pending {
-  background: #FFF7ED;
-  color: #F97316;
+  background: $uni-primary-pale;
+  color: $brand-primary;
 }
 
 .status-approved {
-  background: #F0FDF4;
-  color: #22C55E;
+  background: $status-success-tint-2;
+  color: $status-success-2;
 }
 
 .status-rejected {
   background: #FEF2F2;
-  color: #EF4444;
+  color: $status-danger;
 }
 
 .status-cancelled {
-  background: #F5F5F5;
-  color: #999;
+  background: $facade-fill-2;
+  color: $facade-text-muted;
 }
 
 .status-checked_in {
-  background: #DBEAFE;
-  color: #3B82F6;
+  background: $status-info-soft;
+  color: $status-info;
 }
 
 .status-upcoming {
-  background: #FFF7ED;
-  color: #F97316;
+  background: $uni-primary-pale;
+  color: $brand-primary;
 }
 
 .status-ended {
-  background: #F5F5F5;
-  color: #999;
+  background: $facade-fill-2;
+  color: $facade-text-muted;
 }
 
 .status-published {
-  background: #F0FDF4;
-  color: #22C55E;
+  background: $status-success-tint-2;
+  color: $status-success-2;
 }
 
 .status-draft {
-  background: #FFF7ED;
-  color: #F97316;
+  background: $uni-primary-pale;
+  color: $brand-primary;
 }
 
 .card-price {
   font-size: 28rpx;
-  color: #F97316;
+  color: $brand-primary;
   font-weight: 600;
 }
 
 .card-price.free {
-  color: #22C55E;
+  color: $status-success-2;
 }
 
 .card-actions {
@@ -1090,9 +1090,9 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 .btn-cancel {
   padding: 8rpx 20rpx;
   font-size: 22rpx;
-  color: #999;
-  background: #fff;
-  border: 1rpx solid #ddd;
+  color: $facade-text-muted;
+  background: $app-surface;
+  border: 1rpx solid $facade-border-soft;
   border-radius: 20rpx;
   line-height: 1.5;
 }
@@ -1100,8 +1100,8 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 .btn-checkin {
   padding: 8rpx 20rpx;
   font-size: 22rpx;
-  color: #fff;
-  background: linear-gradient(135deg, #F97316, #FB923C);
+  color: $app-surface;
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
   border: none;
   border-radius: 20rpx;
   line-height: 1.5;
@@ -1123,21 +1123,21 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 .empty-title {
   font-size: 32rpx;
   font-weight: 600;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 12rpx;
 }
 
 .empty-desc {
   font-size: 26rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-bottom: 40rpx;
 }
 
 .empty-btn {
   padding: 16rpx 48rpx;
   font-size: 28rpx;
-  color: #fff;
-  background: linear-gradient(135deg, #F97316, #FB923C);
+  color: $app-surface;
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
   border: none;
   border-radius: 40rpx;
 }
@@ -1154,7 +1154,7 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
   width: 60rpx;
   height: 60rpx;
   border: 4rpx solid #FDE68A;
-  border-top-color: #F97316;
+  border-top-color: $brand-primary;
   border-radius: 50%;
   animation: spin 0.8s linear infinite;
   margin-bottom: 20rpx;
@@ -1166,7 +1166,7 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 
 .loading-text {
   font-size: 26rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .load-more-tip {
@@ -1176,7 +1176,7 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 
 .load-more-text {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 /* Nav add button */
@@ -1195,7 +1195,7 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 }
 
 .nav-add-icon {
-  color: #fff;
+  color: $app-surface;
   font-size: 36rpx;
   font-weight: 300;
   line-height: 1;
@@ -1207,7 +1207,7 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
   flex-direction: row;
   padding: 24rpx;
   gap: 16rpx;
-  background: #fff;
+  background: $app-surface;
   margin: 24rpx 24rpx 0;
   border-radius: 12rpx;
   box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
@@ -1224,25 +1224,25 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 .manage-stat-num {
   font-size: 40rpx;
   font-weight: 700;
-  color: #333;
+  color: $facade-text-strong;
   line-height: 1.2;
 }
 
 .manage-published {
-  color: #22C55E;
+  color: $status-success-2;
 }
 
 .manage-draft {
-  color: #F97316;
+  color: $brand-primary;
 }
 
 .manage-ended {
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .manage-stat-label {
   font-size: 22rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-top: 8rpx;
 }
 
@@ -1258,19 +1258,19 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
   justify-content: center;
   gap: 12rpx;
   padding: 24rpx 0;
-  background: linear-gradient(135deg, #F97316, #FB923C);
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
   border-radius: 12rpx;
   margin: 0 24rpx;
 }
 
 .manage-action-icon {
-  color: #fff;
+  color: $app-surface;
   font-size: 36rpx;
   font-weight: 300;
 }
 
 .manage-action-text {
-  color: #fff;
+  color: $app-surface;
   font-size: 28rpx;
   font-weight: 600;
 }
@@ -1283,7 +1283,7 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
 .manage-section-title text {
   font-size: 28rpx;
   font-weight: 600;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 /* Manage action group in card */
@@ -1298,8 +1298,8 @@ uni.showToast({ title: '请先选择家庭成员身份', icon: 'none' })
   font-size: 22rpx;
   padding: 4rpx 16rpx;
   border-radius: 20rpx;
-  background: #F97316;
-  color: #fff;
+  background: $brand-primary;
+  color: $app-surface;
   font-weight: 500;
 }
 </style>

+ 16 - 16
cfc-frontend/pages/activity/order-pay/order-pay.vue

@@ -170,10 +170,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   min-height: 100vh;
-  background: #F5F7FA;
+  background: $app-bg;
 }
 
 .loading-wrap {
@@ -185,7 +185,7 @@ export default {
 
 .loading-text {
   font-size: 28rpx;
-  color: #94A3B8;
+  color: $app-text-subtle;
 }
 
 .content {
@@ -194,7 +194,7 @@ export default {
 }
 
 .header-section {
-  background: #F97316;
+  background: $brand-primary;
   padding: 48rpx 32rpx;
   display: flex;
   flex-direction: column;
@@ -204,7 +204,7 @@ export default {
 .header-title {
   font-size: 36rpx;
   font-weight: bold;
-  color: #fff;
+  color: $app-surface;
 }
 
 .header-countdown {
@@ -220,7 +220,7 @@ export default {
 }
 
 .section {
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
   margin: 24rpx;
   padding: 28rpx;
@@ -230,7 +230,7 @@ export default {
   display: block;
   font-size: 28rpx;
   font-weight: 600;
-  color: #1E293B;
+  color: $uni-text-color;
   margin-bottom: 20rpx;
 }
 
@@ -244,12 +244,12 @@ export default {
 
 .info-label {
   font-size: 26rpx;
-  color: #64748B;
+  color: $uni-text-color-secondary;
 }
 
 .info-value {
   font-size: 26rpx;
-  color: #1E293B;
+  color: $uni-text-color;
   max-width: 400rpx;
   word-break: break-all;
   text-align: right;
@@ -258,7 +258,7 @@ export default {
 .order-amount {
   font-size: 32rpx;
   font-weight: bold;
-  color: #F97316;
+  color: $brand-primary;
 }
 
 .payment-method-row {
@@ -282,12 +282,12 @@ export default {
 
 .payment-name {
   font-size: 28rpx;
-  color: #1E293B;
+  color: $uni-text-color;
 }
 
 .payment-checked {
   font-size: 28rpx;
-  color: #10B981;
+  color: $status-success;
 }
 
 .pay-section {
@@ -301,8 +301,8 @@ export default {
   width: 100%;
   height: 88rpx;
   border-radius: 44rpx;
-  background: linear-gradient(135deg, #F97316, #FB923C);
-  color: #fff;
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
+  color: $app-surface;
   font-size: 30rpx;
   font-weight: 600;
   display: flex;
@@ -311,12 +311,12 @@ export default {
 }
 
 .pay-btn.disabled {
-  background: #CBD5E1;
+  background: $uni-border-color;
 }
 
 .cancel-link {
   margin-top: 24rpx;
   font-size: 26rpx;
-  color: #94A3B8;
+  color: $app-text-subtle;
 }
 </style>

+ 51 - 51
cfc-frontend/pages/ai/chat.vue

@@ -508,12 +508,12 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .chat-container {
   display: flex;
   flex-direction: column;
   height: 100vh;
-  background: var(--bg, #FFF7ED);
+  background: var(--bg, $uni-primary-pale);
   position: relative;
 }
 
@@ -523,23 +523,23 @@ export default {
   align-items: center;
   justify-content: space-between;
   padding: 88rpx 24rpx 16rpx;
-  background: #fff;
-  border-bottom: 1rpx solid var(--border-light, #E2E8F0);
+  background: $app-surface;
+  border-bottom: 1rpx solid var(--border-light, $uni-border-color-light);
   position: relative;
   z-index: 100;
 }
 
 .chat-nav.mascot-xibao {
-  background: linear-gradient(135deg, #FFF7ED 0%, #FFE4CC 100%);
-  border-bottom-color: #FF8C42;
+  background: linear-gradient(135deg, $uni-primary-pale 0%, #FFE4CC 100%);
+  border-bottom-color: $dim-body;
 }
 .chat-nav.mascot-xibao .chat-nav.mascot-fubao {
-  background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%);
-  border-bottom-color: #F59E0B;
+  background: linear-gradient(135deg, #FFFBEB 0%, $status-warning-soft 100%);
+  border-bottom-color: $dim-wealth;
 }
 .chat-nav.mascot-fubao .nav-back-icon {
   font-size: 40rpx;
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
   font-weight: 600;
 }
 
@@ -553,7 +553,7 @@ export default {
 
 .nav-action {
   font-size: 36rpx;
-  color: var(--text-secondary, #64748B);
+  color: var(--text-secondary, $uni-text-color-secondary);
 }
 
 /* 会话列表侧拉 */
@@ -573,7 +573,7 @@ export default {
   right: -500rpx;
   width: 460rpx;
   height: 100vh;
-  background: #fff;
+  background: $app-surface;
   z-index: 210;
   transition: right 0.3s ease;
   display: flex;
@@ -589,18 +589,18 @@ export default {
   justify-content: space-between;
   align-items: center;
   padding: 100rpx 24rpx 16rpx;
-  border-bottom: 1rpx solid var(--border-light, #E2E8F0);
+  border-bottom: 1rpx solid var(--border-light, $uni-border-color-light);
 }
 
 .conv-header-title {
   font-size: 28rpx;
   font-weight: 600;
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
 }
 
 .conv-header-new {
   font-size: 24rpx;
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, $brand-primary);
   font-weight: 500;
 }
 
@@ -611,7 +611,7 @@ export default {
 
 .conv-item {
   padding: 20rpx 24rpx;
-  border-bottom: 1rpx solid var(--border-light, #E2E8F0);
+  border-bottom: 1rpx solid var(--border-light, $uni-border-color-light);
 }
 
 .conv-item-active {
@@ -620,7 +620,7 @@ export default {
 
 .conv-item-name {
   font-size: 26rpx;
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
   display: block;
   margin-bottom: 6rpx;
   overflow: hidden;
@@ -630,7 +630,7 @@ export default {
 
 .conv-item-time {
   font-size: 20rpx;
-  color: var(--muted, #94A3B8);
+  color: var(--muted, $app-text-subtle);
 }
 
 /* 消息列表 */
@@ -671,14 +671,14 @@ export default {
 .welcome-title {
   font-size: 32rpx;
   font-weight: 600;
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
   display: block;
   margin-bottom: 12rpx;
 }
 
 .welcome-desc {
   font-size: 24rpx;
-  color: var(--text-secondary, #64748B);
+  color: var(--text-secondary, $uni-text-color-secondary);
   display: block;
   margin-bottom: 32rpx;
 }
@@ -693,7 +693,7 @@ export default {
 .welcome-suggestion {
   display: flex;
   align-items: center;
-  background: #fff;
+  background: $app-surface;
   padding: 20rpx 24rpx;
   border-radius: 16rpx;
   box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
@@ -706,7 +706,7 @@ export default {
 
 .suggestion-text {
   font-size: 24rpx;
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
 }
 
 /* 消息 */
@@ -738,12 +738,12 @@ export default {
 }
 
 .mascot-xibao .message-avatar {
-  background: linear-gradient(135deg, #FFF7ED 0%, #FFE4CC 100%);
-  border: 2rpx solid #FF8C42;
+  background: linear-gradient(135deg, $uni-primary-pale 0%, #FFE4CC 100%);
+  border: 2rpx solid $dim-body;
 }
 .mascot-fubao .message-avatar {
-  background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%);
-  border: 2rpx solid #F59E0B;
+  background: linear-gradient(135deg, #FFFBEB 0%, $status-warning-soft 100%);
+  border: 2rpx solid $dim-wealth;
 }
 
 .message-bubble {
@@ -756,14 +756,14 @@ export default {
 }
 
 .user-bubble {
-  background: var(--color-primary, #F97316);
-  color: #fff;
+  background: var(--color-primary, $brand-primary);
+  color: $app-surface;
   border-bottom-right-radius: 4rpx;
 }
 
 .ai-bubble {
-  background: #fff;
-  color: var(--text, #1E293B);
+  background: $app-surface;
+  color: var(--text, $uni-text-color);
   border-bottom-left-radius: 4rpx;
   box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
 }
@@ -774,13 +774,13 @@ export default {
   align-items: center;
   padding: 16rpx 24rpx;
   padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
-  background: #fff;
-  border-top: 1rpx solid var(--border-light, #E2E8F0);
+  background: $app-surface;
+  border-top: 1rpx solid var(--border-light, $uni-border-color-light);
 }
 
 .chat-input-wrap {
   flex: 1;
-  background: var(--bg, #FFF7ED);
+  background: var(--bg, $uni-primary-pale);
   border-radius: 40rpx;
   padding: 0 24rpx;
   margin-right: 12rpx;
@@ -790,7 +790,7 @@ export default {
   width: 60rpx;
   height: 60rpx;
   border-radius: 50%;
-  background: var(--border-light, #E2E8F0);
+  background: var(--border-light, $uni-border-color-light);
   display: flex;
   align-items: center;
   justify-content: center;
@@ -809,14 +809,14 @@ export default {
 .chat-input {
   height: 72rpx;
   font-size: 26rpx;
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
 }
 
 .chat-send-btn {
   width: 72rpx;
   height: 72rpx;
   border-radius: 50%;
-  background: var(--border-light, #E2E8F0);
+  background: var(--border-light, $uni-border-color-light);
   display: flex;
   align-items: center;
   justify-content: center;
@@ -824,12 +824,12 @@ export default {
 }
 
 .chat-send-active {
-  background: var(--color-primary, #F97316);
+  background: var(--color-primary, $brand-primary);
 }
 
 .send-icon {
   font-size: 28rpx;
-  color: #fff;
+  color: $app-surface;
 }
 
 /* 打字动画 */
@@ -867,7 +867,7 @@ export default {
 }
 
 .rec-list {
-  border-top: 1rpx solid var(--border-light, #E2E8F0);
+  border-top: 1rpx solid var(--border-light, $uni-border-color-light);
   padding-top: 16rpx;
   display: flex;
   flex-direction: column;
@@ -876,7 +876,7 @@ export default {
 
 /* 任务卡片列表 */
 .task-list {
-  border-top: 1rpx solid var(--border-light, #E2E8F0);
+  border-top: 1rpx solid var(--border-light, $uni-border-color-light);
   padding-top: 16rpx;
   display: flex;
   flex-direction: column;
@@ -887,14 +887,14 @@ export default {
 .source-label {
   margin-top: 16rpx;
   font-size: 22rpx;
-  color: var(--muted, #94A3B8);
+  color: var(--muted, $app-text-subtle);
   text-align: right;
 }
 
 .rec-card {
   display: flex;
   flex-direction: row;
-  background: var(--bg, #FFF7ED);
+  background: var(--bg, $uni-primary-pale);
   border-radius: 12rpx;
   overflow: hidden;
   padding: 12rpx;
@@ -906,7 +906,7 @@ export default {
   border-radius: 8rpx;
   margin-right: 12rpx;
   flex-shrink: 0;
-  background: #E2E8F0;
+  background: $uni-border-color-light;
 }
 
 .rec-info {
@@ -920,7 +920,7 @@ export default {
 .rec-type-tag {
   display: inline-block;
   font-size: 18rpx;
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, $brand-primary);
   background: rgba(249,115,22,0.1);
   padding: 2rpx 12rpx;
   border-radius: 8rpx;
@@ -930,7 +930,7 @@ export default {
 .rec-name {
   font-size: 26rpx;
   font-weight: 600;
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
@@ -938,7 +938,7 @@ export default {
 
 .rec-desc {
   font-size: 22rpx;
-  color: var(--text-secondary, #64748B);
+  color: var(--text-secondary, $uni-text-color-secondary);
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
@@ -946,7 +946,7 @@ export default {
 
 .rec-price {
   font-size: 26rpx;
-  color: #F97316;
+  color: $brand-primary;
   font-weight: 600;
 }
 
@@ -964,19 +964,19 @@ export default {
   font-size: 24rpx;
 }
 .mascot-badge-name {
-  color: #6366F1;
+  color: $dim-wisdom;
   font-weight: 500;
 }
 .chat-disclaimer {
   text-align: center;
   font-size: 22rpx;
-  color: #999;
+  color: $facade-text-muted;
   padding: 16rpx 0 20rpx;
-  background: #f5f5f5;
+  background: $facade-fill-2;
 }
 .quota-bar-wrapper {
   padding: 16rpx 30rpx;
-  background: #fff;
-  border-top: 1rpx solid #f5f5f5;
+  background: $app-surface;
+  border-top: 1rpx solid $facade-fill-2;
 }
 </style>

+ 12 - 12
cfc-frontend/pages/ai/components/AITaskCard.vue

@@ -130,9 +130,9 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .ai-task-card {
-  background-color: #FFFFFF;
+  background-color: $app-surface;
   border-radius: 16rpx;
   padding: 32rpx;
   margin: 24rpx;
@@ -150,7 +150,7 @@ export default {
 .card-title {
   font-size: 32rpx;
   font-weight: 600;
-  color: #1F2937;
+  color: $facade-text-gray-800;
   flex: 1;
   margin-right: 16rpx;
 }
@@ -164,7 +164,7 @@ export default {
 .dimension-text {
   font-size: 24rpx;
   font-weight: 500;
-  color: #FFFFFF;
+  color: $app-surface;
 }
 
 .card-body {
@@ -173,7 +173,7 @@ export default {
 
 .card-description {
   font-size: 28rpx;
-  color: #6B7280;
+  color: $facade-text-slate;
   line-height: 44rpx;
 }
 
@@ -183,7 +183,7 @@ export default {
   justify-content: space-between;
   align-items: center;
   padding-top: 24rpx;
-  border-top: 1rpx solid #F3F4F6;
+  border-top: 1rpx solid $facade-fill-gray-100;
 }
 
 .reward-info {
@@ -194,19 +194,19 @@ export default {
 
 .reward-label {
   font-size: 26rpx;
-  color: #9CA3AF;
+  color: $facade-text-gray-400;
   margin-right: 12rpx;
 }
 
 .reward-points {
   font-size: 32rpx;
   font-weight: 700;
-  color: #F97316;
+  color: $brand-primary;
 }
 
 .accept-btn {
-  background-color: #F97316;
-  color: #FFFFFF;
+  background-color: $brand-primary;
+  color: $app-surface;
   border: none;
   border-radius: 12rpx;
   padding: 20rpx 40rpx;
@@ -216,7 +216,7 @@ export default {
 }
 
 .accept-btn:active {
-  background-color: #EA580C;
+  background-color: $brand-primary-dark;
 }
 
 .accept-btn:disabled {
@@ -224,6 +224,6 @@ export default {
 }
 
 .btn-text {
-  color: #FFFFFF;
+  color: $app-surface;
 }
 </style>

+ 4 - 4
cfc-frontend/pages/ai/components/QuotaProgressBar.vue

@@ -27,7 +27,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .quota-progress-bar {
   margin-bottom: 16rpx;
 }
@@ -35,18 +35,18 @@ export default {
   display: flex;
   justify-content: space-between;
   font-size: 24rpx;
-  color: #666;
+  color: $facade-text-body;
   margin-bottom: 8rpx;
 }
 .quota-track {
   height: 12rpx;
-  background: #f0f0f0;
+  background: $facade-fill;
   border-radius: 6rpx;
   overflow: hidden;
 }
 .quota-fill {
   height: 100%;
-  background: #F97316;
+  background: $brand-primary;
   border-radius: 6rpx;
   transition: width 0.3s;
 }

+ 10 - 10
cfc-frontend/pages/ai/components/UpgradeGuideModal.vue

@@ -69,7 +69,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .upgrade-guide-mask {
   position: fixed;
   top: 0; left: 0; right: 0; bottom: 0;
@@ -81,7 +81,7 @@ export default {
 }
 .upgrade-guide-content {
   width: 600rpx;
-  background: #fff;
+  background: $app-surface;
   border-radius: 24rpx;
   padding: 48rpx 36rpx 36rpx;
   text-align: center;
@@ -93,12 +93,12 @@ export default {
 .upgrade-guide-title {
   font-size: 32rpx;
   font-weight: 600;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 16rpx;
 }
 .upgrade-guide-desc {
   font-size: 26rpx;
-  color: #666;
+  color: $facade-text-body;
   line-height: 1.6;
   margin-bottom: 36rpx;
 }
@@ -114,16 +114,16 @@ export default {
   font-size: 28rpx;
 }
 .btn-know {
-  background: #f5f5f5;
-  color: #666;
+  background: $facade-fill-2;
+  color: $facade-text-body;
 }
 .btn-learn {
   background: transparent;
-  color: #F97316;
-  border: 2rpx solid #F97316;
+  color: $brand-primary;
+  border: 2rpx solid $brand-primary;
 }
 .btn-upgrade {
-  background: #F97316;
-  color: #fff;
+  background: $brand-primary;
+  color: $app-surface;
 }
 </style>

+ 154 - 64
cfc-frontend/pages/article-center/article-detail.vue

@@ -13,7 +13,7 @@
     </view>
     <template v-else-if="article">
       <scroll-view scroll-y class="content-scroll" @scrolltolower="onScrollToBottom">
-        <image v-if="article.coverImage" class="detail-cover" :src="article.coverImage" mode="widthFix" />
+        <image v-if="article.coverImage" class="detail-cover" :src="article.coverImage" mode="widthFix"  lazy-load />
         <text class="detail-title">{{ article.title }}</text>
         <view class="detail-meta">
           <text class="meta-author">{{ article.author || '浠艾福' }}</text>
@@ -41,16 +41,7 @@
         <view class="divider"></view>
         <view class="detail-body"><mp-html :content="article.content" :domain="config.API_BASE_URL" :tag-style="contentTagStyle" /></view>
 
-        <!-- 关联商品推荐(文章标签 ↔ 商品标签匹配,登录/匿名均可返回) -->
-        <view v-if="relatedProducts.length > 0" class="related-section">
-          <view class="related-header">
-            <text class="related-title">相关商品</text>
-            <text class="related-subtitle">根据文章内容推荐</text>
-          </view>
-          <dimension-products :products="relatedProducts" @productClick="goRelatedProduct" />
-        </view>
-
-        <!-- 相关文章(按标签关联,无匹配整块隐藏) -->
+        <!-- 相关文章(按标签关联,无匹配整块隐藏;触底分页加载) -->
         <view v-if="relatedArticles.length > 0" class="related-section">
           <view class="related-header">
             <text class="related-title">相关文章</text>
@@ -64,13 +55,25 @@
               hover-class="ra-item-pressed"
               @click="goRelatedArticle(item)"
             >
-              <image class="ra-cover" :src="getImageUrl(item.coverImage) || '/static/default-article.png'" mode="aspectFill" />
+              <image class="ra-cover" :src="getImageUrl(item.coverImage) || '/static/default-article.png'" mode="aspectFill"  lazy-load />
               <view class="ra-body">
                 <text class="ra-title">{{ item.title }}</text>
                 <text class="ra-summary" v-if="item.summary">{{ item.summary }}</text>
               </view>
             </view>
           </view>
+          <view v-if="relatedArticlesLoading" class="related-loading-hint">加载中...</view>
+        </view>
+
+        <!-- 关联商品推荐(文章标签 ↔ 商品标签匹配,登录/匿名均可返回;商城两列大卡片样式,触底分页加载) -->
+        <view v-if="relatedProducts.length > 0" class="related-section">
+          <view class="related-header">
+            <text class="related-title">相关商品</text>
+            <text class="related-subtitle">根据文章内容推荐</text>
+          </view>
+          <dimension-products :products="relatedProducts" layout="mall" @productClick="goRelatedProduct" />
+          <view v-if="relatedProductsLoading" class="related-loading-hint">加载中...</view>
+          <view v-else-if="relatedProductsNoMore" class="related-loading-hint dim">没有更多了</view>
         </view>
 
         <!-- 评论区(已屏蔽) -->
@@ -108,7 +111,11 @@
           </view>
           <text class="q-title">{{ currentQuestion.question }}</text>
           <view class="q-options">
-            <text v-for="(opt, oi) in currentQuestion.options" :key="oi" class="q-option" :class="{ selected: quizAnswers[currentQuizIndex] === String.fromCharCode(65 + oi) }" @click="selectQuizAnswer(currentQuizIndex, oi)">{{ opt }}</text>
+            <view v-for="(opt, oi) in currentQuestionOptions" :key="oi" class="q-option-row" :class="opt.selected ? 'selected' : ''" @click="selectQuizAnswer(currentQuizIndex, oi)">
+              <text class="q-option-letter">{{ opt.letter }}.</text>
+              <text class="q-option-text">{{ opt.text }}</text>
+              <text class="q-option-check">✓</text>
+            </view>
           </view>
           <view class="quiz-nav">
             <button v-if="currentQuizIndex > 0" class="quiz-prev" @click="goPrevQuestion">上一题</button>
@@ -148,6 +155,9 @@ export default {
       articleId: '', article: null, loading: true, error: false, errorMsg: '',
       relatedProducts: [],
       relatedArticles: [],
+      // 关联区块分页状态
+      relatedProductsPage: 1, relatedProductsLoading: false, relatedProductsNoMore: false,
+      relatedArticlesPage: 1, relatedArticlesLoading: false, relatedArticlesNoMore: false,
       readingSeconds: 0, lastReportedSeconds: 0, readingCompleted: false,
       isTimerRunning: false, timerHandle: null, syncHandle: null,
       currentChildId: null, isLogin: false,
@@ -173,6 +183,20 @@ export default {
     currentQuestion: function() {
       if (!this.quizQuestions || !this.quizQuestions.length) return null
       return this.quizQuestions[this.currentQuizIndex] || null
+    },
+    // 当前题的选项展示数据:统一派生字母、剥离后端自带前缀、标记选中态。
+    // 避免在模板 :class / 插值中调用方法(小程序渲染层不支持)且数组索引访问不响应。
+    currentQuestionOptions: function() {
+      var q = this.currentQuestion
+      if (!q || !q.options) return []
+      var selected = this.quizAnswers[this.currentQuizIndex] || ''
+      return q.options.map(function(opt, oi) {
+        var letter = String.fromCharCode(65 + oi)
+        var s = opt ? opt.toString().trim() : ''
+        // 剥离 "A." / "A)" / "(A)" 等常见前缀,字母由下方单独渲染避免重复
+        var cleaned = s.replace(/^[A-Za-z][\.\)\::]\s*/i, '').trim()
+        return { letter: letter, text: cleaned, selected: selected === letter }
+      })
     }
   },
   onLoad(options) {
@@ -220,33 +244,78 @@ export default {
           this.article.content = this.resolveContentUrls(this.article.content)
           this.setShareInfo('推荐阅读: ' + (res.data.title || ''), '/pages/article-center/article-detail?id=' + id)
           this.startReadingTimer()
-          // 关联商品:放在 loadDetail 内只走一次(onShow 不重复请求,避免双发)
-          this.loadRelatedProducts(id)
-          // 相关文章:同样只走一次,无匹配时为空数组 → 模板整块隐藏
-          this.loadRelatedArticles(id)
+          // 关联区块:放在 loadDetail 内只走一次(onShow 不重复请求,避免双发);先重置分页状态再加载第 1 页
+          this.resetRelatedState()
+          this.loadRelatedProducts(id, 1, false)
+          this.loadRelatedArticles(id, 1, false)
         } else { this.error = true; this.errorMsg = '文章不存在或无权限查看' }
       } catch (e) { this.error = true; this.errorMsg = '加载失败' }
       finally { this.loading = false }
     },
-    // 加载文章关联商品(后端按 标签匹配数 > 用户权重 > 家庭权重 > 销量 排序)
-    loadRelatedProducts: function(articleId) {
+    // 重置关联区块分页状态(loadDetail 每次加载前调用,避免切文章后旧状态残留)
+    resetRelatedState: function() {
+      this.relatedProductsPage = 1
+      this.relatedProductsLoading = false
+      this.relatedProductsNoMore = false
+      this.relatedArticlesPage = 1
+      this.relatedArticlesLoading = false
+      this.relatedArticlesNoMore = false
+    },
+    // 加载文章关联商品(后端按 标签匹配数 > 用户权重 > 家庭权重 > 销量 排序),每页 4 个,触底翻页
+    loadRelatedProducts: function(articleId, page, append) {
       var self = this
       if (!articleId) return
-      getArticleRelatedProducts({ articleId: articleId, size: 4 }).then(function(res) {
+      var pg = page || 1
+      if (pg === 1) {
+        this.relatedProducts = []
+        this.relatedProductsNoMore = false
+      }
+      if (this.relatedProductsLoading) return
+      this.relatedProductsLoading = true
+      getArticleRelatedProducts({ articleId: articleId, size: 4, page: pg }).then(function(res) {
+        self.relatedProductsLoading = false
         if (res && res.code === 200 && res.data && Array.isArray(res.data)) {
-          self.relatedProducts = res.data
+          if (append && pg > 1) {
+            self.relatedProducts = self.relatedProducts.concat(res.data)
+          } else {
+            self.relatedProducts = res.data
+          }
+          if (res.data.length < 4) self.relatedProductsNoMore = true
+        } else {
+          self.relatedProductsNoMore = true
         }
-      }).catch(function() { self.relatedProducts = [] })
+      }).catch(function() {
+        self.relatedProductsLoading = false
+        self.relatedProductsNoMore = true
+      })
     },
-    // 加载相关文章(后端按共享标签数降序,无匹配返回空数组 → 前端整块隐藏)
-    loadRelatedArticles: function(articleId) {
+    // 加载相关文章(后端按共享标签数降序),每页 3 篇,触底翻页;无匹配返回空数组 → 前端整块隐藏
+    loadRelatedArticles: function(articleId, page, append) {
       var self = this
       if (!articleId) return
-      getRelatedArticles({ articleId: articleId, size: 3 }).then(function(res) {
+      var pg = page || 1
+      if (pg === 1) {
+        this.relatedArticles = []
+        this.relatedArticlesNoMore = false
+      }
+      if (this.relatedArticlesLoading) return
+      this.relatedArticlesLoading = true
+      getRelatedArticles({ articleId: articleId, size: 3, page: pg }).then(function(res) {
+        self.relatedArticlesLoading = false
         if (res && res.code === 200 && res.data && Array.isArray(res.data)) {
-          self.relatedArticles = res.data
+          if (append && pg > 1) {
+            self.relatedArticles = self.relatedArticles.concat(res.data)
+          } else {
+            self.relatedArticles = res.data
+          }
+          if (res.data.length < 3) self.relatedArticlesNoMore = true
+        } else {
+          self.relatedArticlesNoMore = true
         }
-      }).catch(function() { self.relatedArticles = [] })
+      }).catch(function() {
+        self.relatedArticlesLoading = false
+        self.relatedArticlesNoMore = true
+      })
     },
     // 跳转相关文章详情
     goRelatedArticle: function(item) {
@@ -294,7 +363,18 @@ export default {
       }
       return best ? best.code : ''
     },
-    onScrollToBottom() {},
+    onScrollToBottom: function() {
+      // 触底优先翻页「相关商品」(页面下方区块),商品加载完再翻「相关文章」
+      if (this.relatedProducts.length > 0 && !this.relatedProductsNoMore && !this.relatedProductsLoading) {
+        this.relatedProductsPage++
+        this.loadRelatedProducts(this.articleId, this.relatedProductsPage, true)
+        return
+      }
+      if (this.relatedArticles.length > 0 && !this.relatedArticlesNoMore && !this.relatedArticlesLoading) {
+        this.relatedArticlesPage++
+        this.loadRelatedArticles(this.articleId, this.relatedArticlesPage, true)
+      }
+    },
     startNewReadingSession: function() {
       // 每次进入文章详情页都从 0 开始新会话,时长不延续上一次
       this.readingSeconds = 0
@@ -471,7 +551,9 @@ export default {
       this.showResult = false
     },
     selectQuizAnswer: function(qi, oi) {
-      this.quizAnswers[qi] = String.fromCharCode(65 + oi)
+      // Vue 2 中数组索引直接赋值(quizAnswers[qi] = x)不触发响应式更新,
+      // 必须用 $set 才能驱动选中态/圆点高亮刷新
+      this.$set(this.quizAnswers, qi, String.fromCharCode(65 + oi))
     },
     // 一题一页:上一题 / 下一题
     goPrevQuestion: function() {
@@ -485,58 +567,60 @@ export default {
 </script>
 
 <style scoped lang="scss">
-.detail-container { min-height: 100vh; background: #fff; }
+.detail-container { min-height: 100vh; background: $app-surface; }
 .loading-wrap { display: flex; flex-direction: column; align-items: center; padding-top: 300rpx; }
-.loading-spinner { width: 60rpx; height: 60rpx; border: 4rpx solid #e0e0e0; border-top-color: $uni-info; border-radius: 50%; animation: spin 0.8s linear infinite; margin-bottom: 20rpx; }
+.loading-spinner { width: 60rpx; height: 60rpx; border: 4rpx solid $facade-hairline; border-top-color: $uni-info; border-radius: 50%; animation: spin 0.8s linear infinite; margin-bottom: 20rpx; }
 @keyframes spin { 0% { transform: rotate(0deg); } 360% { transform: rotate(360deg); } }
-.loading-text { font-size: 26rpx; color: #999; }
+.loading-text { font-size: 26rpx; color: $facade-text-muted; }
 .error-wrap { display: flex; flex-direction: column; align-items: center; padding-top: 300rpx; }
 .error-icon { font-size: 100rpx; margin-bottom: 24rpx; }
-.error-text { font-size: 28rpx; color: #999; margin-bottom: 30rpx; }
-.retry-btn { width: 240rpx; height: 72rpx; line-height: 72rpx; background: $brand-primary; color: #fff; font-size: 28rpx; border-radius: 36rpx; text-align: center; border: none; }
+.error-text { font-size: 28rpx; color: $facade-text-muted; margin-bottom: 30rpx; }
+.retry-btn { width: 240rpx; height: 72rpx; line-height: 72rpx; background: $brand-primary; color: $app-surface; font-size: 28rpx; border-radius: 36rpx; text-align: center; border: none; }
 .content-scroll { height: calc(100vh - 120rpx); }
 .detail-cover { width: 100%; display: block; }
-.detail-title { display: block; font-size: calc(36rpx * var(--font-scale, 1)); font-weight: bold; color: #333; line-height: 1.4; padding: 30rpx 30rpx 0; }
-.detail-meta { display: flex; align-items: center; padding: 16rpx 30rpx 0; font-size: 22rpx; color: #999; flex-wrap: wrap; }
+.detail-title { display: block; font-size: calc(36rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; line-height: 1.4; padding: 30rpx 30rpx 0; }
+.detail-meta { display: flex; align-items: center; padding: 16rpx 30rpx 0; font-size: 22rpx; color: $facade-text-muted; flex-wrap: wrap; }
 .meta-author { color: $uni-info; }
-.meta-sep { margin: 0 12rpx; color: #ddd; }
+.meta-sep { margin: 0 12rpx; color: $facade-border-soft; }
 .reading-time-badge { margin-left: auto; font-size: 20rpx; color: $uni-info; background: rgba(14, 165, 233, 0.08); padding: 4rpx 12rpx; border-radius: 20rpx; white-space: nowrap; }
-.reading-time-badge.completed { color: #10B981; background: rgba(16,185,129,0.1); }
+.reading-time-badge.completed { color: $status-success; background: rgba(16,185,129,0.1); }
 .meta-readcount {
   font-size: 20rpx;
-  color: #bbb;
+  color: $facade-text-ghost-light;
 }
 .detail-category-row { padding: 16rpx 30rpx 0; }
 .detail-category { display: inline-block; font-size: 20rpx; color: $uni-info; background: rgba(14, 165, 233, 0.1); padding: 4rpx 16rpx; border-radius: 8rpx; }
 .detail-dimensions { padding: 16rpx 30rpx 0; display: flex; gap: 12rpx; }
 .dim-bar-item { flex: 1; }
 .dim-bar { height: 8rpx; border-radius: 4rpx; }
-.dim-label { font-size: 18rpx; color: #999; text-align: center; display: block; margin-top: 4rpx; }
+.dim-label { font-size: 18rpx; color: $facade-text-muted; text-align: center; display: block; margin-top: 4rpx; }
 .detail-summary { padding: 20rpx 30rpx; }
-.detail-summary-text { font-size: calc(26rpx * var(--font-scale, 1)); color: #888; line-height: 1.7; display: block; border-left: 4rpx solid #F97316; padding-left: 20rpx; }
-.divider { height: 1rpx; background: #eee; margin: 24rpx 30rpx; }
+.detail-summary-text { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-faint; line-height: 1.7; display: block; border-left: 4rpx solid $brand-primary; padding-left: 20rpx; }
+.divider { height: 1rpx; background: $facade-hairline-2; margin: 24rpx 30rpx; }
 .detail-body { padding: 0 30rpx; font-size: calc(32rpx * var(--font-scale, 1)); color: #444; line-height: 1.8; }
 
 /* 关联商品推荐 */
 .related-section { margin-top: 20rpx; padding: 0 30rpx 8rpx; }
 .related-header { display: flex; align-items: baseline; margin-bottom: 16rpx; }
-.related-title { font-size: 30rpx; font-weight: bold; color: #333; }
-.related-subtitle { font-size: 20rpx; color: #999; margin-left: 12rpx; }
+.related-title { font-size: 30rpx; font-weight: bold; color: $facade-text-strong; }
+.related-subtitle { font-size: 20rpx; color: $facade-text-muted; margin-left: 12rpx; }
+.related-loading-hint { text-align: center; font-size: 22rpx; color: $brand-primary; padding: 16rpx 0 8rpx; }
+.related-loading-hint.dim { color: $facade-text-ghost-light; }
 
 /* 相关文章 */
 .ra-list { display: flex; flex-direction: column; gap: 16rpx; }
-.ra-item { display: flex; align-items: center; background: #fafafa; border-radius: 12rpx; padding: 16rpx; }
+.ra-item { display: flex; align-items: center; background: $facade-fill-ghost; border-radius: 12rpx; padding: 16rpx; }
 .ra-item-pressed { opacity: 0.7; }
-.ra-cover { width: 140rpx; height: 100rpx; border-radius: 8rpx; background: #eee; flex-shrink: 0; }
+.ra-cover { width: 140rpx; height: 100rpx; border-radius: 8rpx; background: $facade-hairline-2; flex-shrink: 0; }
 .ra-body { margin-left: 20rpx; flex: 1; min-width: 0; }
-.ra-title { display: block; font-size: 28rpx; font-weight: 600; color: #333; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.ra-summary { display: block; margin-top: 8rpx; font-size: 22rpx; color: #999; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.ra-title { display: block; font-size: 28rpx; font-weight: 600; color: $facade-text-strong; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.ra-summary { display: block; margin-top: 8rpx; font-size: 22rpx; color: $facade-text-muted; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
 
-.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; padding: 0 30rpx; background: linear-gradient(135deg, #F97316, #FB923C); border-radius: 36rpx; display: flex; align-items: center; justify-content: center; border: none; }
+.detail-footer { position: fixed; bottom: 0; left: 0; right: 0; background: $app-surface; 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; padding: 0 30rpx; background: linear-gradient(135deg, $brand-primary, $brand-primary-light); border-radius: 36rpx; display: flex; align-items: center; justify-content: center; border: none; }
 .share-btn::after { border: none; }
 .share-btn-icon { font-size: 30rpx; margin-right: 8rpx; }
-.share-btn-text { font-size: 26rpx; color: #fff; font-weight: 600; }
+.share-btn-text { font-size: 26rpx; color: $app-surface; font-weight: 600; }
 
 .listen-btn { height: 72rpx; padding: 0 30rpx; background: rgba(14, 165, 233, 0.1); border: 2rpx solid $uni-info; border-radius: 36rpx; display: flex; align-items: center; justify-content: center; }
 .listen-btn.playing { background: rgba(14, 165, 233, 0.2); }
@@ -545,28 +629,34 @@ export default {
 
 /* 答题弹窗 */
 .quiz-mask, .result-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 999; display: flex; align-items: center; justify-content: center; }
-.quiz-dialog, .result-dialog { background: #fff; border-radius: 24rpx; width: 650rpx; padding: 32rpx; }
+.quiz-dialog, .result-dialog { background: $app-surface; border-radius: 24rpx; width: 650rpx; padding: 32rpx; }
 .quiz-mascot { display: flex; align-items: center; justify-content: center; gap: 12rpx; margin-bottom: 16rpx; }
 .mascot-icon { font-size: 60rpx; }
-.mascot-name { font-size: 32rpx; font-weight: 700; color: #F97316; }
+.mascot-name { font-size: 32rpx; font-weight: 700; color: $brand-primary; }
 .quiz-step-bar { display: flex; align-items: center; justify-content: center; margin-bottom: 24rpx; }
-.quiz-step-text { font-size: 24rpx; color: #999; margin-right: 12rpx; }
+.quiz-step-text { font-size: 24rpx; color: $facade-text-muted; margin-right: 12rpx; }
 .quiz-dots { display: flex; align-items: center; gap: 8rpx; }
-.quiz-dot { width: 12rpx; height: 12rpx; border-radius: 50%; background: #E5E7EB; }
-.quiz-dot.active { background: #F97316; }
+.quiz-dot { width: 12rpx; height: 12rpx; border-radius: 50%; background: $facade-gray-200; }
+.quiz-dot.active { background: $brand-primary; }
 .quiz-dot.done { background: #FDBA74; }
-.q-title { font-size: 30rpx; font-weight: 600; color: #333; line-height: 1.5; margin-bottom: 20rpx; }
+.q-title { font-size: 30rpx; font-weight: 600; color: $facade-text-strong; line-height: 1.5; margin-bottom: 20rpx; }
 .q-options { display: flex; flex-direction: column; gap: 12rpx; }
-.q-option { padding: 16rpx 20rpx; border: 2rpx solid #E5E7EB; border-radius: 12rpx; font-size: 26rpx; color: #333; }
-.q-option.selected { border-color: #F97316; background: #FFF7ED; color: #92400E; }
+.q-option-row { display: flex; align-items: center; padding: 16rpx 20rpx; border: 2rpx solid $facade-gray-200; border-radius: 12rpx; }
+.q-option-row.selected { border-color: $brand-primary; background: $uni-primary-pale; }
+.q-option-letter { width: 44rpx; font-size: 26rpx; font-weight: 600; color: $facade-text-body; flex-shrink: 0; }
+.q-option-row.selected .q-option-letter { color: $brand-primary; }
+.q-option-text { flex: 1; min-width: 0; font-size: 26rpx; color: $facade-text-strong; }
+.q-option-row.selected .q-option-text { color: $status-warning-strong; }
+.q-option-check { width: 32rpx; height: 32rpx; border-radius: 50%; border: 3rpx solid #D1D5DB; color: transparent; font-size: 20rpx; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
+.q-option-row.selected .q-option-check { border-color: $brand-primary; background: $brand-primary; color: $app-surface; }
 .quiz-nav { display: flex; gap: 16rpx; margin-top: 28rpx; }
-.quiz-prev { flex: 1; padding: 20rpx; background: #fff; color: #F97316; border: 2rpx solid #F97316; font-size: 28rpx; border-radius: 12rpx; }
+.quiz-prev { flex: 1; padding: 20rpx; background: $app-surface; color: $brand-primary; border: 2rpx solid $brand-primary; font-size: 28rpx; border-radius: 12rpx; }
 .quiz-prev::after { border: none; }
-.quiz-submit { flex: 1; padding: 20rpx; background: linear-gradient(135deg, #F97316, #FB923C); color: #fff; font-size: 28rpx; border-radius: 12rpx; border: none; }
+.quiz-submit { flex: 1; padding: 20rpx; background: linear-gradient(135deg, $brand-primary, $brand-primary-light); color: $app-surface; font-size: 28rpx; border-radius: 12rpx; border: none; }
 .quiz-submit::after { border: none; }
-.result-btn { width: 100%; padding: 20rpx; background: linear-gradient(135deg, #F97316, #FB923C); color: #fff; font-size: 28rpx; border-radius: 12rpx; border: none; margin-top: 20rpx; }
+.result-btn { width: 100%; padding: 20rpx; background: linear-gradient(135deg, $brand-primary, $brand-primary-light); color: $app-surface; font-size: 28rpx; border-radius: 12rpx; border: none; margin-top: 20rpx; }
 .result-dialog { text-align: center; }
 .result-icon { font-size: 80rpx; }
-.result-text { font-size: 32rpx; font-weight: 700; color: #333; margin: 16rpx 0; }
-.result-energy { font-size: 28rpx; color: #F97316; }
+.result-text { font-size: 32rpx; font-weight: 700; color: $facade-text-strong; margin: 16rpx 0; }
+.result-energy { font-size: 28rpx; color: $brand-primary; }
 </style>

+ 20 - 20
cfc-frontend/pages/article-center/article-edit.vue

@@ -15,7 +15,7 @@
     <scroll-view scroll-y class="edit-form">
       <!-- 封面图 -->
       <view class="form-cover" @click="chooseCover">
-        <image v-if="coverImage" :src="coverImage" mode="aspectFill" class="cover-preview" />
+        <image v-if="coverImage" :src="coverImage" mode="aspectFill" class="cover-preview"  lazy-load />
         <view v-else class="cover-placeholder">
           <text class="cover-icon">📷</text>
           <text class="cover-hint">添加封面</text>
@@ -242,13 +242,13 @@ export default {
 </script>
 
 <style scoped lang="scss">
-.edit-container { min-height: 100vh; background: #f5f7fa; }
+.edit-container { min-height: 100vh; background: $app-bg; }
 .edit-header {
   display: flex;
   align-items: center;
   justify-content: space-between;
   padding: 80rpx 30rpx 20rpx;
-  background: #fff;
+  background: $app-surface;
   position: sticky;
   top: 0;
   z-index: 10;
@@ -256,7 +256,7 @@ export default {
 .header-left { display: flex; align-items: center; }
 .back-icon { font-size: 36rpx; margin-right: 8rpx; }
 
-.header-title { font-size: 32rpx; font-weight: bold; color: #333; }
+.header-title { font-size: 32rpx; font-weight: bold; color: $facade-text-strong; }
 .header-right { }
 .history-text { font-size: 26rpx; color: $brand-primary; }
 .edit-form { padding: 20rpx 30rpx; }
@@ -264,7 +264,7 @@ export default {
 .form-cover {
   width: 100%;
   height: 300rpx;
-  background: #eee;
+  background: $facade-hairline-2;
   border-radius: 16rpx;
   overflow: hidden;
   margin-bottom: 20rpx;
@@ -279,20 +279,20 @@ export default {
   height: 100%;
 }
 .cover-icon { font-size: 60rpx; margin-bottom: 10rpx; }
-.cover-hint { font-size: 24rpx; color: #999; }
+.cover-hint { font-size: 24rpx; color: $facade-text-muted; }
 .cover-change {
   position: absolute;
   right: 16rpx;
   bottom: 16rpx;
   background: rgba(0,0,0,0.5);
-  color: #fff;
+  color: $app-surface;
   padding: 6rpx 20rpx;
   border-radius: 20rpx;
   font-size: 22rpx;
 }
 /* 表单组 */
 .form-group {
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
   padding: 24rpx;
   margin-bottom: 20rpx;
@@ -300,29 +300,29 @@ export default {
 .form-input {
   width: 100%;
   font-size: 30rpx;
-  color: #333;
+  color: $facade-text-strong;
   border: none;
   outline: none;
 }
 .title-input { font-weight: bold; }
-.char-count { display: block; text-align: right; font-size: 20rpx; color: #bbb; margin-top: 8rpx; }
-.form-label { font-size: 24rpx; color: #666; display: block; margin-bottom: 16rpx; }
+.char-count { display: block; text-align: right; font-size: 20rpx; color: $facade-text-ghost-light; margin-top: 8rpx; }
+.form-label { font-size: 24rpx; color: $facade-text-body; display: block; margin-bottom: 16rpx; }
 /* 维度 */
 .dimension-chips { display: flex; flex-wrap: wrap; gap: 16rpx; }
 .dim-chip {
   padding: 10rpx 28rpx;
   border-radius: 30rpx;
   font-size: 24rpx;
-  border: 2rpx solid #ddd;
-  color: #666;
+  border: 2rpx solid $facade-border-soft;
+  color: $facade-text-body;
 }
-.dim-chip.active { color: #fff; border-color: transparent; }
+.dim-chip.active { color: $app-surface; border-color: transparent; }
 /* 文本框 */
 .form-textarea {
   width: 100%;
   height: 300rpx;
   font-size: 28rpx;
-  color: #333;
+  color: $facade-text-strong;
   line-height: 1.6;
   border: none;
   outline: none;
@@ -334,20 +334,20 @@ export default {
   flex: 1;
   padding: 24rpx;
   border-radius: 12rpx;
-  border: 2rpx solid #eee;
+  border: 2rpx solid $facade-hairline-2;
   text-align: center;
 }
 .vis-option.active { border-color: $brand-primary; background: $uni-primary-pale; }
 .vis-icon { font-size: 40rpx; display: block; margin-bottom: 8rpx; }
-.vis-text { font-size: 26rpx; font-weight: bold; color: #333; display: block; }
-.vis-desc { font-size: 20rpx; color: #999; display: block; margin-top: 4rpx; }
+.vis-text { font-size: 26rpx; font-weight: bold; color: $facade-text-strong; display: block; }
+.vis-desc { font-size: 20rpx; color: $facade-text-muted; display: block; margin-top: 4rpx; }
 /* 发布按钮 */
 .publish-btn {
   width: 100%;
   height: 88rpx;
   line-height: 88rpx;
-  background: linear-gradient(135deg, #F97316, #EA580C);
-  color: #fff;
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-dark);
+  color: $app-surface;
   font-size: 32rpx;
   font-weight: bold;
   border-radius: 44rpx;

+ 36 - 36
cfc-frontend/pages/article-center/index.vue

@@ -69,7 +69,7 @@
 
       <!-- 空状态 -->
       <view v-else-if="articles.length === 0" class="ac-empty">
-        <image class="ac-empty-img" src="/static/empty-article.png" mode="aspectFit"></image>
+        <image class="ac-empty-img" src="/static/empty-article.png" mode="aspectFit" lazy-load></image>
         <text class="ac-empty-text">暂无文章</text>
       </view>
 
@@ -87,7 +87,7 @@
               class="ac-card-cover"
               :src="getImageUrl(item.coverImage) || '/static/default-article.png'"
               mode="aspectFill"
-            />
+             lazy-load />
             <view class="ac-card-dimension-tags" v-if="item.relatedDimensions">
               <view
                 v-for="dim in parseDimensions(item.relatedDimensions)"
@@ -329,7 +329,7 @@ onLoad(options) {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 /* ====================================
    知识分享 — 重设计
    白色导航 + 药丸Tab + 精装卡片
@@ -337,7 +337,7 @@ onLoad(options) {
 
 .ac-container {
   min-height: 100vh;
-  background: #f5f7fa;
+  background: $app-bg;
   position: relative;
 }
 
@@ -347,7 +347,7 @@ onLoad(options) {
   align-items: center;
   justify-content: space-between;
   padding: 84rpx 28rpx 20rpx;
-  background: #fff;
+  background: $app-surface;
   position: sticky;
   top: 0;
   z-index: 100;
@@ -362,7 +362,7 @@ onLoad(options) {
 }
 .ac-nav-back {
   font-size: 36rpx;
-  color: #333;
+  color: $facade-text-strong;
   font-weight: bold;
 }
 .ac-nav-title {
@@ -380,11 +380,11 @@ onLoad(options) {
 }
 .ac-nav-search-icon {
   font-size: 32rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 .ac-nav-add {
   font-size: 40rpx;
-  color: #F97316;
+  color: $brand-primary;
   font-weight: 300;
   line-height: 1;
 }
@@ -394,21 +394,21 @@ onLoad(options) {
   display: flex;
   align-items: center;
   padding: 12rpx 28rpx 16rpx;
-  background: #fff;
-  border-bottom: 1rpx solid #f0f0f0;
+  background: $app-surface;
+  border-bottom: 1rpx solid $facade-fill;
 }
 .ac-search-input {
   flex: 1;
   height: 60rpx;
-  background: #f5f5f5;
+  background: $facade-fill-2;
   border-radius: 30rpx;
   padding: 0 28rpx;
   font-size: 26rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 .ac-search-cancel {
   font-size: 26rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-left: 16rpx;
   flex-shrink: 0;
 }
@@ -416,8 +416,8 @@ onLoad(options) {
 /* ---- Tab 药丸导航 ---- */
 .ac-tabs {
   padding: 16rpx 28rpx;
-  background: #fff;
-  border-bottom: 1rpx solid #f0f0f0;
+  background: $app-surface;
+  border-bottom: 1rpx solid $facade-fill;
   position: sticky;
   z-index: 10;
 }
@@ -433,10 +433,10 @@ onLoad(options) {
   padding: 0 28rpx;
   margin-right: 16rpx;
   font-size: 24rpx;
-  color: #666;
-  background: #f0f0f0;
+  color: $facade-text-body;
+  background: $facade-fill;
   border-radius: 26rpx;
-  border: 2rpx solid #f0f0f0;
+  border: 2rpx solid $facade-fill;
   flex-shrink: 0;
   transition: font-weight 0.2s;
 }
@@ -452,7 +452,7 @@ onLoad(options) {
 /* ---- 骨架屏 ---- */
 .ac-skeleton-wrap { padding: 24rpx 28rpx; }
 .sk-card {
-  background: #fff;
+  background: $app-surface;
   border-radius: 24rpx;
   overflow: hidden;
   margin-bottom: 24rpx;
@@ -460,14 +460,14 @@ onLoad(options) {
 }
 .sk-cover {
   height: 340rpx;
-  background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
+  background: linear-gradient(90deg, $facade-fill 25%, $facade-fill-4 50%, $facade-fill 75%);
   background-size: 200% 100%;
   animation: sk-shimmer 1.5s infinite;
 }
 .sk-body { padding: 24rpx; }
 .sk-row {
   height: 22rpx;
-  background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
+  background: linear-gradient(90deg, $facade-fill 25%, $facade-fill-4 50%, $facade-fill 75%);
   background-size: 200% 100%;
   border-radius: 6rpx;
   margin-bottom: 14rpx;
@@ -496,7 +496,7 @@ onLoad(options) {
 }
 .ac-empty-text {
   font-size: 28rpx;
-  color: #bbb;
+  color: $facade-text-ghost-light;
 }
 
 /* ---- 文章卡片(新版) ---- */
@@ -504,7 +504,7 @@ onLoad(options) {
   padding: 24rpx 28rpx;
 }
 .ac-card {
-  background: #fff;
+  background: $app-surface;
   border-radius: 24rpx;
   overflow: hidden;
   margin-bottom: 24rpx;
@@ -525,7 +525,7 @@ onLoad(options) {
   display: block;
   width: 100%;
   height: 340rpx;
-  background: #f0f0f0;
+  background: $facade-fill;
 }
 .ac-card-dimension-tags {
   position: absolute;
@@ -538,7 +538,7 @@ onLoad(options) {
   padding: 2rpx 14rpx;
   border-radius: 12rpx;
   font-size: 20rpx;
-  color: #fff;
+  color: $app-surface;
   font-weight: 500;
   line-height: 1.4;
 }
@@ -553,7 +553,7 @@ onLoad(options) {
 }
 .ac-card-category {
   font-size: 20rpx;
-  color: #F97316;
+  color: $brand-primary;
   background: rgba(249,115,22,0.08);
   padding: 2rpx 14rpx;
   border-radius: 8rpx;
@@ -579,7 +579,7 @@ onLoad(options) {
 .ac-card-summary {
   display: block;
   font-size: calc(24rpx * var(--font-scale, 1));
-  color: #999;
+  color: $facade-text-muted;
   line-height: 1.5;
   margin-bottom: 16rpx;
   overflow: hidden;
@@ -592,23 +592,23 @@ onLoad(options) {
   display: flex;
   align-items: center;
   font-size: 20rpx;
-  color: #bbb;
+  color: $facade-text-ghost-light;
 }
 .ac-card-author {
-  color: #888;
+  color: $facade-text-faint;
 }
 .ac-card-sep {
   margin: 0 10rpx;
-  color: #ddd;
+  color: $facade-border-soft;
 }
 .ac-card-date {
   flex: 1;
 }
 .ac-card-fav {
-  color: #ccc;
+  color: $facade-text-ghost;
 }
 .ac-card-readcount {
-  color: #ccc;
+  color: $facade-text-ghost;
 }
 
 /* ---- 加载更多 / 没有更多 ---- */
@@ -619,11 +619,11 @@ onLoad(options) {
 }
 .ac-loading-text {
   font-size: 24rpx;
-  color: #bbb;
+  color: $facade-text-ghost-light;
 }
 .ac-no-more-text {
   font-size: 24rpx;
-  color: #ddd;
+  color: $facade-border-soft;
 }
 .ac-bottom-gap {
   height: 140rpx;
@@ -636,8 +636,8 @@ onLoad(options) {
   bottom: 100rpx;
   width: 96rpx;
   height: 96rpx;
-  background: linear-gradient(135deg, #F97316, #EA580C);
-  color: #fff;
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-dark);
+  color: $app-surface;
   border-radius: 50%;
   display: flex;
   align-items: center;

+ 14 - 14
cfc-frontend/pages/article-center/my-posts.vue

@@ -18,7 +18,7 @@
             class="post-cover"
             :src="getImageUrl(item.coverImage)"
             mode="aspectFill"
-          />
+           lazy-load />
           <view class="post-body">
             <text class="post-title">{{ item.title }}</text>
             <view class="post-meta">
@@ -131,18 +131,18 @@ export default {
 </script>
 
 <style scoped lang="scss">
-.posts-container { min-height: 100vh; background: #f5f7fa; }
+.posts-container { min-height: 100vh; background: $app-bg; }
 .posts-list { height: 100vh; }
 .loading-wrap, .empty-wrap { display: flex; flex-direction: column; align-items: center; padding-top: 200rpx; }
-.loading-text { font-size: 26rpx; color: #999; }
+.loading-text { font-size: 26rpx; color: $facade-text-muted; }
 .empty-icon { font-size: 100rpx; margin-bottom: 24rpx; }
-.empty-text { font-size: 28rpx; color: #999; margin-bottom: 30rpx; }
-.write-btn { width: 240rpx; height: 72rpx; line-height: 72rpx; background: $brand-primary; color: #fff; font-size: 28rpx; border-radius: 36rpx; border: none; }
+.empty-text { font-size: 28rpx; color: $facade-text-muted; margin-bottom: 30rpx; }
+.write-btn { width: 240rpx; height: 72rpx; line-height: 72rpx; background: $brand-primary; color: $app-surface; font-size: 28rpx; border-radius: 36rpx; border: none; }
 .write-btn::after { border: none; }
 .post-list { padding: 20rpx 24rpx; }
 .post-card {
   display: flex;
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
   overflow: hidden;
   margin-bottom: 20rpx;
@@ -150,15 +150,15 @@ export default {
 }
 .post-cover { width: 200rpx; height: 160rpx; flex-shrink: 0; }
 .post-body { flex: 1; padding: 20rpx; display: flex; flex-direction: column; justify-content: center; }
-.post-title { font-size: 28rpx; font-weight: bold; color: #333; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 8rpx; }
+.post-title { font-size: 28rpx; font-weight: bold; color: $facade-text-strong; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 8rpx; }
 .post-meta { display: flex; align-items: center; font-size: 22rpx; }
-.post-date { color: #999; margin-right: 16rpx; }
+.post-date { color: $facade-text-muted; margin-right: 16rpx; }
 .post-status { padding: 2rpx 12rpx; border-radius: 8rpx; }
-.status-approved { color: #22c55e; background: rgba(34,197,94,0.1); }
-.status-pending { color: #f59e0b; background: rgba(245,158,11,0.1); }
-.status-rejected { color: #ef4444; background: rgba(239,68,68,0.1); }
-.status-draft { color: #999; background: rgba(153,153,153,0.1); }
-.post-reason { font-size: 20rpx; color: #ef4444; margin-top: 6rpx; display: block; }
+.status-approved { color: $status-success-2; background: rgba(34,197,94,0.1); }
+.status-pending { color: $dim-wealth; background: rgba(245,158,11,0.1); }
+.status-rejected { color: $status-danger; background: rgba(239,68,68,0.1); }
+.status-draft { color: $facade-text-muted; background: rgba(153,153,153,0.1); }
+.post-reason { font-size: 20rpx; color: $status-danger; margin-top: 6rpx; display: block; }
 .loading-more, .no-more { text-align: center; padding: 30rpx; }
-.no-more-text { font-size: 24rpx; color: #ccc; }
+.no-more-text { font-size: 24rpx; color: $facade-text-ghost; }
 </style>

+ 15 - 15
cfc-frontend/pages/assessment-plan/detail.vue

@@ -144,7 +144,7 @@ export default {
 <style scoped lang="scss">
 .detail-container {
   min-height: 100vh;
-  background: #F5F7FA;
+  background: $app-bg;
 }
 
 /* ===== 加载状态 ===== */
@@ -155,13 +155,13 @@ export default {
 }
 .loading-text {
   font-size: 28rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 /* ===== 详情卡片 ===== */
 .detail-card {
   margin: 30rpx;
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
   padding: 32rpx 28rpx;
   box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
@@ -176,7 +176,7 @@ export default {
 .detail-title {
   font-size: 34rpx;
   font-weight: 600;
-  color: #333;
+  color: $facade-text-strong;
   flex: 1;
   min-width: 0;
 }
@@ -186,17 +186,17 @@ export default {
   border-radius: 20rpx;
   margin-left: 12rpx;
 }
-.status-active { background: #E3F2FD; color: #1565C0; }
-.status-completed { background: #E8F5E9; color: #2E7D32; }
+.status-active { background: $mat-blue-tint; color: $mat-blue-strong; }
+.status-completed { background: $mat-green-tint; color: $mat-green-strong; }
 .status-cancelled { background: #ECEFF1; color: #78909C; }
-.status-generated { background: #FFF3E0; color: #E65100; }
-.status-reviewed { background: #E8F5E9; color: #2E7D32; }
-.status-rejected { background: #FDE8E8; color: #C62828; }
+.status-generated { background: $fortune-1-tint; color: $mat-orange-strong; }
+.status-reviewed { background: $mat-green-tint; color: $mat-green-strong; }
+.status-rejected { background: #FDE8E8; color: $status-danger-strong-2; }
 .status-unknown { background: #ECEFF1; color: #78909C; }
 
 /* ===== 元信息 ===== */
 .detail-meta {
-  border-top: 1rpx solid #F1F5F9;
+  border-top: 1rpx solid $facade-fill-slate;
   padding-top: 8rpx;
 }
 .meta-row {
@@ -208,14 +208,14 @@ export default {
 .meta-label {
   width: 160rpx;
   font-size: 26rpx;
-  color: #999;
+  color: $facade-text-muted;
   flex-shrink: 0;
   line-height: 1.5;
 }
 .meta-value {
   flex: 1;
   font-size: 26rpx;
-  color: #333;
+  color: $facade-text-strong;
   line-height: 1.5;
   word-break: break-all;
 }
@@ -242,7 +242,7 @@ export default {
 }
 .progress-text {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 /* ===== 空状态 ===== */
@@ -259,12 +259,12 @@ export default {
 .empty-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 12rpx;
 }
 .empty-desc {
   font-size: 26rpx;
-  color: #999;
+  color: $facade-text-muted;
   text-align: center;
   line-height: 1.5;
 }

+ 14 - 14
cfc-frontend/pages/assessment-plan/list.vue

@@ -111,7 +111,7 @@ export default {
 <style scoped lang="scss">
 .list-container {
   min-height: 100vh;
-  background: #F5F7FA;
+  background: $app-bg;
 }
 
 /* ===== 加载状态 ===== */
@@ -122,7 +122,7 @@ export default {
 }
 .loading-text {
   font-size: 28rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 /* ===== 方案列表 ===== */
@@ -130,7 +130,7 @@ export default {
   margin: 20rpx 30rpx;
 }
 .plan-card {
-  background: #fff;
+  background: $app-surface;
   border-radius: 16rpx;
   padding: 28rpx 24rpx;
   margin-bottom: 16rpx;
@@ -149,7 +149,7 @@ export default {
 .plan-name {
   font-size: 30rpx;
   font-weight: 600;
-  color: #333;
+  color: $facade-text-strong;
   flex: 1;
   min-width: 0;
 }
@@ -159,12 +159,12 @@ export default {
   border-radius: 20rpx;
   margin-left: 12rpx;
 }
-.status-active { background: #E3F2FD; color: #1565C0; }
-.status-completed { background: #E8F5E9; color: #2E7D32; }
+.status-active { background: $mat-blue-tint; color: $mat-blue-strong; }
+.status-completed { background: $mat-green-tint; color: $mat-green-strong; }
 .status-cancelled { background: #ECEFF1; color: #78909C; }
-.status-generated { background: #FFF3E0; color: #E65100; }
-.status-reviewed { background: #E8F5E9; color: #2E7D32; }
-.status-rejected { background: #FDE8E8; color: #C62828; }
+.status-generated { background: $fortune-1-tint; color: $mat-orange-strong; }
+.status-reviewed { background: $mat-green-tint; color: $mat-green-strong; }
+.status-rejected { background: #FDE8E8; color: $status-danger-strong-2; }
 .status-unknown { background: #ECEFF1; color: #78909C; }
 
 /* ===== 维度 ===== */
@@ -173,7 +173,7 @@ export default {
 }
 .plan-meta-item {
   font-size: 24rpx;
-  color: #888;
+  color: $facade-text-faint;
 }
 
 /* ===== 进度 ===== */
@@ -198,7 +198,7 @@ export default {
 }
 .progress-text {
   font-size: 22rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 /* ===== 箭头 ===== */
@@ -208,7 +208,7 @@ export default {
   top: 50%;
   transform: translateY(-50%);
   font-size: 36rpx;
-  color: #CCC;
+  color: $facade-text-ghost;
 }
 
 /* ===== 空状态 ===== */
@@ -225,12 +225,12 @@ export default {
 .empty-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 12rpx;
 }
 .empty-desc {
   font-size: 26rpx;
-  color: #999;
+  color: $facade-text-muted;
   text-align: center;
   margin-bottom: 40rpx;
   line-height: 1.5;

+ 27 - 27
cfc-frontend/pages/assessment/apply.vue

@@ -349,10 +349,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   padding: 30rpx;
-  background: #F8F8F8;
+  background: $facade-fill-page;
   min-height: 100vh;
 }
 
@@ -364,18 +364,18 @@ export default {
 .title {
   font-size: 48rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 20rpx;
 }
 
 .subtitle {
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 
 .section {
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 30rpx;
   margin-bottom: 30rpx;
@@ -384,7 +384,7 @@ export default {
 .section-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 20rpx;
 }
 
@@ -400,8 +400,8 @@ export default {
 .type-item,
 .child-item {
   position: relative;
-  background: #F8F8F8;
-  border: 2rpx solid #E0E0E0;
+  background: $facade-fill-page;
+  border: 2rpx solid $facade-hairline;
   border-radius: 10rpx;
   padding: 20rpx;
   min-width: 200rpx;
@@ -411,7 +411,7 @@ export default {
 .guide-item.selected,
 .type-item.selected,
 .child-item.selected {
-  border-color: #667eea;
+  border-color: $accent-indigo;
   background: #F0F4FF;
 }
 
@@ -433,19 +433,19 @@ export default {
 .child-name {
   font-size: 28rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 5rpx;
 }
 
 .guide-price {
   font-size: 24rpx;
-  color: #F97316;
+  color: $brand-primary;
   font-weight: bold;
 }
 
 .child-age {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .guide-check,
@@ -455,8 +455,8 @@ export default {
   right: 10rpx;
   width: 40rpx;
   height: 40rpx;
-  background: #667eea;
-  color: #fff;
+  background: $accent-indigo;
+  color: $app-surface;
   border-radius: 50%;
   display: flex;
   align-items: center;
@@ -473,22 +473,22 @@ export default {
 .type-name {
   font-size: 28rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 5rpx;
 }
 
 .type-desc {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .child-avatar {
   width: 60rpx;
   height: 60rpx;
   border-radius: 50%;
-  background: linear-gradient(135deg, #667eea, #764ba2);
-  color: #fff;
+  background: linear-gradient(135deg, $accent-indigo, $accent-indigo-deep);
+  color: $app-surface;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -507,20 +507,20 @@ export default {
   justify-content: space-between;
   align-items: center;
   height: 80rpx;
-  background: #F8F8F8;
+  background: $facade-fill-page;
   border-radius: 10rpx;
   padding: 0 20rpx;
   font-size: 28rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .picker-arrow {
   font-size: 40rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .price-info {
-  background: #F8F8F8;
+  background: $facade-fill-page;
   border-radius: 10rpx;
   padding: 20rpx;
 }
@@ -538,24 +538,24 @@ export default {
 
 .price-label {
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 
 .price-value {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .price-value.discount {
-  color: #F97316;
+  color: $brand-primary;
 }
 
 .btn-submit {
   width: 100%;
   height: 90rpx;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  color: #fff;
+  background: linear-gradient(135deg, $accent-indigo 0%, $accent-indigo-deep 100%);
+  color: $app-surface;
   border-radius: 45rpx;
   font-size: 32rpx;
   font-weight: bold;

+ 37 - 37
cfc-frontend/pages/assessment/execution.vue

@@ -223,10 +223,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   padding: 40rpx;
-  background: #f5f5f5;
+  background: $facade-fill-2;
   min-height: 100vh;
 }
 
@@ -237,20 +237,20 @@ export default {
 .title {
   font-size: 40rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 10rpx;
 }
 
 .subtitle {
   font-size: 28rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .loading, .empty {
   text-align: center;
   padding: 100rpx 0;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .status-card {
@@ -286,8 +286,8 @@ export default {
 .icon-check {
   width: 40rpx;
   height: 24rpx;
-  border-left: 6rpx solid #fff;
-  border-bottom: 6rpx solid #fff;
+  border-left: 6rpx solid $app-surface;
+  border-bottom: 6rpx solid $app-surface;
   transform: rotate(-45deg);
   margin-top: -6rpx;
   z-index: 1;
@@ -297,7 +297,7 @@ export default {
 .icon-alert {
   width: 6rpx;
   height: 36rpx;
-  background: #fff;
+  background: $app-surface;
   border-radius: 3rpx;
   position: relative;
   z-index: 1;
@@ -310,7 +310,7 @@ export default {
   transform: translateX(-50%);
   width: 10rpx;
   height: 10rpx;
-  background: #fff;
+  background: $app-surface;
   border-radius: 50%;
 }
 
@@ -325,7 +325,7 @@ export default {
 .icon-dots view {
   width: 12rpx;
   height: 12rpx;
-  background: #fff;
+  background: $app-surface;
   border-radius: 50%;
 }
 
@@ -334,7 +334,7 @@ export default {
 }
 
 .status-icon.pending .icon-circle {
-  background: linear-gradient(135deg, #ff9800, #f57c00);
+  background: linear-gradient(135deg, $mat-orange-base, #f57c00);
 }
 
 .status-icon.ready {
@@ -342,7 +342,7 @@ export default {
 }
 
 .status-icon.ready .icon-circle {
-  background: linear-gradient(135deg, #2196f3, #1976d2);
+  background: linear-gradient(135deg, $mat-blue-base, #1976d2);
 }
 
 .status-icon.completed {
@@ -350,13 +350,13 @@ export default {
 }
 
 .status-icon.completed .icon-circle {
-  background: linear-gradient(135deg, #4caf50, #388e3c);
+  background: linear-gradient(135deg, $mat-green, #388e3c);
 }
 
 .status-text {
   font-size: 36rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .timeline {
@@ -383,7 +383,7 @@ export default {
   top: 30rpx;
   bottom: -40rpx;
   width: 2rpx;
-  background: #e0e0e0;
+  background: $facade-hairline;
 }
 
 .timeline-item:last-child::before {
@@ -394,7 +394,7 @@ export default {
   width: 30rpx;
   height: 30rpx;
   border-radius: 50%;
-  background: #e0e0e0;
+  background: $facade-hairline;
   margin-right: 30rpx;
   flex-shrink: 0;
   z-index: 1;
@@ -406,14 +406,14 @@ export default {
 .dot-check {
   width: 12rpx;
   height: 8rpx;
-  border-left: 3rpx solid #fff;
-  border-bottom: 3rpx solid #fff;
+  border-left: 3rpx solid $app-surface;
+  border-bottom: 3rpx solid $app-surface;
   transform: rotate(-45deg);
   margin-top: -2rpx;
 }
 
 .timeline-item.active .timeline-dot {
-  background: #4caf50;
+  background: $mat-green;
 }
 
 /* 时间线头部(图标+标题) */
@@ -436,13 +436,13 @@ export default {
 
 /* 评估师图标 */
 .assessor-icon {
-  background: linear-gradient(135deg, #e3f2fd, #bbdefb);
+  background: linear-gradient(135deg, $mat-blue-tint, #bbdefb);
 }
 .assessor-icon::after {
   content: '';
   width: 16rpx;
   height: 16rpx;
-  border: 3rpx solid #2196f3;
+  border: 3rpx solid $mat-blue-base;
   border-radius: 50%;
   position: absolute;
   top: 8rpx;
@@ -452,7 +452,7 @@ export default {
   content: '';
   width: 20rpx;
   height: 3rpx;
-  background: #2196f3;
+  background: $mat-blue-base;
   position: absolute;
   bottom: 10rpx;
   left: 8rpx;
@@ -461,13 +461,13 @@ export default {
 
 /* 规划师图标 */
 .planner-icon {
-  background: linear-gradient(135deg, #e8f5e9, #c8e6c9);
+  background: linear-gradient(135deg, $mat-green-tint, #c8e6c9);
 }
 .planner-icon::after {
   content: '';
   width: 16rpx;
   height: 16rpx;
-  border: 3rpx solid #4caf50;
+  border: 3rpx solid $mat-green;
   border-radius: 50%;
   position: absolute;
   top: 8rpx;
@@ -477,7 +477,7 @@ export default {
   content: '';
   width: 20rpx;
   height: 3rpx;
-  background: #4caf50;
+  background: $mat-green;
   position: absolute;
   bottom: 10rpx;
   left: 8rpx;
@@ -486,13 +486,13 @@ export default {
 
 /* 确认图标 */
 .confirm-icon {
-  background: linear-gradient(135deg, #fff3e0, #ffe0b2);
+  background: linear-gradient(135deg, $fortune-1-tint, #ffe0b2);
 }
 .confirm-icon::after {
   content: '';
   width: 16rpx;
   height: 16rpx;
-  border: 3rpx solid #ff9800;
+  border: 3rpx solid $mat-orange-base;
   border-radius: 50%;
   position: absolute;
   top: 8rpx;
@@ -502,7 +502,7 @@ export default {
   content: '';
   width: 20rpx;
   height: 3rpx;
-  background: #ff9800;
+  background: $mat-orange-base;
   position: absolute;
   bottom: 10rpx;
   left: 8rpx;
@@ -516,21 +516,21 @@ export default {
 .timeline-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 10rpx;
 }
 
 .timeline-desc {
   font-size: 26rpx;
-  color: #666;
+  color: $facade-text-body;
   display: block;
   margin-bottom: 5rpx;
 }
 
 .timeline-time {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .notes-section {
@@ -543,7 +543,7 @@ export default {
 .section-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 20rpx;
 }
@@ -554,13 +554,13 @@ export default {
 
 .note-label {
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
   font-weight: bold;
 }
 
 .note-content {
   font-size: 28rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .actions {
@@ -583,14 +583,14 @@ export default {
 }
 
 .btn-secondary {
-  background: #ff9800;
+  background: $mat-orange-base;
   color: white;
   border: none;
 }
 
 .btn-outline {
   background: white;
-  color: #666;
-  border: 2rpx solid #ddd;
+  color: $facade-text-body;
+  border: 2rpx solid $facade-border-soft;
 }
 </style>

+ 34 - 34
cfc-frontend/pages/assessment/purchase.vue

@@ -353,10 +353,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   padding: 30rpx;
-  background: #F8F8F8;
+  background: $facade-fill-page;
   min-height: 100vh;
 }
 
@@ -368,18 +368,18 @@ export default {
 .title {
   font-size: 48rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 20rpx;
 }
 
 .subtitle {
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 
 .section {
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 30rpx;
   margin-bottom: 30rpx;
@@ -388,7 +388,7 @@ export default {
 .section-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 20rpx;
 }
 
@@ -403,14 +403,14 @@ export default {
   position: relative;
   display: flex;
   align-items: center;
-  background: #F8F8F8;
-  border: 2rpx solid #E0E0E0;
+  background: $facade-fill-page;
+  border: 2rpx solid $facade-hairline;
   border-radius: 10rpx;
   padding: 20rpx;
 }
 
 .guide-item.selected {
-  border-color: #667eea;
+  border-color: $accent-indigo;
   background: #F0F4FF;
 }
 
@@ -429,22 +429,22 @@ export default {
 .guide-name {
   font-size: 28rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 5rpx;
 }
 
 .guide-price {
   font-size: 24rpx;
-  color: #F97316;
+  color: $brand-primary;
   font-weight: bold;
 }
 
 .guide-check {
   width: 40rpx;
   height: 40rpx;
-  background: #667eea;
-  color: #fff;
+  background: $accent-indigo;
+  color: $app-surface;
   border-radius: 50%;
   display: flex;
   align-items: center;
@@ -454,14 +454,14 @@ export default {
 
 .package-item {
   position: relative;
-  background: #F8F8F8;
-  border: 2rpx solid #E0E0E0;
+  background: $facade-fill-page;
+  border: 2rpx solid $facade-hairline;
   border-radius: 10rpx;
   padding: 20rpx;
 }
 
 .package-item.selected {
-  border-color: #667eea;
+  border-color: $accent-indigo;
   background: #F0F4FF;
 }
 
@@ -475,7 +475,7 @@ export default {
 .package-name {
   font-size: 30rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .package-price {
@@ -485,20 +485,20 @@ export default {
 .price {
   font-size: 32rpx;
   font-weight: bold;
-  color: #F97316;
+  color: $brand-primary;
   display: block;
 }
 
 .original-price {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
   text-decoration: line-through;
   margin-left: 10rpx;
 }
 
 .package-desc {
   font-size: 26rpx;
-  color: #666;
+  color: $facade-text-body;
   margin-bottom: 15rpx;
 }
 
@@ -511,20 +511,20 @@ export default {
 .feature-item {
   display: flex;
   align-items: center;
-  background: #E8F5E9;
+  background: $mat-green-tint;
   padding: 8rpx 12rpx;
   border-radius: 20rpx;
 }
 
 .feature-icon {
-  color: #4CAF50;
+  color: $mat-green;
   font-size: 24rpx;
   margin-right: 8rpx;
 }
 
 .feature-text {
   font-size: 24rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .package-check {
@@ -533,8 +533,8 @@ export default {
   right: 20rpx;
   width: 40rpx;
   height: 40rpx;
-  background: #667eea;
-  color: #fff;
+  background: $accent-indigo;
+  color: $app-surface;
   border-radius: 50%;
   display: flex;
   align-items: center;
@@ -543,7 +543,7 @@ export default {
 }
 
 .price-breakdown {
-  background: #F8F8F8;
+  background: $facade-fill-page;
   border-radius: 10rpx;
   padding: 20rpx;
 }
@@ -561,35 +561,35 @@ export default {
 
 .price-label {
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 
 .price-value {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .price-value.discount {
-  color: #F97316;
+  color: $brand-primary;
 }
 
 .price-row.total .price-label {
   font-size: 30rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .price-row.total .price-value {
   font-size: 36rpx;
-  color: #F97316;
+  color: $brand-primary;
 }
 
 .btn-submit {
   width: 100%;
   height: 90rpx;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  color: #fff;
+  background: linear-gradient(135deg, $accent-indigo 0%, $accent-indigo-deep 100%);
+  color: $app-surface;
   border-radius: 45rpx;
   font-size: 32rpx;
   font-weight: bold;
@@ -597,6 +597,6 @@ export default {
 }
 
 .btn-submit[disabled] {
-  background: #CCCCCC;
+  background: $facade-text-ghost;
 }
 </style>

+ 22 - 22
cfc-frontend/pages/assessment/quota.vue

@@ -97,10 +97,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   padding: 30rpx;
-  background: #F8F8F8;
+  background: $facade-fill-page;
   min-height: 100vh;
 }
 .header {
@@ -110,13 +110,13 @@ export default {
 .title {
   font-size: 44rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 16rpx;
 }
 .subtitle {
   font-size: 26rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .quota-list {
   display: flex;
@@ -124,7 +124,7 @@ export default {
   gap: 24rpx;
 }
 .quota-card {
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 28rpx;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
@@ -138,7 +138,7 @@ export default {
 .product-name {
   font-size: 30rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 .status-tag {
   font-size: 22rpx;
@@ -146,12 +146,12 @@ export default {
   border-radius: 20rpx;
 }
 .status-tag.active {
-  background: #E8F5E9;
-  color: #2E7D32;
+  background: $mat-green-tint;
+  color: $mat-green-strong;
 }
 .status-tag.used {
-  background: #F5F5F5;
-  color: #999;
+  background: $facade-fill-2;
+  color: $facade-text-muted;
 }
 .progress-section {
   display: flex;
@@ -162,22 +162,22 @@ export default {
 .progress-bar-bg {
   flex: 1;
   height: 12rpx;
-  background: #F0F0F0;
+  background: $facade-fill;
   border-radius: 6rpx;
   overflow: hidden;
 }
 .progress-bar-fill {
   height: 100%;
-  background: linear-gradient(90deg, #667eea, #764ba2);
+  background: linear-gradient(90deg, $accent-indigo, $accent-indigo-deep);
   border-radius: 6rpx;
 }
 .progress-text {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
   white-space: nowrap;
 }
 .card-info {
-  border-top: 1rpx solid #F0F0F0;
+  border-top: 1rpx solid $facade-fill;
   padding-top: 16rpx;
 }
 .info-row {
@@ -187,14 +187,14 @@ export default {
 }
 .info-label {
   font-size: 26rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 .info-value {
   font-size: 26rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 .info-value.highlight {
-  color: #667eea;
+  color: $accent-indigo;
   font-weight: bold;
   font-size: 32rpx;
 }
@@ -208,8 +208,8 @@ export default {
   width: 100%;
   height: 76rpx;
   line-height: 76rpx;
-  background: linear-gradient(135deg, #667eea, #764ba2);
-  color: #fff;
+  background: linear-gradient(135deg, $accent-indigo, $accent-indigo-deep);
+  color: $app-surface;
   border-radius: 38rpx;
   font-size: 28rpx;
   text-align: center;
@@ -226,15 +226,15 @@ export default {
 }
 .empty-text {
   font-size: 28rpx;
-  color: #999;
+  color: $facade-text-muted;
   margin-bottom: 40rpx;
 }
 .btn-buy {
   width: 300rpx;
   height: 80rpx;
   line-height: 80rpx;
-  background: linear-gradient(135deg, #667eea, #764ba2);
-  color: #fff;
+  background: linear-gradient(135deg, $accent-indigo, $accent-indigo-deep);
+  color: $app-surface;
   border-radius: 40rpx;
   font-size: 28rpx;
   text-align: center;

+ 17 - 17
cfc-frontend/pages/assessment/rating.vue

@@ -200,10 +200,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   padding: 40rpx;
-  background: #f5f5f5;
+  background: $facade-fill-2;
   min-height: 100vh;
 }
 
@@ -214,20 +214,20 @@ export default {
 .title {
   font-size: 40rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 10rpx;
 }
 
 .subtitle {
   font-size: 28rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .loading, .empty {
   text-align: center;
   padding: 100rpx 0;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .rating-section {
@@ -255,25 +255,25 @@ export default {
 }
 
 .assessor-section-icon {
-  background: linear-gradient(135deg, #e3f2fd, #bbdefb);
+  background: linear-gradient(135deg, $mat-blue-tint, #bbdefb);
 }
 .assessor-section-icon::after {
   content: '';
   width: 20rpx;
   height: 20rpx;
-  border: 4rpx solid #2196f3;
+  border: 4rpx solid $mat-blue-base;
   border-radius: 50%;
   position: absolute;
 }
 
 .planner-section-icon {
-  background: linear-gradient(135deg, #e8f5e9, #c8e6c9);
+  background: linear-gradient(135deg, $mat-green-tint, #c8e6c9);
 }
 .planner-section-icon::after {
   content: '';
   width: 20rpx;
   height: 20rpx;
-  border: 4rpx solid #4caf50;
+  border: 4rpx solid $mat-green;
   border-radius: 50%;
   position: absolute;
 }
@@ -281,7 +281,7 @@ export default {
 .section-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .rating-stars {
@@ -292,7 +292,7 @@ export default {
 
 .star-label {
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
   margin-right: 20rpx;
 }
 
@@ -303,7 +303,7 @@ export default {
 
 .star {
   font-size: 60rpx;
-  color: #e0e0e0;
+  color: $facade-hairline;
   transition: color 0.2s ease, text-shadow 0.2s ease;
   line-height: 1;
 }
@@ -319,7 +319,7 @@ export default {
 
 .rating-text {
   font-size: 24rpx;
-  color: #ff9800;
+  color: $mat-orange-base;
   margin-left: 16rpx;
   font-weight: bold;
 }
@@ -332,7 +332,7 @@ export default {
   width: 100%;
   min-height: 200rpx;
   padding: 20rpx;
-  border: 2rpx solid #e0e0e0;
+  border: 2rpx solid $facade-hairline;
   border-radius: 10rpx;
   font-size: 28rpx;
   box-sizing: border-box;
@@ -359,12 +359,12 @@ export default {
 }
 
 .btn-primary:disabled {
-  background: #ccc;
+  background: $facade-text-ghost;
 }
 
 .btn-outline {
   background: white;
-  color: #666;
-  border: 2rpx solid #ddd;
+  color: $facade-text-body;
+  border: 2rpx solid $facade-border-soft;
 }
 </style>

+ 30 - 30
cfc-frontend/pages/assessment/results.vue

@@ -229,10 +229,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   padding: 30rpx;
-  background: #F8F8F8;
+  background: $facade-fill-page;
   min-height: 100vh;
 }
 
@@ -244,19 +244,19 @@ export default {
 .title {
   font-size: 48rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 20rpx;
 }
 
 .subtitle {
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 
 .tabs {
   display: flex;
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 10rpx;
   margin-bottom: 30rpx;
@@ -268,16 +268,16 @@ export default {
   padding: 20rpx;
   border-radius: 10rpx;
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 
 .tab-item.active {
-  background: #667eea;
-  color: #fff;
+  background: $accent-indigo;
+  color: $app-surface;
 }
 
 .section {
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 30rpx;
   margin-bottom: 30rpx;
@@ -286,7 +286,7 @@ export default {
 .section-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 20rpx;
 }
 
@@ -297,7 +297,7 @@ export default {
 }
 
 .report-item {
-  background: #F8F8F8;
+  background: $facade-fill-page;
   border-radius: 10rpx;
   padding: 20rpx;
 }
@@ -312,17 +312,17 @@ export default {
 .report-child {
   font-size: 30rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .report-date {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 
 .report-guide {
   font-size: 24rpx;
-  color: #667eea;
+  color: $accent-indigo;
 }
 
 .report-scores {
@@ -332,14 +332,14 @@ export default {
 }
 
 .score-item {
-  background: #E8F5E9;
+  background: $mat-green-tint;
   padding: 10rpx 15rpx;
   border-radius: 10rpx;
 }
 
 .score-label {
   font-size: 24rpx;
-  color: #666;
+  color: $facade-text-body;
   display: block;
   margin-bottom: 5rpx;
 }
@@ -347,12 +347,12 @@ export default {
 .score-value {
   font-size: 28rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .report-preview {
   font-size: 26rpx;
-  color: #999;
+  color: $facade-text-muted;
   line-height: 1.5;
 }
 
@@ -369,14 +369,14 @@ export default {
 
 .empty-text {
   font-size: 28rpx;
-  color: #999;
+  color: $facade-text-muted;
   display: block;
   margin-bottom: 10rpx;
 }
 
 .empty-hint {
   font-size: 24rpx;
-  color: #CCC;
+  color: $facade-text-ghost;
 }
 
 .report-modal {
@@ -393,7 +393,7 @@ export default {
 }
 
 .modal-content {
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 40rpx;
   width: 90%;
@@ -411,12 +411,12 @@ export default {
 .modal-title {
   font-size: 32rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 
 .modal-close {
   font-size: 40rpx;
-  color: #999;
+  color: $facade-text-muted;
   padding: 10rpx;
 }
 
@@ -431,7 +431,7 @@ export default {
 .detail-title {
   font-size: 28rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 15rpx;
 }
 
@@ -443,24 +443,24 @@ export default {
 
 .detail-label {
   font-size: 26rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 
 .detail-value {
   font-size: 26rpx;
-  color: #333;
+  color: $facade-text-strong;
   font-weight: bold;
 }
 
 .detail-content {
-  background: #F8F8F8;
+  background: $facade-fill-page;
   padding: 20rpx;
   border-radius: 10rpx;
 }
 
 .content-text {
   font-size: 26rpx;
-  color: #333;
+  color: $facade-text-strong;
   line-height: 1.6;
 }
 
@@ -472,8 +472,8 @@ export default {
 .btn-close {
   flex: 1;
   height: 80rpx;
-  background: #F0F0F0;
-  color: #333;
+  background: $facade-fill;
+  color: $facade-text-strong;
   border-radius: 40rpx;
   font-size: 28rpx;
   font-weight: bold;

+ 18 - 18
cfc-frontend/pages/assessment/upload-report.vue

@@ -110,10 +110,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   padding: 30rpx;
-  background: #F8F8F8;
+  background: $facade-fill-page;
   min-height: 100vh;
 }
 .header {
@@ -123,16 +123,16 @@ export default {
 .title {
   font-size: 44rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 16rpx;
 }
 .subtitle {
   font-size: 26rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .section {
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 30rpx;
   margin-bottom: 24rpx;
@@ -140,7 +140,7 @@ export default {
 .section-title {
   font-size: 30rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
   margin-bottom: 20rpx;
 }
 .child-selector {
@@ -150,20 +150,20 @@ export default {
 }
 .child-item {
   position: relative;
-  background: #F8F8F8;
-  border: 2rpx solid #E0E0E0;
+  background: $facade-fill-page;
+  border: 2rpx solid $facade-hairline;
   border-radius: 10rpx;
   padding: 20rpx;
   min-width: 160rpx;
   text-align: center;
 }
 .child-item.selected {
-  border-color: #667eea;
+  border-color: $accent-indigo;
   background: #F0F4FF;
 }
 .child-name {
   font-size: 28rpx;
-  color: #333;
+  color: $facade-text-strong;
 }
 .child-check {
   position: absolute;
@@ -171,8 +171,8 @@ export default {
   right: -8rpx;
   width: 36rpx;
   height: 36rpx;
-  background: #667eea;
-  color: #fff;
+  background: $accent-indigo;
+  color: $app-surface;
   border-radius: 50%;
   line-height: 36rpx;
   text-align: center;
@@ -184,8 +184,8 @@ export default {
   justify-content: center;
   gap: 16rpx;
   height: 160rpx;
-  background: #F8F8F8;
-  border: 2rpx dashed #CCC;
+  background: $facade-fill-page;
+  border: 2rpx dashed $facade-text-ghost;
   border-radius: 12rpx;
 }
 .upload-icon {
@@ -193,12 +193,12 @@ export default {
 }
 .upload-text {
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
 }
 .textarea {
   width: 100%;
   height: 160rpx;
-  background: #F8F8F8;
+  background: $facade-fill-page;
   border-radius: 10rpx;
   padding: 20rpx;
   font-size: 28rpx;
@@ -208,8 +208,8 @@ export default {
   width: 100%;
   height: 88rpx;
   line-height: 88rpx;
-  background: linear-gradient(135deg, #667eea, #764ba2);
-  color: #fff;
+  background: linear-gradient(135deg, $accent-indigo, $accent-indigo-deep);
+  color: $app-surface;
   border-radius: 44rpx;
   font-size: 30rpx;
   margin-top: 30rpx;

+ 14 - 14
cfc-frontend/pages/butler/apply.vue

@@ -107,10 +107,10 @@ export default {
 }
 </script>
 
-<style>
+<style lang="scss">
 .container {
   padding: 30rpx;
-  background: #f8f8f8;
+  background: $facade-fill-page;
   min-height: 100vh;
 }
 .header {
@@ -120,16 +120,16 @@ export default {
 .title {
   font-size: 40rpx;
   font-weight: bold;
-  color: #333;
+  color: $facade-text-strong;
 }
 .subtitle {
   font-size: 28rpx;
-  color: #666;
+  color: $facade-text-body;
   margin-top: 16rpx;
   display: block;
 }
 .form {
-  background: #fff;
+  background: $app-surface;
   border-radius: 20rpx;
   padding: 40rpx;
 }
@@ -138,7 +138,7 @@ export default {
 }
 .label {
   font-size: 28rpx;
-  color: #333;
+  color: $facade-text-strong;
   display: block;
   margin-bottom: 20rpx;
   font-weight: bold;
@@ -146,7 +146,7 @@ export default {
 .input {
   width: 100%;
   height: 80rpx;
-  border: 1rpx solid #e0e0e0;
+  border: 1rpx solid $facade-hairline;
   border-radius: 10rpx;
   padding: 0 20rpx;
   font-size: 28rpx;
@@ -155,7 +155,7 @@ export default {
 .picker {
   width: 100%;
   height: 80rpx;
-  border: 1rpx solid #e0e0e0;
+  border: 1rpx solid $facade-hairline;
   border-radius: 10rpx;
   padding: 0 20rpx;
   font-size: 28rpx;
@@ -165,16 +165,16 @@ export default {
   box-sizing: border-box;
 }
 .placeholder {
-  color: #999;
+  color: $facade-text-muted;
 }
 .arrow {
   font-size: 32rpx;
-  color: #999;
+  color: $facade-text-muted;
 }
 .textarea {
   width: 100%;
   height: 200rpx;
-  border: 1rpx solid #e0e0e0;
+  border: 1rpx solid $facade-hairline;
   border-radius: 10rpx;
   padding: 20rpx;
   font-size: 28rpx;
@@ -182,7 +182,7 @@ export default {
 }
 .counter {
   font-size: 24rpx;
-  color: #999;
+  color: $facade-text-muted;
   text-align: right;
   display: block;
   margin-top: 10rpx;
@@ -190,8 +190,8 @@ export default {
 .btn-submit {
   width: 100%;
   height: 90rpx;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  color: #fff;
+  background: linear-gradient(135deg, $accent-indigo 0%, $accent-indigo-deep 100%);
+  color: $app-surface;
   border-radius: 45rpx;
   font-size: 32rpx;
   font-weight: bold;

+ 16 - 16
cfc-frontend/pages/butler/select.vue

@@ -140,29 +140,29 @@ export default {
 </script>
 
 <style scoped lang="scss">
-.select-page { min-height: 100vh; background: #f5fafe; padding: 24rpx; box-sizing: border-box; display: flex; flex-direction: column; }
-.status-card { background: #fff; border-radius: 20rpx; padding: 32rpx; margin-bottom: 24rpx; }
-.current-label { font-size: 24rpx; color: #999; margin-bottom: 16rpx; }
+.select-page { min-height: 100vh; background: $facade-fill-blue-ghost; padding: 24rpx; box-sizing: border-box; display: flex; flex-direction: column; }
+.status-card { background: $app-surface; border-radius: 20rpx; padding: 32rpx; margin-bottom: 24rpx; }
+.current-label { font-size: 24rpx; color: $facade-text-muted; margin-bottom: 16rpx; }
 .current-row { display: flex; align-items: center; }
 .current-avatar { width: 96rpx; height: 96rpx; border-radius: 50%; margin-right: 20rpx; }
 .current-info { display: flex; flex-direction: column; }
-.current-name { font-size: 34rpx; font-weight: bold; color: #333; }
-.current-date { font-size: 22rpx; color: #999; margin-top: 6rpx; }
-.current-desc { font-size: 26rpx; color: #666; margin-top: 20rpx; line-height: 1.6; }
-.btn-change { margin-top: 24rpx; background: $brand-primary; color: #fff; font-size: 28rpx; border-radius: 40rpx; }
-.upgrade-banner { display: flex; justify-content: space-between; align-items: center; background: #fff7ed; border: 1rpx solid #ffb56b; border-radius: 16rpx; padding: 24rpx; margin-bottom: 24rpx; }
+.current-name { font-size: 34rpx; font-weight: bold; color: $facade-text-strong; }
+.current-date { font-size: 22rpx; color: $facade-text-muted; margin-top: 6rpx; }
+.current-desc { font-size: 26rpx; color: $facade-text-body; margin-top: 20rpx; line-height: 1.6; }
+.btn-change { margin-top: 24rpx; background: $brand-primary; color: $app-surface; font-size: 28rpx; border-radius: 40rpx; }
+.upgrade-banner { display: flex; justify-content: space-between; align-items: center; background: $uni-primary-pale; border: 1rpx solid #ffb56b; border-radius: 16rpx; padding: 24rpx; margin-bottom: 24rpx; }
 .upgrade-text { font-size: 26rpx; color: #d4770a; flex: 1; }
 .upgrade-arrow { font-size: 36rpx; color: #d4770a; margin-left: 12rpx; }
-.section-title { font-size: 30rpx; font-weight: bold; color: #333; margin: 8rpx 0 20rpx; }
-.butler-card { display: flex; align-items: center; background: #fff; border-radius: 20rpx; padding: 28rpx; margin-bottom: 20rpx; }
+.section-title { font-size: 30rpx; font-weight: bold; color: $facade-text-strong; margin: 8rpx 0 20rpx; }
+.butler-card { display: flex; align-items: center; background: $app-surface; border-radius: 20rpx; padding: 28rpx; margin-bottom: 20rpx; }
 .butler-card.active { border: 2rpx solid $brand-primary; }
 .card-avatar { width: 88rpx; height: 88rpx; border-radius: 50%; margin-right: 20rpx; flex-shrink: 0; }
 .card-body { flex: 1; min-width: 0; }
 .card-top { display: flex; align-items: center; justify-content: space-between; }
-.card-name { font-size: 30rpx; font-weight: bold; color: #333; }
-.card-slots { font-size: 22rpx; color: #10b981; }
-.card-desc { display: block; font-size: 24rpx; color: #888; margin-top: 8rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.btn-select { width: 120rpx; height: 60rpx; line-height: 60rpx; padding: 0; font-size: 26rpx; background: $brand-primary; color: #fff; border-radius: 30rpx; flex-shrink: 0; margin-left: 16rpx; }
-.btn-select[disabled] { background: #ccc; color: #fff; }
-.empty-tip { text-align: center; color: #999; font-size: 26rpx; padding: 60rpx 0; }
+.card-name { font-size: 30rpx; font-weight: bold; color: $facade-text-strong; }
+.card-slots { font-size: 22rpx; color: $status-success; }
+.card-desc { display: block; font-size: 24rpx; color: $facade-text-faint; margin-top: 8rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.btn-select { width: 120rpx; height: 60rpx; line-height: 60rpx; padding: 0; font-size: 26rpx; background: $brand-primary; color: $app-surface; border-radius: 30rpx; flex-shrink: 0; margin-left: 16rpx; }
+.btn-select[disabled] { background: $facade-text-ghost; color: $app-surface; }
+.empty-tip { text-align: center; color: $facade-text-muted; font-size: 26rpx; padding: 60rpx 0; }
 </style>

+ 10 - 10
cfc-frontend/pages/child/rewards.vue

@@ -135,22 +135,22 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container { padding: 30rpx; padding-bottom: 120rpx; }
-.exit-switch { background: #f5f5f5; padding: 20rpx; border-radius: 10rpx; margin-bottom: 20rpx; text-align: center; color: #666; font-size: 28rpx; }
-.reward-item { background: #fff; border-radius: 16rpx; padding: 30rpx; margin-bottom: 20rpx; }
-.reward-title { font-size: 30rpx; color: #333; margin-bottom: 15rpx; }
+.exit-switch { background: $facade-fill-2; padding: 20rpx; border-radius: 10rpx; margin-bottom: 20rpx; text-align: center; color: $facade-text-body; font-size: 28rpx; }
+.reward-item { background: $app-surface; border-radius: 16rpx; padding: 30rpx; margin-bottom: 20rpx; }
+.reward-title { font-size: 30rpx; color: $facade-text-strong; margin-bottom: 15rpx; }
 .reward-progress { width: 100%; }
-.progress-text { font-size: 22rpx; color: #999; display: block; margin-top: 8rpx; }
+.progress-text { font-size: 22rpx; color: $facade-text-muted; display: block; margin-top: 8rpx; }
 .add-btn { position: fixed; bottom: 30rpx; left: 30rpx; right: 30rpx; }
-.empty { text-align: center; color: #999; padding: 100rpx; }
+.empty { text-align: center; color: $facade-text-muted; padding: 100rpx; }
 .modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; }
-.modal { background: #fff; border-radius: 20rpx; padding: 40rpx; width: 80%; }
+.modal { background: $app-surface; border-radius: 20rpx; padding: 40rpx; width: 80%; }
 .modal-title { font-size: 32rpx; font-weight: bold; margin-bottom: 30rpx; text-align: center; }
 .form-item { margin-bottom: 20rpx; }
-.form-label { font-size: 28rpx; color: #333; margin-bottom: 10rpx; }
-.required { color: #F97316; }
-.form-tip { font-size: 24rpx; color: #999; padding: 20rpx; background: #f5f5f5; border-radius: 10rpx; margin-bottom: 20rpx; }
+.form-label { font-size: 28rpx; color: $facade-text-strong; margin-bottom: 10rpx; }
+.required { color: $brand-primary; }
+.form-tip { font-size: 24rpx; color: $facade-text-muted; padding: 20rpx; background: $facade-fill-2; border-radius: 10rpx; margin-bottom: 20rpx; }
 .modal-btns { display: flex; gap: 20rpx; margin-top: 30rpx; }
 .modal-btns button { flex: 1; }
 </style>

+ 24 - 24
cfc-frontend/pages/child/tasks.vue

@@ -376,13 +376,13 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 /* =============================================
    页面基础
    ============================================= */
 .page-bg {
   min-height: 100vh;
-  background: var(--bg, #F5F9FC);
+  background: var(--bg, $uni-bg-color);
   padding-bottom: 40rpx;
 }
 
@@ -396,8 +396,8 @@ export default {
   gap: 12rpx;
   padding: 24rpx;
   margin: 24rpx 32rpx 0;
-  background: var(--surface, #FFFFFF);
-  border: 2rpx solid var(--border, #CBD5E1);
+  background: var(--surface, $app-surface);
+  border: 2rpx solid var(--border, $uni-border-color);
   border-radius: var(--radius-lg, 32rpx);
   box-shadow: var(--shadow-sm);
 }
@@ -413,7 +413,7 @@ export default {
 
 .exit-switch__text {
   font-size: 28rpx;
-  color: var(--text-secondary, #64748B);
+  color: var(--text-secondary, $uni-text-color-secondary);
   font-weight: var(--font-weight-medium, 500);
 }
 
@@ -426,7 +426,7 @@ export default {
   margin: 24rpx 32rpx 0;
   padding: 8rpx;
   border-radius: var(--radius-xl, 40rpx);
-  background: var(--surface, #FFFFFF);
+  background: var(--surface, $app-surface);
   position: relative;
 }
 
@@ -448,7 +448,7 @@ export default {
 }
 
 .tab-item--active {
-  background: linear-gradient(145deg, var(--color-primary-light, #FB923C), var(--color-primary, #F97316));
+  background: linear-gradient(145deg, var(--color-primary-light, $brand-primary-light), var(--color-primary, $brand-primary));
   box-shadow: inset -2rpx -2rpx 8rpx rgba(255, 255, 255, 0.3),
     4rpx 4rpx 12rpx rgba(249,115,22,0.2);
 }
@@ -456,12 +456,12 @@ export default {
 .tab-item__label {
   font-size: 26rpx;
   font-weight: var(--font-weight-medium, 500);
-  color: var(--text-secondary, #64748B);
+  color: var(--text-secondary, $uni-text-color-secondary);
   transition: color var(--transition-fast, 0.15s ease);
 }
 
 .tab-item--active .tab-item__label {
-  color: var(--text-color-inverse, #FFFFFF);
+  color: var(--text-color-inverse, $app-surface);
   font-weight: var(--font-weight-bold, 600);
 }
 
@@ -474,7 +474,7 @@ export default {
   padding: 0 8rpx;
   border-radius: 999rpx;
   background: rgba(249,115,22,0.12);
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, $brand-primary);
   font-size: 20rpx;
   font-weight: var(--font-weight-bold, 600);
   line-height: 1;
@@ -482,7 +482,7 @@ export default {
 
 .tab-item--active .tab-item__count {
   background: rgba(255, 255, 255, 0.25);
-  color: #FFFFFF;
+  color: $app-surface;
 }
 
 /* =============================================
@@ -510,16 +510,16 @@ export default {
 /* --- 已完成状态 --- */
 .task-card--completed {
   opacity: 0.7;
-  border-color: var(--color-success, #22C55E);
-  background: linear-gradient(145deg, #F0FDF4, #ECFDF5);
+  border-color: var(--color-success, $status-success-2);
+  background: linear-gradient(145deg, $status-success-tint-2, #ECFDF5);
   box-shadow: inset -2rpx -2rpx 6rpx rgba(34, 197, 94, 0.05),
     3rpx 3rpx 10rpx rgba(34, 197, 94, 0.08);
 }
 
 /* --- 超时状态 --- */
 .task-card--overdue {
-  border-color: var(--color-error, #EF4444);
-  background: linear-gradient(145deg, #FEF2F2, #FEE2E2);
+  border-color: var(--color-error, $status-danger);
+  background: linear-gradient(145deg, #FEF2F2, $status-danger-soft);
   box-shadow: inset -2rpx -2rpx 6rpx rgba(239, 68, 68, 0.05),
     3rpx 3rpx 10rpx rgba(239, 68, 68, 0.08);
 }
@@ -561,16 +561,16 @@ export default {
   width: 88rpx;
   height: 88rpx;
   border-radius: var(--radius-md, 20rpx);
-  background: linear-gradient(145deg, #F5F9FC, #E2E8F0);
+  background: linear-gradient(145deg, $uni-bg-color, $uni-border-color-light);
   display: flex;
   align-items: center;
   justify-content: center;
   flex-shrink: 0;
-  border: 2rpx solid var(--border-light, #E2E8F0);
+  border: 2rpx solid var(--border-light, $uni-border-color-light);
 }
 
 .task-card--completed .task-icon {
-  background: linear-gradient(145deg, #F0FDF4, #DCFCE7);
+  background: linear-gradient(145deg, $status-success-tint-2, $status-success-soft-2);
   border-color: #BBF7D0;
 }
 
@@ -595,7 +595,7 @@ export default {
 .task-title {
   font-size: 32rpx;
   font-weight: var(--font-weight-bold, 600);
-  color: var(--text, #1E293B);
+  color: var(--text, $uni-text-color);
   line-height: 1.4;
   margin-bottom: 10rpx;
   overflow: hidden;
@@ -605,7 +605,7 @@ export default {
 
 .task-card--completed .task-title {
   text-decoration: line-through;
-  color: var(--text-secondary, #64748B);
+  color: var(--text-secondary, $uni-text-color-secondary);
 }
 
 .task-meta {
@@ -623,12 +623,12 @@ export default {
 
 .task-time {
   font-size: 22rpx;
-  color: var(--text-secondary, #64748B);
+  color: var(--text-secondary, $uni-text-color-secondary);
   line-height: 1.4;
 }
 
 .task-time--overdue {
-  color: var(--color-error, #EF4444);
+  color: var(--color-error, $status-danger);
   font-weight: var(--font-weight-medium, 500);
 }
 
@@ -654,13 +654,13 @@ export default {
 
 .status-badge--completed {
   background: rgba(34, 197, 94, 0.12);
-  color: var(--color-success, #22C55E);
+  color: var(--color-success, $status-success-2);
   border: 2rpx solid rgba(34, 197, 94, 0.2);
 }
 
 .status-badge--overdue {
   background: rgba(239, 68, 68, 0.12);
-  color: var(--color-error, #EF4444);
+  color: var(--color-error, $status-danger);
   border: 2rpx solid rgba(239, 68, 68, 0.2);
 }
 

+ 2 - 2
cfc-frontend/pages/cognitive-hall.vue

@@ -159,8 +159,8 @@ export default {
 <style scoped lang="scss">
 // ===== 页面级局部调色板(单页专有色阶;同值才映射全局令牌,约定见 uni.scss §14) =====
 $c-text-ghost-3: #AAA;
-$c-border-hair:  #EEE;      // 发丝分隔线
-$c-ink-night:    #1A1A2E;   // 深夜蓝黑(本页深色文字)
+$c-border-hair:  $facade-hairline-2;      // 发丝分隔线
+$c-ink-night:    $app-ink-night;   // 深夜蓝黑(本页深色文字)
 
 .page {
   min-height: 100vh;

+ 14 - 14
cfc-frontend/pages/cognitive-test/language-test.vue

@@ -242,10 +242,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .test-container {
   min-height: 100vh;
-  background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
+  background: linear-gradient(135deg, $app-ink-night 0%, #16213e 100%);
   padding: 60rpx 40rpx;
   box-sizing: border-box;
 }
@@ -265,7 +265,7 @@ export default {
 .test-title {
   font-size: 48rpx;
   font-weight: bold;
-  color: #fff;
+  color: $app-surface;
   margin-bottom: 32rpx;
 }
 
@@ -285,8 +285,8 @@ export default {
 }
 
 .btn-primary {
-  background: linear-gradient(135deg, #F59E0B, #FBBF24);
-  color: #1a1a2e;
+  background: linear-gradient(135deg, $dim-wealth, #FBBF24);
+  color: $app-ink-night;
   font-size: 34rpx;
   font-weight: bold;
   width: 480rpx;
@@ -342,7 +342,7 @@ export default {
 
 .timer-fill {
   height: 100%;
-  background: linear-gradient(90deg, #F59E0B, #FBBF24);
+  background: linear-gradient(90deg, $dim-wealth, #FBBF24);
   border-radius: 8rpx;
   transition: width 0.9s linear;
 }
@@ -350,7 +350,7 @@ export default {
 .timer-text {
   display: block;
   font-size: 26rpx;
-  color: #F59E0B;
+  color: $dim-wealth;
   margin-top: 12rpx;
   text-align: center;
 }
@@ -359,7 +359,7 @@ export default {
   display: block;
   font-size: 32rpx;
   font-weight: bold;
-  color: #fff;
+  color: $app-surface;
   margin: 40rpx 0 32rpx;
   text-align: center;
 }
@@ -391,7 +391,7 @@ export default {
 
 .option-text {
   font-size: 34rpx;
-  color: #fff;
+  color: $app-surface;
   font-weight: 500;
 }
 
@@ -409,7 +409,7 @@ export default {
 .result-title {
   font-size: 40rpx;
   font-weight: bold;
-  color: #fff;
+  color: $app-surface;
   margin-bottom: 48rpx;
 }
 
@@ -420,7 +420,7 @@ export default {
   width: 260rpx;
   height: 260rpx;
   border-radius: 50%;
-  border: 10rpx solid #F59E0B;
+  border: 10rpx solid $dim-wealth;
   background: rgba(245,158,11,0.1);
   margin-bottom: 36rpx;
 }
@@ -428,7 +428,7 @@ export default {
 .score-num {
   font-size: 88rpx;
   font-weight: bold;
-  color: #F59E0B;
+  color: $dim-wealth;
 }
 
 .score-row {
@@ -445,8 +445,8 @@ export default {
 
 .dimension-tag {
   font-size: 24rpx;
-  color: #F59E0B;
-  border: 2rpx solid #F59E0B;
+  color: $dim-wealth;
+  border: 2rpx solid $dim-wealth;
   border-radius: 24rpx;
   padding: 6rpx 24rpx;
 }

+ 22 - 22
cfc-frontend/pages/cognitive-test/logic-test.vue

@@ -267,10 +267,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .container {
   min-height: 100vh;
-  background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
+  background: linear-gradient(135deg, $app-ink-night 0%, #16213e 100%);
   padding: 60rpx 40rpx;
   box-sizing: border-box;
 }
@@ -287,7 +287,7 @@ export default {
   height: 160rpx;
   border-radius: 50%;
   background: rgba(255, 215, 0, 0.15);
-  border: 3rpx solid #FFD700;
+  border: 3rpx solid $accent-gold;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -299,7 +299,7 @@ export default {
 .intro-title {
   font-size: 44rpx;
   font-weight: bold;
-  color: #FFD700;
+  color: $accent-gold;
   margin-bottom: 16rpx;
   display: block;
 }
@@ -332,7 +332,7 @@ export default {
 }
 .info-value {
   font-size: 26rpx;
-  color: #FFD700;
+  color: $accent-gold;
   font-weight: bold;
 }
 
@@ -345,12 +345,12 @@ export default {
 }
 .quiz-progress {
   font-size: 30rpx;
-  color: #fff;
+  color: $app-surface;
   font-weight: bold;
 }
 .quiz-timer {
   font-size: 30rpx;
-  color: #FFD700;
+  color: $accent-gold;
   font-weight: bold;
 }
 .timer-track {
@@ -362,7 +362,7 @@ export default {
 }
 .timer-fill {
   height: 100%;
-  background: linear-gradient(90deg, #FFD700, #FFA500);
+  background: linear-gradient(90deg, $accent-gold, #FFA500);
   border-radius: 6rpx;
   transition: width 0.1s linear;
 }
@@ -374,7 +374,7 @@ export default {
 }
 .q-text {
   font-size: 44rpx;
-  color: #fff;
+  color: $app-surface;
   font-weight: bold;
   text-align: center;
   display: block;
@@ -398,7 +398,7 @@ export default {
   margin-bottom: 0;
 }
 .q-option--selected {
-  border-color: #FFD700;
+  border-color: $accent-gold;
   background: rgba(255, 215, 0, 0.12);
 }
 .q-option:active {
@@ -406,11 +406,11 @@ export default {
 }
 .q-option-text {
   font-size: 34rpx;
-  color: #fff;
+  color: $app-surface;
   font-weight: bold;
 }
 .q-option--selected .q-option-text {
-  color: #FFD700;
+  color: $accent-gold;
 }
 
 /* ===== 结果页 ===== */
@@ -424,7 +424,7 @@ export default {
   width: 240rpx;
   height: 240rpx;
   border-radius: 50%;
-  background: linear-gradient(135deg, #FFD700, #FFA500);
+  background: linear-gradient(135deg, $accent-gold, #FFA500);
   display: flex;
   align-items: center;
   justify-content: center;
@@ -434,11 +434,11 @@ export default {
 .score-value {
   font-size: 80rpx;
   font-weight: bold;
-  color: #1a1a2e;
+  color: $app-ink-night;
 }
 .score-label {
   font-size: 30rpx;
-  color: #FFD700;
+  color: $accent-gold;
   margin-bottom: 50rpx;
   display: block;
 }
@@ -465,18 +465,18 @@ export default {
 }
 .result-value {
   font-size: 28rpx;
-  color: #fff;
+  color: $app-surface;
   font-weight: bold;
 }
 .dim-tag {
   padding: 6rpx 24rpx;
   border-radius: 24rpx;
-  border: 3rpx solid #FFD700;
+  border: 3rpx solid $accent-gold;
   background: rgba(255, 215, 0, 0.12);
 }
 .dim-tag-text {
   font-size: 24rpx;
-  color: #FFD700;
+  color: $accent-gold;
   font-weight: bold;
 }
 
@@ -495,16 +495,16 @@ export default {
   margin-bottom: 0;
 }
 .btn--primary {
-  background: linear-gradient(135deg, #FFD700, #FFA500);
-  color: #1a1a2e;
+  background: linear-gradient(135deg, $accent-gold, #FFA500);
+  color: $app-ink-night;
 }
 .btn--primary:active {
   opacity: 0.9;
   transform: scale(0.98);
 }
 .btn--outline {
-  border: 3rpx solid #FFD700;
-  color: #FFD700;
+  border: 3rpx solid $accent-gold;
+  color: $accent-gold;
   background: transparent;
 }
 .btn--outline:active {

+ 18 - 18
cfc-frontend/pages/cognitive-test/memory-test.vue

@@ -302,10 +302,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .page {
   min-height: 100vh;
-  background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
+  background: linear-gradient(135deg, $app-ink-night 0%, #16213e 100%);
   display: flex;
   flex-direction: column;
   align-items: center;
@@ -320,8 +320,8 @@ export default {
   height: 96rpx;
   line-height: 96rpx;
   border-radius: 48rpx;
-  background: linear-gradient(135deg, #6366f1 0%, #818cf8 100%);
-  color: #ffffff;
+  background: linear-gradient(135deg, $dim-wisdom 0%, $dim-wisdom-light 100%);
+  color: $app-surface;
   font-size: 34rpx;
   font-weight: 600;
   border: none;
@@ -332,8 +332,8 @@ export default {
 }
 .primary-btn[disabled] {
   opacity: 0.6;
-  color: #ffffff;
-  background: linear-gradient(135deg, #6366f1 0%, #818cf8 100%);
+  color: $app-surface;
+  background: linear-gradient(135deg, $dim-wisdom 0%, $dim-wisdom-light 100%);
 }
 
 .ghost-btn {
@@ -375,7 +375,7 @@ export default {
 .intro-title {
   font-size: 48rpx;
   font-weight: bold;
-  color: #ffffff;
+  color: $app-surface;
   margin-bottom: 30rpx;
 }
 .intro-desc {
@@ -417,7 +417,7 @@ export default {
 .progress-level {
   font-size: 44rpx;
   font-weight: bold;
-  color: #ffffff;
+  color: $app-surface;
 }
 .progress-digit {
   font-size: 26rpx;
@@ -435,7 +435,7 @@ export default {
 .progress-fill {
   height: 100%;
   border-radius: 4rpx;
-  background: linear-gradient(90deg, #6366f1, #818cf8);
+  background: linear-gradient(90deg, $dim-wisdom, $dim-wisdom-light);
   transition: width 0.3s ease;
 }
 
@@ -471,7 +471,7 @@ export default {
 .show-digit text {
   font-size: 120rpx;
   font-weight: bold;
-  color: #ffffff;
+  color: $app-surface;
 }
 .timer-bar-wrap {
   width: 480rpx;
@@ -484,7 +484,7 @@ export default {
 .timer-bar {
   height: 100%;
   border-radius: 6rpx;
-  background: linear-gradient(90deg, #6366f1, #818cf8);
+  background: linear-gradient(90deg, $dim-wisdom, $dim-wisdom-light);
   transition: width 0.05s linear;
 }
 
@@ -519,7 +519,7 @@ export default {
 }
 .input-cell.filled {
   background: rgba(99, 102, 241, 0.5);
-  border-color: #6366f1;
+  border-color: $dim-wisdom;
 }
 .pad {
   width: 100%;
@@ -544,11 +544,11 @@ export default {
 }
 .pad-key text {
   font-size: 44rpx;
-  color: #ffffff;
+  color: $app-surface;
   font-weight: 500;
 }
 .pad-key:active {
-  border-color: #6366f1;
+  border-color: $dim-wisdom;
   background: rgba(99, 102, 241, 0.2);
 }
 .pad-key-ctrl {
@@ -581,7 +581,7 @@ export default {
 .feedback-text {
   font-size: 56rpx;
   font-weight: bold;
-  color: #ffffff;
+  color: $app-surface;
   padding: 24rpx 60rpx;
   border-radius: 24rpx;
   background: rgba(0, 0, 0, 0.35);
@@ -613,7 +613,7 @@ export default {
   width: 280rpx;
   height: 280rpx;
   border-radius: 50%;
-  background: linear-gradient(135deg, #6366f1 0%, #818cf8 100%);
+  background: linear-gradient(135deg, $dim-wisdom 0%, $dim-wisdom-light 100%);
   display: flex;
   flex-direction: column;
   align-items: center;
@@ -623,7 +623,7 @@ export default {
 .score-num {
   font-size: 96rpx;
   font-weight: bold;
-  color: #ffffff;
+  color: $app-surface;
   line-height: 1;
 }
 .score-unit {
@@ -645,7 +645,7 @@ export default {
 .max-level-value {
   font-size: 40rpx;
   font-weight: bold;
-  color: #ffffff;
+  color: $app-surface;
 }
 .dimension-tag {
   margin-top: 24rpx;

+ 6 - 6
cfc-frontend/pages/cognitive-test/perception-test.vue

@@ -241,10 +241,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .test-container {
   min-height: 100vh;
-  background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
+  background: linear-gradient(135deg, $app-ink-night 0%, #16213e 100%);
   padding: 60rpx 40rpx;
 }
 
@@ -263,7 +263,7 @@ export default {
 .test-title {
   font-size: 48rpx;
   font-weight: bold;
-  color: #fff;
+  color: $app-surface;
   margin-bottom: 32rpx;
 }
 
@@ -284,7 +284,7 @@ export default {
 
 .btn-primary {
   background: linear-gradient(135deg, #4ECDC4, #45B7AA);
-  color: #fff;
+  color: $app-surface;
   font-size: 34rpx;
   font-weight: bold;
   width: 480rpx;
@@ -357,7 +357,7 @@ export default {
   display: block;
   font-size: 32rpx;
   font-weight: bold;
-  color: #fff;
+  color: $app-surface;
   margin: 40rpx 0 32rpx;
   text-align: center;
 }
@@ -404,7 +404,7 @@ export default {
 .result-title {
   font-size: 40rpx;
   font-weight: bold;
-  color: #fff;
+  color: $app-surface;
   margin-bottom: 48rpx;
 }
 

+ 17 - 17
cfc-frontend/pages/cognitive-test/spatial-test.vue

@@ -271,10 +271,10 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .page {
   min-height: 100vh;
-  background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
+  background: linear-gradient(135deg, $app-ink-night 0%, #16213e 100%);
   padding: 60rpx 40rpx;
   box-sizing: border-box;
 }
@@ -300,13 +300,13 @@ export default {
 }
 .hero-symbol {
   font-size: 80rpx;
-  color: #3B82F6;
+  color: $status-info;
   line-height: 1;
 }
 .hero-title {
   font-size: 44rpx;
   font-weight: bold;
-  color: #ffffff;
+  color: $app-surface;
   margin-bottom: 24rpx;
 }
 .hero-desc {
@@ -336,7 +336,7 @@ export default {
   width: 12rpx;
   height: 12rpx;
   border-radius: 50%;
-  background: #3B82F6;
+  background: $status-info;
   margin-right: 16rpx;
 }
 .rule-text {
@@ -371,7 +371,7 @@ export default {
 }
 .timer-bar-fill {
   height: 100%;
-  background: linear-gradient(90deg, #3B82F6, #60A5FA);
+  background: linear-gradient(90deg, $status-info, #60A5FA);
   border-radius: 6rpx;
   transition: width 1s linear;
 }
@@ -392,7 +392,7 @@ export default {
 }
 .direction-arrow {
   font-size: 100rpx;
-  color: #ffffff;
+  color: $app-surface;
   line-height: 1.2;
   margin-bottom: 20rpx;
 }
@@ -419,12 +419,12 @@ export default {
   box-sizing: border-box;
 }
 .option-selected {
-  border-color: #3B82F6;
+  border-color: $status-info;
   background: rgba(59, 130, 246, 0.2);
 }
 .option-arrow {
   font-size: 72rpx;
-  color: #ffffff;
+  color: $app-surface;
 }
 .feedback-row {
   margin-top: 10rpx;
@@ -433,7 +433,7 @@ export default {
   font-size: 30rpx;
 }
 .feedback-correct {
-  color: #34D399;
+  color: $status-success-light-3;
 }
 .feedback-wrong {
   color: #F87171;
@@ -445,7 +445,7 @@ export default {
 /* --- 结果页 --- */
 .result-title {
   font-size: 36rpx;
-  color: #ffffff;
+  color: $app-surface;
   margin-top: 60rpx;
   margin-bottom: 20rpx;
 }
@@ -453,7 +453,7 @@ export default {
   width: 280rpx;
   height: 280rpx;
   border-radius: 50%;
-  border: 10rpx solid #3B82F6;
+  border: 10rpx solid $status-info;
   display: flex;
   flex-direction: column;
   align-items: center;
@@ -463,7 +463,7 @@ export default {
 .score-number {
   font-size: 80rpx;
   font-weight: bold;
-  color: #ffffff;
+  color: $app-surface;
   line-height: 1.1;
 }
 .score-unit {
@@ -502,13 +502,13 @@ export default {
   border: none;
 }
 .btn-primary {
-  background: #3B82F6;
-  color: #ffffff;
+  background: $status-info;
+  color: $app-surface;
 }
 .btn-secondary {
   background: transparent;
-  border: 2rpx solid #3B82F6;
-  color: #3B82F6;
+  border: 2rpx solid $status-info;
+  color: $status-info;
   box-sizing: border-box;
 }
 </style>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio