<template>
|
<div class="page">
|
<h3 class="section-title">审核节点配置</h3>
|
<el-table :data="nodeRows" v-loading="nodesLoading" stripe border class="node-table">
|
<el-table-column prop="nodeLabel" label="节点" min-width="160" show-overflow-tooltip />
|
<el-table-column prop="nodeDesc" label="审批节点说明" min-width="280" show-overflow-tooltip />
|
<el-table-column label="是否开启" width="120" align="center">
|
<template #default="{ row }">
|
<el-switch
|
v-permission="'config:approval-settings:update'"
|
:model-value="row.enabled === 1"
|
:loading="!!row._saving"
|
@change="(v: boolean) => onToggleEnabled(row, v)"
|
/>
|
</template>
|
</el-table-column>
|
</el-table>
|
<p class="hint">
|
节点关闭后新的预约单将该节点隐藏无需审批(开启关闭不影响历史预约)
|
</p>
|
|
<template v-if="visitDeptNodeEnabled">
|
<h3 class="section-title section-title-spaced">访客主管部门配置</h3>
|
<el-form label-width="120px" class="dept-form">
|
<el-form-item label="主管部门" required>
|
<el-tree-select
|
v-model="competentDeptId"
|
:data="deptTree"
|
:props="{ value: 'id', label: 'label', children: 'children' }"
|
check-strictly
|
:render-after-expand="false"
|
placeholder="请选择部门"
|
clearable
|
filterable
|
style="width: 320px"
|
/>
|
</el-form-item>
|
<el-form-item>
|
<el-button v-permission="'config:approval-settings:update'" type="primary" :loading="savingDept" @click="saveCompetentDept">保存</el-button>
|
</el-form-item>
|
</el-form>
|
</template>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { computed, onMounted, ref } from "vue";
|
import { ElMessage } from "element-plus";
|
import http from "../api/http";
|
import { normalizeFormData } from "../utils/formHelper";
|
|
const UI_NODE_CODES = [
|
"LEVEL2_DEPT_LEADER",
|
"LEVEL3_LOCAL_DEPT_LEADER",
|
"LEVEL4_VISIT_DEPT",
|
"LEVEL5_VISIT_DEPT_LEADER",
|
] as const;
|
|
const NODE_COPY: Record<string, { node: string; desc: string }> = {
|
LEVEL2_DEPT_LEADER: {
|
node: "部门负责人审批",
|
desc: "由预约的通行部门/区域的负责人进行审批",
|
},
|
LEVEL3_LOCAL_DEPT_LEADER: {
|
node: "属地管理部门领导审批",
|
desc: "当来访涉及访问属地单位时,由属地管理部门领导进行审批",
|
},
|
LEVEL4_VISIT_DEPT: {
|
node: "访客主管部门",
|
desc: "指定一个部门为主管部门进行审批",
|
},
|
LEVEL5_VISIT_DEPT_LEADER: {
|
node: "访客主管部门领导审批",
|
desc: "当来访日期为国定假期/节假日时,由主管部门的部门领导进行审批",
|
},
|
};
|
|
type ApiNode = {
|
id: number;
|
uuid: string;
|
nodeCode: string;
|
nodeName?: string;
|
enabled: number;
|
sortOrder?: number;
|
};
|
|
type NodeRow = ApiNode & {
|
nodeLabel: string;
|
nodeDesc: string;
|
_saving?: boolean;
|
};
|
|
const nodesLoading = ref(false);
|
const rawNodes = ref<ApiNode[]>([]);
|
|
const nodeRows = computed(() => {
|
const order = new Map(UI_NODE_CODES.map((c, i) => [c, i]));
|
const list = rawNodes.value
|
.filter((n) => order.has(n.nodeCode as (typeof UI_NODE_CODES)[number]))
|
.sort((a, b) => (order.get(a.nodeCode) ?? 0) - (order.get(b.nodeCode) ?? 0));
|
return list.map((n) => {
|
const ui = NODE_COPY[n.nodeCode] || { node: n.nodeName || n.nodeCode, desc: "—" };
|
return {
|
...n,
|
nodeLabel: ui.node,
|
nodeDesc: ui.desc,
|
} as NodeRow;
|
});
|
});
|
|
const visitDeptNodeEnabled = computed(() =>
|
rawNodes.value.some(
|
(n) =>
|
(n.nodeCode === "LEVEL4_VISIT_DEPT" || n.nodeCode === "LEVEL5_VISIT_DEPT_LEADER") &&
|
n.enabled === 1,
|
),
|
);
|
|
async function loadNodes() {
|
nodesLoading.value = true;
|
try {
|
const { data } = await http.get("/config/approval-nodes");
|
rawNodes.value = (data.result || []) as ApiNode[];
|
} catch (e: unknown) {
|
ElMessage.error(e instanceof Error ? e.message : "加载节点失败");
|
} finally {
|
nodesLoading.value = false;
|
}
|
}
|
|
async function onToggleEnabled(row: NodeRow, val: boolean) {
|
const prev = row.enabled;
|
row._saving = true;
|
try {
|
const nextEnabled = val ? 1 : 0;
|
await http.patch(`/config/approval-nodes/${row.id}/enabled`, {
|
uuid: row.uuid,
|
enabled: nextEnabled,
|
});
|
row.enabled = nextEnabled;
|
const target = rawNodes.value.find((n) => n.id === row.id);
|
if (target) target.enabled = nextEnabled;
|
ElMessage.success("已更新");
|
} catch (e: unknown) {
|
row.enabled = prev;
|
ElMessage.error(e instanceof Error ? e.message : "更新失败");
|
} finally {
|
row._saving = false;
|
}
|
}
|
|
type DeptTree = { id: number; label: string; children?: DeptTree[] };
|
|
const deptTree = ref<DeptTree[]>([]);
|
const competentDeptId = ref<number | undefined>(undefined);
|
const savingDept = ref(false);
|
|
function mapDept(nodes: Record<string, unknown>[]): DeptTree[] {
|
return nodes.map((n) => ({
|
id: n.id as number,
|
label: (n.status === 0 ? `${n.deptName}(禁用)` : String(n.deptName)) as string,
|
children: n.children ? mapDept(n.children as Record<string, unknown>[]) : undefined,
|
}));
|
}
|
|
async function loadDeptTree() {
|
const { data } = await http.get("/sys/dept-manage/tree");
|
const raw = (data.result || []) as Record<string, unknown>[];
|
deptTree.value = mapDept(raw);
|
}
|
|
async function loadCompetentDept() {
|
try {
|
const { data } = await http.get("/config/visitor-competent-dept");
|
const id = data.result?.deptId as number | null | undefined;
|
competentDeptId.value = id != null && id > 0 ? id : undefined;
|
} catch {
|
competentDeptId.value = undefined;
|
}
|
}
|
|
async function saveCompetentDept() {
|
if (visitDeptNodeEnabled.value && !competentDeptId.value) {
|
ElMessage.error("请选择主管部门");
|
return;
|
}
|
savingDept.value = true;
|
try {
|
const payload = normalizeFormData({
|
deptId: competentDeptId.value ?? null,
|
});
|
await http.put("/config/visitor-competent-dept", payload);
|
ElMessage.success("已保存");
|
} catch (e: unknown) {
|
ElMessage.error(e instanceof Error ? e.message : "保存失败");
|
} finally {
|
savingDept.value = false;
|
}
|
}
|
|
onMounted(async () => {
|
await loadNodes();
|
try {
|
await loadDeptTree();
|
} catch {
|
/* ignore */
|
}
|
await loadCompetentDept();
|
});
|
</script>
|
|
<style scoped>
|
.page {
|
padding: 0;
|
}
|
.section-title {
|
font-size: 16px;
|
font-weight: 600;
|
margin: 0 0 12px;
|
padding-left: 10px;
|
border-left: 3px solid #409eff;
|
color: #303133;
|
}
|
.section-title-spaced {
|
margin-top: 28px;
|
}
|
.node-table {
|
max-width: 960px;
|
}
|
.hint {
|
margin: 10px 0 0;
|
font-size: 13px;
|
color: #909399;
|
line-height: 1.5;
|
max-width: 960px;
|
}
|
.dept-form {
|
max-width: 560px;
|
margin-top: 8px;
|
}
|
</style>
|