Procházet zdrojové kódy

refactor(beautify): D18 emotion-trend 情绪趋势页清理 (debt 69->~20)

- template 5 个 legend-step 内联 background -> 语义 class(SCSS 变量)
- script 天气色板/canvas 色收编 BrandColors.*(含 import)
- style #FF6B9D->\-mind, #81C784->\-green-light
- 热力图 5 档 + 画布近白收编局部 \-et-heat-*/\\-canvas-bg
- brand-colors.js 补 weatherSunny/textGhost/hairline/fill
- hex 17->0(palette 声明除外);P1 6->5
liaoxg před 6 dny
rodič
revize
b72a110b19

+ 37 - 22
cfc-frontend/pages/mind-detail/emotion-trend.vue

@@ -73,11 +73,11 @@
       <view class="heatmap-legend">
         <text class="legend-label">低</text>
         <view class="legend-bar">
-          <view class="legend-step" style="background:#FFE0E0"></view>
-          <view class="legend-step" style="background:#FFB0B0"></view>
-          <view class="legend-step" style="background:#FFD9A0"></view>
-          <view class="legend-step" style="background:#C8E6C9"></view>
-          <view class="legend-step" style="background:#81C784"></view>
+          <view class="legend-step legend-step-1"></view>
+          <view class="legend-step legend-step-2"></view>
+          <view class="legend-step legend-step-3"></view>
+          <view class="legend-step legend-step-4"></view>
+          <view class="legend-step legend-step-5"></view>
         </view>
         <text class="legend-label">高</text>
       </view>
@@ -123,6 +123,7 @@
 </template>
 
 <script>
+import { BrandColors } from '../../utils/brand-colors.js'
 import config from '../../config'
 import PsychCrisisBanner from '@/pages/mind-detail/components/PsychCrisisBanner.vue'
 import { parseDate } from '../../utils/format.js'
@@ -137,11 +138,11 @@ export default {
       trend: null,
       weekdays: ['日', '一', '二', '三', '四', '五', '六'],
       weatherConfig: {
-        sunny: { icon: '\u2600\uFE0F', label: '心情很好', color: '#FFB347' },
-        cloudy: { icon: '\u26C5', label: '一般般', color: '#A0A0A0' },
-        rainy: { icon: '\uD83C\uDF27\uFE0F', label: '有点难过', color: '#6B9BD2' },
-        stormy: { icon: '\u26A1', label: '很烦躁', color: '#8B5CF6' },
-        rainbow: { icon: '\uD83C\uDF08', label: '特别开心', color: '#FF6B9D' }
+        sunny: { icon: '\u2600\uFE0F', label: '心情很好', color: BrandColors.weatherSunny },
+        cloudy: { icon: '\u26C5', label: '一般般', color: BrandColors.emotionDisgust },
+        rainy: { icon: '\uD83C\uDF27\uFE0F', label: '有点难过', color: BrandColors.emotionSadness },
+        stormy: { icon: '\u26A1', label: '很烦躁', color: BrandColors.violet },
+        rainbow: { icon: '\uD83C\uDF08', label: '特别开心', color: BrandColors.mind }
       },
       // Plutchik 情绪中文映射 + 成长页词表(旧数据兼容)
       emotionTypeLabels: {
@@ -242,7 +243,7 @@ export default {
       if (total === 0) return []
 
       return Object.keys(dist).map(w => {
-        const cfg = this.weatherConfig[w] || { icon: '?', label: w, color: '#ccc' }
+        const cfg = this.weatherConfig[w] || { icon: '?', label: w, color: BrandColors.textGhost }
         return {
           weather: w,
           icon: cfg.icon,
@@ -378,7 +379,7 @@ export default {
           var plotH = canvasHeight - padding.top - padding.bottom
 
           // Draw axes
-          ctx.setStrokeStyle('#E0E0E0')
+          ctx.setStrokeStyle(BrandColors.hairline)
           ctx.setLineWidth(1 * dpr)
           ctx.beginPath()
           ctx.moveTo(padding.left, padding.top)
@@ -388,13 +389,13 @@ export default {
 
           // Draw Y axis labels
           ctx.setFontSize(10 * dpr)
-          ctx.setFillStyle('#999')
+          ctx.setFillStyle(BrandColors.textMuted)
           ctx.setTextAlign('right')
           for (var yVal = 2; yVal <= 10; yVal += 2) {
             var yPos = padding.top + plotH - (yVal / 10) * plotH
             ctx.fillText(yVal + '', padding.left - 6 * dpr, yPos + 3 * dpr)
             // Grid line
-            ctx.setStrokeStyle('#F0F0F0')
+            ctx.setStrokeStyle(BrandColors.fill)
             ctx.setLineWidth(1 * dpr)
             ctx.beginPath()
             ctx.moveTo(padding.left, yPos)
@@ -408,7 +409,7 @@ export default {
             avg += data[si].moodScore
           }
           avg /= count
-          var lineColor = avg >= 5 ? '#10B981' : '#FF6B9D'
+          var lineColor = avg >= 5 ? BrandColors.success : BrandColors.mind
 
           // Draw data points and lines
           ctx.setStrokeStyle(lineColor)
@@ -436,7 +437,7 @@ export default {
 
           // Draw X axis labels (dates)
           ctx.setFontSize(9 * dpr)
-          ctx.setFillStyle('#999')
+          ctx.setFillStyle(BrandColors.textMuted)
           ctx.setTextAlign('center')
           var step = Math.max(1, Math.floor(count / 6))
           for (var di = 0; di < count; di += step) {
@@ -497,7 +498,7 @@ export default {
         ctx.fillText('低落', padding.left + plotW * 0.25, padding.top + plotH * 0.8)
 
         // Axes
-        ctx.setStrokeStyle('#E0E0E0')
+        ctx.setStrokeStyle(BrandColors.hairline)
         ctx.setLineWidth(1 * dpr)
         ctx.beginPath()
         ctx.moveTo(padding.left, padding.top + plotH / 2)
@@ -510,7 +511,7 @@ export default {
 
         // Axis labels
         ctx.setFontSize(9 * dpr)
-        ctx.setFillStyle('#999')
+        ctx.setFillStyle(BrandColors.textMuted)
         ctx.setTextAlign('center')
         ctx.fillText('效价 →', padding.left + plotW / 2, padding.top + plotH + 18 * dpr)
         ctx.save()
@@ -529,11 +530,11 @@ export default {
           // Color based on valence
           var color
           if (pt.valence >= 7) {
-            color = '#10B981'
+            color = BrandColors.success
           } else if (pt.valence >= 5) {
-            color = '#FFD93D'
+            color = BrandColors.emotionJoy
           } else {
-            color = '#FF6B9D'
+            color = BrandColors.mind
           }
 
           ctx.beginPath()
@@ -552,8 +553,22 @@ export default {
 </script>
 
 <style scoped lang="scss">
+// ===== 情绪趋势页专属色阶(热力图绿阶 + 画布近白;单页专用,约定见 uni.scss §14)=====
+$c-et-heat-1: #FFE0E0; // 热力档 1(最浅红)
+$c-et-heat-2: #FFB0B0; // 热力档 2(浅红)
+$c-et-heat-3: #FFD9A0; // 热力档 3(浅橙)
+$c-et-heat-4: #C8E6C9; // 热力档 4(浅绿)
+$c-et-heat-5: #81C784; // 热力档 5(中绿)
+$c-canvas-bg: #FCFCFC; // 画布近白
+
+.legend-step-1 { background: $c-et-heat-1; }
+.legend-step-2 { background: $c-et-heat-2; }
+.legend-step-3 { background: $c-et-heat-3; }
+.legend-step-4 { background: $c-et-heat-4; }
+.legend-step-5 { background: $c-et-heat-5; }
+
 // ===== 页面级局部调色板(单页专有色阶;同值才映射全局令牌,约定见 uni.scss §14) =====
-$c-canvas-bg:  #FCFCFC;   // 图表画布近白底
+$c-canvas-bg:  $c-canvas-bg;   // 图表画布近白底
 
 .trend-container {
   min-height: 100vh;

+ 98 - 0
cfc-frontend/scripts/d18-emotion-trend.js

@@ -0,0 +1,98 @@
+#!/usr/bin/env node
+/**
+ * D18: emotion-trend.vue 情绪趋势页清理 (debt 69)
+ *
+ * 1) template 5 个 legend-step 内联 background -> class(SCSS 变量)
+ * 2) script 天气色板/canvas 色收编 BrandColors + 补 key
+ * 3) style 局部变量/全局映射
+ */
+const fs = require('fs');
+const F = 'pages/mind-detail/emotion-trend.vue';
+let src = fs.readFileSync(F, 'utf8');
+let replaced = 0;
+
+function repl(from, to) {
+  const cnt = src.split(from).length - 1;
+  if (cnt === 0) { console.warn('NOT FOUND:', from.slice(0, 90)); return; }
+  src = src.split(from).join(to);
+  replaced += cnt;
+}
+
+// ---- 1. template legend-step 内联 -> 语义 class ----
+repl('<view class="legend-step" style="background:#FFE0E0"></view>', '<view class="legend-step legend-step-1"></view>');
+repl('<view class="legend-step" style="background:#FFB0B0"></view>', '<view class="legend-step legend-step-2"></view>');
+repl('<view class="legend-step" style="background:#FFD9A0"></view>', '<view class="legend-step legend-step-3"></view>');
+repl('<view class="legend-step" style="background:#C8E6C9"></view>', '<view class="legend-step legend-step-4"></view>');
+repl('<view class="legend-step" style="background:#81C784"></view>', '<view class="legend-step legend-step-5"></view>');
+
+// ---- 2. script 天气色板 / canvas ----
+repl("sunny: { icon: '\\u2600\\uFE0F', label: '心情很好', color: '#FFB347' }", "sunny: { icon: '\\u2600\\uFE0F', label: '心情很好', color: BrandColors.weatherSunny }");
+repl("cloudy: { icon: '\\u26C5', label: '一般般', color: '#A0A0A0' }", "cloudy: { icon: '\\u26C5', label: '一般般', color: BrandColors.emotionDisgust }");
+repl("rainy: { icon: '\\uD83C\\uDF27\\uFE0F', label: '有点难过', color: '#6B9BD2' }", "rainy: { icon: '\\uD83C\\uDF27\\uFE0F', label: '有点难过', color: BrandColors.emotionSadness }");
+repl("stormy: { icon: '\\u26A1', label: '很烦躁', color: '#8B5CF6' }", "stormy: { icon: '\\u26A1', label: '很烦躁', color: BrandColors.violet }");
+repl("rainbow: { icon: '\\uD83C\\uDF08', label: '特别开心', color: '#FF6B9D' }", "rainbow: { icon: '\\uD83C\\uDF08', label: '特别开心', color: BrandColors.mind }");
+repl("color: '#ccc' }", "color: BrandColors.textGhost }");
+repl("ctx.setStrokeStyle('#E0E0E0')", "ctx.setStrokeStyle(BrandColors.hairline)");
+repl("ctx.setFillStyle('#999')", "ctx.setFillStyle(BrandColors.textMuted)");
+repl("ctx.setStrokeStyle('#F0F0F0')", "ctx.setStrokeStyle(BrandColors.fill)");
+repl("var lineColor = avg >= 5 ? '#10B981' : '#FF6B9D'", "var lineColor = avg >= 5 ? BrandColors.success : BrandColors.mind");
+repl("color = '#10B981'", "color = BrandColors.success");
+repl("color = '#FFD93D'", "color = BrandColors.emotionJoy");
+repl("color = '#FF6B9D'", "color = BrandColors.mind");
+
+// ---- 3. import ----
+const hasImport = src.includes('utils/brand-colors.js');
+if (!hasImport) {
+  const m = src.match(/<script>\r?\n/);
+  if (m) src = src.replace(m[0], m[0] + "import { BrandColors } from '../../utils/brand-colors.js'\r\n");
+  else console.warn('script tag not found');
+}
+
+// ---- 4. style ----
+const styleIdx = src.indexOf('<style');
+const before = src.slice(0, styleIdx);
+let styleBody = src.slice(styleIdx);
+
+styleBody = styleBody.replace(/#FF6B9D(?![0-9a-fA-F])/gi, '$dim-mind');
+styleBody = styleBody.replace(/#81C784(?![0-9a-fA-F])/gi, '$mat-green-light');
+
+const palette = [
+  '// ===== 情绪趋势页专属色阶(热力图绿阶 + 画布近白;单页专用,约定见 uni.scss §14)=====',
+  '$c-et-heat-1: #FFE0E0; // 热力档 1(最浅红)',
+  '$c-et-heat-2: #FFB0B0; // 热力档 2(浅红)',
+  '$c-et-heat-3: #FFD9A0; // 热力档 3(浅橙)',
+  '$c-et-heat-4: #C8E6C9; // 热力档 4(浅绿)',
+  '$c-et-heat-5: #81C784; // 热力档 5(中绿)',
+  '$c-canvas-bg: #FCFCFC; // 画布近白',
+  ''
+].join('\n');
+styleBody = styleBody.replace(/(<style[^>]*>\s*\n)/, '$1' + palette + '\n');
+
+// legend-step 类(追加在局部调色板后)
+styleBody = styleBody.replace(
+  /(<style[^>]*>\s*\n[^]*?<style[^>]*>)/,
+  '$1'
+);
+// 直接在 style 开头 palette 后插入 legend-step 类
+styleBody = styleBody.replace(
+  /(\$c-canvas-bg: #FCFCFC; \/\/ 画布近白\r?\n)/,
+  '$1\n.legend-step-1 { background: $c-et-heat-1; }\n.legend-step-2 { background: $c-et-heat-2; }\n.legend-step-3 { background: $c-et-heat-3; }\n.legend-step-4 { background: $c-et-heat-4; }\n.legend-step-5 { background: $c-et-heat-5; }\n'
+);
+
+// style 内残留:$c-canvas-bg 声明本身保留;替换 style 里的 #FCFCFC
+styleBody = styleBody.replace(/#FCFCFC(?![0-9a-fA-F])/gi, '$c-canvas-bg');
+// 恢复声明行(避免变成 $c-canvas-bg: $c-canvas-bg)
+styleBody = styleBody.replace('$c-canvas-bg: $c-canvas-bg;', '$c-canvas-bg: #FCFCFC;');
+
+src = before + styleBody;
+fs.writeFileSync(F, src);
+console.log('total replaced:', replaced);
+
+const out = fs.readFileSync(F, 'utf8');
+const lines = out.split('\n');
+let n = 0;
+for (let i = 0; i < lines.length; i++) {
+  const L = lines[i].trim();
+  if (/#[0-9a-fA-F]{3,8}\b/.test(L) && L.charAt(0) !== '$') { console.log('HEX ' + (i + 1) + ': ' + L); n++; }
+}
+console.log('remaining non-palette hex:', n);

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

@@ -77,6 +77,10 @@ export const BrandColors = {
   appTextSubtle: '#94A3B8', // slate-400 弱文字($app-text-subtle)
   pink: '#EC4899',          // pink-500($accent-pink)
   tealDeep: '#14B8A6',      // teal-500(天盘 9 档色板)
+  weatherSunny: '#FFB347',  // 晴朗心情色(情绪趋势天气板)
+  textGhost: '#CCC',        // $facade-text-ghost
+  hairline: '#E0E0E0',      // $facade-hairline
+  fill: '#F0F0F0',          // $facade-fill
 
   // Plutchik 八基本情绪色板(情绪打卡专属;单一事实源)
   emotionJoy: '#FFD93D',       // 开心