初始化

This commit is contained in:
2026-03-03 09:36:51 +08:00
commit 76c1a8668f
510 changed files with 28241 additions and 0 deletions

View File

@@ -0,0 +1,241 @@
<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>