|
|
|
@ -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>
|
|
|
|
|