92 lines
3.0 KiB
JavaScript
92 lines
3.0 KiB
JavaScript
/**
|
||
* 功能描述:校验平台48类资源均具备中文展示字段,并防止列表、详情退回数据库ID或动态英文列。
|
||
* 版本:v1.0.0
|
||
*/
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import process from 'node:process';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
const currentDirectory = path.dirname(fileURLToPath(import.meta.url));
|
||
const root = path.resolve(currentDirectory, '..');
|
||
|
||
function read(relativePath) {
|
||
return fs.readFileSync(path.join(root, relativePath), 'utf8');
|
||
}
|
||
|
||
function assertIncludes(source, expected, message) {
|
||
if (!source.includes(expected)) throw new Error(message);
|
||
}
|
||
|
||
const resources = read('src/api/resources.ts');
|
||
const display = read('src/api/resource-display.ts');
|
||
const detailContract = read('src/api/resource-detail-contract.ts');
|
||
const detailPage = read('src/views/resource/ResourceDetailContent.vue');
|
||
const listPage = read('src/views/shared/CrudListPage.vue');
|
||
|
||
const resourceNames = [...resources.matchAll(/\bdefine\('([^']+)'/g)].map(
|
||
(match) => match[1],
|
||
);
|
||
if (resourceNames.length !== 48 || new Set(resourceNames).size !== 48) {
|
||
throw new Error(`平台资源定义应为48类,当前读取到${resourceNames.length}类`);
|
||
}
|
||
if (/define\([^;]*?'readonly'\s*,\s*\[\s*\]/s.test(resources)) {
|
||
throw new Error('只读资源不得继续使用空字段契约');
|
||
}
|
||
|
||
const labelBlock = resources.match(
|
||
/const fieldLabels: Record<string, string> = \{([\s\S]*?)\n\};/,
|
||
)?.[1];
|
||
if (!labelBlock) throw new Error('无法读取全局中文字段字典');
|
||
const labels = new Set(
|
||
[...labelBlock.matchAll(/^\s{2}([A-Za-z_][A-Za-z0-9_]*):\s*'[^']+'/gm)].map(
|
||
(match) => match[1],
|
||
),
|
||
);
|
||
const usedKeys = new Set(
|
||
[...resources.matchAll(/\b(?:f|relation)\('([^']+)'/g)].map(
|
||
(match) => match[1],
|
||
),
|
||
);
|
||
const missingLabels = [...usedKeys].filter((key) => !labels.has(key));
|
||
if (missingLabels.length) {
|
||
throw new Error(`资源字段缺少中文名称:${missingLabels.join('、')}`);
|
||
}
|
||
|
||
if (/title="ID"|data-index="id"/.test(listPage)) {
|
||
throw new Error('标准资源列表不得把数据库ID作为业务列展示');
|
||
}
|
||
assertIncludes(listPage, 'title="系统唯一标识"', '列表必须中文标注系统唯一标识');
|
||
assertIncludes(
|
||
listPage,
|
||
'class="resource-table-shell"',
|
||
'列表必须使用内部横向滚动容器',
|
||
);
|
||
assertIncludes(
|
||
display,
|
||
'hasResourceFieldLabel',
|
||
'详情必须提供未知字段中文契约检查',
|
||
);
|
||
assertIncludes(
|
||
detailPage,
|
||
'!hasResourceFieldLabel(props.definition, actualKey)',
|
||
'详情不得直接回显未配置字段',
|
||
);
|
||
assertIncludes(
|
||
detailPage,
|
||
'resourceDetailContract(props.definition.name)',
|
||
'详情关联表必须使用资源专属固定列契约',
|
||
);
|
||
for (const collection of [
|
||
'products', 'revisions', 'items', 'assignments', 'statuses',
|
||
'tracks', 'confirmations', 'payments',
|
||
]) {
|
||
assertIncludes(
|
||
detailContract,
|
||
`${collection}: {`,
|
||
`缺少关联记录固定列契约:${collection}`,
|
||
);
|
||
}
|
||
|
||
process.stdout.write('平台48类资源中文展示契约检查通过\n');
|