mirror of
				https://github.com/pure-admin/vue-pure-admin.git
				synced 2025-11-03 13:44:47 +08:00 
			
		
		
		
	Merge branch 'main' into gitee
This commit is contained in:
		
						commit
						08110770e0
					
				@ -1,8 +1,28 @@
 | 
				
			|||||||
 | 
					# 5.1.0 (2024-03-02)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### ✔️ refactor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Reconstruct the tab page `UI` to make it more convenient to click the close button
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🎫 Feat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added multi-select card example
 | 
				
			||||||
 | 
					- The menu supports all browser behaviors of right-clicking on the `a` tag (opening links in new tabs, new windows, dragging and dropping to open in new tabs, etc.)
 | 
				
			||||||
 | 
					- Added search history and collection functions to menu search
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🐞 Bug fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed the problem of scroll bars appearing on the login page under `windows` system
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🍏 Perf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Standardize the naming rules when registering local icons to better match the icon selector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 5.0.0 (2024-02-26)
 | 
					# 5.0.0 (2024-02-26)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Totally `ESM` version
 | 
					Totally `ESM` version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### ✔️refactor
 | 
					### ✔️ refactor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Upgrade `vite` to `v5` version, specify `node` version `>18.18.0`, `pnpm` version `>=8.6.10`
 | 
					- Upgrade `vite` to `v5` version, specify `node` version `>18.18.0`, `pnpm` version `>=8.6.10`
 | 
				
			||||||
- Use [vite-plugin-fake-server](https://www.npmjs.com/package/vite-plugin-fake-server) to replace [vite-plugin-mock](https://www.npmjs.com/package/vite-plugin-mock), use [@faker-js/faker](https://www.npmjs.com/package/@faker-js/faker) to replace [mockjs](https://www.npmjs.com/package/mockjs)
 | 
					- Use [vite-plugin-fake-server](https://www.npmjs.com/package/vite-plugin-fake-server) to replace [vite-plugin-mock](https://www.npmjs.com/package/vite-plugin-mock), use [@faker-js/faker](https://www.npmjs.com/package/@faker-js/faker) to replace [mockjs](https://www.npmjs.com/package/mockjs)
 | 
				
			||||||
@ -18,7 +38,7 @@ Totally `ESM` version
 | 
				
			|||||||
- Cleaner and neater pop-up panel on the right side of project configuration
 | 
					- Cleaner and neater pop-up panel on the right side of project configuration
 | 
				
			||||||
- Restructure the About page to make it more compact and key information more prominent
 | 
					- Restructure the About page to make it more compact and key information more prominent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 🎫Feat
 | 
					### 🎫 Feat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Add system management-menu management
 | 
					- Add system management-menu management
 | 
				
			||||||
- Improve system management-user management
 | 
					- Improve system management-user management
 | 
				
			||||||
@ -59,7 +79,7 @@ Totally `ESM` version
 | 
				
			|||||||
- Fixed the problem that the right-click tab page panel cannot be closed when clicking on the `iframe` page and the right-click tab page panel is blocked when on the `iframe` page
 | 
					- Fixed the problem that the right-click tab page panel cannot be closed when clicking on the `iframe` page and the right-click tab page panel is blocked when on the `iframe` page
 | 
				
			||||||
- Fixed the problem of missing parameters when clicking on breadcrumbs to jump to the page in routing `query` and `params` modes
 | 
					- Fixed the problem of missing parameters when clicking on breadcrumbs to jump to the page in routing `query` and `params` modes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 🍏Perf
 | 
					### 🍏 Perf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Optimize theme color
 | 
					- Optimize theme color
 | 
				
			||||||
- Tabs can be slid left or right according to the sliding force
 | 
					- Tabs can be slid left or right according to the sliding force
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										26
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@ -1,8 +1,28 @@
 | 
				
			|||||||
 | 
					# 5.1.0 (2024-03-02)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### ✔️ refactor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Reconstruct the tab page `UI` to make it more convenient to click the close button
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🎫 Feat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added multi-select card example
 | 
				
			||||||
 | 
					- The menu supports all browser behaviors of right-clicking on the `a` tag (opening links in new tabs, new windows, dragging and dropping to open in new tabs, etc.)
 | 
				
			||||||
 | 
					- Added search history and collection functions to menu search
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🐞 Bug fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed the problem of scroll bars appearing on the login page under `windows` system
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🍏 Perf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Standardize the naming rules when registering local icons to better match the icon selector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 5.0.0 (2024-02-26)
 | 
					# 5.0.0 (2024-02-26)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Totally `ESM` version
 | 
					Totally `ESM` version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### ✔️refactor
 | 
					### ✔️ refactor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Upgrade `vite` to `v5` version, specify `node` version `>18.18.0`, `pnpm` version `>=8.6.10`
 | 
					- Upgrade `vite` to `v5` version, specify `node` version `>18.18.0`, `pnpm` version `>=8.6.10`
 | 
				
			||||||
- Use [vite-plugin-fake-server](https://www.npmjs.com/package/vite-plugin-fake-server) to replace [vite-plugin-mock](https://www.npmjs.com/package/vite-plugin-mock), use [@faker-js/faker](https://www.npmjs.com/package/@faker-js/faker) to replace [mockjs](https://www.npmjs.com/package/mockjs)
 | 
					- Use [vite-plugin-fake-server](https://www.npmjs.com/package/vite-plugin-fake-server) to replace [vite-plugin-mock](https://www.npmjs.com/package/vite-plugin-mock), use [@faker-js/faker](https://www.npmjs.com/package/@faker-js/faker) to replace [mockjs](https://www.npmjs.com/package/mockjs)
 | 
				
			||||||
@ -18,7 +38,7 @@ Totally `ESM` version
 | 
				
			|||||||
- Cleaner and neater pop-up panel on the right side of project configuration
 | 
					- Cleaner and neater pop-up panel on the right side of project configuration
 | 
				
			||||||
- Restructure the About page to make it more compact and key information more prominent
 | 
					- Restructure the About page to make it more compact and key information more prominent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 🎫Feat
 | 
					### 🎫 Feat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Add system management-menu management
 | 
					- Add system management-menu management
 | 
				
			||||||
- Improve system management-user management
 | 
					- Improve system management-user management
 | 
				
			||||||
@ -59,7 +79,7 @@ Totally `ESM` version
 | 
				
			|||||||
- Fixed the problem that the right-click tab page panel cannot be closed when clicking on the `iframe` page and the right-click tab page panel is blocked when on the `iframe` page
 | 
					- Fixed the problem that the right-click tab page panel cannot be closed when clicking on the `iframe` page and the right-click tab page panel is blocked when on the `iframe` page
 | 
				
			||||||
- Fixed the problem of missing parameters when clicking on breadcrumbs to jump to the page in routing `query` and `params` modes
 | 
					- Fixed the problem of missing parameters when clicking on breadcrumbs to jump to the page in routing `query` and `params` modes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 🍏Perf
 | 
					### 🍏 Perf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Optimize theme color
 | 
					- Optimize theme color
 | 
				
			||||||
- Tabs can be slid left or right according to the sliding force
 | 
					- Tabs can be slid left or right according to the sliding force
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,23 @@
 | 
				
			|||||||
 | 
					# 5.1.0 (2024-03-02)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### ✔️ refactor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 重构标签页`UI`,点击关闭按钮更方便
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🎫 Feat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 添加多选卡片示例
 | 
				
			||||||
 | 
					- 菜单支持`a`标签右键的所有浏览器行为(在新标签页中、新窗口中打开链接,拖拽到新标签页打开等)
 | 
				
			||||||
 | 
					- 菜单搜索新增搜索历史和收藏功能
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🐞 Bug fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 修复`windows`系统下登录页出现滚动条的问题
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🍏 Perf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 规范注册本地图标时的命名规则,使其更好地配合图标选择器
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 5.0.0 (2024-02-26)
 | 
					# 5.0.0 (2024-02-26)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
全面`ESM`版本
 | 
					全面`ESM`版本
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "vue-pure-admin",
 | 
					  "name": "vue-pure-admin",
 | 
				
			||||||
  "version": "5.0.0",
 | 
					  "version": "5.1.0",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "type": "module",
 | 
					  "type": "module",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "Version": "5.0.0",
 | 
					  "Version": "5.1.0",
 | 
				
			||||||
  "Title": "PureAdmin",
 | 
					  "Title": "PureAdmin",
 | 
				
			||||||
  "FixedHeader": true,
 | 
					  "FixedHeader": true,
 | 
				
			||||||
  "HiddenSideBar": false,
 | 
					  "HiddenSideBar": false,
 | 
				
			||||||
@ -22,6 +22,7 @@
 | 
				
			|||||||
  "CachingAsyncRoutes": false,
 | 
					  "CachingAsyncRoutes": false,
 | 
				
			||||||
  "TooltipEffect": "light",
 | 
					  "TooltipEffect": "light",
 | 
				
			||||||
  "ResponsiveStorageNameSpace": "responsive-",
 | 
					  "ResponsiveStorageNameSpace": "responsive-",
 | 
				
			||||||
 | 
					  "MenuSearchHistory": 6,
 | 
				
			||||||
  "MapConfigure": {
 | 
					  "MapConfigure": {
 | 
				
			||||||
    "amapKey": "97b3248d1553172e81f168cf94ea667e",
 | 
					    "amapKey": "97b3248d1553172e81f168cf94ea667e",
 | 
				
			||||||
    "options": {
 | 
					    "options": {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
 | 
					 | 
				
			||||||
import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
 | 
					 | 
				
			||||||
import { useNav } from "@/layout/hooks/useNav";
 | 
					import { useNav } from "@/layout/hooks/useNav";
 | 
				
			||||||
import mdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
 | 
					import mdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
 | 
				
			||||||
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
 | 
					import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
 | 
				
			||||||
 | 
					import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
 | 
				
			||||||
 | 
					import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = withDefaults(defineProps<{ total: number }>(), {
 | 
					const props = withDefaults(defineProps<{ total: number }>(), {
 | 
				
			||||||
  total: 0
 | 
					  total: 0
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										198
									
								
								src/layout/components/search/components/SearchHistory.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/layout/components/search/components/SearchHistory.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,198 @@
 | 
				
			|||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import Sortable from "sortablejs";
 | 
				
			||||||
 | 
					import SearchHistoryItem from "./SearchHistoryItem.vue";
 | 
				
			||||||
 | 
					import type { optionsItem, dragItem, Props } from "../types";
 | 
				
			||||||
 | 
					import { useEpThemeStoreHook } from "@/store/modules/epTheme";
 | 
				
			||||||
 | 
					import { useResizeObserver, isArray, delay } from "@pureadmin/utils";
 | 
				
			||||||
 | 
					import { ref, watch, nextTick, computed, getCurrentInstance } from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Emits {
 | 
				
			||||||
 | 
					  (e: "update:value", val: string): void;
 | 
				
			||||||
 | 
					  (e: "enter"): void;
 | 
				
			||||||
 | 
					  (e: "collect", val: optionsItem): void;
 | 
				
			||||||
 | 
					  (e: "delete", val: optionsItem): void;
 | 
				
			||||||
 | 
					  (e: "drag", val: dragItem): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const historyRef = ref();
 | 
				
			||||||
 | 
					const innerHeight = ref();
 | 
				
			||||||
 | 
					/** 判断是否停止鼠标移入事件处理 */
 | 
				
			||||||
 | 
					const stopMouseEvent = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits<Emits>();
 | 
				
			||||||
 | 
					const instance = getCurrentInstance()!;
 | 
				
			||||||
 | 
					const props = withDefaults(defineProps<Props>(), {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const itemStyle = computed(() => {
 | 
				
			||||||
 | 
					  return item => {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      background:
 | 
				
			||||||
 | 
					        item?.path === active.value ? useEpThemeStoreHook().epThemeColor : "",
 | 
				
			||||||
 | 
					      color: item.path === active.value ? "#fff" : "",
 | 
				
			||||||
 | 
					      fontSize: item.path === active.value ? "16px" : "14px"
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const titleStyle = computed(() => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    color: useEpThemeStoreHook().epThemeColor,
 | 
				
			||||||
 | 
					    fontWeight: 500
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const active = computed({
 | 
				
			||||||
 | 
					  get() {
 | 
				
			||||||
 | 
					    return props.value;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  set(val: string) {
 | 
				
			||||||
 | 
					    emit("update:value", val);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					  () => props.value,
 | 
				
			||||||
 | 
					  newValue => {
 | 
				
			||||||
 | 
					    if (newValue) {
 | 
				
			||||||
 | 
					      if (stopMouseEvent.value) {
 | 
				
			||||||
 | 
					        delay(100).then(() => (stopMouseEvent.value = false));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const historyList = computed(() => {
 | 
				
			||||||
 | 
					  return props.options.filter(item => item.type === "history");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const collectList = computed(() => {
 | 
				
			||||||
 | 
					  return props.options.filter(item => item.type === "collect");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleCollect(item) {
 | 
				
			||||||
 | 
					  emit("collect", item);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleDelete(item) {
 | 
				
			||||||
 | 
					  stopMouseEvent.value = true;
 | 
				
			||||||
 | 
					  emit("delete", item);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 鼠标移入 */
 | 
				
			||||||
 | 
					async function handleMouse(item) {
 | 
				
			||||||
 | 
					  if (!stopMouseEvent.value) active.value = item.path;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleTo() {
 | 
				
			||||||
 | 
					  emit("enter");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function resizeResult() {
 | 
				
			||||||
 | 
					  // el-scrollbar max-height="calc(90vh - 140px)"
 | 
				
			||||||
 | 
					  innerHeight.value = window.innerHeight - window.innerHeight / 10 - 140;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					useResizeObserver(historyRef, resizeResult);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleScroll(index: number) {
 | 
				
			||||||
 | 
					  const curInstance = instance?.proxy?.$refs[`historyItemRef${index}`];
 | 
				
			||||||
 | 
					  if (!curInstance) return 0;
 | 
				
			||||||
 | 
					  const curRef = isArray(curInstance)
 | 
				
			||||||
 | 
					    ? (curInstance[0] as ElRef)
 | 
				
			||||||
 | 
					    : (curInstance as ElRef);
 | 
				
			||||||
 | 
					  const scrollTop = curRef.offsetTop + 128; // 128 两个history-item(56px+56px=112px)高度加上下margin(8px+8px=16px)
 | 
				
			||||||
 | 
					  return scrollTop > innerHeight.value ? scrollTop - innerHeight.value : 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleChangeIndex = (evt): void => {
 | 
				
			||||||
 | 
					  emit("drag", { oldIndex: evt.oldIndex, newIndex: evt.newIndex });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let sortableInstance = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					  collectList,
 | 
				
			||||||
 | 
					  val => {
 | 
				
			||||||
 | 
					    if (val.length > 1) {
 | 
				
			||||||
 | 
					      nextTick(() => {
 | 
				
			||||||
 | 
					        const wrapper: HTMLElement =
 | 
				
			||||||
 | 
					          document.querySelector(".collect-container");
 | 
				
			||||||
 | 
					        if (!wrapper || sortableInstance) return;
 | 
				
			||||||
 | 
					        sortableInstance = Sortable.create(wrapper, {
 | 
				
			||||||
 | 
					          animation: 160,
 | 
				
			||||||
 | 
					          onStart: event => {
 | 
				
			||||||
 | 
					            event.item.style.cursor = "move";
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          onEnd: event => {
 | 
				
			||||||
 | 
					            event.item.style.cursor = "pointer";
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          onUpdate: handleChangeIndex
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        resizeResult();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  { deep: true, immediate: true }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineExpose({ handleScroll });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div ref="historyRef" class="history">
 | 
				
			||||||
 | 
					    <template v-if="historyList.length">
 | 
				
			||||||
 | 
					      <div :style="titleStyle">搜索历史</div>
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        v-for="(item, index) in historyList"
 | 
				
			||||||
 | 
					        :key="item.path"
 | 
				
			||||||
 | 
					        :ref="'historyItemRef' + index"
 | 
				
			||||||
 | 
					        class="history-item dark:bg-[#1d1d1d]"
 | 
				
			||||||
 | 
					        :style="itemStyle(item)"
 | 
				
			||||||
 | 
					        @click="handleTo"
 | 
				
			||||||
 | 
					        @mouseenter="handleMouse(item)"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <SearchHistoryItem
 | 
				
			||||||
 | 
					          :item="item"
 | 
				
			||||||
 | 
					          @delete-item="handleDelete"
 | 
				
			||||||
 | 
					          @collect-item="handleCollect"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </template>
 | 
				
			||||||
 | 
					    <template v-if="collectList.length">
 | 
				
			||||||
 | 
					      <div :style="titleStyle">
 | 
				
			||||||
 | 
					        收藏{{ collectList.length > 1 ? "(可拖拽排序)" : "" }}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="collect-container">
 | 
				
			||||||
 | 
					        <div
 | 
				
			||||||
 | 
					          v-for="(item, index) in collectList"
 | 
				
			||||||
 | 
					          :key="item.path"
 | 
				
			||||||
 | 
					          :ref="'historyItemRef' + (index + historyList.length)"
 | 
				
			||||||
 | 
					          class="history-item dark:bg-[#1d1d1d]"
 | 
				
			||||||
 | 
					          :style="itemStyle(item)"
 | 
				
			||||||
 | 
					          @click="handleTo"
 | 
				
			||||||
 | 
					          @mouseenter="handleMouse(item)"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <SearchHistoryItem :item="item" @delete-item="handleDelete" />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </template>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.history {
 | 
				
			||||||
 | 
					  padding-bottom: 12px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &-item {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    height: 56px;
 | 
				
			||||||
 | 
					    padding: 14px;
 | 
				
			||||||
 | 
					    margin: 8px auto 10px;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    border: 0.1px solid #ccc;
 | 
				
			||||||
 | 
					    border-radius: 4px;
 | 
				
			||||||
 | 
					    transition: font-size 0.16s;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import type { optionsItem } from "../types";
 | 
				
			||||||
 | 
					import { transformI18n } from "@/plugins/i18n";
 | 
				
			||||||
 | 
					import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 | 
				
			||||||
 | 
					import Star from "@iconify-icons/ep/star";
 | 
				
			||||||
 | 
					import Close from "@iconify-icons/ep/close";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Props {
 | 
				
			||||||
 | 
					  item: optionsItem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Emits {
 | 
				
			||||||
 | 
					  (e: "collectItem", val: optionsItem): void;
 | 
				
			||||||
 | 
					  (e: "deleteItem", val: optionsItem): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const emit = defineEmits<Emits>();
 | 
				
			||||||
 | 
					withDefaults(defineProps<Props>(), {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleCollect(item) {
 | 
				
			||||||
 | 
					  emit("collectItem", item);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleDelete(item) {
 | 
				
			||||||
 | 
					  emit("deleteItem", item);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <component :is="useRenderIcon(item.meta?.icon)" />
 | 
				
			||||||
 | 
					  <span class="history-item-title">
 | 
				
			||||||
 | 
					    {{ transformI18n(item.meta?.title) }}
 | 
				
			||||||
 | 
					  </span>
 | 
				
			||||||
 | 
					  <IconifyIconOffline
 | 
				
			||||||
 | 
					    v-show="item.type === 'history'"
 | 
				
			||||||
 | 
					    :icon="Star"
 | 
				
			||||||
 | 
					    class="w-[18px] h-[18px] mr-2 hover:text-[#d7d5d4]"
 | 
				
			||||||
 | 
					    @click.stop="handleCollect(item)"
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					  <IconifyIconOffline
 | 
				
			||||||
 | 
					    :icon="Close"
 | 
				
			||||||
 | 
					    class="w-[18px] h-[18px] hover:text-[#d7d5d4] cursor-pointer"
 | 
				
			||||||
 | 
					    @click.stop="handleDelete(item)"
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.history-item-title {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex: 1;
 | 
				
			||||||
 | 
					  margin-left: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@ -1,15 +1,18 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { match } from "pinyin-pro";
 | 
					import { match } from "pinyin-pro";
 | 
				
			||||||
import { useI18n } from "vue-i18n";
 | 
					import { useI18n } from "vue-i18n";
 | 
				
			||||||
 | 
					import { getConfig } from "@/config";
 | 
				
			||||||
import { useRouter } from "vue-router";
 | 
					import { useRouter } from "vue-router";
 | 
				
			||||||
import SearchResult from "./SearchResult.vue";
 | 
					import SearchResult from "./SearchResult.vue";
 | 
				
			||||||
import SearchFooter from "./SearchFooter.vue";
 | 
					import SearchFooter from "./SearchFooter.vue";
 | 
				
			||||||
import { useNav } from "@/layout/hooks/useNav";
 | 
					import { useNav } from "@/layout/hooks/useNav";
 | 
				
			||||||
import { transformI18n } from "@/plugins/i18n";
 | 
					import { transformI18n } from "@/plugins/i18n";
 | 
				
			||||||
import { ref, computed, shallowRef } from "vue";
 | 
					import SearchHistory from "./SearchHistory.vue";
 | 
				
			||||||
import { cloneDeep, isAllEmpty } from "@pureadmin/utils";
 | 
					import type { optionsItem, dragItem } from "../types";
 | 
				
			||||||
 | 
					import { ref, computed, shallowRef, watch } from "vue";
 | 
				
			||||||
import { useDebounceFn, onKeyStroke } from "@vueuse/core";
 | 
					import { useDebounceFn, onKeyStroke } from "@vueuse/core";
 | 
				
			||||||
import { usePermissionStoreHook } from "@/store/modules/permission";
 | 
					import { usePermissionStoreHook } from "@/store/modules/permission";
 | 
				
			||||||
 | 
					import { cloneDeep, isAllEmpty, storageLocal } from "@pureadmin/utils";
 | 
				
			||||||
import Search from "@iconify-icons/ri/search-line";
 | 
					import Search from "@iconify-icons/ri/search-line";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Props {
 | 
					interface Props {
 | 
				
			||||||
@ -24,16 +27,26 @@ interface Emits {
 | 
				
			|||||||
const { device } = useNav();
 | 
					const { device } = useNav();
 | 
				
			||||||
const emit = defineEmits<Emits>();
 | 
					const emit = defineEmits<Emits>();
 | 
				
			||||||
const props = withDefaults(defineProps<Props>(), {});
 | 
					const props = withDefaults(defineProps<Props>(), {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = useRouter();
 | 
					const router = useRouter();
 | 
				
			||||||
const { locale } = useI18n();
 | 
					const { locale } = useI18n();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const HISTORY_TYPE = "history";
 | 
				
			||||||
 | 
					const COLLECT_TYPE = "collect";
 | 
				
			||||||
 | 
					const LOCALEHISTORYKEY = "menu-search-history";
 | 
				
			||||||
 | 
					const LOCALECOLLECTKEY = "menu-search-collect";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const keyword = ref("");
 | 
					const keyword = ref("");
 | 
				
			||||||
const scrollbarRef = ref();
 | 
					 | 
				
			||||||
const resultRef = ref();
 | 
					const resultRef = ref();
 | 
				
			||||||
 | 
					const historyRef = ref();
 | 
				
			||||||
 | 
					const scrollbarRef = ref();
 | 
				
			||||||
const activePath = ref("");
 | 
					const activePath = ref("");
 | 
				
			||||||
const inputRef = ref<HTMLInputElement | null>(null);
 | 
					const historyPath = ref("");
 | 
				
			||||||
const resultOptions = shallowRef([]);
 | 
					const resultOptions = shallowRef([]);
 | 
				
			||||||
 | 
					const historyOptions = shallowRef([]);
 | 
				
			||||||
const handleSearch = useDebounceFn(search, 300);
 | 
					const handleSearch = useDebounceFn(search, 300);
 | 
				
			||||||
 | 
					const historyNum = getConfig().MenuSearchHistory;
 | 
				
			||||||
 | 
					const inputRef = ref<HTMLInputElement | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 菜单树形结构 */
 | 
					/** 菜单树形结构 */
 | 
				
			||||||
const menusData = computed(() => {
 | 
					const menusData = computed(() => {
 | 
				
			||||||
@ -49,6 +62,36 @@ const show = computed({
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					  () => props.value,
 | 
				
			||||||
 | 
					  newValue => {
 | 
				
			||||||
 | 
					    if (newValue) getHistory();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showSearchResult = computed(() => {
 | 
				
			||||||
 | 
					  return keyword.value && resultOptions.value.length > 0;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showSearchHistory = computed(() => {
 | 
				
			||||||
 | 
					  return !keyword.value && historyOptions.value.length > 0;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showEmpty = computed(() => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    (!keyword.value && historyOptions.value.length === 0) ||
 | 
				
			||||||
 | 
					    (keyword.value && resultOptions.value.length === 0)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getStorageItem(key) {
 | 
				
			||||||
 | 
					  return storageLocal().getItem<optionsItem[]>(key) || [];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function setStorageItem(key, value) {
 | 
				
			||||||
 | 
					  storageLocal().setItem(key, value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** 将菜单树形结构扁平化为一维数组,用于菜单查询 */
 | 
					/** 将菜单树形结构扁平化为一维数组,用于菜单查询 */
 | 
				
			||||||
function flatTree(arr) {
 | 
					function flatTree(arr) {
 | 
				
			||||||
  const res = [];
 | 
					  const res = [];
 | 
				
			||||||
@ -79,11 +122,8 @@ function search() {
 | 
				
			|||||||
          ))
 | 
					          ))
 | 
				
			||||||
      : false
 | 
					      : false
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  if (resultOptions.value?.length > 0) {
 | 
					  activePath.value =
 | 
				
			||||||
    activePath.value = resultOptions.value[0].path;
 | 
					    resultOptions.value?.length > 0 ? resultOptions.value[0].path : "";
 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    activePath.value = "";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleClose() {
 | 
					function handleClose() {
 | 
				
			||||||
@ -91,54 +131,143 @@ function handleClose() {
 | 
				
			|||||||
  /** 延时处理防止用户看到某些操作 */
 | 
					  /** 延时处理防止用户看到某些操作 */
 | 
				
			||||||
  setTimeout(() => {
 | 
					  setTimeout(() => {
 | 
				
			||||||
    resultOptions.value = [];
 | 
					    resultOptions.value = [];
 | 
				
			||||||
 | 
					    historyPath.value = "";
 | 
				
			||||||
    keyword.value = "";
 | 
					    keyword.value = "";
 | 
				
			||||||
  }, 200);
 | 
					  }, 200);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function scrollTo(index) {
 | 
					function scrollTo(index) {
 | 
				
			||||||
  const scrollTop = resultRef.value.handleScroll(index);
 | 
					  const ref = resultOptions.value.length ? resultRef.value : historyRef.value;
 | 
				
			||||||
 | 
					  const scrollTop = ref.handleScroll(index);
 | 
				
			||||||
  scrollbarRef.value.setScrollTop(scrollTop);
 | 
					  scrollbarRef.value.setScrollTop(scrollTop);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 获取当前选项和路径 */
 | 
				
			||||||
 | 
					function getCurrentOptionsAndPath() {
 | 
				
			||||||
 | 
					  const isResultOptions = resultOptions.value.length > 0;
 | 
				
			||||||
 | 
					  const options = isResultOptions ? resultOptions.value : historyOptions.value;
 | 
				
			||||||
 | 
					  const currentPath = isResultOptions ? activePath.value : historyPath.value;
 | 
				
			||||||
 | 
					  return { options, currentPath, isResultOptions };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 更新路径并滚动到指定项 */
 | 
				
			||||||
 | 
					function updatePathAndScroll(newIndex, isResultOptions) {
 | 
				
			||||||
 | 
					  if (isResultOptions) {
 | 
				
			||||||
 | 
					    activePath.value = resultOptions.value[newIndex].path;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    historyPath.value = historyOptions.value[newIndex].path;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  scrollTo(newIndex);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** key up */
 | 
					/** key up */
 | 
				
			||||||
function handleUp() {
 | 
					function handleUp() {
 | 
				
			||||||
  const { length } = resultOptions.value;
 | 
					  const { options, currentPath, isResultOptions } = getCurrentOptionsAndPath();
 | 
				
			||||||
  if (length === 0) return;
 | 
					  if (options.length === 0) return;
 | 
				
			||||||
  const index = resultOptions.value.findIndex(
 | 
					  const index = options.findIndex(item => item.path === currentPath);
 | 
				
			||||||
    item => item.path === activePath.value
 | 
					  const prevIndex = (index - 1 + options.length) % options.length;
 | 
				
			||||||
  );
 | 
					  updatePathAndScroll(prevIndex, isResultOptions);
 | 
				
			||||||
  if (index === 0) {
 | 
					 | 
				
			||||||
    activePath.value = resultOptions.value[length - 1].path;
 | 
					 | 
				
			||||||
    scrollTo(resultOptions.value.length - 1);
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    activePath.value = resultOptions.value[index - 1].path;
 | 
					 | 
				
			||||||
    scrollTo(index - 1);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** key down */
 | 
					/** key down */
 | 
				
			||||||
function handleDown() {
 | 
					function handleDown() {
 | 
				
			||||||
  const { length } = resultOptions.value;
 | 
					  const { options, currentPath, isResultOptions } = getCurrentOptionsAndPath();
 | 
				
			||||||
  if (length === 0) return;
 | 
					  if (options.length === 0) return;
 | 
				
			||||||
  const index = resultOptions.value.findIndex(
 | 
					  const index = options.findIndex(item => item.path === currentPath);
 | 
				
			||||||
    item => item.path === activePath.value
 | 
					  const nextIndex = (index + 1) % options.length;
 | 
				
			||||||
  );
 | 
					  updatePathAndScroll(nextIndex, isResultOptions);
 | 
				
			||||||
  if (index + 1 === length) {
 | 
					 | 
				
			||||||
    activePath.value = resultOptions.value[0].path;
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    activePath.value = resultOptions.value[index + 1].path;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  scrollTo(index + 1);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** key enter */
 | 
					/** key enter */
 | 
				
			||||||
function handleEnter() {
 | 
					function handleEnter() {
 | 
				
			||||||
  const { length } = resultOptions.value;
 | 
					  const { options, currentPath, isResultOptions } = getCurrentOptionsAndPath();
 | 
				
			||||||
  if (length === 0 || activePath.value === "") return;
 | 
					  if (options.length === 0 || currentPath === "") return;
 | 
				
			||||||
  router.push(activePath.value);
 | 
					  const index = options.findIndex(item => item.path === currentPath);
 | 
				
			||||||
 | 
					  if (index === -1) return;
 | 
				
			||||||
 | 
					  if (isResultOptions) {
 | 
				
			||||||
 | 
					    saveHistory();
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    updateHistory();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  router.push(options[index].path);
 | 
				
			||||||
  handleClose();
 | 
					  handleClose();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 删除历史记录 */
 | 
				
			||||||
 | 
					function handleDelete(item) {
 | 
				
			||||||
 | 
					  const key = item.type === HISTORY_TYPE ? LOCALEHISTORYKEY : LOCALECOLLECTKEY;
 | 
				
			||||||
 | 
					  let list = getStorageItem(key);
 | 
				
			||||||
 | 
					  list = list.filter(listItem => listItem.path !== item.path);
 | 
				
			||||||
 | 
					  setStorageItem(key, list);
 | 
				
			||||||
 | 
					  getHistory();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 收藏历史记录 */
 | 
				
			||||||
 | 
					function handleCollect(item) {
 | 
				
			||||||
 | 
					  let searchHistoryList = getStorageItem(LOCALEHISTORYKEY);
 | 
				
			||||||
 | 
					  let searchCollectList = getStorageItem(LOCALECOLLECTKEY);
 | 
				
			||||||
 | 
					  searchHistoryList = searchHistoryList.filter(
 | 
				
			||||||
 | 
					    historyItem => historyItem.path !== item.path
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  setStorageItem(LOCALEHISTORYKEY, searchHistoryList);
 | 
				
			||||||
 | 
					  if (!searchCollectList.some(collectItem => collectItem.path === item.path)) {
 | 
				
			||||||
 | 
					    searchCollectList.unshift({ ...item, type: COLLECT_TYPE });
 | 
				
			||||||
 | 
					    setStorageItem(LOCALECOLLECTKEY, searchCollectList);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  getHistory();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 存储搜索记录 */
 | 
				
			||||||
 | 
					function saveHistory() {
 | 
				
			||||||
 | 
					  const { path, meta } = resultOptions.value.find(
 | 
				
			||||||
 | 
					    item => item.path === activePath.value
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const searchHistoryList = getStorageItem(LOCALEHISTORYKEY);
 | 
				
			||||||
 | 
					  const searchCollectList = getStorageItem(LOCALECOLLECTKEY);
 | 
				
			||||||
 | 
					  const isCollected = searchCollectList.some(item => item.path === path);
 | 
				
			||||||
 | 
					  const existingIndex = searchHistoryList.findIndex(item => item.path === path);
 | 
				
			||||||
 | 
					  if (!isCollected) {
 | 
				
			||||||
 | 
					    if (existingIndex !== -1) searchHistoryList.splice(existingIndex, 1);
 | 
				
			||||||
 | 
					    if (searchHistoryList.length >= historyNum) searchHistoryList.pop();
 | 
				
			||||||
 | 
					    searchHistoryList.unshift({ path, meta, type: HISTORY_TYPE });
 | 
				
			||||||
 | 
					    storageLocal().setItem(LOCALEHISTORYKEY, searchHistoryList);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 更新存储的搜索记录 */
 | 
				
			||||||
 | 
					function updateHistory() {
 | 
				
			||||||
 | 
					  let searchHistoryList = getStorageItem(LOCALEHISTORYKEY);
 | 
				
			||||||
 | 
					  const historyIndex = searchHistoryList.findIndex(
 | 
				
			||||||
 | 
					    item => item.path === historyPath.value
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  if (historyIndex !== -1) {
 | 
				
			||||||
 | 
					    const [historyItem] = searchHistoryList.splice(historyIndex, 1);
 | 
				
			||||||
 | 
					    searchHistoryList.unshift(historyItem);
 | 
				
			||||||
 | 
					    setStorageItem(LOCALEHISTORYKEY, searchHistoryList);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 获取本地历史记录 */
 | 
				
			||||||
 | 
					function getHistory() {
 | 
				
			||||||
 | 
					  const searchHistoryList = getStorageItem(LOCALEHISTORYKEY);
 | 
				
			||||||
 | 
					  const searchCollectList = getStorageItem(LOCALECOLLECTKEY);
 | 
				
			||||||
 | 
					  historyOptions.value = [...searchHistoryList, ...searchCollectList];
 | 
				
			||||||
 | 
					  historyPath.value = historyOptions.value[0]?.path;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 拖拽改变收藏顺序 */
 | 
				
			||||||
 | 
					function handleDrag(item: dragItem) {
 | 
				
			||||||
 | 
					  const searchCollectList = getStorageItem(LOCALECOLLECTKEY);
 | 
				
			||||||
 | 
					  const [reorderedItem] = searchCollectList.splice(item.oldIndex, 1);
 | 
				
			||||||
 | 
					  searchCollectList.splice(item.newIndex, 0, reorderedItem);
 | 
				
			||||||
 | 
					  storageLocal().setItem(LOCALECOLLECTKEY, searchCollectList);
 | 
				
			||||||
 | 
					  historyOptions.value = [
 | 
				
			||||||
 | 
					    ...getStorageItem(LOCALEHISTORYKEY),
 | 
				
			||||||
 | 
					    ...getStorageItem(LOCALECOLLECTKEY)
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					  historyPath.value = reorderedItem.path;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onKeyStroke("Enter", handleEnter);
 | 
					onKeyStroke("Enter", handleEnter);
 | 
				
			||||||
onKeyStroke("ArrowUp", handleUp);
 | 
					onKeyStroke("ArrowUp", handleUp);
 | 
				
			||||||
onKeyStroke("ArrowDown", handleDown);
 | 
					onKeyStroke("ArrowDown", handleDown);
 | 
				
			||||||
@ -174,14 +303,21 @@ onKeyStroke("ArrowDown", handleDown);
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </el-input>
 | 
					    </el-input>
 | 
				
			||||||
    <div class="search-result-container">
 | 
					    <div class="search-content">
 | 
				
			||||||
      <el-scrollbar ref="scrollbarRef" max-height="calc(90vh - 140px)">
 | 
					      <el-scrollbar ref="scrollbarRef" max-height="calc(90vh - 140px)">
 | 
				
			||||||
        <el-empty
 | 
					        <el-empty v-if="showEmpty" description="暂无搜索结果" />
 | 
				
			||||||
          v-if="resultOptions.length === 0"
 | 
					        <SearchHistory
 | 
				
			||||||
          description="暂无搜索结果"
 | 
					          v-if="showSearchHistory"
 | 
				
			||||||
 | 
					          ref="historyRef"
 | 
				
			||||||
 | 
					          v-model:value="historyPath"
 | 
				
			||||||
 | 
					          :options="historyOptions"
 | 
				
			||||||
 | 
					          @click="handleEnter"
 | 
				
			||||||
 | 
					          @delete="handleDelete"
 | 
				
			||||||
 | 
					          @collect="handleCollect"
 | 
				
			||||||
 | 
					          @drag="handleDrag"
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        <SearchResult
 | 
					        <SearchResult
 | 
				
			||||||
          v-else
 | 
					          v-if="showSearchResult"
 | 
				
			||||||
          ref="resultRef"
 | 
					          ref="resultRef"
 | 
				
			||||||
          v-model:value="activePath"
 | 
					          v-model:value="activePath"
 | 
				
			||||||
          :options="resultOptions"
 | 
					          :options="resultOptions"
 | 
				
			||||||
@ -196,7 +332,7 @@ onKeyStroke("ArrowDown", handleDown);
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.search-result-container {
 | 
					.search-content {
 | 
				
			||||||
  margin-top: 12px;
 | 
					  margin-top: 12px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,24 +1,11 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import type { Props } from "../types";
 | 
				
			||||||
import { transformI18n } from "@/plugins/i18n";
 | 
					import { transformI18n } from "@/plugins/i18n";
 | 
				
			||||||
import { useResizeObserver } from "@pureadmin/utils";
 | 
					import { useResizeObserver } from "@pureadmin/utils";
 | 
				
			||||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
 | 
					import { useEpThemeStoreHook } from "@/store/modules/epTheme";
 | 
				
			||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 | 
					import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 | 
				
			||||||
import { ref, computed, getCurrentInstance, onMounted } from "vue";
 | 
					import { ref, computed, getCurrentInstance, onMounted } from "vue";
 | 
				
			||||||
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
 | 
					import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
 | 
				
			||||||
import Bookmark2Line from "@iconify-icons/ri/bookmark-2-line";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface optionsItem {
 | 
					 | 
				
			||||||
  path: string;
 | 
					 | 
				
			||||||
  meta?: {
 | 
					 | 
				
			||||||
    icon?: string;
 | 
					 | 
				
			||||||
    title?: string;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  value: string;
 | 
					 | 
				
			||||||
  options: Array<optionsItem>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Emits {
 | 
					interface Emits {
 | 
				
			||||||
  (e: "update:value", val: string): void;
 | 
					  (e: "update:value", val: string): void;
 | 
				
			||||||
@ -27,9 +14,9 @@ interface Emits {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const resultRef = ref();
 | 
					const resultRef = ref();
 | 
				
			||||||
const innerHeight = ref();
 | 
					const innerHeight = ref();
 | 
				
			||||||
const props = withDefaults(defineProps<Props>(), {});
 | 
					 | 
				
			||||||
const emit = defineEmits<Emits>();
 | 
					const emit = defineEmits<Emits>();
 | 
				
			||||||
const instance = getCurrentInstance()!;
 | 
					const instance = getCurrentInstance()!;
 | 
				
			||||||
 | 
					const props = withDefaults(defineProps<Props>(), {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const itemStyle = computed(() => {
 | 
					const itemStyle = computed(() => {
 | 
				
			||||||
  return item => {
 | 
					  return item => {
 | 
				
			||||||
@ -93,7 +80,7 @@ defineExpose({ handleScroll });
 | 
				
			|||||||
      @click="handleTo"
 | 
					      @click="handleTo"
 | 
				
			||||||
      @mouseenter="handleMouse(item)"
 | 
					      @mouseenter="handleMouse(item)"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <component :is="useRenderIcon(item.meta?.icon ?? Bookmark2Line)" />
 | 
					      <component :is="useRenderIcon(item.meta?.icon)" />
 | 
				
			||||||
      <span class="result-item-title">
 | 
					      <span class="result-item-title">
 | 
				
			||||||
        {{ transformI18n(item.meta?.title) }}
 | 
					        {{ transformI18n(item.meta?.title) }}
 | 
				
			||||||
      </span>
 | 
					      </span>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										20
									
								
								src/layout/components/search/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/layout/components/search/types.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					interface optionsItem {
 | 
				
			||||||
 | 
					  path: string;
 | 
				
			||||||
 | 
					  type: "history" | "collect";
 | 
				
			||||||
 | 
					  meta: {
 | 
				
			||||||
 | 
					    icon?: string;
 | 
				
			||||||
 | 
					    title?: string;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface dragItem {
 | 
				
			||||||
 | 
					  oldIndex: number;
 | 
				
			||||||
 | 
					  newIndex: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Props {
 | 
				
			||||||
 | 
					  value: string;
 | 
				
			||||||
 | 
					  options: Array<optionsItem>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type { optionsItem, dragItem, Props };
 | 
				
			||||||
@ -85,6 +85,22 @@ class StorageProxy implements ProxyStorage {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @description 获取数据仓库中所有的key
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async keys() {
 | 
				
			||||||
 | 
					    return new Promise<string[]>((resolve, reject) => {
 | 
				
			||||||
 | 
					      this.storage
 | 
				
			||||||
 | 
					        .keys()
 | 
				
			||||||
 | 
					        .then(keys => {
 | 
				
			||||||
 | 
					          resolve(keys);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(err => {
 | 
				
			||||||
 | 
					          reject(err);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								types/global.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								types/global.d.ts
									
									
									
									
										vendored
									
									
								
							@ -96,6 +96,7 @@ declare global {
 | 
				
			|||||||
    CachingAsyncRoutes?: boolean;
 | 
					    CachingAsyncRoutes?: boolean;
 | 
				
			||||||
    TooltipEffect?: Effect;
 | 
					    TooltipEffect?: Effect;
 | 
				
			||||||
    ResponsiveStorageNameSpace?: string;
 | 
					    ResponsiveStorageNameSpace?: string;
 | 
				
			||||||
 | 
					    MenuSearchHistory?: number;
 | 
				
			||||||
    MapConfigure?: {
 | 
					    MapConfigure?: {
 | 
				
			||||||
      amapKey?: string;
 | 
					      amapKey?: string;
 | 
				
			||||||
      options: {
 | 
					      options: {
 | 
				
			||||||
@ -131,6 +132,7 @@ declare global {
 | 
				
			|||||||
    overallStyle?: string;
 | 
					    overallStyle?: string;
 | 
				
			||||||
    showLogo?: boolean;
 | 
					    showLogo?: boolean;
 | 
				
			||||||
    showModel?: string;
 | 
					    showModel?: string;
 | 
				
			||||||
 | 
					    menuSearchHistory?: number;
 | 
				
			||||||
    mapConfigure?: {
 | 
					    mapConfigure?: {
 | 
				
			||||||
      amapKey?: string;
 | 
					      amapKey?: string;
 | 
				
			||||||
      options: {
 | 
					      options: {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user