<script setup lang="ts">
|
import { computed } from 'vue'
|
import { useRoute } from 'vue-router'
|
import GlobalSearch from '@/components/GlobalSearch.vue'
|
import NotificationCenter from '@/components/NotificationCenter.vue'
|
|
defineProps<{ sidebarCollapsed: boolean }>()
|
defineEmits<{ toggleMenu: []; toggleSidebar: [] }>()
|
|
const route = useRoute()
|
const pageTitle = computed(() => String(route.meta.title || '工作台'))
|
</script>
|
|
<template>
|
<header class="topbar">
|
<div class="title-group">
|
<button
|
class="sidebar-toggle"
|
type="button"
|
:aria-label="sidebarCollapsed ? '展开导航' : '收起导航'"
|
:title="sidebarCollapsed ? '展开导航' : '收起导航'"
|
@click="$emit('toggleSidebar')"
|
>
|
<el-icon><Expand v-if="sidebarCollapsed" /><Fold v-else /></el-icon>
|
</button>
|
<button class="mobile-menu" type="button" aria-label="打开导航" title="打开导航" @click="$emit('toggleMenu')">
|
<el-icon><Menu /></el-icon>
|
</button>
|
<h1>{{ pageTitle }}</h1>
|
<span>/ WORKBENCH-01</span>
|
</div>
|
|
<div class="topbar-actions">
|
<div class="sync-state">
|
<el-icon><Refresh /></el-icon>
|
<span>10 秒前同步</span>
|
</div>
|
<GlobalSearch />
|
<NotificationCenter />
|
</div>
|
</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>
|
</template>
|
|
<style scoped>
|
.topbar {
|
position: sticky;
|
top: 0;
|
z-index: 50;
|
height: 58px;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 0 20px;
|
background: #fff;
|
border-bottom: 1px solid var(--line);
|
}
|
|
.title-group, .topbar-actions, .sync-state {
|
display: flex;
|
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 > span { color: #7b8794; font-size: 10px; font-weight: 500; }
|
.topbar-actions { gap: 14px; }
|
.sync-state { gap: 6px; color: #52606d; font-size: 12px; }
|
.sync-state .el-icon { color: var(--blue); font-size: 14px; }
|
|
.mobile-menu, .sidebar-toggle {
|
border: 0;
|
padding: 0;
|
background: transparent;
|
color: #52606d;
|
cursor: pointer;
|
}
|
.sidebar-toggle { width: 28px; height: 28px; display: inline-grid; place-items: center; font-size: 19px; }
|
.sidebar-toggle:hover { color: var(--blue); background: #edf4fa; }
|
.mobile-menu { display: none; font-size: 19px; }
|
|
.system-strip {
|
height: 38px;
|
display: flex;
|
align-items: center;
|
gap: 24px;
|
padding: 0 20px;
|
overflow-x: auto;
|
white-space: nowrap;
|
background: #edf4fa;
|
border-bottom: 1px solid #d8e6f1;
|
}
|
.system-strip div { display: flex; align-items: center; gap: 7px; font-size: 12px; color: #6f7b86; }
|
.system-strip strong { color: #52606d; font-size: 13px; font-weight: 600; }
|
.system-strip .healthy { color: #23865e; }
|
.system-strip .active { color: var(--blue); }
|
.system-strip .warning { color: #b35a25; }
|
|
@media (max-width: 900px) {
|
.sidebar-toggle { display: none; }
|
.mobile-menu { display: inline-flex; }
|
}
|
|
@media (max-width: 620px) {
|
.title-group > span, .sync-state span { display: none; }
|
.system-strip { gap: 18px; padding: 0 14px; }
|
}
|
</style>
|