audit-beautify-debt.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. #!/usr/bin/env node
  2. /* 全站页面美化审计 v2(2026-10-17)
  3. * 对齐 UniForge UI 取证阶段:量化每个页面的样式债务
  4. * 维度:硬编码色 / 旧品牌蓝 / transition:all / getSystemInfoSync / 内联样式 / px 单位 / 页面体量
  5. */
  6. const fs = require('fs')
  7. const path = require('path')
  8. const ROOT = path.resolve(__dirname, '..')
  9. const EXCLUDE_DIRS = new Set(['node_modules', 'dist', 'unpackage', '.git', 'dist_bak_1792213156'])
  10. // 已重制完成的页面(阶段一~四 + 成员标签),不纳入待美化清单
  11. const DONE_PAGES = new Set([
  12. 'pages/body-detail/index.vue',
  13. 'pages/mind-detail/index.vue',
  14. 'pages/wisdom-detail/index.vue',
  15. 'pages/wealth/index.vue',
  16. 'pages/action-detail/index.vue',
  17. ])
  18. function walk(dir, out = []) {
  19. for (const name of fs.readdirSync(dir)) {
  20. if (EXCLUDE_DIRS.has(name)) continue
  21. const p = path.join(dir, name)
  22. const st = fs.statSync(p)
  23. if (st.isDirectory()) walk(p, out)
  24. else if (name.endsWith('.vue')) out.push(p)
  25. }
  26. return out
  27. }
  28. const files = walk(ROOT)
  29. const HEX = /#[0-9a-fA-F]{3,8}\b/g
  30. const OLD_BLUE = /#5[Bb]9[Bb][Dd]5/g
  31. const TRANS_ALL = /transition\s*:\s*all/g
  32. const SYSINFO = /getSystemInfoSync/g
  33. const INLINE_STYLE = /style="/g
  34. const PX_UNIT = /(?<![\w-])\d+px(?![\w-])/g
  35. const rows = []
  36. for (const abs of files) {
  37. const rel = path.relative(ROOT, abs).split(path.sep).join('/')
  38. if (rel.startsWith('components/')) continue // 组件单独一轮
  39. const src = fs.readFileSync(abs, 'utf8')
  40. const styleStart = src.search(/<style/)
  41. const styleBlock = styleStart === -1 ? '' : src.slice(styleStart)
  42. const hexInStyle = (styleBlock.match(HEX) || []).length
  43. const hexAll = (src.match(HEX) || []).length
  44. rows.push({
  45. file: rel,
  46. lines: src.split('\n').length,
  47. hex: hexAll,
  48. hexStyle: hexInStyle,
  49. oldBlue: (styleBlock.match(OLD_BLUE) || []).length,
  50. transAll: (styleBlock.match(TRANS_ALL) || []).length,
  51. sysInfo: (styleBlock.match(SYSINFO) || []).length,
  52. inlineStyle: (src.match(INLINE_STYLE) || []).length,
  53. px: (styleBlock.match(PX_UNIT) || []).length,
  54. done: DONE_PAGES.has(rel),
  55. })
  56. }
  57. // 债务分:硬编码色权重最高(与品牌令牌脱钩的直接证据),旧品牌蓝次之
  58. for (const r of rows) {
  59. r.debt = r.hex * 3 + r.oldBlue * 5 + r.transAll * 2 + r.sysInfo * 2 + r.px * 0.1
  60. }
  61. const pagesCfg = JSON.parse(fs.readFileSync(path.join(ROOT, 'pages.json'), 'utf8'))
  62. const mainPaths = pagesCfg.pages.map((p) => p.path + '.vue')
  63. console.log('========== 汇总 ==========')
  64. console.log('页面文件总数(不含 components):', rows.length)
  65. console.log('硬编码色总数:', rows.reduce((s, r) => s + r.hex, 0))
  66. console.log('旧品牌蓝 #5B9BD5 总数:', rows.reduce((s, r) => s + r.oldBlue, 0))
  67. console.log('transition:all 总数:', rows.reduce((s, r) => s + r.transAll, 0))
  68. console.log('getSystemInfoSync 总数:', rows.reduce((s, r) => s + r.sysInfo, 0))
  69. console.log('\n========== 主包 7 门面页 ==========')
  70. console.log('file|lines|hex|oldBlue|transAll|px|inlineStyle|debt')
  71. for (const p of mainPaths) {
  72. const r = rows.find((x) => x.file === p)
  73. if (r) console.log(`${r.file}|${r.lines}|${r.hex}|${r.oldBlue}|${r.transAll}|${r.px}|${r.inlineStyle}|${r.debt.toFixed(1)}`)
  74. }
  75. console.log('\n========== 旧品牌蓝 TOP25(品牌残留,必须收敛) ==========')
  76. console.log('file|oldBlue|hex|lines')
  77. rows
  78. .filter((r) => r.oldBlue > 0)
  79. .sort((a, b) => b.oldBlue - a.oldBlue || b.hex - a.hex)
  80. .slice(0, 25)
  81. .forEach((r) => console.log(`${r.file}|${r.oldBlue}|${r.hex}|${r.lines}`))
  82. console.log('\n========== 硬编码色 TOP30(样式债务) ==========')
  83. console.log('file|hex|hexStyle|oldBlue|transAll|px|lines|debt|done')
  84. rows
  85. .filter((r) => !r.done)
  86. .sort((a, b) => b.debt - a.debt)
  87. .slice(0, 30)
  88. .forEach((r) => console.log(`${r.file}|${r.hex}|${r.hexStyle}|${r.oldBlue}|${r.transAll}|${r.px}|${r.lines}|${r.debt.toFixed(1)}|${r.done}`))
  89. console.log('\n========== transition:all 分布 ==========')
  90. rows
  91. .filter((r) => r.transAll > 0)
  92. .sort((a, b) => b.transAll - a.transAll)
  93. .forEach((r) => console.log(`${r.file}|${r.transAll}`))
  94. console.log('\n========== getSystemInfoSync 分布 ==========')
  95. rows
  96. .filter((r) => r.sysInfo > 0)
  97. .forEach((r) => console.log(`${r.file}|${r.sysInfo}`))
  98. // 债务分层
  99. const alive = rows.filter((r) => !r.done)
  100. const buckets = { P0: [], P1: [], P2: [], P3: [] }
  101. for (const r of alive) {
  102. if (r.debt >= 120) buckets.P0.push(r)
  103. else if (r.debt >= 60) buckets.P1.push(r)
  104. else if (r.debt >= 25) buckets.P2.push(r)
  105. else buckets.P3.push(r)
  106. }
  107. console.log('\n========== 债务分层(已重制 5 页除外) ==========')
  108. for (const k of ['P0', 'P1', 'P2', 'P3']) {
  109. console.log(`${k}: ${buckets[k].length} 页`)
  110. buckets[k]
  111. .sort((a, b) => b.debt - a.debt)
  112. .slice(0, 12)
  113. .forEach((r) => console.log(` ${r.file} (debt ${r.debt.toFixed(0)})`))
  114. }