فهرست منبع

refactor(c-2): C-2c 旧蓝分流收官·令牌族删除 + 终审补漏

- uni.scss:删除 $brand-legacy-blue 族 4 令牌定义(C-2b 后页面引用已清零,无编译产物残留;uni.scss 独立编译 + dimension-detail 抽页编译均 OK,#4A9BD7 归零)
- butler/select.vue 补漏 3 处:终审全库扫描发现此前批次遗漏的小写 #4a9bd7(btn-change/btn-select 实底 + butler-card.active 描边)-> $brand-primary + 补 lang="scss"
- 终审结论:旧蓝族源文件剩余命中均为已登记保留项——wuxing-sandbox(深面板 chrome + canvas 五边形网格)、energy/detail、gate-guide-bar(深面板 chrome);uni.scss 历史注释与审计脚本标签非翻转对象;dist/ 为陈旧构建产物
- 验证:butler C-2 PASS + node --check PASS;scripts/tmp_c2_verify.js 被外部进程误删后按原逻辑重建
liaoxg 6 روز پیش
والد
کامیت
9905bfb07a
3فایلهای تغییر یافته به همراه125 افزوده شده و 9 حذف شده
  1. 4 4
      cfc-frontend/pages/butler/select.vue
  2. 121 0
      cfc-frontend/scripts/tmp_c2_verify.js
  3. 0 5
      cfc-frontend/uni.scss

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

@@ -139,7 +139,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<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; }
@@ -149,20 +149,20 @@ export default {
 .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: #4a9bd7; color: #fff; font-size: 28rpx; border-radius: 40rpx; }
+.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; }
 .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; }
-.butler-card.active { border: 2rpx solid #4a9bd7; }
+.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: #4a9bd7; color: #fff; border-radius: 30rpx; flex-shrink: 0; margin-left: 16rpx; }
+.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; }
 </style>

+ 121 - 0
cfc-frontend/scripts/tmp_c2_verify.js

@@ -0,0 +1,121 @@
+#!/usr/bin/env node
+/* C-2 视觉变更验证(旧蓝→品牌橙/信息蓝分流批次专用)
+ * 用法:node scripts/tmp_c2_verify.js <页面路径> <替换规则.json>
+ * 替换规则.json 支持两种规则形态(按序施加):
+ *   1. 全局规则:["#5B9BD5", "#F97316"] 字面替换;["/^color: #5B9BD5/m", "color: #0EA5E9"] 正则(flags 直通,缺省补 g)
+ *   2. 块级规则:{"block": ".channel-tip-text", "pairs": [["color: #5B9BD5", "color: #0EA5E9"]]}
+ *      —— 仅在该选择器规则块(至第一个 } 行)内应用 pairs;块内未命中即报错
+ * 原理:before(HEAD) 与 after(工作区) 分别拼接 uni.scss 编译并归一;
+ * 对 before 产物按序施加预期替换后,必须与 after 产物逐字节相同
+ * → 证明除预期颜色替换外零其他变化。
+ */
+const { execSync } = require('child_process')
+const fs = require('fs')
+const path = require('path')
+
+const PAGE = process.argv[2]
+const SUBST_FILE = process.argv[3]
+if (!PAGE || !SUBST_FILE) {
+  console.error('用法: node scripts/tmp_c2_verify.js <页面路径> <替换规则.json>')
+  process.exit(2)
+}
+
+const ROOT = path.resolve(__dirname, '..')
+const REPO = path.resolve(ROOT, '..')
+const REL = path.relative(REPO, path.join(ROOT, PAGE)).split(path.sep).join('/')
+const TMP = path.join(ROOT, '.tmp_equiv')
+fs.mkdirSync(TMP, { recursive: true })
+
+function extractStyle(src) {
+  const m = src.match(/^[ \t]*<style[^>]*>[\s\S]*?<\/style>/m)
+  if (!m) throw new Error('未找到 style 块: ' + PAGE)
+  return m[0].replace(/^[ \t]*<style[^>]*>/, '').replace(/<\/style>$/, '')
+}
+
+const uniScss = fs.readFileSync(path.join(ROOT, 'uni.scss'), 'utf8')
+const afterVue = fs.readFileSync(path.join(ROOT, PAGE), 'utf8')
+const beforeVue = execSync(`git -C "${REPO}" show HEAD:${REL}`, { encoding: 'utf8', maxBuffer: 1e8 })
+
+fs.writeFileSync(path.join(TMP, 'after.scss'), uniScss + '\n' + extractStyle(afterVue))
+fs.writeFileSync(path.join(TMP, 'before.scss'), uniScss + '\n' + extractStyle(beforeVue))
+
+const sass = path.join(ROOT, 'node_modules/.bin/sass.cmd')
+execSync(`"${sass}" --no-source-map "${TMP}/after.scss" "${TMP}/after.out.css"`, { stdio: 'pipe' })
+execSync(`"${sass}" --no-source-map "${TMP}/before.scss" "${TMP}/before.out.css"`, { stdio: 'pipe' })
+
+function norm(s) {
+  return s
+    .replace(/rgba\(([^)]+)\)/g, (m, inner) => 'rgba(' + inner.split(',').map((x) => x.trim()).join(',') + ')') // canonical rgba spacing
+    .replace(/\/\*[\s\S]*?\*\//g, '') // strip block comments
+    .replace(/#([0-9a-fA-F]{3,8})\b/g, (m) => {
+      let h = m.slice(1).toUpperCase()
+      if (h.length === 3) h = h.split('').map((c) => c + c).join('')
+      return '#' + h
+    })
+    .split('\n').map((l) => l.trim()).filter(Boolean).join('\n')
+}
+
+function toRegExp(pat) {
+  const last = pat.lastIndexOf('/')
+  const flags = pat.slice(last + 1)
+  return new RegExp(pat.slice(1, last), flags.includes('g') ? flags : flags + 'g')
+}
+
+function applyBlockRules(text, sel, pairs) {
+  const lines = text.split('\n')
+  let idx = -1
+  for (let i = 0; i < lines.length; i++) {
+    if (lines[i].trim() === sel + ' {') { idx = i; break }
+  }
+  if (idx === -1) throw new Error('块选择器未找到: ' + sel)
+  let end = -1
+  for (let j = idx + 1; j < lines.length; j++) {
+    if (lines[j].trim() === '}') { end = j; break }
+  }
+  if (end === -1) throw new Error('块未闭合: ' + sel)
+  const body = lines.slice(idx + 1, end)
+  for (const [pat, rep] of pairs) {
+    const before = body.join('\n')
+    const after = pat.startsWith('/') && pat.lastIndexOf('/') > 0
+      ? before.replace(toRegExp(pat), rep)
+      : before.split(pat).join(rep)
+    if (after === before) throw new Error('块内替换未生效: ' + sel + ' :: ' + pat)
+    body.length = 0
+    body.push(...after.split('\n'))
+  }
+  lines.splice(idx + 1, end - idx - 1, ...body)
+  return lines.join('\n')
+}
+
+const substs = JSON.parse(fs.readFileSync(SUBST_FILE, 'utf8'))
+let expected = norm(fs.readFileSync(path.join(TMP, 'before.out.css'), 'utf8'))
+for (const rule of substs) {
+  if (Array.isArray(rule)) {
+    const [pat, rep] = rule
+    expected = pat.startsWith('/') && pat.lastIndexOf('/') > 0
+      ? expected.replace(toRegExp(pat), rep)
+      : expected.split(pat).join(rep)
+  } else if (rule && rule.block && Array.isArray(rule.pairs)) {
+    expected = applyBlockRules(expected, rule.block, rule.pairs)
+  } else {
+    throw new Error('未知规则形态: ' + JSON.stringify(rule))
+  }
+}
+
+const after = norm(fs.readFileSync(path.join(TMP, 'after.out.css'), 'utf8'))
+
+if (expected === after) {
+  console.log(`C-2 验证 PASS(${PAGE}):before 施加预期替换后与 after 逐字节相同,仅含预期颜色变更`)
+} else {
+  const el = expected.split('\n'), al = after.split('\n')
+  console.log(`C-2 验证 DIFF(${PAGE}):expected ${el.length} 行 / after ${al.length} 行`)
+  let shown = 0
+  for (let i = 0; i < Math.max(el.length, al.length) && shown < 40; i++) {
+    if (el[i] !== al[i]) {
+      console.log(`L${i + 1}\n  expected: ${el[i]}\n  after:    ${al[i]}`)
+      shown++
+    }
+  }
+  if (shown >= 40) console.log('...(仅显示前 40 处差异)')
+  process.exit(1)
+}

+ 0 - 5
cfc-frontend/uni.scss

@@ -618,11 +618,6 @@ $facade-fill-warm-tint:#F8F6F0;  // 暖灰浅底(vs $facade-fill-warm-gray #F8
 $facade-fill-ghost:    #FAFAFA;  // 近白填充(duo-compatibility + emotion-checkin + screening)
 $facade-fill-ghost-2:  #F9F9F9;  // 近白填充·副档(emotion-checkin + emotion-report + soothe-toolbox,同角色不同值)
 
-// ---- 旧蓝族(C-2 待翻转:原值令牌化保零观感,翻转移交 C-2 角色分流)----
-$brand-legacy-blue:      #4A9BD7; // 旧蓝主色(16 文件;dimension-detail 渐变起点/百分位/序号徽章)
-$brand-legacy-blue-light:#63B3ED; // 旧蓝亮档(2 文件;旧蓝渐变终点伴生)
-$brand-legacy-blue-tint: #F5FAFE; // 旧蓝极浅底(22 文件;旧蓝系页面浅底)
-$brand-legacy-blue-select:#F0F8FF; // 旧蓝 select 浅底(旧蓝族选中态档;C-2 待翻转)
 $facade-fill-4:        #E8E8E8; // 灰填充·深档(4 文件;vs $facade-fill-3 #EEEEEE)
 $status-danger-strong-2: #C62828; // red-800 深文字(16 文件;vs $status-danger-strong #B91C1C red-700)
 $status-info-soft:    #DBEAFE; // blue-100 信息浅档(15 文件;vs $status-info-tint #EFF6FF blue-50)