Files
dafuweng-saiadmin/saiadmin-vue/src/layout/search.vue
2026-03-03 09:36:51 +08:00

242 lines
6.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="sys-search-container">
<div class="ssc-bg">
<div class="w-6/12 mx-auto center-box">
<div class="mt-10"><img src="../assets/logo.png" width="100" class="mx-auto" /></div>
<div class="mt-10">
<a-input
size="large"
ref="searchInputRef"
placeholder="搜索页面支持名称、标识以及URL的模糊查询"
@input="searchPage">
<template #prefix><icon-search /></template>
</a-input>
</div>
<div class="mt-5">
<a-space size="large" class="flex justify-center">
<a-space><a-tag>ALT+S</a-tag><a-tag>唤醒搜索面板</a-tag></a-space>
<a-space>
<a-tag><icon-caret-up /></a-tag>
<a-tag><icon-caret-down /></a-tag>
<a-tag>切换搜索结果</a-tag>
</a-space>
<a-space><a-tag>Enter</a-tag><a-tag>进入页面</a-tag></a-space>
<a-space><a-tag>Esc</a-tag><a-tag>关闭搜索面板</a-tag></a-space>
</a-space>
</div>
<ul class="mt-10 results shadow-lg customer-scrollbar">
<template v-for="res in resultList">
<li
class="flex items-center"
v-if="res && res.path.indexOf(':') === -1 && res.components && res?.meta?.type === 'M'"
@click="gotoPage(res)">
<div class="icon-box flex justify-center items-center">
<component
v-if="res.meta.icon"
:is="res.meta.icon"
:class="res.meta.icon.indexOf('ma') > 0 ? 'icon' : ''" />
<icon-menu v-else />
</div>
<div class="ml-5 leading-6">
<div class="title">{{ res.meta.title }}</div>
<div class="path">{{ res.path }}</div>
</div>
</li>
</template>
</ul>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/store'
const appStore = useAppStore()
const router = useRouter()
const searchInputRef = ref()
const resultList = ref(router.getRoutes())
const searchPage = (value) => {
resultList.value = router.getRoutes().filter((item) => {
if (item.path && item.path.indexOf(value) > -1) {
return true
}
if (item.name && item.name.indexOf(value) > -1) {
return true
}
if (item.meta && item.meta.title && item.meta.title.indexOf(value) > -1) {
return true
}
return false
})
}
const gotoPage = (res) => {
appStore.searchOpen = false
router.push(res.path)
}
onMounted(() => {
searchInputRef.value.focus()
document.addEventListener('keydown', (e) => {
const keyCode = e.keyCode ?? e.which ?? e.charCode
const active = document.querySelector('.active-search-li')
const getActiveItemInfo = () => {
const li = document.querySelectorAll('.results li')
let activeItem = { idx: 0, path: '/' }
li.forEach((item, index) => {
if (item.className.split(' ').includes('active-search-li')) {
activeItem.path = item.querySelector('.path').innerHTML
activeItem.idx = index
return
}
})
return activeItem
}
const add = (index) => {
document.querySelectorAll('.results li')[index].classList.add('active-search-li')
}
const remove = (index) => {
document.querySelectorAll('.results li')[index].classList.remove('active-search-li')
}
if (appStore.searchOpen) {
// down
if (keyCode === 40) {
if (!active) {
add(0)
return
} else {
const li = document.querySelectorAll('.results li')
let item = getActiveItemInfo(),
nextIndex = item.idx + 1
if (nextIndex >= li.length) {
nextIndex = 0
}
remove(item.idx)
add(nextIndex)
}
}
// up
if (keyCode === 38) {
if (!active) {
add(document.querySelectorAll('.results li').length - 1)
return
} else {
const li = document.querySelectorAll('.results li')
let item = getActiveItemInfo(),
prevIndex = item.idx - 1
if (prevIndex < 0) {
prevIndex = li.length - 1
}
remove(item.idx)
add(prevIndex)
}
}
if (keyCode === 13) {
const item = getActiveItemInfo()
remove(item.idx)
item.path !== '/' && gotoPage(item)
}
}
nextTick(() => {
const dom = document.querySelector('.results')
if (dom && dom.scrollTop !== false) {
dom.scrollTop = (getActiveItemInfo()['idx'] + 1) * 80 - document.querySelectorAll('.results li').length * 10
}
})
})
})
</script>
<style scoped lang="less">
.sys-search-container {
top: 0;
left: 0;
position: absolute;
z-index: 999;
width: 100%;
height: 100%;
overflow: hidden;
& .ssc-bg {
position: absolute;
z-index: 999;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: rgba(100, 100, 100, 0.2);
backdrop-filter: blur(12px);
}
& .center-box {
height: 90%;
}
& .results {
background-color: var(--color-bg-2);
border-radius: 6px;
height: calc(100% - 250px);
overflow-y: auto;
& li {
border-bottom: 1px solid var(--color-border-1);
cursor: pointer;
.title {
font-size: 16px;
}
.path {
color: var(--color-text-3);
}
}
li:hover,
.active-search-li {
background-color: var(--color-neutral-1);
.arco-icon,
.icon {
transition: all 0.25s;
width: 1.8em !important;
height: 1.8em !important;
}
.arco-icon {
color: rgb(var(--primary-6));
}
.icon {
fill: rgb(var(--primary-6));
}
.title {
color: rgb(var(--primary-6));
}
.path {
color: rgb(var(--primary-3));
}
}
.icon-box {
width: 80px;
height: 80px;
border-right: 1px solid var(--color-border-1);
}
}
.arco-icon,
.icon {
width: 1.5em !important;
height: 1.5em !important;
}
.arco-menu-selected .icon {
fill: rgb(var(--primary-6));
}
}
</style>