菜单过滤

This commit is contained in:
xh
2025-12-22 12:02:47 +08:00
parent 7cc69210b4
commit cce263f69d
3 changed files with 149 additions and 11 deletions

View 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 // 合并后的完整列表(去重)
}
}

View File

@@ -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() {

View File

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