2025-09-05 09:28:13 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="dashboard-layout">
|
|
|
|
|
|
<!-- 顶部导航栏(通用布局) -->
|
|
|
|
|
|
<header class="layout-header">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="header-btn back-btn"
|
|
|
|
|
|
@click="handleBack"
|
|
|
|
|
|
aria-label="返回"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-icon><ArrowLeft /></el-icon>
|
|
|
|
|
|
<span>返回</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<h1 class="layout-title">{{ title }}</h1>
|
|
|
|
|
|
|
|
|
|
|
|
<div style="display: flex;align-items: center;gap: 10px;">
|
|
|
|
|
|
<button class="header-btn" aria-label="设置" @click="settingVisible = true">
|
|
|
|
|
|
<el-icon><Setting /></el-icon>
|
|
|
|
|
|
<span>设置</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="header-btn refresh-btn"
|
|
|
|
|
|
@click="handleRefresh"
|
|
|
|
|
|
aria-label="刷新"
|
|
|
|
|
|
:disabled="loading"
|
|
|
|
|
|
:class="{ refreshing: isRefreshing }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-icon><Refresh /></el-icon>
|
|
|
|
|
|
<span>刷新</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 内容插槽:用于插入图表网格 -->
|
|
|
|
|
|
<main class="layout-content" v-loading="loading">
|
|
|
|
|
|
<slot></slot>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
2025-09-06 14:36:26 +08:00
|
|
|
|
<el-dialog v-model="settingVisible" title="图表设置" width="80%">
|
2025-09-05 09:28:13 +08:00
|
|
|
|
<ChartSetting />
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ArrowLeft, Refresh, Setting } from '@element-plus/icons-vue';
|
|
|
|
|
|
import ChartSetting from '../grid/setting.vue';
|
|
|
|
|
|
|
|
|
|
|
|
// 接收外部传入的状态与方法(props 类型约束)
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
title: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
default: '数据可视化大屏'
|
|
|
|
|
|
},
|
|
|
|
|
|
loading: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
default: false
|
|
|
|
|
|
},
|
|
|
|
|
|
isRefreshing: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
default: false
|
|
|
|
|
|
},
|
|
|
|
|
|
handleBack: {
|
|
|
|
|
|
type: Function,
|
|
|
|
|
|
required: true
|
|
|
|
|
|
},
|
|
|
|
|
|
handleRefresh: {
|
|
|
|
|
|
type: Function,
|
|
|
|
|
|
required: true
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const settingVisible = ref(false);
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
/* 布局根容器样式 */
|
|
|
|
|
|
.dashboard-layout {
|
|
|
|
|
|
width: 100vw;
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
background-color: #0f172a; /* 深色背景统一在布局层定义 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 顶部导航样式 */
|
|
|
|
|
|
.layout-header {
|
|
|
|
|
|
height: 60px;
|
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
background: linear-gradient(120deg, #1e293b, #0f172a);
|
|
|
|
|
|
border-bottom: 1px solid #334155;
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.layout-title {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #f8fafc;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-btn {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
|
background-color: #334155;
|
|
|
|
|
|
color: #f8fafc;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-btn:hover {
|
|
|
|
|
|
background-color: #475569;
|
|
|
|
|
|
box-shadow: 0 0 8px rgba(255, 255, 255, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-btn:disabled {
|
|
|
|
|
|
background-color: #2d3748;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-btn i {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 刷新按钮动画 */
|
|
|
|
|
|
.refreshing el-icon {
|
|
|
|
|
|
animation: spin 0.8s linear;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
|
from { transform: rotate(0deg); }
|
|
|
|
|
|
to { transform: rotate(360deg); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 内容区域容器(插槽父容器) */
|
|
|
|
|
|
.layout-content {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
overflow: hidden; /* 避免与子组件滚动冲突 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 小屏幕适配(导航栏) */
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.layout-title {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.header-btn span {
|
|
|
|
|
|
display: none; /* 隐藏按钮文字节省空间 */
|
|
|
|
|
|
}
|
|
|
|
|
|
.header-btn {
|
|
|
|
|
|
padding: 6px 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|