| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 |
- <!--窨井盖监测首页大屏-->
- <template>
- <div class="monitor-container">
- <!-- 左侧栏 -->
- <div class="left-col">
- <!-- 待办工单 -->
- <div class="panel">
- <h3 class="panel-title">待办工单 <span class="title-count">{{ workOrders.length }} 条</span></h3>
- <el-table :data="workOrders" class="panel-table" :header-cell-style="tableHeaderStyle" size="small" v-loading="loading.work">
- <el-table-column prop="orderNo" label="工单编号" min-width="120" show-overflow-tooltip />
- <el-table-column prop="deviceCode" label="设备" min-width="100" show-overflow-tooltip />
- <el-table-column label="优先级" width="70">
- <template #default="{ row }">
- <span :class="['priority-tag', priorityClass(row.orderLevel)]">{{ priorityText(row.orderLevel) }}</span>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <!-- 告警类型统计(15天内) -->
- <div class="panel">
- <h3 class="panel-title">告警类型统计(15天内)</h3>
- <div ref="alarmChartRef" class="chart" v-loading="loading.alarm" />
- </div>
- <!-- 设备区域信息 -->
- <div class="panel">
- <h3 class="panel-title">设备区域信息</h3>
- <div ref="areaChartRef" class="chart" v-loading="loading.device" />
- </div>
- </div>
- <!-- 中间地图区域 -->
- <div class="center-col">
- <div class="panel map-panel">
- <div class="map-header">
- <span>窨井设备分布</span>
- <div class="map-legend">
- <span><i class="legend-dot online"></i> 在线 {{ stats.online }}</span>
- <span><i class="legend-dot alert"></i> 告警 {{ stats.alarm }}</span>
- <span><i class="legend-dot offline"></i> 离线 {{ stats.offline }}</span>
- </div>
- </div>
- <div id="homeMap" class="bmap-container"></div>
- </div>
- <!-- 设备分类 -->
- <div class="panel chart-panel">
- <h3 class="panel-title">设备分类</h3>
- <div ref="deviceTypeChartRef" class="chart" v-loading="loading.device" />
- </div>
- </div>
- <!-- 右侧栏 -->
- <div class="right-col">
- <!-- 设备列表 -->
- <div class="panel">
- <h3 class="panel-title">设备列表 <span class="title-count">共 {{ filteredDevices.length }} 台</span></h3>
- <div class="filter-bar">
- <el-select v-model="deviceTypeFilter" size="small" placeholder="设备类型" clearable style="width: 120px">
- <el-option label="供水窨井" value="5" />
- <el-option label="排水窨井" value="9" />
- <el-option label="燃气窨井" value="10" />
- </el-select>
- </div>
- <el-table :data="filteredDevices" class="panel-table" :header-cell-style="tableHeaderStyle" size="small" v-loading="loading.device">
- <el-table-column prop="name" label="设备名称" min-width="100" show-overflow-tooltip />
- <el-table-column label="类型" width="70">
- <template #default="{ row }">{{ typeText(row.typeId) }}</template>
- </el-table-column>
- <el-table-column label="电量" width="60">
- <template #default="{ row }">
- <el-tag :type="batteryTag(row.battery)" size="small">{{ row.battery ?? '-' }}%</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="状态" width="60">
- <template #default="{ row }">
- <span :class="['status-dot', row.status]"></span>
- <span class="status-text">{{ statusText(row.status) }}</span>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <!-- 动态信息 -->
- <div class="panel">
- <h3 class="panel-title">动态信息</h3>
- <el-table :data="dynamicLogs" class="panel-table" :header-cell-style="tableHeaderStyle" size="small" v-loading="loading.alarm">
- <el-table-column prop="time" label="时间" width="140" />
- <el-table-column prop="event" label="事件" min-width="120" show-overflow-tooltip />
- <el-table-column label="状态" width="70">
- <template #default="{ row }">
- <span :class="['status-tag', row.alarmStatus === 1 ? 'done' : 'processing']">{{ row.alarmStatus === 1 ? '已处理' : '未处理' }}</span>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, onMounted, onBeforeUnmount, nextTick, computed } from 'vue'
- import * as echarts from 'echarts'
- import { getManholeLayerData } from '@/api/pipeNetwork/basic'
- import { listWorkOrder } from '@/api/pipeNetwork/ops/workOrder.js'
- import { ORDER_STATUS_GROUP } from '@/views/subSystem/pipeNetwork/ops/workOrder/workOrderDict.js'
- import { getAlarmDataPage } from '@/api/drainage'
- import { ElMessage } from 'element-plus'
- import locationIcon from '@/assets/images/location.png'
- // ==================== 状态 ====================
- const loading = reactive({ work: false, alarm: false, device: false })
- const stats = reactive({ total: 0, online: 0, offline: 0, alarm: 0 })
- const workOrders = ref([])
- const allDevices = ref([])
- const dynamicLogs = ref([])
- const deviceTypeFilter = ref('')
- // 图表引用
- const alarmChartRef = ref(null)
- const areaChartRef = ref(null)
- const deviceTypeChartRef = ref(null)
- let alarmChart = null, areaChart = null, deviceTypeChart = null
- // ==================== 常量映射 ====================
- const TYPE_MAP = { '5': '供水窨井', '9': '排水窨井', '10': '燃气窨井' }
- // 所属片区:后端 DistrictEnum(0-城南 1-城北 2-太常)
- const DISTRICT_MAP = { 0: '城南', 1: '城北', 2: '太常' }
- function districtLabel(code) {
- return code == null ? '' : (DISTRICT_MAP[code] || String(code))
- }
- const tableHeaderStyle = { background: 'rgba(0,100,200,0.3)', color: '#fff', borderColor: '#2b4b8c' }
- const priorityText = (l) => ({ 0: '紧急', 1: '一般', 2: '低' }[l] || '-')
- const priorityClass = (l) => ({ 0: 'high', 1: 'medium', 2: 'low' }[l] || 'low')
- /** 待办工单口径:待派单/待接单/已接单/处理中/延期中(枚举派生,见 workOrderDict.js) */
- const TODO_ORDER_STATUS = ORDER_STATUS_GROUP.todo.join(',')
- /** 设备父类别ID(equipment_type.parent_id),与工单列表页保持同一口径 */
- const DEVICE_TYPE_PARENT_ID = '4'
- const typeText = (id) => TYPE_MAP[id] || '-'
- const statusText = (s) => ({ normal: '在线', alert: '告警', offline: '离线' }[s] || s)
- const batteryTag = (b) => {
- if (b == null) return 'info'
- if (Number(b) <= 20) return 'danger'
- if (Number(b) <= 50) return 'warning'
- return 'success'
- }
- // ==================== 筛选后设备列表 ====================
- const filteredDevices = computed(() => {
- if (!deviceTypeFilter.value) return allDevices.value
- return allDevices.value.filter(d => d.typeId === deviceTypeFilter.value)
- })
- // ==================== 数据加载 ====================
- async function loadAll() {
- await Promise.allSettled([loadDevices(), loadWorkOrders(), loadAlarmData()])
- await nextTick()
- handleResize()
- }
- async function loadDevices() {
- loading.device = true
- try {
- const res = await getManholeLayerData({ isQueryManholeData: true })
- const devices = res.data?.devices || []
- allDevices.value = devices.map(d => {
- const onlineStatus = Number(d.equipmentStatus?.onlineStatus)
- const alarmStatus = Number(d.equipmentStatus?.alarmStatus)
- const manholeAlarm = Number(d.manholeData?.alarmStatus)
- const isAlert = alarmStatus === 1 || manholeAlarm === 1 ||
- Number(d.manholeData?.waterInfiltrationAlarmStatus) === 1 ||
- Number(d.manholeData?.waterLevelAlarmStatus) === 1
- let status = 'offline'
- if (isAlert) status = 'alert'
- else if (onlineStatus === 1) status = 'normal'
- return {
- id: d.equipmentId, code: d.equipmentCode, name: d.equipmentName,
- typeId: d.equipmentTypeId, typeName: d.equipmentTypeName,
- location: d.equipmentLocation, district: districtLabel(d.district),
- lng: d.longitude != null ? Number(d.longitude) : null,
- lat: d.latitude != null ? Number(d.latitude) : null,
- battery: d.manholeData?.batteryLevel ?? null,
- status
- }
- })
- const statistics = res.data?.statistics || {}
- stats.total = statistics.total || allDevices.value.length
- stats.online = statistics.online || 0
- stats.offline = statistics.offline || 0
- stats.alarm = statistics.alarm || 0
- renderAreaChart()
- renderDeviceTypeChart()
- refreshMarkers()
- } catch (e) {
- ElMessage.error('设备数据加载失败')
- } finally {
- loading.device = false
- }
- }
- async function loadWorkOrders() {
- loading.work = true
- try {
- const res = await listWorkOrder({
- pageNum: 1,
- pageSize: 20,
- orderStatus: TODO_ORDER_STATUS,
- deviceTypeParentId: DEVICE_TYPE_PARENT_ID
- })
- workOrders.value = res.data?.records || []
- } catch (e) {
- ElMessage.error('工单数据加载失败')
- } finally {
- loading.work = false
- }
- }
- async function loadAlarmData() {
- loading.alarm = true
- try {
- const res = await getAlarmDataPage(1, 500, {})
- const records = res.rows || res.data?.records || res.data || []
- const now = new Date()
- const fifteenDaysAgo = new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000)
- // 筛选15天内的告警
- const recentAlarms = records.filter(r => {
- const t = new Date(r.alarmTime || r.createTime)
- return t >= fifteenDaysAgo
- })
- // 告警类型统计
- const typeMap = {}
- recentAlarms.forEach(r => {
- const type = r.warningType || '其他'
- typeMap[type] = (typeMap[type] || 0) + 1
- })
- renderAlarmChart(typeMap)
- // 动态信息(最新10条)
- dynamicLogs.value = records.slice(0, 10).map(r => ({
- time: r.alarmTime || r.createTime || '',
- event: `${r.warningType || '告警'} - ${r.deviceName || r.deviceCode || ''}`,
- alarmStatus: r.alarmStatus ?? 0
- }))
- } catch (e) {
- ElMessage.error('告警数据加载失败')
- } finally {
- loading.alarm = false
- }
- }
- // ==================== ECharts ====================
- function renderAlarmChart(typeMap) {
- if (!alarmChartRef.value) return
- if (!alarmChart) alarmChart = echarts.init(alarmChartRef.value)
- const colors = ['#ff3333', '#ffb333', '#33cc33', '#3399ff', '#9966ff', '#ff6699', '#33cccc']
- const data = Object.entries(typeMap).map(([name, value], i) => ({
- name, value, itemStyle: { color: colors[i % colors.length] }
- }))
- alarmChart.setOption({
- backgroundColor: 'transparent',
- tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
- legend: { bottom: 0, textStyle: { color: '#ccc', fontSize: 10 } },
- series: [{
- type: 'pie', radius: ['35%', '60%'], center: ['50%', '40%'],
- label: { color: '#ccc', fontSize: 10 },
- data: data.length ? data : [{ name: '暂无数据', value: 0 }]
- }]
- })
- }
- function renderAreaChart() {
- if (!areaChartRef.value) return
- if (!areaChart) areaChart = echarts.init(areaChartRef.value)
- const areaMap = {}
- allDevices.value.forEach(d => {
- const area = d.district || d.location || '未分配区域'
- // 取区域名(截取前4个字)
- const shortArea = area.length > 6 ? area.slice(0, 6) + '...' : area
- areaMap[shortArea] = (areaMap[shortArea] || 0) + 1
- })
- const entries = Object.entries(areaMap).sort((a, b) => b[1] - a[1]).slice(0, 8)
- areaChart.setOption({
- backgroundColor: 'transparent',
- tooltip: { trigger: 'axis' },
- grid: { left: '12%', right: '8%', bottom: '18%', top: '8%' },
- xAxis: {
- type: 'category', data: entries.map(e => e[0]),
- axisLabel: { color: '#ccc', fontSize: 10, interval: 0, rotate: 30 }
- },
- yAxis: { type: 'value', axisLabel: { color: '#ccc' }, splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } } },
- series: [{ type: 'bar', data: entries.map(e => e[1]), itemStyle: { color: '#3399ff' } }]
- })
- }
- function renderDeviceTypeChart() {
- if (!deviceTypeChartRef.value) return
- if (!deviceTypeChart) deviceTypeChart = echarts.init(deviceTypeChartRef.value)
- const typeCount = { '5': 0, '9': 0, '10': 0 }
- allDevices.value.forEach(d => { if (typeCount[d.typeId] != null) typeCount[d.typeId]++ })
- const labels = ['供水窨井', '排水窨井', '燃气窨井']
- const values = [typeCount['5'], typeCount['9'], typeCount['10']]
- const colors = ['#3399ff', '#67c23a', '#e6a23c']
- deviceTypeChart.setOption({
- backgroundColor: 'transparent',
- tooltip: { trigger: 'axis' },
- grid: { left: '12%', right: '8%', bottom: '15%', top: '8%' },
- xAxis: {
- type: 'category', data: labels,
- axisLabel: { color: '#ccc', fontSize: 11, interval: 0 }
- },
- yAxis: { type: 'value', axisLabel: { color: '#ccc' }, splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } } },
- series: [{
- type: 'bar', data: values.map((v, i) => ({ value: v, itemStyle: { color: colors[i] } })),
- barWidth: '40%', label: { show: true, position: 'top', color: '#ccc' }
- }]
- })
- }
- // ==================== 百度地图 ====================
- let mapInstance = null
- const markerMap = new Map()
- const YUANLING_CENTER = { lng: 110.393, lat: 28.452 }
- function initMap() {
- const container = document.getElementById('homeMap')
- if (!container || typeof BMapGL === 'undefined') return
- try {
- mapInstance = new BMapGL.Map('homeMap')
- mapInstance.centerAndZoom(new BMapGL.Point(YUANLING_CENTER.lng, YUANLING_CENTER.lat), 13)
- mapInstance.enableScrollWheelZoom(true)
- } catch (e) { console.error('地图初始化失败', e) }
- }
- function getStatusColor(status) {
- if (status === 'alert') return '#F56C6C'
- if (status === 'offline') return '#909399'
- return '#67C23A'
- }
- function refreshMarkers() {
- if (!mapInstance) return
- markerMap.forEach(label => mapInstance.removeOverlay(label))
- markerMap.clear()
- allDevices.value.forEach(d => {
- if (d.lng == null || d.lat == null) return
- const color = getStatusColor(d.status)
- const html = `<div style="text-align:center;cursor:pointer;">
- <div style="color:#fff;background:${color};border-radius:3px;padding:1px 6px;font-size:10px;white-space:nowrap;display:inline-block;margin-bottom:2px;box-shadow:0 1px 3px rgba(0,0,0,0.3);">${d.name}</div>
- <div><img src="${locationIcon}" style="width:22px;height:22px;display:block;margin:0 auto;"/></div>
- </div>`
- const label = new BMapGL.Label(html, {
- position: new BMapGL.Point(d.lng, d.lat),
- offset: new BMapGL.Size(-25, -40)
- })
- label.setStyle({ border: 'none', background: 'transparent', padding: '0', zIndex: '10' })
- mapInstance.addOverlay(label)
- markerMap.set(d.id, label)
- })
- }
- // ==================== 生命周期 ====================
- const handleResize = () => {
- alarmChart && alarmChart.resize()
- areaChart && areaChart.resize()
- deviceTypeChart && deviceTypeChart.resize()
- mapInstance && mapInstance.resize && mapInstance.resize()
- }
- onMounted(async () => {
- await nextTick()
- initMap()
- window.addEventListener('resize', handleResize)
- loadAll()
- })
- onBeforeUnmount(() => {
- window.removeEventListener('resize', handleResize)
- alarmChart && alarmChart.dispose()
- areaChart && areaChart.dispose()
- deviceTypeChart && deviceTypeChart.dispose()
- if (mapInstance) {
- mapInstance.clearOverlays && mapInstance.clearOverlays()
- mapInstance = null
- }
- markerMap.clear()
- })
- </script>
- <!-- ==================== 全局样式(非 scoped):el-table 深色主题覆盖 ==================== -->
- <!-- 用 .monitor-container 限定范围,不影响其他页面 -->
- <style>
- .monitor-container .el-table {
- --el-table-bg-color: transparent;
- --el-table-tr-bg-color: transparent;
- --el-table-header-bg-color: transparent;
- --el-table-current-row-bg-color: rgba(0, 100, 200, 0.25);
- --el-table-row-hover-bg-color: rgba(0, 100, 200, 0.15);
- --el-table-text-color: #dce3ee;
- --el-table-header-text-color: #fff;
- --el-table-border-color: rgba(43, 75, 140, 0.35);
- --el-table-border: 1px solid rgba(43, 75, 140, 0.35);
- --el-fill-color-lighter: rgba(30, 40, 90, 0.25);
- --el-fill-color-light: rgba(30, 40, 90, 0.25);
- --el-fill-color: rgba(30, 40, 90, 0.25);
- --el-fill-color-blank: transparent;
- --el-text-color-regular: #dce3ee;
- --el-text-color-secondary: #9ba8c0;
- --el-border-color: rgba(43, 75, 140, 0.35);
- --el-border-color-lighter: rgba(43, 75, 140, 0.2);
- background: transparent !important;
- border: none !important;
- }
- /* 隐藏 EP 表格自带的底部/右侧边框伪元素 */
- .monitor-container .el-table::before,
- .monitor-container .el-table::after,
- .monitor-container .el-table .el-table__inner-wrapper::before,
- .monitor-container .el-table .el-table__inner-wrapper::after {
- display: none !important;
- }
- /* 内部容器全透明 */
- .monitor-container .el-table .el-table__inner-wrapper,
- .monitor-container .el-table .el-table__body-wrapper,
- .monitor-container .el-table .el-table__header-wrapper,
- .monitor-container .el-table .el-table__body,
- .monitor-container .el-table .el-table__header {
- background: transparent !important;
- }
- /* 表头单元格 — EP 2.x 使用 th.el-table__cell */
- .monitor-container .el-table th.el-table__cell {
- background: rgba(0, 100, 200, 0.35) !important;
- color: #fff !important;
- font-weight: 600;
- border-bottom: 1px solid #2b4b8c !important;
- }
- /* 内容单元格 */
- .monitor-container .el-table td.el-table__cell {
- background: transparent !important;
- border-bottom: 1px solid rgba(43, 75, 140, 0.25) !important;
- padding: 7px 0 !important;
- }
- /* 行 */
- .monitor-container .el-table .el-table__row {
- background: transparent !important;
- }
- .monitor-container .el-table .el-table__row:hover > td.el-table__cell {
- background: rgba(0, 100, 200, 0.15) !important;
- }
- /* 空数据 */
- .monitor-container .el-table .el-table__empty-block {
- background: transparent !important;
- }
- .monitor-container .el-table .el-table__empty-text {
- color: #8090a8;
- }
- /* 滚动条 */
- .monitor-container .el-table .el-scrollbar__bar {
- opacity: 0.4;
- }
- /* el-select 输入框深色适配 */
- .monitor-container .el-select .el-input__wrapper {
- background: rgba(30, 40, 90, 0.6);
- box-shadow: 0 0 0 1px rgba(43, 75, 140, 0.5) inset;
- }
- .monitor-container .el-select .el-input__inner {
- color: #dce3ee;
- }
- /* el-table 在 flex 容器中撑满高度(无 max-height 时) */
- .monitor-container .el-table {
- height: 100% !important;
- display: flex;
- flex-direction: column;
- }
- .monitor-container .el-table .el-table__inner-wrapper {
- flex: 1;
- min-height: 0;
- display: flex;
- flex-direction: column;
- }
- .monitor-container .el-table .el-table__body-wrapper {
- flex: 1;
- min-height: 0;
- }
- </style>
- <!-- ==================== 页面布局样式(scoped) ==================== -->
- <style scoped>
- .monitor-container {
- display: flex;
- width: 100%;
- height: 100vh;
- min-height: 600px;
- background: #1a1a4a;
- color: #fff;
- gap: 16px;
- padding: 16px;
- box-sizing: border-box;
- overflow: hidden;
- }
- .left-col {
- flex: 0 0 25%;
- min-width: 280px;
- max-width: 420px;
- display: flex;
- flex-direction: column;
- gap: 16px;
- min-height: 0;
- }
- .right-col {
- flex: 0 0 25%;
- min-width: 280px;
- max-width: 420px;
- display: flex;
- flex-direction: column;
- gap: 16px;
- min-height: 0;
- }
- .center-col {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 16px;
- min-height: 0;
- }
- .panel {
- flex: 1;
- background: rgba(30, 40, 90, 0.8);
- border: 1px solid #2b4b8c;
- border-radius: 8px;
- padding: 14px;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- min-height: 0;
- }
- /* 设备分类固定高度,不参与均分 */
- .chart-panel {
- flex: 0 0 190px;
- }
- .panel-title {
- margin: 0 0 10px 0;
- font-size: 14px;
- color: #66ccff;
- border-left: 3px solid #66ccff;
- padding-left: 8px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- flex-shrink: 0;
- }
- .title-count {
- font-size: 12px;
- color: #aaa;
- font-weight: normal;
- }
- .panel-table {
- width: 100%;
- flex: 1;
- min-height: 0;
- overflow: hidden;
- }
- .chart {
- width: 100%;
- flex: 1;
- min-height: 100px;
- }
- .map-panel {
- flex: 1;
- display: flex;
- flex-direction: column;
- min-height: 0;
- }
- .map-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- font-size: 14px;
- color: #66ccff;
- flex-shrink: 0;
- }
- .map-legend {
- display: flex;
- gap: 12px;
- font-size: 11px;
- }
- .legend-dot {
- display: inline-block;
- width: 8px;
- height: 8px;
- border-radius: 50%;
- margin-right: 3px;
- }
- .legend-dot.online { background: #67c23a; }
- .legend-dot.alert { background: #f56c6c; }
- .legend-dot.offline { background: #909399; }
- .bmap-container {
- width: 100%;
- flex: 1;
- min-height: 200px;
- border-radius: 4px;
- overflow: hidden;
- }
- .filter-bar {
- margin-bottom: 10px;
- flex-shrink: 0;
- }
- .priority-tag {
- padding: 1px 6px;
- border-radius: 3px;
- font-size: 11px;
- color: #fff;
- }
- .priority-tag.high { background: #ff3333; }
- .priority-tag.medium { background: #ffb333; color: #333; }
- .priority-tag.low { background: #33cc33; }
- .status-tag {
- padding: 1px 6px;
- border-radius: 3px;
- font-size: 11px;
- color: #fff;
- }
- .status-tag.processing { background: #3399ff; }
- .status-tag.done { background: #33cc33; }
- .status-dot {
- display: inline-block;
- width: 8px;
- height: 8px;
- border-radius: 50%;
- margin-right: 3px;
- }
- .status-dot.normal { background: #67c23a; }
- .status-dot.alert { background: #f56c6c; }
- .status-dot.offline { background: #909399; }
- .status-text {
- font-size: 11px;
- color: #dce3ee;
- }
- /* === 响应式 === */
- @media (max-width: 1600px) {
- .left-col, .right-col { flex: 0 0 24%; min-width: 260px; max-width: 380px; }
- .monitor-container { gap: 12px; padding: 12px; }
- .panel { padding: 12px; }
- .chart-panel { flex-basis: 170px; }
- }
- @media (max-width: 1366px) {
- .left-col, .right-col { flex: 0 0 27%; min-width: 240px; max-width: 340px; }
- .monitor-container { gap: 10px; padding: 10px; }
- .panel { padding: 10px; }
- .chart-panel { flex-basis: 150px; }
- }
- @media (max-width: 1024px) {
- .monitor-container {
- flex-direction: column;
- height: auto;
- min-height: 100vh;
- overflow: auto;
- }
- .left-col, .right-col {
- flex: 1 1 auto;
- min-width: 100%;
- max-width: 100%;
- }
- .center-col {
- flex: 1 1 auto;
- min-width: 100%;
- }
- }
- </style>
|