@@ -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
|