|
@@ -20,7 +20,6 @@
|
|
|
|
|
|
|
|
<!-- A 指挥台 -->
|
|
<!-- A 指挥台 -->
|
|
|
<section class="home-section">
|
|
<section class="home-section">
|
|
|
- <div class="section-heading"><el-icon><Monitor /></el-icon><span>A · 指挥台</span></div>
|
|
|
|
|
<div class="metric-grid">
|
|
<div class="metric-grid">
|
|
|
<div v-for="card in homeKpis" :key="card.key" class="metric">
|
|
<div v-for="card in homeKpis" :key="card.key" class="metric">
|
|
|
<div class="metric-icon"><el-icon><component :is="kpiIcon(card.key)"/></el-icon></div>
|
|
<div class="metric-icon"><el-icon><component :is="kpiIcon(card.key)"/></el-icon></div>
|
|
@@ -42,13 +41,14 @@
|
|
|
</template>
|
|
</template>
|
|
|
<div class="compact-list">
|
|
<div class="compact-list">
|
|
|
<button v-for="item in alarmRows" :key="item.id || item.deviceCode" type="button"
|
|
<button v-for="item in alarmRows" :key="item.id || item.deviceCode" type="button"
|
|
|
- class="compact-item" @click="goPage('/monitorAlarm/audit', { alarmId: item.id || undefined, deviceCode: item.deviceCode })">
|
|
|
|
|
|
|
+ class="compact-item" @click="openRealtimeAlarm(item)">
|
|
|
<span class="status-dot dot-alarm" />
|
|
<span class="status-dot dot-alarm" />
|
|
|
<span class="compact-main">
|
|
<span class="compact-main">
|
|
|
<b>{{ item.deviceName }}</b>
|
|
<b>{{ item.deviceName }}</b>
|
|
|
<small>{{ item.deviceCode }}</small>
|
|
<small>{{ item.deviceCode }}</small>
|
|
|
</span>
|
|
</span>
|
|
|
<span class="compact-meta">
|
|
<span class="compact-meta">
|
|
|
|
|
+ <el-tag size="small" :type="item.displayStatusType">{{ item.displayStatusText }}</el-tag>
|
|
|
<el-tag size="small" :type="levelType(item.alarmLevel)">{{ item.alarmLevelText }}</el-tag>
|
|
<el-tag size="small" :type="levelType(item.alarmLevel)">{{ item.alarmLevelText }}</el-tag>
|
|
|
<small>{{ item.alarmTime }}</small>
|
|
<small>{{ item.alarmTime }}</small>
|
|
|
</span>
|
|
</span>
|
|
@@ -63,34 +63,79 @@
|
|
|
<el-icon><MapLocation /></el-icon><span>全域态势</span>
|
|
<el-icon><MapLocation /></el-icon><span>全域态势</span>
|
|
|
<div class="map-legend">
|
|
<div class="map-legend">
|
|
|
<i class="legend-dot dot-alarm" />报警
|
|
<i class="legend-dot dot-alarm" />报警
|
|
|
|
|
+ <i class="legend-dot dot-workorder" />工单
|
|
|
|
|
+ <i class="legend-dot dot-warning" />预警
|
|
|
<i class="legend-dot dot-offline" />离线
|
|
<i class="legend-dot dot-offline" />离线
|
|
|
<i class="legend-dot dot-normal" />正常
|
|
<i class="legend-dot dot-normal" />正常
|
|
|
</div>
|
|
</div>
|
|
|
- <el-button link type="primary" class="panel-link" @click="goPage('/device/gis')">监测设备一张图</el-button>
|
|
|
|
|
|
|
+ <div class="panel-links">
|
|
|
|
|
+ <el-button link type="primary" class="panel-link" @click="goPage('/facility/gis')">设施</el-button>
|
|
|
|
|
+ <el-button link type="primary" class="panel-link" @click="goPage('/device/gis')">设备</el-button>
|
|
|
|
|
+ <el-button link type="primary" class="panel-link" @click="goPage('/monitorAlarm/map')">报警</el-button>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
<div class="map-stage">
|
|
<div class="map-stage">
|
|
|
- <div v-for="point in overviewMapPoints" :key="point.id" class="map-point"
|
|
|
|
|
- :class="`state-${point.state}`" :style="{ left: point.mapX + '%', top: point.mapY + '%' }"
|
|
|
|
|
- :title="`${point.name} · ${point.meta}`">
|
|
|
|
|
- <span />
|
|
|
|
|
- <b>{{ point.name }}</b>
|
|
|
|
|
- </div>
|
|
|
|
|
- <el-empty v-if="!overviewMapPoints.length" description="暂无可展示的设备点位" :image-size="70" />
|
|
|
|
|
- <div class="map-note">点位归一化示意 · 精确位置请进入各一张图</div>
|
|
|
|
|
|
|
+ <GisMapCanvas
|
|
|
|
|
+ ref="overviewMapRef"
|
|
|
|
|
+ :points="overviewMapPoints"
|
|
|
|
|
+ :lines="overviewMapLines"
|
|
|
|
|
+ cluster-label="供水点位"
|
|
|
|
|
+ :center="overviewMapCenter"
|
|
|
|
|
+ :zoom="overviewMapZoom"
|
|
|
|
|
+ @point-click="openOverviewPoint"
|
|
|
|
|
+ @line-click="openOverviewPoint"
|
|
|
|
|
+ @map-click="overviewDetailOpen = false"
|
|
|
|
|
+ @ready="handleOverviewMapReady"
|
|
|
|
|
+ @error="overviewMapError = $event"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-alert v-if="overviewMapError" class="map-inline-alert" type="warning" show-icon :closable="false"
|
|
|
|
|
+ :title="overviewMapError">
|
|
|
|
|
+ <el-button link type="primary" @click="loadAll">重试</el-button>
|
|
|
|
|
+ </el-alert>
|
|
|
|
|
+ <el-empty v-else-if="overviewLoaded && !overviewMapPoints.length && !overviewMapLines.length"
|
|
|
|
|
+ class="map-inline-empty" description="暂无可展示的全域点位" :image-size="70" />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <el-drawer v-model="overviewDetailOpen" title="点位详情" size="420px" direction="rtl"
|
|
|
|
|
+ class="detail-drawer" append-to-body>
|
|
|
|
|
+ <div v-if="overviewDetail" class="overview-detail">
|
|
|
|
|
+ <section class="detail-hero">
|
|
|
|
|
+ <h3>{{ overviewDetail.name }}</h3>
|
|
|
|
|
+ <p>{{ overviewDetail.code }}</p>
|
|
|
|
|
+ <div class="status-tags">
|
|
|
|
|
+ <el-tag size="small" :type="gisStatusTagType(overviewDetail.statusKey)">
|
|
|
|
|
+ {{ gisStatusLabel(overviewDetail.statusKey) }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ <el-tag v-if="overviewDetail.meta" size="small" type="info">{{ overviewDetail.meta }}</el-tag>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ <section class="detail-section">
|
|
|
|
|
+ <h4>点位信息</h4>
|
|
|
|
|
+ <dl>
|
|
|
|
|
+ <dt>名称</dt><dd>{{ overviewDetail.name }}</dd>
|
|
|
|
|
+ <dt>编码</dt><dd>{{ overviewDetail.code }}</dd>
|
|
|
|
|
+ <dt>类别</dt><dd>{{ overviewKindLabel(overviewDetail.kind) }}</dd>
|
|
|
|
|
+ <dt>状态</dt><dd>{{ gisStatusLabel(overviewDetail.statusKey) }}</dd>
|
|
|
|
|
+ <dt>类型 / 报警</dt><dd>{{ overviewDetail.meta }}</dd>
|
|
|
|
|
+ <dt>经度</dt><dd>{{ overviewDetail.lng }}</dd>
|
|
|
|
|
+ <dt>纬度</dt><dd>{{ overviewDetail.lat }}</dd>
|
|
|
|
|
+ </dl>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-empty v-else description="请点击地图点位查看详情" :image-size="70" />
|
|
|
|
|
+ </el-drawer>
|
|
|
</el-card>
|
|
</el-card>
|
|
|
|
|
|
|
|
<el-card shadow="never" class="water-panel home-panel">
|
|
<el-card shadow="never" class="water-panel home-panel">
|
|
|
<template #header>
|
|
<template #header>
|
|
|
<div class="card-title">
|
|
<div class="card-title">
|
|
|
<el-icon><Tickets /></el-icon><span>工单进展</span>
|
|
<el-icon><Tickets /></el-icon><span>工单进展</span>
|
|
|
- <el-button link type="primary" class="panel-link" @click="goPage('/monitorAlarm/workOrder')">统一工单</el-button>
|
|
|
|
|
|
|
+ <el-button link type="primary" class="panel-link" @click="goPage('/device/maintain')">设备维修派单</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
<div class="compact-list">
|
|
<div class="compact-list">
|
|
|
<button v-for="item in workOrderRows" :key="item.orderId || item.orderNo" type="button"
|
|
<button v-for="item in workOrderRows" :key="item.orderId || item.orderNo" type="button"
|
|
|
- class="compact-item" @click="goPage('/monitorAlarm/workOrder', { keyword: item.orderNo })">
|
|
|
|
|
|
|
+ class="compact-item" @click="goPage('/device/maintain', { keyword: item.orderNo })">
|
|
|
<span class="status-dot" :class="dotByOrderType(item.orderStatusType)" />
|
|
<span class="status-dot" :class="dotByOrderType(item.orderStatusType)" />
|
|
|
<span class="compact-main">
|
|
<span class="compact-main">
|
|
|
<b>{{ item.orderNo }}</b>
|
|
<b>{{ item.orderNo }}</b>
|
|
@@ -114,7 +159,6 @@
|
|
|
|
|
|
|
|
<!-- B 异常待办 -->
|
|
<!-- B 异常待办 -->
|
|
|
<section class="home-section">
|
|
<section class="home-section">
|
|
|
- <div class="section-heading"><el-icon><WarningFilled /></el-icon><span>B · 异常待办</span></div>
|
|
|
|
|
<div class="workbench-grid">
|
|
<div class="workbench-grid">
|
|
|
<el-card shadow="never" class="water-panel home-panel">
|
|
<el-card shadow="never" class="water-panel home-panel">
|
|
|
<template #header>
|
|
<template #header>
|
|
@@ -165,7 +209,7 @@
|
|
|
<el-descriptions-item label="异常原因">{{ selectedDevice.abnormalReasonText || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="异常原因">{{ selectedDevice.abnormalReasonText || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="关联工单">
|
|
<el-descriptions-item label="关联工单">
|
|
|
<el-button v-if="selectedDevice.relatedOrderNo && selectedDevice.relatedOrderNo !== '-'" link type="primary"
|
|
<el-button v-if="selectedDevice.relatedOrderNo && selectedDevice.relatedOrderNo !== '-'" link type="primary"
|
|
|
- @click="goPage('/monitorAlarm/workOrder', { keyword: selectedDevice.relatedOrderNo })">
|
|
|
|
|
|
|
+ @click="goPage('/device/maintain', { keyword: selectedDevice.relatedOrderNo })">
|
|
|
{{ selectedDevice.relatedOrderNo }}
|
|
{{ selectedDevice.relatedOrderNo }}
|
|
|
</el-button>
|
|
</el-button>
|
|
|
<span v-else>-</span>
|
|
<span v-else>-</span>
|
|
@@ -192,7 +236,6 @@
|
|
|
|
|
|
|
|
<!-- C 数据总览 -->
|
|
<!-- C 数据总览 -->
|
|
|
<section class="home-section">
|
|
<section class="home-section">
|
|
|
- <div class="section-heading"><el-icon><DataAnalysis /></el-icon><span>C · 数据总览</span></div>
|
|
|
|
|
<div class="metric-grid">
|
|
<div class="metric-grid">
|
|
|
<div v-for="card in overviewCards" :key="card.key" class="metric">
|
|
<div v-for="card in overviewCards" :key="card.key" class="metric">
|
|
|
<div class="metric-icon"><el-icon><component :is="overviewIcon(card.key)"/></el-icon></div>
|
|
<div class="metric-icon"><el-icon><component :is="overviewIcon(card.key)"/></el-icon></div>
|
|
@@ -237,17 +280,22 @@ import { useRouter } from 'vue-router'
|
|
|
import * as echarts from 'echarts'
|
|
import * as echarts from 'echarts'
|
|
|
import {
|
|
import {
|
|
|
Bell, BellFilled, Connection, Cpu, DataAnalysis, Finished, Grid, HomeFilled,
|
|
Bell, BellFilled, Connection, Cpu, DataAnalysis, Finished, Grid, HomeFilled,
|
|
|
- MapLocation, Monitor, OfficeBuilding, PieChart, Refresh, Tickets, TrendCharts,
|
|
|
|
|
|
|
+ MapLocation, OfficeBuilding, PieChart, Refresh, Tickets, TrendCharts,
|
|
|
Warning, WarningFilled
|
|
Warning, WarningFilled
|
|
|
} from '@element-plus/icons-vue'
|
|
} from '@element-plus/icons-vue'
|
|
|
import { getAlarmLedgerDashboard, getAlarmStats, getDeviceGisAllPoints, getDeviceLedgerStatistics,
|
|
import { getAlarmLedgerDashboard, getAlarmStats, getDeviceGisAllPoints, getDeviceLedgerStatistics,
|
|
|
- getWaterFacilityOverview, listAbnormalDevices, listRealtimeAlarms } from '@/api/pipeNetwork/waterSupply'
|
|
|
|
|
|
|
+ getWaterFacilityFeatures, getWaterFacilityOverview, listAbnormalDevices, listAlarmMapPoints,
|
|
|
|
|
+ listRealtimeAlarms } from '@/api/pipeNetwork/waterSupply'
|
|
|
import { listWorkOrder, workOrderStatusCount } from '@/api/pipeNetwork/ops/workOrder'
|
|
import { listWorkOrder, workOrderStatusCount } from '@/api/pipeNetwork/ops/workOrder'
|
|
|
-import { normalizePage, unwrapResponse } from '@/utils/waterSupplyModel'
|
|
|
|
|
|
|
+import { normalizeGeoJson, normalizePage, unwrapResponse } from '@/utils/waterSupplyModel'
|
|
|
|
|
+import { normalizeDeviceGisPoints } from '@/utils/waterSupplyUiModel'
|
|
|
|
|
+import { gisPointStatusLabels, gisPointStatusTagTypes } from '@/utils/gisMapColors'
|
|
|
|
|
+import { buildFacilityMapModel } from '@/utils/waterFacilityGisModel'
|
|
|
|
|
+import GisMapCanvas from '@/components/GisMapCanvas.vue'
|
|
|
import {
|
|
import {
|
|
|
- HOME_QUICK_ENTRIES, buildHomeWorkOrderQuery, buildOverviewMapPoints,
|
|
|
|
|
|
|
+ HOME_QUICK_ENTRIES, buildHomeGisOverview, buildHomeWorkOrderQuery,
|
|
|
normalizeAbnormalTodoRows, normalizeAlarmOverviewCards, normalizeAlarmTrend, normalizeDeviceSummary,
|
|
normalizeAbnormalTodoRows, normalizeAlarmOverviewCards, normalizeAlarmTrend, normalizeDeviceSummary,
|
|
|
- normalizeHomeKpis, normalizePieRatio, normalizeRealtimeAlarmRows,
|
|
|
|
|
|
|
+ normalizeHomeKpis, normalizePieRatio, normalizeRealtimeAlarmRows, realtimeAlarmJumpTarget,
|
|
|
normalizeWorkOrderRows, normalizeWorkOrderStatusStat
|
|
normalizeWorkOrderRows, normalizeWorkOrderStatusStat
|
|
|
} from '@/utils/waterHomeModel'
|
|
} from '@/utils/waterHomeModel'
|
|
|
|
|
|
|
@@ -267,6 +315,15 @@ const workOrderRows = ref([])
|
|
|
const workOrderStat = ref({})
|
|
const workOrderStat = ref({})
|
|
|
const abnormalTodoRows = ref([])
|
|
const abnormalTodoRows = ref([])
|
|
|
const overviewMapPoints = ref([])
|
|
const overviewMapPoints = ref([])
|
|
|
|
|
+const overviewMapLines = ref([])
|
|
|
|
|
+const overviewDeviceRaw = ref([])
|
|
|
|
|
+const overviewMapCenter = { lng: 110.3965, lat: 28.4638 }
|
|
|
|
|
+const overviewMapZoom = 15
|
|
|
|
|
+const overviewMapError = ref('')
|
|
|
|
|
+const overviewLoaded = ref(false)
|
|
|
|
|
+const overviewDetailOpen = ref(false)
|
|
|
|
|
+const overviewDetail = ref(null)
|
|
|
|
|
+const overviewMapRef = ref(null)
|
|
|
const selectedDevice = ref(null)
|
|
const selectedDevice = ref(null)
|
|
|
|
|
|
|
|
const homeKpis = ref([])
|
|
const homeKpis = ref([])
|
|
@@ -373,9 +430,8 @@ async function loadAll() {
|
|
|
workOrderRows.value = normalizeWorkOrderRows(orderList?.records || [], 6)
|
|
workOrderRows.value = normalizeWorkOrderRows(orderList?.records || [], 6)
|
|
|
|
|
|
|
|
const gisData = unwrapResponse(deviceGis.value || {})
|
|
const gisData = unwrapResponse(deviceGis.value || {})
|
|
|
- const devicePoints = Array.isArray(gisData) ? gisData : (gisData?.features || [])
|
|
|
|
|
- const alarmSource = normalizeRealtimeAlarmRows(unwrapResponse(realtime.value || []), 50)
|
|
|
|
|
- overviewMapPoints.value = buildOverviewMapPoints({ devicePoints, alarmRows: alarmSource })
|
|
|
|
|
|
|
+ overviewDeviceRaw.value = Array.isArray(gisData) ? gisData : (gisData?.features || [])
|
|
|
|
|
+ await loadOverviewMap(recordProblem)
|
|
|
|
|
|
|
|
homeKpis.value = normalizeHomeKpis({ facilityRows: facilityRows.value, ledgerStats: ledgerStats.value, alarmStats: alarmStats.value })
|
|
homeKpis.value = normalizeHomeKpis({ facilityRows: facilityRows.value, ledgerStats: ledgerStats.value, alarmStats: alarmStats.value })
|
|
|
overviewCards.value = normalizeAlarmOverviewCards({
|
|
overviewCards.value = normalizeAlarmOverviewCards({
|
|
@@ -397,6 +453,40 @@ function selectAbnormal(row) {
|
|
|
selectedDevice.value = normalizeDeviceSummary(row)
|
|
selectedDevice.value = normalizeDeviceSummary(row)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+async function loadOverviewMap(recordProblem) {
|
|
|
|
|
+ overviewMapError.value = ''
|
|
|
|
|
+ const [facilityRes, alarmRes] = await Promise.all([
|
|
|
|
|
+ settle(() => getWaterFacilityFeatures(), { features: [] }).catch(recordProblem('设施GIS点位')),
|
|
|
|
|
+ settle(() => listAlarmMapPoints(), []).catch(recordProblem('报警点位'))
|
|
|
|
|
+ ])
|
|
|
|
|
+ const facilityModel = buildFacilityMapModel(normalizeGeoJson(facilityRes))
|
|
|
|
|
+ const overviewModel = buildHomeGisOverview({
|
|
|
|
|
+ facilityPoints: facilityModel.points,
|
|
|
|
|
+ facilityLines: facilityModel.lines,
|
|
|
|
|
+ devicePoints: normalizeDeviceGisPoints(overviewDeviceRaw.value),
|
|
|
|
|
+ alarmPoints: (Array.isArray(alarmRes) ? alarmRes : [])
|
|
|
|
|
+ })
|
|
|
|
|
+ overviewMapPoints.value = overviewModel.points
|
|
|
|
|
+ overviewMapLines.value = overviewModel.lines
|
|
|
|
|
+ overviewLoaded.value = true
|
|
|
|
|
+ await nextTick()
|
|
|
|
|
+ overviewMapRef.value?.fitBounds()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleOverviewMapReady() {
|
|
|
|
|
+ nextTick(() => overviewMapRef.value?.fitBounds())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openOverviewPoint(point) {
|
|
|
|
|
+ overviewDetail.value = point || null
|
|
|
|
|
+ overviewDetailOpen.value = Boolean(point)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openRealtimeAlarm(item) {
|
|
|
|
|
+ const target = realtimeAlarmJumpTarget(item)
|
|
|
|
|
+ goPage(target.path, target.query)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function resolveRoutePath(suffix) {
|
|
function resolveRoutePath(suffix) {
|
|
|
const matched = router.getRoutes().find(item => item.path.endsWith(suffix))
|
|
const matched = router.getRoutes().find(item => item.path.endsWith(suffix))
|
|
|
return matched ? matched.path : ''
|
|
return matched ? matched.path : ''
|
|
@@ -411,6 +501,18 @@ function goPage(suffix, query = {}) {
|
|
|
router.push({ path, query }).catch(() => {})
|
|
router.push({ path, query }).catch(() => {})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function gisStatusLabel(statusKey) {
|
|
|
|
|
+ return gisPointStatusLabels[statusKey] || '正常'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function gisStatusTagType(statusKey) {
|
|
|
|
|
+ return gisPointStatusTagTypes[statusKey] || 'success'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function overviewKindLabel(kind) {
|
|
|
|
|
+ return { device: '监测设备', alarm: '报警', facility: '供水设施' }[kind] || '点位'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function renderCharts() {
|
|
function renderCharts() {
|
|
|
renderTrendChart()
|
|
renderTrendChart()
|
|
|
warningPie?.dispose()
|
|
warningPie?.dispose()
|
|
@@ -535,21 +637,6 @@ onBeforeUnmount(() => {
|
|
|
margin-bottom: 20px;
|
|
margin-bottom: 20px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.section-heading {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
- margin-bottom: 12px;
|
|
|
|
|
- color: #17324d;
|
|
|
|
|
- font-size: 15px;
|
|
|
|
|
- font-weight: 700;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.section-heading :deep(.el-icon) {
|
|
|
|
|
- color: #2563eb;
|
|
|
|
|
- font-size: 18px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
.metric-grid {
|
|
.metric-grid {
|
|
|
display: grid;
|
|
display: grid;
|
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
@@ -617,8 +704,9 @@ onBeforeUnmount(() => {
|
|
|
|
|
|
|
|
.workbench-grid {
|
|
.workbench-grid {
|
|
|
display: grid;
|
|
display: grid;
|
|
|
- grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
|
|
+ grid-template-columns: minmax(240px, 1fr) minmax(0, 3fr) minmax(240px, 1fr);
|
|
|
gap: 12px;
|
|
gap: 12px;
|
|
|
|
|
+ align-items: stretch;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.home-panel {
|
|
.home-panel {
|
|
@@ -661,6 +749,24 @@ onBeforeUnmount(() => {
|
|
|
margin-left: auto;
|
|
margin-left: auto;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.panel-links {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 2px;
|
|
|
|
|
+ margin-left: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.panel-links .panel-link {
|
|
|
|
|
+ margin-left: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.legend-dot {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ width: 8px;
|
|
|
|
|
+ height: 8px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.compact-list {
|
|
.compact-list {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
@@ -708,6 +814,7 @@ onBeforeUnmount(() => {
|
|
|
.dot-primary { background: #2563eb; }
|
|
.dot-primary { background: #2563eb; }
|
|
|
.dot-success { background: #16a34a; }
|
|
.dot-success { background: #16a34a; }
|
|
|
.dot-info { background: #94a3b8; }
|
|
.dot-info { background: #94a3b8; }
|
|
|
|
|
+.dot-workorder { background: #2563eb; }
|
|
|
|
|
|
|
|
.compact-main {
|
|
.compact-main {
|
|
|
display: block;
|
|
display: block;
|
|
@@ -748,6 +855,37 @@ onBeforeUnmount(() => {
|
|
|
white-space: nowrap;
|
|
white-space: nowrap;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.map-stage {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ height: 340px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ border: 1px solid #e1ebf3;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #eef6ff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.map-stage :deep(.gis-map-canvas) {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.map-inline-alert {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ left: 16px;
|
|
|
|
|
+ bottom: 16px;
|
|
|
|
|
+ z-index: 3;
|
|
|
|
|
+ width: 340px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.map-inline-empty {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ left: 50%;
|
|
|
|
|
+ top: 50%;
|
|
|
|
|
+ z-index: 2;
|
|
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.map-legend {
|
|
.map-legend {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
@@ -757,74 +895,50 @@ onBeforeUnmount(() => {
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.legend-dot {
|
|
|
|
|
- display: inline-block;
|
|
|
|
|
- width: 8px;
|
|
|
|
|
- height: 8px;
|
|
|
|
|
- border-radius: 50%;
|
|
|
|
|
|
|
+.overview-detail {
|
|
|
|
|
+ min-height: 240px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.map-stage {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- min-height: 226px;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- border: 1px solid #e1ebf3;
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
- background:
|
|
|
|
|
- linear-gradient(rgba(219, 234, 254, 0.35) 1px, transparent 1px),
|
|
|
|
|
- linear-gradient(90deg, rgba(219, 234, 254, 0.35) 1px, transparent 1px),
|
|
|
|
|
- linear-gradient(135deg, #eef6ff 0%, #f8fbff 100%);
|
|
|
|
|
- background-size: 28px 28px;
|
|
|
|
|
|
|
+.detail-hero h3,
|
|
|
|
|
+.detail-section h4 {
|
|
|
|
|
+ margin: 0 0 8px;
|
|
|
|
|
+ color: #1c4b78;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.map-point {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- transform: translate(-50%, -50%);
|
|
|
|
|
- z-index: 2;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 3px;
|
|
|
|
|
- cursor: default;
|
|
|
|
|
|
|
+.detail-hero p {
|
|
|
|
|
+ margin: 0 0 10px;
|
|
|
|
|
+ color: #678197;
|
|
|
|
|
+ font-size: 13px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.map-point span {
|
|
|
|
|
- display: block;
|
|
|
|
|
- width: 12px;
|
|
|
|
|
- height: 12px;
|
|
|
|
|
- border-radius: 50%;
|
|
|
|
|
- border: 2px solid #fff;
|
|
|
|
|
- box-shadow: 0 1px 4px rgba(15, 23, 42, 0.28);
|
|
|
|
|
|
|
+.status-tags {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 6px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.map-point.state-alarm span {
|
|
|
|
|
- background: #dc2626;
|
|
|
|
|
- box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.16);
|
|
|
|
|
|
|
+.detail-section {
|
|
|
|
|
+ margin-top: 18px;
|
|
|
|
|
+ padding-top: 12px;
|
|
|
|
|
+ border-top: 1px solid #e5eef7;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.map-point.state-offline span { background: #94a3b8; }
|
|
|
|
|
-.map-point.state-normal span { background: #16a34a; }
|
|
|
|
|
|
|
+.detail-section dl {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: 92px minmax(0, 1fr);
|
|
|
|
|
+ gap: 7px 10px;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-.map-point b {
|
|
|
|
|
- max-width: 110px;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- padding: 1px 5px;
|
|
|
|
|
- color: #334155;
|
|
|
|
|
- background: rgba(255, 255, 255, 0.88);
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- font-size: 10px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- text-overflow: ellipsis;
|
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
|
|
+.detail-section dt {
|
|
|
|
|
+ color: #71889c;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.map-note {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- left: 10px;
|
|
|
|
|
- bottom: 8px;
|
|
|
|
|
- z-index: 1;
|
|
|
|
|
- color: #94a3b8;
|
|
|
|
|
- font-size: 11px;
|
|
|
|
|
|
|
+.detail-section dd {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ color: #2b4c67;
|
|
|
|
|
+ overflow-wrap: anywhere;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.order-brief {
|
|
.order-brief {
|
|
@@ -984,6 +1098,10 @@ onBeforeUnmount(() => {
|
|
|
display: none;
|
|
display: none;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ .panel-links {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
.quick-grid {
|
|
.quick-grid {
|
|
|
grid-template-columns: 1fr;
|
|
grid-template-columns: 1fr;
|
|
|
}
|
|
}
|