diff --git a/ruoyi-ui/src/components/HeaderSearch/index.vue b/ruoyi-ui/src/components/HeaderSearch/index.vue index c3467cffe..94aa95b8b 100644 --- a/ruoyi-ui/src/components/HeaderSearch/index.vue +++ b/ruoyi-ui/src/components/HeaderSearch/index.vue @@ -5,6 +5,7 @@ :visible.sync="show" width="600px" @close="close" + @opened="onDialogOpened" :show-close="false" append-to-body > @@ -21,24 +22,54 @@ @keydown.down.native="navigateResult('down')" > + +
+ 找到 {{ options.length }} 个结果 +
+
-
-
- -
-
- + +
+ +
@@ -83,33 +114,37 @@ export default { click() { this.show = !this.show if (this.show) { - this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus() this.options = this.searchPool } }, + onDialogOpened() { + this.$nextTick(() => { + this.$refs.headerSearchSelectRef && this.$refs.headerSearchSelectRef.focus() + }) + }, close() { - this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur() + this.$refs.headerSearchSelectRef && this.$refs.headerSearchSelectRef.blur() this.search = '' - this.options = [] + this.options = this.searchPool this.show = false this.activeIndex = -1 }, change(val) { - const path = val.path + const p = val.path const query = val.query - if(isHttp(val.path)) { + if (isHttp(val.path)) { // http(s):// 路径新窗口打开 - const pindex = path.indexOf("http") - window.open(path.substr(pindex, path.length), "_blank") + const pindex = p.indexOf('http') + window.open(p.substr(pindex, p.length), '_blank') } else { if (query) { - this.$router.push({ path: path, query: JSON.parse(query) }) + this.$router.push({ path: p, query: JSON.parse(query) }) } else { - this.$router.push(path) + this.$router.push(p) } } this.search = '' - this.options = [] + this.options = this.searchPool this.$nextTick(() => { this.show = false }) @@ -117,7 +152,7 @@ export default { initFuse(list) { this.fuse = new Fuse(list, { shouldSort: true, - threshold: 0.4, + threshold: 0.2, minMatchCharLength: 1, keys: [{ name: 'title', @@ -128,37 +163,25 @@ export default { }] }) }, - // Filter out the routes that can be displayed in the sidebar - // And generate the internationalized title generateRoutes(routes, basePath = '/', prefixTitle = []) { let res = [] - for (const router of routes) { - // skip hidden router if (router.hidden) { continue } - const data = { path: !isHttp(router.path) ? path.resolve(basePath, router.path) : router.path, title: [...prefixTitle], icon: '' } - 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) } } - if (router.query) { data.query = router.query } - - // recursive child routes if (router.children) { const tempRoutes = this.generateRoutes(router.children, data.path, data.title) if (tempRoutes.length >= 1) { @@ -171,7 +194,18 @@ export default { querySearch(query) { this.activeIndex = -1 if (query !== '') { - this.options = this.fuse.search(query).map((item) => item.item) ?? this.searchPool + const q = query.toLowerCase() + const pathMatches = this.searchPool.filter(item => + item.path.toLowerCase().includes(q) + ) + const fuseMatches = this.fuse.search(query).map(item => item.item) + const merged = [...pathMatches] + fuseMatches.forEach(item => { + if (!merged.find(m => m.path === item.path)) { + merged.push(item) + } + }) + this.options = merged } else { this.options = this.searchPool } @@ -179,14 +213,14 @@ export default { activeStyle(index) { if (index !== this.activeIndex) return {} return { - "background-color": this.theme, - "color": "#fff" + 'background-color': this.theme, + 'color': '#fff' } }, navigateResult(direction) { - if (direction === "up") { + if (direction === 'up') { this.activeIndex = this.activeIndex <= 0 ? this.options.length - 1 : this.activeIndex - 1 - } else if (direction === "down") { + } else if (direction === 'down') { this.activeIndex = this.activeIndex >= this.options.length - 1 ? 0 : this.activeIndex + 1 } }, @@ -194,6 +228,16 @@ export default { if (this.options.length > 0 && this.activeIndex >= 0) { this.change(this.options[this.activeIndex]) } + }, + highlightText(text) { + if (!text) return '' + if (!this.search) return text + const keyword = this.escapeRegExp(this.search) + const reg = new RegExp(`(${keyword})`, 'gi') + return text.replace(reg, '$1') + }, + escapeRegExp(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } } } @@ -202,7 +246,17 @@ export default { +.search-footer { + display: flex; + align-items: center; + gap: 28px; + padding: 10px 20px; + border-top: 1px solid #f0f0f0; + color: #999; + font-size: 12px; + + .shortcut-item { + display: flex; + align-items: center; + gap: 5px; + } + + kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 5px; + border: 1px solid #ddd; + border-radius: 4px; + background: #f7f7f7; + color: #555; + font-size: 11px; + font-family: inherit; + line-height: 1; + box-shadow: 0 1px 0 #ccc; + } +} + \ No newline at end of file