fuse.js结合elment-ui做路由导航搜索
<template>
<div id="search" class="searching" v-show="visible">
<div class="container" ref="container">
<div class="search-box">
<el-input
placeholder="请输入内容"
prefix-icon="el-icon-search"
@input="valListen"
v-model="serachVal"
>
</el-input>
<div class="tips">
你可以使用快捷键<span>alt</span>+<span>s</span>唤醒搜索面板,按<span>esc</span>退出
</div>
</div>
<div class="result">
<a
target="_self"
class="item"
v-for="(option, index) in options"
:key="index"
@click="hrefClickTrigger(option.item)"
>
<div v-if="option.item" class="item-wrapper">
<div class="icon">
<svg-icon :icon-class="option.item.icon" />
</div>
<div class="info">
<div class="title">
{{ option.item.title[option.item.title.length - 1] }}
</div>
<div class="breadcrumb">
<span
v-html="
option.item.title.join(
'<i class=' + 'el-icon-arrow-right' + '></i>'
)
"
>
</span>
</div>
<div class="path">{{ option.item.path }}</div>
</div>
</div>
</a>
</div>
</div>
</div>
</template>
<script>
// fuse is a lightweight fuzzy-search module
// make search results more in line with expectations
import Fuse from "fuse.js";
import path from "path";
export default {
name: "search",
props: {
visible: {
type: Boolean,
default: false,
},
},
data() {
return {
serachVal: "",
searchPool: [],
options: [],
};
},
watch: {
routes(val) {
this.searchPool = this.generateRoutes(val);
},
searchPool(list) {
this.initFuse(list);
},
},
mounted() {
this.searchPool = this.generateRoutes(this.routes);
this.options = this.searchPool.map((val) => {
return {
item: val,
};
});
window.addEventListener("click", this.otherPartClose);
document.addEventListener("keydown", this.colseTrigger);
},
beforeDestroy() {
window.removeEventListener("click", this.otherPartClose);
},
computed: {
routes() {
return this.$store.getters.permission_routes;
},
},
methods: {
// alt + s 组合键打开, esc退出
colseTrigger(e) {
let key = window.event.keyCode
? window.event.keyCode
: window.event.which;
if (key === 83 && e.altKey) {
this.$emit("update:visible", true);
e.preventDefault();
}
if (key === 27) {
this.close();
}
},
// 点击其它区域关闭
otherPartClose(e) {
if (!this.$refs.container.contains(e.target)) this.close();
},
hrefClickTrigger(val) {
if (this.ishttp(val.path)) {
window.open(val.path, "_blank");
} else {
this.$router.push(val.path);
}
this.close();
},
close() {
this.$emit("update:visible", false);
this.serachVal = "";
this.$nextTick(() => {
this.valListen();
});
},
valListen() {
if (this.serachVal !== "") {
this.options = this.fuse.search(this.serachVal);
} else {
this.options = this.searchPool.map((val) => {
return {
item: val,
};
});
}
},
initFuse(list) {
this.fuse = new Fuse(list, {
shouldSort: true,
threshold: 0.2,
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) {
// skip hidden router
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];
data.icon = router.meta.icon || "";
if (router.redirect !== "noRedirect") {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data);
}
}
// recursive child routes
if (router.children) {
const tempRoutes = this.generateRoutes(
router.children,
data.path,
data.title
);
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes];
}
}
}
return res;
},
ishttp(url) {
return url.indexOf("http://") !== -1 || url.indexOf("https://") !== -1;
},
},
};
</script>
<style lang="scss" scoped>
#search {
position: fixed;
z-index: 2000;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
transition: all 0.2s;
transform: translateZ(0);
&.searching {
opacity: 1;
visibility: visible;
}
.container {
display: flex;
flex-direction: column;
max-width: 800px;
height: 100%;
margin: 0 auto;
transition: all 0.2s;
.search-box {
margin: 50px 20px 0;
.tips {
margin: 20px 0 40px;
line-height: 24px;
font-size: 14px;
text-align: center;
color: #fff;
span {
margin: 0 5px;
padding: 1px 5px 2px;
border-radius: 5px;
font-weight: 700;
background-color: rgba(0, 0, 0, 0.5);
}
}
}
.result {
margin: 0 20px;
max-height: calc(100% - 250px);
border-radius: 5px;
overflow: auto;
background-color: #fff;
.item {
display: flex;
align-items: center;
text-decoration: none;
cursor: pointer;
.item-wrapper {
display: flex;
align-items: center;
width: 100%;
.icon {
flex: 0 0 66px;
text-align: center;
line-height: 0;
.svg-icon {
color: #999;
font-size: 16px;
transition: all 0.3s;
}
}
.info {
flex: 1;
height: 70px;
display: flex;
flex-direction: column;
justify-content: space-around;
border-left: 1px solid #ebeef5;
padding: 5px 10px 7px;
text-overflow: ellipsis;
white-space: nowrap;
line-height: normal;
.title {
font-size: 18px;
font-weight: 700;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.breadcrumb,
.path {
font-size: 12px;
color: #c0c4cc;
transition: all 0.3s;
text-overflow: ellipsis;
white-space: nowrap;
}
}
&:hover {
transition: all 0.3s;
background-color: #f5f7fa;
color: #333333;
.title {
color: #000000;
}
.svg-icon {
color: #409eff;
transform: scale(1.2);
}
.breadcrumb,
.path {
color: #333333;
}
}
}
}
}
}
}
::v-deep .el-input {
.el-input__inner {
height: 52px;
line-height: 52px;
}
.el-input__prefix {
font-size: 14px;
}
}
::-webkit-scrollbar {
width: 13px;
height: 13px;
}
::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.4);
background-clip: padding-box;
border: 3px solid transparent;
border-radius: 7px;
}
::-webkit-scrollbar-track {
background-color: transparent;
}
</style>