feat(ems): 添加报警规则措施步骤功能

- 新增报警规则措施步骤管理页面
- 在记录报警规则页面添加措施步骤弹窗
- 实现报警规则措施步骤的查询、添加、修改、删除等功能
- 优化报警数据页面,增加措施步骤查看功能
boardTest
zch 4 weeks ago
parent 60326cb723
commit 65d40ca12a

@ -0,0 +1,69 @@
import request from '@/utils/request'
// 查询报警规则具体措施步骤列表
export function listEmsAlarmActionStep(query) {
return request({
url: '/ems/base/emsAlarmActionStep/list',
method: 'get',
params: query
})
}
// 查询报警规则具体措施步骤详细
export function getEmsAlarmActionStep(objId) {
return request({
url: '/ems/base/emsAlarmActionStep/' + objId,
method: 'get'
})
}
// 根据报警规则ID查询措施步骤列表包含图片
export function getEmsAlarmActionStepsByRuleId(ruleObjId) {
return request({
url: '/ems/base/emsAlarmActionStep/rule/' + ruleObjId,
method: 'get'
})
}
// 根据报警数据信息查询措施步骤列表(包含图片)
export function getEmsAlarmActionStepsByAlarmInfo(monitorId, cause) {
return request({
url: '/ems/base/emsAlarmActionStep/alarm/' + encodeURIComponent(monitorId) + '/' + encodeURIComponent(cause),
method: 'get'
})
}
// 新增报警规则具体措施步骤
export function addEmsAlarmActionStep(data) {
return request({
url: '/ems/base/emsAlarmActionStep',
method: 'post',
data: data
})
}
// 修改报警规则具体措施步骤
export function updateEmsAlarmActionStep(data) {
return request({
url: '/ems/base/emsAlarmActionStep',
method: 'put',
data: data
})
}
// 删除报警规则具体措施步骤
export function delEmsAlarmActionStep(objId) {
return request({
url: '/ems/base/emsAlarmActionStep/' + objId,
method: 'delete'
})
}
// 批量保存措施步骤(包含图片信息)
export function batchSaveActionSteps(ruleObjId, stepList) {
return request({
url: '/ems/base/emsAlarmActionStep/batchSave/' + ruleObjId,
method: 'post',
data: stepList
})
}

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询报警措施步骤图片列表
export function listEmsAlarmActionStepImage(query) {
return request({
url: '/ems/base/emsAlarmActionStepImage/list',
method: 'get',
params: query
})
}
// 查询报警措施步骤图片详细
export function getEmsAlarmActionStepImage(objId) {
return request({
url: '/ems/base/emsAlarmActionStepImage/' + objId,
method: 'get'
})
}
// 新增报警措施步骤图片
export function addEmsAlarmActionStepImage(data) {
return request({
url: '/ems/base/emsAlarmActionStepImage',
method: 'post',
data: data
})
}
// 修改报警措施步骤图片
export function updateEmsAlarmActionStepImage(data) {
return request({
url: '/ems/base/emsAlarmActionStepImage',
method: 'put',
data: data
})
}
// 删除报警措施步骤图片
export function delEmsAlarmActionStepImage(objId) {
return request({
url: '/ems/base/emsAlarmActionStepImage/' + objId,
method: 'delete'
})
}

@ -50,3 +50,11 @@ export function getEmsRecordAlarmRuleTotalCount() {
method: 'get'
})
}
// 查询异常告警规则
export function getEmsRecordAlarmRuleList(query) {
return request({
url: '/ems/record/recordAlarmRule/getEmsRecordAlarmRuleList',
method: 'get'
})
}

@ -60,55 +60,135 @@
<el-dialog
:title="alarmTitle"
:visible.sync="alarmOpen"
width="800px"
width="1000px"
append-to-body
>
<el-table
v-loading="alarmLoading"
:data="alarmDataList"
@selection-change="handleSelectionChangeAlarm"
>
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="异常设备" align="center" prop="deviceName"/>
<!-- <el-table-column label="异常类型" align="center" prop="alarmType">-->
<!-- <template slot-scope="scope">-->
<!-- <dict-tag :options="dict.type.alarm_type" :value="scope.row.alarmType"/>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="异常类型" align="center" prop="cause"/>
<el-table-column label="异常数据" align="center" prop="alarmData"/>
<el-table-column label="异常状态" align="center" prop="alarmStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.alarm_status" :value="scope.row.alarmStatus"/>
</template>
</el-table-column>
<el-table-column label="记录时间" align="center" prop="collectTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<!-- <el-table-column align="center" class-name="small-padding fixed-width" label="操作" width="100">-->
<!-- <template slot-scope="scope">-->
<!-- <el-button-->
<!-- icon="el-icon-d-arrow-right"-->
<!-- size="small"-->
<!-- type="Info"-->
<!-- @click="jumpProcessing(scope.row)"-->
<!-- >跳转-->
<!-- </el-button>-->
<!-- </template>-->
<!-- </el-table-column>-->
</el-table>
<pagination
v-show="alarmDataTotal > 0"
:total="alarmDataTotal"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageNum"
@pagination="getAlarmDataList"
/>
<el-tabs v-model="activeTab" type="card">
<!-- 告警列表标签页 -->
<el-tab-pane label="告警列表" name="alarmList">
<el-table
v-loading="alarmLoading"
:data="alarmDataList"
@selection-change="handleSelectionChangeAlarm"
@row-click="handleRowClick"
highlight-current-row
>
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="异常设备" align="center" prop="deviceName"/>
<el-table-column label="异常类型" align="center" prop="cause"/>
<el-table-column label="异常数据" align="center" prop="alarmData"/>
<el-table-column label="异常状态" align="center" prop="alarmStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.alarm_status" :value="scope.row.alarmStatus"/>
</template>
</el-table-column>
<el-table-column label="记录时间" align="center" prop="collectTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="120">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click.stop="viewActionSteps(scope.row)"
>查看措施</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="alarmDataTotal > 0"
:total="alarmDataTotal"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getAlarmDataList"
/>
</el-tab-pane>
<!-- 处置措施标签页 -->
<el-tab-pane label="处置措施" name="actionSteps" :disabled="!currentAlarmData">
<div v-if="currentAlarmData">
<el-alert
:title="`设备:${currentAlarmData.deviceName} | 异常类型:${currentAlarmData.cause} | 异常数据:${currentAlarmData.alarmData}`"
type="warning"
:closable="false"
style="margin-bottom: 20px;"
/>
<div v-loading="actionStepsLoading">
<div v-if="actionSteps.length === 0" class="no-steps">
<el-empty description="该告警规则暂无配置处置措施"></el-empty>
</div>
<div v-else>
<el-timeline>
<el-timeline-item
v-for="(step, index) in actionSteps"
:key="step.objId"
:timestamp="`步骤 ${step.stepSequence}`"
placement="top"
type="primary"
size="large"
>
<el-card class="step-card">
<div slot="header" class="clearfix">
<span class="step-title">{{ step.stepSequence }}</span>
</div>
<!-- 步骤描述 -->
<div class="step-description">
<p>{{ step.description }}</p>
</div>
<!-- 步骤图片 -->
<div v-if="step.stepImages && step.stepImages.length > 0" class="step-images">
<div class="images-title">参考图片</div>
<div class="image-gallery">
<div
v-for="image in step.stepImages"
:key="image.objId"
class="image-item"
@click="previewImage(image.imageUrl)"
>
<img :src="image.imageUrl" :alt="image.description" />
<div v-if="image.description" class="image-desc">{{ image.description }}</div>
</div>
</div>
</div>
<!-- 步骤备注 -->
<div v-if="step.remark" class="step-remark">
<el-tag type="info" size="small">备注{{ step.remark }}</el-tag>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="jumpProcessing"> </el-button>
<el-button @click="alarmOpen = false"> </el-button>
<el-button type="primary" @click="jumpProcessing" :disabled="objIds.length === 0">
标记已处理 ({{ objIds.length }})
</el-button>
<el-button @click="closeDialog"> </el-button>
</div>
</el-dialog>
<!-- 图片预览对话框 -->
<el-dialog
title="图片预览"
:visible.sync="imagePreviewVisible"
width="80%"
append-to-body
center
>
<div class="image-preview-container">
<img :src="previewImageUrl" style="max-width: 100%; max-height: 70vh;" />
</div>
</el-dialog>
@ -127,6 +207,7 @@ import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
import settings from '@/settings'
import { handleExceptions, listRecordAlarmData } from '@/api/ems/record/recordAlarmData'
import { getEmsAlarmActionStepsByRuleId, getEmsAlarmActionStepsByAlarmInfo } from '@/api/ems/base/emsAlarmActionStep'
export default {
dicts: ['alarm_type', 'alarm_status'],
@ -150,7 +231,15 @@ export default {
pageNum: 1,
pageSize: 10,
alarmStatus: '1'
}
},
//
currentAlarmData: null,
showActionSteps: false,
actionSteps: [],
actionStepsLoading: false,
activeTab: 'alarmList',
imagePreviewVisible: false,
previewImageUrl: ''
}
},
created() {
@ -263,6 +352,34 @@ export default {
})
}).catch(() => {
})
},
handleRowClick(row) {
this.currentAlarmData = row
this.activeTab = 'actionSteps'
this.getActionSteps(row)
},
getActionSteps(alarmData) {
this.actionStepsLoading = true
getEmsAlarmActionStepsByAlarmInfo(alarmData.monitorId, alarmData.cause).then((response) => {
this.actionSteps = response.data || []
this.actionStepsLoading = false
}).catch(() => {
this.actionSteps = []
this.actionStepsLoading = false
})
},
viewActionSteps(row) {
this.currentAlarmData = row
this.activeTab = 'actionSteps'
this.getActionSteps(row)
},
previewImage(url) {
this.previewImageUrl = url
this.imagePreviewVisible = true
},
closeDialog() {
this.alarmOpen = false
this.activeTab = 'alarmList'
}
}
}
@ -372,4 +489,84 @@ export default {
}
}
}
//
.step-card {
margin-bottom: 20px;
.step-title {
font-weight: bold;
font-size: 16px;
color: #409EFF;
}
.step-description {
margin-bottom: 15px;
p {
line-height: 1.6;
margin: 0;
color: #606266;
}
}
.step-images {
margin-bottom: 15px;
.images-title {
font-size: 14px;
color: #909399;
margin-bottom: 10px;
}
.image-gallery {
display: flex;
flex-wrap: wrap;
gap: 10px;
.image-item {
cursor: pointer;
border: 2px solid #f0f0f0;
border-radius: 4px;
overflow: hidden;
transition: all 0.3s;
max-width: 200px;
&:hover {
border-color: #409EFF;
transform: scale(1.02);
}
img {
width: 100%;
height: 150px;
object-fit: cover;
display: block;
}
.image-desc {
padding: 8px;
font-size: 12px;
color: #666;
background: #f9f9f9;
text-align: center;
}
}
}
}
.step-remark {
margin-top: 10px;
}
}
.no-steps {
text-align: center;
padding: 40px;
}
.image-preview-container {
text-align: center;
}
</style>

@ -0,0 +1,296 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="关联的报警规则ID (逻辑关联 ems_record_alarm_rule.obj_id)" prop="ruleObjId">
<el-input
v-model="queryParams.ruleObjId"
placeholder="请输入关联的报警规则ID (逻辑关联 ems_record_alarm_rule.obj_id)"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="步骤顺序从1开始" prop="stepSequence">
<el-input
v-model="queryParams.stepSequence"
placeholder="请输入步骤顺序从1开始"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['ems/base:emsAlarmActionStep:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['ems/base:emsAlarmActionStep:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['ems/base:emsAlarmActionStep:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['ems/base:emsAlarmActionStep:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="emsAlarmActionStepList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键ID" align="center" prop="objId" />
<el-table-column label="关联的报警规则ID (逻辑关联 ems_record_alarm_rule.obj_id)" align="center" prop="ruleObjId">
</el-table-column>
<el-table-column label="步骤顺序从1开始" align="center" prop="stepSequence">
</el-table-column>
<el-table-column label="步骤文字描述" align="center" prop="description">
</el-table-column>
<el-table-column label="备注" align="center" prop="remark">
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['ems/base:emsAlarmActionStep:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['ems/base:emsAlarmActionStep:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改报警规则具体措施步骤对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="关联的报警规则ID (逻辑关联 ems_record_alarm_rule.obj_id)" prop="ruleObjId">
<!-- <el-input v-model="form.ruleObjId" placeholder="请输入关联的报警规则ID (逻辑关联 ems_record_alarm_rule.obj_id)" /> -->
<el-select v-model="form.ruleObjId" placeholder="请选择关联的报警规则ID (逻辑关联 ems_record_alarm_rule.obj_id)">
<el-option v-for="item in recordAlarmRuleList" :key="item.objId" :label="item.ruleName" :value="item.objId" />
</el-select>
</el-form-item>
<el-form-item label="步骤顺序从1开始" prop="stepSequence">
<el-input v-model="form.stepSequence" placeholder="请输入步骤顺序从1开始" />
</el-form-item>
<el-form-item label="步骤文字描述" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listEmsAlarmActionStep, getEmsAlarmActionStep, delEmsAlarmActionStep, addEmsAlarmActionStep, updateEmsAlarmActionStep } from "@/api/ems/base/emsAlarmActionStep"
import {getEmsRecordAlarmRuleList} from "@/api/ems/record/recordAlarmRule";
export default {
name: "EmsAlarmActionStep",
data() {
return {
//
loading: true,
//
ids: [],
//
recordAlarmRuleList: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
emsAlarmActionStepList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
ruleObjId: null,
stepSequence: null,
description: null,
},
//
form: {},
//
rules: {
ruleObjId: [
{ required: true, message: "关联的报警规则ID (逻辑关联 ems_record_alarm_rule.obj_id)不能为空", trigger: "blur" }
],
stepSequence: [
{ required: true, message: "步骤顺序从1开始不能为空", trigger: "blur" }
],
}
}
},
created() {
this.getList()
this.getRecordAlarmRuleList()
},
methods: {
/** 查询报警规则具体措施步骤列表 */
getList() {
this.loading = true
listEmsAlarmActionStep(this.queryParams).then(response => {
this.emsAlarmActionStepList = response.rows
this.total = response.total
this.loading = false
})
},
getRecordAlarmRuleList(){
getEmsRecordAlarmRuleList().then(response => {
this.recordAlarmRuleList = response.data
})
},
//
cancel() {
this.open = false
this.reset()
},
//
reset() {
this.form = {
objId: null,
ruleObjId: null,
stepSequence: null,
description: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null
}
this.resetForm("form")
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
this.handleQuery()
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.objId)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
this.open = true
this.title = "添加报警规则具体措施步骤"
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
const objId = row.objId || this.ids
getEmsAlarmActionStep(objId).then(response => {
this.form = response.data
this.open = true
this.title = "修改报警规则具体措施步骤"
})
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.objId != null) {
updateEmsAlarmActionStep(this.form).then(response => {
this.$modal.msgSuccess("修改成功")
this.open = false
this.getList()
})
} else {
addEmsAlarmActionStep(this.form).then(response => {
this.$modal.msgSuccess("新增成功")
this.open = false
this.getList()
})
}
}
})
},
/** 删除按钮操作 */
handleDelete(row) {
const objIds = row.objId || this.ids
this.$modal.confirm('是否确认删除报警规则具体措施步骤编号为"' + objIds + '"的数据项?').then(function() {
return delEmsAlarmActionStep(objIds)
}).then(() => {
this.getList()
this.$modal.msgSuccess("删除成功")
}).catch(() => {})
},
/** 导出按钮操作 */
handleExport() {
this.download('ems/base/emsAlarmActionStep/export', {
...this.queryParams
}, `emsAlarmActionStep_${new Date().getTime()}.xlsx`)
}
}
}
</script>

@ -0,0 +1,302 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="关联的措施步骤ID (逻辑关联 ems_alarm_action_step.obj_id)" prop="actionStepObjId">
<el-input
v-model="queryParams.actionStepObjId"
placeholder="请输入关联的措施步骤ID (逻辑关联 ems_alarm_action_step.obj_id)"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="图片存储URL" prop="imageUrl">
<el-input
v-model="queryParams.imageUrl"
placeholder="请输入图片存储URL"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="图片在当前步骤内的顺序从1开始" prop="imageSequence">
<el-input
v-model="queryParams.imageSequence"
placeholder="请输入图片在当前步骤内的顺序从1开始"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['ems/base:emsAlarmActionStepImage:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['ems/base:emsAlarmActionStepImage:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['ems/base:emsAlarmActionStepImage:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['ems/base:emsAlarmActionStepImage:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="emsAlarmActionStepImageList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键ID" align="center" prop="objId" />
<el-table-column label="关联的措施步骤ID (逻辑关联 ems_alarm_action_step.obj_id)" align="center" prop="actionStepObjId">
</el-table-column>
<el-table-column label="图片存储URL" align="center" prop="imageUrl">
</el-table-column>
<el-table-column label="图片在当前步骤内的顺序从1开始" align="center" prop="imageSequence">
</el-table-column>
<el-table-column label="图片描述 (可选)" align="center" prop="description">
</el-table-column>
<el-table-column label="备注" align="center" prop="remark">
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['ems/base:emsAlarmActionStepImage:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['ems/base:emsAlarmActionStepImage:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改报警措施步骤图片对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="关联的措施步骤ID (逻辑关联 ems_alarm_action_step.obj_id)" prop="actionStepObjId">
<el-input v-model="form.actionStepObjId" placeholder="请输入关联的措施步骤ID (逻辑关联 ems_alarm_action_step.obj_id)" />
</el-form-item>
<el-form-item label="图片存储URL" prop="imageUrl">
<el-input v-model="form.imageUrl" placeholder="请输入图片存储URL" />
</el-form-item>
<el-form-item label="图片在当前步骤内的顺序从1开始" prop="imageSequence">
<el-input v-model="form.imageSequence" placeholder="请输入图片在当前步骤内的顺序从1开始" />
</el-form-item>
<el-form-item label="图片描述 (可选)" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listEmsAlarmActionStepImage, getEmsAlarmActionStepImage, delEmsAlarmActionStepImage, addEmsAlarmActionStepImage, updateEmsAlarmActionStepImage } from "@/api/ems/base/emsAlarmActionStepImage"
export default {
name: "EmsAlarmActionStepImage",
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
emsAlarmActionStepImageList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
actionStepObjId: null,
imageUrl: null,
imageSequence: null,
description: null,
},
//
form: {},
//
rules: {
actionStepObjId: [
{ required: true, message: "关联的措施步骤ID (逻辑关联 ems_alarm_action_step.obj_id)不能为空", trigger: "blur" }
],
imageUrl: [
{ required: true, message: "图片存储URL不能为空", trigger: "blur" }
],
imageSequence: [
{ required: true, message: "图片在当前步骤内的顺序从1开始不能为空", trigger: "blur" }
],
}
}
},
created() {
this.getList()
},
methods: {
/** 查询报警措施步骤图片列表 */
getList() {
this.loading = true
listEmsAlarmActionStepImage(this.queryParams).then(response => {
this.emsAlarmActionStepImageList = response.rows
this.total = response.total
this.loading = false
})
},
//
cancel() {
this.open = false
this.reset()
},
//
reset() {
this.form = {
objId: null,
actionStepObjId: null,
imageUrl: null,
imageSequence: null,
description: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null
}
this.resetForm("form")
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
this.handleQuery()
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.objId)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
this.open = true
this.title = "添加报警措施步骤图片"
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
const objId = row.objId || this.ids
getEmsAlarmActionStepImage(objId).then(response => {
this.form = response.data
this.open = true
this.title = "修改报警措施步骤图片"
})
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.objId != null) {
updateEmsAlarmActionStepImage(this.form).then(response => {
this.$modal.msgSuccess("修改成功")
this.open = false
this.getList()
})
} else {
addEmsAlarmActionStepImage(this.form).then(response => {
this.$modal.msgSuccess("新增成功")
this.open = false
this.getList()
})
}
}
})
},
/** 删除按钮操作 */
handleDelete(row) {
const objIds = row.objId || this.ids
this.$modal.confirm('是否确认删除报警措施步骤图片编号为"' + objIds + '"的数据项?').then(function() {
return delEmsAlarmActionStepImage(objIds)
}).then(() => {
this.getList()
this.$modal.msgSuccess("删除成功")
}).catch(() => {})
},
/** 导出按钮操作 */
handleExport() {
this.download('ems/base/emsAlarmActionStepImage/export', {
...this.queryParams
}, `emsAlarmActionStepImage_${new Date().getTime()}.xlsx`)
}
}
}
</script>

@ -105,8 +105,16 @@
<el-table-column label="触发阈值量" align="center" prop="triggerValue" v-if="columns[9].visible"/>
<el-table-column label="通知用户" align="center" prop="notifyUser" v-if="columns[10].visible"/>
<el-table-column label="备注" align="center" prop="cause" v-if="columns[11].visible"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-setting"
@click="handleActionSteps(scope.row)"
v-hasPermi="['ems/record:recordAlarmRule:edit']"
>措施
</el-button>
<el-button
size="mini"
type="text"
@ -196,6 +204,125 @@
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- 措施管理对话框 -->
<el-dialog :title="actionStepsTitle" :visible.sync="actionStepsOpen" width="1200px" append-to-body>
<div class="action-steps-container">
<!-- 步骤列表 -->
<div class="steps-section">
<div class="section-header">
<span class="section-title">处置措施步骤</span>
<el-button type="primary" size="mini" icon="el-icon-plus" @click="addActionStep">
添加步骤
</el-button>
</div>
<div v-if="actionStepsList.length === 0" class="empty-steps">
<el-empty description="暂无处置措施,请点击添加步骤"></el-empty>
</div>
<div v-else class="steps-list">
<div
v-for="(step, index) in actionStepsList"
:key="step.tempId || step.objId"
class="step-item"
>
<div class="step-header">
<span class="step-number">步骤 {{ step.stepSequence }}</span>
<el-button type="danger" size="mini" icon="el-icon-delete" @click="removeActionStep(index)" circle></el-button>
</div>
<div class="step-content">
<el-form :model="step" label-width="100px" size="small">
<el-form-item label="步骤描述">
<el-input
v-model="step.description"
type="textarea"
:rows="3"
placeholder="请输入步骤描述"
></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input
v-model="step.remark"
placeholder="请输入备注信息"
></el-input>
</el-form-item>
<el-form-item label="参考图片">
<div class="image-upload-section">
<!-- 图片上传 -->
<el-upload
:action="uploadAction"
:headers="uploadHeaders"
:on-success="(res, file, fileList) => handleImageUploadSuccess(res, file, fileList, index)"
:before-upload="beforeImageUpload"
:show-file-list="false"
accept="image/*"
drag
multiple
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将图片拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传jpg/png文件且不超过2MB</div>
</el-upload>
<!-- 图片列表 -->
<div v-if="step.stepImages && step.stepImages.length > 0" class="image-list">
<div
v-for="(image, imgIndex) in step.stepImages"
:key="image.tempId || image.objId"
class="image-item"
>
<div class="image-preview" @click="previewImage(image.imageUrl)">
<img :src="image.imageUrl" alt="预览图" />
</div>
<div class="image-info">
<el-input
v-model="image.description"
placeholder="图片描述"
size="mini"
></el-input>
<el-button
type="danger"
size="mini"
icon="el-icon-delete"
@click="removeStepImage(index, imgIndex)"
circle
></el-button>
</div>
</div>
</div>
</div>
</el-form-item>
</el-form>
</div>
</div>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="saveActionSteps" :loading="actionStepsSaving">
保存措施
</el-button>
<el-button @click="cancelActionSteps"></el-button>
</div>
</el-dialog>
<!-- 图片预览对话框 -->
<el-dialog
title="图片预览"
:visible.sync="imagePreviewVisible"
width="80%"
append-to-body
center
>
<div class="image-preview-container">
<img :src="previewImageUrl" style="max-width: 100%; max-height: 70vh;" />
</div>
</el-dialog>
</div>
</template>
@ -208,6 +335,8 @@ import {
updateRecordAlarmRule
} from '@/api/ems/record/recordAlarmRule'
import {listBaseMonitorInfo} from "@/api/ems/base/baseMonitorInfo";
import {getEmsAlarmActionStepsByRuleId, batchSaveActionSteps} from '@/api/ems/base/emsAlarmActionStep'
import {getToken} from '@/utils/auth'
export default {
name: 'RecordAlarmRule',
@ -276,7 +405,18 @@ export default {
{ key: 13, label: `创建时间`, visible: true },
{ key: 14, label: `更新人`, visible: false },
{ key: 15, label: `更新时间`, visible: false }
]
],
actionStepsTitle: '',
actionStepsOpen: false,
actionStepsList: [],
actionStepsSaving: false,
currentRuleObjId: '',
uploadAction: process.env.VUE_APP_BASE_API + '/common/upload',
uploadHeaders: {
Authorization: 'Bearer ' + getToken()
},
previewImageUrl: '',
imagePreviewVisible: false
}
},
created() {
@ -416,6 +556,266 @@ export default {
this.form.monitorField = null; //
}
},
handleActionSteps(row) {
this.actionStepsTitle = '措施管理 - ' + row.ruleName;
this.currentRuleObjId = row.objId;
this.actionStepsOpen = true;
this.getActionSteps(row.objId);
},
getActionSteps(ruleObjId) {
getEmsAlarmActionStepsByRuleId(ruleObjId).then(response => {
this.actionStepsList = response.data || [];
// stepImages
this.actionStepsList.forEach(step => {
if (!step.stepImages) {
step.stepImages = [];
}
});
}).catch(() => {
this.actionStepsList = [];
});
},
addActionStep() {
const newSequence = this.actionStepsList.length > 0
? Math.max(...this.actionStepsList.map(s => s.stepSequence || 0)) + 1
: 1;
const newStep = {
tempId: Date.now() + Math.random(), // ID
ruleObjId: this.currentRuleObjId,
stepSequence: newSequence,
description: '',
remark: '',
stepImages: []
};
this.actionStepsList.push(newStep);
},
removeActionStep(index) {
this.$confirm('确定要删除这个步骤吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.actionStepsList.splice(index, 1);
//
this.actionStepsList.forEach((step, idx) => {
step.stepSequence = idx + 1;
});
});
},
handleImageUploadSuccess(res, file, fileList, stepIndex) {
if (res.code === 200) {
const step = this.actionStepsList[stepIndex];
if (!step.stepImages) {
step.stepImages = [];
}
const newImage = {
tempId: Date.now() + Math.random(),
imageUrl: res.url,
imageSequence: step.stepImages.length + 1,
description: ''
};
step.stepImages.push(newImage);
this.$message.success('图片上传成功');
} else {
this.$message.error('图片上传失败:' + res.msg);
}
},
beforeImageUpload(file) {
const isImage = file.type.indexOf('image') !== -1;
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isImage) {
this.$message.error('只能上传图片文件!');
return false;
}
if (!isLt2M) {
this.$message.error('上传文件大小不能超过 2MB!');
return false;
}
return true;
},
removeStepImage(stepIndex, imgIndex) {
this.$confirm('确定要删除这张图片吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.actionStepsList[stepIndex].stepImages.splice(imgIndex, 1);
//
this.actionStepsList[stepIndex].stepImages.forEach((img, idx) => {
img.imageSequence = idx + 1;
});
});
},
saveActionSteps() {
//
for (let i = 0; i < this.actionStepsList.length; i++) {
const step = this.actionStepsList[i];
if (!step.description || step.description.trim() === '') {
this.$message.warning(`步骤 ${step.stepSequence} 的描述不能为空`);
return;
}
}
this.actionStepsSaving = true;
// ID
const stepsToSave = this.actionStepsList.map(step => {
const stepData = { ...step };
delete stepData.tempId;
if (stepData.stepImages && stepData.stepImages.length > 0) {
stepData.stepImages = stepData.stepImages.map(img => {
const imgData = { ...img };
delete imgData.tempId;
return imgData;
});
}
return stepData;
});
batchSaveActionSteps(this.currentRuleObjId, stepsToSave).then(response => {
this.$message.success('措施步骤保存成功');
this.actionStepsOpen = false;
this.actionStepsSaving = false;
}).catch(() => {
this.actionStepsSaving = false;
});
},
cancelActionSteps() {
this.actionStepsOpen = false;
this.actionStepsList = [];
this.currentRuleObjId = '';
},
previewImage(url) {
this.previewImageUrl = url;
this.imagePreviewVisible = true;
}
}
}
</script>
<style lang="scss" scoped>
.action-steps-container {
padding: 20px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #409EFF;
}
.section-title {
font-size: 18px;
font-weight: bold;
color: #409EFF;
}
.empty-steps {
text-align: center;
padding: 60px 0;
}
.steps-list {
.step-item {
margin-bottom: 30px;
border: 1px solid #e4e7ed;
border-radius: 8px;
background: #fafafa;
.step-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: #f0f9ff;
border-bottom: 1px solid #e4e7ed;
border-radius: 8px 8px 0 0;
.step-number {
font-size: 16px;
font-weight: bold;
color: #409EFF;
}
}
.step-content {
padding: 20px;
}
}
}
.image-upload-section {
.el-upload-dragger {
width: 100%;
height: 120px;
margin-bottom: 20px;
}
.image-list {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 15px;
.image-item {
width: 200px;
border: 1px solid #e4e7ed;
border-radius: 6px;
overflow: hidden;
background: white;
.image-preview {
height: 150px;
cursor: pointer;
position: relative;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s;
}
&:hover img {
transform: scale(1.05);
}
}
.image-info {
padding: 10px;
display: flex;
align-items: center;
gap: 8px;
.el-input {
flex: 1;
}
}
}
}
}
.image-preview-container {
text-align: center;
img {
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
.dialog-footer {
text-align: right;
}
</style>

@ -345,18 +345,18 @@ export default {
}
//
if (device.temperature && (device.temperature > 40 || device.temperature < -10)) {
return 'error'
}
if (device.humidity && (device.humidity > 90 || device.humidity < 10)) {
return 'warning'
}
if (device.noise && device.noise > 80) {
return 'warning'
}
if (device.concentration && device.concentration > 10) {
return 'error'
}
// if (device.temperature && (device.temperature > 40 || device.temperature < -10)) {
// return 'error'
// }
// if (device.humidity && (device.humidity > 90 || device.humidity < 10)) {
// return 'warning'
// }
// if (device.noise && device.noise > 80) {
// return 'warning'
// }
// if (device.concentration && device.concentration > 10) {
// return 'error'
// }
return 'normal'
},
@ -365,8 +365,8 @@ export default {
const status = this.getDeviceStatus(device)
const statusMap = {
'normal': '正常',
'warning': '警告',
'error': '异常',
// 'warning': '',
// 'error': '',
'offline': '离线'
}
return statusMap[status] || '未知'

Loading…
Cancel
Save