search.vue 3.9 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. v-model="state.menuQuery"
  7. :fetch-suggestions="menuSearch"
  8. placeholder="搜索"
  9. ref="layoutMenuAutocompleteRef"
  10. @select="onHandleSelect"
  11. :fit-input-width="true"
  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 { RouteOption } 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);
  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: Function) => {
  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: RouteOption[], 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. .el-dialog__header,
  133. .el-dialog__body {
  134. display: none;
  135. }
  136. .el-dialog__footer {
  137. width: 100%;
  138. position: absolute;
  139. left: 50%;
  140. transform: translateX(-50%);
  141. top: -53vh;
  142. }
  143. }
  144. :deep(.el-autocomplete) {
  145. width: 560px;
  146. position: absolute;
  147. top: 150px;
  148. left: 50%;
  149. transform: translateX(-50%);
  150. }
  151. }
  152. </style>