# 利用fuse.js实现搜索菜单功能
对于后台管理系统而言,当页面菜单数量很多的时候,找一个功能点,就可能找半天。因此急需出一个搜索菜单功能,于是发现了fuse.js这个能帮助实现该功能的东西。
fuse.js (opens new window)是一个轻量的模糊搜索库
# 安装
npm install --save fuse.js
# 使用
mport Fuse from 'fuse.js'
const list = [...] // 带搜索的数据
const options = {keys:['name']} // 搜索配置,可以配置多个查找字段
const fuse = new Fuse(list, options);
return fuse.search('psr') // 根据模式返回搜索结果,形式如[{item:{匹配的对象},refIndex:0},...]
# 常用配置项
配置项 | 描述 | 默认值 | 说明 |
---|---|---|---|
isCaseSensitive | 大小写敏感 | false | |
includeScore | 结果包含匹配度 | false | 结果值:0表示完全匹配,1表示完全不匹配 |
includeMatches | 结果包含匹配字符的索引值 | false | 可用于高亮显示搜索字符的场景 |
minMatchCharLength | 最小匹配长度 | 1 | 可用于需要至少几个字符才执行搜索的场景 |
shouldSort | 结果集排序 | true | 结果集按照匹配度排序 |
findAllMatches | 查找所有项目 | false | 即使找到了完全匹配项目也继续查找完其他所有项目 |
keys | 查找字段配置 | 被查字段的路径(支持嵌套查找),权重(默认权重值为1),例如:[‘name.first’,{name:‘name.last’,weight:0.5}] | |
location | 匹配的字符预期的位置 | 0 | 匹配到的字符距离指定位置越近分数越高 |
threshold | 匹配度阈值 | 0.6 | 0.0表示完全匹配(字符和位置);1.0将会匹配所有值 |
distance | l匹配的字符在location指定位置的范围 | 100 | 0表示必须正好在location指定的位置 |
ignoreLocation | 忽略location配置参数 | false | location和distance都会被忽略 |
# 项目实战
新增一个搜索组件,代码如下
<template>
<div :class="{'show':show}" class="header-search">
<span class="search" @click.stop="click">
<svg-icon class-name="search-icon" icon-class="search" />
</span>
<el-select ref="headerSearchSelect" v-model="search" :remote-method="querySearch" filterable default-first-option remote placeholder="搜索菜单" class="header-search-select" @change="change">
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
</el-select>
</div>
</template>
<script>
// fuse is a lightweight fuzzy-search module
// make search results more in line with expectations
import Fuse from "fuse.js/dist/fuse.min.js"
import path from "path"
export default {
name: "HeaderSearch",
data() {
return {
search: "",
options: [],
searchPool: [],
show: false,
fuse: undefined
}
},
computed: {
routes() {
//返回菜单树
return this.$store.getters.permission_routers
}
},
watch: {
routes() {
this.searchPool = this.generateRoutes(this.routes)
},
searchPool(list) {
this.initFuse(list)
},
show(value) {
if (value) {
document.body.addEventListener("click", this.close)
} else {
document.body.removeEventListener("click", this.close)
}
}
},
mounted() {
this.searchPool = this.generateRoutes(this.routes)
},
methods: {
click() {
this.show = !this.show
if (this.show) {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
}
},
close() {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
this.options = []
this.show = false
},
change(val) {
const path = val.path
if (this.ishttp(path)) {
// http(s):// 路径新窗口打开
window.open(path, "_blank")
} else {
this.$router.push(val.path)
}
this.search = ""
this.options = []
this.$nextTick(() => {
this.show = false
})
},
initFuse(list) {
this.fuse = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
name: "title",
weight: 0.7
}, {
name: "path",
weight: 0.3
}]
})
},
// 筛选出可以显示在侧栏中的路线 并生成标题
generateRoutes(routes, basePath = "/", prefixTitle = []) {
let res = []
for (const router of routes) {
// 跳过隐藏路由器
if (router.hidden) { continue }
const data = {
path: !this.ishttp(router.path) ? path.resolve(basePath, router.path) : router.path,
title: [...prefixTitle]
}
if (router.meta && router.meta.title) {
data.title = [...data.title, router.meta.title]
if (router.redirect !== 'noredirect') {
//只推带标题的路线
// 特殊情况:需要在不重定向的情况下排除父路由器
res.push(data)
}
}
// 递归子路由
if (router.children) {
const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes]
}
}
}
return res
},
querySearch(query) {
if (query !== "") {
this.options = this.fuse.search(query)
} else {
this.options = []
}
},
ishttp(url) {
return url.indexOf("http://") !== -1 || url.indexOf("https://") !== -1
}
}
}
</script>
<style lang="scss" scoped>
.header-search {
font-size: 0 !important;
.search-icon {
cursor: pointer;
font-size: 18px;
color: #fff;
vertical-align: middle;
display: inline-block;
}
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
border-radius: 4px;
/deep/ .el-input__inner {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
/deep/ .el-input__inner {
padding: 0 12px;
}
}
}
}
</style>
keys为查找字段配置,上述组件仅配置 title和path,如需新增搜索条件,可多个seo字段,然后也给seo字段一个搜索权重配置。
# 项目中使用效果如下
# 归纳
以上组件已实现菜单搜索功能,组件不足之处可自行升级使用。