/** * 功能描述:校验配送点资源的独立详情、新建、编辑页面与后端契约保持一致。 * 版本: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}`);