Procházet zdrojové kódy

fix(beautify): 补齐 3 处 BrandColors 缺失 import + 新增品牌色引用完整性审计

- sleep-checkin / profile-main/profile:script 内使用 BrandColors.* 但从未 import
  (D31 收编时漏加 import,运行时 ReferenceError)
- emotion-checkin:改导入 BrandColors,去掉 3 个已不再使用的具名导入
- scripts/audit-brandcolor-integrity.js:一次性体检四类历史回归
  'BrandColors.xxx' 字符串 / 缺少 import / import 落在 </script> 后 /
  具名导入不存在;退出码可直接接入 CI
- 审计结果:四项全 0,3 文件 node --check 通过
liaoxg před 5 dny
rodič
revize
2986a9b42b

+ 1 - 0
cfc-frontend/pages/growth/sleep-checkin.vue

@@ -159,6 +159,7 @@
 <script>
 import config from '../../config'
 import { formatDate } from '../../utils/format.js'
+import { BrandColors } from '../../utils/brand-colors.js'
 
 export default {
   data() {

+ 1 - 1
cfc-frontend/pages/mind-detail/emotion-checkin.vue

@@ -200,7 +200,7 @@
 </template>
 
 <script>
-import { body, danger, success } from '@/utils/brand-colors.js'
+import { BrandColors } from '@/utils/brand-colors.js'
 import config from '../../config'
 import PsychCrisisBanner from '@/pages/mind-detail/components/PsychCrisisBanner.vue'
 import { pickImage } from '@/utils/image-picker.js'

+ 1 - 0
cfc-frontend/pages/profile-main/profile.vue

@@ -313,6 +313,7 @@ import {
   getReferralCode, extractReferralCode, getShareQrCode, getEnergySandbox,
   vendorStatus
 } from '../../utils/api.js'
+import { BrandColors } from '../../utils/brand-colors.js'
 
 export default {
   components: { FloatingAvatar, ContentSharePoster },

+ 84 - 0
cfc-frontend/scripts/audit-brandcolor-integrity.js

@@ -0,0 +1,84 @@
+#!/usr/bin/env node
+/**
+ * 品牌色/令牌引用完整性审计
+ *
+ * 检查美化治理过程中容易出现的四类回归(均为真实踩过的坑):
+ *   1) 'BrandColors.xxx' 字符串字面量 —— 字符串不是变量,作为颜色值无效
+ *   2) BrandColors.xxx 使用但未 import —— 运行时 ReferenceError / undefined
+ *   3) import 落在 </script> 之后 —— 模块作用域外,运行时标识符未定义
+ *   4) 从 utils/brand-colors.js 具名导入不存在的导出 —— 值为 undefined
+ *
+ * 用法: node scripts/audit-brandcolor-integrity.js
+ * 退出码: 0 通过 / 1 存在问题
+ */
+const fs = require('fs')
+const path = require('path')
+
+const ROOT = path.resolve(__dirname, '..')
+const EXCLUDE_DIRS = new Set(['node_modules', 'dist', 'unpackage', '.git', 'scripts', 'docs', '.tmp_equiv'])
+const SKIP_FILES = new Set(['components/mp-html/node/node.vue']) // mp-html 源码保持原样
+
+function walk(dir, out = []) {
+  for (const name of fs.readdirSync(dir)) {
+    if (EXCLUDE_DIRS.has(name)) continue
+    const p = path.join(dir, name)
+    const st = fs.statSync(p)
+    if (st.isDirectory()) walk(p, out)
+    else if (name.endsWith('.vue')) out.push(p)
+  }
+  return out
+}
+
+const bcSrc = fs.readFileSync(path.join(ROOT, 'utils/brand-colors.js'), 'utf8')
+const exportsSet = new Set()
+for (const m of bcSrc.matchAll(/export\s+const\s+(\w+)/g)) exportsSet.add(m[1])
+for (const m of bcSrc.matchAll(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*'/gm)) exportsSet.add(m[1])
+exportsSet.add('default')
+
+const issues = { stringLiteral: [], missingImport: [], strayImport: [], badNamedImport: [] }
+
+for (const abs of walk(ROOT)) {
+  const rel = path.relative(ROOT, abs).split(path.sep).join('/')
+  if (SKIP_FILES.has(rel)) continue
+  const src = fs.readFileSync(abs, 'utf8')
+  const scriptIdx = src.search(/<script/)
+  const closeIdx = src.indexOf('</script>')
+  const styleIdx = src.search(/<style/)
+  const head = scriptIdx === -1 ? src : src.slice(0, scriptIdx)
+  const scriptBody = scriptIdx === -1 || closeIdx === -1 ? '' : src.slice(scriptIdx, closeIdx)
+
+  if (/['"]BrandColors\.\w+['"]/.test(src)) issues.stringLiteral.push(rel)
+
+  const usesBrandColors = /\bBrandColors\./.test(scriptBody) || /\bBrandColors\./.test(head)
+  const importedBrandColors = /import\s*\{[^}]*\bBrandColors\b[^}]*\}\s*from\s*['"][^'"]*brand-colors/.test(src)
+  if (usesBrandColors && !importedBrandColors) issues.missingImport.push(rel)
+
+  if (closeIdx !== -1 && styleIdx > closeIdx) {
+    const between = src.slice(closeIdx + 9, styleIdx)
+    if (/^\s*import\s/m.test(between)) issues.strayImport.push(rel)
+  }
+
+  for (const m of src.matchAll(/import\s*\{([^}]*)\}\s*from\s*['"][^'"]*brand-colors(?:\.js)?['"]/g)) {
+    for (const raw of m[1].split(',')) {
+      const name = raw.trim()
+      if (!name) continue
+      if (!exportsSet.has(name)) issues.badNamedImport.push(rel + ' -> ' + name)
+    }
+  }
+}
+
+const labels = {
+  stringLiteral: "'BrandColors.xxx' 字符串字面量(颜色值无效)",
+  missingImport: 'BrandColors 已使用但未 import',
+  strayImport: 'import 落在 </script> 之后',
+  badNamedImport: 'brand-colors 具名导入不存在',
+}
+
+let failed = 0
+for (const [key, list] of Object.entries(issues)) {
+  if (list.length) failed += list.length
+  console.log(labels[key] + ': ' + list.length)
+  list.slice(0, 20).forEach(x => console.log('  - ' + x))
+}
+console.log(failed === 0 ? 'PASS' : 'FAIL(' + failed + ')')
+process.exit(failed === 0 ? 0 : 1)