feature[Navbar]: add header-search component (#1591)

* init

* init

* refactor search function by fuse

* fix bug

* fix bug

* tweak
This commit is contained in:
花裤衩 2019-02-13 15:09:14 +08:00 committed by GitHub
parent fe381503eb
commit c71f3110fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 212 additions and 12 deletions

View File

@ -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",

View 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
View 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

View File

@ -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;
&.hover-effect {
cursor: pointer;
transition: background .3s; transition: background .3s;
&:hover { &:hover {
background: rgba(0, 0, 0, .025) 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;