fix(gxmu): 修复 spec #58 双轴 review 的 admin 侧问题
1. 新建审批链丢审批人规则:新建/编辑共用 toReviewFlowPayload,reviewerRule 不再漏带; 自检新增「审批链保存体」一组断言。 2. 审核列表模块筛选取注册表全量清单(?all=true),停用模块标题带「(已停用)」; 只有审批链配置下拉仍用启用集。 3. 审批人规则 placeholder 换成真实角色码 YouthLeagueCommitteeOfSecondaryColleges。 4. getModuleDetailConfig 改为显式传入注册表快照(纯函数),消除加载前后配置不一致; 快照在 module-registry 里冻结,并落 localStorage 供失败兜底。 5. 自检脚本:fixture 与种子对齐(10 行、5 个启用,含 cms_manuscript), 「5 组行为」改为按实际组数统计;新增 npm run selfcheck:module-registry。 6. 注册表取不到时:保留上次成功清单 + 顶部 a-alert 提示 + 重试按钮(不静默留空下拉)。 门禁:npm run build 通过;npm run selfcheck:module-registry 6 组行为全绿。
This commit is contained in:
@@ -2,13 +2,16 @@
|
||||
/**
|
||||
* 模块注册表前端逻辑的最小自检(仓库无单测基建,用 Node + typescript.transpileModule 跑纯函数)。
|
||||
*
|
||||
* 覆盖 ticket #65 的可见行为:
|
||||
* 覆盖 ticket #65 与 spec #58 修复后的可见行为:
|
||||
* 1. 「可新建审批链」下拉只含 enabled=1 的模块,按 sortNumber 升序,标题取注册表 title;
|
||||
* 2. module → detailKey 的映射收在 resolveDetailKey 一处,注册表里没有该模块时回退 module 本身,
|
||||
* 保证停用模块的历史审批链仍能回显标题、仍能打开详情;
|
||||
* 3. 编辑历史配置时把停用的模块补进下拉(带「已停用」标记),不重复添加。
|
||||
* 2. 审核列表的模块筛选用全量清单(含停用模块),停用项标题带「(已停用)」,
|
||||
* 且只有审批链配置下拉用启用清单;
|
||||
* 3. module → detailKey 的映射收在 resolveDetailKey 一处,注册表里没有该模块时回退 module 本身;
|
||||
* 4. 标题解析:注册表命中取 title,未命中回退 module 本身;
|
||||
* 5. 编辑历史配置时把停用的模块补进下拉(带「已停用」标记),不重复添加;
|
||||
* 6. 审批链保存体:新建与编辑两条分支都必须带上每级的 reviewerRule(spec #58 A5)。
|
||||
*
|
||||
* 运行:node scripts/selfcheck-module-registry.mjs
|
||||
* 运行:npm run selfcheck:module-registry
|
||||
*/
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { createRequire } from 'node:module';
|
||||
@@ -38,7 +41,10 @@ const loadTs = (relative) => {
|
||||
return mod.exports;
|
||||
};
|
||||
|
||||
const readSource = (relative) => readFileSync(resolve(root, relative), 'utf8');
|
||||
|
||||
let failed = 0;
|
||||
let groupCount = 0;
|
||||
const assert = (name, condition, detail) => {
|
||||
if (condition) {
|
||||
console.log(' ok ' + name);
|
||||
@@ -47,37 +53,71 @@ const assert = (name, condition, detail) => {
|
||||
failed += 1;
|
||||
console.log(' FAIL ' + name + (detail ? ' -> ' + detail : ''));
|
||||
};
|
||||
/** 开始一组行为断言,结尾按实际组数统计,避免硬编码组数 */
|
||||
const group = (name) => {
|
||||
groupCount += 1;
|
||||
console.log(name);
|
||||
};
|
||||
|
||||
const core = loadTs('src/utils/module-registry-core.ts');
|
||||
const payloadBuilder = loadTs(
|
||||
'src/views/gxmu/reviewFlow/components/reviewFlowPayload.ts'
|
||||
);
|
||||
|
||||
// 与后端种子数据一致:10 个模块里只有 4 个 enabled=1
|
||||
// 与后端种子一致:sql/20261110_spec58_approval_chain.sql 里 10 个模块,
|
||||
// 其中 5 个 enabled=1(含稿件 cms_manuscript,它有在跑的审核链)。
|
||||
const REGISTRY = [
|
||||
{ module: 'gxmu_sjqn', detailKey: 'gxmu_sjqn', title: '十佳青年岗位能手', enabled: 1, sortNumber: 1 },
|
||||
{ module: 'gxmu_sjtbzbsj', detailKey: 'gxmu_sjtbzbsj', title: '十佳团支部书记', enabled: 0, sortNumber: 2 },
|
||||
{ module: 'gxmu_wshqtw', detailKey: 'gxmu_wshqtw', title: '五四红旗团委', enabled: 0, sortNumber: 3 },
|
||||
{ module: 'gxmu_wshqtzb', detailKey: 'gxmu_wshqtzb', title: '五四红旗团支部', enabled: 0, sortNumber: 4 },
|
||||
{ module: 'gxmu_yxgqtdgb', detailKey: 'gxmu_yxgqtdgb', title: '优秀共青团干部', enabled: 0, sortNumber: 5 },
|
||||
{ module: 'gxmu_yxgqty', detailKey: 'gxmu_yxgqty', title: '优秀共青团员', enabled: 0, sortNumber: 6 },
|
||||
{ module: 'gxmu_tzbcy_form', detailKey: 'gxmu_tzbcy_form', title: '挑战杯', enabled: 1, sortNumber: 7 },
|
||||
{ module: 'gxmu_qmgc_form', detailKey: 'gxmu_qmgc_form', title: '青马工程', enabled: 1, sortNumber: 8 },
|
||||
{ module: 'gxmu_wxxzx_project', detailKey: 'gxmu_wxxzx_project', title: '未来学术之星', enabled: 1, sortNumber: 9 },
|
||||
{ module: 'cms_manuscript', detailKey: 'cms_manuscript', title: '稿件', enabled: 0, sortNumber: 10 }
|
||||
{ module: 'cms_manuscript', detailKey: 'cms_manuscript', title: '稿件', enabled: 1, sortNumber: 10 }
|
||||
];
|
||||
|
||||
console.log('新建审批链的模块下拉(enabled=1,按 sortNumber)');
|
||||
group('新建审批链的模块下拉(enabled=1,按 sortNumber)');
|
||||
const options = core.toModuleOptions(REGISTRY);
|
||||
assert(
|
||||
'只含已启用模块',
|
||||
options.map((o) => o.value).join(',') ===
|
||||
'gxmu_sjqn,gxmu_tzbcy_form,gxmu_qmgc_form,gxmu_wxxzx_project',
|
||||
'gxmu_sjqn,gxmu_tzbcy_form,gxmu_qmgc_form,gxmu_wxxzx_project,cms_manuscript',
|
||||
JSON.stringify(options)
|
||||
);
|
||||
assert('label 取注册表标题', options[0].label === '十佳青年岗位能手');
|
||||
assert('停用模块不出现', !options.some((o) => o.value === 'gxmu_sjtbzbsj'));
|
||||
|
||||
console.log('审核列表的模块筛选项(同理,按注册表顺序)');
|
||||
group('审核列表的模块筛选(全量清单,停用项带标记)');
|
||||
const filterOptions = core.toAllModuleOptions(REGISTRY);
|
||||
assert('含停用模块', filterOptions.length === REGISTRY.length, String(filterOptions.length));
|
||||
assert(
|
||||
'与新建下拉同源',
|
||||
core.toModuleOptions(REGISTRY).length === options.length
|
||||
'按 sortNumber 升序',
|
||||
filterOptions.map((o) => o.value).join(',') === REGISTRY.map((r) => r.module).join(','),
|
||||
JSON.stringify(filterOptions.map((o) => o.value))
|
||||
);
|
||||
const disabledOption = filterOptions.find((o) => o.value === 'gxmu_sjtbzbsj');
|
||||
assert('停用项标题带「(已停用)」', disabledOption?.label === '十佳团支部书记(已停用)', JSON.stringify(disabledOption));
|
||||
assert('启用项标题不加标记', filterOptions.find((o) => o.value === 'gxmu_sjqn')?.label === '十佳青年岗位能手');
|
||||
assert(
|
||||
'停用模块仍能被筛出来(ADR 0006 历史数据仍可查)',
|
||||
['gxmu_sjtbzbsj', 'gxmu_wshqtw', 'gxmu_wshqtzb', 'gxmu_yxgqtdgb', 'gxmu_yxgqty'].every((m) =>
|
||||
filterOptions.some((o) => o.value === m)
|
||||
)
|
||||
);
|
||||
const searchSource = readSource('src/views/gxmu/reviewList/components/search.vue');
|
||||
assert('审核列表筛选入口用 allModuleOptions', /allModuleOptions\(/.test(searchSource));
|
||||
const configEditSource = readSource(
|
||||
'src/views/gxmu/reviewFlowConfig/components/reviewFlowConfigEdit.vue'
|
||||
);
|
||||
assert(
|
||||
'审批链配置下拉仍只用启用清单',
|
||||
/enabledModuleOptions\(/.test(configEditSource) && !/allModuleOptions\(/.test(configEditSource)
|
||||
);
|
||||
|
||||
console.log('module → detailKey 映射(收在一处)');
|
||||
group('module → detailKey 映射(收在一处)');
|
||||
assert('走注册表 detailKey', core.resolveDetailKey(REGISTRY, 'gxmu_sjqn') === 'gxmu_sjqn');
|
||||
assert(
|
||||
'注册表缺该模块时回退 module(停用模块的详情仍可解析)',
|
||||
@@ -85,11 +125,11 @@ assert(
|
||||
);
|
||||
assert('空模块返回空', core.resolveDetailKey(REGISTRY, undefined) === undefined);
|
||||
|
||||
console.log('标题解析(历史配置回显)');
|
||||
group('标题解析(历史配置回显)');
|
||||
assert('注册表命中', core.resolveModuleTitle(REGISTRY, 'gxmu_qmgc_form') === '青马工程');
|
||||
assert('注册表未命中回退 module 本身', core.resolveModuleTitle(REGISTRY, 'legacy_module') === 'legacy_module');
|
||||
|
||||
console.log('编辑历史配置:把停用模块补进下拉');
|
||||
group('编辑历史配置:把停用模块补进下拉');
|
||||
const merged = core.ensureModuleOption(options, 'gxmu_sjtbzbsj', '十佳团支部书记');
|
||||
assert('补进来的项标记已停用', merged[0]?.label === '十佳团支部书记(已停用)', JSON.stringify(merged[0]));
|
||||
assert('补进来的项在最前面且可回显', merged[0]?.value === 'gxmu_sjtbzbsj');
|
||||
@@ -101,8 +141,48 @@ assert(
|
||||
core.ensureModuleOption(options, 'legacy_module')[0]?.label === 'legacy_module(已停用)'
|
||||
);
|
||||
|
||||
group('审批链保存体:新建与编辑都带 reviewerRule(spec #58 A5)');
|
||||
const newChainForm = {
|
||||
title: '新链',
|
||||
organizationIds: [1, 2],
|
||||
flowItems: [{ organizationId: 1 }, { organizationId: 2 }],
|
||||
id: undefined
|
||||
};
|
||||
const created = payloadBuilder.toReviewFlowPayload(
|
||||
newChainForm,
|
||||
{ organizationId: 1, reviewerRule: ' COLLEGE_ROLE:YouthLeagueCommitteeOfSecondaryColleges ' },
|
||||
1
|
||||
);
|
||||
assert(
|
||||
'新建分支带 reviewerRule(已 trim)',
|
||||
created.reviewerRule === 'COLLEGE_ROLE:YouthLeagueCommitteeOfSecondaryColleges',
|
||||
JSON.stringify(created.reviewerRule)
|
||||
);
|
||||
assert('新建分支带 level', created.level === 1);
|
||||
assert(
|
||||
'提交体不带前端专用字段',
|
||||
!('organizationIds' in created) && !('flowItems' in created) && !('id' in created)
|
||||
);
|
||||
assert(
|
||||
'未填规则时写 undefined 而不是空串',
|
||||
payloadBuilder.toReviewFlowPayload(newChainForm, { organizationId: 2 }, 2).reviewerRule === undefined
|
||||
);
|
||||
const edited = payloadBuilder.toReviewFlowPayload(
|
||||
newChainForm,
|
||||
{ organizationId: 2, reviewUserId: 7, reviewerRule: 'USER:12' },
|
||||
2
|
||||
);
|
||||
assert('编辑分支同样带 reviewerRule', edited.reviewerRule === 'USER:12');
|
||||
assert('编辑分支保留指定人', edited.reviewUserId === 7);
|
||||
const flowEditSource = readSource('src/views/gxmu/reviewFlow/components/reviewFlowEdit.vue');
|
||||
assert(
|
||||
'新建/编辑两条分支共用同一份构造逻辑(不再逐字段手拼)',
|
||||
(flowEditSource.match(/toReviewFlowPayload\(/g) || []).length === 2 &&
|
||||
!/const formData: ReviewFlow = \{/.test(flowEditSource)
|
||||
);
|
||||
|
||||
if (failed) {
|
||||
console.error('\n自检失败:' + failed + ' 项');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('\n自检通过:' + 5 + ' 组行为');
|
||||
console.log('\n自检通过:' + groupCount + ' 组行为');
|
||||
|
||||
Reference in New Issue
Block a user