| | |
| | | <el-dialog v-model="state.isShowSearch" destroy-on-close :show-close="false"> |
| | | <template #footer> |
| | | <el-autocomplete |
| | | ref="layoutMenuAutocompleteRef" |
| | | v-model="state.menuQuery" |
| | | :fetch-suggestions="menuSearch" |
| | | placeholder="搜索" |
| | | ref="layoutMenuAutocompleteRef" |
| | | @select="onHandleSelect" |
| | | :fit-input-width="true" |
| | | @select="onHandleSelect" |
| | | > |
| | | <template #prefix> |
| | | <svg-icon class-name="search-icon" icon-class="search" /> |
| | |
| | | import { getNormalPath } from '@/utils/ruoyi'; |
| | | import { isHttp } from '@/utils/validate'; |
| | | import usePermissionStore from '@/store/modules/permission'; |
| | | import { RouteOption } from 'vue-router'; |
| | | import { RouteRecordRaw } from 'vue-router'; |
| | | type Router = Array<{ |
| | | path: string; |
| | | icon: string; |
| | | title: string[]; |
| | | }> |
| | | path: string; |
| | | icon: string; |
| | | title: string[]; |
| | | }>; |
| | | type SearchState<T = any> = { |
| | | isShowSearch: boolean; |
| | | menuQuery: string; |
| | | menuList: T[]; |
| | | isShowSearch: boolean; |
| | | menuQuery: string; |
| | | menuList: T[]; |
| | | }; |
| | | // 定义变量内容 |
| | | const layoutMenuAutocompleteRef = ref(); |
| | | const router = useRouter(); |
| | | const routes = computed(() => usePermissionStore().routes); |
| | | const state = reactive<SearchState>({ |
| | | isShowSearch: false, |
| | | menuQuery: '', |
| | | menuList: [], |
| | | isShowSearch: false, |
| | | menuQuery: '', |
| | | menuList: [] |
| | | }); |
| | | |
| | | // 搜索弹窗打开 |
| | | const openSearch = () => { |
| | | state.menuQuery = ''; |
| | | state.isShowSearch = true; |
| | | state.menuList = generateRoutes(routes.value); |
| | | nextTick(() => { |
| | | setTimeout(() => { |
| | | layoutMenuAutocompleteRef.value.focus(); |
| | | }); |
| | | }); |
| | | state.menuQuery = ''; |
| | | state.isShowSearch = true; |
| | | state.menuList = generateRoutes(routes.value); |
| | | nextTick(() => { |
| | | setTimeout(() => { |
| | | layoutMenuAutocompleteRef.value.focus(); |
| | | }); |
| | | }); |
| | | }; |
| | | // 搜索弹窗关闭 |
| | | const closeSearch = () => { |
| | | state.isShowSearch = false; |
| | | state.isShowSearch = false; |
| | | }; |
| | | // 菜单搜索数据过滤 |
| | | const menuSearch = (queryString: string, cb: Function) => { |
| | | let options = state.menuList.filter((item) => { |
| | | return item.title.indexOf(queryString) > -1; |
| | | }); |
| | | cb(options); |
| | | let options = state.menuList.filter((item) => { |
| | | return item.title.indexOf(queryString) > -1; |
| | | }); |
| | | cb(options); |
| | | }; |
| | | |
| | | // Filter out the routes that can be displayed in the sidebar |
| | | // And generate the internationalized title |
| | | const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: string[] = []) => { |
| | | let res: Router = [] |
| | | routes.forEach(r => { |
| | | // skip hidden router |
| | | if (!r.hidden) { |
| | | const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path; |
| | | const data: any = { |
| | | path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path, |
| | | icon: r.meta?.icon, |
| | | 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]; |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | res.forEach((item: any) => { |
| | | if (item.title instanceof Array) { |
| | | item.title = item.title.join('/'); |
| | | } |
| | | }); |
| | | return res; |
| | | } |
| | | const generateRoutes = (routes: RouteRecordRaw[], basePath = '', prefixTitle: string[] = []) => { |
| | | let res: Router = []; |
| | | routes.forEach((r) => { |
| | | // skip hidden router |
| | | if (!r.hidden) { |
| | | const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path; |
| | | const data: any = { |
| | | path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path, |
| | | icon: r.meta?.icon, |
| | | 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]; |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | res.forEach((item: any) => { |
| | | if (item.title instanceof Array) { |
| | | item.title = item.title.join('/'); |
| | | } |
| | | }); |
| | | return res; |
| | | }; |
| | | // 当前菜单选中时 |
| | | const onHandleSelect = (val: any) => { |
| | | const paths = val.path; |
| | | if (isHttp(paths)) { |
| | | // http(s):// 路径新窗口打开 |
| | | const pindex = paths.indexOf("http"); |
| | | window.open(paths.substring(pindex, paths.length), "_blank"); |
| | | } else { |
| | | router.push(paths); |
| | | } |
| | | state.menuQuery = '' |
| | | closeSearch(); |
| | | |
| | | const paths = val.path; |
| | | if (isHttp(paths)) { |
| | | // http(s):// 路径新窗口打开 |
| | | const pindex = paths.indexOf('http'); |
| | | window.open(paths.substring(pindex, paths.length), '_blank'); |
| | | } else { |
| | | router.push(paths); |
| | | } |
| | | state.menuQuery = ''; |
| | | closeSearch(); |
| | | }; |
| | | |
| | | // 暴露变量 |
| | | defineExpose({ |
| | | openSearch |
| | | openSearch |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .layout-search-dialog { |
| | | position: relative; |
| | | :deep(.el-dialog) { |
| | | .el-dialog__header, |
| | | .el-dialog__body { |
| | | display: none; |
| | | } |
| | | .el-dialog__footer { |
| | | width: 100%; |
| | | position: absolute; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | top: -53vh; |
| | | } |
| | | } |
| | | :deep(.el-autocomplete) { |
| | | width: 560px; |
| | | position: absolute; |
| | | top: 150px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | } |
| | | position: relative; |
| | | :deep(.el-dialog) { |
| | | .el-dialog__header, |
| | | .el-dialog__body { |
| | | display: none; |
| | | } |
| | | .el-dialog__footer { |
| | | width: 100%; |
| | | position: absolute; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | top: -53vh; |
| | | } |
| | | } |
| | | :deep(.el-autocomplete) { |
| | | width: 560px; |
| | | position: absolute; |
| | | top: 150px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | } |
| | | } |
| | | </style> |