|
|
@@ -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);
|