<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().routes); 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; if (isHttp(path)) { // http(s):// 路径新窗口打开 const pindex = path.indexOf("http"); window.open(path.substr(pindex, path.length), "_blank"); } 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); } } // 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>