feat(dept): 应用设置-科室管理 tab(组+成员维护+一键拉取);办理人映射页下线
build / build (push) Successful in 2m3s

- AppSetting tab2:科室表格(成员维护 Modal 复用 UserSelectInput/改名/停启用/删除)+ 拉取预览确认导入
- 编辑器科室下拉/校验数据源切 deptList(停用科室不进下拉);Maps.vue 与 104 路由删除
- flow.ts:dept 系列 API;map 系列 CRUD 删除(保留 mapLookupUser 选人检索)
This commit is contained in:
chenxing
2026-10-10 19:48:00 +08:00
parent f835965e29
commit ed5f818c1a
8 changed files with 347 additions and 255 deletions
+32 -18
View File
@@ -165,20 +165,42 @@ class FlowApi {
return get(`${ROOT}/admin/def/diagram`, params);
}
// ==================== 管理面:办理人映射 ====================
mapList(deptKey?: string) {
return get(`${ROOT}/admin/map/list`, deptKey ? { deptKey } : undefined);
// ==================== 管理面:科室管理(2026-10-10,取代旧 /map/* 映射 CRUD) ====================
/** 科室列表(deptKey=自增 id 字符串 + 启用成员数;编辑器 ctx.depts 同结构) */
deptList() {
return get(`${ROOT}/admin/dept/list`);
}
mapSave(body: { deptKey: string; deptName?: string; userId: number; userName?: string }) {
return post(`${ROOT}/admin/map/save`, body);
deptCreate(deptName: string) {
return post(`${ROOT}/admin/dept/create`, { deptName });
}
mapDisable(id: number | string) {
return post(`${ROOT}/admin/map/disable/${id}`);
deptRename(id: number | string, deptName: string) {
return post(`${ROOT}/admin/dept/rename`, { id, deptName });
}
/** 启用映射(停用行的恢复入口;DEPT_KEY 解析只取启用行) */
mapEnable(id: number | string) {
return post(`${ROOT}/admin/map/enable/${id}`);
deptStatus(id: number | string, enabled: boolean) {
return post(`${ROOT}/admin/dept/status`, { id, enabled });
}
deptDelete(id: number | string) {
return post(`${ROOT}/admin/dept/delete`, { id });
}
deptMembers(deptId: number | string) {
return get(`${ROOT}/admin/dept/members`, { deptId });
}
/** 批量加成员(返回新增数) */
deptMemberAdd(deptId: number | string, userIds: number[]) {
return post(`${ROOT}/admin/dept/memberAdd`, { deptId, userIds });
}
deptMemberRemove(memberId: number | string) {
return post(`${ROOT}/admin/dept/memberRemove`, { memberId });
}
/** 一键拉取教师组·预览(不落库,确认后导入) */
deptGroupPreview() {
return get(`${ROOT}/admin/dept/groupPreview`);
}
/** 一键拉取教师组·导入(source_group_id 幂等;重复拉取=刷新组名与成员集) */
deptImportGroups() {
return post(`${ROOT}/admin/dept/importGroups`);
}
/** 成员检索(UserBase byId;编辑器选人/姓名补全共用,原 /map/lookupUser 通道保留) */
mapLookupUser(userId: number) {
return get(`${ROOT}/admin/map/lookupUser`, { userId });
}
@@ -190,14 +212,6 @@ class FlowApi {
adminTeacherTree() {
return get(`${ROOT}/admin/user/teacherTree`);
}
/** 一键拉取教师组·预览(学校子树教师组容器 → 科室+组内成员) */
mapGroupPreview() {
return get(`${ROOT}/admin/map/groupPreview`);
}
/** 一键拉取教师组·导入(批量落映射,已存在跳过) */
mapImportGroups() {
return post(`${ROOT}/admin/map/importGroups`);
}
// ==================== 管理面:回调注册/日志/死信 ====================
regList() {
+1 -6
View File
@@ -66,12 +66,7 @@ const routes: RouteRecordRaw[] = [
meta: { title: "流程编辑器", parentId: 103 },
},
{
path: "/admin/maps",
name: "flow_admin_maps",
component: () => import("@/views/flow/admin/Maps.vue"),
meta: { title: "办理人映射", modId: 104 },
},
{
// 回调管理(原 104 办理人映射已于 2026-10-10 下线,由 应用设置-科室管理 承载)
path: "/admin/callbacks",
name: "flow_admin_callbacks",
component: () => import("@/views/flow/admin/Callbacks.vue"),
+300 -9
View File
@@ -1,15 +1,17 @@
<script setup lang="ts">
// 应用设置(2026-10-10 四议题):tab1 应用管理(flw_app 白名单 + 学校池同步 + 开关);
// tab2 科室管理(Part E 填充,本版本占位)。
// 应用开通过程在 UserBase 管理端完成(学校组开通);本页只决定"已开通的应用里哪些对接流程"。
// tab2 科室管理(flw_dept 组+成员模型,取代旧「办理人映射」页——deptKey=本地自增 id,
// 切源后旧流程定义引用的 UserBase 组 id 需重建科室后重新配置)。
import { ref, onMounted } from "vue";
import { Message } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm";
import UserSelectInput from "@/components/UserSelectInput.vue";
import { flowApi } from "@/api/module/flow";
import Utils from "@/utils";
const tab = ref("apps");
// ==================== tab1 应用管理 ====================
const loading = ref(false);
const rows = ref<any[]>([]);
const tableHeight = ref(Utils.useTableHeight({ isOperat: true }));
@@ -55,18 +57,249 @@ async function onToggle(row: any, enabled: boolean) {
Message.success(enabled ? `已开启「${row.appName}」` : `已停用「${row.appName}」`);
} catch (e: any) {
Message.error(e?.message || "操作失败");
// 失败回滚开关显示(重新拉取)
load();
} finally {
row._switching = false;
}
}
function onRefresh() {
load();
// ==================== tab2 科室管理 ====================
const deptLoading = ref(false);
const depts = ref<any[]>([]);
const deptColumns = [
{ title: "序号", type: "index", width: 60, align: "center" },
{ title: "科室名", key: "deptName", minWidth: 160 },
{ title: "deptKey", key: "deptKey", width: 90, align: "center" },
{
title: "启用成员",
key: "count",
width: 90,
align: "center",
render: (h: any, p: any) => h("span", String(p.row.count)),
},
{
title: "来源",
width: 90,
align: "center",
render: (h: any, p: any) => h("Tag", () => (p.row._pulled ? "拉取" : "手工")),
},
{
title: "状态",
width: 80,
align: "center",
render: (h: any, p: any) =>
h("Tag", { props: { color: p.row.status === 0 ? "green" : "gray" } },
() => (p.row.status === 0 ? "启用" : "停用")),
},
{
title: "操作",
width: 260,
align: "center",
render: (h: any, p: any) => [
Utils.operatingHandle(h, "成员", "icon-yonghu", () => onMembers(p.row)),
h("span", { style: "display:inline-block;width:10px" }),
Utils.operatingHandle(h, "改名", "icon-edit", () => onRename(p.row)),
h("span", { style: "display:inline-block;width:10px" }),
p.row.status === 0
? Utils.operatingHandle(h, "停用", "icon-stop", () => onDeptStatus(p.row, false))
: Utils.operatingHandle(h, "启用", "icon-play", () => onDeptStatus(p.row, true)),
h("span", { style: "display:inline-block;width:10px" }),
Utils.operatingHandle(h, "删除", "icon-shanchu", () => onDelete(p.row)),
],
},
];
async function loadDepts() {
deptLoading.value = true;
try {
depts.value = ((await flowApi.deptList().send()) as any[]) || [];
} finally {
deptLoading.value = false;
}
}
onMounted(load);
// ---- 新建科室 ----
const createVisible = ref(false);
const createSaving = ref(false);
const createName = ref("");
function onCreate() {
createName.value = "";
createVisible.value = true;
}
async function doCreate() {
if (!createName.value.trim()) {
Message.warning("科室名称不能为空");
return;
}
createSaving.value = true;
try {
await flowApi.deptCreate(createName.value.trim()).send();
Message.success("已创建");
createVisible.value = false;
loadDepts();
} catch (e: any) {
Message.error(e?.message || "创建失败");
} finally {
createSaving.value = false;
}
}
// ---- 改名 ----
const renameVisible = ref(false);
const renameSaving = ref(false);
const renameRow = ref<any>(null);
const renameName = ref("");
function onRename(row: any) {
renameRow.value = row;
renameName.value = row.deptName;
renameVisible.value = true;
}
async function doRename() {
if (!renameName.value.trim()) {
Message.warning("科室名称不能为空");
return;
}
renameSaving.value = true;
try {
await flowApi.deptRename(renameRow.value.id, renameName.value.trim()).send();
Message.success("已改名");
renameVisible.value = false;
loadDepts();
} catch (e: any) {
Message.error(e?.message || "改名失败");
} finally {
renameSaving.value = false;
}
}
// ---- 停启用 / 删除 ----
function onDeptStatus(row: any, enabled: boolean) {
MyConfirm({
title: enabled ? "启用科室" : "停用科室",
msg: `确定${enabled ? "启用" : "停用"}「${row.deptName}」吗?`,
info: enabled
? "引用它的流程节点恢复解析到科室成员"
: "停用后引用它的流程定义不能发布;在途办件推进到该节点会报无可用办理人",
async onOk() {
await flowApi.deptStatus(row.id, enabled).send();
Message.success("操作成功");
loadDepts();
},
});
}
function onDelete(row: any) {
MyConfirm({
title: "删除科室",
msg: `确定删除「${row.deptName}」及其全部成员吗?`,
info: "删除后引用该 deptKey 的流程定义节点将解析失败(发布校验拦截/办理报错),请先确认无流程引用",
async onOk() {
await flowApi.deptDelete(row.id).send();
Message.success("已删除");
loadDepts();
},
});
}
// ---- 成员维护 ----
const memberVisible = ref(false);
const memberRow = ref<any>(null);
const members = ref<any[]>([]);
const memberLoading = ref(false);
const addUserId = ref<number | null>(null);
const addUserName = ref("");
function onMembers(row: any) {
memberRow.value = row;
memberVisible.value = true;
loadMembers(row);
}
async function loadMembers(row: any) {
memberLoading.value = true;
try {
members.value = ((await flowApi.deptMembers(row.id).send()) as any[]) || [];
} finally {
memberLoading.value = false;
}
}
async function onMemberAdd() {
if (!addUserId.value) {
Message.warning("请先选择用户");
return;
}
try {
const added: any = await flowApi.deptMemberAdd(memberRow.value.id, [addUserId.value]).send();
Message.success(added > 0 ? `已添加 ${added} 人` : "该成员已存在");
addUserId.value = null;
addUserName.value = "";
loadMembers(memberRow.value);
loadDepts();
} catch (e: any) {
Message.error(e?.message || "添加失败");
}
}
function onMemberRemove(m: any) {
MyConfirm({
title: "移除成员",
msg: `确定把「${m.userName || "#" + m.userId}」移出「${memberRow.value.deptName}」吗?`,
info: "移除后引用该科室的流程节点解析时不再包含此人;在途待办不受影响",
async onOk() {
await flowApi.deptMemberRemove(m.id).send();
loadMembers(memberRow.value);
loadDepts();
},
});
}
// ---- 一键拉取教师组(初始化语义) ----
const groupVisible = ref(false);
const groupLoading = ref(false);
const groupImporting = ref(false);
const groupRows = ref<any[]>([]);
const groupColumns = [
{ title: "科室名称", key: "deptName", width: 160 },
{ title: "成员数", width: 90, align: "center", render: (h: any, p: any) => h("span", String(p.row.users?.length || 0)) },
{ title: "成员", key: "members", render: (h: any, p: any) => h("span", (p.row.users || []).map((u: any) => `${u.userName}(${u.userId})`).join("、") || "(空,导入后可手工补)") },
];
async function onGroupPreview() {
groupVisible.value = true;
groupLoading.value = true;
try {
groupRows.value = (await flowApi.deptGroupPreview().send()) || [];
} catch (e: any) {
Message.error(e?.message || "教师组拉取失败(UserBase 不可达?)");
groupVisible.value = false;
} finally {
groupLoading.value = false;
}
}
async function onGroupImport() {
groupImporting.value = true;
try {
const r: any = await flowApi.deptImportGroups().send();
Message.success(`导入完成:新建 ${r?.created ?? 0} / 刷新 ${r?.refreshed ?? 0} 个科室,新增成员 ${r?.users ?? 0} 人`);
groupVisible.value = false;
loadDepts();
} catch (e: any) {
Message.error(e?.message || "导入失败");
} finally {
groupImporting.value = false;
}
}
onMounted(() => {
load();
loadDepts();
});
</script>
<template>
@@ -75,7 +308,7 @@ onMounted(load);
<Tabs v-model="tab">
<TabPane label="应用管理" name="apps">
<div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="onRefresh">刷新(同步学校池)</Button>
<Button class="fl" type="primary" size="large" @click="load">刷新(同步学校池)</Button>
<span class="fl" style="color: #808695; font-size: 12px; line-height: 40px; margin-left: 10px">
应用清单来自本校已开通应用(UserBase 学校组开通,含学校别名);开启后可用于流程归属与筛选。
新开通/改名在 UserBase 管理端操作后回本页刷新即可
@@ -84,10 +317,68 @@ onMounted(load);
<Table :columns="columns" :data="rows" :loading="loading" class="link-table table-bottom-box"
:height="tableHeight" no-data-text="本校暂无已开通应用(请先在用户中心开通)" />
</TabPane>
<TabPane label="科室管理" name="depts" disabled>
<div style="padding: 40px; text-align: center; color: #808695">科室管理建设中(即将上线)</div>
<TabPane label="科室管理" name="depts">
<div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="onCreate">新建科室</Button>
<Button class="fl" size="large" style="margin-left: 8px" @click="onGroupPreview">一键拉取教师组</Button>
<span class="fl" style="color: #808695; font-size: 12px; line-height: 40px; margin-left: 10px">
流程节点「按科室」办理的解析源;重复拉取=刷新组名与成员集(不重复建组)
</span>
</div>
<Table :columns="deptColumns" :data="depts" :loading="deptLoading" class="link-table table-bottom-box"
:height="tableHeight" no-data-text="暂无科室(可一键拉取教师组或手工新建)" />
</TabPane>
</Tabs>
<Modal v-model="createVisible" title="新建科室" :mask-closable="false">
<Input v-model="createName" placeholder="科室名称,如 人事处" :maxlength="30" />
<template #footer>
<Button @click="createVisible = false">取消</Button>
<Button type="primary" :loading="createSaving" @click="doCreate">创建</Button>
</template>
</Modal>
<Modal v-model="renameVisible" title="科室改名" :mask-closable="false">
<Input v-model="renameName" placeholder="科室名称" :maxlength="30" />
<template #footer>
<Button @click="renameVisible = false">取消</Button>
<Button type="primary" :loading="renameSaving" @click="doRename">保存</Button>
</template>
</Modal>
<Modal v-model="memberVisible" :title="`成员维护 - ${memberRow?.deptName || ''}`" :mask-closable="false" width="640">
<div class="fix" style="margin-bottom: 10px">
<UserSelectInput
class="fl"
style="width: 260px"
:model-value="addUserId"
:display-name="addUserName"
@update:model-value="addUserId = $event"
@update:display-name="addUserName = $event"
/>
<Button class="fl" type="primary" style="margin-left: 8px" @click="onMemberAdd">添加</Button>
</div>
<Table :columns="[
{ title: '用户ID', key: 'userId', width: 100 },
{ title: '姓名', key: 'userName' },
{ title: '操作', width: 90, align: 'center', render: (h, p) => Utils.operatingHandle(h, '移除', 'icon-shanchu', () => onMemberRemove(p.row)) },
]" :data="members" :loading="memberLoading" size="small" max-height="360" no-data-text="暂无成员" />
<template #footer>
<Button @click="memberVisible = false">关闭</Button>
</template>
</Modal>
<Modal v-model="groupVisible" title="一键拉取教师组(预览)" :mask-closable="false" width="720">
<p style="color: #808695; font-size: 12px; line-height: 1.6; margin-bottom: 8px">
以下为本校教师组容器下的科室及组内成员;确认后导入。已拉取过的科室(同名 UserBase 组)将刷新组名与成员集,不会重复建组
</p>
<Table :columns="groupColumns" :data="groupRows" :loading="groupLoading" size="small" max-height="380"
no-data-text="未发现可拉取的教师组科室" />
<template #footer>
<Button @click="groupVisible = false">取消</Button>
<Button type="primary" :loading="groupImporting" :disabled="!groupRows.length" @click="onGroupImport">导入</Button>
</template>
</Modal>
</div>
</div>
</template>
-205
View File
@@ -1,205 +0,0 @@
<script setup lang="ts">
// 办理人映射管理(概览 §4.7/§六):DEPT_KEY → 办理人;办理人改为弹窗选人(参照 Notice 选人范式)
import { ref, onMounted } from "vue";
import { Message } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm";
import UserSelectInput from "@/components/UserSelectInput.vue";
import { flowApi } from "@/api/module/flow";
import Utils from "@/utils";
const loading = ref(false);
const deptKeyFilter = ref("");
const rows = ref<any[]>([]);
// 操作按钮区 + 搜索区、无分页
const tableHeight = ref(Utils.useTableHeight({ isSearch: true, isOperat: true }));
// 一键拉取教师组(预览确认后批量导入)
const groupVisible = ref(false);
const groupLoading = ref(false);
const groupImporting = ref(false);
const groupRows = ref<any[]>([]);
const groupColumns = [
{ title: "科室编码", key: "deptKey", width: 100 },
{ title: "科室名称", key: "deptName", width: 160 },
{ title: "成员数", key: "memberCount", width: 90, align: "center", render: (h: any, p: any) => h("span", String(p.row.users?.length || 0)) },
{ title: "成员", key: "members", render: (h: any, p: any) => h("span", (p.row.users || []).map((u: any) => `${u.userName}(${u.userId})`).join("、") || "(空,导入后可手工补)") },
];
async function onGroupPreview() {
groupVisible.value = true;
groupLoading.value = true;
try {
groupRows.value = (await flowApi.mapGroupPreview().send()) || [];
} catch (e: any) {
Message.error(e?.message || "教师组拉取失败(UserBase 不可达?)");
groupVisible.value = false;
} finally {
groupLoading.value = false;
}
}
async function onGroupImport() {
groupImporting.value = true;
try {
const r: any = await flowApi.mapImportGroups().send();
Message.success(`导入完成:${r?.depts ?? 0} 个科室 / 新增 ${r?.users ?? 0} 条映射 / 跳过已存在 ${r?.skippedExisting ?? 0} 条`);
groupVisible.value = false;
load();
} catch (e: any) {
Message.error(e?.message || "导入失败");
} finally {
groupImporting.value = false;
}
}
const columns = [
{ title: "序号", type: "index", width: 70, align: "center" },
{ title: "科室编码", key: "deptKey", width: 140 },
{ title: "科室名称", width: 160, render: (h: any, p: any) => h("span", p.row.deptName || "-") },
{ title: "办理人", key: "userName", width: 120 },
{ title: "用户ID", key: "userId", width: 90 },
{ title: "状态", width: 90, align: "center", render: (h: any, p: any) => h("Tag", { color: p.row.status === 0 ? "green" : "gray" }, () => (p.row.status === 0 ? "启用" : "停用")) },
{ title: "操作", width: 100, align: "center", fixed: "right", render: (h: any, p: any) =>
p.row.status === 0
? Utils.operatingHandle(h, "停用", "icon-stop", () => onDisable(p.row))
: Utils.operatingHandle(h, "启用", "icon-play", () => onEnable(p.row)) },
];
async function load() {
loading.value = true;
try {
rows.value = (await flowApi.mapList(deptKeyFilter.value.trim() || undefined).send()) || [];
} finally {
loading.value = false;
}
}
function onSearch() {
load();
}
// ---- 新增/更新映射弹窗(规范:必填红星标注,校验失败提示统一文案) ----
const saveVisible = ref(false);
const saving = ref(false);
const formRef = ref<any>(null);
const form = ref<any>({ deptKey: "", deptName: "", userId: null, userName: "" });
const saveRules = {
deptKey: [{ required: true, message: "请填写红色星号内容!", trigger: "blur" }],
userId: [{ required: true, message: "请填写红色星号内容!", trigger: "change" }],
};
function onAdd() {
form.value = { deptKey: "", deptName: "", userId: null, userName: "" };
saveVisible.value = true;
}
function onUserPick(u: any) {
// 选人组件回传 id+name 同帧快照(姓名不再自查)
form.value.userName = u?.name || "";
}
function onSave() {
formRef.value?.validate(async (valid: boolean) => {
if (!valid) return;
saving.value = true;
try {
await flowApi.mapSave({
deptKey: form.value.deptKey.trim(),
deptName: form.value.deptName,
userId: Number(form.value.userId),
userName: form.value.userName,
}).send();
Message.success("已保存");
saveVisible.value = false;
load();
} finally {
saving.value = false;
}
});
}
function onDisable(row: any) {
MyConfirm({
title: "停用",
msg: `确定停用科室「${row.deptName || row.deptKey}」的办理人映射吗?`,
info: "停用后该科室流程节点需重新配置办理人",
async onOk() {
await flowApi.mapDisable(row.id).send();
Message.success("已停用");
load();
},
});
}
/** 启用(2026-09-18 补齐:原来停用后只能重新"新增映射"恢复) */
function onEnable(row: any) {
MyConfirm({
title: "启用",
msg: `确定启用科室「${row.deptName || row.deptKey}」的办理人「${row.userName || row.userId}」吗?`,
info: "启用后该科室的流程节点会重新解析到这名办理人",
async onOk() {
await flowApi.mapEnable(row.id).send();
Message.success("已启用");
load();
},
});
}
onMounted(load);
</script>
<template>
<div class="page-wrapper">
<div class="main-container">
<!-- 操作按钮区:主操作靠左、其余靠右(bsch应用页规范 §1) -->
<div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="onAdd">新增映射</Button>
<Button class="fr" size="large" :loading="groupLoading" @click="onGroupPreview">一键拉取教师组</Button>
</div>
<!-- 搜索区:关键字即输即搜,不设查询按钮 -->
<div class="search-wrap fix">
<Input class="fr" v-model="deptKeyFilter" placeholder="按科室编码筛选" search suffix="ios-search"
style="width: 200px" @on-search="onSearch" @on-clear="onSearch" />
</div>
<Table :columns="columns" :data="rows" :loading="loading" class="link-table table-bottom-box"
:height="tableHeight" no-data-text="暂无数据" />
<Modal v-model="saveVisible" title="新增/更新映射" :mask-closable="false">
<Form ref="formRef" :model="form" :rules="saveRules" :label-width="90">
<FormItem label="科室编码" prop="deptKey">
<Input v-model="form.deptKey" placeholder="流程 JSON 中 DEPT_KEY 引用的编码,如 ybb" />
</FormItem>
<FormItem label="科室名称">
<Input v-model="form.deptName" placeholder="如 医保办(可空)" />
</FormItem>
<FormItem label="办理人" prop="userId">
<UserSelectInput
:model-value="form.userId"
:display-name="form.userName"
@update:model-value="form.userId = $event"
@update:display-name="form.userName = $event"
@on-change="onUserPick"
/>
</FormItem>
</Form>
<template #footer>
<Button @click="saveVisible = false">取消</Button>
<Button type="primary" :loading="saving" @click="onSave">提交</Button>
</template>
</Modal>
<Modal v-model="groupVisible" title="一键拉取教师组(UserBase 学校组树)" width="720" :mask-closable="false">
<p style="margin-bottom: 8px; color: #808695">
按学校组树中类型=教师组(type 7)的容器组取其子组作为科室(无子组时容器自身即科室),科室编码=UserBase 组 id,成员=组内用户。已存在且启用中的映射自动跳过。
</p>
<Table :columns="groupColumns" :data="groupRows" :loading="groupLoading" max-height="360" no-data-text="暂无数据" />
<template #footer>
<Button @click="groupVisible = false">取消</Button>
<Button type="primary" :loading="groupImporting" :disabled="!groupRows.length" @click="onGroupImport">确认导入</Button>
</template>
</Modal>
</div>
</div>
</template>
@@ -59,7 +59,7 @@ watch(
{ immediate: true }
);
/** 科室下拉项:mapList 聚合 + 当前值(可能已不在映射表中)兜底,避免选项丢失 */
/** 科室下拉项:科室管理列表 + 当前值(可能已删除)兜底,避免选项丢失 */
const deptOptions = computed(() => {
const list = ctx.depts.map((d) => ({
value: d.deptKey,
@@ -52,7 +52,7 @@ watch(
{ immediate: true }
);
/** 科室下拉项:mapList 聚合 + 当前值(可能已不在映射表中)兜底,避免选项丢失 */
/** 科室下拉项:科室管理列表 + 当前值(可能已删除)兜底,避免选项丢失 */
const deptOptions = computed(() => {
const list = ctx.depts.map((d) => ({
value: d.deptKey,
+11 -14
View File
@@ -61,7 +61,7 @@ const appKey = ref("");
const appOptions = ref<{ appKey: string; appName: string }[]>([]);
const model = reactive<EditorModel>(createEmptyModel());
/** 科室映射聚合(mapList):仅用于下拉选项与"无启用办理人"提示 */
/** 科室列表(deptList 聚合行):仅用于下拉选项与"无启用人员"提示 */
const depts = reactive<DeptOption[]>([]);
/** userId → 姓名快照(mapLookupUser 检索补全,失败降级 #id) */
const userNames = reactive<Record<number, string>>({});
@@ -120,20 +120,17 @@ const editingCondition = computed<ConditionNode | null>(() => {
async function loadDepts(): Promise<void> {
try {
const rows: any[] = (await flowApi.mapList().send()) || [];
const grouped: Record<string, DeptOption> = {};
rows.forEach((r) => {
const key = r.deptKey;
if (!key) return;
if (!grouped[key]) grouped[key] = { deptKey: key, deptName: r.deptName || "", count: 0 };
if (!grouped[key].deptName && r.deptName) grouped[key].deptName = r.deptName;
// 状态 0=启用(与办理人映射页口径一致),发布校验要求至少 1 名启用办理人
if (r.status === 0) grouped[key].count += 1;
});
depts.splice(0, depts.length, ...Object.values(grouped));
// 科室管理(2026-10-10 切源):deptList 返回聚合行 {deptKey,deptName,status,count}
const rows: any[] = (await flowApi.deptList().send()) || [];
depts.splice(0, depts.length, ...rows.map((r) => ({
deptKey: String(r.deptKey),
deptName: r.deptName || "",
// 停用科室不进编辑器下拉(与发布校验口径一致:解析不出办理人)
count: r.status === 0 ? r.count : 0,
})));
} catch {
// 映射表拉取失败不阻塞编辑:审批人下拉退化为"仅当前值",校验降级为提示
Message.warning("办理人映射拉取失败,科室下拉可能不完整");
// 科室拉取失败不阻塞编辑:审批人下拉退化为"仅当前值",校验降级为提示
Message.warning("科室数据拉取失败,科室下拉可能不完整");
}
}
+1 -1
View File
@@ -307,7 +307,7 @@ export interface DeptOption {
count: number;
}
/** 审批人摘要文案(卡片第二行;deptOptions 来自 mapList,userNames 为 userId→姓名 快照) */
/** 审批人摘要文案(卡片第二行;deptOptions 来自科室管理列表,userNames 为 userId→姓名 快照) */
export function assigneeText(a: Assignee | null, depts: DeptOption[], userNames: Record<number, string>): string {
if (!a) return "未配置审批人";
if (a.type === "DEPT_KEY") {