diff --git a/frontend/src/views/settings/components/DepartmentManagement.vue b/frontend/src/views/settings/components/DepartmentManagement.vue index 5f00076..8d14ae5 100644 --- a/frontend/src/views/settings/components/DepartmentManagement.vue +++ b/frontend/src/views/settings/components/DepartmentManagement.vue @@ -65,12 +65,20 @@

使用部门列表

- 当前共 {{ displayedDepartments.length }} 个部门节点 + 当前共 {{ flatDepartments.length }} 个部门节点,默认折叠显示

- +
+ + + +
@@ -91,7 +99,17 @@ >
- 📁 + + + + {{ expandedIds.has(department.id) ? '📂' : '📁' }} + 📄
{{ department.name }}
@@ -219,6 +237,7 @@ type FlatDepartmentNode = DepartmentTreeNode & { const loading = ref(false) const createLoading = ref(false) const updateLoading = ref(false) +const expandedIds = ref>(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() + const matchedIds = new Set() + 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