Files
platforms/frontend/delivery_admin/scripts/check-resource-pages.mjs
2026-08-22 23:24:12 +08:00

93 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 功能描述:校验配送点资源的独立详情、新建、编辑页面与后端契约保持一致。
* 版本v1.0.0。
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const contract = JSON.parse(fs.readFileSync(
path.join(projectRoot, 'src/contracts/delivery-resources.json'),
'utf8',
));
const read = (relativePath) => fs.readFileSync(path.join(projectRoot, relativePath), 'utf8');
const routeKeys = new Set(contract.routes.map((route) => `${route.method} ${route.path}`));
const listResources = contract.resources.filter(
(resource) => resource.pageKind === 'list' && resource.name !== 'delivery_profile',
);
const creatable = new Set([
'staff_account', 'staff_credential', 'user_account', 'user_address',
'gasorder_contract', 'gasorder_contract_product', 'gasorder_basic',
'wallet_recharge', 'wallet_apply_cash',
]);
const editable = new Set([
'staff_account', 'staff_credential', 'user_account', 'user_address', 'gasorder_contract',
]);
const resourcesByName = new Map(contract.resources.map((resource) => [resource.name, resource]));
/** 在条件不成立时中止检查,并给出可直接定位的原因。 */
function assert(condition, message) {
if (!condition) throw new Error(message);
}
for (const resource of listResources) {
assert(
routeKeys.has(`GET ${resource.path}/:identity`),
`${resource.name} 缺少详情接口`,
);
if (creatable.has(resource.name)) {
assert(routeKeys.has(`POST ${resource.path}`), `${resource.name} 缺少新建接口`);
}
if (editable.has(resource.name)) {
assert(routeKeys.has(`PUT ${resource.path}/:identity`), `${resource.name} 缺少编辑接口`);
}
}
assert(listResources.length === 17, `详情页面资源应为 17 个,当前为 ${listResources.length}`);
assert(creatable.size === 9, `新建页面资源应为 9 个,当前为 ${creatable.size}`);
assert(editable.size === 5, `编辑页面资源应为 5 个,当前为 ${editable.size}`);
const builder = read('src/router/routes/modules/resource-route-builder.ts');
const platform = read('src/router/routes/modules/platform.ts');
const resourcePage = read('src/views/shared/ResourcePage.vue');
const definitions = read('src/api/resources.ts');
assert(builder.includes("recordRoute('detail', ':identity'"), '路由生成器未创建详情页');
assert(builder.includes("recordRoute('create', 'new'"), '路由生成器未创建新建页');
assert(builder.includes("recordRoute('edit', ':identity/edit'"), '路由生成器未创建编辑页');
assert(platform.includes("createChild('add'"), '工作人员正式新建路由必须保留 /staff/add');
assert(platform.includes("createChild('create'"), '配送订单正式新建路由必须保留 /gasorder/create');
assert(resourcePage.includes('ResourceListPage'), '标准资源列表尚未切换到独立页面导航');
assert(!resourcePage.includes('CrudListPage'), '活动列表仍依赖抽屉式 CrudListPage');
assert(definitions.includes("define('payment_order'"), '支付资源必须使用后端名称 payment_order');
assert(definitions.includes("define('payment_refund'"), '退款资源必须使用后端名称 payment_refund');
assert(routeKeys.has('GET /staff_account/:identity/avatar'), '配送人员缺少受保护头像读取接口');
assert(routeKeys.has('GET /user_account/:identity/avatar'), '用户账户缺少受保护头像读取接口');
const recordPage = read('src/views/resource/ResourceRecordPage.vue');
assert(recordPage.includes('ResourceAccountSummary'), '账户资源页尚未接入 5173 头像摘要卡片');
assert(recordPage.includes("field.key !== 'avatar'"), '头像字段仍可能显示为普通文本框');
const listPage = read('src/views/shared/ResourceListPage.vue');
assert(!listPage.includes('title="ID"'), '标准列表仍暴露数据库自增 ID');
assert(listPage.includes('ProtectedAvatarThumbnail'), '账户列表尚未接入受控头像缩略图');
assert(listPage.includes('avatarLoader.reset()'), '列表刷新未清理头像缓存和请求');
const avatarLoader = read('src/views/shared/protected-list-avatar-loader.ts');
assert(avatarLoader.includes('MAX_CONCURRENT_REQUESTS = 6'), '头像请求并发上限未与 5173 对齐');
assert(avatarLoader.includes("new Set(['staff_account', 'user_account'])"), '头像列表资源白名单不正确');
assert(
resourcesByName.get('staff_credential').searchFields.map((field) => field.key).join(',') ===
'credential_type,credential_no',
'人员资质搜索契约必须包含资质类型和资质编号',
);
for (const name of ['delivery_profile', 'user_address', 'wallet_bank']) {
assert(!(resourcesByName.get(name).searchFields?.length), `${name} 不应显示无效搜索`);
}
assert(listPage.includes('v-if="searchEnabled"'), '列表搜索区域未按后端契约控制显示');
assert(listPage.includes('label="模糊搜索"'), '列表搜索标签未明确说明模糊匹配');
assert(!listPage.includes('label="关键字"'), '列表仍使用含义不清的关键字标签');
assert(!listPage.includes('placeholder="关键字段模糊搜索"'), '列表仍使用无意义的通用搜索提示');
assert(listPage.includes('ensureCredentialContext'), '人员资质列表缺少服务端人员上下文校验');
assert(listPage.includes("field.key === 'staff_account_identity'"), '人员范围资质列表未隐藏重复人员列');
assert(recordPage.includes('已锁定,不可更换'), '人员资质独立页未锁定所属配送人员');
console.log(`独立资源页面契约通过:详情 ${listResources.length},新建 ${creatable.size},编辑 ${editable.size}`);