mirror of
https://gitee.com/xiangheng/x_admin.git
synced 2025-12-24 08:12:55 +08:00
菜单过滤
This commit is contained in:
116
admin/src/utils/flatTreeUtils.ts
Normal file
116
admin/src/utils/flatTreeUtils.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
export type WithIdPid = {
|
||||
id?: string | number
|
||||
pid?: string | number | null | undefined
|
||||
}
|
||||
|
||||
// ---------- 1. 查询匹配节点 ----------
|
||||
export function findMatchedNodes<T extends WithIdPid>(
|
||||
data: T[],
|
||||
keyword: string,
|
||||
fields: string[] = ['name'],
|
||||
exact = false
|
||||
): T[] {
|
||||
if (!keyword) return []
|
||||
return data.filter((node) =>
|
||||
fields.some((field) => {
|
||||
const val = node[field]
|
||||
return typeof val === 'string' && (exact ? val === keyword : val.includes(keyword))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// ---------- 2. 查询所有子级(后代) ----------
|
||||
export function findDescendants<T extends WithIdPid>(data: T[], rootIds: (string | number)[]): T[] {
|
||||
// 构建 pid -> children 映射
|
||||
const childrenMap = new Map<string | number, T[]>()
|
||||
for (const node of data) {
|
||||
const p = node.pid ?? '__null__'
|
||||
if (!childrenMap.has(p)) childrenMap.set(p, [])
|
||||
childrenMap.get(p)!.push(node)
|
||||
}
|
||||
|
||||
const result: T[] = []
|
||||
const queue: (string | number)[] = [...rootIds]
|
||||
const visited = new Set<string | number>()
|
||||
|
||||
while (queue.length > 0) {
|
||||
const id = queue.shift()!
|
||||
if (visited.has(id)) continue
|
||||
visited.add(id)
|
||||
|
||||
const children = childrenMap.get(id) || []
|
||||
for (const child of children) {
|
||||
result.push(child)
|
||||
queue.push(child.id)
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(new Map(result.map((n) => [n.id, n])).values())
|
||||
}
|
||||
|
||||
// ---------- 3. 查询所有父级(祖先) ----------
|
||||
export function findAncestors<T extends WithIdPid>(data: T[], startIds: (string | number)[]): T[] {
|
||||
// 构建 id -> node 映射
|
||||
const nodeMap = new Map<string | number, T>()
|
||||
for (const node of data) {
|
||||
nodeMap.set(node.id, node)
|
||||
}
|
||||
|
||||
const result: T[] = []
|
||||
const visited = new Set<string | number>()
|
||||
|
||||
for (const id of startIds) {
|
||||
let currentId: string | number | null | undefined = id
|
||||
while (currentId != null) {
|
||||
const node = nodeMap.get(currentId)
|
||||
if (!node || visited.has(currentId)) break
|
||||
|
||||
visited.add(currentId)
|
||||
// 跳过起始节点自身(只查“父级”)
|
||||
if (currentId !== id) {
|
||||
result.push(node)
|
||||
}
|
||||
|
||||
currentId = node.pid
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(new Map(result.map((n) => [n.id, n])).values())
|
||||
}
|
||||
|
||||
// ---------- 4. 合并所有层级 ----------
|
||||
export function collectAllLevels<T extends WithIdPid>(
|
||||
matched: T[],
|
||||
ancestors: T[],
|
||||
descendants: T[]
|
||||
): T[] {
|
||||
const all = [...matched, ...ancestors, ...descendants]
|
||||
return Array.from(new Map(all.map((n) => [n.id, n])).values())
|
||||
}
|
||||
|
||||
// ---------- 5. 主入口:一键获取结构化结果 ----------
|
||||
export function queryHierarchy<T extends WithIdPid>(
|
||||
data: T[],
|
||||
keyword: string,
|
||||
options: {
|
||||
fields?: string[]
|
||||
exact?: boolean
|
||||
} = {}
|
||||
) {
|
||||
const { fields = ['name'], exact = false } = options
|
||||
|
||||
const matched = findMatchedNodes(data, keyword, fields, exact)
|
||||
const matchedIds = matched.map((n) => n.id)
|
||||
|
||||
const descendants = findDescendants(data, matchedIds)
|
||||
const ancestors = findAncestors(data, matchedIds)
|
||||
|
||||
const all = collectAllLevels(matched, ancestors, descendants)
|
||||
|
||||
return {
|
||||
matched, // 匹配的节点
|
||||
ancestors, // 所有父级(不包含 matched 自身)
|
||||
descendants, // 所有子级
|
||||
all // 合并后的完整列表(去重)
|
||||
}
|
||||
}
|
||||
@@ -289,7 +289,10 @@ const menuOptions = ref<any[]>([])
|
||||
const getMenu = async () => {
|
||||
const data: any = await menuLists()
|
||||
const menu: any = { id: '0', menuName: '顶级', children: [] }
|
||||
menu.children = arrayToTree(data.filter((item) => item.menuType != MenuEnum.BUTTON))
|
||||
menu.children = arrayToTree(
|
||||
data.filter((item) => item.menuType != MenuEnum.BUTTON),
|
||||
'0'
|
||||
)
|
||||
menuOptions.value.push(menu)
|
||||
}
|
||||
function getApiListFn() {
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
<template>
|
||||
<div class="menu-lists p-4 h-full box-border flex flex-col">
|
||||
<div>
|
||||
<el-input v-model="menuName" style="width: 240px" placeholder="Please input" />
|
||||
<el-input
|
||||
v-model="menuName"
|
||||
style="width: 240px"
|
||||
clearable
|
||||
placeholder="请输入菜单名称"
|
||||
/>
|
||||
|
||||
<el-button v-perms="['admin:system:menu:add']" type="primary" @click="handleAdd()">
|
||||
<el-button
|
||||
v-perms="['admin:system:menu:add']"
|
||||
type="primary"
|
||||
@click="handleAdd()"
|
||||
class="ml-4"
|
||||
>
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
@@ -11,12 +21,13 @@
|
||||
</el-button>
|
||||
<el-button @click="handleExpand"> 展开/收起 </el-button>
|
||||
</div>
|
||||
|
||||
<div class="mt-4" style="height: 100%">
|
||||
<vxe-table
|
||||
ref="tableRef"
|
||||
:row-config="rowConfig"
|
||||
:tree-config="treeConfig"
|
||||
:data="lists"
|
||||
:data="filterList"
|
||||
:border="'inner'"
|
||||
height="100%"
|
||||
:virtual-y-config="{ enabled: true, gt: 0 }"
|
||||
@@ -165,12 +176,13 @@
|
||||
<EditPopup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef, nextTick, computed } from 'vue'
|
||||
import { ref, useTemplateRef, nextTick, computed, toRaw } from 'vue'
|
||||
import { menuDelete, menuLists, SystemAuthMenuResp } from '@/api/perms/menu'
|
||||
// import { arrayToTree } from '@/utils/util'
|
||||
import { MenuEnum } from '@/enums/appEnums'
|
||||
import EditPopup from './edit.vue'
|
||||
import feedback from '@/utils/feedback'
|
||||
import { queryHierarchy } from '@/utils/flatTreeUtils'
|
||||
|
||||
import { VxeTableInstance } from 'vxe-table'
|
||||
|
||||
@@ -197,12 +209,19 @@ const loading = ref(false)
|
||||
const showEdit = ref(false)
|
||||
const lists = ref<SystemAuthMenuResp[]>([])
|
||||
const menuName = ref('')
|
||||
// const filterList = computed(() => {
|
||||
// if (!menuName.value) {
|
||||
// return lists.value
|
||||
// }
|
||||
// return lists.value.filter((item) => item.menuName.includes(menuName.value))
|
||||
// })
|
||||
const filterList = computed(() => {
|
||||
if (!menuName.value) {
|
||||
return lists.value
|
||||
}
|
||||
const raw = toRaw(lists.value)
|
||||
console.log('raw', raw)
|
||||
|
||||
const { all } = queryHierarchy(raw, menuName.value, {
|
||||
fields: ['menuName'],
|
||||
exact: false
|
||||
})
|
||||
return all
|
||||
})
|
||||
const getLists = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user