更新
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="project-overview">
|
<div class="project-overview">
|
||||||
<div class="main-panel">
|
<div class="top-panel">
|
||||||
<!-- 地图区域 -->
|
<!-- 地图区域 -->
|
||||||
<div class="overview-card">
|
<div class="overview-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<div :class="['tab', { active: deviceTab === 'device' }]" @click="deviceTab = 'device'">设备概况</div>
|
<div :class="['tab', { active: deviceTab === 'device' }]" @click="deviceTab = 'device'">设备概况</div>
|
||||||
<div :class="['tab', { active: deviceTab === 'project' }]" @click="deviceTab = 'project'">项目概况</div>
|
<!-- <div :class="['tab', { active: deviceTab === 'project' }]" @click="deviceTab = 'project'">项目概况</div> -->
|
||||||
</div>
|
</div>
|
||||||
<el-icon class="fullscreen-icon" @click="toggleMapFullscreen"><FullScreen /></el-icon>
|
<el-icon class="fullscreen-icon" @click="toggleMapFullscreen"><FullScreen /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -15,6 +15,117 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 警情情况(右侧) -->
|
||||||
|
<div class="alarm-section">
|
||||||
|
<div class="alarm-title">所属项目</div>
|
||||||
|
<div class="alarm-card special">
|
||||||
|
<div class="project-info">
|
||||||
|
<div class="project-icon">
|
||||||
|
<el-icon><OfficeBuilding /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="project-detail">
|
||||||
|
<div class="project-name">{{ currentProject.name }}</div>
|
||||||
|
<div class="project-address">
|
||||||
|
<el-icon><Location /></el-icon>
|
||||||
|
<span>{{ currentProject.address }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button class="change-project-btn" type="primary" link @click="openChangeProjectDialog"
|
||||||
|
>切换项目</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alarm-title">警情情况</div>
|
||||||
|
|
||||||
|
<div class="alarm-card">
|
||||||
|
<div class="alarm-row">
|
||||||
|
<div class="alarm-icon device-icon">
|
||||||
|
<el-icon><Monitor /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="alarm-content">
|
||||||
|
<div class="alarm-labels">
|
||||||
|
<span class="label-item">设备总数</span>
|
||||||
|
<span class="label-item">在线设备数</span>
|
||||||
|
<span class="label-item">离线设备数</span>
|
||||||
|
</div>
|
||||||
|
<div class="alarm-values">
|
||||||
|
<span class="value-item">{{ alarmSituationSummary.device.total }}</span>
|
||||||
|
<span class="value-item online">{{ alarmSituationSummary.device.online }}</span>
|
||||||
|
<span class="value-item offline">{{ alarmSituationSummary.device.offline }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alarm-card">
|
||||||
|
<div class="alarm-row">
|
||||||
|
<div class="alarm-icon line-icon">
|
||||||
|
<el-icon><Connection /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="alarm-content">
|
||||||
|
<div class="alarm-labels">
|
||||||
|
<span class="label-item">线路总数</span>
|
||||||
|
<span class="label-item">在线线路数</span>
|
||||||
|
<span class="label-item">离线线路数</span>
|
||||||
|
</div>
|
||||||
|
<div class="alarm-values">
|
||||||
|
<span class="value-item">{{ alarmSituationSummary.line.total }}</span>
|
||||||
|
<span class="value-item online">{{ alarmSituationSummary.line.online }}</span>
|
||||||
|
<span class="value-item offline">{{ alarmSituationSummary.line.offline }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alarm-card">
|
||||||
|
<div class="alarm-row">
|
||||||
|
<div class="alarm-icon warn-icon">
|
||||||
|
<el-icon><Bell /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="alarm-content alarm-stats-grid">
|
||||||
|
<div class="alarm-stat">
|
||||||
|
<span class="alarm-stat-label">本月报警总数</span>
|
||||||
|
<span class="alarm-stat-value danger">{{ alarmSituationSummary.alarm.currentMonthTotal }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="alarm-stat">
|
||||||
|
<span class="alarm-stat-label">昨日报警总数</span>
|
||||||
|
<span class="alarm-stat-value danger">{{ alarmSituationSummary.alarm.yesterdayTotal }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="alarm-stat">
|
||||||
|
<span class="alarm-stat-label">今日报警总数</span>
|
||||||
|
<span class="alarm-stat-value danger">{{ alarmSituationSummary.alarm.todayTotal }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="alarm-stat">
|
||||||
|
<span class="alarm-stat-label">今日报警设备数</span>
|
||||||
|
<span class="alarm-stat-value danger">{{ alarmSituationSummary.alarm.todayDeviceCount }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="alarm-card">
|
||||||
|
<div class="alarm-row process-row">
|
||||||
|
<div class="process-stat">
|
||||||
|
<div class="process-ring green">
|
||||||
|
<span class="process-ring-value">0.18%</span>
|
||||||
|
</div>
|
||||||
|
<div class="process-label">本月已处理报警数</div>
|
||||||
|
<div class="process-sub-value processed">35</div>
|
||||||
|
</div>
|
||||||
|
<div class="process-divider"></div>
|
||||||
|
<div class="process-stat">
|
||||||
|
<div class="process-ring red">
|
||||||
|
<span class="process-ring-value">99.82%</span>
|
||||||
|
</div>
|
||||||
|
<div class="process-label">本月未处理报警数</div>
|
||||||
|
<div class="process-sub-value unprocessed">19606</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 警情列表 -->
|
<!-- 警情列表 -->
|
||||||
<div class="alarm-list">
|
<div class="alarm-list">
|
||||||
<div class="list-header">
|
<div class="list-header">
|
||||||
@@ -23,7 +134,7 @@
|
|||||||
警情列表
|
警情列表
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="record-tag">智能竖井</div>
|
|
||||||
<el-table :data="alarmTableData" border stripe class="alarm-table">
|
<el-table :data="alarmTableData" border stripe class="alarm-table">
|
||||||
<el-table-column align="center" label="序号" prop="index" width="60" />
|
<el-table-column align="center" label="序号" prop="index" width="60" />
|
||||||
<el-table-column align="left" label="事件ID" prop="eventId" width="110" />
|
<el-table-column align="left" label="事件ID" prop="eventId" width="110" />
|
||||||
@@ -58,116 +169,215 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 警情情况(右侧) -->
|
<el-dialog v-model="projectDialogVisible" destroy-on-close title="更换项目" width="480px">
|
||||||
<div class="alarm-section">
|
<el-radio-group v-model="selectedProjectId" class="project-radio-group">
|
||||||
<div class="alarm-title">警情情况</div>
|
<el-radio v-for="item in projectList" :key="item.id" :value="item.id" class="project-radio-item">
|
||||||
|
<div class="project-option">
|
||||||
<div class="alarm-card">
|
<div class="project-option-name">{{ item.name }}</div>
|
||||||
<div class="alarm-row">
|
<div class="project-option-address">{{ item.address }}</div>
|
||||||
<div class="alarm-icon device-icon">
|
|
||||||
<el-icon><Monitor /></el-icon>
|
|
||||||
</div>
|
|
||||||
<div class="alarm-content">
|
|
||||||
<div class="alarm-labels">
|
|
||||||
<span class="label-item">设备总数</span>
|
|
||||||
<span class="label-item">在线设备数</span>
|
|
||||||
<span class="label-item">离线设备数</span>
|
|
||||||
</div>
|
|
||||||
<div class="alarm-values">
|
|
||||||
<span class="value-item">17</span>
|
|
||||||
<span class="value-item online">12</span>
|
|
||||||
<span class="value-item offline">5</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alarm-card">
|
|
||||||
<div class="alarm-row">
|
|
||||||
<div class="alarm-icon line-icon">
|
|
||||||
<el-icon><Connection /></el-icon>
|
|
||||||
</div>
|
|
||||||
<div class="alarm-content">
|
|
||||||
<div class="alarm-labels">
|
|
||||||
<span class="label-item">线路总数</span>
|
|
||||||
<span class="label-item">在线线路数</span>
|
|
||||||
<span class="label-item">离线线路数</span>
|
|
||||||
</div>
|
|
||||||
<div class="alarm-values">
|
|
||||||
<span class="value-item">92</span>
|
|
||||||
<span class="value-item online">74</span>
|
|
||||||
<span class="value-item offline">18</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alarm-card">
|
|
||||||
<div class="alarm-row">
|
|
||||||
<div class="alarm-icon warn-icon">
|
|
||||||
<el-icon><Bell /></el-icon>
|
|
||||||
</div>
|
|
||||||
<div class="alarm-content">
|
|
||||||
<div class="alarm-labels">
|
|
||||||
<span class="label-item">本月报警总数</span>
|
|
||||||
<span class="label-item">本月预警总数</span>
|
|
||||||
<span class="label-item">昨日报警总数</span>
|
|
||||||
</div>
|
|
||||||
<div class="alarm-values">
|
|
||||||
<span class="value-item danger">19641</span>
|
|
||||||
<span class="value-item warning">208</span>
|
|
||||||
<span class="value-item danger">25</span>
|
|
||||||
</div>
|
|
||||||
<div class="alarm-labels second-row">
|
|
||||||
<span class="label-item">今日报警总数</span>
|
|
||||||
<span class="label-item">今日报警设备数</span>
|
|
||||||
</div>
|
|
||||||
<div class="alarm-values two-cols">
|
|
||||||
<span class="value-item danger">13</span>
|
|
||||||
<span class="value-item danger">5</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alarm-card">
|
|
||||||
<div class="alarm-row process-row">
|
|
||||||
<div class="process-stat">
|
|
||||||
<div class="process-ring green">
|
|
||||||
<span class="process-ring-value">0.18%</span>
|
|
||||||
</div>
|
|
||||||
<div class="process-label">本月已处理报警数</div>
|
|
||||||
<div class="process-sub-value processed">35</div>
|
|
||||||
</div>
|
|
||||||
<div class="process-divider"></div>
|
|
||||||
<div class="process-stat">
|
|
||||||
<div class="process-ring red">
|
|
||||||
<span class="process-ring-value">99.82%</span>
|
|
||||||
</div>
|
|
||||||
<div class="process-label">本月未处理报警数</div>
|
|
||||||
<div class="process-sub-value unprocessed">19606</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="projectDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmChangeProject">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import * as serve from '@/api/masterStation/project'
|
||||||
|
import * as equipmentServe from '@/api/masterStation/equipment'
|
||||||
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||||
import { FullScreen, Monitor, Connection, Bell } from '@element-plus/icons-vue'
|
import { FullScreen, Monitor, Connection, Bell, OfficeBuilding, Location } from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MasterStationProject'
|
name: 'MasterStationProject'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const PROJECT_STORAGE_KEY = 'masterStation_current_project_id'
|
||||||
|
const MAP_ZOOM = 13
|
||||||
|
const MAP_ZOOM_OUT = 9
|
||||||
|
const MAP_STEP_DURATION = 350
|
||||||
|
|
||||||
|
const normalizeProject = (item) => ({
|
||||||
|
id: item.projectId,
|
||||||
|
name: item.projectName,
|
||||||
|
address: item.projectAddress,
|
||||||
|
lng: Number(item.projectLong),
|
||||||
|
lat: Number(item.projectLat),
|
||||||
|
projectUser: item.projectUser,
|
||||||
|
projectPhone: item.projectPhone
|
||||||
|
})
|
||||||
|
|
||||||
|
const projectList = ref([])
|
||||||
|
const currentProject = ref({ id: null, name: '', address: '', lng: 119.1, lat: 36.7 })
|
||||||
|
const projectDialogVisible = ref(false)
|
||||||
|
const selectedProjectId = ref(null)
|
||||||
|
const alarmSituationSummary = ref({
|
||||||
|
device: { total: 0, online: 0, offline: 0 },
|
||||||
|
line: { total: 0, online: 0, offline: 0 },
|
||||||
|
alarm: { currentMonthTotal: 0, yesterdayTotal: 0, todayTotal: 0, todayDeviceCount: 0 },
|
||||||
|
preAlarm: { currentMonthTotal: 0 }
|
||||||
|
})
|
||||||
|
|
||||||
|
let mapSwitchToken = 0
|
||||||
|
|
||||||
|
const waitMapEvent = (map, eventName, token) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
map.off(eventName, handler)
|
||||||
|
resolve()
|
||||||
|
}, MAP_STEP_DURATION + 400)
|
||||||
|
const handler = () => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
map.off(eventName, handler)
|
||||||
|
if (token === mapSwitchToken) resolve()
|
||||||
|
}
|
||||||
|
map.on(eventName, handler)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const moveMapToProject = (project) => {
|
||||||
|
if (!mapInstance.value || !project?.lng || !project?.lat) return
|
||||||
|
mapInstance.value.setZoomAndCenter(MAP_ZOOM, [project.lng, project.lat], true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const animateMapSwitch = async (fromProject, toProject) => {
|
||||||
|
const map = mapInstance.value
|
||||||
|
if (!map || !fromProject?.lng || !fromProject?.lat || !toProject?.lng || !toProject?.lat) return
|
||||||
|
|
||||||
|
const token = ++mapSwitchToken
|
||||||
|
const fromCenter = [fromProject.lng, fromProject.lat]
|
||||||
|
const toCenter = [toProject.lng, toProject.lat]
|
||||||
|
|
||||||
|
map.setZoomAndCenter(MAP_ZOOM, fromCenter, true)
|
||||||
|
|
||||||
|
map.setZoom(MAP_ZOOM_OUT, false, MAP_STEP_DURATION)
|
||||||
|
await waitMapEvent(map, 'zoomend', token)
|
||||||
|
if (token !== mapSwitchToken) return
|
||||||
|
|
||||||
|
hideProjectMarker()
|
||||||
|
map.panTo(toCenter, MAP_STEP_DURATION)
|
||||||
|
await waitMapEvent(map, 'moveend', token)
|
||||||
|
if (token !== mapSwitchToken) return
|
||||||
|
|
||||||
|
map.setZoom(MAP_ZOOM, false, MAP_STEP_DURATION)
|
||||||
|
await waitMapEvent(map, 'zoomend', token)
|
||||||
|
if (token !== mapSwitchToken) return
|
||||||
|
|
||||||
|
updateProjectMarker(toProject)
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyCurrentProject = (project) => {
|
||||||
|
if (!project) return
|
||||||
|
currentProject.value = project
|
||||||
|
selectedProjectId.value = project.id
|
||||||
|
updateProjectMarker(project)
|
||||||
|
moveMapToProject(project)
|
||||||
|
fetchAlarmSituationSummary()
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchAlarmSituationSummary = () => {
|
||||||
|
if (!currentProject.value.id) return
|
||||||
|
serve.getAlarmSituationSummary({ projectId: currentProject.value.id }).then((res) => {
|
||||||
|
if (res.code !== 0 || !res.data) return
|
||||||
|
alarmSituationSummary.value = {
|
||||||
|
device: { total: 0, online: 0, offline: 0, ...res.data.device },
|
||||||
|
line: { total: 0, online: 0, offline: 0, ...res.data.line },
|
||||||
|
alarm: {
|
||||||
|
currentMonthTotal: 0,
|
||||||
|
yesterdayTotal: 0,
|
||||||
|
todayTotal: 0,
|
||||||
|
todayDeviceCount: 0,
|
||||||
|
...res.data.alarm
|
||||||
|
},
|
||||||
|
preAlarm: { currentMonthTotal: 0, ...res.data.preAlarm }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getProjectList()
|
||||||
|
getAlarmRecordListByPage()
|
||||||
|
nextTick(() => {
|
||||||
|
initMap()
|
||||||
|
if (mapWrapperRef.value) {
|
||||||
|
mapResizeObserver = new ResizeObserver(handleResize)
|
||||||
|
mapResizeObserver.observe(mapWrapperRef.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取警情情况
|
||||||
|
const getAlarmRecordListByPage = () => {
|
||||||
|
console.log('currentProject.value', currentProject.value)
|
||||||
|
if (!currentProject.value.id) return
|
||||||
|
equipmentServe
|
||||||
|
.getAlarmRecordListByPage({ page: 1, pageSize: 10, projectId: currentProject.value.id })
|
||||||
|
.then((res) => {
|
||||||
|
if (res.code !== 0 || !res.data) return
|
||||||
|
alarmTableData.value = res.data.list
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 获取项目列表
|
||||||
|
const getProjectList = () => {
|
||||||
|
return serve.getProjectList().then((res) => {
|
||||||
|
if (res.code !== 0 || !res.data?.length) return
|
||||||
|
|
||||||
|
const seen = new Set()
|
||||||
|
projectList.value = res.data
|
||||||
|
.filter((item) => {
|
||||||
|
if (seen.has(item.projectId)) return false
|
||||||
|
seen.add(item.projectId)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.map(normalizeProject)
|
||||||
|
|
||||||
|
const savedId = Number(localStorage.getItem(PROJECT_STORAGE_KEY))
|
||||||
|
// console.log('savedId', savedId)
|
||||||
|
const project = projectList.value.find((item) => item.id === savedId) || projectList.value[0]
|
||||||
|
// console.log('project', project)
|
||||||
|
applyCurrentProject(project)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const openChangeProjectDialog = () => {
|
||||||
|
selectedProjectId.value = currentProject.value.id
|
||||||
|
projectDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmChangeProject = () => {
|
||||||
|
const project = projectList.value.find((item) => item.id === selectedProjectId.value)
|
||||||
|
if (!project) return
|
||||||
|
|
||||||
|
if (project.id === currentProject.value.id) {
|
||||||
|
projectDialogVisible.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevProject = { ...currentProject.value }
|
||||||
|
currentProject.value = project
|
||||||
|
selectedProjectId.value = project.id
|
||||||
|
localStorage.setItem(PROJECT_STORAGE_KEY, String(project.id))
|
||||||
|
projectDialogVisible.value = false
|
||||||
|
ElMessage.success(`已切换至:${project.name}`)
|
||||||
|
fetchAlarmSituationSummary()
|
||||||
|
nextTick(() => {
|
||||||
|
animateMapSwitch(prevProject, project)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const deviceTab = ref('device')
|
const deviceTab = ref('device')
|
||||||
const mapWrapperRef = ref(null)
|
const mapWrapperRef = ref(null)
|
||||||
const mapInstance = ref(null)
|
const mapInstance = ref(null)
|
||||||
const markers = ref([])
|
let projectMarker = null
|
||||||
|
let mapResizeObserver = null
|
||||||
|
let resizeTimer = null
|
||||||
|
|
||||||
const alarmTableData = ref([
|
const alarmTableData = ref([
|
||||||
{
|
{
|
||||||
@@ -237,33 +447,48 @@
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
const mapDevices = [
|
const createProjectMarkerContent = (name) => {
|
||||||
{ lng: 116.4, lat: 39.9, label: '10', type: 'cluster' },
|
|
||||||
{ lng: 113.2, lat: 23.1, label: '6', type: 'cluster' },
|
|
||||||
{ lng: 118.8, lat: 32.0, label: '98CC4D2052AE', subLabel: '98CC4D2052AE', type: 'device' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const createClusterContent = (label) => {
|
|
||||||
return `
|
|
||||||
<div class="map-cluster-marker">
|
|
||||||
<div class="map-cluster-pulse"></div>
|
|
||||||
<div class="map-cluster-core">${label}</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
const createDeviceContent = (label, subLabel) => {
|
|
||||||
return `
|
return `
|
||||||
<div class="map-device-marker">
|
<div class="map-device-marker">
|
||||||
<div class="map-device-icon">📍</div>
|
<div class="map-device-icon">📍</div>
|
||||||
<div class="map-device-label">
|
<div class="map-device-label">
|
||||||
<div class="map-device-name">${label}</div>
|
<div class="map-device-name">${name}</div>
|
||||||
<div class="map-device-sub">[${subLabel}]</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hideProjectMarker = () => {
|
||||||
|
if (!projectMarker || !mapInstance.value) return
|
||||||
|
mapInstance.value.remove(projectMarker)
|
||||||
|
projectMarker = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateProjectMarker = (project) => {
|
||||||
|
if (!mapInstance.value || !project?.lng || !project?.lat) return
|
||||||
|
const position = [project.lng, project.lat]
|
||||||
|
|
||||||
|
if (!projectMarker) {
|
||||||
|
projectMarker = new AMap.Marker({
|
||||||
|
position,
|
||||||
|
content: createProjectMarkerContent(project.name),
|
||||||
|
offset: new AMap.Pixel(-10, -30),
|
||||||
|
zIndex: 10
|
||||||
|
})
|
||||||
|
mapInstance.value.add(projectMarker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
projectMarker.setPosition(position)
|
||||||
|
const content = projectMarker.getContent()
|
||||||
|
if (content?.querySelector) {
|
||||||
|
const nameEl = content.querySelector('.map-device-name')
|
||||||
|
if (nameEl) nameEl.textContent = project.name
|
||||||
|
} else {
|
||||||
|
projectMarker.setContent(createProjectMarkerContent(project.name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const initMap = () => {
|
const initMap = () => {
|
||||||
if (typeof AMap === 'undefined') {
|
if (typeof AMap === 'undefined') {
|
||||||
console.error('高德地图API未加载')
|
console.error('高德地图API未加载')
|
||||||
@@ -271,29 +496,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
mapInstance.value = new AMap.Map('projectMap', {
|
mapInstance.value = new AMap.Map('projectMap', {
|
||||||
zoom: 5,
|
zoom: MAP_ZOOM,
|
||||||
center: [116.4074, 39.9042],
|
center: [currentProject.value.lng, currentProject.value.lat],
|
||||||
mapStyle: 'amap://styles/whitesmoke',
|
mapStyle: 'amap://styles/whitesmoke',
|
||||||
viewMode: '2D',
|
viewMode: '2D',
|
||||||
features: ['bg', 'road', 'building', 'point']
|
features: ['bg', 'road', 'building', 'point']
|
||||||
})
|
})
|
||||||
|
|
||||||
mapDevices.forEach((item) => {
|
if (currentProject.value.id) {
|
||||||
const content =
|
updateProjectMarker(currentProject.value)
|
||||||
item.type === 'cluster'
|
}
|
||||||
? createClusterContent(item.label)
|
|
||||||
: createDeviceContent(item.label, item.subLabel)
|
|
||||||
|
|
||||||
const marker = new AMap.Marker({
|
|
||||||
position: [item.lng, item.lat],
|
|
||||||
content,
|
|
||||||
offset: item.type === 'cluster' ? new AMap.Pixel(-18, -18) : new AMap.Pixel(-10, -30),
|
|
||||||
zIndex: item.type === 'device' ? 10 : 5
|
|
||||||
})
|
|
||||||
|
|
||||||
mapInstance.value.add(marker)
|
|
||||||
markers.value.push(marker)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleMapFullscreen = () => {
|
const toggleMapFullscreen = () => {
|
||||||
@@ -317,18 +529,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
|
clearTimeout(resizeTimer)
|
||||||
|
resizeTimer = setTimeout(() => {
|
||||||
mapInstance.value?.resize()
|
mapInstance.value?.resize()
|
||||||
|
}, 150)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
initMap()
|
|
||||||
})
|
|
||||||
window.addEventListener('resize', handleResize)
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
clearTimeout(resizeTimer)
|
||||||
window.removeEventListener('resize', handleResize)
|
window.removeEventListener('resize', handleResize)
|
||||||
|
mapResizeObserver?.disconnect()
|
||||||
|
mapResizeObserver = null
|
||||||
|
if (projectMarker) {
|
||||||
|
mapInstance.value?.remove(projectMarker)
|
||||||
|
projectMarker = null
|
||||||
|
}
|
||||||
if (mapInstance.value) {
|
if (mapInstance.value) {
|
||||||
mapInstance.value.destroy()
|
mapInstance.value.destroy()
|
||||||
mapInstance.value = null
|
mapInstance.value = null
|
||||||
@@ -341,17 +556,18 @@
|
|||||||
background-color: #f5f7fa;
|
background-color: #f5f7fa;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
min-height: calc(100vh - 120px);
|
min-height: calc(100vh - 120px);
|
||||||
color: #303133;
|
color: #303133;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-panel {
|
.top-panel {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overview-card,
|
.overview-card,
|
||||||
@@ -364,7 +580,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.overview-card {
|
.overview-card {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
@@ -419,7 +640,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.map-container {
|
.map-container {
|
||||||
height: 360px;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid #ebeef5;
|
border: 1px solid #ebeef5;
|
||||||
@@ -449,6 +671,103 @@
|
|||||||
|
|
||||||
.alarm-card {
|
.alarm-card {
|
||||||
padding: 14px 12px;
|
padding: 14px 12px;
|
||||||
|
|
||||||
|
&.special {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: linear-gradient(135deg, #409eff, #2777ec);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-detail {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-address {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
margin-top: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-project-btn {
|
||||||
|
align-self: flex-end;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-radio-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-radio-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
height: auto;
|
||||||
|
margin-right: 0;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.is-checked {
|
||||||
|
border-color: #409eff;
|
||||||
|
background: #ecf5ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-option {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-option-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-option-address {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alarm-row {
|
.alarm-row {
|
||||||
@@ -487,6 +806,41 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
|
&.alarm-stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-stat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-stat-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-stat-value {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: 'DIN', 'Arial', sans-serif;
|
||||||
|
color: #303133;
|
||||||
|
line-height: 1.2;
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
color: #e6a23c;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.alarm-labels {
|
.alarm-labels {
|
||||||
@@ -650,18 +1004,6 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-tag {
|
|
||||||
display: inline-block;
|
|
||||||
background: #409eff;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 5px 14px;
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
margin-bottom: -1px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alarm-table {
|
.alarm-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user