feat: 部门管理使用树形结构展示层级关系

This commit is contained in:
Your Name 2026-01-07 10:32:44 +08:00
parent 4c9384dbed
commit 11c9fcc7e6

View File

@ -502,12 +502,23 @@
</span>
</div>
<a-alert type="info" show-icon style="margin-bottom: 16px;">
<template #message>
<span>部门类型说明</span>
</template>
<template #description>
<span>需求部门:只能发起需求;接单部门:只能接收任务。请根据实际情况设置部门类型。</span>
</template>
</a-alert>
<a-table
:columns="departmentColumns"
:data-source="filteredDepartments"
:data-source="filteredDepartmentsTree"
:loading="loading.departments"
row-key="id"
:pagination="{ pageSize: 10 }"
:pagination="false"
:default-expand-all-rows="true"
:indent-size="24"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'deptInfo'">
@ -515,9 +526,27 @@
<div class="dept-info-main">
<span class="dept-name-text">{{ record.deptName }}</span>
<a-tag
v-if="getDepartmentLevel(record) === 1"
size="small"
:color="record.deptType === 'demand' ? 'blue' : 'green'"
color="blue"
style="margin-left: 8px;"
>一级</a-tag>
<a-tag
v-else-if="getDepartmentLevel(record) === 2"
size="small"
color="cyan"
style="margin-left: 8px;"
>二级</a-tag>
<a-tag
v-else
size="small"
color="purple"
style="margin-left: 8px;"
>三级</a-tag>
<a-tag
size="small"
:color="record.deptType === 'demand' ? 'orange' : 'green'"
style="margin-left: 4px;"
>
{{ record.deptType === 'demand' ? '需求部门' : '接单部门' }}
</a-tag>
@ -1725,15 +1754,29 @@
</a-form-item>
<a-form-item label="上级部门">
<a-select v-model:value="departmentParentId" placeholder="请选择上级部门" allow-clear>
<a-select-option :value="0">顶级部门</a-select-option>
<a-select-option
v-for="dept in departments.filter(d => !editingItem || d.id !== editingItem.id)"
:key="dept.id"
:value="dept.id"
>
{{ dept.deptName }}
</a-select-option>
<a-select-option :value="0">顶级部门(一级)</a-select-option>
<a-select-opt-group label="一级部门(选择后成为二级)">
<a-select-option
v-for="dept in parentDepartmentOptions.level1"
:key="dept.id"
:value="dept.id"
>
{{ dept.deptName }}
</a-select-option>
</a-select-opt-group>
<a-select-opt-group label="二级部门(选择后成为三级)">
<a-select-option
v-for="dept in parentDepartmentOptions.level2"
:key="dept.id"
:value="dept.id"
>
{{ dept.parentName }} / {{ dept.deptName }}
</a-select-option>
</a-select-opt-group>
</a-select>
<div class="form-tip" style="margin-top: 4px; color: #888; font-size: 12px;">
选择顶级表示创建一级部门,选择一级部门则创建二级部门,选择二级部门则创建三级部门
</div>
</a-form-item>
<a-form-item label="描述">
<a-textarea v-model:value="formData.department.description" :rows="3" placeholder="请输入描述" />
@ -2382,6 +2425,116 @@ const getDemandTypeLevel = (record: DemandType): number => {
return 3
}
// 构建部门树形结构数据
const departmentsTree = computed(() => {
// 获取所有一级部门(没有parentId的)
const level1 = departments.value.filter(d => !d.parentId)
// 为每个一级部门添加子节点
return level1.map(l1 => {
// 获取该一级部门下的二级部门
const level2Children = departments.value.filter(d => d.parentId === l1.id)
// 为每个二级部门添加三级子节点
const l1WithChildren = {
...l1,
children: level2Children.map(l2 => {
// 获取该二级部门下的三级部门
const level3Children = departments.value.filter(d => d.parentId === l2.id)
return {
...l2,
children: level3Children.length > 0 ? level3Children : undefined
}
})
}
// 如果没有子节点,不设置children属性
if (l1WithChildren.children.length === 0) {
delete l1WithChildren.children
}
return l1WithChildren
})
})
// 过滤后的部门树(支持搜索)
const filteredDepartmentsTree = computed(() => {
if (!departmentSearchText.value) {
return departmentsTree.value
}
const searchText = departmentSearchText.value.toLowerCase()
// 递归过滤函数
const filterTree = (nodes: any[]): any[] => {
return nodes.reduce((acc, node) => {
const nameMatch = node.deptName?.toLowerCase().includes(searchText)
const codeMatch = node.deptCode?.toLowerCase().includes(searchText)
const leaderMatch = node.leaderName?.toLowerCase().includes(searchText)
// 递归过滤子节点
const filteredChildren = node.children ? filterTree(node.children) : []
// 如果当前节点匹配,或者有匹配的子节点,则保留
if (nameMatch || codeMatch || leaderMatch || filteredChildren.length > 0) {
acc.push({
...node,
children: filteredChildren.length > 0 ? filteredChildren : node.children
})
}
return acc
}, [] as any[])
}
return filterTree(departmentsTree.value)
})
// 获取部门的层级
const getDepartmentLevel = (record: DepartmentOverview): number => {
if (!record.parentId) return 1
const parent = departments.value.find(d => d.id === record.parentId)
if (!parent) return 1
if (!parent.parentId) return 2
return 3
}
// 上级部门选项(分层级)- 用于部门编辑弹窗
const parentDepartmentOptions = computed(() => {
// 排除当前正在编辑的部门及其子部门
const excludeIds = new Set<number>()
if (editingItem.value?.id) {
excludeIds.add(editingItem.value.id)
// 递归获取所有子部门ID
const getChildIds = (parentId: number) => {
departments.value.filter(d => d.parentId === parentId).forEach(d => {
if (d.id) {
excludeIds.add(d.id)
getChildIds(d.id)
}
})
}
getChildIds(editingItem.value.id)
}
const filteredDepts = departments.value.filter(d => d.id && !excludeIds.has(d.id))
// 一级部门(没有parentId)
const level1 = filteredDepts.filter(d => !d.parentId)
// 二级部门(parentId指向一级部门)
const level1Ids = new Set(level1.map(d => d.id))
const level2 = filteredDepts.filter(d => d.parentId && level1Ids.has(d.parentId)).map(d => {
const parent = level1.find(l1 => l1.id === d.parentId)
return {
...d,
parentName: parent?.deptName || ''
}
})
return { level1, level2 }
})
// 扁平化的部门选项(带层级显示)
const flatDepartmentOptions = computed(() => {
const receiving = departments.value.filter(dept => dept.deptType === 'receiving')
@ -2558,14 +2711,14 @@ const categoryColumns = [
]
const departmentColumns = [
{ title: '部门信息', key: 'deptInfo' },
{ title: '负责人', key: 'leader', width: 160 },
{ title: '成员数', key: 'memberCount', width: 120 },
{ title: '积分余额', key: 'score', width: 160 },
{ title: '本周期消耗', key: 'limit', width: 200 },
{ title: '预警', key: 'alert', width: 120 },
{ title: '状态', key: 'status', width: 120 },
{ title: '操作', key: 'actions', width: 240 }
{ title: '部门信息', key: 'deptInfo', width: 320 },
{ title: '负责人', key: 'leader', width: 120 },
{ title: '成员数', key: 'memberCount', width: 80 },
{ title: '积分余额', key: 'score', width: 140 },
{ title: '本周期消耗', key: 'limit', width: 140 },
{ title: '预警', key: 'alert', width: 80 },
{ title: '状态', key: 'status', width: 100 },
{ title: '操作', key: 'actions', width: 200 }
]
// 加载数据