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

refactor(beautify): D41 模板区剩余硬编码色收编 (hex 349->323)

- BrandColors 新增 9 键:五维雷达图 grid/label 成对档(emerald100/800、orange200/800、
  pink100/800、indigo100/800)+ 占位符色 textHint (#BBB)
- 雷达图 5 页 gridColor/labelColor 属性字面量改 data 绑定
- 模板表达式拼接色收编:article-edit / article-center / mind-detail/articles 的
  ';color:#fff',challenge-progress 的 conic-gradient 双色
- 组件属性绑定:progress backgroundColor(child/rewards、rewards/rewards)、
  switch color(shop/consignee-edit)
- placeholder-style 7 处改 :placeholder-style 绑定(shop/address-edit ×3、
  shop/checkout ×3、diet/index ×1)
- 模板区 hex 29 -> 3(余 2 处 SVG stroke/stop 属计划边界,1 处 ✓ 为误报)
- 全量自检 383 文件 0 失败;brandcolor 完整性 PASS;硬编码色 349 -> 323
liaoxg 4 дней назад
Родитель
Сommit
32dcd51a4f

+ 4 - 2
cfc-frontend/pages/action-detail/index.vue

@@ -195,8 +195,8 @@
         :avg-scores="actionFamilyScores"
         :population-scores="actionPopulationScores"
         :fillColor="successColor"
-        gridColor="#D1FAE5"
-        labelColor="#065F46"
+        :gridColor="gridColor"
+        :labelColor="labelColor"
         :width="580"
         :height="580" />
     </view>
@@ -280,6 +280,8 @@ export default {
   components: { TabTransition, PageBanner, FamilyEnergyBar, FamilyRelationGraph, PearlDiagram, AbilityDiagram, OctopusDiagram, FamilyMemberStrip, DimensionTasks, DimensionActivities, DimensionProducts, DimensionIntroCard, DimensionVisitorCard, FloatingAvatar, RadarChart, DimensionSubDims, DimensionPageSkeleton },
   data() {
     return {
+      gridColor: BrandColors.emerald100,
+      labelColor: BrandColors.emerald800,
       successColor: BrandColors.success,
     pageLoading: false,
       isLoggedIn: false,

+ 2 - 1
cfc-frontend/pages/activity/create.vue

@@ -136,7 +136,7 @@
           <view class="tpl-colors">
             <view class="tpl-dot tpl-main" :style="{ background: tpl.mainColor || BrandColors.primary }"></view>
             <view class="tpl-dot tpl-dark" :style="{ background: tpl.darkColor || BrandColors.orangeDeep }"></view>
-            <view class="tpl-dot tpl-light" :style="{ background: tpl.lightColor || '#FFF7ED' }"></view>
+            <view class="tpl-dot tpl-light" :style="{ background: tpl.lightColor || paleColor }"></view>
           </view>
           <text class="tpl-name">{{ tpl.name }}</text>
         </view>
@@ -190,6 +190,7 @@ export default {
   },
   data() {
     return {
+      paleColor: BrandColors.primaryPale,
       isNew: true,
       activityId: null,
       loading: false,

+ 2 - 1
cfc-frontend/pages/article-center/article-edit.vue

@@ -44,7 +44,7 @@
             v-for="dim in dimensionList"
             :key="dim.code"
             :class="['dim-chip', selectedDimensions.indexOf(dim.code) >= 0 ? 'active' : '']"
-            :style="selectedDimensions.indexOf(dim.code) >= 0 ? 'background:' + dim.color + ';color:#fff;border-color:' + dim.color : ''"
+            :style="selectedDimensions.indexOf(dim.code) >= 0 ? 'background:' + dim.color + ';color:' + whiteColor + ';border-color:' + dim.color : ''"
             @click="toggleDimension(dim.code)"
           >
             {{ dim.name }}
@@ -104,6 +104,7 @@ import { pickImage } from '@/utils/image-picker.js'
 export default {
   data() {
     return {
+      whiteColor: BrandColors.white,
       title: '',
       coverImage: '',
       content: '',

+ 2 - 1
cfc-frontend/pages/article-center/index.vue

@@ -34,7 +34,7 @@
           v-for="tab in tabList"
           :key="tab.code"
           :class="['ac-tab', currentTab === tab.code ? 'active' : '']"
-          :style="currentTab === tab.code ? 'background:' + tab.color + ';color:#fff;border-color:' + tab.color : ''"
+          :style="currentTab === tab.code ? 'background:' + tab.color + ';color:' + whiteColor + ';border-color:' + tab.color : ''"
           @click="onTabChange(tab.code)"
         >
           {{ tab.name }}
@@ -144,6 +144,7 @@ export default {
   mixins: [fontScaleMixin],
   data() {
     return {
+      whiteColor: BrandColors.white,
       tabList: [
         { code: '', name: '全部', color: BrandColors.primary },
         { code: 'body', name: '身', color: BrandColors.body },

+ 4 - 2
cfc-frontend/pages/body-detail/index.vue

@@ -90,8 +90,8 @@
         :avg-scores="radarFamilyScores"
         :population-scores="radarPopulationScores"
         :fillColor="bodyColor"
-        gridColor="#FED7AA"
-        labelColor="#9A3412"
+        :gridColor="gridColor"
+        :labelColor="labelColor"
         :width="580"
         :height="580"
         @dimensionClick="goDimensionDetail" />
@@ -272,6 +272,8 @@ export default {
   components: { TabTransition, PageBanner, FamilyEnergyBar, RadarChart, FloatingAvatar, DimensionMemberTabs, DimensionIntroCard, DimensionVisitorCard, DimensionPageSkeleton },
   data() {
     return {
+      gridColor: BrandColors.orange200,
+      labelColor: BrandColors.orange800,
       bodyColor: BrandColors.body,
     memberScoreMap: {},
     pageLoading: false,

+ 2 - 1
cfc-frontend/pages/child/rewards.vue

@@ -10,7 +10,7 @@
         <view class="reward-info">
           <view class="reward-title">{{ reward.title }}</view>
           <view class="reward-progress">
-            <progress :percent="reward.pointsRequired > 0 ? (currentPoints / reward.pointsRequired) * 100 : 0" stroke-width="6" :activeColor="primaryColor" backgroundColor="#eee" />
+            <progress :percent="reward.pointsRequired > 0 ? (currentPoints / reward.pointsRequired) * 100 : 0" stroke-width="6" :activeColor="primaryColor" :backgroundColor="trackColor" />
             <text class="progress-text">{{ currentPoints }}/{{ reward.pointsRequired }}积分</text>
           </view>
         </view>
@@ -49,6 +49,7 @@ import { getWishlist, createReward as createRewardApi, getPointsBalance, getChil
 export default {
   data() {
     return {
+      trackColor: BrandColors.hairline2,
       primaryColor: BrandColors.primary,
       rewards: [],
       currentPoints: 0,

+ 3 - 1
cfc-frontend/pages/diet/index.vue

@@ -142,7 +142,7 @@
           </view>
         </view>
         <view class="search-wrap">
-          <input class="search-input" placeholder="搜索食材..." placeholder-style="color:#bbb" v-model="searchQuery" @input="searchFood" />
+          <input class="search-input" placeholder="搜索食材..." :placeholder-style="placeholderStyle" v-model="searchQuery" @input="searchFood" />
         </view>
         <scroll-view class="food-list" scroll-y>
           <view class="food-item" v-for="food in searchResults" :key="food.id" @tap="addFood(food)">
@@ -159,12 +159,14 @@
 </template>
 
 <script>
+import { BrandColors } from '@/utils/brand-colors.js'
 import { getDietIngredients, refreshDietIngredients, addDietIngredient, removeDietIngredient, generateDietRecommendation, getMealConfig, searchDietFoods, reportAppEnter} from '@/utils/api.js'
 import { parseDate } from '@/utils/format.js'
 
 export default {
   data: function() {
     return {
+      placeholderStyle: 'color:' + BrandColors.textHint,
       familyId: null,
       familyMemberId: null,
       ingredients: [],

+ 4 - 1
cfc-frontend/pages/growth/challenge-progress.vue

@@ -10,7 +10,7 @@
     <scroll-view class="content" scroll-y>
       <!-- 进度概览 -->
       <view class="progress-overview">
-        <view class="progress-circle" :style="{ background: 'conic-gradient(#F97316 ' + progressPercent + '%, #f0f0f0 ' + progressPercent + '%)' }">
+        <view class="progress-circle" :style="{ background: 'conic-gradient(' + ringColor + ' ' + progressPercent + '%, ' + ringTrack + ' ' + progressPercent + '%)' }">
           <text class="progress-percent">{{ progressPercent }}%</text>
         </view>
         <text class="progress-label">已完成 {{ completedCount }}/{{ totalCount }} 项任务</text>
@@ -55,11 +55,14 @@
 </template>
 
 <script>
+import { BrandColors } from '@/utils/brand-colors.js'
 import { getChallengeProgress } from '../../utils/api.js'
 
 export default {
   data() {
     return {
+      ringColor: BrandColors.primary,
+      ringTrack: BrandColors.fill,
       loading: true,
       tasksByWeek: {},
       totalReward: 0,

+ 4 - 2
cfc-frontend/pages/member-detail/components/member-detail-sections/body-section.vue

@@ -29,8 +29,8 @@
         :dimensions="dimensionOverview.dimensions.map(function(d) { return { label: d.label, key: d.dimension } })"
         :scores="dimensionOverview.dimensions.map(function(d) { return d.score })"
         :fillColor="bodyColor"
-        gridColor="#FED7AA"
-        labelColor="#9A3412"
+        :gridColor="gridColor"
+        :labelColor="labelColor"
         :width="580"
         :height="400" />
     </view>
@@ -75,6 +75,8 @@ export default {
   },
   data: function() {
     return {
+      gridColor: BrandColors.orange200,
+      labelColor: BrandColors.orange800,
       bodyColor: BrandColors.body,
       loading: true,
       healthReport: null,

+ 2 - 1
cfc-frontend/pages/mind-detail/articles.vue

@@ -8,7 +8,7 @@
             v-for="dim in dimensionList"
             :key="dim.code"
             :class="['filter-chip', currentDimension === dim.code ? 'active' : '']"
-            :style="currentDimension === dim.code ? 'background:' + dim.color + ';color:#fff' : ''"
+            :style="currentDimension === dim.code ? 'background:' + dim.color + ';color:' + whiteColor : ''"
             @click="onDimensionChange(dim.code)"
           >
             {{ dim.label }}
@@ -104,6 +104,7 @@ export default {
   components: { },
   data() {
     return {
+      whiteColor: BrandColors.white,
       dimensionList: Object.keys(dimensionConfig).map(function(k) {
         return { code: k, label: dimensionConfig[k].label, color: dimensionConfig[k].color }
       }),

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

@@ -240,8 +240,8 @@
           :avg-scores="getMindGroupFamilyScores(group)"
           :population-scores="getGroupPopulationScores(group)"
           :fillColor="mindColor"
-          gridColor="#FCE7F3"
-          labelColor="#9D174D"
+          :gridColor="gridColor"
+          :labelColor="labelColor"
           :width="540"
           :height="540" />
       </view>
@@ -278,6 +278,8 @@ export default {
 components: { TabTransition, PageBanner, RadarChart, FamilyEnergyBar, PsychCrisisBanner, FloatingAvatar, FamilyTianpanCard, DimensionMemberTabs, DimensionIntroCard, DimensionVisitorCard, DimensionPageSkeleton },
   data() {
     return {
+      gridColor: BrandColors.pink100,
+      labelColor: BrandColors.pink800,
       mindColor: BrandColors.mind,
     memberScoreMap: {},
     pageLoading: false,

+ 2 - 1
cfc-frontend/pages/rewards/rewards.vue

@@ -9,7 +9,7 @@
               :percent="(currentPoints / reward.pointsRequired) * 100" 
               stroke-width="6" 
               :activeColor="primaryColor"
-              backgroundColor="#eee"
+              :backgroundColor="trackColor"
             />
             <text class="progress-text">{{ currentPoints }}/{{ reward.pointsRequired }}积分</text>
           </view>
@@ -71,6 +71,7 @@ import { getWishlist, exchangeReward as exchangeRewardApi, createReward as creat
 export default {
   data() {
     return {
+      trackColor: BrandColors.hairline2,
       primaryColor: BrandColors.primary,
       rewards: [],
       currentPoints: 0,

+ 5 - 3
cfc-frontend/pages/shop/address-edit/address-edit.vue

@@ -8,7 +8,7 @@
           class="form-input"
           v-model="form.receiverName"
           placeholder="请输入收货人姓名"
-          placeholder-style="color: #ccc; font-size: 28rpx;"
+          :placeholder-style="placeholderStyle"
         />
       </view>
 
@@ -21,7 +21,7 @@
           placeholder="请输入手机号"
           type="number"
           maxlength="11"
-          placeholder-style="color: #ccc; font-size: 28rpx;"
+          :placeholder-style="placeholderStyle"
         />
       </view>
 
@@ -42,7 +42,7 @@
           class="form-input"
           v-model="form.street"
           placeholder="请输入详细地址"
-          placeholder-style="color: #ccc; font-size: 28rpx;"
+          :placeholder-style="placeholderStyle"
         />
       </view>
 
@@ -79,6 +79,7 @@
 </template>
 
 <script>
+import { BrandColors } from '@/utils/brand-colors.js'
 import config from '@/config.js'
 import AddressPicker from '@/components/address-picker.vue'
 
@@ -86,6 +87,7 @@ export default {
   components: { AddressPicker },
   data() {
     return {
+      placeholderStyle: 'color:' + BrandColors.textGhost + '; font-size: 28rpx;',
       addressId: null,
       saving: false,
       form: {

+ 5 - 3
cfc-frontend/pages/shop/checkout/checkout.vue

@@ -122,7 +122,7 @@
             class="purchase-field-input"
             :placeholder="'请输入' + field.fieldName + (field.isRequired ? '' : '(选填)')"
             :disabled="!!field.prefillValue"
-            placeholder-style="color: #ccc; font-size: 26rpx;"
+            :placeholder-style="placeholderStyle"
           />
           <text v-if="field.prefillValue" class="purchase-field-prefill">已填写</text>
         </view>
@@ -272,7 +272,7 @@
             :value="pointsUsed"
             @input="onPointsInput"
             placeholder="输入使用积分"
-            placeholder-style="color: #ccc; font-size: 26rpx;"
+            :placeholder-style="placeholderStyle"
           />
           <text class="points-hint">≈ ¥{{ pointsYuanEquivalent }}</text>
         </view>
@@ -289,7 +289,7 @@
             class="remark-input"
             v-model="remark"
             placeholder="选填,给商家留言"
-            placeholder-style="color: #ccc; font-size: 26rpx;"
+            :placeholder-style="placeholderStyle"
           />
         </view>
       </view>
@@ -482,6 +482,7 @@
 </template>
 
 <script>
+import { BrandColors } from '@/utils/brand-colors.js'
 import config from '@/config.js'
 import { getCouponList, getCouponCheckoutList, addressList, getProductRequiredFields, getMyMembership, getGiftMembershipProgress, productDetail, getFamilyPlatformBalance, exchangeCouponByCf, shopDiscountCalculate } from '../../../utils/api.js'
 import { parseDate } from '@/utils/format.js'
@@ -495,6 +496,7 @@ export default {
   },
   data() {
     return {
+      placeholderStyle: 'color:' + BrandColors.textGhost + '; font-size: 26rpx;',
       items: [],
       selectedAddress: null,
       requiredFields: [],       // 需要的购买信息字段

+ 3 - 1
cfc-frontend/pages/shop/consignee-edit/consignee-edit.vue

@@ -79,7 +79,7 @@
       <!-- 设为默认 -->
       <view class="form-row form-row-switch">
         <text class="form-label">设为默认收货人</text>
-        <switch :checked="form.isDefault === 1" @change="onDefaultChange" color="#667eea" />
+        <switch :checked="form.isDefault === 1" @change="onDefaultChange" :color="switchColor" />
       </view>
 
       <!-- Save -->
@@ -95,6 +95,7 @@
 </template>
 
 <script>
+import { BrandColors } from '@/utils/brand-colors.js'
 import { consigneeDetail, consigneeSave } from '../../../utils/api.js'
 import HandSignature from '@/components/hand-signature/hand-signature.vue'
 
@@ -102,6 +103,7 @@ export default {
   components: { HandSignature },
   data() {
     return {
+      switchColor: BrandColors.indigoPeriwinkle,
       form: {
         name: '',
         phone: '',

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

@@ -29,8 +29,8 @@
         :population-scores="cognitivePopulationScores"
         :childName="activeChildName"
         :fillColor="wisdomColor"
-        gridColor="#E0E7FF"
-        labelColor="#3730A3" />
+        :gridColor="gridColor"
+        :labelColor="labelColor" />
     </view>
 
     <!-- 家庭成员选择条 -->
@@ -256,6 +256,8 @@ export default {
   components: { TabTransition, RadarChart, FamilyEnergyBar, FloatingAvatar, DimensionMemberTabs, DimensionIntroCard, DimensionVisitorCard, DimensionPageSkeleton },
   data() {
     return {
+      gridColor: BrandColors.indigo100,
+      labelColor: BrandColors.indigo800,
       wisdomColor: BrandColors.wisdom,
     memberScoreMap: {},
     pageLoading: false,

+ 179 - 0
cfc-frontend/scripts/d41-template-hex-bind.js

@@ -0,0 +1,179 @@
+#!/usr/bin/env node
+/**
+ * D41: 模板区剩余硬编码色收编
+ *
+ * 覆盖三类模板硬编码:
+ *   A) 组件属性色值(RadarChart gridColor/labelColor、progress backgroundColor、switch color)
+ *      → 绑 data/computed(BrandColors)
+ *   B) 模板表达式字符串拼接里的 hex(';color:#fff'、conic-gradient(#F97316 …))
+ *      → 拼接 BrandColors 值(data 属性)
+ *   C) placeholder-style / 兜底色字面量 → 绑定 data 字符串
+ *
+ * 用法: node scripts/d41-template-hex-bind.js [--dry]
+ */
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '..');
+const DRY = process.argv.includes('--dry');
+const IMPORT_LINE = "import { BrandColors } from '@/utils/brand-colors.js'";
+
+/**
+ * data: { 属性名: BrandColors 键 }  —— 值以 BrandColors.<键> 注入
+ * raw:  { 属性名: JS 表达式字符串 }  —— 原样注入(用于拼接字符串)
+ * replace: [[旧文本, 新文本], ...]  —— 模板区精确替换(全局)
+ */
+const JOBS = [
+  {
+    file: 'pages/action-detail/index.vue',
+    data: { gridColor: 'emerald100', labelColor: 'emerald800' },
+    replace: [
+      ['gridColor="#D1FAE5"', ':gridColor="gridColor"'],
+      ['labelColor="#065F46"', ':labelColor="labelColor"'],
+    ],
+  },
+  {
+    file: 'pages/body-detail/index.vue',
+    data: { gridColor: 'orange200', labelColor: 'orange800' },
+    replace: [
+      ['gridColor="#FED7AA"', ':gridColor="gridColor"'],
+      ['labelColor="#9A3412"', ':labelColor="labelColor"'],
+    ],
+  },
+  {
+    file: 'pages/member-detail/components/member-detail-sections/body-section.vue',
+    data: { gridColor: 'orange200', labelColor: 'orange800' },
+    replace: [
+      ['gridColor="#FED7AA"', ':gridColor="gridColor"'],
+      ['labelColor="#9A3412"', ':labelColor="labelColor"'],
+    ],
+  },
+  {
+    file: 'pages/mind-detail/index.vue',
+    data: { gridColor: 'pink100', labelColor: 'pink800' },
+    replace: [
+      ['gridColor="#FCE7F3"', ':gridColor="gridColor"'],
+      ['labelColor="#9D174D"', ':labelColor="labelColor"'],
+    ],
+  },
+  {
+    file: 'pages/wisdom-detail/index.vue',
+    data: { gridColor: 'indigo100', labelColor: 'indigo800' },
+    replace: [
+      ['gridColor="#E0E7FF"', ':gridColor="gridColor"'],
+      ['labelColor="#3730A3"', ':labelColor="labelColor"'],
+    ],
+  },
+  {
+    file: 'pages/article-center/article-edit.vue',
+    data: { whiteColor: 'white' },
+    replace: [["';color:#fff;border-color:'", "';color:' + whiteColor + ';border-color:'"]],
+  },
+  {
+    file: 'pages/article-center/index.vue',
+    data: { whiteColor: 'white' },
+    replace: [["';color:#fff;border-color:'", "';color:' + whiteColor + ';border-color:'"]],
+  },
+  {
+    file: 'pages/mind-detail/articles.vue',
+    data: { whiteColor: 'white' },
+    replace: [["';color:#fff'", "';color:' + whiteColor"]],
+  },
+  {
+    file: 'pages/growth/challenge-progress.vue',
+    data: { ringColor: 'primary', ringTrack: 'fill' },
+    replace: [
+      ["'conic-gradient(#F97316 ' + progressPercent + '%, #f0f0f0 ' + progressPercent + '%)'",
+        "'conic-gradient(' + ringColor + ' ' + progressPercent + '%, ' + ringTrack + ' ' + progressPercent + '%)'"],
+    ],
+  },
+  {
+    file: 'pages/activity/create.vue',
+    data: { paleColor: 'primaryPale' },
+    replace: [["tpl.lightColor || '#FFF7ED'", 'tpl.lightColor || paleColor']],
+  },
+  {
+    file: 'pages/child/rewards.vue',
+    data: { trackColor: 'hairline2' },
+    replace: [['backgroundColor="#eee"', ':backgroundColor="trackColor"']],
+  },
+  {
+    file: 'pages/rewards/rewards.vue',
+    data: { trackColor: 'hairline2' },
+    replace: [['backgroundColor="#eee"', ':backgroundColor="trackColor"']],
+  },
+  {
+    file: 'pages/shop/consignee-edit/consignee-edit.vue',
+    data: { switchColor: 'indigoPeriwinkle' },
+    replace: [['color="#667eea"', ':color="switchColor"']],
+  },
+  {
+    file: 'pages/shop/address-edit/address-edit.vue',
+    raw: { placeholderStyle: "'color:' + BrandColors.textGhost + '; font-size: 28rpx;'" },
+    replace: [['placeholder-style="color: #ccc; font-size: 28rpx;"', ':placeholder-style="placeholderStyle"']],
+  },
+  {
+    file: 'pages/shop/checkout/checkout.vue',
+    raw: { placeholderStyle: "'color:' + BrandColors.textGhost + '; font-size: 26rpx;'" },
+    replace: [['placeholder-style="color: #ccc; font-size: 26rpx;"', ':placeholder-style="placeholderStyle"']],
+  },
+  {
+    file: 'pages/diet/index.vue',
+    raw: { placeholderStyle: "'color:' + BrandColors.textHint" },
+    replace: [['placeholder-style="color:#bbb"', ':placeholder-style="placeholderStyle"']],
+  },
+];
+
+const stats = [];
+
+for (const job of JOBS) {
+  const abs = path.join(ROOT, job.file);
+  if (!fs.existsSync(abs)) { console.log('SKIP(not found): ' + job.file); continue; }
+  let src = fs.readFileSync(abs, 'utf8');
+  const eol = src.includes('\r\n') ? '\r\n' : '\n';
+
+  const scriptStart = src.search(/^<script/m);
+  if (scriptStart === -1) { console.log('SKIP(no script): ' + job.file); continue; }
+  let head = src.slice(0, scriptStart);
+  let body = src.slice(scriptStart);
+
+  let replaced = 0;
+  for (const [from, to] of job.replace) {
+    if (!head.includes(from)) { console.log('  !! 未命中: ' + job.file + '  ' + from.slice(0, 60)); continue; }
+    head = head.split(from).join(to);
+    replaced += (head.split(to).length - 1) > 0 ? 1 : 1;
+  }
+
+  // import
+  if (!/brand-colors/.test(body)) {
+    body = body.replace(/^<script[^>]*>\r?\n/, m => m + IMPORT_LINE + eol);
+  }
+
+  // data 注入
+  const entries = [];
+  for (const [name, key] of Object.entries(job.data || {})) entries.push(name + ': BrandColors.' + key + ',');
+  for (const [name, expr] of Object.entries(job.raw || {})) entries.push(name + ': ' + expr + ',');
+
+  const dataPatterns = [
+    /^([ \t]*)data\s*(?:\([^)]*\)|:\s*function\s*\([^)]*\))\s*\{\r?\n([ \t]*)return\s*\{\r?\n/m,
+    /^([ \t]*)data\s*:\s*\(\)\s*=>\s*\(\{\r?\n([ \t]*)/m,
+  ];
+  let injected = false;
+  for (const re of dataPatterns) {
+    const m = body.match(re);
+    if (!m) continue;
+    const idx = body.indexOf(m[0]) + m[0].length;
+    const indent = m[2] + '  ';
+    const block = entries.map(e => indent + e).join(eol) + eol;
+    body = body.slice(0, idx) + block + body.slice(idx);
+    injected = true;
+    break;
+  }
+  if (!injected) { console.log('FAIL(no data): ' + job.file); continue; }
+
+  if (!DRY) fs.writeFileSync(abs, head + body, 'utf8');
+  stats.push(job.file);
+  console.log((DRY ? 'DRY ' : 'OK  ') + job.file + '  属性=' + entries.length);
+}
+
+console.log('处理文件: ' + stats.length);

+ 13 - 0
cfc-frontend/utils/brand-colors.js

@@ -165,6 +165,19 @@ export const BrandColors = {
 
   // Element UI 语义色
   elementDanger: '#F56C6C',
+
+  // 五维雷达图 grid/label 档(按维度成对;对应页面 RadarChart props)
+  emerald100: '#D1FAE5',   // 行动维度 网格
+  emerald800: '#065F46',   // 行动维度 标签
+  orange200: '#FED7AA',    // 身体维度 网格
+  orange800: '#9A3412',    // 身体维度 标签
+  pink100: '#FCE7F3',      // 心智维度 网格
+  pink800: '#9D174D',      // 心智维度 标签
+  indigo100: '#E0E7FF',    // 智慧维度 网格
+  indigo800: '#3730A3',    // 智慧维度 标签
+
+  // 输入占位符(placeholder-style)
+  textHint: '#BBBBBB',
 }
 
 // 向后兼容导出(避免 import 路径散乱)