| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- #!/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);
|