merge: #65 admin 模块清单改由注册表驱动并支持审批人规则
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
#!/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 + ' 组行为');
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import type { ApiResult } from '@/api';
|
||||||
|
import type { ModuleRegistry } from './model';
|
||||||
|
import { MODULES_API_URL } from '@/config/setting';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用模块清单(注册表唯一来源,前端不再维护模块清单)
|
||||||
|
*/
|
||||||
|
export async function listModuleRegistry() {
|
||||||
|
const res = await request.get<ApiResult<ModuleRegistry[]>>(
|
||||||
|
MODULES_API_URL + '/gxmu/module-registry/list'
|
||||||
|
);
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
return res.data.data || [];
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 模块注册表:业务模块元数据的唯一来源,见 docs/adr/0006-approval-chain-convergence.md。
|
||||||
|
*/
|
||||||
|
export interface ModuleRegistry {
|
||||||
|
// 主键
|
||||||
|
id?: number;
|
||||||
|
// 模块标识,同时是业务表名
|
||||||
|
module?: string;
|
||||||
|
// 业务表名,必须与 module 一致
|
||||||
|
tableName?: string;
|
||||||
|
// 模块标题
|
||||||
|
title?: string;
|
||||||
|
// 详情页配置键(moduleDetailConfig.ts)
|
||||||
|
detailKey?: string;
|
||||||
|
// 导出模板标识,空表示走默认导出
|
||||||
|
exportTemplate?: string;
|
||||||
|
// 是否启用:0否 1是
|
||||||
|
enabled?: number;
|
||||||
|
// 排序号,小的在前
|
||||||
|
sortNumber?: number;
|
||||||
|
}
|
||||||
@@ -17,6 +17,9 @@ export interface ReviewFlow {
|
|||||||
// 租户id
|
// 租户id
|
||||||
tenantId?: number;
|
tenantId?: number;
|
||||||
reviewUserId?: number;
|
reviewUserId?: number;
|
||||||
|
// 审批人规则,';' 分隔可拼装多段:USER:<userId> / COLLEGE_ROLE:<roleCode>,
|
||||||
|
// 为空回退 reviewUserId(见 docs/adr/0006-approval-chain-convergence.md)
|
||||||
|
reviewerRule?: string;
|
||||||
// 创建时间
|
// 创建时间
|
||||||
createTime?: string;
|
createTime?: string;
|
||||||
// 修改时间
|
// 修改时间
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export interface ReviewFlowConfig {
|
|||||||
flowId?: number;
|
flowId?: number;
|
||||||
// 模块
|
// 模块
|
||||||
module?: string;
|
module?: string;
|
||||||
|
// 模块名称(后端填充)
|
||||||
|
moduleName?: string;
|
||||||
// 是否删除, 0否, 1是
|
// 是否删除, 0否, 1是
|
||||||
deleted?: number;
|
deleted?: number;
|
||||||
// 租户id
|
// 租户id
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* 模块注册表的纯逻辑:选项构造、module → detailKey 映射、标题回退。
|
||||||
|
*
|
||||||
|
* 这里刻意不依赖 request / Vue,便于 scripts/selfcheck-module-registry.mjs
|
||||||
|
* 在无单测基建的前端仓库里直接验证行为。副作用(拉取与缓存)在
|
||||||
|
* src/utils/module-registry.ts。
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** 注册表条目(与后端 ModuleRegistry 字段一致) */
|
||||||
|
export interface RegistryModule {
|
||||||
|
module?: string;
|
||||||
|
title?: string;
|
||||||
|
detailKey?: string;
|
||||||
|
enabled?: number;
|
||||||
|
sortNumber?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 下拉选项 */
|
||||||
|
export interface ModuleOption {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造模块下拉选项:只含启用模块,按 sortNumber 升序,标题取注册表 title。
|
||||||
|
*
|
||||||
|
* 停用模块(enabled=0)不出现在「可新建审批链」的下拉里,历史数据仍可查,
|
||||||
|
* 见 docs/adr/0006-approval-chain-convergence.md。
|
||||||
|
*/
|
||||||
|
export const toModuleOptions = (list?: RegistryModule[]): ModuleOption[] => {
|
||||||
|
return (list || [])
|
||||||
|
.filter((item) => !!item.module && item.enabled === 1)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => (a.sortNumber ?? 0) - (b.sortNumber ?? 0))
|
||||||
|
.map((item) => ({
|
||||||
|
label: item.title || item.module || '',
|
||||||
|
value: item.module || ''
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* module → 详情页配置键(moduleDetailConfig.ts 的键)的唯一映射。
|
||||||
|
*
|
||||||
|
* 注册表里没有该模块时回退 module 本身:停用模块的历史审批链仍能打开详情,
|
||||||
|
* 而注册表的 detail_key 与 module 不一致时以注册表为准。
|
||||||
|
*/
|
||||||
|
export const resolveDetailKey = (
|
||||||
|
list: RegistryModule[] | undefined,
|
||||||
|
module?: string
|
||||||
|
): string | undefined => {
|
||||||
|
if (!module) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const item = (list || []).find((entry) => entry.module === module);
|
||||||
|
return item?.detailKey || module;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 模块标题,注册表未命中时回退 module 本身,避免回显成空白 */
|
||||||
|
export const resolveModuleTitle = (
|
||||||
|
list: RegistryModule[] | undefined,
|
||||||
|
module?: string
|
||||||
|
): string | undefined => {
|
||||||
|
if (!module) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const item = (list || []).find((entry) => entry.module === module);
|
||||||
|
return item?.title || module;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑存量配置时把当前值补进下拉。
|
||||||
|
*
|
||||||
|
* 停用模块不在启用清单里,但历史审批链仍要回显标题而不是空白/裸 module 值,
|
||||||
|
* 因此按「标题(已停用)」补一项;已经在清单里则原样返回。
|
||||||
|
*/
|
||||||
|
export const ensureModuleOption = (
|
||||||
|
options: ModuleOption[],
|
||||||
|
module?: string,
|
||||||
|
moduleName?: string
|
||||||
|
): ModuleOption[] => {
|
||||||
|
if (!module || options.some((item) => item.value === module)) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
return [{ label: `${moduleName || module}(已停用)`, value: module }, ...options];
|
||||||
|
};
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { listModuleRegistry } from '@/api/gxmu/moduleRegistry';
|
||||||
|
import type { ModuleRegistry } from '@/api/gxmu/moduleRegistry/model';
|
||||||
|
import {
|
||||||
|
ensureModuleOption,
|
||||||
|
resolveDetailKey,
|
||||||
|
resolveModuleTitle,
|
||||||
|
toModuleOptions
|
||||||
|
} from './module-registry-core';
|
||||||
|
import type { ModuleOption } from './module-registry-core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块注册表缓存:首次使用时拉一次,进程内缓存;并发调用共用同一个请求。
|
||||||
|
*/
|
||||||
|
let cache: ModuleRegistry[] = [];
|
||||||
|
let pending: Promise<ModuleRegistry[]> | null = null;
|
||||||
|
|
||||||
|
/** 当前缓存(未加载时为空数组) */
|
||||||
|
export const getModuleRegistryCache = (): ModuleRegistry[] => cache;
|
||||||
|
|
||||||
|
/** 清空缓存(注册表变更后手动失效) */
|
||||||
|
export const invalidateModuleRegistry = (): void => {
|
||||||
|
cache = [];
|
||||||
|
pending = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载模块注册表。默认命中缓存直接返回;force 为 true 时重新拉取。
|
||||||
|
*/
|
||||||
|
export const loadModuleRegistry = async (
|
||||||
|
force = false
|
||||||
|
): Promise<ModuleRegistry[]> => {
|
||||||
|
if (!force && cache.length) {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
if (!pending) {
|
||||||
|
pending = listModuleRegistry()
|
||||||
|
.then((list) => {
|
||||||
|
cache = list || [];
|
||||||
|
return cache;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
pending = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return pending;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 「可新建审批链」的模块下拉:只含启用模块 */
|
||||||
|
export const enabledModuleOptions = (): ModuleOption[] =>
|
||||||
|
toModuleOptions(cache);
|
||||||
|
|
||||||
|
/** module → 详情页配置键,映射收在这一处 */
|
||||||
|
export const detailKeyOf = (module?: string): string | undefined =>
|
||||||
|
resolveDetailKey(cache, module);
|
||||||
|
|
||||||
|
/** 模块标题,注册表未命中时回退 module 本身 */
|
||||||
|
export const moduleTitleOf = (module?: string): string | undefined =>
|
||||||
|
resolveModuleTitle(cache, module);
|
||||||
|
|
||||||
|
/** 编辑存量配置时把停用模块补进下拉,保证标题回显 */
|
||||||
|
export const withLegacyModuleOption = (
|
||||||
|
options: ModuleOption[],
|
||||||
|
module?: string,
|
||||||
|
moduleName?: string
|
||||||
|
): ModuleOption[] => ensureModuleOption(options, module, moduleName);
|
||||||
@@ -63,6 +63,12 @@
|
|||||||
@focus="loadReviewUsers(item.organizationId)"
|
@focus="loadReviewUsers(item.organizationId)"
|
||||||
@update:value="updateReviewUser(index, $event)"
|
@update:value="updateReviewUser(index, $event)"
|
||||||
/>
|
/>
|
||||||
|
<a-input
|
||||||
|
allow-clear
|
||||||
|
:value="item.reviewerRule"
|
||||||
|
:placeholder="reviewerRulePlaceholder"
|
||||||
|
@update:value="updateReviewerRule(index, $event)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<a-space :size="4">
|
<a-space :size="4">
|
||||||
<a-button
|
<a-button
|
||||||
@@ -109,6 +115,8 @@
|
|||||||
interface ReviewFlowItem {
|
interface ReviewFlowItem {
|
||||||
organizationId?: number;
|
organizationId?: number;
|
||||||
reviewUserId?: number;
|
reviewUserId?: number;
|
||||||
|
// 审批人规则:';' 分隔可拼装多段,USER:<userId> / COLLEGE_ROLE:<roleCode>
|
||||||
|
reviewerRule?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReviewFlowForm extends Omit<ReviewFlow, 'reviewUserId'> {
|
interface ReviewFlowForm extends Omit<ReviewFlow, 'reviewUserId'> {
|
||||||
@@ -148,6 +156,9 @@
|
|||||||
const reviewUserOptionsMap = ref<Record<number, User[]>>({});
|
const reviewUserOptionsMap = ref<Record<number, User[]>>({});
|
||||||
const reviewUserLoadingMap = ref<Record<number, boolean>>({});
|
const reviewUserLoadingMap = ref<Record<number, boolean>>({});
|
||||||
const reviewUserNameMap = ref<Record<number, string>>({});
|
const reviewUserNameMap = ref<Record<number, string>>({});
|
||||||
|
// 审批人规则语法见 docs/adr/0006-approval-chain-convergence.md
|
||||||
|
const reviewerRulePlaceholder =
|
||||||
|
'审批人规则:USER:12 或 COLLEGE_ROLE:COLLEGE_LEAGUE_SECRETARY,可用 ; 拼装多段,留空沿用「指定人」';
|
||||||
|
|
||||||
// 用户信息
|
// 用户信息
|
||||||
const form = reactive<ReviewFlowForm>({
|
const form = reactive<ReviewFlowForm>({
|
||||||
@@ -190,9 +201,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: async () => {
|
validator: async () => {
|
||||||
const invalid = form.flowItems.some((item) => !item.reviewUserId);
|
const invalid = form.flowItems.some(
|
||||||
|
(item) => !item.reviewUserId && !item.reviewerRule?.trim()
|
||||||
|
);
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
return Promise.reject(new Error('请为每一级选择审核人'));
|
return Promise.reject(
|
||||||
|
new Error('请为每一级选择审核人或填写审批人规则')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
@@ -357,6 +372,12 @@
|
|||||||
syncOrganizationIds();
|
syncOrganizationIds();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateReviewerRule = (index: number, reviewerRule?: string) => {
|
||||||
|
const items = [...form.flowItems];
|
||||||
|
items[index] = { ...items[index], reviewerRule };
|
||||||
|
form.flowItems = items;
|
||||||
|
};
|
||||||
|
|
||||||
const getSortedFlows = () => {
|
const getSortedFlows = () => {
|
||||||
if (props.data?.flows?.length) {
|
if (props.data?.flows?.length) {
|
||||||
return [...props.data.flows].sort(
|
return [...props.data.flows].sort(
|
||||||
@@ -399,6 +420,7 @@
|
|||||||
title: form.title,
|
title: form.title,
|
||||||
organizationId: item.organizationId,
|
organizationId: item.organizationId,
|
||||||
reviewUserId: item.reviewUserId,
|
reviewUserId: item.reviewUserId,
|
||||||
|
reviewerRule: item.reviewerRule?.trim() || undefined,
|
||||||
level: index + 1
|
level: index + 1
|
||||||
};
|
};
|
||||||
delete (formData as ReviewFlow & { organizationIds?: number[] })
|
delete (formData as ReviewFlow & { organizationIds?: number[] })
|
||||||
@@ -452,7 +474,8 @@
|
|||||||
.filter((item) => typeof item.organizationId === 'number')
|
.filter((item) => typeof item.organizationId === 'number')
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
organizationId: item.organizationId,
|
organizationId: item.organizationId,
|
||||||
reviewUserId: item.reviewUserId
|
reviewUserId: item.reviewUserId,
|
||||||
|
reviewerRule: item.reviewerRule
|
||||||
}));
|
}));
|
||||||
syncOrganizationIds();
|
syncOrganizationIds();
|
||||||
await preloadReviewUsers();
|
await preloadReviewUsers();
|
||||||
@@ -503,7 +526,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flow-step-reviewer {
|
.flow-step-reviewer {
|
||||||
width: 220px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
width: 320px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
allow-clear
|
allow-clear
|
||||||
show-search
|
show-search
|
||||||
:options="moduleOptions"
|
:options="moduleOptions"
|
||||||
|
:loading="moduleLoading"
|
||||||
:filter-option="filterSelectOption"
|
:filter-option="filterSelectOption"
|
||||||
placeholder="请选择模块"
|
placeholder="请选择模块"
|
||||||
v-model:value="form.module"
|
v-model:value="form.module"
|
||||||
@@ -81,6 +82,12 @@
|
|||||||
import type { ReviewFlow } from '@/api/gxmu/reviewFlow/model';
|
import type { ReviewFlow } from '@/api/gxmu/reviewFlow/model';
|
||||||
import { listOrganizations } from '@/api/system/organization';
|
import { listOrganizations } from '@/api/system/organization';
|
||||||
import type { Organization } from '@/api/system/organization/model';
|
import type { Organization } from '@/api/system/organization/model';
|
||||||
|
import {
|
||||||
|
enabledModuleOptions,
|
||||||
|
loadModuleRegistry,
|
||||||
|
withLegacyModuleOption
|
||||||
|
} from '@/utils/module-registry';
|
||||||
|
import type { ModuleOption } from '@/utils/module-registry-core';
|
||||||
|
|
||||||
// 是否是修改
|
// 是否是修改
|
||||||
const isUpdate = ref(false);
|
const isUpdate = ref(false);
|
||||||
@@ -111,18 +118,9 @@
|
|||||||
const reviewFlowOptions = ref<{ label: string; value: number }[]>([]);
|
const reviewFlowOptions = ref<{ label: string; value: number }[]>([]);
|
||||||
const organizationLoading = ref(false);
|
const organizationLoading = ref(false);
|
||||||
const organizationTreeData = ref<Organization[]>([]);
|
const organizationTreeData = ref<Organization[]>([]);
|
||||||
const moduleOptions = [
|
// 模块下拉来自模块注册表(只含启用模块),不再维护前端硬编码清单
|
||||||
{ label: '稿件', value: 'cms_manuscript' },
|
const moduleLoading = ref(false);
|
||||||
{ label: '十佳青年岗位能手', value: 'gxmu_sjqn' },
|
const moduleOptions = ref<ModuleOption[]>([]);
|
||||||
{ label: '十佳团支部书记', value: 'gxmu_sjtbzbsj' },
|
|
||||||
{ label: '五四红旗团委', value: 'gxmu_wshqtw' },
|
|
||||||
{ label: '五四红旗团支部', value: 'gxmu_wshqtzb' },
|
|
||||||
{ label: '优秀共青团干部', value: 'gxmu_yxgqtdgb' },
|
|
||||||
{ label: '优秀共青团员', value: 'gxmu_yxgqty' },
|
|
||||||
{ label: '挑战杯', value: 'gxmu_tzbcy_form' },
|
|
||||||
{ label: '青马工程', value: 'gxmu_qmgc_form' },
|
|
||||||
{ label: '未来学术之星', value: 'gxmu_wxxzx_project' }
|
|
||||||
];
|
|
||||||
|
|
||||||
// 用户信息
|
// 用户信息
|
||||||
const form = reactive<ReviewFlowConfig>({
|
const form = reactive<ReviewFlowConfig>({
|
||||||
@@ -204,6 +202,28 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载模块下拉。
|
||||||
|
*
|
||||||
|
* 注册表接口只返回启用模块,编辑停用模块的历史审批链时把当前值补成
|
||||||
|
* 「标题(已停用)」,保证回显不空白。
|
||||||
|
*/
|
||||||
|
const loadModuleOptions = async (module?: string, moduleName?: string) => {
|
||||||
|
moduleLoading.value = true;
|
||||||
|
try {
|
||||||
|
await loadModuleRegistry();
|
||||||
|
moduleOptions.value = withLegacyModuleOption(
|
||||||
|
enabledModuleOptions(),
|
||||||
|
module,
|
||||||
|
moduleName
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e.message || '模块清单加载失败');
|
||||||
|
} finally {
|
||||||
|
moduleLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const loadOrganizations = async () => {
|
const loadOrganizations = async () => {
|
||||||
organizationLoading.value = true;
|
organizationLoading.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -273,9 +293,11 @@
|
|||||||
}
|
}
|
||||||
if (props.data) {
|
if (props.data) {
|
||||||
assignObject(form, props.data);
|
assignObject(form, props.data);
|
||||||
|
await loadModuleOptions(props.data.module, props.data.moduleName);
|
||||||
isUpdate.value = true;
|
isUpdate.value = true;
|
||||||
} else {
|
} else {
|
||||||
resetFields();
|
resetFields();
|
||||||
|
await loadModuleOptions();
|
||||||
isUpdate.value = false;
|
isUpdate.value = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
* 中展示的附件字段,未列出的字段会兜底追加到「其他信息」分组,避免数据丢失。
|
* 中展示的附件字段,未列出的字段会兜底追加到「其他信息」分组,避免数据丢失。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { detailKeyOf } from '@/utils/module-registry';
|
||||||
|
|
||||||
export interface ModuleSection {
|
export interface ModuleSection {
|
||||||
// 分组标题
|
// 分组标题
|
||||||
title: string;
|
title: string;
|
||||||
@@ -896,9 +898,19 @@ export const TABLE_FIELD_LABELS: Record<string, string> = {
|
|||||||
year: '年份'
|
year: '年份'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取模块的详情页配置。
|
||||||
|
*
|
||||||
|
* module → detailKey 的映射只走 detailKeyOf 一处(注册表 detail_key);
|
||||||
|
* 注册表未命中时回退 module 本身,停用模块的历史记录仍能打开详情。
|
||||||
|
*/
|
||||||
export const getModuleDetailConfig = (module?: string) => {
|
export const getModuleDetailConfig = (module?: string) => {
|
||||||
if (!module) {
|
if (!module) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return MODULE_DETAIL_CONFIGS[module];
|
const detailKey = detailKeyOf(module);
|
||||||
|
return (
|
||||||
|
(detailKey && MODULE_DETAIL_CONFIGS[detailKey]) ||
|
||||||
|
MODULE_DETAIL_CONFIGS[module]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,8 +20,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, watch } from 'vue';
|
import { onMounted, reactive, ref, watch } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
import type { ReviewListParam } from '@/api/gxmu/reviewList/model';
|
import type { ReviewListParam } from '@/api/gxmu/reviewList/model';
|
||||||
|
import {
|
||||||
|
enabledModuleOptions,
|
||||||
|
loadModuleRegistry
|
||||||
|
} from '@/utils/module-registry';
|
||||||
|
import type { ModuleOption } from '@/utils/module-registry-core';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -42,17 +48,17 @@
|
|||||||
keywords: undefined
|
keywords: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
const moduleOptions = [
|
// 模块筛选项来自模块注册表(只含启用模块),不再维护前端硬编码清单
|
||||||
{ label: '十佳青年岗位能手', value: 'gxmu_sjqn' },
|
const moduleOptions = ref<ModuleOption[]>([]);
|
||||||
{ label: '十佳团支部书记', value: 'gxmu_sjtbzbsj' },
|
|
||||||
{ label: '五四红旗团委', value: 'gxmu_wshqtw' },
|
onMounted(async () => {
|
||||||
{ label: '五四红旗团支部', value: 'gxmu_wshqtzb' },
|
try {
|
||||||
{ label: '优秀共青团干部', value: 'gxmu_yxgqtdgb' },
|
await loadModuleRegistry();
|
||||||
{ label: '优秀共青团员', value: 'gxmu_yxgqty' },
|
moduleOptions.value = enabledModuleOptions();
|
||||||
{ label: '挑战杯', value: 'gxmu_tzbcy_form' },
|
} catch (e: any) {
|
||||||
{ label: '青马工程', value: 'gxmu_qmgc_form' },
|
message.error(e.message || '模块清单加载失败');
|
||||||
{ label: '未来学术之星', value: 'gxmu_wxxzx_project' }
|
}
|
||||||
];
|
});
|
||||||
|
|
||||||
const search = () => {
|
const search = () => {
|
||||||
emit('search', {
|
emit('search', {
|
||||||
|
|||||||
Reference in New Issue
Block a user