|
|
|
@ -1,546 +1,328 @@
|
|
|
|
<template>
|
|
|
|
<template>
|
|
|
|
<ContentWrap class="body" >
|
|
|
|
<div class="dashboard">
|
|
|
|
<el-row :gutter="20">
|
|
|
|
<!-- 顶部标题栏 -->
|
|
|
|
<!-- 左侧区域 -->
|
|
|
|
<div class="dashboard-header">
|
|
|
|
<el-col :span="7" >
|
|
|
|
<div class="header-left">
|
|
|
|
<!-- 左上:统计信息 -->
|
|
|
|
<span class="header-time">{{ currentTime }}</span>
|
|
|
|
<el-card class="box-card" style="height: 250px" shadow="always" >
|
|
|
|
<span class="header-weekday">{{ currentWeekday }}</span>
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="card-header" :body-style="{ borderTop: 'none' }">
|
|
|
|
|
|
|
|
<span>统计信息</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<div class="header-title">
|
|
|
|
<div>
|
|
|
|
<span class="title-deco">✦</span>
|
|
|
|
<p class="colour-text">总盘点数: {{ statistics.checkLogCount }}</p>
|
|
|
|
智能安全管控系统
|
|
|
|
<p class="colour-text">总随行数: {{ statistics.orderCount }}</p>
|
|
|
|
<span class="title-deco">✦</span>
|
|
|
|
<p class="colour-text">月盘点数: {{ statistics.checkLogMonthCount }}</p>
|
|
|
|
|
|
|
|
<p class="colour-text">月随行数: {{ statistics.orderMonthCount }}</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</el-card>
|
|
|
|
<div class="header-right"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 左中:条状图 -->
|
|
|
|
|
|
|
|
<el-card class="box-card" shadow="always">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
|
|
<span>盘点状态</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<Echart dark="true" style="width: 100%; height: 200px" :options="deviceStatusChartOption" />
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 中间区域 选择相机 -->
|
|
|
|
<!-- 主体 -->
|
|
|
|
<el-col :span="10">
|
|
|
|
<el-row :gutter="10" class="main-row">
|
|
|
|
|
|
|
|
<!-- 左列 1/4 -->
|
|
|
|
<el-card class="box-card" shadow="always" style="height: 590px">
|
|
|
|
<el-col :span="6">
|
|
|
|
|
|
|
|
<div class="left-col">
|
|
|
|
<el-select v-model="selectedCamera" placeholder="请选择相机" style="width: 100%">
|
|
|
|
<div class="glass-panel">
|
|
|
|
<el-option
|
|
|
|
<div class="card-title"><span class="title-badge">📊</span> 设备在线率<span class="title-en">DEVICE ONLINE</span></div>
|
|
|
|
v-for="camera in cameraList"
|
|
|
|
<div class="rate-content">
|
|
|
|
:key="camera.id"
|
|
|
|
<div class="ring-wrap">
|
|
|
|
:label="camera.name"
|
|
|
|
<svg viewBox="0 0 100 100" class="rate-ring-svg">
|
|
|
|
:value="camera.id"
|
|
|
|
<circle cx="50" cy="50" r="42" class="ring-bg" />
|
|
|
|
/>
|
|
|
|
<circle cx="50" cy="50" r="42" class="ring-fg" :stroke-dasharray="`${onlineRate * 264} 264`" />
|
|
|
|
</el-select>
|
|
|
|
<circle cx="50" cy="50" r="42" class="ring-glow" :stroke-dasharray="`${onlineRate * 264} 264`" />
|
|
|
|
<div style="height: 500px; background-color: #555555" class="mt-20px">
|
|
|
|
</svg>
|
|
|
|
<Camera v-if="selectedCamera" :cameraId="selectedCamera" />
|
|
|
|
<div class="ring-center">
|
|
|
|
|
|
|
|
<div class="ring-pct">{{ Math.round(onlineRate * 100) }}%</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
<!-- <el-col :span="10">
|
|
|
|
|
|
|
|
<el-card class="box-card" shadow="always" style="height: 590px">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
|
|
<span>实时视频</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<div class="rate-detail">
|
|
|
|
<div style="height: 500px; background-color: #555555; display: flex; align-items: center; justify-content: center;">
|
|
|
|
<div class="rate-item"><span class="dot dot-online"></span><span class="rate-label">在线</span><b>{{ onlineCount }}</b></div>
|
|
|
|
<video
|
|
|
|
<div class="rate-item"><span class="dot dot-offline"></span><span class="rate-label">离线</span><b>{{ offlineCount }}</b></div>
|
|
|
|
src="http://121.43.244.209:9010/record/live/camera16/2025-08-25/15-45-26-0_compressed.mp4"
|
|
|
|
<div class="rate-item rate-total"><span class="rate-label">总数</span><b>{{ onlineCount + offlineCount }}</b></div>
|
|
|
|
autoplay
|
|
|
|
|
|
|
|
loop
|
|
|
|
|
|
|
|
muted
|
|
|
|
|
|
|
|
style="width: 100%; height: 100%; object-fit: contain;"
|
|
|
|
|
|
|
|
></video>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</el-col> -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 右侧区域 -->
|
|
|
|
|
|
|
|
<el-col :span="7">
|
|
|
|
|
|
|
|
<!-- 右上:快捷方式 -->
|
|
|
|
|
|
|
|
<el-card class="box-card" style="height: 250px" shadow="always">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
|
|
<span>快捷方式</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<!-- <el-skeleton :loading="loading" animated>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</el-skeleton> -->
|
|
|
|
|
|
|
|
<el-row>
|
|
|
|
|
|
|
|
<p></p>
|
|
|
|
|
|
|
|
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-50px">
|
|
|
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
|
|
|
<Icon :icon="item.icon" size="30" class="mr-3px" />
|
|
|
|
|
|
|
|
<router-link :to="item.url">
|
|
|
|
|
|
|
|
<el-link type="default" :underline="false">
|
|
|
|
|
|
|
|
<span class='white-text-icon'>{{ item.name }}</span>
|
|
|
|
|
|
|
|
</el-link>
|
|
|
|
|
|
|
|
</router-link>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</el-col>
|
|
|
|
<div class="glass-panel">
|
|
|
|
</el-row>
|
|
|
|
<div class="card-title"><span class="title-badge">🌡</span> 区域温度<span class="title-en">TEMPERATURE</span></div>
|
|
|
|
</el-card>
|
|
|
|
<div class="chart-wrap">
|
|
|
|
|
|
|
|
<Echart :options="tempBarOption" />
|
|
|
|
<!-- 右中:另一个条状图 -->
|
|
|
|
</div>
|
|
|
|
<el-card class="box-card" shadow="always">
|
|
|
|
</div>
|
|
|
|
<template #header>
|
|
|
|
<div class="glass-panel">
|
|
|
|
<div class="card-header">
|
|
|
|
<div class="card-title"><span class="title-badge">💧</span> 区域湿度<span class="title-en">HUMIDITY</span></div>
|
|
|
|
<span>本月随行情况</span>
|
|
|
|
<div class="chart-wrap">
|
|
|
|
|
|
|
|
<Echart :options="humidityLineOption" />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<Echart dark="true" style="width: 100%; height: 200px" :options="orderChartOption" />
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
</el-col>
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 底部区域 -->
|
|
|
|
<!-- 中右列 3/4 -->
|
|
|
|
<el-row :gutter="20" class="mt-20px">
|
|
|
|
<el-col :span="18">
|
|
|
|
<el-col :span="7">
|
|
|
|
<div class="right-area">
|
|
|
|
<!-- 左下:实时滚动信息 -->
|
|
|
|
<!-- 上面全部是 3D 仓库背景 -->
|
|
|
|
<el-card class="box-card" shadow="always">
|
|
|
|
<div class="bg-section">
|
|
|
|
<template #header>
|
|
|
|
<WarehouseBg />
|
|
|
|
<div class="card-header">
|
|
|
|
</div>
|
|
|
|
<span>盘点饼状图</span>
|
|
|
|
<!-- 下面一行:报警记录 + 按钮 -->
|
|
|
|
|
|
|
|
<div class="bottom-row">
|
|
|
|
|
|
|
|
<div class="bottom-left">
|
|
|
|
|
|
|
|
<div class="glass-panel">
|
|
|
|
|
|
|
|
<div class="card-title"><span class="title-badge">📋</span> 报警记录<span class="title-en">RECORDS</span></div>
|
|
|
|
|
|
|
|
<el-table :data="alarmRecords" size="small" class="dark-table" stripe>
|
|
|
|
|
|
|
|
<el-table-column label="时间" prop="time" min-width="80" />
|
|
|
|
|
|
|
|
<el-table-column label="区域" prop="area" min-width="50" />
|
|
|
|
|
|
|
|
<el-table-column label="类型" prop="type" min-width="80" />
|
|
|
|
|
|
|
|
<el-table-column label="信息" prop="message" min-width="80" />
|
|
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="bottom-right">
|
|
|
|
|
|
|
|
<div class="glass-panel nav-panel">
|
|
|
|
|
|
|
|
<div class="nav-bar">
|
|
|
|
|
|
|
|
<router-link to="/warehousealarm/deviceDashboard">
|
|
|
|
|
|
|
|
<el-button class="nav-btn"><span class="nav-icon">📡</span> 设备看板</el-button>
|
|
|
|
|
|
|
|
</router-link>
|
|
|
|
|
|
|
|
<router-link to="/warehousealarm/alarmStatistics">
|
|
|
|
|
|
|
|
<el-button class="nav-btn"><span class="nav-icon">📋</span> 报警记录</el-button>
|
|
|
|
|
|
|
|
</router-link>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<Echart dark="true" style="width: 100%; height: 200px" :options="stockPieOptions" />
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
<el-col :span="10">
|
|
|
|
|
|
|
|
<el-card class="box-card" shadow="always">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
|
|
<span>随行折线图</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<Echart dark="true" style="width: 100%; height: 200px" :options="laneInventoryLineOptions" />
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
<el-col :span="7">
|
|
|
|
|
|
|
|
<!-- 左下:实时滚动信息 -->
|
|
|
|
|
|
|
|
<el-card class="box-card" shadow="always">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
|
|
<span>实时滚动信息</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<el-scrollbar height="200px">
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
v-for="(item, index) in realTimeLogs.slice().reverse()"
|
|
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
|
|
class="log-item"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<p class="colour-text">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{{ item }}
|
|
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
</el-col>
|
|
|
|
</el-row>
|
|
|
|
</el-row>
|
|
|
|
</ContentWrap>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
<script setup lang="ts">
|
|
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
|
|
|
import { CameraApi, CameraVO } from '@/api/logistics/camera'
|
|
|
|
|
|
|
|
import { homeApi } from '@/api/logistics/home'
|
|
|
|
|
|
|
|
import Camera from '@/components/camera/camera.vue'
|
|
|
|
|
|
|
|
import { EChartsOption } from 'echarts'
|
|
|
|
import { EChartsOption } from 'echarts'
|
|
|
|
import { StockApi } from '@/api/logistics/stock'
|
|
|
|
import WarehouseBg from '@/components/warehouseBg/index.vue'
|
|
|
|
import type { Shortcut } from './types'
|
|
|
|
import { DeviceLatestDataApi } from '@/api/warehousealarm/devicelatestdata'
|
|
|
|
|
|
|
|
import { AlarmRecordsApi } from '@/api/warehousealarm/alarmrecords'
|
|
|
|
// ----------------------------- 数据定义 -----------------------------
|
|
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'HomeDashboard' })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const currentTime = ref('')
|
|
|
|
|
|
|
|
const currentWeekday = ref('')
|
|
|
|
|
|
|
|
let timer: any = null
|
|
|
|
|
|
|
|
const updateTime = () => {
|
|
|
|
|
|
|
|
const now = new Date()
|
|
|
|
|
|
|
|
const w = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'][now.getDay()]
|
|
|
|
|
|
|
|
currentTime.value = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')}`
|
|
|
|
|
|
|
|
currentWeekday.value = w
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 统计信息
|
|
|
|
const onlineCount = ref(0)
|
|
|
|
const statistics = ref({
|
|
|
|
const offlineCount = ref(0)
|
|
|
|
checkLogCount: 0,
|
|
|
|
const onlineRate = computed(() => {
|
|
|
|
orderCount: 0,
|
|
|
|
const total = onlineCount.value + offlineCount.value
|
|
|
|
checkLogMonthCount: 0,
|
|
|
|
return total > 0 ? onlineCount.value / total : 0
|
|
|
|
orderMonthCount: 0
|
|
|
|
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const laneInventoryStatistics = ref()
|
|
|
|
const alarmRecords = ref<any[]>([])
|
|
|
|
const laneInventoryLine = ref()
|
|
|
|
|
|
|
|
const stockLaneInventoryStatistics = ref()
|
|
|
|
|
|
|
|
const buttonByStatusMap = ref(new Map<string, number>()) // 存储 row-column 到 statusString 的映射
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 选择相机直播
|
|
|
|
|
|
|
|
const selectedCamera = ref<number | null>(null)
|
|
|
|
|
|
|
|
const cameraList = ref<CameraVO[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 快捷方式
|
|
|
|
const tempBarOption = ref<EChartsOption>({})
|
|
|
|
let shortcut = reactive<Shortcut[]>([])
|
|
|
|
const humidityLineOption = ref<EChartsOption>({})
|
|
|
|
|
|
|
|
|
|
|
|
// 实时滚动信息
|
|
|
|
const initCharts = () => {
|
|
|
|
const realTimeLogs = ref<string[]>([])
|
|
|
|
// 初始占位,加载数据后会更新
|
|
|
|
let logTimer: NodeJS.Timeout | null = null
|
|
|
|
tempBarOption.value = {
|
|
|
|
|
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
|
// ----------------------------- 数据获取 -----------------------------
|
|
|
|
grid: { left: 34, right: 8, bottom: 18, top: 6 },
|
|
|
|
|
|
|
|
xAxis: { type: 'category', data: [], axisLabel: { color: 'rgba(10,255,255,0.8)', fontSize: 9 } },
|
|
|
|
// 获取所有数据
|
|
|
|
yAxis: { type: 'value', axisLabel: { color: 'rgba(10,255,255,0.6)', fontSize: 9 }, splitLine: { lineStyle: { color: 'rgba(10,255,255,0.06)' } } },
|
|
|
|
const getAll = async () => {
|
|
|
|
series: [{ type: 'bar', data: [], itemStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: '#0affff' }, { offset: 1, color: 'rgba(10,255,255,0.2)' }] }, borderRadius: [4,4,0,0] } }],
|
|
|
|
try {
|
|
|
|
|
|
|
|
statistics.value = await homeApi.statistics()
|
|
|
|
|
|
|
|
laneInventoryStatistics.value = await homeApi.laneInventoryStatistics()
|
|
|
|
|
|
|
|
laneInventoryLine.value = await homeApi.laneInventoryLine()
|
|
|
|
|
|
|
|
stockLaneInventoryStatistics.value = await homeApi.stockLaneInventoryStatistics()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const chartData = stockLaneInventoryStatistics.value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 盘点状态条状图选项
|
|
|
|
|
|
|
|
deviceStatusChartOption.value = {
|
|
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
|
|
type: 'shadow'
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
humidityLineOption.value = {
|
|
|
|
legend: {
|
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
|
data: chartData.series.map((series) => series.name)
|
|
|
|
grid: { left: 34, right: 8, bottom: 18, top: 6 },
|
|
|
|
},
|
|
|
|
xAxis: { type: 'category', data: [], axisLabel: { color: 'rgba(10,255,255,0.8)', fontSize: 9 } },
|
|
|
|
grid: {
|
|
|
|
yAxis: { type: 'value', axisLabel: { color: 'rgba(10,255,255,0.6)', fontSize: 9 }, splitLine: { lineStyle: { color: 'rgba(10,255,255,0.06)' } } },
|
|
|
|
left: '3%',
|
|
|
|
series: [{ type: 'line', smooth: true, data: [], lineStyle: { color: '#0affff', width: 2 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: 'rgba(10,255,255,0.25)' }, { offset: 1, color: 'rgba(10,255,255,0)' }] } }, symbol: 'circle', symbolSize: 4 }],
|
|
|
|
right: '4%',
|
|
|
|
|
|
|
|
bottom: '3%',
|
|
|
|
|
|
|
|
containLabel: true
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
xAxis: chartData.xaxis,
|
|
|
|
|
|
|
|
yAxis: chartData.yaxis,
|
|
|
|
|
|
|
|
series: chartData.series.map((series) => ({
|
|
|
|
|
|
|
|
name: series.name,
|
|
|
|
|
|
|
|
type: series.type,
|
|
|
|
|
|
|
|
stack: series.stack,
|
|
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
|
|
color: series.color
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
data: series.data
|
|
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置本月随行情况条状图选项
|
|
|
|
|
|
|
|
orderChartOption.value = {
|
|
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
|
|
type: 'shadow'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
|
|
left: '3%',
|
|
|
|
|
|
|
|
right: '4%',
|
|
|
|
|
|
|
|
bottom: '10%', // 标签多时增加底部边距
|
|
|
|
|
|
|
|
containLabel: true
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
xAxis: laneInventoryStatistics.value.xaxis,
|
|
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
|
|
minInterval: 1
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
series: laneInventoryStatistics.value.series
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置随行折线图选项
|
|
|
|
|
|
|
|
laneInventoryLineOptions.value = {
|
|
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
|
|
type: 'shadow'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
legend: laneInventoryLine.value.legend,
|
|
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
|
|
left: '3%',
|
|
|
|
|
|
|
|
right: '4%',
|
|
|
|
|
|
|
|
bottom: '3%',
|
|
|
|
|
|
|
|
containLabel: true
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
xAxis: laneInventoryLine.value.xaxis,
|
|
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
|
|
minInterval: 1
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
series: laneInventoryLine.value.series
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取街道状态数据并更新饼状图
|
|
|
|
const loadOnlineRate = async () => {
|
|
|
|
const data = await homeApi.stockLaneInventoryStatisticsPie()
|
|
|
|
try {
|
|
|
|
stockPieOptions.series[0].data = data
|
|
|
|
const res = await DeviceLatestDataApi.getOnlineRate()
|
|
|
|
} catch (error) {
|
|
|
|
const data = (res as any)
|
|
|
|
console.error('获取数据时出错:', error)
|
|
|
|
if (data) {
|
|
|
|
|
|
|
|
onlineCount.value = data.onlineCount ?? 0
|
|
|
|
|
|
|
|
offlineCount.value = data.offlineCount ?? 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取快捷入口
|
|
|
|
const loadTemperature = async () => {
|
|
|
|
const getShortcut = async () => {
|
|
|
|
try {
|
|
|
|
const data = [
|
|
|
|
const res = await DeviceLatestDataApi.getTemperatureByZone()
|
|
|
|
{
|
|
|
|
const data = (res as any)
|
|
|
|
name: '随行记录',
|
|
|
|
const zones = data.map((d: any) => d.zone)
|
|
|
|
icon: 'ep:data-analysis',
|
|
|
|
const values = data.map((d: any) => d.value)
|
|
|
|
url: 'logistics/order',
|
|
|
|
if (tempBarOption.value.xAxis && 'data' in tempBarOption.value.xAxis) {
|
|
|
|
},
|
|
|
|
tempBarOption.value.xAxis.data = zones
|
|
|
|
{
|
|
|
|
|
|
|
|
name: '盘点管理',
|
|
|
|
|
|
|
|
icon: 'ep:coin',
|
|
|
|
|
|
|
|
url: 'logistics/check-log',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
name: '盘点信息',
|
|
|
|
|
|
|
|
icon: 'ep:pointer',
|
|
|
|
|
|
|
|
url: 'logistics/stock',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
name: '实时视频',
|
|
|
|
|
|
|
|
icon: 'ep:magic-stick',
|
|
|
|
|
|
|
|
url: 'cameraTree/cameraStreet',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
name: '巷道管理',
|
|
|
|
|
|
|
|
icon: 'fa-solid:rainbow',
|
|
|
|
|
|
|
|
url: 'system/street',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
name: '相机管理',
|
|
|
|
|
|
|
|
icon: 'ep:camera-filled',
|
|
|
|
|
|
|
|
url: 'system/camera',
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
if (tempBarOption.value.series && Array.isArray(tempBarOption.value.series)) {
|
|
|
|
shortcut = Object.assign(shortcut, data)
|
|
|
|
tempBarOption.value.series[0].data = values
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tempBarOption.value = { ...tempBarOption.value }
|
|
|
|
// ----------------------------- 数据处理 -----------------------------
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
|
|
|
|
// 构造 ECharts 数据,使用 statusString 作为 name
|
|
|
|
|
|
|
|
const echartData = (): { name: string; value: number }[] => {
|
|
|
|
|
|
|
|
return Array.from(buttonByStatusMap.value, ([name, value]) => ({ name, value }))
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------- 图表选项 -----------------------------
|
|
|
|
const loadHumidity = async () => {
|
|
|
|
|
|
|
|
try {
|
|
|
|
// 条状图数据
|
|
|
|
const res = await DeviceLatestDataApi.getHumidityByZone()
|
|
|
|
const deviceStatusChartOption = ref<EChartsOption>({})
|
|
|
|
const data = (res as any)
|
|
|
|
|
|
|
|
const zones = data.map((d: any) => d.zone)
|
|
|
|
const laneInventoryLineOptions = ref<EChartsOption>({})
|
|
|
|
const values = data.map((d: any) => d.value)
|
|
|
|
|
|
|
|
if (humidityLineOption.value.xAxis && 'data' in humidityLineOption.value.xAxis) {
|
|
|
|
const orderChartOption = ref<EChartsOption>({})
|
|
|
|
humidityLineOption.value.xAxis.data = zones
|
|
|
|
|
|
|
|
|
|
|
|
let eventSource: any = null
|
|
|
|
|
|
|
|
const sseUid = ref(`${Date.now()}-${Math.floor(Math.random() * 10000)}`)
|
|
|
|
|
|
|
|
const sse = ()=>{
|
|
|
|
|
|
|
|
// 创建一个 EventSource 实例,连接到后端的 SSE 路径
|
|
|
|
|
|
|
|
eventSource = new EventSource(window.location.protocol + '//' + window.location.hostname+":"+import.meta.env.VITE_BASE_PORT+'/app-api/sse/createSse?uid=' + sseUid.value );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 监听来自后端的消息
|
|
|
|
|
|
|
|
eventSource.onmessage = (event) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log(event.data);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
realTimeLogs.value.push(event.data)
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 处理错误
|
|
|
|
|
|
|
|
eventSource.onerror = (error) => {
|
|
|
|
|
|
|
|
console.error('SSE 连接发生错误:', error);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (humidityLineOption.value.series && Array.isArray(humidityLineOption.value.series)) {
|
|
|
|
const colorListHex = {'未盘点':'#ebc26e','盘点异常':'#ed6d6d','盘点正确':'#6cc238','人工核对正确':'#5061c2'};
|
|
|
|
humidityLineOption.value.series[0].data = values
|
|
|
|
// 盘点饼状图选项
|
|
|
|
|
|
|
|
const stockPieOptions = reactive({
|
|
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
|
|
trigger: 'item'
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
|
|
top: '4%',
|
|
|
|
|
|
|
|
left: 'center'
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
series: [
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
|
|
radius: ['40%', '70%'],
|
|
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
|
|
borderRadius: 10,
|
|
|
|
|
|
|
|
borderWidth: 2,
|
|
|
|
|
|
|
|
color: function (params) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 params.dataIndex 获取当前数据项的索引
|
|
|
|
|
|
|
|
return colorListHex[params.name];
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
humidityLineOption.value = { ...humidityLineOption.value }
|
|
|
|
label: {
|
|
|
|
} catch {}
|
|
|
|
show: false,
|
|
|
|
|
|
|
|
position: 'center'
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
|
|
label: {
|
|
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
|
|
fontSize: 20,
|
|
|
|
|
|
|
|
fontWeight: 'bold'
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
labelLine: {
|
|
|
|
|
|
|
|
show: false
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data: []
|
|
|
|
const loadAlarmRecords = async () => {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const res = await AlarmRecordsApi.getAlarmRecordsPage({ pageNo: 1, pageSize: 7 })
|
|
|
|
|
|
|
|
const list = res.list ?? []
|
|
|
|
|
|
|
|
alarmRecords.value = list.map((item: any) => ({
|
|
|
|
|
|
|
|
time: formatShortTime(item.triggerTime),
|
|
|
|
|
|
|
|
area: item.deviceName?.slice(0, 2) || '-',
|
|
|
|
|
|
|
|
type: item.ruleName?.slice(0, 2) || '-',
|
|
|
|
|
|
|
|
message: item.ruleName || '-',
|
|
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
} catch {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------- 快捷方式处理 -----------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 快捷方式处理
|
|
|
|
const formatShortTime = (ts: any) => {
|
|
|
|
const handleShortcut = (action: string) => {
|
|
|
|
if (!ts) return '-'
|
|
|
|
switch (action) {
|
|
|
|
const d = new Date(ts)
|
|
|
|
case 'setting':
|
|
|
|
return `${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`
|
|
|
|
ElMessage.info('打开设置')
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
case 'help':
|
|
|
|
|
|
|
|
ElMessageBox.alert('帮助信息', '帮助', {
|
|
|
|
|
|
|
|
confirmButtonText: '确定'
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
case 'logout':
|
|
|
|
|
|
|
|
ElMessageBox.confirm('确认退出?', '提示', {
|
|
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.then(() => {
|
|
|
|
|
|
|
|
ElMessage.success('退出成功')
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
|
|
ElMessage.info('已取消')
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
ElMessage.warning('未知操作')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------- 实时滚动信息 -----------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 启动日志定时器
|
|
|
|
|
|
|
|
// const startLogTimer = () => {
|
|
|
|
|
|
|
|
// logTimer = setInterval(() => {
|
|
|
|
|
|
|
|
// const newLog = `日志 ${realTimeLogs.value.length + 1}: ${new Date().toLocaleTimeString()}`
|
|
|
|
|
|
|
|
// realTimeLogs.value.push(newLog)
|
|
|
|
|
|
|
|
// if (realTimeLogs.value.length > 10) {
|
|
|
|
|
|
|
|
// realTimeLogs.value.shift()
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// }, 2000)
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// // 清除日志定时器
|
|
|
|
|
|
|
|
// const clearLogTimer = () => {
|
|
|
|
|
|
|
|
// if (logTimer) {
|
|
|
|
|
|
|
|
// clearInterval(logTimer)
|
|
|
|
|
|
|
|
// logTimer = null
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------- 生命周期钩子 -----------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
onMounted(() => {
|
|
|
|
getShortcut()
|
|
|
|
updateTime()
|
|
|
|
getAll() // 获取所有数据
|
|
|
|
timer = setInterval(updateTime, 60000)
|
|
|
|
// 获取相机列表
|
|
|
|
initCharts()
|
|
|
|
CameraApi.getCameraList().then((res) => {
|
|
|
|
loadOnlineRate()
|
|
|
|
cameraList.value = res
|
|
|
|
loadTemperature()
|
|
|
|
})
|
|
|
|
loadHumidity()
|
|
|
|
sse()
|
|
|
|
loadAlarmRecords()
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
|
|
// 在组件销毁时关闭 EventSource 连接
|
|
|
|
|
|
|
|
if (eventSource) {
|
|
|
|
|
|
|
|
eventSource.close();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
onUnmounted(() => { if (timer) clearInterval(timer) })
|
|
|
|
</script>
|
|
|
|
</script>
|
|
|
|
<style scoped>
|
|
|
|
|
|
|
|
.body{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
background-image:url(/public/pic02.jpg) ;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
background-size:cover;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
|
|
background: url('/public/pic01.png') no-repeat center;
|
|
|
|
|
|
|
|
background-size: cover;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.colour-text {
|
|
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
|
|
margin:10px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.card-header::v-deep .el-card__header {
|
|
|
|
|
|
|
|
border-bottom: none !important;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.el-card {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
border-color: rgba(5, 2, 53, 0.87); /* 深蓝色透明边框 */
|
|
|
|
<style scoped>
|
|
|
|
background-color: rgba(5, 2, 53, 0.87); /* 深蓝色透明背景 */
|
|
|
|
.dashboard {
|
|
|
|
|
|
|
|
background: url('/src/assets/imgs/home/background.png') no-repeat center;
|
|
|
|
|
|
|
|
background-size: 100% 100%;
|
|
|
|
|
|
|
|
background-color: #0a1628;
|
|
|
|
|
|
|
|
min-height: 85vh; padding: 4px 8px;
|
|
|
|
|
|
|
|
color: #0affff; font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.log-item {
|
|
|
|
.dashboard-header {
|
|
|
|
padding: 5px 0;
|
|
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
|
|
border-bottom: 1px solid #ebeef5;
|
|
|
|
height: 36px;
|
|
|
|
|
|
|
|
background: linear-gradient(90deg, transparent, rgba(10,255,255,0.08), transparent);
|
|
|
|
|
|
|
|
border-bottom: 1px solid rgba(10,255,255,0.12);
|
|
|
|
|
|
|
|
padding: 0 16px; margin-bottom: 6px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.header-left, .header-right { width: 180px; }
|
|
|
|
.log-item:last-child {
|
|
|
|
.header-time { color: #0affff; font-size: 14px; font-weight: 600; letter-spacing: 1px; }
|
|
|
|
border-bottom: none;
|
|
|
|
.header-weekday { color: rgba(136,221,255,0.7); font-size: 11px; margin-left: 6px; }
|
|
|
|
|
|
|
|
.header-title {
|
|
|
|
|
|
|
|
font-size: 18px; font-weight: 700;
|
|
|
|
|
|
|
|
background: linear-gradient(180deg, #ffffff 0%, #0affff 80%);
|
|
|
|
|
|
|
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
|
|
|
|
|
|
letter-spacing: 6px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.title-deco { color: rgba(10,255,255,0.5); font-size: 12px; margin: 0 8px; }
|
|
|
|
.mt-20px {
|
|
|
|
.main-row { height: calc(82vh); }
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
.left-col { display: flex; flex-direction: column; gap: 8px; height: 100%; }
|
|
|
|
|
|
|
|
.left-col .glass-panel { flex: 1; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.right-area { display: flex; flex-direction: column; height: 100%; gap: 8px; }
|
|
|
|
|
|
|
|
.bg-section { flex: 2; overflow: hidden; }
|
|
|
|
|
|
|
|
.bottom-row { display: flex; gap: 8px; flex-shrink: 0; height: 35%; }
|
|
|
|
|
|
|
|
.bottom-left { flex: 3; overflow: hidden; }
|
|
|
|
|
|
|
|
.bottom-left .glass-panel { height: 100%; }
|
|
|
|
|
|
|
|
.bottom-right { width: 24%; overflow: hidden; }
|
|
|
|
|
|
|
|
.bottom-right .glass-panel { height: 100%; display: flex; align-items: center; justify-content: center; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.glass-panel {
|
|
|
|
|
|
|
|
background: linear-gradient(135deg, rgba(10,30,60,0.4) 0%, rgba(10,30,60,0.08) 100%);
|
|
|
|
|
|
|
|
border: 1px solid rgba(10,255,255,0.12);
|
|
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
|
|
|
display: flex; flex-direction: column;
|
|
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
backdrop-filter: blur(8px);
|
|
|
|
|
|
|
|
-webkit-backdrop-filter: blur(8px);
|
|
|
|
|
|
|
|
box-shadow: 0 0 15px rgba(10,255,255,0.05) inset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mb-20px {
|
|
|
|
.card-title {
|
|
|
|
margin-bottom: 30px;
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
|
|
background: linear-gradient(90deg, #ffffff 0%, #0affff 60%, rgba(10,255,255,0.6) 100%);
|
|
|
|
|
|
|
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
|
|
|
|
|
|
font-weight: 700; letter-spacing: 3px; font-style: italic;
|
|
|
|
|
|
|
|
padding: 2px 0 3px 10px; margin: -8px -12px 4px -12px; flex-shrink: 0;
|
|
|
|
|
|
|
|
display: flex; align-items: center; gap: 6px;
|
|
|
|
|
|
|
|
text-shadow: 0 0 10px rgba(10,255,255,0.3);
|
|
|
|
|
|
|
|
border-bottom: 1px solid rgba(10,255,255,0.12);
|
|
|
|
|
|
|
|
position: relative;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.card-title::before {
|
|
|
|
.box-card {
|
|
|
|
content: ''; position: absolute; left: 0; top: 0; bottom: 0;
|
|
|
|
|
|
|
|
width: 3px; background: linear-gradient(180deg, #0affff, transparent);
|
|
|
|
margin-bottom: 20px;
|
|
|
|
box-shadow: 0 0 6px rgba(10,255,255,0.4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.title-badge { font-size: 14px; background: none !important; -webkit-background-clip: unset !important; background-clip: unset !important; color: initial !important; }
|
|
|
|
.box-card ::v-deep .el-card__header {
|
|
|
|
.title-en { color: rgba(10,255,255,0.3); font-size: 9px; letter-spacing: 2px; font-weight: normal; margin-left: auto; font-style: normal; }
|
|
|
|
border-bottom: none !important;
|
|
|
|
.chart-wrap { flex: 1; min-height: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.rate-content { display: flex; align-items: center; gap: 6px; flex: 1; padding: 4px; }
|
|
|
|
|
|
|
|
.ring-wrap { width: 60%; height: 60%; aspect-ratio: 1; position: relative; flex-shrink: 0; }
|
|
|
|
|
|
|
|
.rate-ring-svg { width: 100%; height: 100%; transform: rotate(-90deg); }
|
|
|
|
|
|
|
|
.ring-bg { fill: none; stroke: rgba(10,255,255,0.08); stroke-width: 6; }
|
|
|
|
|
|
|
|
.ring-fg { fill: none; stroke: #0affff; stroke-width: 6; filter: drop-shadow(0 0 4px rgba(10,255,255,0.5)); }
|
|
|
|
|
|
|
|
.ring-glow { fill: none; stroke: rgba(10,255,255,0.12); stroke-width: 10; }
|
|
|
|
|
|
|
|
.ring-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
|
|
|
|
|
|
|
|
.ring-pct { color: #0affff; font-size: 16px; font-weight: 700; text-shadow: 0 0 6px rgba(10,255,255,0.4); }
|
|
|
|
|
|
|
|
.rate-detail { flex: 1; }
|
|
|
|
|
|
|
|
.rate-item { color: rgba(136,221,255,0.7); font-size: 12px; margin-bottom: 4px; display: flex; align-items: center; gap: 4px; }
|
|
|
|
|
|
|
|
.rate-item b { color: #0affff; margin-left: auto; font-size: 14px; }
|
|
|
|
|
|
|
|
.rate-label { flex: 1; }
|
|
|
|
|
|
|
|
.rate-total { border-top: 1px solid rgba(10,255,255,0.08); padding-top: 3px; margin-top: 3px; }
|
|
|
|
|
|
|
|
.dot { display: inline-block; width: 5px; height: 5px; border-radius: 50%; }
|
|
|
|
|
|
|
|
.dot-online { background: #0affff; box-shadow: 0 0 4px rgba(10,255,255,0.6); }
|
|
|
|
|
|
|
|
.dot-offline { background: #ffa500; box-shadow: 0 0 4px rgba(255,165,0,0.6); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.nav-bar { display: flex; flex-direction: column; align-items: center; gap: 14px; width: 100%; padding: 8px; }
|
|
|
|
|
|
|
|
.nav-btn {
|
|
|
|
|
|
|
|
background: linear-gradient(135deg, rgba(10,255,255,0.1), rgba(10,255,255,0.03)) !important;
|
|
|
|
|
|
|
|
border: 1px solid rgba(10,255,255,0.2) !important;
|
|
|
|
|
|
|
|
color: #0affff !important;
|
|
|
|
|
|
|
|
border-radius: 8px !important;
|
|
|
|
|
|
|
|
padding: 10px 16px !important;
|
|
|
|
|
|
|
|
font-size: 13px !important;
|
|
|
|
|
|
|
|
letter-spacing: 1px;
|
|
|
|
|
|
|
|
width: 90%;
|
|
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.nav-bar a { text-decoration: none; display: block; width: 90%; }
|
|
|
|
.box-card ::v-deep .el-card__body {
|
|
|
|
.nav-btn:hover { background: rgba(10,255,255,0.2) !important; border-color: #0affff !important; box-shadow: 0 0 20px rgba(10,255,255,0.15); transform: scale(1.02); }
|
|
|
|
background-color: #070225;
|
|
|
|
.nav-icon { margin-right: 4px; font-size: 14px; }
|
|
|
|
color: #7d6ea3ee; /* 接近黑色*/
|
|
|
|
|
|
|
|
}
|
|
|
|
.dark-table { background: transparent !important; }
|
|
|
|
|
|
|
|
.dark-table :deep(.el-table__row), .dark-table :deep(tr) { background: transparent !important; }
|
|
|
|
.white-text-icon, .el-icon {
|
|
|
|
.dark-table :deep(td) { background: transparent !important; color: rgba(136,221,255,0.7) !important; border-bottom: 1px solid rgba(10,255,255,0.04) !important; font-size: 11px !important; padding: 3px 0 !important; }
|
|
|
|
color: white !important;
|
|
|
|
.dark-table :deep(th.el-table__cell) { background: transparent !important; color: #0affff !important; border-bottom: 1px solid rgba(10,255,255,0.08) !important; font-size: 11px !important; padding: 3px 0 !important; }
|
|
|
|
}
|
|
|
|
.dark-table :deep(.el-table__inner-wrapper::before) { background: transparent !important; }
|
|
|
|
|
|
|
|
.dark-table :deep(.el-table--striped .el-table__body tr.el-table__row--striped td) { background: rgba(10,255,255,0.02) !important; }
|
|
|
|
/*.box-card { box-shadow: 0 0 10px rgba(0, 0, 255, 0.5); } */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.box-card { border-radius: 10px; } /* 加圆角*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</style>
|
|
|
|
</style>
|