<script setup lang="ts">
|
import { computed, ref } from 'vue'
|
import { useRouter } from 'vue-router'
|
import { useTests } from '@/stores/tests'
|
import { useCompareReports } from '@/stores/compareReports'
|
import { useFormationReports } from '@/stores/formationReports'
|
import { useSpliceReports } from '@/stores/spliceReports'
|
|
interface SearchResult {
|
key: string
|
title: string
|
description: string
|
category: string
|
path: string
|
icon: string
|
keywords: string
|
}
|
|
const router = useRouter()
|
const visible = ref(false)
|
const keyword = ref('')
|
const { records: tests } = useTests()
|
const { records: compareReports } = useCompareReports()
|
const { records: formationReports } = useFormationReports()
|
const { records: spliceReports } = useSpliceReports()
|
|
const pages: SearchResult[] = [
|
{ key: 'dashboard', title: '工作台', description: '测试、设备、异常和待办概览', category: '页面', path: '/dashboard', icon: 'HomeFilled', keywords: '首页 控制台 工作台' },
|
{ key: 'test-list', title: '测试列表', description: '查询、新建和管理测试任务', category: '测试管理', path: '/test-list', icon: 'Document', keywords: '测试 检测 任务' },
|
{ key: 'battery-group', title: '电池组管理', description: '电池组、模板和电池号维护', category: '测试管理', path: '/battery-group', icon: 'Box', keywords: '电池 电池号 模板' },
|
{ key: 'battery-fields', title: '电池组字段管理', description: '字段类型、启禁用和删除', category: '测试管理', path: '/battery-group/fields', icon: 'Tickets', keywords: '字段 模板' },
|
{ key: 'test-type', title: '测试类型管理', description: '维护测试类型和状态', category: '测试管理', path: '/test-type', icon: 'CollectionTag', keywords: '测试 类型' },
|
{ key: 'revision', title: '数据修订记录', description: '查询数据修改审计记录', category: '测试管理', path: '/data-revision', icon: 'Clock', keywords: '数据 修订 修改 审计' },
|
{ key: 'exception', title: '异常管理', description: '查看并处理测试异常', category: '测试管理', path: '/exception', icon: 'Warning', keywords: '异常 告警 预警' },
|
{ key: 'cabinet', title: '检测柜', description: '检测柜设备和通道管理', category: '设备管理', path: '/device-cabinet', icon: 'SetUp', keywords: '设备 测试柜 检测柜 通道' },
|
{ key: 'collector', title: '采集器', description: '采集器设备和通道管理', category: '设备管理', path: '/device-collector', icon: 'Cpu', keywords: '设备 采集器 通道' },
|
{ key: 'compare', title: '对比报表', description: '测试曲线与数据对比', category: '数据分析', path: '/report-compare', icon: 'TrendCharts', keywords: '报告 对比 图表' },
|
{ key: 'formation', title: '化成报表', description: '化成循环数据和曲线', category: '数据分析', path: '/report-formation', icon: 'DataLine', keywords: '报告 化成 图表 循环' },
|
{ key: 'splice', title: '拼接报表', description: '多测试曲线横向拼接', category: '数据分析', path: '/report-splice', icon: 'Connection', keywords: '报告 拼接 图表' },
|
{ key: 'todo', title: '待办列表', description: '待办创建、处理和查询', category: '代办管理', path: '/todo-list', icon: 'List', keywords: '代办 待办 任务' },
|
{ key: 'account', title: '账号管理', description: '账号、状态和密码维护', category: '角色与权限', path: '/account', icon: 'User', keywords: '用户 账号 密码' },
|
{ key: 'role', title: '角色管理', description: '角色和操作权限配置', category: '角色与权限', path: '/role', icon: 'UserFilled', keywords: '角色 权限' },
|
{ key: 'operation-log', title: '操作日志', description: '查询系统用户的操作审计记录', category: '角色与权限', path: '/operation-log', icon: 'Memo', keywords: '日志 操作 审计 用户行为' },
|
]
|
|
const allResults = computed<SearchResult[]>(() => [
|
...pages,
|
...tests.value.map(item => ({
|
key: `test-${item.id}`, title: item.name, description: `${item.type} · ${item.status} · ${item.batteryGroup}`,
|
category: '测试任务', path: `/test-list/${item.id}/console`, icon: 'Monitor', keywords: `${item.id} ${item.batteryNo} ${item.owner}`,
|
})),
|
...compareReports.value.map(item => ({
|
key: `compare-${item.id}`, title: item.name, description: `对比报表 · ${item.creator} · ${item.createdAt}`,
|
category: '报表', path: `/report-compare/${item.id}/edit?mode=view`, icon: 'TrendCharts', keywords: `${item.id} 对比 报告`,
|
})),
|
...formationReports.value.map(item => ({
|
key: `formation-${item.id}`, title: item.name, description: `化成报表 · ${item.creator} · ${item.createdAt}`,
|
category: '报表', path: `/report-formation/${item.id}/edit?mode=view`, icon: 'DataLine', keywords: `${item.id} 化成 报告`,
|
})),
|
...spliceReports.value.map(item => ({
|
key: `splice-${item.id}`, title: item.name, description: `拼接报表 · ${item.creator} · ${item.createdAt}`,
|
category: '报表', path: `/report-splice/${item.id}/edit?mode=view`, icon: 'Connection', keywords: `${item.id} 拼接 报告`,
|
})),
|
])
|
|
const searchResults = computed(() => {
|
const query = keyword.value.trim().toLocaleLowerCase()
|
if (!query) return pages.slice(0, 6)
|
return allResults.value.filter(item => `${item.title} ${item.description} ${item.category} ${item.keywords}`.toLocaleLowerCase().includes(query)).slice(0, 12)
|
})
|
|
async function openResult(result: SearchResult) {
|
visible.value = false
|
keyword.value = ''
|
await router.push(result.path)
|
}
|
|
function openFirstResult() {
|
const first = searchResults.value[0]
|
if (first && keyword.value.trim()) openResult(first)
|
}
|
</script>
|
|
<template>
|
<button class="header-icon-button" type="button" aria-label="页面搜索" title="页面搜索" @click="visible = true"><el-icon><Search /></el-icon></button>
|
|
<el-dialog v-model="visible" class="global-search-dialog" width="680px" align-center destroy-on-close @closed="keyword = ''">
|
<template #header><div class="search-dialog-title"><el-icon><Search /></el-icon><strong>页面搜索</strong></div></template>
|
<el-input v-model="keyword" class="search-input" size="large" clearable autofocus placeholder="搜索页面、测试名称或报表" @keyup.enter="openFirstResult">
|
<template #prefix><el-icon><Search /></el-icon></template>
|
</el-input>
|
<div class="search-summary"><span>{{ keyword.trim() ? '搜索结果' : '常用页面' }}</span><small>{{ searchResults.length }} 项</small></div>
|
<div v-if="searchResults.length" class="search-results">
|
<button v-for="item in searchResults" :key="item.key" type="button" class="search-result" @click="openResult(item)">
|
<span class="result-icon"><el-icon><component :is="item.icon" /></el-icon></span>
|
<span class="result-content"><strong>{{ item.title }}</strong><small>{{ item.description }}</small></span>
|
<span class="result-category">{{ item.category }}</span>
|
<el-icon class="result-arrow"><ArrowRight /></el-icon>
|
</button>
|
</div>
|
<div v-else class="search-empty"><el-icon><Search /></el-icon><strong>没有匹配结果</strong><span>请更换关键词后重试</span></div>
|
</el-dialog>
|
</template>
|
|
<style scoped>
|
.header-icon-button{position:relative;width:28px;height:28px;display:inline-grid;place-items:center;padding:0;border:0;color:#52606d;background:transparent;font-size:17px;cursor:pointer}.header-icon-button:hover{color:var(--blue);background:#edf4fa}.search-dialog-title{display:flex;align-items:center;gap:9px;color:#26333f}.search-dialog-title .el-icon{color:var(--blue);font-size:18px}.search-dialog-title strong{font-size:15px}.search-input :deep(.el-input__wrapper){min-height:42px;border-radius:3px;box-shadow:0 0 0 1px #b8cadb inset;background:#f8fafc}.search-summary{height:38px;display:flex;align-items:center;justify-content:space-between;color:#52606d;font-size:12px}.search-summary small{color:#8795a3}.search-results{max-height:390px;overflow-y:auto;border:1px solid #d8e1e9}.search-result{width:100%;min-height:58px;display:grid;grid-template-columns:34px minmax(0,1fr) auto 18px;align-items:center;gap:11px;padding:8px 12px;border:0;border-bottom:1px solid #e8eef3;color:#52606d;background:#fff;text-align:left;cursor:pointer}.search-result:last-child{border-bottom:0}.search-result:hover,.search-result:focus-visible{background:#eef6ff;outline:0}.result-icon{width:32px;height:32px;display:grid;place-items:center;color:#2b78a3;background:#e7f2fd;font-size:16px}.result-content{min-width:0}.result-content strong,.result-content small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.result-content strong{color:#26333f;font-size:13px;font-weight:600}.result-content small{margin-top:4px;color:#7b8794;font-size:11px}.result-category{padding:3px 6px;border:1px solid #d8e1e9;color:#647380;background:#f8fafc;font-size:10px}.result-arrow{color:#9aabb9}.search-empty{height:190px;display:flex;align-items:center;justify-content:center;flex-direction:column;color:#8795a3}.search-empty>.el-icon{font-size:30px}.search-empty strong{margin-top:10px;color:#52606d;font-size:13px}.search-empty span{margin-top:5px;font-size:11px}:global(.global-search-dialog){max-width:calc(100vw - 28px);border-radius:4px!important}:global(.global-search-dialog .el-dialog__header){margin:0;padding:17px 20px;border-bottom:1px solid #d8e1e9}:global(.global-search-dialog .el-dialog__body){padding:18px 20px 20px}@media(max-width:620px){.search-result{grid-template-columns:34px minmax(0,1fr) 18px}.result-category{display:none}}
|
</style>
|