|
|
@@ -0,0 +1,844 @@
|
|
|
+<template>
|
|
|
+<vue-fullscreen
|
|
|
+ class="box-container"
|
|
|
+ v-cloak
|
|
|
+ v-model="isFullscreen"
|
|
|
+ fullscreenClass="box-container"
|
|
|
+ :exit-on-click-wrapper="false"
|
|
|
+ >
|
|
|
+ <div class="box-container" v-cloak>
|
|
|
+ <div class="scroll-wrapper">
|
|
|
+ <!-- 头部区域 -->
|
|
|
+ <div class="header-section">
|
|
|
+ <div class="box-header-scroll">
|
|
|
+ <!-- <div class="logo">
|
|
|
+ <el-select
|
|
|
+ v-model="selectedStation"
|
|
|
+ placeholder="请选择"
|
|
|
+ class="custom-select"
|
|
|
+ popper-class="custom-select-dropdown"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in stationList"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </div> -->
|
|
|
+ <div class="header-center">
|
|
|
+ <div class="title">设备运维看板</div>
|
|
|
+ </div>
|
|
|
+ <!-- <div class="time">
|
|
|
+ <div class="time-display">
|
|
|
+ <span class="date-text">{{ currentDate }}</span>
|
|
|
+ <span class="time-text">{{ currentTime }}</span>
|
|
|
+ </div>
|
|
|
+ </div> -->
|
|
|
+ <div class="time">
|
|
|
+ <div class="time-display">
|
|
|
+ <span class="date-text">{{ currentDate }}</span>
|
|
|
+ <span class="time-text">{{ currentTime }}</span>
|
|
|
+ </div>
|
|
|
+ <span class="fullscreen-toggle" @click.passive="onFullscreen">
|
|
|
+ <i
|
|
|
+ v-if="isFullscreen"
|
|
|
+ title="取消全屏"
|
|
|
+ class="el-icon-_screen-restore"
|
|
|
+ />
|
|
|
+ <i v-else title="全屏" class="el-icon-_screen-full" />
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 统计卡片区域 -->
|
|
|
+ <div class="stats">
|
|
|
+ <!-- <el-row :gutter="16">
|
|
|
+ <el-col :span="8"> -->
|
|
|
+ <stat-card
|
|
|
+ v-for="item in statsCardList"
|
|
|
+ :key="item.key"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ :unit="item.unit"
|
|
|
+ :icon="item.icon"
|
|
|
+ :icon-color="item.iconColor"
|
|
|
+ :icon-bg="item.iconBg"
|
|
|
+ :value-color="item.valueColor"
|
|
|
+ :bgimg="'url(' + item.bgimg + ')'"
|
|
|
+ />
|
|
|
+ <!-- </el-col>
|
|
|
+ <el-col :span="8"> -->
|
|
|
+ <!-- <stat-card
|
|
|
+ :bgimg="'url(' + require('@/assets/pcs/runDevice.png') + ')'"
|
|
|
+ label="运行数"
|
|
|
+ :value="345"
|
|
|
+ unit="台"
|
|
|
+ icon="el-icon-video-play"
|
|
|
+ icon-color="#67C23A"
|
|
|
+ icon-bg="#f0f9eb"
|
|
|
+ value-color="#67C23A"
|
|
|
+ /> -->
|
|
|
+ <!-- </el-col>
|
|
|
+ <el-col :span="8"> -->
|
|
|
+ <!-- <stat-card
|
|
|
+ :bgimg="'url(' + require('@/assets/pcs/runDevice.png') + ')'"
|
|
|
+ label="维修数"
|
|
|
+ :value="5"
|
|
|
+ unit="台"
|
|
|
+ icon="el-icon-setting"
|
|
|
+ icon-color="#E6A23C"
|
|
|
+ icon-bg="#fdf6ec"
|
|
|
+ value-color="#E6A23C"
|
|
|
+ /> -->
|
|
|
+ <!-- </el-col> -->
|
|
|
+ <!-- </el-row> -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 双栏布局区域 -->
|
|
|
+ <div class="two-column-layout">
|
|
|
+ <!-- 左侧区域 -->
|
|
|
+ <div class="left-section">
|
|
|
+
|
|
|
+ <!-- 年运行率趋势图 -->
|
|
|
+ <div class="panel chart-panel">
|
|
|
+ <div class="panel-title">
|
|
|
+ <div class="panel-icon"></div>
|
|
|
+ 年运行率趋势图
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <trend-chart :option="yearRunRateOption" height="100%" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 年故障率趋势图 -->
|
|
|
+ <div class="panel chart-panel">
|
|
|
+ <div class="panel-title">
|
|
|
+ <div class="panel-icon"></div>
|
|
|
+ 年故障率趋势图
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <trend-chart :option="yearFaultRateOption" height="100%" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 年维修次数趋势图 -->
|
|
|
+ <div class="panel chart-panel">
|
|
|
+ <div class="panel-title">
|
|
|
+ <div class="panel-icon"></div>
|
|
|
+ 年维修次数趋势图
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <trend-chart :option="yearRepairCountOption" height="100%" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧区域 -->
|
|
|
+ <div class="right-section">
|
|
|
+
|
|
|
+ <!-- 月运行率趋势 -->
|
|
|
+ <div class="panel chart-panel">
|
|
|
+ <div class="panel-title">
|
|
|
+ <div class="panel-icon"></div>
|
|
|
+ 月运行率趋势图
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <trend-chart :option="monthRunRateOption" height="100%" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 月故障率趋势图 -->
|
|
|
+ <div class="panel chart-panel">
|
|
|
+ <div class="panel-title">
|
|
|
+ <div class="panel-icon"></div>
|
|
|
+ 月故障率趋势图
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <trend-chart :option="monthFaultRateOption" height="100%" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 月维修次数趋势图 -->
|
|
|
+ <div class="panel chart-panel">
|
|
|
+ <div class="panel-title">
|
|
|
+ <div class="panel-icon"></div>
|
|
|
+ 月维修次数趋势图
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <trend-chart :option="monthRepairCountOption" height="100%" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</vue-fullscreen>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import * as echarts from 'echarts'
|
|
|
+import { component } from 'vue-fullscreen'
|
|
|
+import TrendChart from './components/TrendChart.vue'
|
|
|
+import StatCard from './components/StatCard.vue'
|
|
|
+import { deviceIndexStatistics } from '@/api/main/index.js'
|
|
|
+
|
|
|
+// 固定场站列表
|
|
|
+const FIXED_STATIONS = ['5#站', '6#站', '7#站']
|
|
|
+
|
|
|
+// 固定 12 个月份的 x 轴标签
|
|
|
+const MONTHS_12 = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
|
|
+
|
|
|
+// 获取当月所有天数(1日 ~ 28/29/30/31日)
|
|
|
+function getCurrentMonthDays() {
|
|
|
+ const now = new Date()
|
|
|
+ const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate()
|
|
|
+ return Array.from({ length: daysInMonth }, (_, i) => (i + 1) + '日')
|
|
|
+}
|
|
|
+
|
|
|
+// 将 API 数据(dateTime="2026-05-15")格式化为当月日报图 xData + seriesConfig
|
|
|
+function formatDayTrendData(list, valueKey, colors) {
|
|
|
+ const valueField = valueKey || 'rate'
|
|
|
+ const DAYS = getCurrentMonthDays()
|
|
|
+ const stationMap = {}
|
|
|
+
|
|
|
+ list.forEach((item) => {
|
|
|
+ const station = item.stationName
|
|
|
+ const day = parseInt(item.dateTime.split('-')[2]) // "2026-05-15" → 15
|
|
|
+ const dayLabel = DAYS[day - 1] // 15 → "15日"
|
|
|
+ if (!stationMap[station]) stationMap[station] = {}
|
|
|
+ stationMap[station][dayLabel] = item[valueField]
|
|
|
+ })
|
|
|
+
|
|
|
+ const seriesConfig = FIXED_STATIONS.map((name, idx) => ({
|
|
|
+ name,
|
|
|
+ data: DAYS.map((d) => stationMap[name]?.[d] ?? 0),
|
|
|
+ color: colors[idx % colors.length]
|
|
|
+ }))
|
|
|
+
|
|
|
+ return { xData: DAYS, seriesConfig }
|
|
|
+}
|
|
|
+
|
|
|
+// 将 API 数据 [{stationName, dateTime, rate}] 格式化为 chart 所需的 xData(固定12月) + seriesConfig
|
|
|
+// 始终按 FIXED_STATIONS 顺序输出,无数据月份补 0
|
|
|
+function formatTrendData(list, valueKey, colors) {
|
|
|
+ const valueField = valueKey || 'rate'
|
|
|
+ const stationMap = {}
|
|
|
+
|
|
|
+ list.forEach((item) => {
|
|
|
+ const station = item.stationName
|
|
|
+ const month = parseInt(item.dateTime.split('-')[1]) // "2026-05" → 5
|
|
|
+ const monthLabel = MONTHS_12[month - 1] // 5 → "5月"
|
|
|
+ if (!stationMap[station]) stationMap[station] = {}
|
|
|
+ stationMap[station][monthLabel] = item[valueField]
|
|
|
+ })
|
|
|
+
|
|
|
+ const seriesConfig = FIXED_STATIONS.map((name, idx) => ({
|
|
|
+ name,
|
|
|
+ data: MONTHS_12.map((m) => stationMap[name]?.[m] ?? 0), // 缺月/缺站统一补 0
|
|
|
+ color: colors[idx % colors.length]
|
|
|
+ }))
|
|
|
+
|
|
|
+ return { xData: MONTHS_12, seriesConfig }
|
|
|
+}
|
|
|
+
|
|
|
+const palette = ['#3b82f6', '#22d3ee', '#facc15', '#f87171', '#a78bfa', '#34d399']
|
|
|
+
|
|
|
+function makeMultiLineOption(xData, seriesConfig, unit) {
|
|
|
+ const series = seriesConfig.map((item, idx) => {
|
|
|
+ const color = item.color || palette[idx % palette.length]
|
|
|
+ return {
|
|
|
+ name: item.name,
|
|
|
+ type: 'line',
|
|
|
+ data: item.data,
|
|
|
+ smooth: true,
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 4,
|
|
|
+ lineStyle: { width: 2, color: color },
|
|
|
+ itemStyle: { color: color },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: color },
|
|
|
+ { offset: 1, color: 'rgba(13,31,74,0.01)' }
|
|
|
+ ]),
|
|
|
+ opacity: 0.2
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ return {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ backgroundColor: 'rgba(8,21,46,0.9)',
|
|
|
+ borderColor: 'rgba(42,90,170,0.4)',
|
|
|
+ textStyle: { color: '#fff', fontSize: 12 }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ bottom: 0,
|
|
|
+ textStyle: { color: '#8ba3c7', fontSize: 11 },
|
|
|
+ itemWidth: 14,
|
|
|
+ itemHeight: 8
|
|
|
+ },
|
|
|
+ grid: { left: '3%', right: '4%', bottom: '12%', top: '8%', containLabel: true },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: false,
|
|
|
+ data: xData,
|
|
|
+ axisLine: { lineStyle: { color: '#4a6fa5' } },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: { color: '#8ba3c7', fontSize: 11 }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ max: function (value) { return Math.ceil(value.max * 1.1) },
|
|
|
+ axisLine: { show: false },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: { color: '#8ba3c7', fontSize: 11, formatter: '{value}' + unit },
|
|
|
+ splitLine: { lineStyle: { color: 'rgba(74,111,165,0.2)', type: 'dashed' } }
|
|
|
+ },
|
|
|
+ series: series
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'DeviceDashboard',
|
|
|
+ components: { VueFullscreen: component, TrendChart, StatCard },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ isFullscreen: false,
|
|
|
+ currentDate: '',
|
|
|
+ currentTime: '',
|
|
|
+ selectedStation: 'station-6',
|
|
|
+ stationList: [
|
|
|
+ { label: '5#场站', value: 'station-5' },
|
|
|
+ { label: '6#场站', value: 'station-6' },
|
|
|
+ { label: '7#场站', value: 'station-7' }
|
|
|
+ ],
|
|
|
+ statsCardList: [
|
|
|
+ { label: '设备总数', value: 0, unit: '台', key: 'totalNum', bgimg: require('@/assets/pcs/runDevice.png'), valueColor: '#409EFF', icon: 'el-icon-monitor', iconColor: '#409EFF', iconBg: '#ecf5ff'},
|
|
|
+ { label: '运行数', value: 0, unit: '台', key: 'runNum', bgimg: require('@/assets/pcs/successInfo.png'), valueColor: '#67C23A', icon: 'el-icon-play', iconColor: '#67C23A', iconBg: '#ecf5ff'},
|
|
|
+ { label: '维修数', value: 0, unit: '台', key: 'malfunctionNum', bgimg: require('@/assets/pcs/faultDevice.png'), valueColor: '#E6A23C', icon: 'el-icon-maintenance', iconColor: '#E6A23C', iconBg: '#ecf5ff'},
|
|
|
+ ],
|
|
|
+ yearRunRateOption: {},
|
|
|
+ monthRunRateOption: {},
|
|
|
+ yearFaultRateOption: {},
|
|
|
+ monthFaultRateOption: {},
|
|
|
+ yearRepairCountOption: {},
|
|
|
+ monthRepairCountOption: {}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {},
|
|
|
+ mounted() {
|
|
|
+ this.updateTime()
|
|
|
+ this.initCharts()
|
|
|
+ this.updateTimer = setInterval(this.updateTime, 1000)
|
|
|
+ window.addEventListener('resize', this.handleResize)
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ clearInterval(this.updateTimer)
|
|
|
+ window.removeEventListener('resize', this.handleResize)
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ isFullscreen: {
|
|
|
+ handler() {
|
|
|
+ this.$nextTick(() => this.handleResize())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ onFullscreen() {
|
|
|
+ this.isFullscreen = !this.isFullscreen;
|
|
|
+ },
|
|
|
+ handleResize() {
|
|
|
+ const { clientWidth, clientHeight } = document.documentElement
|
|
|
+ const containers = document.getElementsByClassName('box-container')
|
|
|
+ Array.from(containers).forEach((item) => {
|
|
|
+ if (this.isFullscreen) {
|
|
|
+ item.style.height = clientHeight + 'px'
|
|
|
+ item.style.width = clientWidth + 'px'
|
|
|
+ } else {
|
|
|
+ item.style.height = clientHeight - 100 + 'px'
|
|
|
+ item.style.width = clientWidth - 240 + 'px'
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.$nextTick(() => {
|
|
|
+ Object.values(this.$children).forEach(child => {
|
|
|
+ child.chartInstance && child.chartInstance.resize()
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ updateTime() {
|
|
|
+ const now = new Date()
|
|
|
+ const y = now.getFullYear()
|
|
|
+ const m = (now.getMonth() + 1)
|
|
|
+ const d = now.getDate()
|
|
|
+ this.currentDate = y + '-' + m + '-' + d
|
|
|
+ this.currentTime =
|
|
|
+ now.getHours().toString().padStart(2, '0') + ':' +
|
|
|
+ now.getMinutes().toString().padStart(2, '0') + ':' +
|
|
|
+ now.getSeconds().toString().padStart(2, '0')
|
|
|
+ },
|
|
|
+ async initCharts() {
|
|
|
+ const res = await deviceIndexStatistics()
|
|
|
+ console.log('res', res)
|
|
|
+ this.statsCardList.forEach((item) => {
|
|
|
+ item.value = res[item.key];
|
|
|
+ });
|
|
|
+
|
|
|
+ // ========== 年运行率趋势图 — nyxlList ==========
|
|
|
+ if (res.nyxlList && res.nyxlList.length) {
|
|
|
+ const { xData, seriesConfig } = formatTrendData(res.nyxlList, 'rate', palette)
|
|
|
+ this.yearRunRateOption = makeMultiLineOption(xData, seriesConfig, '%')
|
|
|
+ } else {
|
|
|
+ this.yearRunRateOption = makeMultiLineOption(MONTHS_12, [
|
|
|
+ { name: '暂无数据', data: new Array(12).fill(0), color: '#8ba3c7' }
|
|
|
+ ], '%')
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 月运行率趋势图 — ryxlList(X轴=当月天数)==========
|
|
|
+ const daysOfMonth = getCurrentMonthDays()
|
|
|
+ if (res.ryxlList && res.ryxlList.length) {
|
|
|
+ const { xData: x2, seriesConfig: sc2 } = formatDayTrendData(res.ryxlList, 'rate', palette)
|
|
|
+ this.monthRunRateOption = makeMultiLineOption(x2, sc2, '%')
|
|
|
+ } else {
|
|
|
+ this.monthRunRateOption = makeMultiLineOption(daysOfMonth, [
|
|
|
+ { name: '暂无数据', data: new Array(daysOfMonth.length).fill(0), color: '#8ba3c7' }
|
|
|
+ ], '%')
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 年故障率趋势图 — ngzlList ==========
|
|
|
+ if (res.ngzlList && res.ngzlList.length) {
|
|
|
+ const { xData: x3, seriesConfig: sc3 } = formatTrendData(res.ngzlList, 'rate', ['#f87171', '#facc15', '#8ba3c7'])
|
|
|
+ this.yearFaultRateOption = makeMultiLineOption(x3, sc3, '%')
|
|
|
+ } else {
|
|
|
+ this.yearFaultRateOption = makeMultiLineOption(MONTHS_12, [
|
|
|
+ { name: '暂无数据', data: new Array(12).fill(0), color: '#8ba3c7' }
|
|
|
+ ], '%')
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 月故障率趋势图(暂用模拟,后续可接入其他数据源)==========
|
|
|
+ if(res.rgzList && res.rgzList.length) {
|
|
|
+ const { xData: x4, seriesConfig: sc4 } = formatDayTrendData(res.rgzList, 'rate', ['#f87171', '#facc15', '#8ba3c7'])
|
|
|
+ this.monthFaultRateOption = makeMultiLineOption(x4, sc4, '%')
|
|
|
+ } else {
|
|
|
+ this.monthFaultRateOption = makeMultiLineOption(daysOfMonth, [
|
|
|
+ { name: '暂无数据', data: new Array(daysOfMonth.length).fill(0), color: '#a78bfa' }
|
|
|
+ ], '%')
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 年维修次数趋势图 — ngzlList ==========
|
|
|
+ if (res.ngzlList && res.ngzlList.length) {
|
|
|
+ const { xData: x5, seriesConfig: sc5 } = formatTrendData(res.ngzlList, 'rate', ['#f87171', '#facc15', '#3b82f6'])
|
|
|
+ this.yearRepairCountOption = makeMultiLineOption(x5, sc5, '%')
|
|
|
+ } else {
|
|
|
+ this.yearRepairCountOption = makeMultiLineOption(MONTHS_12, [
|
|
|
+ { name: '暂无数据', data: new Array(12).fill(0), color: '#8ba3c7' }
|
|
|
+ ], '%')
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 月维修次数趋势图(暂用默认占位,X轴=当月天数)==========
|
|
|
+ if(res.rgzList && res.rgzList.length) {
|
|
|
+ const { xData: x6, seriesConfig: sc6 } = formatDayTrendData(res.rgzList, 'rate', ['#f87171', '#facc15', '#8ba3c7'])
|
|
|
+ this.monthRepairCountOption = makeMultiLineOption(x6, sc6, '%')
|
|
|
+ } else {
|
|
|
+ this.monthRepairCountOption = makeMultiLineOption(daysOfMonth, [
|
|
|
+ { name: '暂无数据', data: new Array(daysOfMonth.length).fill(0), color: '#a78bfa' }
|
|
|
+ ], '%')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.box-container {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 100vh;
|
|
|
+
|
|
|
+ .scroll-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 100vh;
|
|
|
+ background:
|
|
|
+ radial-gradient(ellipse at top, rgba(13, 45, 110, 0.3) 0%, transparent 50%),
|
|
|
+ radial-gradient(ellipse at bottom, rgba(8, 25, 65, 0.5) 0%, transparent 50%),
|
|
|
+ linear-gradient(180deg, #0a1630 0%, #0d1f4a 50%, #08152e 100%);
|
|
|
+ font-family: 'Microsoft YaHei', sans-serif;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-section {
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding-top: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .box-header-scroll {
|
|
|
+ height: 80px;
|
|
|
+ background: url('~@/assets/pcs/header.png') no-repeat center top;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ display: flex;
|
|
|
+ // align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 0 20px;
|
|
|
+ position: relative;
|
|
|
+ padding-top: 10px;
|
|
|
+
|
|
|
+ .logo {
|
|
|
+ padding-top: 10px;
|
|
|
+ position: absolute;
|
|
|
+ left: 80px;
|
|
|
+
|
|
|
+ ::v-deep .custom-select {
|
|
|
+ .el-input__inner {
|
|
|
+ background: #0d2960;
|
|
|
+ border: 1px solid #1a4a8a;
|
|
|
+ color: #fff;
|
|
|
+ height: 36px;
|
|
|
+ line-height: 36px;
|
|
|
+
|
|
|
+ &::placeholder { color: #8ba3c7; }
|
|
|
+ }
|
|
|
+ .el-input__suffix .el-input__icon { color: #8ba3c7; }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-center {
|
|
|
+ .title {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #fff;
|
|
|
+ text-shadow: 0 0 10px rgba(59, 130, 246, 0.5);
|
|
|
+ letter-spacing: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .time {
|
|
|
+ position: absolute;
|
|
|
+ right: 30px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .time-display {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-end;
|
|
|
+
|
|
|
+ .date-text {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #8ba3c7;
|
|
|
+ }
|
|
|
+ .time-text {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stats {
|
|
|
+ display: flex;
|
|
|
+ padding: 10px 30px;
|
|
|
+ justify-content: space-around;
|
|
|
+ }
|
|
|
+
|
|
|
+ .two-column-layout {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ padding: 0 20px 20px 20px;
|
|
|
+ background: url('~@/assets/pcs/back.png') no-repeat center top;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ font-size: 0;
|
|
|
+ margin-top: 10px;
|
|
|
+
|
|
|
+ .left-section,
|
|
|
+ .right-section {
|
|
|
+ display: inline-flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 15px;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ font-size: 14px;
|
|
|
+ white-space: normal;
|
|
|
+ margin-right: 20px;
|
|
|
+
|
|
|
+ &:last-child { margin-right: 0; }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .panel {
|
|
|
+ padding: 15px;
|
|
|
+ min-height: 0;
|
|
|
+
|
|
|
+ .panel-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-family: 'YouSheBiaoTiHei', 'Microsoft YaHei', sans-serif;
|
|
|
+ color: #fff;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ height: 42px;
|
|
|
+ padding-left: 40px;
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ background: url('~@/assets/pcs/titleb.png') no-repeat center top;
|
|
|
+ background-size: 100% 100%;
|
|
|
+
|
|
|
+ .panel-icon {
|
|
|
+ position: absolute;
|
|
|
+ left: 15px;
|
|
|
+ width: 20px;
|
|
|
+ height: 25px;
|
|
|
+ background: url('~@/assets/pcs/titleIcon.png') no-repeat center top;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-panel {
|
|
|
+ flex: 0.6;
|
|
|
+ min-height: 200px;
|
|
|
+
|
|
|
+ .data-grid {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ height: calc(100% - 50px);
|
|
|
+
|
|
|
+ .data-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding-left: 15px;
|
|
|
+ width: 21%;
|
|
|
+ height: 60px;
|
|
|
+ background: url('~@/assets/pcs/leftItem.png') no-repeat center top;
|
|
|
+ background-size: 100% 100%;
|
|
|
+
|
|
|
+ .img {
|
|
|
+ width: 35px;
|
|
|
+ height: 35px;
|
|
|
+ background: url('~@/assets/pcs/icon.png') no-repeat center top;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-item_value {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-label {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #8ba3c7;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-value {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #b6ceff;
|
|
|
+ text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-panel {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 250px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ .chart-container {
|
|
|
+ width: 100%;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .alert-panel {
|
|
|
+ flex: 0.6;
|
|
|
+ min-height: 220px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ .alert-stats {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ height: calc(100% - 50px);
|
|
|
+
|
|
|
+ .alert-cards {
|
|
|
+ width: 50%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ justify-content: space-around;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .alert-card-item {
|
|
|
+ width: 42%;
|
|
|
+ height: 28%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding-left: 15px;
|
|
|
+ background: url('~@/assets/pcs/leftItem.png') no-repeat center top;
|
|
|
+ background-size: 100% 100%;
|
|
|
+
|
|
|
+ .img {
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ background: url('~@/assets/pcs/icon.png') no-repeat center top;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-item_value {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-label {
|
|
|
+ font-size: 11px;
|
|
|
+ color: #8ba3c7;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-value {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #b6ceff;
|
|
|
+ text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
|
|
|
+
|
|
|
+ small { font-size: 10px; color: #8ba3c7; margin-left: 2px; }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .alert-pie-chart {
|
|
|
+ width: 50%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ .alert-pie {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ min-height: 180px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// .stat-cards-section {
|
|
|
+// display: flex;
|
|
|
+// gap: 20px;
|
|
|
+// padding: 0 20px 10px 20px;
|
|
|
+// flex-shrink: 0;
|
|
|
+
|
|
|
+// .stat-card {
|
|
|
+// flex: 1;
|
|
|
+// height: 100px;
|
|
|
+// background: linear-gradient(135deg, rgba(16, 42, 88, 0.9) 0%, rgba(10, 28, 65, 0.85) 100%);
|
|
|
+// border: 1px solid rgba(42, 90, 170, 0.4);
|
|
|
+// border-radius: 8px;
|
|
|
+// display: flex;
|
|
|
+// align-items: center;
|
|
|
+// padding: 0 24px;
|
|
|
+// position: relative;
|
|
|
+// overflow: hidden;
|
|
|
+
|
|
|
+// &::before {
|
|
|
+// content: '';
|
|
|
+// position: absolute;
|
|
|
+// top: 0; left: 0; right: 0;
|
|
|
+// height: 2px;
|
|
|
+// background: linear-gradient(90deg, transparent, #00d4ff, transparent);
|
|
|
+// box-shadow: 0 0 8px rgba(0, 212, 255, 0.6);
|
|
|
+// }
|
|
|
+
|
|
|
+// &.blue {
|
|
|
+// .stat-icon {
|
|
|
+// background: linear-gradient(135deg, #3b82f6, #2563eb);
|
|
|
+// box-shadow: 0 0 15px rgba(59, 130, 246, 0.4);
|
|
|
+// }
|
|
|
+// .stat-value .num { color: #3b82f6; }
|
|
|
+// }
|
|
|
+
|
|
|
+// &.green {
|
|
|
+// .stat-icon {
|
|
|
+// background: linear-gradient(135deg, #22c55e, #16a34a);
|
|
|
+// box-shadow: 0 0 15px rgba(34, 197, 94, 0.4);
|
|
|
+// }
|
|
|
+// .stat-value .num { color: #22c55e; }
|
|
|
+// }
|
|
|
+
|
|
|
+// &.red {
|
|
|
+// .stat-icon {
|
|
|
+// background: linear-gradient(135deg, #ef4444, #dc2626);
|
|
|
+// box-shadow: 0 0 15px rgba(239, 68, 68, 0.4);
|
|
|
+// }
|
|
|
+// .stat-value .num { color: #ef4444; }
|
|
|
+// }
|
|
|
+
|
|
|
+// .stat-icon {
|
|
|
+// width: 56px;
|
|
|
+// height: 56px;
|
|
|
+// border-radius: 50%;
|
|
|
+// display: flex;
|
|
|
+// align-items: center;
|
|
|
+// justify-content: center;
|
|
|
+// flex-shrink: 0;
|
|
|
+
|
|
|
+// i {
|
|
|
+// font-size: 24px;
|
|
|
+// color: #fff;
|
|
|
+// }
|
|
|
+// }
|
|
|
+
|
|
|
+// .stat-label {
|
|
|
+// flex: 1;
|
|
|
+// font-size: 16px;
|
|
|
+// color: #8ba3c7;
|
|
|
+// padding: 0 20px;
|
|
|
+// text-align: center;
|
|
|
+// }
|
|
|
+
|
|
|
+// .stat-value {
|
|
|
+// display: flex;
|
|
|
+// align-items: baseline;
|
|
|
+
|
|
|
+// .num {
|
|
|
+// font-size: 36px;
|
|
|
+// font-weight: 700;
|
|
|
+// text-shadow: 0 0 10px rgba(0, 212, 255, 0.3);
|
|
|
+// }
|
|
|
+
|
|
|
+// .unit {
|
|
|
+// font-size: 14px;
|
|
|
+// color: #8ba3c7;
|
|
|
+// margin-left: 4px;
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+
|
|
|
+.fullscreen-toggle {
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 20px;
|
|
|
+ color: #8ba3c7;
|
|
|
+ transition: color 0.3s;
|
|
|
+ margin-left: 10px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #3b82f6;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+[v-cloak] { display: none; }
|
|
|
+</style>
|