|
@@ -1,14 +1,16 @@
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
|
|
+import { ref, computed } from 'vue'
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
import { useAuthStore } from './stores/auth'
|
|
import { useAuthStore } from './stores/auth'
|
|
|
-import { computed } from 'vue'
|
|
|
|
|
|
|
+import { useBreakpoint } from './composables/useBreakpoint'
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
const route = useRoute()
|
|
|
const router = useRouter()
|
|
const router = useRouter()
|
|
|
const authStore = useAuthStore()
|
|
const authStore = useAuthStore()
|
|
|
|
|
+const { isNarrow } = useBreakpoint(768)
|
|
|
|
|
+const drawerVisible = ref(false)
|
|
|
|
|
|
|
|
const showLayout = computed(() => {
|
|
const showLayout = computed(() => {
|
|
|
- // Check meta first, default to true unless explicitly false (if not login)
|
|
|
|
|
if (route.name === 'login') return false
|
|
if (route.name === 'login') return false
|
|
|
return route.meta.showLayout !== false
|
|
return route.meta.showLayout !== false
|
|
|
})
|
|
})
|
|
@@ -25,6 +27,11 @@ const handleMenuSelect = (index: string) => {
|
|
|
} else {
|
|
} else {
|
|
|
router.push(index)
|
|
router.push(index)
|
|
|
}
|
|
}
|
|
|
|
|
+ drawerVisible.value = false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const openDrawer = () => {
|
|
|
|
|
+ drawerVisible.value = true
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
@@ -32,14 +39,27 @@ const handleMenuSelect = (index: string) => {
|
|
|
<div v-if="showLayout" class="app-container">
|
|
<div v-if="showLayout" class="app-container">
|
|
|
<el-container class="main-container">
|
|
<el-container class="main-container">
|
|
|
<el-header class="header">
|
|
<el-header class="header">
|
|
|
- <div class="logo">AI 智能值班平台</div>
|
|
|
|
|
|
|
+ <div class="header-left">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="isNarrow"
|
|
|
|
|
+ class="nav-toggle"
|
|
|
|
|
+ text
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="openDrawer"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon :size="22"><Menu /></el-icon>
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <div class="logo">AI 智能值班平台</div>
|
|
|
|
|
+ </div>
|
|
|
<div class="user-info">
|
|
<div class="user-info">
|
|
|
- <span>{{ authStore.username }}</span>
|
|
|
|
|
- <el-button type="danger" size="small" @click="handleLogout" style="margin-left: 10px;">退出登录</el-button>
|
|
|
|
|
|
|
+ <span class="username-text">{{ authStore.username }}</span>
|
|
|
|
|
+ <el-button type="danger" size="small" class="logout-btn" @click="handleLogout">
|
|
|
|
|
+ 退出登录
|
|
|
|
|
+ </el-button>
|
|
|
</div>
|
|
</div>
|
|
|
</el-header>
|
|
</el-header>
|
|
|
<el-container class="content-container">
|
|
<el-container class="content-container">
|
|
|
- <el-aside width="200px" class="aside">
|
|
|
|
|
|
|
+ <el-aside v-if="!isNarrow" width="200px" class="aside">
|
|
|
<el-menu
|
|
<el-menu
|
|
|
:default-active="route.path"
|
|
:default-active="route.path"
|
|
|
class="el-menu-vertical"
|
|
class="el-menu-vertical"
|
|
@@ -80,6 +100,50 @@ const handleMenuSelect = (index: string) => {
|
|
|
</el-main>
|
|
</el-main>
|
|
|
</el-container>
|
|
</el-container>
|
|
|
</el-container>
|
|
</el-container>
|
|
|
|
|
+
|
|
|
|
|
+ <el-drawer
|
|
|
|
|
+ v-if="isNarrow"
|
|
|
|
|
+ v-model="drawerVisible"
|
|
|
|
|
+ direction="ltr"
|
|
|
|
|
+ size="85%"
|
|
|
|
|
+ :with-header="false"
|
|
|
|
|
+ class="mobile-nav-drawer"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-menu
|
|
|
|
|
+ :default-active="route.path"
|
|
|
|
|
+ class="el-menu-mobile"
|
|
|
|
|
+ @select="handleMenuSelect"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-menu-item index="/big-screen">
|
|
|
|
|
+ <el-icon><Monitor /></el-icon>
|
|
|
|
|
+ <span>监控大屏</span>
|
|
|
|
|
+ </el-menu-item>
|
|
|
|
|
+ <el-menu-item index="/cameras" v-if="authStore.isSuperuser">
|
|
|
|
|
+ <el-icon><VideoCamera /></el-icon>
|
|
|
|
|
+ <span>摄像头管理</span>
|
|
|
|
|
+ </el-menu-item>
|
|
|
|
|
+ <el-menu-item index="/models" v-if="authStore.isSuperuser">
|
|
|
|
|
+ <el-icon><Connection /></el-icon>
|
|
|
|
|
+ <span>模型管理</span>
|
|
|
|
|
+ </el-menu-item>
|
|
|
|
|
+ <el-menu-item index="/tasks" v-if="authStore.isSuperuser">
|
|
|
|
|
+ <el-icon><List /></el-icon>
|
|
|
|
|
+ <span>任务调度</span>
|
|
|
|
|
+ </el-menu-item>
|
|
|
|
|
+ <el-menu-item index="/logs">
|
|
|
|
|
+ <el-icon><Document /></el-icon>
|
|
|
|
|
+ <span>报警日志</span>
|
|
|
|
|
+ </el-menu-item>
|
|
|
|
|
+ <el-menu-item index="/reports">
|
|
|
|
|
+ <el-icon><Document /></el-icon>
|
|
|
|
|
+ <span>值班报告</span>
|
|
|
|
|
+ </el-menu-item>
|
|
|
|
|
+ <el-menu-item index="/users" v-if="authStore.isSuperuser">
|
|
|
|
|
+ <el-icon><User /></el-icon>
|
|
|
|
|
+ <span>用户管理</span>
|
|
|
|
|
+ </el-menu-item>
|
|
|
|
|
+ </el-menu>
|
|
|
|
|
+ </el-drawer>
|
|
|
</div>
|
|
</div>
|
|
|
<div v-else class="login-container">
|
|
<div v-else class="login-container">
|
|
|
<router-view />
|
|
<router-view />
|
|
@@ -90,6 +154,7 @@ const handleMenuSelect = (index: string) => {
|
|
|
.app-container {
|
|
.app-container {
|
|
|
height: 100%;
|
|
height: 100%;
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
|
|
+ min-width: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.main-container {
|
|
.main-container {
|
|
@@ -99,18 +164,51 @@ const handleMenuSelect = (index: string) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.header {
|
|
.header {
|
|
|
- background-color: #409EFF;
|
|
|
|
|
|
|
+ background-color: #409eff;
|
|
|
color: white;
|
|
color: white;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- height: 60px;
|
|
|
|
|
- padding: 0 20px;
|
|
|
|
|
|
|
+ min-height: 56px;
|
|
|
|
|
+ height: auto;
|
|
|
|
|
+ padding: 8px 12px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.header-left {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.nav-toggle {
|
|
|
|
|
+ color: #fff !important;
|
|
|
|
|
+ padding: 4px 8px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.user-info {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.username-text {
|
|
|
|
|
+ max-width: 42vw;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.content-container {
|
|
.content-container {
|
|
|
- flex: 1; /* Fill remaining height */
|
|
|
|
|
- overflow: hidden; /* Prevent double scrollbar */
|
|
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.aside {
|
|
.aside {
|
|
@@ -124,14 +222,17 @@ const handleMenuSelect = (index: string) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.main-content {
|
|
.main-content {
|
|
|
- padding: 20px;
|
|
|
|
|
- overflow-y: auto; /* Scroll inside main content */
|
|
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ overflow-x: hidden;
|
|
|
background-color: #fff;
|
|
background-color: #fff;
|
|
|
|
|
+ min-width: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.logo {
|
|
.logo {
|
|
|
- font-size: 20px;
|
|
|
|
|
|
|
+ font-size: clamp(16px, 4vw, 20px);
|
|
|
font-weight: bold;
|
|
font-weight: bold;
|
|
|
|
|
+ min-width: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.login-container {
|
|
.login-container {
|
|
@@ -140,5 +241,35 @@ const handleMenuSelect = (index: string) => {
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
background-color: #f5f7fa;
|
|
background-color: #f5f7fa;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (min-width: 768px) {
|
|
|
|
|
+ .header {
|
|
|
|
|
+ padding: 0 20px;
|
|
|
|
|
+ height: 60px;
|
|
|
|
|
+ min-height: 60px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .main-content {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .username-text {
|
|
|
|
|
+ max-width: none;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|
|
|
|
|
+
|
|
|
|
|
+<style>
|
|
|
|
|
+/* Drawer: full-height menu without scoped pierce */
|
|
|
|
|
+.mobile-nav-drawer .el-drawer__body {
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.el-menu-mobile {
|
|
|
|
|
+ border-right: none;
|
|
|
|
|
+ min-height: 100%;
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|