支持部门列表折叠与展开
This commit is contained in:
parent
358bac21b4
commit
0027c6b94f
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user