11<template >
2- <div :class = " {'show':show} " class =" header-search" >
2+ <div class =" header-search" >
33 <svg-icon class-name =" search-icon" icon-class =" search" @click.stop =" 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"
4+ <el-dialog
5+ :visible.sync =" show"
6+ width =" 600px"
7+ @close =" close"
8+ :show-close =" false"
9+ append-to-body
1410 >
15- <el-option v-for =" option in options" :key =" option.item.path" :value =" option.item" :label =" option.item.title.join(' > ')" />
16- </el-select >
11+ <el-input
12+ v-model =" search"
13+ ref =" headerSearchSelectRef"
14+ size =" large"
15+ @input =" querySearch"
16+ prefix-icon =" el-icon-search"
17+ placeholder =" 菜单搜索,支持标题、URL模糊查询"
18+ clearable
19+ @keyup.enter.native =" selectActiveResult"
20+ @keydown.up.native =" navigateResult('up')"
21+ @keydown.down.native =" navigateResult('down')"
22+ >
23+ </el-input >
24+ <el-scrollbar wrap-class =" right-scrollbar-wrapper" >
25+ <div class =" result-wrap" >
26+ <div class =" search-item" v-for =" (item, index) in options" :key =" item.path" :style =" activeStyle(index)" @mouseenter =" activeIndex = index" @mouseleave =" activeIndex = -1" >
27+ <div class =" left" >
28+ <svg-icon class =" menu-icon" :icon-class =" item.icon" />
29+ </div >
30+ <div class =" search-info" @click =" change(item)" >
31+ <div class =" menu-title" >
32+ {{ item.title.join(" / ") }}
33+ </div >
34+ <div class =" menu-path" >
35+ {{ item.path }}
36+ </div >
37+ </div >
38+ <svg-icon icon-class =" enter" v-show =" index === activeIndex" />
39+ </div >
40+ </div >
41+ </el-scrollbar >
42+ </el-dialog >
1743 </div >
1844</template >
1945
2046<script >
21- // fuse is a lightweight fuzzy-search module
22- // make search results more in line with expectations
2347import Fuse from ' fuse.js/dist/fuse.min.js'
2448import path from ' path'
2549import { isHttp } from ' @/utils/validate'
@@ -31,11 +55,15 @@ export default {
3155 search: ' ' ,
3256 options: [],
3357 searchPool: [],
58+ activeIndex: - 1 ,
3459 show: false ,
3560 fuse: undefined
3661 }
3762 },
3863 computed: {
64+ theme () {
65+ return this .$store .state .settings .theme
66+ },
3967 routes () {
4068 return this .$store .getters .defaultRoutes
4169 }
@@ -46,13 +74,6 @@ export default {
4674 },
4775 searchPool (list ) {
4876 this .initFuse (list)
49- },
50- show (value ) {
51- if (value) {
52- document .body .addEventListener (' click' , this .close )
53- } else {
54- document .body .removeEventListener (' click' , this .close )
55- }
5677 }
5778 },
5879 mounted () {
@@ -63,23 +84,26 @@ export default {
6384 this .show = ! this .show
6485 if (this .show ) {
6586 this .$refs .headerSearchSelect && this .$refs .headerSearchSelect .focus ()
87+ this .options = this .searchPool
6688 }
6789 },
6890 close () {
6991 this .$refs .headerSearchSelect && this .$refs .headerSearchSelect .blur ()
92+ this .search = ' '
7093 this .options = []
7194 this .show = false
95+ this .activeIndex = - 1
7296 },
7397 change (val ) {
74- const path = val .path ;
75- const query = val .query ;
98+ const path = val .path
99+ const query = val .query
76100 if (isHttp (val .path )) {
77101 // http(s):// 路径新窗口打开
78- const pindex = path .indexOf (" http" );
79- window .open (path .substr (pindex, path .length ), " _blank" );
102+ const pindex = path .indexOf (" http" )
103+ window .open (path .substr (pindex, path .length ), " _blank" )
80104 } else {
81105 if (query) {
82- this .$router .push ({ path: path, query: JSON .parse (query) });
106+ this .$router .push ({ path: path, query: JSON .parse (query) })
83107 } else {
84108 this .$router .push (path)
85109 }
@@ -117,11 +141,13 @@ export default {
117141
118142 const data = {
119143 path: ! isHttp (router .path ) ? path .resolve (basePath, router .path ) : router .path ,
120- title: [... prefixTitle]
144+ title: [... prefixTitle],
145+ icon: ' '
121146 }
122147
123148 if (router .meta && router .meta .title ) {
124149 data .title = [... data .title , router .meta .title ]
150+ data .icon = router .meta .icon
125151
126152 if (router .redirect !== ' noRedirect' ) {
127153 // only push the routes with title
@@ -145,52 +171,94 @@ export default {
145171 return res
146172 },
147173 querySearch (query ) {
174+ this .activeIndex = - 1
148175 if (query !== ' ' ) {
149- this .options = this .fuse .search (query)
176+ this .options = this .fuse .search (query). map (( item ) => item . item ) ?? this . searchPool
150177 } else {
151- this .options = []
178+ this .options = this .searchPool
179+ }
180+ },
181+ activeStyle (index ) {
182+ if (index !== this .activeIndex ) return {}
183+ return {
184+ " background-color" : this .theme ,
185+ " color" : " #fff"
186+ }
187+ },
188+ navigateResult (direction ) {
189+ if (direction === " up" ) {
190+ this .activeIndex = this .activeIndex <= 0 ? this .options .length - 1 : this .activeIndex - 1
191+ } else if (direction === " down" ) {
192+ this .activeIndex = this .activeIndex >= this .options .length - 1 ? 0 : this .activeIndex + 1
193+ }
194+ },
195+ selectActiveResult () {
196+ if (this .options .length > 0 && this .activeIndex >= 0 ) {
197+ this .change (this .options [this .activeIndex ])
152198 }
153199 }
154200 }
155201}
156202< / script>
157203
158- <style lang="scss" scoped>
159- .header-search {
160- font-size : 0 !important ;
204+ < style lang= ' scss' scoped>
205+ :: v- deep {
206+ .el - dialog__header {
207+ padding: 0 ! important;
208+ }
209+ }
161210
211+ .header - search {
162212 .search - icon {
163213 cursor: pointer;
164214 font- size: 18px ;
165215 vertical- align: middle;
166216 }
217+ }
167218
168- .header-search-select {
169- font-size : 18 px ;
170- transition : width 0.2 s ;
171- width : 0 ;
172- overflow : hidden ;
173- background : transparent ;
174- border-radius : 0 ;
175- display : inline-block ;
176- vertical-align : middle ;
219+ . result - wrap {
220+ height : 280px ;
221+ margin : 6px 0 ;
222+
223+ . search - item {
224+ display : flex ;
225+ height : 48px ;
226+ align - items : center ;
227+ padding - right : 10px ;
177228
178- ::v - deep .el - input__inner {
179- border-radius : 0 ;
180- border : 0 ;
181- padding-left : 0 ;
182- padding-right : 0 ;
183- box-shadow : none !important ;
184- border-bottom : 1 px solid #d9d9d9 ;
185- vertical-align : middle ;
229+ . left {
230+ width : 60px ;
231+ text - align : center ;
232+
233+ . menu - icon {
234+ width : 18px ;
235+ height : 18px ;
236+ }
186237 }
187- }
188238
189- & .show {
190- .header-search-select {
191- width : 210px ;
192- margin-left : 10px ;
239+ .search - info {
240+ padding- left: 5px ;
241+ margin- top: 10px ;
242+ width: 100 % ;
243+ display: flex;
244+ flex- direction: column;
245+ justify- content: flex- start;
246+ flex: 1 ;
247+
248+ .menu - title,
249+ .menu - path {
250+ height: 20px ;
251+ }
252+ .menu - path {
253+ color: #ccc;
254+ font- size: 10px ;
255+ }
193256 }
194257 }
258+
259+ .search - item: hover {
260+ cursor: pointer;
261+ }
195262}
196263< / style>
264+
0 commit comments