feat(gxmu): 模块下拉改由模块注册表驱动,审批链每级支持审批人规则
- 新增模块注册表 api 与进程内缓存(首次使用拉一次、并发共用同一个请求), 启用清单驱动「可新建审批链」的模块下拉与审核列表模块筛选 - reviewFlowConfigEdit 不再维护硬编码模块清单;停用模块的历史配置按 「标题(已停用)」回显,避免下拉空白 - module → detailKey 的映射收敛到 detailKeyOf 一处,moduleDetailConfig 未命中注册表时回退 module 本身(停用模块的详情仍可打开) - 审核流每级新增「审批人规则」输入(USER:<userId> / COLLEGE_ROLE:<roleCode>, 分号拼装多段,留空沿用「指定人」),随该级提交 reviewerRule 并回显 - 新增 scripts/selfcheck-module-registry.mjs:前端无单测基建时验证下拉过滤、 detailKey 回退与停用模块回显
This commit is contained in:
@@ -63,6 +63,12 @@
|
||||
@focus="loadReviewUsers(item.organizationId)"
|
||||
@update:value="updateReviewUser(index, $event)"
|
||||
/>
|
||||
<a-input
|
||||
allow-clear
|
||||
:value="item.reviewerRule"
|
||||
:placeholder="reviewerRulePlaceholder"
|
||||
@update:value="updateReviewerRule(index, $event)"
|
||||
/>
|
||||
</div>
|
||||
<a-space :size="4">
|
||||
<a-button
|
||||
@@ -109,6 +115,8 @@
|
||||
interface ReviewFlowItem {
|
||||
organizationId?: number;
|
||||
reviewUserId?: number;
|
||||
// 审批人规则:';' 分隔可拼装多段,USER:<userId> / COLLEGE_ROLE:<roleCode>
|
||||
reviewerRule?: string;
|
||||
}
|
||||
|
||||
interface ReviewFlowForm extends Omit<ReviewFlow, 'reviewUserId'> {
|
||||
@@ -148,6 +156,9 @@
|
||||
const reviewUserOptionsMap = ref<Record<number, User[]>>({});
|
||||
const reviewUserLoadingMap = ref<Record<number, boolean>>({});
|
||||
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>({
|
||||
@@ -190,9 +201,13 @@
|
||||
},
|
||||
{
|
||||
validator: async () => {
|
||||
const invalid = form.flowItems.some((item) => !item.reviewUserId);
|
||||
const invalid = form.flowItems.some(
|
||||
(item) => !item.reviewUserId && !item.reviewerRule?.trim()
|
||||
);
|
||||
if (invalid) {
|
||||
return Promise.reject(new Error('请为每一级选择审核人'));
|
||||
return Promise.reject(
|
||||
new Error('请为每一级选择审核人或填写审批人规则')
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
@@ -357,6 +372,12 @@
|
||||
syncOrganizationIds();
|
||||
};
|
||||
|
||||
const updateReviewerRule = (index: number, reviewerRule?: string) => {
|
||||
const items = [...form.flowItems];
|
||||
items[index] = { ...items[index], reviewerRule };
|
||||
form.flowItems = items;
|
||||
};
|
||||
|
||||
const getSortedFlows = () => {
|
||||
if (props.data?.flows?.length) {
|
||||
return [...props.data.flows].sort(
|
||||
@@ -399,6 +420,7 @@
|
||||
title: form.title,
|
||||
organizationId: item.organizationId,
|
||||
reviewUserId: item.reviewUserId,
|
||||
reviewerRule: item.reviewerRule?.trim() || undefined,
|
||||
level: index + 1
|
||||
};
|
||||
delete (formData as ReviewFlow & { organizationIds?: number[] })
|
||||
@@ -452,7 +474,8 @@
|
||||
.filter((item) => typeof item.organizationId === 'number')
|
||||
.map((item) => ({
|
||||
organizationId: item.organizationId,
|
||||
reviewUserId: item.reviewUserId
|
||||
reviewUserId: item.reviewUserId,
|
||||
reviewerRule: item.reviewerRule
|
||||
}));
|
||||
syncOrganizationIds();
|
||||
await preloadReviewUsers();
|
||||
@@ -503,7 +526,10 @@
|
||||
}
|
||||
|
||||
.flow-step-reviewer {
|
||||
width: 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
width: 320px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
allow-clear
|
||||
show-search
|
||||
:options="moduleOptions"
|
||||
:loading="moduleLoading"
|
||||
:filter-option="filterSelectOption"
|
||||
placeholder="请选择模块"
|
||||
v-model:value="form.module"
|
||||
@@ -81,6 +82,12 @@
|
||||
import type { ReviewFlow } from '@/api/gxmu/reviewFlow/model';
|
||||
import { listOrganizations } from '@/api/system/organization';
|
||||
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);
|
||||
@@ -111,18 +118,9 @@
|
||||
const reviewFlowOptions = ref<{ label: string; value: number }[]>([]);
|
||||
const organizationLoading = ref(false);
|
||||
const organizationTreeData = ref<Organization[]>([]);
|
||||
const moduleOptions = [
|
||||
{ label: '稿件', value: 'cms_manuscript' },
|
||||
{ label: '十佳青年岗位能手', value: 'gxmu_sjqn' },
|
||||
{ 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 moduleLoading = ref(false);
|
||||
const moduleOptions = ref<ModuleOption[]>([]);
|
||||
|
||||
// 用户信息
|
||||
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 () => {
|
||||
organizationLoading.value = true;
|
||||
try {
|
||||
@@ -273,9 +293,11 @@
|
||||
}
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
await loadModuleOptions(props.data.module, props.data.moduleName);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
resetFields();
|
||||
await loadModuleOptions();
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* 中展示的附件字段,未列出的字段会兜底追加到「其他信息」分组,避免数据丢失。
|
||||
*/
|
||||
|
||||
import { detailKeyOf } from '@/utils/module-registry';
|
||||
|
||||
export interface ModuleSection {
|
||||
// 分组标题
|
||||
title: string;
|
||||
@@ -896,9 +898,19 @@ export const TABLE_FIELD_LABELS: Record<string, string> = {
|
||||
year: '年份'
|
||||
};
|
||||
|
||||
/**
|
||||
* 取模块的详情页配置。
|
||||
*
|
||||
* module → detailKey 的映射只走 detailKeyOf 一处(注册表 detail_key);
|
||||
* 注册表未命中时回退 module 本身,停用模块的历史记录仍能打开详情。
|
||||
*/
|
||||
export const getModuleDetailConfig = (module?: string) => {
|
||||
if (!module) {
|
||||
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>
|
||||
|
||||
<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 {
|
||||
enabledModuleOptions,
|
||||
loadModuleRegistry
|
||||
} from '@/utils/module-registry';
|
||||
import type { ModuleOption } from '@/utils/module-registry-core';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -42,17 +48,17 @@
|
||||
keywords: undefined
|
||||
});
|
||||
|
||||
const moduleOptions = [
|
||||
{ label: '十佳青年岗位能手', value: 'gxmu_sjqn' },
|
||||
{ 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 moduleOptions = ref<ModuleOption[]>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await loadModuleRegistry();
|
||||
moduleOptions.value = enabledModuleOptions();
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '模块清单加载失败');
|
||||
}
|
||||
});
|
||||
|
||||
const search = () => {
|
||||
emit('search', {
|
||||
|
||||
Reference in New Issue
Block a user