forked from AkkomaGang/admin-fe
feature[Navbar]: add header-search component (#1591)
* init * init * refactor search function by fuse * fix bug * fix bug * tweak
This commit is contained in:
parent
fe381503eb
commit
c71f3110fb
4 changed files with 212 additions and 12 deletions
|
@ -43,6 +43,7 @@
|
||||||
"echarts": "4.1.0",
|
"echarts": "4.1.0",
|
||||||
"element-ui": "2.4.11",
|
"element-ui": "2.4.11",
|
||||||
"file-saver": "1.3.8",
|
"file-saver": "1.3.8",
|
||||||
|
"fuse.js": "3.4.2",
|
||||||
"js-cookie": "2.2.0",
|
"js-cookie": "2.2.0",
|
||||||
"jsonlint": "1.6.3",
|
"jsonlint": "1.6.3",
|
||||||
"jszip": "3.1.5",
|
"jszip": "3.1.5",
|
||||||
|
|
187
src/components/HeaderSearch/index.vue
Normal file
187
src/components/HeaderSearch/index.vue
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
<template>
|
||||||
|
<div :class="{'show':show}" class="header-search">
|
||||||
|
<svg-icon class-name="search-icon" icon-class="search" @click="click" />
|
||||||
|
<el-select
|
||||||
|
ref="headerSearchSelect"
|
||||||
|
v-model="search"
|
||||||
|
:remote-method="querySearch"
|
||||||
|
filterable
|
||||||
|
default-first-option
|
||||||
|
remote
|
||||||
|
placeholder="Search"
|
||||||
|
class="header-search-select"
|
||||||
|
@change="change">
|
||||||
|
<el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')"/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Fuse from 'fuse.js'
|
||||||
|
import path from 'path'
|
||||||
|
import i18n from '@/lang'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HeaderSearch',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
search: '',
|
||||||
|
options: [],
|
||||||
|
searchPool: [],
|
||||||
|
show: false,
|
||||||
|
fuse: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
routers() {
|
||||||
|
return this.$store.getters.permission_routers
|
||||||
|
},
|
||||||
|
lang() {
|
||||||
|
return this.$store.getters.language
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
lang() {
|
||||||
|
this.searchPool = this.generateRouters(this.routers)
|
||||||
|
},
|
||||||
|
routers() {
|
||||||
|
this.searchPool = this.generateRouters(this.routers)
|
||||||
|
},
|
||||||
|
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.generateRouters(this.routers)
|
||||||
|
},
|
||||||
|
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) {
|
||||||
|
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
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// Filter out the routes that can be displayed in the sidebar
|
||||||
|
// And generate the internationalized title
|
||||||
|
generateRouters(routers, basePath = '/', prefixTitle = []) {
|
||||||
|
let res = []
|
||||||
|
|
||||||
|
for (const router of routers) {
|
||||||
|
// skip hidden router
|
||||||
|
if (router.hidden) { continue }
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
path: path.resolve(basePath, router.path),
|
||||||
|
title: [...prefixTitle]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (router.meta && router.meta.title) {
|
||||||
|
// generate internationalized title
|
||||||
|
const i18ntitle = i18n.t(`route.${router.meta.title}`)
|
||||||
|
|
||||||
|
data.title = [...data.title, i18ntitle]
|
||||||
|
|
||||||
|
if (router.redirect !== 'noredirect') {
|
||||||
|
// only push the routes with title
|
||||||
|
// special case: need to exclude parent router without redirect
|
||||||
|
res.push(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursive child routers
|
||||||
|
if (router.children) {
|
||||||
|
const tempRouters = this.generateRouters(router.children, data.path, data.title)
|
||||||
|
if (tempRouters.length >= 1) {
|
||||||
|
res = [...res, ...tempRouters]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
querySearch(query) {
|
||||||
|
if (query !== '') {
|
||||||
|
this.options = this.fuse.search(query)
|
||||||
|
} else {
|
||||||
|
this.options = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.header-search {
|
||||||
|
font-size: 0 !important;
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
|
||||||
|
/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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
1
src/icons/svg/search.svg
Normal file
1
src/icons/svg/search.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 0 0 8.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 0 0 7.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"/></svg>
|
After Width: | Height: | Size: 600 B |
|
@ -6,24 +6,26 @@
|
||||||
|
|
||||||
<div class="right-menu">
|
<div class="right-menu">
|
||||||
<template v-if="device!=='mobile'">
|
<template v-if="device!=='mobile'">
|
||||||
<error-log class="errLog-container right-menu-item"/>
|
<search class="right-menu-item" />
|
||||||
|
|
||||||
|
<error-log class="errLog-container right-menu-item hover-effect"/>
|
||||||
|
|
||||||
<el-tooltip :content="$t('navbar.screenfull')" effect="dark" placement="bottom">
|
<el-tooltip :content="$t('navbar.screenfull')" effect="dark" placement="bottom">
|
||||||
<screenfull class="right-menu-item"/>
|
<screenfull class="right-menu-item hover-effect"/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<el-tooltip :content="$t('navbar.size')" effect="dark" placement="bottom">
|
<el-tooltip :content="$t('navbar.size')" effect="dark" placement="bottom">
|
||||||
<size-select class="right-menu-item"/>
|
<size-select class="right-menu-item hover-effect"/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<lang-select class="right-menu-item"/>
|
<lang-select class="right-menu-item hover-effect"/>
|
||||||
|
|
||||||
<el-tooltip :content="$t('navbar.theme')" effect="dark" placement="bottom">
|
<el-tooltip :content="$t('navbar.theme')" effect="dark" placement="bottom">
|
||||||
<theme-picker class="theme-picker right-menu-item"/>
|
<theme-picker class="right-menu-item hover-effect"/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-dropdown class="avatar-container right-menu-item" trigger="click">
|
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
|
||||||
<div class="avatar-wrapper">
|
<div class="avatar-wrapper">
|
||||||
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
|
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
|
||||||
<i class="el-icon-caret-bottom"/>
|
<i class="el-icon-caret-bottom"/>
|
||||||
|
@ -57,6 +59,7 @@ import Screenfull from '@/components/Screenfull'
|
||||||
import SizeSelect from '@/components/SizeSelect'
|
import SizeSelect from '@/components/SizeSelect'
|
||||||
import LangSelect from '@/components/LangSelect'
|
import LangSelect from '@/components/LangSelect'
|
||||||
import ThemePicker from '@/components/ThemePicker'
|
import ThemePicker from '@/components/ThemePicker'
|
||||||
|
import Search from '@/components/HeaderSearch'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -66,7 +69,8 @@ export default {
|
||||||
Screenfull,
|
Screenfull,
|
||||||
SizeSelect,
|
SizeSelect,
|
||||||
LangSelect,
|
LangSelect,
|
||||||
ThemePicker
|
ThemePicker,
|
||||||
|
Search
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters([
|
...mapGetters([
|
||||||
|
@ -100,6 +104,7 @@ export default {
|
||||||
float: left;
|
float: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background .3s;
|
transition: background .3s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, .025)
|
background: rgba(0, 0, 0, .025)
|
||||||
}
|
}
|
||||||
|
@ -124,24 +129,30 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-menu-item {
|
.right-menu-item {
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 20px;
|
font-size: 18px;
|
||||||
color: #5a5e66;
|
color: #5a5e66;
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
transition: background .3s;
|
|
||||||
&:hover {
|
&.hover-effect {
|
||||||
background: rgba(0, 0, 0, .025)
|
cursor: pointer;
|
||||||
|
transition: background .3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, .025)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-container {
|
.avatar-container {
|
||||||
margin-right: 30px;
|
margin-right: 30px;
|
||||||
|
|
||||||
.avatar-wrapper {
|
.avatar-wrapper {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.user-avatar {
|
.user-avatar {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
|
|
Loading…
Reference in a new issue