yeg
2026-08-26 9f48020dfd3e29d1c0049a3da9df3203c40bc264
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<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>