search.vue 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. <template>
  2. <div class="layout-search-dialog">
  3. <el-dialog v-model="state.isShowSearch" destroy-on-close :show-close="false">
  4. <template #footer>
  5. <el-autocomplete
  6. ref="layoutMenuAutocompleteRef"
  7. v-model="state.menuQuery"
  8. :fetch-suggestions="menuSearch"
  9. placeholder="搜索"
  10. :fit-input-width="true"
  11. @select="onHandleSelect"
  12. >
  13. <template #prefix>
  14. <svg-icon class-name="search-icon" icon-class="search" />
  15. </template>
  16. <template #default="{ item }">
  17. <div>
  18. <svg-icon :icon-class="item.icon" class="mr5" />
  19. {{ item.title }}
  20. </div>
  21. </template>
  22. </el-autocomplete>
  23. </template>
  24. </el-dialog>
  25. </div>
  26. </template>
  27. <script setup lang="ts" name="layoutBreadcrumbSearch">
  28. import { getNormalPath } from '@/utils/ruoyi';
  29. import { isHttp } from '@/utils/validate';
  30. import usePermissionStore from '@/store/modules/permission';
  31. import { RouteRecordRaw } from 'vue-router';
  32. type Router = Array<{
  33. path: string;
  34. icon: string;
  35. title: string[];
  36. }>;
  37. type SearchState<T = any> = {
  38. isShowSearch: boolean;
  39. menuQuery: string;
  40. menuList: T[];
  41. };
  42. // 定义变量内容
  43. const layoutMenuAutocompleteRef = ref();
  44. const router = useRouter();
  45. const routes = computed(() => usePermissionStore().routes);
  46. const state = reactive<SearchState>({
  47. isShowSearch: false,
  48. menuQuery: '',
  49. menuList: []
  50. });
  51. // 搜索弹窗打开
  52. const openSearch = () => {
  53. state.menuQuery = '';
  54. state.isShowSearch = true;
  55. state.menuList = generateRoutes(routes.value as any);
  56. nextTick(() => {
  57. setTimeout(() => {
  58. layoutMenuAutocompleteRef.value.focus();
  59. });
  60. });
  61. };
  62. // 搜索弹窗关闭
  63. const closeSearch = () => {
  64. state.isShowSearch = false;
  65. };
  66. // 菜单搜索数据过滤
  67. const menuSearch = (queryString: string, cb: (options: any[]) => void) => {
  68. let options = state.menuList.filter((item) => {
  69. return item.title.indexOf(queryString) > -1;
  70. });
  71. cb(options);
  72. };
  73. // Filter out the routes that can be displayed in the sidebar
  74. // And generate the internationalized title
  75. const generateRoutes = (routes: RouteRecordRaw[], basePath = '', prefixTitle: string[] = []) => {
  76. let res: Router = [];
  77. routes.forEach((r) => {
  78. // skip hidden router
  79. if (!r.hidden) {
  80. const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
  81. const data: any = {
  82. path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
  83. icon: r.meta?.icon,
  84. title: [...prefixTitle]
  85. };
  86. if (r.meta && r.meta.title) {
  87. data.title = [...data.title, r.meta.title];
  88. if (r.redirect !== 'noRedirect') {
  89. // only push the routes with title
  90. // special case: need to exclude parent router without redirect
  91. res.push(data);
  92. }
  93. }
  94. // recursive child routes
  95. if (r.children) {
  96. const tempRoutes = generateRoutes(r.children, data.path, data.title);
  97. if (tempRoutes.length >= 1) {
  98. res = [...res, ...tempRoutes];
  99. }
  100. }
  101. }
  102. });
  103. res.forEach((item: any) => {
  104. if (item.title instanceof Array) {
  105. item.title = item.title.join('/');
  106. }
  107. });
  108. return res;
  109. };
  110. // 当前菜单选中时
  111. const onHandleSelect = (val: any) => {
  112. const paths = val.path;
  113. if (isHttp(paths)) {
  114. // http(s):// 路径新窗口打开
  115. const pindex = paths.indexOf('http');
  116. window.open(paths.substring(pindex, paths.length), '_blank');
  117. } else {
  118. router.push(paths);
  119. }
  120. state.menuQuery = '';
  121. closeSearch();
  122. };
  123. // 暴露变量
  124. defineExpose({
  125. openSearch
  126. });
  127. </script>
  128. <style scoped lang="scss">
  129. .layout-search-dialog {
  130. position: relative;
  131. :deep(.el-dialog) {
  132. padding: 0;
  133. .el-dialog__header,
  134. .el-dialog__body {
  135. display: none;
  136. }
  137. .el-dialog__footer {
  138. width: 100%;
  139. position: absolute;
  140. left: 50%;
  141. transform: translateX(-50%);
  142. top: -53vh;
  143. }
  144. }
  145. :deep(.el-autocomplete) {
  146. width: 560px;
  147. position: absolute;
  148. top: 150px;
  149. left: 50%;
  150. transform: translateX(-50%);
  151. }
  152. }
  153. </style>