## 变更内容 - 将全局样式拆分为设计令牌、基础规则和语义组件层 - 集中管理 Naive UI 主题配置 - 迁移页面容器、表单、信息面板和按钮等重复样式 - 增加样式体系文档与自动契约检查 ## 验证 - `npm run check:styles` - `npm run build`Reviewed-on: #4
90 lines
2.9 KiB
JavaScript
90 lines
2.9 KiB
JavaScript
import { readdir, readFile } from 'node:fs/promises'
|
|
import { extname, join, relative } from 'node:path'
|
|
|
|
const projectRoot = new URL('..', import.meta.url)
|
|
const sourceRoot = new URL('../src', import.meta.url)
|
|
|
|
const deprecatedPatterns = [
|
|
{
|
|
pattern: /\b(?:bg|border|ring|text)-(?:gray|indigo|purple|violet)-\d+\b/g,
|
|
message: '使用项目约定的 slate/sky/teal 语义色,不要重新引入灰色或紫色系。',
|
|
},
|
|
{
|
|
pattern: /text-xs uppercase tracking-\[0\.18em\] text-slate-500/g,
|
|
message: '重复的小节标签应使用 oc-section-label。',
|
|
},
|
|
{
|
|
pattern: /border border-slate-200 bg-slate-50 p-4/g,
|
|
message: '标准内嵌信息面板应使用 oc-inset-panel。',
|
|
},
|
|
{
|
|
pattern: /border-b border-sky-100 bg-\[linear-gradient\(180deg,#e0f2fe_0%,#f8fafc_100%\)\]/g,
|
|
message: '标准页面头部应使用 oc-page-hero。',
|
|
},
|
|
]
|
|
|
|
async function collectVueFiles(directory) {
|
|
const entries = await readdir(directory, { withFileTypes: true })
|
|
const files = await Promise.all(entries.map(async (entry) => {
|
|
const path = join(directory, entry.name)
|
|
return entry.isDirectory() ? collectVueFiles(path) : [path]
|
|
}))
|
|
|
|
return files.flat().filter((path) => extname(path) === '.vue')
|
|
}
|
|
|
|
function lineNumberAt(source, index) {
|
|
return source.slice(0, index).split('\n').length
|
|
}
|
|
|
|
const violations = []
|
|
|
|
for (const file of await collectVueFiles(sourceRoot.pathname)) {
|
|
const source = await readFile(file, 'utf8')
|
|
|
|
for (const rule of deprecatedPatterns) {
|
|
for (const match of source.matchAll(rule.pattern)) {
|
|
violations.push({
|
|
file: relative(projectRoot.pathname, file),
|
|
line: lineNumberAt(source, match.index),
|
|
message: rule.message,
|
|
value: match[0],
|
|
})
|
|
}
|
|
}
|
|
|
|
for (const match of source.matchAll(/(?:^|\s)class="([^"]+)"/gm)) {
|
|
const classes = match[1].split(/\s+/)
|
|
const panelModifier = classes.some((name) => name.startsWith('oc-panel-button--'))
|
|
const primaryModifier = classes.some((name) => name.startsWith('oc-primary-button--'))
|
|
|
|
if (panelModifier && !classes.includes('oc-panel-button')) {
|
|
violations.push({
|
|
file: relative(projectRoot.pathname, file),
|
|
line: lineNumberAt(source, match.index),
|
|
message: 'oc-panel-button 修饰类必须与基础类一起使用。',
|
|
value: match[0],
|
|
})
|
|
}
|
|
|
|
if (primaryModifier && !classes.includes('oc-primary-button')) {
|
|
violations.push({
|
|
file: relative(projectRoot.pathname, file),
|
|
line: lineNumberAt(source, match.index),
|
|
message: 'oc-primary-button 修饰类必须与基础类一起使用。',
|
|
value: match[0],
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if (violations.length > 0) {
|
|
console.error('样式体系检查失败:')
|
|
for (const violation of violations) {
|
|
console.error(`- ${violation.file}:${violation.line} ${violation.message} (${violation.value})`)
|
|
}
|
|
process.exitCode = 1
|
|
} else {
|
|
console.log('样式体系检查通过。')
|
|
}
|