-
Notifications
You must be signed in to change notification settings - Fork 905
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import SearchModal from './search/index.vue'; | ||
|
||
export { SearchModal }; |
31 changes: 31 additions & 0 deletions
31
src/layout/header/components/search/components/SearchFooter.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<template> | ||
<div class="flex items-center"> | ||
<span class="mr-14px"> | ||
<EnterOutlined class="icon text-15px p-2px mr-3px" /> | ||
确认 | ||
</span> | ||
<span class="mr-14px"> | ||
<ArrowUpOutlined class="icon text-15px p-2px mr-5px" /> | ||
<ArrowDownOutlined class="icon text-15px p-2px mr-3px" /> | ||
切换 | ||
</span> | ||
<span> | ||
<CloseOutlined class="icon text-15px p-2px mr-3px" /> | ||
关闭 | ||
</span> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { | ||
EnterOutlined, | ||
ArrowDownOutlined, | ||
ArrowUpOutlined, | ||
CloseOutlined, | ||
} from '@ant-design/icons-vue'; | ||
</script> | ||
<style lang="less" scoped> | ||
.icon { | ||
box-shadow: inset 0 -2px #cdcde6, inset 0 0 1px 1px #fff, 0 1px 2px 1px #1e235a66; | ||
} | ||
</style> |
58 changes: 58 additions & 0 deletions
58
src/layout/header/components/search/components/SearchResult.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<template> | ||
<div> | ||
<div class="pb-12px"> | ||
<template v-for="item in options" :key="item.name"> | ||
<div | ||
class="bg-[#e5e7eb] h-56px mt-8px px-14px rounded-4px flex items-center justify-justify-between" | ||
style="cursor: pointer" | ||
:style="{ | ||
background: item.name === active ? '#1890ff' : '', | ||
color: item.name === active ? '#fff' : '', | ||
}" | ||
@click="handleTo" | ||
@mouseenter="handleMouse(item)" | ||
> | ||
<BookOutlined /> | ||
<span class="flex-1 ml-5px">{{ item.meta?.title }}</span> | ||
<EnterOutlined class="icon text-20px p-2px mr-3px" /> | ||
</div> | ||
</template> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { computed } from 'vue'; | ||
import type { RouteRecordRaw } from 'vue-router'; | ||
import { EnterOutlined, BookOutlined } from '@ant-design/icons-vue'; | ||
interface Props { | ||
value: string; | ||
options: RouteRecordRaw[]; | ||
} | ||
interface Emits { | ||
(e: 'update:value', val: string): void; | ||
(e: 'enter'): void; | ||
} | ||
const props = withDefaults(defineProps<Props>(), {}); | ||
const emit = defineEmits<Emits>(); | ||
const active = computed({ | ||
get() { | ||
return props.value; | ||
}, | ||
set(val: string) { | ||
emit('update:value', val); | ||
}, | ||
}); | ||
/** 鼠标移入 */ | ||
async function handleMouse(item: RouteRecordRaw) { | ||
active.value = item.name as string; | ||
} | ||
function handleTo() { | ||
emit('enter'); | ||
} | ||
</script> | ||
<style lang="less" scoped></style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
<template> | ||
<CustomAModal title="搜索菜单" v-model:visible="show" :keyboard="false"> | ||
<a-input | ||
ref="inputRef" | ||
v-model:value="keyword" | ||
clearable | ||
placeholder="请输入关键词搜索" | ||
@change="handleSearch" | ||
> | ||
<template #prefix> | ||
<SearchOutlined class="text-15px text-[#c2c2c2]" /> | ||
</template> | ||
</a-input> | ||
<div class="mt-20px"> | ||
<Empty v-if="resultOptions.length === 0" description="暂无搜索结果" /> | ||
<search-result | ||
v-else | ||
v-model:value="activePath" | ||
:options="resultOptions" | ||
@enter="handleEnter" | ||
/> | ||
</div> | ||
<template #footer> | ||
<search-footer /> | ||
</template> | ||
</CustomAModal> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { ref, shallowRef, computed, watch, nextTick } from 'vue'; | ||
import { useRouter } from 'vue-router'; | ||
import type { RouteRecordRaw } from 'vue-router'; | ||
import { Empty } from 'ant-design-vue'; | ||
import { CustomAModal } from '@/components/a-custom-modal'; | ||
import { useDebounceFn, onKeyStroke } from '@vueuse/core'; | ||
import { useUserStore } from '@/store/modules/user'; | ||
import { SearchOutlined } from '@ant-design/icons-vue'; | ||
import SearchResult from './components/SearchResult.vue'; | ||
import SearchFooter from './components/SearchFooter.vue'; | ||
interface Props { | ||
/** 弹窗显隐 */ | ||
value: boolean; | ||
} | ||
interface Emits { | ||
(e: 'update:value', val: boolean): void; | ||
} | ||
const props = withDefaults(defineProps<Props>(), {}); | ||
const emit = defineEmits<Emits>(); | ||
const userStore = useUserStore(); | ||
const router = useRouter(); | ||
const keyword = ref(''); | ||
const activePath = ref(''); | ||
const menusList = computed(() => transformRouteToList(userStore.menus)); | ||
const resultOptions = shallowRef<RouteRecordRaw[]>([]); | ||
const inputRef = ref<HTMLInputElement | null>(null); | ||
const handleSearch = useDebounceFn(search, 300); | ||
const show = computed({ | ||
get() { | ||
return props.value; | ||
}, | ||
set(val: boolean) { | ||
emit('update:value', val); | ||
}, | ||
}); | ||
watch(show, async (val) => { | ||
if (val) { | ||
/** 自动聚焦 */ | ||
await nextTick(); | ||
inputRef.value?.focus(); | ||
} | ||
}); | ||
/** 查询 */ | ||
function search() { | ||
resultOptions.value = menusList.value.filter((menu) => { | ||
const title = menu.meta?.title as string; | ||
return keyword.value && title.includes(keyword.value.trim()); | ||
}); | ||
if (resultOptions.value?.length > 0) { | ||
activePath.value = resultOptions.value[0].name as string; | ||
} else { | ||
activePath.value = ''; | ||
} | ||
} | ||
/** 将路由转换成菜单列表 */ | ||
function transformRouteToList(routes: RouteRecordRaw[], treeMap: RouteRecordRaw[] = []) { | ||
if (routes && routes.length === 0) return []; | ||
return routes.reduce((acc, cur) => { | ||
/** 允许在菜单内显示并且无子路由 */ | ||
if (!cur.meta?.hideInMenu && !cur.children) { | ||
acc.push(cur); | ||
} | ||
if (cur.children && cur.children.length > 0) { | ||
transformRouteToList(cur.children, treeMap); | ||
} | ||
return acc; | ||
}, treeMap); | ||
} | ||
function handleClose() { | ||
resultOptions.value = []; | ||
keyword.value = ''; | ||
show.value = false; | ||
} | ||
/** key up */ | ||
function handleUp() { | ||
const { length } = resultOptions.value; | ||
if (length === 0) return; | ||
const index = resultOptions.value.findIndex((item) => item.name === activePath.value); | ||
if (index === 0) { | ||
activePath.value = resultOptions.value[length - 1].name as string; | ||
} else { | ||
activePath.value = resultOptions.value[index - 1].name as string; | ||
} | ||
} | ||
/** key down */ | ||
function handleDown() { | ||
const { length } = resultOptions.value; | ||
if (length === 0) return; | ||
const index = resultOptions.value.findIndex((item) => item.name === activePath.value); | ||
if (index + 1 === length) { | ||
activePath.value = resultOptions.value[0].name as string; | ||
} else { | ||
activePath.value = resultOptions.value[index + 1].name as string; | ||
} | ||
} | ||
/** key enter */ | ||
function handleEnter() { | ||
if (/http(s)?:/.test(activePath.value)) { | ||
window.open(activePath.value); | ||
} else { | ||
router.push({ name: activePath.value }); | ||
handleClose(); | ||
} | ||
} | ||
onKeyStroke('Escape', handleClose); | ||
onKeyStroke('Enter', handleEnter); | ||
onKeyStroke('ArrowUp', handleUp); | ||
onKeyStroke('ArrowDown', handleDown); | ||
</script> | ||
<style lang="less" scoped></style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters