| <template> | 
|   <div :class="{ 'show': show }" class="header-search"> | 
|     <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" /> | 
|     <el-select | 
|       ref="headerSearchSelectRef" | 
|       v-model="search" | 
|       :remote-method="querySearch" | 
|       filterable | 
|       default-first-option | 
|       remote | 
|       placeholder="Search" | 
|       class="header-search-select" | 
|       @change="change" | 
|     > | 
|       <el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" /> | 
|     </el-select> | 
|   </div> | 
| </template> | 
|   | 
| <script setup> | 
| import Fuse from 'fuse.js' | 
| import { getNormalPath } from '@/utils/ruoyi' | 
| import { isHttp } from '@/utils/validate' | 
| import usePermissionStore from '@/store/modules/permission' | 
|   | 
| const search = ref(''); | 
| const options = ref([]); | 
| const searchPool = ref([]); | 
| const show = ref(false); | 
| const fuse = ref(undefined); | 
| const headerSearchSelectRef = ref(null); | 
| const router = useRouter(); | 
| const routes = computed(() => usePermissionStore().defaultRoutes); | 
|   | 
| function click() { | 
|   show.value = !show.value | 
|   if (show.value) { | 
|     headerSearchSelectRef.value && headerSearchSelectRef.value.focus() | 
|   } | 
| }; | 
| function close() { | 
|   headerSearchSelectRef.value && headerSearchSelectRef.value.blur() | 
|   options.value = [] | 
|   show.value = false | 
| } | 
| function change(val) { | 
|   const path = val.path; | 
|   const query = val.query; | 
|   if (isHttp(path)) { | 
|     // http(s):// 路径新窗口打开 | 
|     const pindex = path.indexOf("http"); | 
|     window.open(path.substr(pindex, path.length), "_blank"); | 
|   } else { | 
|     if (query) { | 
|       router.push({ path: path, query: JSON.parse(query) }); | 
|     } else { | 
|       router.push(path) | 
|     } | 
|   } | 
|   | 
|   search.value = '' | 
|   options.value = [] | 
|   nextTick(() => { | 
|     show.value = false | 
|   }) | 
| } | 
| function initFuse(list) { | 
|   fuse.value = new Fuse(list, { | 
|     shouldSort: true, | 
|     threshold: 0.4, | 
|     location: 0, | 
|     distance: 100, | 
|     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 | 
| function generateRoutes(routes, basePath = '', prefixTitle = []) { | 
|   let res = [] | 
|   | 
|   for (const r of routes) { | 
|     // skip hidden router | 
|     if (r.hidden) { continue } | 
|     const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path; | 
|     const data = { | 
|       path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path, | 
|       title: [...prefixTitle] | 
|     } | 
|   | 
|     if (r.meta && r.meta.title) { | 
|       data.title = [...data.title, r.meta.title] | 
|   | 
|       if (r.redirect !== 'noRedirect') { | 
|         // only push the routes with title | 
|         // special case: need to exclude parent router without redirect | 
|         res.push(data) | 
|       } | 
|     } | 
|     if (r.query) { | 
|       data.query = r.query | 
|     } | 
|   | 
|     // recursive child routes | 
|     if (r.children) { | 
|       const tempRoutes = generateRoutes(r.children, data.path, data.title) | 
|       if (tempRoutes.length >= 1) { | 
|         res = [...res, ...tempRoutes] | 
|       } | 
|     } | 
|   } | 
|   return res | 
| } | 
| function querySearch(query) { | 
|   if (query !== '') { | 
|     options.value = fuse.value.search(query) | 
|   } else { | 
|     options.value = [] | 
|   } | 
| } | 
|   | 
| onMounted(() => { | 
|   searchPool.value = generateRoutes(routes.value); | 
| }) | 
|   | 
| watchEffect(() => { | 
|   searchPool.value = generateRoutes(routes.value) | 
| }) | 
|   | 
| watch(show, (value) => { | 
|   if (value) { | 
|     document.body.addEventListener('click', close) | 
|   } else { | 
|     document.body.removeEventListener('click', close) | 
|   } | 
| }) | 
|   | 
| watch(searchPool, (list) => { | 
|   initFuse(list) | 
| }) | 
| </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> |