-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
9 changed files
with
272 additions
and
1 deletion.
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
24 changes: 24 additions & 0 deletions
24
src/layouts/common/GlobalSearch/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,24 @@ | ||
<template> | ||
<div class="px-24px h-44px flex-y-center"> | ||
<span class="mr-14px"> | ||
<icon-ant-design:enter-outlined class="icon text-20px p-2px mr-3px" /> | ||
确认 | ||
</span> | ||
<span class="mr-14px"> | ||
<icon-mdi:arrow-up-thin class="icon text-20px p-2px mr-5px" /> | ||
<icon-mdi:arrow-down-thin class="icon text-20px p-2px mr-3px" /> | ||
切换 | ||
</span> | ||
<span> | ||
<icon-mdi:close class="icon text-20px p-2px mr-3px" /> | ||
关闭 | ||
</span> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts" setup></script> | ||
<style lang="scss" scoped> | ||
.icon { | ||
box-shadow: inset 0 -2px #cdcde6, inset 0 0 1px 1px #fff, 0 1px 2px 1px #1e235a66; | ||
} | ||
</style> |
136 changes: 136 additions & 0 deletions
136
src/layouts/common/GlobalSearch/components/SearchModal.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,136 @@ | ||
<template> | ||
<n-modal | ||
v-model:show="show" | ||
:segmented="{ footer: 'soft' }" | ||
:closable="false" | ||
preset="card" | ||
footer-style="padding: 0; margin: 0" | ||
class="w-630px fixed top-50px left-1/2 transform -translate-x-1/2" | ||
@after-leave="handleClose" | ||
> | ||
<n-input ref="inputRef" v-model:value="keyword" clearable placeholder="请输入关键词搜索" @input="handleSearch"> | ||
<template #prefix> | ||
<icon-uil:search class="text-15px text-[#c2c2c2]" /> | ||
</template> | ||
</n-input> | ||
<div class="mt-20px"> | ||
<n-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> | ||
</n-modal> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { ref, shallowRef, computed, watch, nextTick } from 'vue'; | ||
import { useRouter } from 'vue-router'; | ||
import { NModal, NInput, NEmpty } from 'naive-ui'; | ||
import { useDebounceFn, onKeyStroke } from '@vueuse/core'; | ||
import { useRouteStore } from '@/store'; | ||
import type { RouteList } from './types'; | ||
import SearchResult from './SearchResult.vue'; | ||
import SearchFooter from './SearchFooter.vue'; | ||
interface Props { | ||
/** 弹窗显隐 */ | ||
value: boolean; | ||
} | ||
interface Emits { | ||
(e: 'update:value', val: boolean): void; | ||
} | ||
const props = withDefaults(defineProps<Props>(), {}); | ||
const emit = defineEmits<Emits>(); | ||
const router = useRouter(); | ||
const routeStore = useRouteStore(); | ||
const keyword = ref(''); | ||
const activePath = ref(''); | ||
const resultOptions = shallowRef<RouteList[]>([]); | ||
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 = routeStore.menusList.filter( | ||
menu => keyword.value && menu.meta?.title.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim()) | ||
); | ||
if (resultOptions.value?.length > 0) { | ||
activePath.value = resultOptions.value[0].path; | ||
} else { | ||
activePath.value = ''; | ||
} | ||
} | ||
function handleClose() { | ||
show.value = false; | ||
/** 延时处理防止用户看到某些操作 */ | ||
setTimeout(() => { | ||
resultOptions.value = []; | ||
keyword.value = ''; | ||
}, 200); | ||
} | ||
/** key up */ | ||
function handleUp() { | ||
const { length } = resultOptions.value; | ||
if (length === 0) return; | ||
const index = resultOptions.value.findIndex(item => item.path === activePath.value); | ||
if (index === 0) { | ||
activePath.value = resultOptions.value[length - 1].path; | ||
} else { | ||
activePath.value = resultOptions.value[index - 1].path; | ||
} | ||
} | ||
/** key down */ | ||
function handleDown() { | ||
const { length } = resultOptions.value; | ||
if (length === 0) return; | ||
const index = resultOptions.value.findIndex(item => item.path === activePath.value); | ||
if (index + 1 === length) { | ||
activePath.value = resultOptions.value[0].path; | ||
} else { | ||
activePath.value = resultOptions.value[index + 1].path; | ||
} | ||
} | ||
/** key enter */ | ||
function handleEnter() { | ||
const { length } = resultOptions.value; | ||
if (length === 0 || activePath.value === '') return; | ||
const item = resultOptions.value.find(item => item.path === activePath.value); | ||
if (item?.meta?.href) { | ||
window.open(activePath.value, '__blank'); | ||
} else { | ||
router.push(activePath.value); | ||
handleClose(); | ||
} | ||
} | ||
onKeyStroke('Escape', handleClose); | ||
onKeyStroke('Enter', handleEnter); | ||
onKeyStroke('ArrowUp', handleUp); | ||
onKeyStroke('ArrowDown', handleDown); | ||
</script> | ||
<style lang="scss" scoped></style> |
62 changes: 62 additions & 0 deletions
62
src/layouts/common/GlobalSearch/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,62 @@ | ||
<template> | ||
<n-scrollbar> | ||
<div class="pb-12px"> | ||
<template v-for="item in options" :key="item.path"> | ||
<div | ||
class="bg-[#e5e7eb] dark:bg-dark h-56px mt-8px px-14px rounded-4px cursor-pointer flex-y-center justify-between" | ||
:style="{ | ||
background: item.path === active ? theme.themeColor : '', | ||
color: item.path === active ? '#fff' : '' | ||
}" | ||
@click="handleTo" | ||
@mouseenter="handleMouse(item)" | ||
> | ||
<Icon :icon="item.meta?.icon ?? 'mdi:bookmark-minus-outline'" /> | ||
<span class="flex-1 ml-5px">{{ item.meta?.title }}</span> | ||
<icon-ant-design:enter-outlined class="icon text-20px p-2px mr-3px" /> | ||
</div> | ||
</template> | ||
</div> | ||
</n-scrollbar> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { computed } from 'vue'; | ||
import { NScrollbar } from 'naive-ui'; | ||
import { Icon } from '@iconify/vue'; | ||
import { useThemeStore } from '@/store'; | ||
import type { RouteList } from './types'; | ||
interface Props { | ||
value: string; | ||
options: RouteList[]; | ||
} | ||
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); | ||
} | ||
}); | ||
const theme = useThemeStore(); | ||
/** 鼠标移入 */ | ||
async function handleMouse(item: RouteList) { | ||
active.value = item.path; | ||
} | ||
function handleTo() { | ||
emit('enter'); | ||
} | ||
</script> | ||
<style lang="scss" 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,3 @@ | ||
import SearchModal from './SearchModal.vue'; | ||
|
||
export { SearchModal }; |
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 @@ | ||
export type RouteList = AuthRoute.Route; |
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,20 @@ | ||
<template> | ||
<div> | ||
<hover-container tooltip-content="搜索" class="w-40px h-full" @click="handleSearch"> | ||
<icon-uil:search class="text-20px text-[#666]" /> | ||
</hover-container> | ||
<search-modal v-model:value="show" /> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { HoverContainer } from '@/components'; | ||
import { useBoolean } from '@/hooks'; | ||
import { SearchModal } from './components'; | ||
const { bool: show, toggle } = useBoolean(); | ||
function handleSearch() { | ||
toggle(); | ||
} | ||
</script> | ||
<style lang="scss" 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
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