Vue实现全局菜单搜索框的示例
山山而川~xyj 人气:0前言
本篇文章分享一下我在实际开发
Vue
项目时遇到的需要 —— 全局菜单搜索。全局菜单搜索本质是router
的使用,该功能已经实现,接下来分享一下开发心得。
一、过滤路由
首先需要过滤出符合条件的路由信息,过滤的条件包含两个:
- 路由可以显示出现(
hidden: false
) - 路由元信息中包含
title
属性
代码展示:
/** * 筛选出可以在侧边栏显示的路由 * @param routes 路由 * @param basePath 路径 * @param prefixTitle 标题 */ const generateRoutes = (routes, basePath = '/', prefixTitle = []) => { let filterRoutes = [] for (const route of routes) { // 如果路由已经隐藏,跳过这次 if (route.hidden) { continue } const data = { path: path.resolve(basePath, route.path), title: [...prefixTitle], } // 仅推送有标题的路由 if (route.meta && route.meta.title) { data.title = [...data.title, route.meta.title] if (route.redirect !== 'noReDirect') { filterRoutes.push(data) } } // 循环子路由 if (route.children) { const childRoutes = generateRoutes(route.children, data.path, data.title) if (childRoutes.length >= 1) { filterRoutes = [...filterRoutes, ...childRoutes] } } } return filterRoutes }
注意:如果路由包含子路由,就要递归调用 generateRoutes
方法, 在递归结束之后把符合条件的路由赋值给 filterRoutes
,之后将其返回。
二、搜索框展示路由
实现搜索框使用的是 el-select
组件,在刚进入页面时需要在 onMounted
声明周期中调用 generateRoutes
方法,将其赋值给变量 searchPool
。
onMounted(() => { searchPool.value = generateRoutes(JSON.parse(JSON.stringify(authRoute))) })
接下来,需要定义 el-select
组件的 远程搜索方法
和 change
事件。其中远程搜索方法 query
作用是把符合搜索结果的信息筛选出来赋值给 options
,之后通过下拉选项展示这些信息。而 change
事件是当选中路由时实现路由的跳转并完成一些变量的初始化。
// 搜索框的远程搜索方法 const query = (queryVal) => { if (queryVal !== '') { options.value = fuse.value.search(queryVal) } else { options.value = [] } } /** * 输入框填充内容触发该方法 * @param val 搜索框中输入的值 */ const change = (val) => { if (val) { router.push({ path: val, }) } options.value = [] search.value = '' isShowSearch.value = false }
三、雏形出现但有缺陷
经过不断探索,终于实现了一个全局菜单搜索框,但是这时我们会发现一个 bug
:
这个问题是必须要输入完整的路由名称,路由才会在下拉框中展示,也就是说没有实现模糊查询功能,接下来针对这一问题进行解决。
四、优化搜索方式
路由搜索过程中没有实现模糊搜索的功能,接下来将借助 fusejs
实现这一功能。fuse.js
具体用法请参照 Fuse 官网。
初始化 fuse
:
/** * fuse 实现模糊搜索 * @param list 需要进行模糊搜索的集合 */ const fuseInit = (list) => { fuse.value = new Fuse(list, { shouldSort: true, threshold: 0.4, location: 0, distance: 100, minMatchCharLength: 1, keys: [ { name: 'title', weight: 0.7, }, { name: 'path', weight: 0.3, }, ], }) }
另外,需要不断监听 searchPool
,当 searchPool
改变时 调用 fuseInit
方法。
/** * 监听 searchPool */ watch(searchPool, (list) => { fuseInit(list) })
添加了模糊搜索功能之后,全局菜单搜索框就基本实现,接下来看一下展示效果:
五、完整代码展示
<template> <div class="search"> <el-tooltip content="菜单搜索" placement="bottom"> <el-icon style="font-size: 20px"><Search @click="handleSearch" /></el-icon> </el-tooltip> <el-dialog v-model="isShowSearch" class="header_dialog" width="600px" destroy-on-close :show-close="false" > <el-select style="width: 100%" ref="headerSearchSelect" v-model="search" :remote-method="query" filterable default-first-option remote placeholder="菜单搜索 :支持菜单名称、路径" class="header_search_select" @change="change" > <el-option v-for="item in options" :key="item.item.path" :value="item.item.path" :label=" item.item && item.item.title && item.item.title.length && item.item.title.join(' > ') " > </el-option> </el-select> </el-dialog> </div> </template> <script lang="ts" setup> import { onMounted, ref, watch } from 'vue' import { useRouter } from 'vue-router' import path from 'path-browserify' import Fuse from 'fuse.js' import authRoute from '@/router/modules/authRoute' const router = useRouter() const search = ref('') const isShowSearch = ref(false) const searchPool = ref([]) const options = ref([]) const fuse = ref(null) /** * fuse 实现模糊搜索 * @param list 需要进行模糊搜索的集合 */ const fuseInit = (list) => { fuse.value = new Fuse(list, { shouldSort: true, threshold: 0.4, location: 0, distance: 100, minMatchCharLength: 1, keys: [ { name: 'title', weight: 0.7, }, { name: 'path', weight: 0.3, }, ], }) } /** * 监听 searchPool */ watch(searchPool, (list) => { fuseInit(list) }) /** * 筛选出可以在侧边栏显示的路由 * @param routes 路由 * @param basePath 路径 * @param prefixTitle 标题 */ const generateRoutes = (routes, basePath = '/', prefixTitle = []) => { let filterRoutes = [] for (const route of routes) { // 如果路由已经隐藏,跳过这次 if (route.hidden) { continue } const data = { path: path.resolve(basePath, route.path), title: [...prefixTitle], } // 仅推送有标题的路由 if (route.meta && route.meta.title) { data.title = [...data.title, route.meta.title] if (route.redirect !== 'noReDirect') { filterRoutes.push(data) } } // 循环子路由 if (route.children) { const childRoutes = generateRoutes(route.children, data.path, data.title) if (childRoutes.length >= 1) { filterRoutes = [...filterRoutes, ...childRoutes] } } } return filterRoutes } /** * 控制搜索框的展示 */ const handleSearch = () => { isShowSearch.value = true } onMounted(() => { searchPool.value = generateRoutes(JSON.parse(JSON.stringify(authRoute))) }) // 搜索框的远程搜索方法 const query = (queryVal) => { if (queryVal !== '') { options.value = fuse.value.search(queryVal) } else { options.value = [] } } /** * 输入框填充内容触发该方法 * @param val 搜索框中输入的值 */ const change = (val) => { if (val) { router.push({ path: val, }) } options.value = [] search.value = '' isShowSearch.value = false } </script> <style lang="scss" scoped> .search { height: 100%; display: flex; justify-content: center; align-items: center; :deep(.el-dialog) { .el-dialog__header { display: none; } .el-dialog__body { padding: 0; } } .header_search_select { height: 50px; :deep(.el-input__wrapper) { height: 50px; } } } </style>
结论
加载全部内容