Skip to content

Commit c71f311

Browse files
authored
feature[Navbar]: add header-search component (#1591)
* init * init * refactor search function by fuse * fix bug * fix bug * tweak
1 parent fe38150 commit c71f311

File tree

4 files changed

+212
-12
lines changed

4 files changed

+212
-12
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"echarts": "4.1.0",
4444
"element-ui": "2.4.11",
4545
"file-saver": "1.3.8",
46+
"fuse.js": "3.4.2",
4647
"js-cookie": "2.2.0",
4748
"jsonlint": "1.6.3",
4849
"jszip": "3.1.5",
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<template>
2+
<div :class="{'show':show}" class="header-search">
3+
<svg-icon class-name="search-icon" icon-class="search" @click="click" />
4+
<el-select
5+
ref="headerSearchSelect"
6+
v-model="search"
7+
:remote-method="querySearch"
8+
filterable
9+
default-first-option
10+
remote
11+
placeholder="Search"
12+
class="header-search-select"
13+
@change="change">
14+
<el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')"/>
15+
</el-select>
16+
</div>
17+
</template>
18+
19+
<script>
20+
import Fuse from 'fuse.js'
21+
import path from 'path'
22+
import i18n from '@/lang'
23+
24+
export default {
25+
name: 'HeaderSearch',
26+
data() {
27+
return {
28+
search: '',
29+
options: [],
30+
searchPool: [],
31+
show: false,
32+
fuse: undefined
33+
}
34+
},
35+
computed: {
36+
routers() {
37+
return this.$store.getters.permission_routers
38+
},
39+
lang() {
40+
return this.$store.getters.language
41+
}
42+
},
43+
watch: {
44+
lang() {
45+
this.searchPool = this.generateRouters(this.routers)
46+
},
47+
routers() {
48+
this.searchPool = this.generateRouters(this.routers)
49+
},
50+
searchPool(list) {
51+
this.initFuse(list)
52+
},
53+
show(value) {
54+
if (value) {
55+
document.body.addEventListener('click', this.close)
56+
} else {
57+
document.body.removeEventListener('click', this.close)
58+
}
59+
}
60+
},
61+
mounted() {
62+
this.searchPool = this.generateRouters(this.routers)
63+
},
64+
methods: {
65+
click() {
66+
this.show = !this.show
67+
if (this.show) {
68+
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
69+
}
70+
},
71+
close() {
72+
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
73+
this.options = []
74+
this.show = false
75+
},
76+
change(val) {
77+
this.$router.push(val.path)
78+
this.search = ''
79+
this.options = []
80+
this.$nextTick(() => {
81+
this.show = false
82+
})
83+
},
84+
initFuse(list) {
85+
this.fuse = new Fuse(list, {
86+
shouldSort: true,
87+
threshold: 0.4,
88+
location: 0,
89+
distance: 100,
90+
maxPatternLength: 32,
91+
minMatchCharLength: 1,
92+
keys: [{
93+
name: 'title',
94+
weight: 0.7
95+
}, {
96+
name: 'path',
97+
weight: 0.3
98+
}]
99+
})
100+
},
101+
// Filter out the routes that can be displayed in the sidebar
102+
// And generate the internationalized title
103+
generateRouters(routers, basePath = '/', prefixTitle = []) {
104+
let res = []
105+
106+
for (const router of routers) {
107+
// skip hidden router
108+
if (router.hidden) { continue }
109+
110+
const data = {
111+
path: path.resolve(basePath, router.path),
112+
title: [...prefixTitle]
113+
}
114+
115+
if (router.meta && router.meta.title) {
116+
// generate internationalized title
117+
const i18ntitle = i18n.t(`route.${router.meta.title}`)
118+
119+
data.title = [...data.title, i18ntitle]
120+
121+
if (router.redirect !== 'noredirect') {
122+
// only push the routes with title
123+
// special case: need to exclude parent router without redirect
124+
res.push(data)
125+
}
126+
}
127+
128+
// recursive child routers
129+
if (router.children) {
130+
const tempRouters = this.generateRouters(router.children, data.path, data.title)
131+
if (tempRouters.length >= 1) {
132+
res = [...res, ...tempRouters]
133+
}
134+
}
135+
}
136+
return res
137+
},
138+
querySearch(query) {
139+
if (query !== '') {
140+
this.options = this.fuse.search(query)
141+
} else {
142+
this.options = []
143+
}
144+
}
145+
}
146+
}
147+
</script>
148+
149+
<style lang="scss" scoped>
150+
.header-search {
151+
font-size: 0 !important;
152+
153+
.search-icon {
154+
cursor: pointer;
155+
font-size: 18px;
156+
vertical-align: middle;
157+
}
158+
159+
.header-search-select {
160+
font-size: 18px;
161+
transition: width 0.2s;
162+
width: 0;
163+
overflow: hidden;
164+
background: transparent;
165+
border-radius: 0;
166+
display: inline-block;
167+
vertical-align: middle;
168+
169+
/deep/ .el-input__inner {
170+
border-radius: 0;
171+
border: 0;
172+
padding-left: 0;
173+
padding-right: 0;
174+
box-shadow: none !important;
175+
border-bottom: 1px solid #d9d9d9;
176+
vertical-align: middle;
177+
}
178+
}
179+
180+
&.show {
181+
.header-search-select {
182+
width: 210px;
183+
margin-left: 10px;
184+
}
185+
}
186+
}
187+
</style>

src/icons/svg/search.svg

Lines changed: 1 addition & 0 deletions
Loading

src/views/layout/components/Navbar.vue

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,26 @@
66

77
<div class="right-menu">
88
<template v-if="device!=='mobile'">
9-
<error-log class="errLog-container right-menu-item"/>
9+
<search class="right-menu-item" />
10+
11+
<error-log class="errLog-container right-menu-item hover-effect"/>
1012

1113
<el-tooltip :content="$t('navbar.screenfull')" effect="dark" placement="bottom">
12-
<screenfull class="right-menu-item"/>
14+
<screenfull class="right-menu-item hover-effect"/>
1315
</el-tooltip>
1416

1517
<el-tooltip :content="$t('navbar.size')" effect="dark" placement="bottom">
16-
<size-select class="right-menu-item"/>
18+
<size-select class="right-menu-item hover-effect"/>
1719
</el-tooltip>
1820

19-
<lang-select class="right-menu-item"/>
21+
<lang-select class="right-menu-item hover-effect"/>
2022

2123
<el-tooltip :content="$t('navbar.theme')" effect="dark" placement="bottom">
22-
<theme-picker class="theme-picker right-menu-item"/>
24+
<theme-picker class="right-menu-item hover-effect"/>
2325
</el-tooltip>
2426
</template>
2527

26-
<el-dropdown class="avatar-container right-menu-item" trigger="click">
28+
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
2729
<div class="avatar-wrapper">
2830
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
2931
<i class="el-icon-caret-bottom"/>
@@ -57,6 +59,7 @@ import Screenfull from '@/components/Screenfull'
5759
import SizeSelect from '@/components/SizeSelect'
5860
import LangSelect from '@/components/LangSelect'
5961
import ThemePicker from '@/components/ThemePicker'
62+
import Search from '@/components/HeaderSearch'
6063
6164
export default {
6265
components: {
@@ -66,7 +69,8 @@ export default {
6669
Screenfull,
6770
SizeSelect,
6871
LangSelect,
69-
ThemePicker
72+
ThemePicker,
73+
Search
7074
},
7175
computed: {
7276
...mapGetters([
@@ -100,6 +104,7 @@ export default {
100104
float: left;
101105
cursor: pointer;
102106
transition: background .3s;
107+
103108
&:hover {
104109
background: rgba(0, 0, 0, .025)
105110
}
@@ -124,24 +129,30 @@ export default {
124129
}
125130
126131
.right-menu-item {
127-
cursor: pointer;
128132
display: inline-block;
129133
padding: 0 8px;
130134
height: 100%;
131-
font-size: 20px;
135+
font-size: 18px;
132136
color: #5a5e66;
133137
vertical-align: text-bottom;
134-
transition: background .3s;
135-
&:hover {
136-
background: rgba(0, 0, 0, .025)
138+
139+
&.hover-effect {
140+
cursor: pointer;
141+
transition: background .3s;
142+
143+
&:hover {
144+
background: rgba(0, 0, 0, .025)
145+
}
137146
}
138147
}
139148
140149
.avatar-container {
141150
margin-right: 30px;
151+
142152
.avatar-wrapper {
143153
margin-top: 5px;
144154
position: relative;
155+
145156
.user-avatar {
146157
cursor: pointer;
147158
width: 40px;

0 commit comments

Comments
 (0)