|
|
@@ -0,0 +1,128 @@
|
|
|
+#!/usr/bin/env node
|
|
|
+/* 全站页面美化审计 v2(2026-10-17)
|
|
|
+ * 对齐 UniForge UI 取证阶段:量化每个页面的样式债务
|
|
|
+ * 维度:硬编码色 / 旧品牌蓝 / transition:all / getSystemInfoSync / 内联样式 / px 单位 / 页面体量
|
|
|
+ */
|
|
|
+const fs = require('fs')
|
|
|
+const path = require('path')
|
|
|
+
|
|
|
+const ROOT = path.resolve(__dirname, '..')
|
|
|
+const EXCLUDE_DIRS = new Set(['node_modules', 'dist', 'unpackage', '.git', 'dist_bak_1792213156'])
|
|
|
+
|
|
|
+// 已重制完成的页面(阶段一~四 + 成员标签),不纳入待美化清单
|
|
|
+const DONE_PAGES = new Set([
|
|
|
+ 'pages/body-detail/index.vue',
|
|
|
+ 'pages/mind-detail/index.vue',
|
|
|
+ 'pages/wisdom-detail/index.vue',
|
|
|
+ 'pages/wealth/index.vue',
|
|
|
+ 'pages/action-detail/index.vue',
|
|
|
+])
|
|
|
+
|
|
|
+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 files = walk(ROOT)
|
|
|
+const HEX = /#[0-9a-fA-F]{3,8}\b/g
|
|
|
+const OLD_BLUE = /#5[Bb]9[Bb][Dd]5/g
|
|
|
+const TRANS_ALL = /transition\s*:\s*all/g
|
|
|
+const SYSINFO = /getSystemInfoSync/g
|
|
|
+const INLINE_STYLE = /style="/g
|
|
|
+const PX_UNIT = /(?<![\w-])\d+px(?![\w-])/g
|
|
|
+
|
|
|
+const rows = []
|
|
|
+for (const abs of files) {
|
|
|
+ const rel = path.relative(ROOT, abs).split(path.sep).join('/')
|
|
|
+ if (rel.startsWith('components/')) continue // 组件单独一轮
|
|
|
+ const src = fs.readFileSync(abs, 'utf8')
|
|
|
+ const styleStart = src.search(/<style/)
|
|
|
+ const styleBlock = styleStart === -1 ? '' : src.slice(styleStart)
|
|
|
+ const hexInStyle = (styleBlock.match(HEX) || []).length
|
|
|
+ const hexAll = (src.match(HEX) || []).length
|
|
|
+ rows.push({
|
|
|
+ file: rel,
|
|
|
+ lines: src.split('\n').length,
|
|
|
+ hex: hexAll,
|
|
|
+ hexStyle: hexInStyle,
|
|
|
+ oldBlue: (styleBlock.match(OLD_BLUE) || []).length,
|
|
|
+ transAll: (styleBlock.match(TRANS_ALL) || []).length,
|
|
|
+ sysInfo: (styleBlock.match(SYSINFO) || []).length,
|
|
|
+ inlineStyle: (src.match(INLINE_STYLE) || []).length,
|
|
|
+ px: (styleBlock.match(PX_UNIT) || []).length,
|
|
|
+ done: DONE_PAGES.has(rel),
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 债务分:硬编码色权重最高(与品牌令牌脱钩的直接证据),旧品牌蓝次之
|
|
|
+for (const r of rows) {
|
|
|
+ r.debt = r.hex * 3 + r.oldBlue * 5 + r.transAll * 2 + r.sysInfo * 2 + r.px * 0.1
|
|
|
+}
|
|
|
+
|
|
|
+const pagesCfg = JSON.parse(fs.readFileSync(path.join(ROOT, 'pages.json'), 'utf8'))
|
|
|
+const mainPaths = pagesCfg.pages.map((p) => p.path + '.vue')
|
|
|
+
|
|
|
+console.log('========== 汇总 ==========')
|
|
|
+console.log('页面文件总数(不含 components):', rows.length)
|
|
|
+console.log('硬编码色总数:', rows.reduce((s, r) => s + r.hex, 0))
|
|
|
+console.log('旧品牌蓝 #5B9BD5 总数:', rows.reduce((s, r) => s + r.oldBlue, 0))
|
|
|
+console.log('transition:all 总数:', rows.reduce((s, r) => s + r.transAll, 0))
|
|
|
+console.log('getSystemInfoSync 总数:', rows.reduce((s, r) => s + r.sysInfo, 0))
|
|
|
+
|
|
|
+console.log('\n========== 主包 7 门面页 ==========')
|
|
|
+console.log('file|lines|hex|oldBlue|transAll|px|inlineStyle|debt')
|
|
|
+for (const p of mainPaths) {
|
|
|
+ const r = rows.find((x) => x.file === p)
|
|
|
+ if (r) console.log(`${r.file}|${r.lines}|${r.hex}|${r.oldBlue}|${r.transAll}|${r.px}|${r.inlineStyle}|${r.debt.toFixed(1)}`)
|
|
|
+}
|
|
|
+
|
|
|
+console.log('\n========== 旧品牌蓝 TOP25(品牌残留,必须收敛) ==========')
|
|
|
+console.log('file|oldBlue|hex|lines')
|
|
|
+rows
|
|
|
+ .filter((r) => r.oldBlue > 0)
|
|
|
+ .sort((a, b) => b.oldBlue - a.oldBlue || b.hex - a.hex)
|
|
|
+ .slice(0, 25)
|
|
|
+ .forEach((r) => console.log(`${r.file}|${r.oldBlue}|${r.hex}|${r.lines}`))
|
|
|
+
|
|
|
+console.log('\n========== 硬编码色 TOP30(样式债务) ==========')
|
|
|
+console.log('file|hex|hexStyle|oldBlue|transAll|px|lines|debt|done')
|
|
|
+rows
|
|
|
+ .filter((r) => !r.done)
|
|
|
+ .sort((a, b) => b.debt - a.debt)
|
|
|
+ .slice(0, 30)
|
|
|
+ .forEach((r) => console.log(`${r.file}|${r.hex}|${r.hexStyle}|${r.oldBlue}|${r.transAll}|${r.px}|${r.lines}|${r.debt.toFixed(1)}|${r.done}`))
|
|
|
+
|
|
|
+console.log('\n========== transition:all 分布 ==========')
|
|
|
+rows
|
|
|
+ .filter((r) => r.transAll > 0)
|
|
|
+ .sort((a, b) => b.transAll - a.transAll)
|
|
|
+ .forEach((r) => console.log(`${r.file}|${r.transAll}`))
|
|
|
+
|
|
|
+console.log('\n========== getSystemInfoSync 分布 ==========')
|
|
|
+rows
|
|
|
+ .filter((r) => r.sysInfo > 0)
|
|
|
+ .forEach((r) => console.log(`${r.file}|${r.sysInfo}`))
|
|
|
+
|
|
|
+// 债务分层
|
|
|
+const alive = rows.filter((r) => !r.done)
|
|
|
+const buckets = { P0: [], P1: [], P2: [], P3: [] }
|
|
|
+for (const r of alive) {
|
|
|
+ if (r.debt >= 120) buckets.P0.push(r)
|
|
|
+ else if (r.debt >= 60) buckets.P1.push(r)
|
|
|
+ else if (r.debt >= 25) buckets.P2.push(r)
|
|
|
+ else buckets.P3.push(r)
|
|
|
+}
|
|
|
+console.log('\n========== 债务分层(已重制 5 页除外) ==========')
|
|
|
+for (const k of ['P0', 'P1', 'P2', 'P3']) {
|
|
|
+ console.log(`${k}: ${buckets[k].length} 页`)
|
|
|
+ buckets[k]
|
|
|
+ .sort((a, b) => b.debt - a.debt)
|
|
|
+ .slice(0, 12)
|
|
|
+ .forEach((r) => console.log(` ${r.file} (debt ${r.debt.toFixed(0)})`))
|
|
|
+}
|