74f801a081
- 新增模块注册表 api 与进程内缓存(首次使用拉一次、并发共用同一个请求), 启用清单驱动「可新建审批链」的模块下拉与审核列表模块筛选 - reviewFlowConfigEdit 不再维护硬编码模块清单;停用模块的历史配置按 「标题(已停用)」回显,避免下拉空白 - module → detailKey 的映射收敛到 detailKeyOf 一处,moduleDetailConfig 未命中注册表时回退 module 本身(停用模块的详情仍可打开) - 审核流每级新增「审批人规则」输入(USER:<userId> / COLLEGE_ROLE:<roleCode>, 分号拼装多段,留空沿用「指定人」),随该级提交 reviewerRule 并回显 - 新增 scripts/selfcheck-module-registry.mjs:前端无单测基建时验证下拉过滤、 detailKey 回退与停用模块回显
109 lines
4.7 KiB
JavaScript
109 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* 模块注册表前端逻辑的最小自检(仓库无单测基建,用 Node + typescript.transpileModule 跑纯函数)。
|
||
*
|
||
* 覆盖 ticket #65 的可见行为:
|
||
* 1. 「可新建审批链」下拉只含 enabled=1 的模块,按 sortNumber 升序,标题取注册表 title;
|
||
* 2. module → detailKey 的映射收在 resolveDetailKey 一处,注册表里没有该模块时回退 module 本身,
|
||
* 保证停用模块的历史审批链仍能回显标题、仍能打开详情;
|
||
* 3. 编辑历史配置时把停用的模块补进下拉(带「已停用」标记),不重复添加。
|
||
*
|
||
* 运行:node scripts/selfcheck-module-registry.mjs
|
||
*/
|
||
import { readFileSync } from 'node:fs';
|
||
import { createRequire } from 'node:module';
|
||
import { dirname, resolve } from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
const require = createRequire(import.meta.url);
|
||
const ts = require('typescript');
|
||
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||
|
||
const loadTs = (relative) => {
|
||
const file = resolve(root, relative);
|
||
const source = readFileSync(file, 'utf8');
|
||
const { outputText } = ts.transpileModule(source, {
|
||
compilerOptions: {
|
||
module: ts.ModuleKind.CommonJS,
|
||
target: ts.ScriptTarget.ES2019
|
||
},
|
||
fileName: file
|
||
});
|
||
const mod = { exports: {} };
|
||
new Function('exports', 'require', 'module', outputText)(
|
||
mod.exports,
|
||
require,
|
||
mod
|
||
);
|
||
return mod.exports;
|
||
};
|
||
|
||
let failed = 0;
|
||
const assert = (name, condition, detail) => {
|
||
if (condition) {
|
||
console.log(' ok ' + name);
|
||
return;
|
||
}
|
||
failed += 1;
|
||
console.log(' FAIL ' + name + (detail ? ' -> ' + detail : ''));
|
||
};
|
||
|
||
const core = loadTs('src/utils/module-registry-core.ts');
|
||
|
||
// 与后端种子数据一致:10 个模块里只有 4 个 enabled=1
|
||
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_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 }
|
||
];
|
||
|
||
console.log('新建审批链的模块下拉(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',
|
||
JSON.stringify(options)
|
||
);
|
||
assert('label 取注册表标题', options[0].label === '十佳青年岗位能手');
|
||
assert('停用模块不出现', !options.some((o) => o.value === 'gxmu_sjtbzbsj'));
|
||
|
||
console.log('审核列表的模块筛选项(同理,按注册表顺序)');
|
||
assert(
|
||
'与新建下拉同源',
|
||
core.toModuleOptions(REGISTRY).length === options.length
|
||
);
|
||
|
||
console.log('module → detailKey 映射(收在一处)');
|
||
assert('走注册表 detailKey', core.resolveDetailKey(REGISTRY, 'gxmu_sjqn') === 'gxmu_sjqn');
|
||
assert(
|
||
'注册表缺该模块时回退 module(停用模块的详情仍可解析)',
|
||
core.resolveDetailKey(REGISTRY, 'legacy_module') === 'legacy_module'
|
||
);
|
||
assert('空模块返回空', core.resolveDetailKey(REGISTRY, undefined) === undefined);
|
||
|
||
console.log('标题解析(历史配置回显)');
|
||
assert('注册表命中', core.resolveModuleTitle(REGISTRY, 'gxmu_qmgc_form') === '青马工程');
|
||
assert('注册表未命中回退 module 本身', core.resolveModuleTitle(REGISTRY, 'legacy_module') === 'legacy_module');
|
||
|
||
console.log('编辑历史配置:把停用模块补进下拉');
|
||
const merged = core.ensureModuleOption(options, 'gxmu_sjtbzbsj', '十佳团支部书记');
|
||
assert('补进来的项标记已停用', merged[0]?.label === '十佳团支部书记(已停用)', JSON.stringify(merged[0]));
|
||
assert('补进来的项在最前面且可回显', merged[0]?.value === 'gxmu_sjtbzbsj');
|
||
assert('原有选项保留', merged.length === options.length + 1);
|
||
const duplicated = core.ensureModuleOption(options, 'gxmu_sjqn');
|
||
assert('已在选项里则不重复添加', duplicated.length === options.length);
|
||
assert(
|
||
'无 moduleName 时用 module 当标题',
|
||
core.ensureModuleOption(options, 'legacy_module')[0]?.label === 'legacy_module(已停用)'
|
||
);
|
||
|
||
if (failed) {
|
||
console.error('\n自检失败:' + failed + ' 项');
|
||
process.exit(1);
|
||
}
|
||
console.log('\n自检通过:' + 5 + ' 组行为');
|