| | |
| | | <script setup lang="ts"> |
| | | import { computed } from 'vue' |
| | | import { computed, onMounted } from 'vue' |
| | | import { useRoute } from 'vue-router' |
| | | import { getUserInfo } from '@/api/auth' |
| | | import { useDashboard, clearDashboard } from '@/stores/dashboard' |
| | | import { formatBusinessTime } from '@/utils/businessTime' |
| | | import GlobalSearch from '@/components/GlobalSearch.vue' |
| | | import NotificationCenter from '@/components/NotificationCenter.vue' |
| | | |
| | | defineProps<{ sidebarCollapsed: boolean }>() |
| | | defineEmits<{ toggleMenu: []; toggleSidebar: [] }>() |
| | | |
| | | const { snapshot, loadedAt, error, load } = useDashboard() |
| | | onMounted(async () => { |
| | | try { |
| | | const permissions = (await getUserInfo()).permissions || [] |
| | | if (permissions.some(value => ['*:*:*', 'dashboard:workbench:list', 'dashboard:workbench:query'].includes(value))) await load() |
| | | else clearDashboard() |
| | | } catch { /* 共享查询错误由顶部状态和首页错误提示展示。 */ } |
| | | }) |
| | | const route = useRoute() |
| | | const pageTitle = computed(() => String(route.meta.title || '工作台')) |
| | | </script> |
| | |
| | | </div> |
| | | |
| | | <div class="topbar-actions"> |
| | | <div class="sync-state"> |
| | | <div v-if="loadedAt" class="sync-state"> |
| | | <el-icon><Refresh /></el-icon> |
| | | <span>10 秒前同步</span> |
| | | <span>{{ formatBusinessTime(loadedAt) }} 更新</span> |
| | | </div> |
| | | <GlobalSearch /> |
| | | <NotificationCenter /> |
| | |
| | | </header> |
| | | |
| | | <div v-if="!route.path.startsWith('/test-list')" class="system-strip"> |
| | | <div><span>系统状态</span><strong class="healthy">NORMAL</strong></div> |
| | | <div><span>在线检测柜</span><strong class="healthy">18 / 36</strong></div> |
| | | <div><span>活动测试</span><strong class="active">12</strong></div> |
| | | <div><span>未处理告警</span><strong class="warning">3</strong></div> |
| | | <div><span>采集延迟</span><strong>26 ms</strong></div> |
| | | <div v-if="error"><span role="alert">概览加载失败</span></div> |
| | | <template v-else-if="snapshot"> |
| | | <div><span>在线检测柜</span><strong class="healthy">{{ snapshot.cabinets.filter(item => item.onlineStatus === '在线').length }} / {{ snapshot.cabinets.length }}</strong></div> |
| | | <div><span>检测中测试</span><strong class="active">{{ snapshot.tests.filter(item => item.status === 'RUNNING').length }}</strong></div> |
| | | <div><span>开放异常</span><strong class="warning">{{ snapshot.warnings.length }}</strong></div> |
| | | </template> |
| | | <div v-else><span>暂无已加载概览</span></div> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | align-items: center; |
| | | } |
| | | |
| | | .title-group { gap: 12px; } |
| | | .title-group h1 { margin: 0; font-size: 20px; line-height: 1; font-weight: 600; color: var(--ink); } |
| | | .title-group { gap: 12px; min-width:0; } |
| | | .title-group h1 { overflow:hidden;white-space:nowrap;text-overflow:ellipsis;margin: 0; font-size: 20px; line-height: 1; font-weight: 600; color: var(--ink); } |
| | | .title-group > span { color: #7b8794; font-size: 10px; font-weight: 500; } |
| | | .topbar-actions { gap: 14px; } |
| | | .topbar-actions { gap: 14px; flex-shrink:0; } |
| | | .sync-state { gap: 6px; color: #52606d; font-size: 12px; } |
| | | .sync-state .el-icon { color: var(--blue); font-size: 14px; } |
| | | |