更新
This commit is contained in:
@@ -31,7 +31,7 @@ export const getDeviceDetailsListByPage = (data) => {
|
||||
data: data
|
||||
})
|
||||
}
|
||||
// 报警
|
||||
// 报警列表
|
||||
export const getAlarmRecordListByPage = (data) => {
|
||||
return service({
|
||||
url: '/device/getAlarmRecordListByPage',
|
||||
|
||||
26
src/api/masterStation/project.js
Normal file
26
src/api/masterStation/project.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import service from '@/utils/request'
|
||||
// 项目列表
|
||||
export const getProjectList = (data) => {
|
||||
return service({
|
||||
url: '/device/getProjectList',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
// 警情情况
|
||||
export const getAlarmSituationSummary = (data) => {
|
||||
return service({
|
||||
url: '/device/getAlarmSituationSummary',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 折线图
|
||||
export const getAlarmTrendProcessingRate = (data) => {
|
||||
return service({
|
||||
url: '/device/getAlarmTrendProcessingRate',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import service from '@/utils/request'
|
||||
// echarts图
|
||||
// 环图
|
||||
export const getAlarmCategorySummary = (data) => {
|
||||
return service({
|
||||
url: '/device/getAlarmCategorySummary',
|
||||
@@ -7,6 +7,7 @@ export const getAlarmCategorySummary = (data) => {
|
||||
data: data
|
||||
})
|
||||
}
|
||||
// 折线图
|
||||
export const getAlarmTrend = (data) => {
|
||||
return service({
|
||||
url: '/device/getAlarmTrend',
|
||||
@@ -14,3 +15,11 @@ export const getAlarmTrend = (data) => {
|
||||
data: data
|
||||
})
|
||||
}
|
||||
// 报警列表
|
||||
export const getAlarmRecordListByPage = (data) => {
|
||||
return service({
|
||||
url: '/device/getAlarmRecordListByPage',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
@@ -64,13 +64,14 @@
|
||||
"/src/view/masterStation/equipment/components/list/index.vue": "Index",
|
||||
"/src/view/masterStation/equipment/index.vue": "Index",
|
||||
"/src/view/masterStation/index.vue": "MasterStation",
|
||||
"/src/view/masterStation/project/index.vue": "Index",
|
||||
"/src/view/masterStation/project/index.vue": "MasterStationProject",
|
||||
"/src/view/person/person.vue": "Person",
|
||||
"/src/view/routerHolder.vue": "RouterHolder",
|
||||
"/src/view/securityControl/alarmList/index.vue": "Index",
|
||||
"/src/view/securityControl/currentAlarm/index.vue": "Index",
|
||||
"/src/view/securityControl/index.vue": "SecurityControl",
|
||||
"/src/view/securityControl/temperatureAlarm/index.vue": "Index",
|
||||
"/src/view/securityControl/overloadAlarm/index.vue": "Index",
|
||||
"/src/view/securityControl/voltageAlarm/index.vue": "Index",
|
||||
"/src/view/superAdmin/api/api.vue": "Api",
|
||||
"/src/view/superAdmin/authority/authority.vue": "Authority",
|
||||
"/src/view/superAdmin/authority/components/apis.vue": "Apis",
|
||||
|
||||
@@ -2,7 +2,8 @@ import { createPinia } from 'pinia'
|
||||
import { useAppStore } from '@/pinia/modules/app'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { useDictionaryStore } from '@/pinia/modules/dictionary'
|
||||
import { useProjectStore } from '@/pinia/modules/project'
|
||||
|
||||
const store = createPinia()
|
||||
|
||||
export { store, useAppStore, useUserStore, useDictionaryStore }
|
||||
export { store, useAppStore, useUserStore, useDictionaryStore, useProjectStore }
|
||||
|
||||
26
src/pinia/modules/project.js
Normal file
26
src/pinia/modules/project.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const PROJECT_STORAGE_KEY = 'masterStation_current_project_id'
|
||||
|
||||
export const useProjectStore = defineStore('project', () => {
|
||||
const currentProject = ref(null)
|
||||
|
||||
const setCurrentProject = (project) => {
|
||||
currentProject.value = project || null
|
||||
if (project?.id) {
|
||||
localStorage.setItem(PROJECT_STORAGE_KEY, String(project.id))
|
||||
}
|
||||
}
|
||||
|
||||
const getSavedProjectId = () => {
|
||||
const savedId = Number(localStorage.getItem(PROJECT_STORAGE_KEY))
|
||||
return savedId || null
|
||||
}
|
||||
|
||||
return {
|
||||
currentProject,
|
||||
setCurrentProject,
|
||||
getSavedProjectId
|
||||
}
|
||||
})
|
||||
@@ -2,26 +2,13 @@
|
||||
<div
|
||||
class="rounded-lg flex items-center justify-evenly w-full h-full relative md:w-screen md:h-screen md:bg-[#194bfb] overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="rounded-md w-full h-full flex items-center justify-center overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="oblique h-[130%] w-3/5 bg-white dark:bg-slate-900 transform -rotate-12 absolute -ml-80"
|
||||
/>
|
||||
<div
|
||||
v-if="!page.showForm"
|
||||
:class="[page.showReadme ? 'slide-out-right' : 'slide-in-fwd-top']"
|
||||
>
|
||||
<div class="rounded-md w-full h-full flex items-center justify-center overflow-hidden">
|
||||
<div class="oblique h-[130%] w-3/5 bg-white dark:bg-slate-900 transform -rotate-12 absolute -ml-80" />
|
||||
<div v-if="!page.showForm" :class="[page.showReadme ? 'slide-out-right' : 'slide-in-fwd-top']">
|
||||
<div class="text-lg">
|
||||
<div
|
||||
class="font-sans text-4xl font-bold text-center mb-4 dark:text-white"
|
||||
>
|
||||
GIN-VUE-ADMIN
|
||||
</div>
|
||||
<div class="font-sans text-4xl font-bold text-center mb-4 dark:text-white">GIN-VUE-ADMIN</div>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-2">初始化须知</p>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-2">
|
||||
1.您需有用一定的VUE和GOLANG基础
|
||||
</p>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-2">1.您需有用一定的VUE和GOLANG基础</p>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-2">
|
||||
2.请您确认是否已经阅读过<a
|
||||
class="text-blue-600 font-bold"
|
||||
@@ -29,54 +16,28 @@
|
||||
target="_blank"
|
||||
>官方文档</a
|
||||
>
|
||||
<a
|
||||
class="text-blue-600 font-bold"
|
||||
href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=2"
|
||||
target="_blank"
|
||||
<a class="text-blue-600 font-bold" href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=2" target="_blank"
|
||||
>初始化视频</a
|
||||
>
|
||||
</p>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-2">3.请您确认是否了解后续的配置流程</p>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-2">
|
||||
3.请您确认是否了解后续的配置流程
|
||||
</p>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-2">
|
||||
4.如果您使用mysql数据库,请确认数据库引擎为<span
|
||||
class="text-red-600 font-bold text-3xl ml-2"
|
||||
>innoDB</span
|
||||
>
|
||||
</p>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-2">
|
||||
注:开发组不为文档中书写过的内容提供无偿服务
|
||||
4.如果您使用mysql数据库,请确认数据库引擎为<span class="text-red-600 font-bold text-3xl ml-2">innoDB</span>
|
||||
</p>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-2">注:开发组不为文档中书写过的内容提供无偿服务</p>
|
||||
<p class="flex items-center justify-between mt-8">
|
||||
<el-button type="primary" size="large" @click="goDoc">
|
||||
阅读文档
|
||||
</el-button>
|
||||
<el-button type="primary" size="large" @click="showNext">
|
||||
我已确认
|
||||
</el-button>
|
||||
<el-button type="primary" size="large" @click="goDoc"> 阅读文档 </el-button>
|
||||
<el-button type="primary" size="large" @click="showNext"> 我已确认 </el-button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="page.showForm"
|
||||
:class="[page.showForm ? 'slide-in-left' : 'slide-out-right']"
|
||||
class="w-96"
|
||||
>
|
||||
<div v-if="page.showForm" :class="[page.showForm ? 'slide-in-left' : 'slide-out-right']" class="w-96">
|
||||
<el-form ref="formRef" :model="form" label-width="100px" size="large">
|
||||
<el-form-item label="管理员密码">
|
||||
<el-input
|
||||
v-model="form.adminPassword"
|
||||
placeholder="admin账号的默认密码"
|
||||
></el-input>
|
||||
<el-input v-model="form.adminPassword" placeholder="admin账号的默认密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库类型">
|
||||
<el-select
|
||||
v-model="form.dbType"
|
||||
placeholder="请选择"
|
||||
class="w-full"
|
||||
@change="changeDB"
|
||||
>
|
||||
<el-select v-model="form.dbType" placeholder="请选择" class="w-full" @change="changeDB">
|
||||
<el-option key="mysql" label="mysql" value="mysql" />
|
||||
<el-option key="pgsql" label="pgsql" value="pgsql" />
|
||||
<el-option key="oracle" label="oracle" value="oracle" />
|
||||
@@ -91,31 +52,19 @@
|
||||
<el-input v-model="form.port" placeholder="请输入数据库端口" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.dbType !== 'sqlite'" label="userName">
|
||||
<el-input
|
||||
v-model="form.userName"
|
||||
placeholder="请输入数据库用户名"
|
||||
/>
|
||||
<el-input v-model="form.userName" placeholder="请输入数据库用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.dbType !== 'sqlite'" label="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
placeholder="请输入数据库密码(没有则为空)"
|
||||
/>
|
||||
<el-input v-model="form.password" placeholder="请输入数据库密码(没有则为空)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="dbName">
|
||||
<el-input v-model="form.dbName" placeholder="请输入数据库名称" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.dbType === 'sqlite'" label="dbPath">
|
||||
<el-input
|
||||
v-model="form.dbPath"
|
||||
placeholder="请输入sqlite数据库文件存放路径"
|
||||
/>
|
||||
<el-input v-model="form.dbPath" placeholder="请输入sqlite数据库文件存放路径" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.dbType === 'pgsql'" label="template">
|
||||
<el-input
|
||||
v-model="form.template"
|
||||
placeholder="请输入postgresql指定template"
|
||||
/>
|
||||
<el-input v-model="form.template" placeholder="请输入postgresql指定template" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div style="text-align: right">
|
||||
@@ -285,11 +234,13 @@
|
||||
type: 'success',
|
||||
center: true
|
||||
}
|
||||
).then(() => {
|
||||
)
|
||||
.then(() => {
|
||||
// 点击确认按钮,打开AI配置文档
|
||||
window.open('https://www.gin-vue-admin.com/guide/server/mcp.html', '_blank')
|
||||
router.push({ name: 'Login' })
|
||||
}).catch(() => {
|
||||
})
|
||||
.catch(() => {
|
||||
// 点击取消按钮或关闭弹窗,直接跳转到登录页
|
||||
router.push({ name: 'Login' })
|
||||
})
|
||||
@@ -303,18 +254,15 @@
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.slide-in-fwd-top {
|
||||
-webkit-animation: slide-in-fwd-top 0.4s
|
||||
cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
-webkit-animation: slide-in-fwd-top 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
animation: slide-in-fwd-top 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
}
|
||||
.slide-out-right {
|
||||
-webkit-animation: slide-out-right 0.5s
|
||||
cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
|
||||
-webkit-animation: slide-out-right 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
|
||||
animation: slide-out-right 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
|
||||
}
|
||||
.slide-in-left {
|
||||
-webkit-animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94)
|
||||
both;
|
||||
-webkit-animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
}
|
||||
@-webkit-keyframes slide-in-fwd-top {
|
||||
|
||||
@@ -415,7 +415,7 @@
|
||||
const testData = devices.value.slice(0, 20)
|
||||
|
||||
testData.forEach((device, index) => {
|
||||
console.log(`渲染第 ${index} 个点:`, device)
|
||||
// console.log(`渲染第 ${index} 个点:`, device)
|
||||
|
||||
const marker = new AMap.Marker({
|
||||
position: [device.gatewayLong, device.gatewayLat],
|
||||
|
||||
@@ -137,39 +137,36 @@
|
||||
|
||||
<el-table :data="alarmTableData" border stripe class="alarm-table">
|
||||
<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="设备别名" prop="deviceAlias" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column align="left" label="设备号" prop="deviceNum" width="150" />
|
||||
<el-table-column align="center" label="线路" prop="line" width="80" />
|
||||
<el-table-column align="left" label="报警/预警类型" prop="alarmType" width="120" />
|
||||
<el-table-column align="center" label="报警/预警值(红色)/ 阈值(蓝色)" width="200">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.alarmValue" class="value-red">{{ scope.row.alarmValue }}</span>
|
||||
<span v-if="scope.row.alarmValue && scope.row.threshold"> / </span>
|
||||
<span v-if="scope.row.threshold" class="value-blue">{{ scope.row.threshold }}</span>
|
||||
<span v-if="!scope.row.alarmValue && !scope.row.threshold">/</span>
|
||||
<el-table-column align="left" label="设备别名" prop="mac" />
|
||||
<el-table-column align="left" label="设备号" prop="mac" />
|
||||
<el-table-column align="left" label="线路" prop="nodeNumber">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.nodeNumber == 0">总路</span>
|
||||
<span v-else>线路{{ row.nodeNumber }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="耗时" prop="duration" width="80" />
|
||||
<el-table-column align="center" label="状态" prop="status" width="90">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === '已处理' ? 'success' : 'danger'" size="small" effect="light">
|
||||
{{ scope.row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="报警/预警时间" prop="alarmTime" width="170" />
|
||||
<el-table-column align="center" label="动作" width="100" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button v-if="scope.row.status === '未处理'" type="primary" link @click="handleProcess(scope.row)">
|
||||
处理
|
||||
</el-button>
|
||||
<el-button v-else type="primary" link @click="handleRecall(scope.row)">撤回</el-button>
|
||||
<el-table-column align="left" label="报警类型" prop="alarmType">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.alarmType == '45'">设备事件</span>
|
||||
<span v-else>操作事件</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="报警描述" prop="remark" />
|
||||
<el-table-column align="center" label="报警时间" prop="CreatedAt" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 报警趋势 -->
|
||||
<div class="alarm-trend">
|
||||
<div class="list-header">
|
||||
<div class="list-title">
|
||||
<span class="dot"></span>
|
||||
报警趋势
|
||||
</div>
|
||||
</div>
|
||||
<v-chart :option="alarmTrendOption" autoresize class="alarm-trend-canvas" />
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="projectDialogVisible" destroy-on-close title="更换项目" width="480px">
|
||||
<el-radio-group v-model="selectedProjectId" class="project-radio-group">
|
||||
<el-radio v-for="item in projectList" :key="item.id" :value="item.id" class="project-radio-item">
|
||||
@@ -190,15 +187,23 @@
|
||||
<script setup>
|
||||
import * as serve from '@/api/masterStation/project'
|
||||
import * as equipmentServe from '@/api/masterStation/equipment'
|
||||
import { useProjectStore } from '@/pinia/modules/project'
|
||||
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import { FullScreen, Monitor, Connection, Bell, OfficeBuilding, Location } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { use } from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { LineChart } from 'echarts/charts'
|
||||
import { TooltipComponent, GridComponent } from 'echarts/components'
|
||||
import VChart from 'vue-echarts'
|
||||
|
||||
use([CanvasRenderer, LineChart, TooltipComponent, GridComponent])
|
||||
|
||||
defineOptions({
|
||||
name: 'MasterStationProject'
|
||||
})
|
||||
|
||||
const PROJECT_STORAGE_KEY = 'masterStation_current_project_id'
|
||||
const projectStore = useProjectStore()
|
||||
const MAP_ZOOM = 13
|
||||
const MAP_ZOOM_OUT = 9
|
||||
const MAP_STEP_DURATION = 350
|
||||
@@ -276,12 +281,13 @@
|
||||
if (!project) return
|
||||
currentProject.value = project
|
||||
selectedProjectId.value = project.id
|
||||
projectStore.setCurrentProject(project)
|
||||
updateProjectMarker(project)
|
||||
moveMapToProject(project)
|
||||
fetchAlarmSituationSummary()
|
||||
refreshAlarmData()
|
||||
}
|
||||
|
||||
const fetchAlarmSituationSummary = () => {
|
||||
const getAlarmSituationSummary = () => {
|
||||
if (!currentProject.value.id) return
|
||||
serve.getAlarmSituationSummary({ projectId: currentProject.value.id }).then((res) => {
|
||||
if (res.code !== 0 || !res.data) return
|
||||
@@ -300,9 +306,88 @@
|
||||
})
|
||||
}
|
||||
|
||||
const getAlarmRecordListByPage = () => {
|
||||
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 buildAlarmTrendOption = (dates = [], alarmCounts = []) => ({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
borderColor: '#e4e7ed',
|
||||
textStyle: { color: '#303133' }
|
||||
},
|
||||
grid: {
|
||||
left: 48,
|
||||
right: 24,
|
||||
top: 24,
|
||||
bottom: 32
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dates,
|
||||
boundaryGap: false,
|
||||
axisLine: { lineStyle: { color: '#e4e7ed' } },
|
||||
axisLabel: { color: '#909399', fontSize: 11 }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
minInterval: 1,
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
splitLine: { lineStyle: { color: '#f0f2f5' } },
|
||||
axisLabel: { color: '#909399', fontSize: 11 }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '报警次数',
|
||||
type: 'line',
|
||||
data: alarmCounts,
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: { color: '#f56c6c', width: 2 },
|
||||
itemStyle: { color: '#f56c6c' },
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(245, 108, 108, 0.25)' },
|
||||
{ offset: 1, color: 'rgba(245, 108, 108, 0)' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const alarmTrendOption = ref(buildAlarmTrendOption())
|
||||
|
||||
const getAlarmTrendProcessingRate = () => {
|
||||
if (!currentProject.value.id) return
|
||||
serve.getAlarmTrendProcessingRate({ projectId: currentProject.value.id }).then((res) => {
|
||||
if (res.code !== 0 || !res.data) return
|
||||
alarmTrendOption.value = buildAlarmTrendOption(res.data.dates || [], res.data.alarmCounts || [])
|
||||
})
|
||||
}
|
||||
|
||||
const refreshAlarmData = () => {
|
||||
if (!currentProject.value.id) return
|
||||
getAlarmSituationSummary()
|
||||
getAlarmRecordListByPage()
|
||||
getAlarmTrendProcessingRate()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getProjectList()
|
||||
getAlarmRecordListByPage()
|
||||
nextTick(() => {
|
||||
initMap()
|
||||
if (mapWrapperRef.value) {
|
||||
@@ -313,17 +398,6 @@
|
||||
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) => {
|
||||
@@ -338,8 +412,7 @@
|
||||
})
|
||||
.map(normalizeProject)
|
||||
|
||||
const savedId = Number(localStorage.getItem(PROJECT_STORAGE_KEY))
|
||||
// console.log('savedId', savedId)
|
||||
const savedId = projectStore.getSavedProjectId()
|
||||
const project = projectList.value.find((item) => item.id === savedId) || projectList.value[0]
|
||||
// console.log('project', project)
|
||||
applyCurrentProject(project)
|
||||
@@ -363,10 +436,10 @@
|
||||
const prevProject = { ...currentProject.value }
|
||||
currentProject.value = project
|
||||
selectedProjectId.value = project.id
|
||||
localStorage.setItem(PROJECT_STORAGE_KEY, String(project.id))
|
||||
projectStore.setCurrentProject(project)
|
||||
projectDialogVisible.value = false
|
||||
ElMessage.success(`已切换至:${project.name}`)
|
||||
fetchAlarmSituationSummary()
|
||||
refreshAlarmData()
|
||||
nextTick(() => {
|
||||
animateMapSwitch(prevProject, project)
|
||||
})
|
||||
@@ -379,73 +452,7 @@
|
||||
let mapResizeObserver = null
|
||||
let resizeTimer = null
|
||||
|
||||
const alarmTableData = ref([
|
||||
{
|
||||
index: 1,
|
||||
eventId: '634949976',
|
||||
deviceAlias: 'DEV-98CC4D11AA3C',
|
||||
deviceNum: '98CC4D11AA3C',
|
||||
line: '-',
|
||||
alarmType: '上线提醒',
|
||||
alarmValue: '',
|
||||
threshold: '',
|
||||
duration: '-',
|
||||
status: '已处理',
|
||||
alarmTime: '2026-06-10 14:52:02'
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
eventId: '634924606',
|
||||
deviceAlias: 'DEV-98CC4D11AA3C',
|
||||
deviceNum: '98CC4D11AA3C',
|
||||
line: '线路03',
|
||||
alarmType: '异常分闸',
|
||||
alarmValue: '0',
|
||||
threshold: '100',
|
||||
duration: '2h',
|
||||
status: '已处理',
|
||||
alarmTime: '2026-06-10 14:17:33'
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
eventId: '634924607',
|
||||
deviceAlias: 'DEV-98CC4D11AA3C',
|
||||
deviceNum: '98CC4D11AA3C',
|
||||
line: '-',
|
||||
alarmType: '上线提醒',
|
||||
alarmValue: '',
|
||||
threshold: '',
|
||||
duration: '-',
|
||||
status: '已处理',
|
||||
alarmTime: '2026-06-10 14:17:19'
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
eventId: '634906602',
|
||||
deviceAlias: 'DEV-98CC4D11AA3C',
|
||||
deviceNum: '98CC4D11AA3C',
|
||||
line: '-',
|
||||
alarmType: '上线提醒',
|
||||
alarmValue: '',
|
||||
threshold: '',
|
||||
duration: '-',
|
||||
status: '未处理',
|
||||
alarmTime: '2026-06-10 13:54:02'
|
||||
},
|
||||
{
|
||||
index: 5,
|
||||
eventId: '634898301',
|
||||
deviceAlias: 'DEV-98CC4D2052AE',
|
||||
deviceNum: '98CC4D2052AE',
|
||||
line: '线路01',
|
||||
alarmType: '异常分闸',
|
||||
alarmValue: '120',
|
||||
threshold: '100',
|
||||
duration: '35m',
|
||||
status: '未处理',
|
||||
alarmTime: '2026-06-10 13:20:15'
|
||||
}
|
||||
])
|
||||
const alarmTableData = ref([])
|
||||
|
||||
const createProjectMarkerContent = (name) => {
|
||||
return `
|
||||
@@ -572,6 +579,7 @@
|
||||
|
||||
.overview-card,
|
||||
.alarm-list,
|
||||
.alarm-trend,
|
||||
.alarm-card {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e4e7ed;
|
||||
@@ -979,7 +987,8 @@
|
||||
}
|
||||
|
||||
/* 警情列表 */
|
||||
.alarm-list {
|
||||
.alarm-list,
|
||||
.alarm-trend {
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
}
|
||||
@@ -1017,6 +1026,11 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.alarm-trend-canvas {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- <div class="p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2">
|
||||
<el-radio-group v-model="radio" size="large" fill="#409eff">
|
||||
<el-radio-button label="报警事件" value="New York" />
|
||||
<el-radio-button label="预警事件" value="Washington" />
|
||||
<el-radio-button label="二级报警事件" value="Los Angeles" />
|
||||
</el-radio-group>
|
||||
</div> -->
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="searchForm" :inline="true" :model="searchInfo">
|
||||
<el-form-item label="设备号">
|
||||
<el-input v-model="searchInfo.deviceId" placeholder="设备号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日期">
|
||||
<el-select v-model="dateType" class="date-type-select" @change="handleDateTypeChange">
|
||||
<el-option label="月" value="month" />
|
||||
<el-option label="日" value="day" />
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-if="dateType === 'month'"
|
||||
v-model="dateValue"
|
||||
type="month"
|
||||
value-format="YYYY-MM"
|
||||
placeholder="选择月份"
|
||||
class="date-picker"
|
||||
/>
|
||||
<el-date-picker
|
||||
v-else
|
||||
v-model="dateValue"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
class="date-picker"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="onSubmit"> 查询 </el-button>
|
||||
<el-button icon="refresh" @click="onReset"> 重置 </el-button>
|
||||
@@ -32,8 +47,28 @@
|
||||
<v-chart :option="alarmCountOption" autoresize class="alarm-count-chart" />
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column align="left" label="网关ID" prop="gatewayId" width="200" />
|
||||
</div>
|
||||
|
||||
<!-- 报警列表 -->
|
||||
<div class="alarm-table-box">
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column align="center" label="序号" prop="index" width="60" />
|
||||
<el-table-column align="left" label="设备别名" prop="mac" />
|
||||
<el-table-column align="left" label="设备号" prop="mac" />
|
||||
<el-table-column align="left" label="线路" prop="nodeNumber">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.nodeNumber == 0">总路</span>
|
||||
<span v-else>线路{{ row.nodeNumber }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="报警类型" prop="alarmType">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.alarmType == '45'">设备事件</span>
|
||||
<span v-else>操作事件</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="报警描述" prop="remark" />
|
||||
<el-table-column align="center" label="报警时间" prop="CreatedAt" />
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
@@ -61,20 +96,35 @@
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
// import { id } from 'element-plus/es/locale'
|
||||
import { useAppStore } from '@/pinia'
|
||||
import { useProjectStore } from '@/pinia'
|
||||
|
||||
// 按需注册 ECharts 模块
|
||||
use([CanvasRenderer, PieChart, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
||||
|
||||
const appStore = useAppStore()
|
||||
const projectStore = useProjectStore()
|
||||
|
||||
const searchInfo = ref({
|
||||
username: '',
|
||||
nickname: '',
|
||||
phone: '',
|
||||
email: ''
|
||||
deviceId: ''
|
||||
})
|
||||
const radio = ref('New York')
|
||||
|
||||
const getCurrentMonth = () => {
|
||||
const now = new Date()
|
||||
const y = now.getFullYear()
|
||||
const m = String(now.getMonth() + 1).padStart(2, '0')
|
||||
return `${y}-${m}`
|
||||
}
|
||||
|
||||
const dateType = ref('month')
|
||||
const dateValue = ref(getCurrentMonth())
|
||||
|
||||
const handleDateTypeChange = (type) => {
|
||||
dateValue.value = type === 'month' ? getCurrentMonth() : formatDateKey(new Date())
|
||||
}
|
||||
|
||||
const buildDateParams = () => {
|
||||
if (!dateValue.value) return {}
|
||||
return dateType.value === 'month' ? { month: dateValue.value } : { day: dateValue.value }
|
||||
}
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
@@ -420,8 +470,25 @@
|
||||
// loadChartData()
|
||||
getAlarmCategorySummary()
|
||||
getAlarmTrend()
|
||||
getAlarmRecordListByPage()
|
||||
})
|
||||
|
||||
const getAlarmRecordListByPage = async () => {
|
||||
const res = await serve.getAlarmRecordListByPage({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
projectId: projectStore.currentProject?.id,
|
||||
...searchInfo.value,
|
||||
...buildDateParams()
|
||||
})
|
||||
if (res.code === 0) {
|
||||
tableData.value = res.data.list
|
||||
total.value = res.data.total
|
||||
page.value = res.data.page
|
||||
pageSize.value = res.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
@@ -452,7 +519,9 @@
|
||||
const table = await serve.getAlarmRecordListByPage({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
...searchInfo.value
|
||||
projectId: projectStore.currentProject?.id,
|
||||
...searchInfo.value,
|
||||
...buildDateParams()
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
@@ -471,6 +540,8 @@
|
||||
searchInfo.value = {
|
||||
deviceId: ''
|
||||
}
|
||||
dateType.value = 'month'
|
||||
dateValue.value = getCurrentMonth()
|
||||
getTableData()
|
||||
}
|
||||
</script>
|
||||
@@ -508,4 +579,22 @@
|
||||
width: 100%;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.date-type-select {
|
||||
width: 72px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.alarm-table-box {
|
||||
padding: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- <div class="p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2">
|
||||
<el-radio-group v-model="radio" size="large" fill="#409eff">
|
||||
<el-radio-button label="报警事件" value="New York" />
|
||||
<el-radio-button label="预警事件" value="Washington" />
|
||||
<el-radio-button label="二级报警事件" value="Los Angeles" />
|
||||
</el-radio-group>
|
||||
</div> -->
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="searchForm" :inline="true" :model="searchInfo">
|
||||
<el-form-item label="设备号">
|
||||
<el-input v-model="searchInfo.deviceId" placeholder="设备号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日期">
|
||||
<el-select v-model="dateType" class="date-type-select" @change="handleDateTypeChange">
|
||||
<el-option label="月" value="month" />
|
||||
<el-option label="日" value="day" />
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-if="dateType === 'month'"
|
||||
v-model="dateValue"
|
||||
type="month"
|
||||
value-format="YYYY-MM"
|
||||
placeholder="选择月份"
|
||||
class="date-picker"
|
||||
/>
|
||||
<el-date-picker
|
||||
v-else
|
||||
v-model="dateValue"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
class="date-picker"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="onSubmit"> 查询 </el-button>
|
||||
<el-button icon="refresh" @click="onReset"> 重置 </el-button>
|
||||
@@ -28,8 +43,28 @@
|
||||
<v-chart :option="alarmCountOption" autoresize class="alarm-count-chart" />
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column align="left" label="网关ID" prop="gatewayId" width="200" />
|
||||
</div>
|
||||
|
||||
<!-- 报警列表 -->
|
||||
<div class="alarm-table-box">
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column align="center" label="序号" prop="index" width="60" />
|
||||
<el-table-column align="left" label="设备别名" prop="mac" />
|
||||
<el-table-column align="left" label="设备号" prop="mac" />
|
||||
<el-table-column align="left" label="线路" prop="nodeNumber">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.nodeNumber == 0">总路</span>
|
||||
<span v-else>线路{{ row.nodeNumber }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="报警类型" prop="alarmType">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.alarmType == '45'">设备事件</span>
|
||||
<span v-else>操作事件</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="报警描述" prop="remark" />
|
||||
<el-table-column align="center" label="报警时间" prop="CreatedAt" />
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
@@ -58,20 +93,35 @@
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
// import { id } from 'element-plus/es/locale'
|
||||
import { useAppStore } from '@/pinia'
|
||||
import { useProjectStore } from '@/pinia'
|
||||
|
||||
// 按需注册 ECharts 模块
|
||||
use([CanvasRenderer, PieChart, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
||||
|
||||
const appStore = useAppStore()
|
||||
const projectStore = useProjectStore()
|
||||
|
||||
const searchInfo = ref({
|
||||
username: '',
|
||||
nickname: '',
|
||||
phone: '',
|
||||
email: ''
|
||||
deviceId: ''
|
||||
})
|
||||
const radio = ref('New York')
|
||||
|
||||
const getCurrentMonth = () => {
|
||||
const now = new Date()
|
||||
const y = now.getFullYear()
|
||||
const m = String(now.getMonth() + 1).padStart(2, '0')
|
||||
return `${y}-${m}`
|
||||
}
|
||||
|
||||
const dateType = ref('month')
|
||||
const dateValue = ref(getCurrentMonth())
|
||||
|
||||
const handleDateTypeChange = (type) => {
|
||||
dateValue.value = type === 'month' ? getCurrentMonth() : formatDateKey(new Date())
|
||||
}
|
||||
|
||||
const buildDateParams = () => {
|
||||
if (!dateValue.value) return {}
|
||||
return dateType.value === 'month' ? { month: dateValue.value } : { day: dateValue.value }
|
||||
}
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
@@ -149,45 +199,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 把后端时间字段格式化成 YYYY-MM-DD
|
||||
function formatDate(value) {
|
||||
if (!value) return ''
|
||||
const d = new Date(value)
|
||||
if (isNaN(d.getTime())) return ''
|
||||
return formatDateKey(d)
|
||||
}
|
||||
function formatDateKey(d) {
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${day}`
|
||||
}
|
||||
// X 轴显示用 MM-DD
|
||||
function formatDateLabel(d) {
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${m}-${day}`
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// getTableData()
|
||||
// loadChartData()
|
||||
getAlarmTrend()
|
||||
getAlarmRecordListByPage()
|
||||
})
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
// 折线图
|
||||
const getAlarmTrend = async () => {
|
||||
const res = await serve.getAlarmTrend({ category: '剩余电流报警', alarmType: '剩余电流报警' })
|
||||
const res = await serve.getAlarmTrend({ category: '漏电报警', alarmType: '漏电报警' })
|
||||
if (res.code === 0) {
|
||||
const { dates = [], series = [] } = res.data || {}
|
||||
alarmCountOption.value = buildAlarmTrendOption({ dates, series })
|
||||
@@ -198,8 +224,10 @@
|
||||
const table = await equipmentListServe.getAlarmRecordListByPage({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
alarmType: '剩余电流报警',
|
||||
...searchInfo.value
|
||||
alarmType: '漏电报警',
|
||||
projectId: projectStore.currentProject?.id,
|
||||
...searchInfo.value,
|
||||
...buildDateParams()
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
@@ -208,6 +236,15 @@
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
page.value = 1
|
||||
@@ -218,6 +255,8 @@
|
||||
searchInfo.value = {
|
||||
deviceId: ''
|
||||
}
|
||||
dateType.value = 'month'
|
||||
dateValue.value = getCurrentMonth()
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
</script>
|
||||
@@ -248,4 +287,22 @@
|
||||
width: 100%;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.date-type-select {
|
||||
width: 72px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.alarm-table-box {
|
||||
padding: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- <div class="p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2">
|
||||
<el-radio-group v-model="radio" size="large" fill="#409eff">
|
||||
<el-radio-button label="报警事件" value="New York" />
|
||||
<el-radio-button label="预警事件" value="Washington" />
|
||||
<el-radio-button label="二级报警事件" value="Los Angeles" />
|
||||
</el-radio-group>
|
||||
</div> -->
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="searchForm" :inline="true" :model="searchInfo">
|
||||
<el-form-item label="设备号">
|
||||
<el-input v-model="searchInfo.deviceId" placeholder="设备号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日期">
|
||||
<el-select v-model="dateType" class="date-type-select" @change="handleDateTypeChange">
|
||||
<el-option label="月" value="month" />
|
||||
<el-option label="日" value="day" />
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-if="dateType === 'month'"
|
||||
v-model="dateValue"
|
||||
type="month"
|
||||
value-format="YYYY-MM"
|
||||
placeholder="选择月份"
|
||||
class="date-picker"
|
||||
/>
|
||||
<el-date-picker
|
||||
v-else
|
||||
v-model="dateValue"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
class="date-picker"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="onSubmit"> 查询 </el-button>
|
||||
<el-button icon="refresh" @click="onReset"> 重置 </el-button>
|
||||
@@ -28,8 +43,28 @@
|
||||
<v-chart :option="alarmCountOption" autoresize class="alarm-count-chart" />
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column align="left" label="网关ID" prop="gatewayId" width="200" />
|
||||
</div>
|
||||
|
||||
<!-- 报警列表 -->
|
||||
<div class="alarm-table-box">
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column align="center" label="序号" prop="index" width="60" />
|
||||
<el-table-column align="left" label="设备别名" prop="mac" />
|
||||
<el-table-column align="left" label="设备号" prop="mac" />
|
||||
<el-table-column align="left" label="线路" prop="nodeNumber">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.nodeNumber == 0">总路</span>
|
||||
<span v-else>线路{{ row.nodeNumber }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="报警类型" prop="alarmType">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.alarmType == '45'">设备事件</span>
|
||||
<span v-else>操作事件</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="报警描述" prop="remark" />
|
||||
<el-table-column align="center" label="报警时间" prop="CreatedAt" />
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
@@ -48,6 +83,7 @@
|
||||
|
||||
<script setup>
|
||||
import * as serve from '@/api/securityControl/alarmList'
|
||||
import * as equipmentListServe from '@/api/equipment/list.js'
|
||||
import { use } from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { PieChart, LineChart } from 'echarts/charts'
|
||||
@@ -57,20 +93,35 @@
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
// import { id } from 'element-plus/es/locale'
|
||||
import { useAppStore } from '@/pinia'
|
||||
import { useProjectStore } from '@/pinia'
|
||||
|
||||
// 按需注册 ECharts 模块
|
||||
use([CanvasRenderer, PieChart, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
||||
|
||||
const appStore = useAppStore()
|
||||
const projectStore = useProjectStore()
|
||||
|
||||
const searchInfo = ref({
|
||||
username: '',
|
||||
nickname: '',
|
||||
phone: '',
|
||||
email: ''
|
||||
deviceId: ''
|
||||
})
|
||||
const radio = ref('New York')
|
||||
|
||||
const getCurrentMonth = () => {
|
||||
const now = new Date()
|
||||
const y = now.getFullYear()
|
||||
const m = String(now.getMonth() + 1).padStart(2, '0')
|
||||
return `${y}-${m}`
|
||||
}
|
||||
|
||||
const dateType = ref('month')
|
||||
const dateValue = ref(getCurrentMonth())
|
||||
|
||||
const handleDateTypeChange = (type) => {
|
||||
dateValue.value = type === 'month' ? getCurrentMonth() : formatDateKey(new Date())
|
||||
}
|
||||
|
||||
const buildDateParams = () => {
|
||||
if (!dateValue.value) return {}
|
||||
return dateType.value === 'month' ? { month: dateValue.value } : { day: dateValue.value }
|
||||
}
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
@@ -81,7 +132,7 @@
|
||||
const alarmCountOption = ref(buildAlarmTrendOption({ dates: [], series: [] }))
|
||||
|
||||
// 颜色数组
|
||||
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#1ABC9C', '#A0CFFF', '#B37000']
|
||||
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C']
|
||||
|
||||
// 构建报警趋势折线图 option(基于 dates/series)
|
||||
function buildAlarmTrendOption({ dates = [], series = [] }) {
|
||||
@@ -148,135 +199,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 构建报警数量折线图 option(按日期统计最近 30 天)
|
||||
function buildAlarmCountOption(list) {
|
||||
// 按日期分组(YYYY-MM-DD)
|
||||
const countMap = {}
|
||||
list.forEach((item) => {
|
||||
const date = formatDate(item.CreatedAt || item.createdAt || item.time)
|
||||
if (!date) return
|
||||
countMap[date] = (countMap[date] || 0) + 1
|
||||
})
|
||||
|
||||
// 生成最近 30 天的 X 轴(缺失日期补 0)
|
||||
const xAxisData = []
|
||||
const seriesData = []
|
||||
const today = new Date()
|
||||
for (let i = 29; i >= 0; i--) {
|
||||
const d = new Date(today)
|
||||
d.setDate(today.getDate() - i)
|
||||
const key = formatDateKey(d)
|
||||
xAxisData.push(formatDateLabel(d))
|
||||
seriesData.push(countMap[key] || 0)
|
||||
}
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(15, 23, 42, 0.9)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: { color: '#fff' }
|
||||
},
|
||||
grid: {
|
||||
left: 36,
|
||||
right: 16,
|
||||
top: 16,
|
||||
bottom: 28
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xAxisData,
|
||||
boundaryGap: false,
|
||||
axisLine: { lineStyle: { color: '#e4e7ed' } },
|
||||
axisLabel: { color: '#909399', fontSize: 11 }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
minInterval: 1,
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
splitLine: { lineStyle: { color: '#f0f2f5' } },
|
||||
axisLabel: { color: '#909399', fontSize: 11 }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '报警数量',
|
||||
type: 'line',
|
||||
data: seriesData,
|
||||
symbol: 'none', // 不画数据点,全一条线
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
color: '#409EFF',
|
||||
width: 2
|
||||
},
|
||||
// 折线下面积渐变
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(64, 158, 255, 0.35)' },
|
||||
{ offset: 1, color: 'rgba(64, 158, 255, 0.02)' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// 把后端时间字段格式化成 YYYY-MM-DD
|
||||
function formatDate(value) {
|
||||
if (!value) return ''
|
||||
const d = new Date(value)
|
||||
if (isNaN(d.getTime())) return ''
|
||||
return formatDateKey(d)
|
||||
}
|
||||
function formatDateKey(d) {
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${day}`
|
||||
}
|
||||
// X 轴显示用 MM-DD
|
||||
function formatDateLabel(d) {
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${m}-${day}`
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// getTableData()
|
||||
// loadChartData()
|
||||
getAlarmTrend()
|
||||
getAlarmRecordListByPage()
|
||||
})
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
// 折线图
|
||||
const getAlarmTrend = async () => {
|
||||
const res = await serve.getAlarmTrend({ alarmType: '温度报警' })
|
||||
const res = await serve.getAlarmTrend({ category: '过载报警', alarmType: '过载报警' })
|
||||
if (res.code === 0) {
|
||||
const { dates = [], series = [] } = res.data || {}
|
||||
alarmCountOption.value = buildAlarmTrendOption({ dates, series })
|
||||
}
|
||||
}
|
||||
// 查询
|
||||
const getTableData = async () => {
|
||||
const table = await serve.getAlarmRecordListByPage({
|
||||
const getAlarmRecordListByPage = async () => {
|
||||
const table = await equipmentListServe.getAlarmRecordListByPage({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
...searchInfo.value
|
||||
alarmType: '过载报警',
|
||||
projectId: projectStore.currentProject?.id,
|
||||
...searchInfo.value,
|
||||
...buildDateParams()
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
@@ -285,17 +236,28 @@
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
page.value = 1
|
||||
getTableData()
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
searchInfo.value = {
|
||||
deviceId: ''
|
||||
}
|
||||
getTableData()
|
||||
dateType.value = 'month'
|
||||
dateValue.value = getCurrentMonth()
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -325,4 +287,22 @@
|
||||
width: 100%;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.date-type-select {
|
||||
width: 72px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.alarm-table-box {
|
||||
padding: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
0
src/view/securityControl/voltage
Normal file
0
src/view/securityControl/voltage
Normal file
308
src/view/securityControl/voltageAlarm/index.vue
Normal file
308
src/view/securityControl/voltageAlarm/index.vue
Normal file
@@ -0,0 +1,308 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="searchForm" :inline="true" :model="searchInfo">
|
||||
<el-form-item label="设备号">
|
||||
<el-input v-model="searchInfo.deviceId" placeholder="设备号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日期">
|
||||
<el-select v-model="dateType" class="date-type-select" @change="handleDateTypeChange">
|
||||
<el-option label="月" value="month" />
|
||||
<el-option label="日" value="day" />
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-if="dateType === 'month'"
|
||||
v-model="dateValue"
|
||||
type="month"
|
||||
value-format="YYYY-MM"
|
||||
placeholder="选择月份"
|
||||
class="date-picker"
|
||||
/>
|
||||
<el-date-picker
|
||||
v-else
|
||||
v-model="dateValue"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
class="date-picker"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="onSubmit"> 查询 </el-button>
|
||||
<el-button icon="refresh" @click="onReset"> 重置 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<!-- <el-button type="primary" icon="plus" @click="addUser">新增用户</el-button> -->
|
||||
</div>
|
||||
<div class="echarts-box">
|
||||
<div class="item broken-line">
|
||||
<p class="title">报警数量</p>
|
||||
<v-chart :option="alarmCountOption" autoresize class="alarm-count-chart" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 报警列表 -->
|
||||
<div class="alarm-table-box">
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column align="center" label="序号" prop="index" width="60" />
|
||||
<el-table-column align="left" label="设备别名" prop="mac" />
|
||||
<el-table-column align="left" label="设备号" prop="mac" />
|
||||
<el-table-column align="left" label="线路" prop="nodeNumber">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.nodeNumber == 0">总路</span>
|
||||
<span v-else>线路{{ row.nodeNumber }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="报警类型" prop="alarmType">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.alarmType == '45'">设备事件</span>
|
||||
<span v-else>操作事件</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="报警描述" prop="remark" />
|
||||
<el-table-column align="center" label="报警时间" prop="CreatedAt" />
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import * as serve from '@/api/securityControl/alarmList'
|
||||
import * as equipmentListServe from '@/api/equipment/list.js'
|
||||
import { use } from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { PieChart, LineChart } from 'echarts/charts'
|
||||
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
|
||||
import VChart from 'vue-echarts'
|
||||
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
// import { id } from 'element-plus/es/locale'
|
||||
import { useProjectStore } from '@/pinia'
|
||||
|
||||
// 按需注册 ECharts 模块
|
||||
use([CanvasRenderer, PieChart, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
||||
|
||||
const projectStore = useProjectStore()
|
||||
|
||||
const searchInfo = ref({
|
||||
deviceId: ''
|
||||
})
|
||||
|
||||
const getCurrentMonth = () => {
|
||||
const now = new Date()
|
||||
const y = now.getFullYear()
|
||||
const m = String(now.getMonth() + 1).padStart(2, '0')
|
||||
return `${y}-${m}`
|
||||
}
|
||||
|
||||
const dateType = ref('month')
|
||||
const dateValue = ref(getCurrentMonth())
|
||||
|
||||
const handleDateTypeChange = (type) => {
|
||||
dateValue.value = type === 'month' ? getCurrentMonth() : formatDateKey(new Date())
|
||||
}
|
||||
|
||||
const buildDateParams = () => {
|
||||
if (!dateValue.value) return {}
|
||||
return dateType.value === 'month' ? { month: dateValue.value } : { day: dateValue.value }
|
||||
}
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
|
||||
// 报警趋势折线图配置
|
||||
const alarmCountOption = ref(buildAlarmTrendOption({ dates: [], series: [] }))
|
||||
|
||||
// 颜色数组
|
||||
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C']
|
||||
|
||||
// 构建报警趋势折线图 option(基于 dates/series)
|
||||
function buildAlarmTrendOption({ dates = [], series = [] }) {
|
||||
const seriesData = series.map((item, index) => ({
|
||||
name: item.category,
|
||||
type: 'line',
|
||||
data: item.data || [],
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: colorList[index % colorList.length],
|
||||
width: 2
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: colorList[index % colorList.length] + '40' },
|
||||
{ offset: 1, color: colorList[index % colorList.length] + '00' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(15, 23, 42, 0.9)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: { color: '#fff' }
|
||||
},
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
top: 0,
|
||||
left: 'center',
|
||||
textStyle: { color: '#606266', fontSize: 12 }
|
||||
},
|
||||
grid: {
|
||||
left: 36,
|
||||
right: 16,
|
||||
top: 40,
|
||||
bottom: 28
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dates,
|
||||
boundaryGap: false,
|
||||
axisLine: { lineStyle: { color: '#e4e7ed' } },
|
||||
axisLabel: { color: '#909399', fontSize: 11 }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
minInterval: 1,
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
splitLine: { lineStyle: { color: '#f0f2f5' } },
|
||||
axisLabel: { color: '#909399', fontSize: 11 }
|
||||
},
|
||||
series: seriesData
|
||||
}
|
||||
}
|
||||
|
||||
function formatDateKey(d) {
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${day}`
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getAlarmTrend()
|
||||
getAlarmRecordListByPage()
|
||||
})
|
||||
|
||||
// 折线图
|
||||
const getAlarmTrend = async () => {
|
||||
const res = await serve.getAlarmTrend({ category: '过欠压报警', alarmType: '过欠压报警' })
|
||||
if (res.code === 0) {
|
||||
const { dates = [], series = [] } = res.data || {}
|
||||
alarmCountOption.value = buildAlarmTrendOption({ dates, series })
|
||||
}
|
||||
}
|
||||
// 查询
|
||||
const getAlarmRecordListByPage = async () => {
|
||||
const table = await equipmentListServe.getAlarmRecordListByPage({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
alarmType: '过欠压报警',
|
||||
projectId: projectStore.currentProject?.id,
|
||||
...searchInfo.value,
|
||||
...buildDateParams()
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
page.value = 1
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
searchInfo.value = {
|
||||
deviceId: ''
|
||||
}
|
||||
dateType.value = 'month'
|
||||
dateValue.value = getCurrentMonth()
|
||||
getAlarmRecordListByPage()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.header-img-box {
|
||||
@apply w-52 h-52 border border-solid border-gray-300 rounded-xl flex justify-center items-center cursor-pointer;
|
||||
}
|
||||
|
||||
.gva-table-box {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.echarts-box {
|
||||
display: flex;
|
||||
> .item {
|
||||
height: 251px;
|
||||
|
||||
&.broken-line {
|
||||
flex: 1;
|
||||
}
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
.alarm-count-chart {
|
||||
width: 100%;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.date-type-select {
|
||||
width: 72px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.alarm-table-box {
|
||||
padding: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -65,6 +65,7 @@ export default ({ mode }) => {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
server: {
|
||||
// 如果使用docker-compose开发模式,设置为false
|
||||
open: true,
|
||||
@@ -105,7 +106,10 @@ export default ({ mode }) => {
|
||||
svgBuilder(['./src/plugin/', './src/assets/icons/'], base, outDir, 'assets', NODE_ENV),
|
||||
[Banner(`\n Build based on gin-vue-admin \n Time : ${timestamp}`)],
|
||||
VueFilePathPlugin('./src/pathInfo.json'),
|
||||
UnoCSS()
|
||||
UnoCSS(),
|
||||
vueDevTools({
|
||||
launchEditor: 'cursor' // 关键配置:指定 Cursor
|
||||
})
|
||||
]
|
||||
}
|
||||
return config
|
||||
|
||||
Reference in New Issue
Block a user