task-3(壳层/路由): 定义并锁定 AdminLayout 页面容器职责契约
壳层只做页面容器:唯一 RouterView 挂载业务视图、不静态 import 业务页面、 不直接调后台 API、Element Plus 按需命名导入;页面接线归 router 懒加载。 配套 tests/helpers.ts 提供只读源码分析与 @/ import 可解析校验。
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
|
||||
const ROOT = process.cwd()
|
||||
const SRC = join(ROOT, 'src')
|
||||
const EXTENSIONS = ['', '.ts', '.tsx', '.vue', '.js', '.json']
|
||||
const INDEX_CANDIDATES = ['/index.ts', '/index.tsx', '/index.vue', '/index.js']
|
||||
|
||||
/** 读取工程内源码文件(相对工程根)。 */
|
||||
export function readSource(rel: string): string {
|
||||
return readFileSync(join(ROOT, rel), 'utf8')
|
||||
}
|
||||
|
||||
/** 统计非重叠子串出现次数(幂等、只读)。 */
|
||||
export function occurrences(text: string, token: string): number {
|
||||
if (!token) return 0
|
||||
let count = 0
|
||||
let index = text.indexOf(token)
|
||||
while (index !== -1) {
|
||||
count += 1
|
||||
index = text.indexOf(token, index + token.length)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
/** 从源码里抓出所有 @/ 别名 import 说明符(含 import type)。 */
|
||||
export function aliasImports(source: string): string[] {
|
||||
const specs = new Set<string>()
|
||||
const re = /(?:import\s+type\s+)?[^'"]*from\s*['"](@\/[^'"]+)['"]/g
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = re.exec(source))) specs.add(match[1])
|
||||
// 处理动态 import('@/...') 与直接裸 @/ 引用
|
||||
const dyn = /(?:import|import\s*\(\s*)['"](@\/[^'"]+)['"]/g
|
||||
while ((match = dyn.exec(source))) specs.add(match[1])
|
||||
return [...specs]
|
||||
}
|
||||
|
||||
/** 把 import 说明符解析为存在的模块文件;解析不到返回 null(含可操作路径)。 */
|
||||
export function resolveModuleSpec(spec: string, fromRel: string): string | null {
|
||||
let base: string
|
||||
if (spec.startsWith('@/')) {
|
||||
base = join(SRC, spec.slice(2))
|
||||
} else {
|
||||
base = resolve(dirname(join(ROOT, fromRel)), spec)
|
||||
}
|
||||
for (const ext of EXTENSIONS) {
|
||||
if (existsSync(base + ext)) return base + ext
|
||||
}
|
||||
for (const idx of INDEX_CANDIDATES) {
|
||||
if (existsSync(base + idx)) return base + idx
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 校验一个源文件的所有 @/ import 都能解析;返回缺失清单(空 = 全部可解析)。 */
|
||||
export function missingImports(rel: string): string[] {
|
||||
const source = readSource(rel)
|
||||
const missing: string[] = []
|
||||
for (const spec of aliasImports(source)) {
|
||||
if (!resolveModuleSpec(spec, rel)) missing.push(`${rel} 无法解析 import: ${spec}`)
|
||||
}
|
||||
return missing
|
||||
}
|
||||
Reference in New Issue
Block a user