支持部门列表折叠与展开

This commit is contained in:
WuYongJie 2026-03-11 20:59:48 +08:00
parent 358bac21b4
commit 0027c6b94f

View File

@ -65,12 +65,20 @@
<div>
<h2 class="text-apple-title">使用部门列表</h2>
<p class="mt-1 text-apple-footnote text-system-label-tertiary">
当前共 {{ displayedDepartments.length }} 个部门节点
当前共 {{ flatDepartments.length }} 个部门节点,默认折叠显示
</p>
</div>
<button class="btn-apple-secondary" :disabled="loading" @click="fetchDepartments">
刷新
</button>
<div class="flex items-center gap-2">
<button class="btn-apple-secondary" :disabled="loading" @click="expandAll">
全部展开
</button>
<button class="btn-apple-secondary" :disabled="loading" @click="collapseAll">
全部收起
</button>
<button class="btn-apple-secondary" :disabled="loading" @click="fetchDepartments">
刷新
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full">
@ -91,7 +99,17 @@
>
<td class="py-3 px-4">
<div class="flex items-center gap-2" :style="{ paddingLeft: `${department.level * 20}px` }">
<span v-if="department.children_count > 0" class="text-apple-blue">📁</span>
<button
v-if="department.children_count > 0"
class="flex h-5 w-5 items-center justify-center rounded text-system-label-secondary hover:bg-system-gray-6 hover:text-system-label-primary"
@click="toggleExpand(department.id)"
>
{{ expandedIds.has(department.id) ? '▾' : '▸' }}
</button>
<span v-else class="inline-block h-5 w-5"></span>
<span v-if="department.children_count > 0" class="text-apple-blue">
{{ expandedIds.has(department.id) ? '📂' : '📁' }}
</span>
<span v-else class="text-system-label-tertiary">📄</span>
<div>
<div class="font-medium">{{ department.name }}</div>
@ -219,6 +237,7 @@ type FlatDepartmentNode = DepartmentTreeNode & {
const loading = ref(false)
const createLoading = ref(false)
const updateLoading = ref(false)
const expandedIds = ref<Set<number>>(new Set())
const filters = reactive({
keyword: '',
@ -269,14 +288,58 @@ const parentOptions = computed(() =>
const displayedDepartments = computed(() => {
const keyword = filters.keyword.trim().toLowerCase()
return flatDepartments.value.filter((item) => {
const matchKeyword =
!keyword ||
item.name.toLowerCase().includes(keyword) ||
(item.parent_name || '').toLowerCase().includes(keyword)
const matchStatus = !filters.status || item.status === filters.status
return matchKeyword && matchStatus
})
const visibleIds = new Set<number>()
const matchedIds = new Set<number>()
const expanded = expandedIds.value
const walk = (
nodes: DepartmentTreeNode[],
level = 0,
parentName: string | null = null,
parentVisible = true
): FlatDepartmentNode[] => {
const list: FlatDepartmentNode[] = []
for (const node of nodes) {
const item: FlatDepartmentNode = {
...node,
level,
parent_name: parentName,
optionLabel: `${level > 0 ? `${'— '.repeat(level)}` : ''}${node.name}`
}
const matchKeyword =
!keyword ||
item.name.toLowerCase().includes(keyword) ||
(item.parent_name || '').toLowerCase().includes(keyword)
const matchStatus = !filters.status || item.status === filters.status
const selfMatch = matchKeyword && matchStatus
const childList = walk(node.children || [], level + 1, node.name, parentVisible && expanded.has(node.id))
const childMatched = childList.some((child) => matchedIds.has(child.id))
if (selfMatch || childMatched) {
matchedIds.add(node.id)
}
const shouldShow = keyword
? selfMatch || childMatched
: parentVisible && matchStatus
if (shouldShow) {
visibleIds.add(node.id)
list.push(item)
}
if (keyword) {
list.push(...childList.filter((child) => matchedIds.has(child.id)))
} else if (expanded.has(node.id)) {
list.push(...childList.filter((child) => visibleIds.has(child.id)))
}
}
return list
}
return walk(treeData.value)
})
const canCreate = computed(() => createForm.name.trim().length > 0)
@ -302,6 +365,37 @@ const getTotalEquipmentCount = (dept: FlatDepartmentNode): number => {
const getLevelLabel = (level: number) => `第 ${level + 1} 级`
const collectExpandableIds = (nodes: DepartmentTreeNode[]): number[] => {
const ids: number[] = []
for (const node of nodes) {
if (node.children_count > 0) {
ids.push(node.id)
}
if (node.children?.length) {
ids.push(...collectExpandableIds(node.children))
}
}
return ids
}
const toggleExpand = (id: number) => {
const next = new Set(expandedIds.value)
if (next.has(id)) {
next.delete(id)
} else {
next.add(id)
}
expandedIds.value = next
}
const expandAll = () => {
expandedIds.value = new Set(collectExpandableIds(treeData.value))
}
const collapseAll = () => {
expandedIds.value = new Set()
}
const resetCreateForm = () => {
createForm.name = ''
createForm.parent_id = null