完成GPS和监控警告

master
lynn 5 months ago
parent 6aaa72859f
commit ae1aba3583

@ -23,7 +23,6 @@ export function destroyGasConfig(params) {
})
}
export function getGasList(params) {
return request({
url: '/api/admin/gas/index',
@ -32,4 +31,36 @@ export function getGasList(params) {
})
}
export function saveConfig(data) {
return request({
url: '/api/admin/config/save',
method: 'post',
data
})
}
export function getConfigList(data) {
return request({
url: '/api/admin/config/index',
method: 'post',
data
})
}
export function getNotificationsList(params, options = {}) {
return request({
url: '/api/admin/other/notifications-list',
method: 'get',
params,
...options
})
}
export function getMileageStatistics(params, options = {}) {
return request({
url: '/api/admin/gps/mileage-statistics',
method: 'get',
params,
...options
})
}

@ -94,7 +94,8 @@
<script>
import * as echarts from 'echarts'
import { getDrivingOverview, getDrivingTrend, getDrivingRanking } from '@/api/car/statistics'
import qs from 'qs'
import { getMileageStatistics } from '@/api/monitor'
export default {
data() {
@ -139,28 +140,38 @@ export default {
},
async loadData() {
try {
//
const overviewRes = await getDrivingOverview({
type: this.filterForm.type,
date: this.filterForm.date
const params = {}
if (this.filterForm.type === 'month') {
params.month = this.filterForm.date
} else {
params.year = this.filterForm.date
}
const res = await getMileageStatistics(params, {
paramsSerializer: params => qs.stringify(params, { arrayFormat: 'indices' })
})
this.overviewData = overviewRes.data
const data = res || {}
//
const trendRes = await getDrivingTrend({
type: this.filterForm.type,
date: this.filterForm.date
})
this.chartData = trendRes.data
this.updateChart()
//
this.overviewData = {
totalMileage: Number(data.top?.total_mileage) || 0,
avgMileage: Number(data.top?.average_mileage) || 0,
maxMileage: Number(data.top?.highest_mileage) || 0,
minMileage: Number(data.top?.lowest_mileage) || 0
}
//
const rankingRes = await getDrivingRanking({
type: this.filterForm.type,
date: this.filterForm.date,
limit: 10
})
this.rankingData = rankingRes.data
//
this.chartData = {
xAxis: (data.list || []).map(item => item.day),
series: (data.list || []).map(item => Number(item.total_mileage) || 0)
}
//
this.rankingData = (data.ranking || []).map(item => ({
deviceName: item.section_boats?.name || '-',
mileage: Number(item.total_mileage) || 0
}))
this.updateChart()
} catch (error) {
console.error('加载数据失败:', error)
this.$message.error('加载数据失败')

@ -3,8 +3,8 @@
<div class="warning-page">
<div class="filter-header">
<el-form :inline="true" class="filter-form">
<el-form-item label="设备">
<el-input v-model="filterForm.keyword" placeholder="请输入设备号或设备名称" />
<el-form-item label="泵车名称">
<el-input v-model="filterForm.keyword" placeholder="请输入泵车名称" clearable @clear="handleClear" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch"></el-button>
@ -14,22 +14,24 @@
</div>
<el-table :data="tableData" style="width: 100%" border>
<el-table-column prop="device_no" label="泵车编号" width="120" />
<el-table-column prop="device_name" label="泵车名称" width="120" />
<el-table-column label="预警类型" width="150">
<el-table-column label="泵车名称" width="320">
<template slot-scope="scope">
<el-tag :type="getWarningTypeStyle(scope.row.warning_type)">
{{ scope.row.warning_type }}
</el-tag>
{{ scope.row.detail ? scope.row.detail.name : '-' }}
</template>
</el-table-column>
<el-table-column prop="warning_time" label="预警时间" width="180" />
<el-table-column prop="location" label="位置信息" />
<el-table-column prop="status" label="状态" width="100">
<el-table-column label="泵车编号" width="320">
<template slot-scope="scope">
<el-tag :type="getStatusStyle(scope.row.status)">
{{ scope.row.status }}
</el-tag>
{{ scope.row.detail ? scope.row.detail.plate : '-' }}
</template>
</el-table-column>
<el-table-column label="预警内容" width="320">
<template slot-scope="scope">
{{ getWarningContent(scope.row) }}
</template>
</el-table-column>
<el-table-column label="预警时间" width="180">
<template slot-scope="scope">
{{ formatDateTime(scope.row.created_at) }}
</template>
</el-table-column>
</el-table>
@ -50,7 +52,8 @@
</template>
<script>
import { getWarningList, searchWarnings } from '@/api/car/warning'
import qs from 'qs'
import { getNotificationsList } from '@/api/monitor'
export default {
data() {
@ -72,14 +75,18 @@ export default {
async loadData() {
this.loading = true
try {
const response = await getWarningList({
const params = {
page: this.currentPage,
pageSize: this.pageSize
page_size: this.pageSize,
type: 'GpsStatus',
filter: []
}
const response = await getNotificationsList(params, {
paramsSerializer: params => qs.stringify(params, { arrayFormat: 'indices' })
})
this.tableData = response.data
this.total = response.total
this.tableData = response.data || []
this.total = response.total || 0
} catch (error) {
console.error('加载数据失败:', error)
this.$message.error('加载数据失败')
} finally {
this.loading = false
@ -88,58 +95,78 @@ export default {
async handleSearch() {
this.loading = true
try {
if (this.filterForm.keyword) {
const response = await searchWarnings({
keyword: this.filterForm.keyword,
page: this.currentPage,
pageSize: this.pageSize
})
this.tableData = response.data
this.total = response.total
} else {
await this.loadData()
const params = {
page: this.currentPage,
page_size: this.pageSize,
type: 'GpsStatus',
name: this.filterForm.keyword
}
// if (this.filterForm.keyword) {
// params.filter.push({
// key: 'name',
// op: 'like',
// value: this.filterForm.keyword
// })
// }
const response = await getNotificationsList(params, {
paramsSerializer: params => qs.stringify(params, { arrayFormat: 'indices' })
})
this.tableData = response.data || []
this.total = response.total || 0
} catch (error) {
console.error('搜索失败:', error)
this.$message.error('搜索失败')
} finally {
this.loading = false
}
},
resetForm() {
this.filterForm = {
keyword: ''
}
this.filterForm = { keyword: '' }
this.currentPage = 1
this.loadData()
},
handleClear() {
this.resetForm()
},
handleSizeChange(val) {
this.pageSize = val
this.currentPage = 1
this.loadData()
},
handleCurrentChange(val) {
this.currentPage = val
this.loadData()
},
getWarningContent(row) {
try {
if (row.data) {
const data = JSON.parse(row.data)
return data.content || '未知预警'
}
return '未知预警'
} catch (error) {
return '未知预警'
}
},
formatDateTime(dateTimeStr) {
if (!dateTimeStr) return ''
const date = new Date(dateTimeStr)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
},
getWarningTypeStyle(type) {
switch (type) {
case '低电量':
return 'warning' //
return 'warning'
case '低电量预警':
return 'primary' //
return 'primary'
case '低电量超低预警':
return 'danger' //
default:
return ''
}
},
getStatusStyle(status) {
switch (status) {
case '未处理':
return 'danger'
case '处理中':
return 'warning'
case '已处理':
return 'success'
default:
return ''
}

@ -119,6 +119,7 @@ export default {
try {
const response = await getGasList()
const list = response.data ? response.data : []
console.log('原始数据:', list)
// node_id1,3,5
const wantedNodeIds = ['1', '3', '5']
const filtered = wantedNodeIds.map(nodeId => {
@ -127,10 +128,9 @@ export default {
group.sort((a, b) => new Date(b.update_time || b.created_at || 0) - new Date(a.update_time || a.created_at || 0))
return group[0]
}).filter(Boolean)
const nodeIdMap = { '5': '3', '3': '2', '1': '1' }
this.environmentPoints = filtered.map(item => ({
id: item.id,
name: `监控点${nodeIdMap[String(item.node_id)] || item.node_id}`,
name: item.gas_config && item.gas_config.name ? item.gas_config.name : `监控点${item.node_id}`,
status: item.status || '正常',
coConcentration: item.co_concentration != null && item.co_concentration !== '' ? `${item.co_concentration} ppm` : '-',
temperature: item.temperature != null && item.temperature !== '' ? `${item.temperature}` : '-',
@ -138,6 +138,7 @@ export default {
updateTime: item.record_time || ''
}))
} catch (error) {
console.error('环境检测数据获取失败:', error)
this.$message && this.$message.error('环境检测数据获取失败')
}
},

@ -0,0 +1,213 @@
<template>
<div class="container">
<div class="warning-page">
<div class="filter-header">
<el-form :inline="true" class="filter-form">
<el-form-item label="监控点">
<el-input v-model="filterForm.camera_name" placeholder="请输入监控点名称" clearable @clear="handleClear"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch"></el-button>
<el-button @click="resetForm"></el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" style="width: 100%" border>
<el-table-column label="监控点" width="150">
<template slot-scope="scope">
<span>{{ scope.row.detail && scope.row.detail.gas_config ? scope.row.detail.gas_config.name : scope.row.detail.camera_name || '未知监控点' }}</span>
</template>
</el-table-column>
<el-table-column label="设备ID" width="120">
<template slot-scope="scope">
<span>{{ scope.row.detail && scope.row.detail.gas_config ? scope.row.detail.gas_config.device_id : scope.row.detail.device_id || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="节点ID" width="100">
<template slot-scope="scope">
<span>{{ scope.row.detail && scope.row.detail.gas_config ? scope.row.detail.gas_config.node_id : scope.row.detail.node_id || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="预警内容" width="200">
<template slot-scope="scope">
<span>{{ getWarningContent(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column label="监测值" width="280">
<template slot-scope="scope">
<div v-if="scope.row.detail && scope.row.detail.gas_data">
<div>CO浓度: {{ scope.row.detail.gas_data.co_concentration || '-' }} ppm</div>
<div>温度: {{ scope.row.detail.gas_data.temperature || '-' }} </div>
<div>湿度: {{ scope.row.detail.gas_data.humidity || '-' }} %</div>
</div>
<div v-else>
<span>-</span>
</div>
</template>
</el-table-column>
<el-table-column label="阈值信息" width="278">
<template slot-scope="scope">
<div v-if="scope.row.detail && scope.row.detail.gas_config">
<div>CO阈值: {{ scope.row.detail.gas_config.co_threshold }} ppm</div>
<div>温度: {{ scope.row.detail.gas_config.temperature_low_threshold }}-{{ scope.row.detail.gas_config.temperature_high_threshold }}°C</div>
<div>湿度: {{ scope.row.detail.gas_config.humidity_low_threshold }}-{{ scope.row.detail.gas_config.humidity_high_threshold }}%</div>
</div>
<div v-else-if="scope.row.detail">
<div>CO阈值: {{ scope.row.detail.co_threshold }} ppm</div>
<div>温度: {{ scope.row.detail.temperature_low_threshold }}-{{ scope.row.detail.temperature_high_threshold }}°C</div>
<div>湿度: {{ scope.row.detail.humidity_low_threshold }}-{{ scope.row.detail.humidity_high_threshold }}%</div>
</div>
</template>
</el-table-column>
<el-table-column prop="created_at" label="预警时间" width="180">
<template slot-scope="scope">
{{ formatDateTime(scope.row.created_at) }}
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
:current-page="currentPage"
:page-sizes="[10, 20, 30, 50]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</template>
<script>
import qs from 'qs'
import { getNotificationsList } from '@/api/monitor'
export default {
data() {
return {
filterForm: {
camera_name: ''
},
tableData: [],
loading: false,
currentPage: 1,
pageSize: 10,
total: 0
}
},
created() {
this.loadData()
},
methods: {
async loadData() {
this.loading = true
try {
const response = await getNotificationsList({
page: this.currentPage,
pageSize: this.pageSize,
type: 'GasAlarm'
})
this.tableData = response.data || []
this.total = response.total || 0
} catch (error) {
console.error('加载数据失败:', error)
this.$message.error('加载数据失败')
} finally {
this.loading = false
}
},
async handleSearch() {
this.loading = true
try {
const params = {
page: this.currentPage,
page_size: this.pageSize,
type: 'GasAlarm',
name: this.filterForm.camera_name
// filter: [{
// key: 'name',
// op: 'eq',
// value: this.filterForm.camera_name
// }]
}
const response = await getNotificationsList(params, {
paramsSerializer: params => qs.stringify(params, { arrayFormat: 'indices' })
})
this.tableData = response.data || []
this.total = response.total || 0
} catch (error) {
console.error('搜索失败:', error)
this.$message.error('搜索失败')
} finally {
this.loading = false
}
},
resetForm() {
this.filterForm = {
camera_name: ''
}
this.currentPage = 1
this.loadData()
},
handleSizeChange(val) {
this.pageSize = val
this.currentPage = 1
this.loadData()
},
handleCurrentChange(val) {
this.currentPage = val
this.loadData()
},
handleClear() {
this.resetForm()
},
getWarningContent(row) {
try {
if (row.data) {
const data = JSON.parse(row.data)
return data.content || '未知预警'
}
return '未知预警'
} catch (error) {
return '未知预警'
}
},
formatDateTime(dateTimeStr) {
if (!dateTimeStr) return ''
const date = new Date(dateTimeStr)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
}
}
</script>
<style scoped>
.warning-page {
padding: 20px;
}
.filter-header {
margin-bottom: 20px;
background: #fff;
padding: 20px;
border-radius: 4px;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
</style>

@ -19,24 +19,38 @@
<span>监控点 {{ point.name }}</span>
</div>
<!-- 视频配置 -->
<el-form-item :label="`摄像头地址`">
<el-input v-model="point.videoUrl" placeholder="请输入摄像头视频流地址" />
</el-form-item>
<el-form-item label="摄像头名称">
<el-input v-model="point.videoName" placeholder="请输入摄像头名称" />
</el-form-item>
<el-form-item label="设备地址">
<el-input v-model="point.deviceAddress" placeholder="请输入设备地址" />
</el-form-item>
<el-form-item label="节点ID">
<el-input v-model="point.nodeId" placeholder="请输入节点ID" />
</el-form-item>
<!-- 视频与设备信息两行两列布局 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="监控点名称">
<el-input v-model="point.name" placeholder="请输入监控点名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="摄像头地址">
<el-input v-model="point.videoUrl" placeholder="请输入摄像头视频流地址" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="摄像头名称">
<el-input v-model="point.videoName" placeholder="请输入摄像头名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备地址">
<el-input v-model="point.deviceAddress" placeholder="请输入设备地址" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="节点ID">
<el-input v-model="point.nodeId" placeholder="请输入节点ID" />
</el-form-item>
</el-col>
</el-row>
<!-- 环境监测阈值 -->
<el-divider content-position="left">环境监测阈值超过此值将触发预警</el-divider>
@ -51,7 +65,6 @@
/>
<span class="threshold-label">PPM</span>
</el-form-item>
<el-form-item label="温度阈值(℃)">
<el-row>
<el-col :span="11">
@ -126,11 +139,23 @@
<div class="section-title">预警接收设置</div>
<el-form ref="warningForm" :model="warningForm" label-width="120px">
<el-form-item label="预警手机号">
<el-input v-model="warningForm.phoneNumber" placeholder="请输入接收预警信息的手机号">
<template slot="append">
<el-button @click="addReceiver"></el-button>
</template>
</el-input>
<el-row :gutter="10" type="flex" align="middle">
<el-col :span="8">
<el-input
v-model="warningForm.phoneNumber"
placeholder="请输入接收预警信息的手机号"
maxlength="11"
type="tel"
@blur="validatePhone"
/>
</el-col>
<el-col :span="8">
<el-input v-model="warningForm.contactName" placeholder="请输入联系人姓名" />
</el-col>
<el-col :span="4" style="display: flex; align-items: center;">
<el-button @click="addReceiver" style="width: 100%;">添加</el-button>
</el-col>
</el-row>
</el-form-item>
<el-table
@ -139,7 +164,7 @@
style="width: 100%; margin-top: 10px;"
>
<el-table-column label="序号" type="index" width="50" />
<el-table-column prop="phone" label="手机号" />
<el-table-column prop="mobile" label="手机号" />
<el-table-column prop="name" label="联系人" />
<el-table-column label="操作" width="120">
<template slot-scope="scope">
@ -157,8 +182,7 @@
</template>
<script>
import { getGasConfig, saveGasConfig, destroyGasConfig } from '@/api/monitor'
import { getGasConfig, saveGasConfig, destroyGasConfig, saveConfig, getConfigList } from '@/api/monitor'
export default {
name: 'WarningSetting',
data() {
@ -166,6 +190,7 @@ export default {
monitorPoints: [],
warningForm: {
phoneNumber: '',
contactName: '',
receivers: [
{ phone: '13800138000', name: '张三' },
{ phone: '13900139000', name: '李四' }
@ -175,6 +200,7 @@ export default {
}
},
mounted() {
this.loadWarningReceivers()
this.loadMonitorConfig()
},
methods: {
@ -186,6 +212,7 @@ export default {
const list = response.data ? response.data : []
this.monitorPoints = list.map(item => ({
id: item.id,
name: item.name || `监控点${item.id}`,
videoUrl: item.camera_url,
videoName: item.camera_name,
coThreshold: Number(item.co_threshold),
@ -201,28 +228,73 @@ export default {
this.$message.error('加载监控点配置失败')
}
},
addReceiver() {
async loadWarningReceivers() {
try {
const filter = [
{ key: 'id', op: 'eq', value: 1 }
]
const res = await getConfigList({ filter })
const list = res.data ? res.data : []
let receivers = []
if (list.length > 0) {
let value = {}
try {
value = JSON.parse(list[0].value)
} catch (e) {
console.warn('解析配置值失败:', e)
}
if (Array.isArray(value)) {
receivers = value
} else if (value && value.mobile) {
receivers = [{ mobile: value.mobile, name: value.name }]
}
}
this.warningForm.receivers = receivers
} catch (e) {
this.$message && this.$message.error('获取预警联系人失败')
}
},
async addReceiver() {
if (!this.warningForm.phoneNumber) {
this.$message.warning('请输入手机号')
return
}
//
if (!this.warningForm.contactName) {
this.$message.warning('请输入联系人姓名')
return
}
const phoneReg = /^1[3-9]\d{9}$/
if (!phoneReg.test(this.warningForm.phoneNumber)) {
this.$message.error('请输入正确的手机号格式')
return
}
this.warningForm.receivers.push({
phone: this.warningForm.phoneNumber,
name: '联系人' + (this.warningForm.receivers.length + 1)
mobile: this.warningForm.phoneNumber,
name: this.warningForm.contactName
})
try {
await saveConfig({
id: 1,
value: JSON.stringify(this.warningForm.receivers)
})
this.$message.success('添加联系人成功')
} catch (e) {
this.$message.error('添加联系人失败')
}
this.warningForm.phoneNumber = ''
this.warningForm.contactName = ''
},
removeReceiver(index) {
async removeReceiver(index) {
this.warningForm.receivers.splice(index, 1)
try {
await saveConfig({
id: 1,
value: JSON.stringify(this.warningForm.receivers)
})
this.$message.success('删除联系人成功')
} catch (e) {
this.$message.error('删除联系人失败')
}
},
saveSettings() {
// API
@ -241,7 +313,7 @@ export default {
},
addMonitorPoint() {
this.monitorPoints.push({
name: '监控点',
name: `监控点${this.monitorPoints.length + 1}`,
videoName: '',
videoUrl: '',
deviceAddress: '',
@ -259,6 +331,7 @@ export default {
const point = this.monitorPoints[index]
const param = {
id: point.id,
name: point.name,
camera_url: point.videoUrl,
camera_name: point.videoName,
co_threshold: point.coThreshold,
@ -293,7 +366,6 @@ export default {
const pointToDelete = this.monitorPoints[index]
console.log(pointToDelete)
await destroyGasConfig({ id: pointToDelete.id })
this.monitorPoints.splice(index, 1)
this.$message({
type: 'success',
@ -308,6 +380,12 @@ export default {
},
cancelAddMonitorPoint(index) {
this.monitorPoints.splice(index, 1)
},
validatePhone() {
const phoneReg = /^1[3-9]\d{9}$/
if (this.warningForm.phoneNumber && !phoneReg.test(this.warningForm.phoneNumber)) {
this.$message.error('请输入正确的手机号格式')
}
}
}
}

Loading…
Cancel
Save