2025-10-26 22:44:04 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<el-dialog
|
2026-02-15 16:24:29 +08:00
|
|
|
|
v-loading="loading"
|
|
|
|
|
|
width="92%"
|
|
|
|
|
|
:visible.sync="dialogTableVisible"
|
|
|
|
|
|
class="ems-dialog plan-dialog"
|
|
|
|
|
|
title="保护方案"
|
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
|
:show-close="false"
|
2025-10-26 22:44:04 +08:00
|
|
|
|
>
|
|
|
|
|
|
<el-form
|
2026-02-15 16:24:29 +08:00
|
|
|
|
v-loading="loading > 0"
|
|
|
|
|
|
ref="addTempForm"
|
|
|
|
|
|
:model="formData"
|
|
|
|
|
|
:rules="rules"
|
|
|
|
|
|
size="medium"
|
|
|
|
|
|
label-width="120px"
|
|
|
|
|
|
class="plan-form"
|
2025-10-26 22:44:04 +08:00
|
|
|
|
>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
<div class="base-panel">
|
|
|
|
|
|
<div class="panel-title">基础信息</div>
|
|
|
|
|
|
<div class="base-grid">
|
|
|
|
|
|
<el-form-item label="设备保护名称" prop="faultName" class="span-1">
|
|
|
|
|
|
<el-input v-model="formData.faultName" placeholder="请输入" clearable></el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="是否告警" prop="isAlert" class="span-1">
|
|
|
|
|
|
<el-checkbox v-model="formData.isAlert" :true-label="1" :false-label="0">启用告警</el-checkbox>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="告警等级" prop="faultLevel" class="span-1">
|
|
|
|
|
|
<el-radio-group v-model="formData.faultLevel" :disabled="mode === 'edit'">
|
|
|
|
|
|
<el-radio :label="1">等级1</el-radio>
|
|
|
|
|
|
<el-radio :label="2">等级2</el-radio>
|
|
|
|
|
|
<el-radio :label="3">等级3</el-radio>
|
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="处理方案描述" prop="description" class="span-2">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="formData.description"
|
|
|
|
|
|
type="textarea"
|
|
|
|
|
|
:rows="2"
|
|
|
|
|
|
placeholder="请输入"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
></el-input>
|
|
|
|
|
|
</el-form-item>
|
2025-10-26 22:44:04 +08:00
|
|
|
|
</div>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="plan-section">
|
|
|
|
|
|
<div class="section-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="section-title">保护前提</div>
|
|
|
|
|
|
<div class="section-desc">配置触发故障的判定条件和关系</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="section-actions">
|
|
|
|
|
|
<el-button @click.native.prevent="addRow('protectionSettings')" type="primary" size="mini">
|
|
|
|
|
|
新增保护前提
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
2025-10-26 22:44:04 +08:00
|
|
|
|
</div>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="row-card" v-for="(item, index) in protectionSettings" :key="'protectionSettings' + index">
|
|
|
|
|
|
<div class="row-index">{{ index + 1 }}</div>
|
|
|
|
|
|
<div class="row-grid setting-grid">
|
|
|
|
|
|
<div class="field-block field-point">
|
|
|
|
|
|
<div class="field-label">点位</div>
|
|
|
|
|
|
<el-autocomplete
|
|
|
|
|
|
v-model="item.point"
|
|
|
|
|
|
placeholder="请输入点位ID/描述"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
:trigger-on-focus="false"
|
|
|
|
|
|
:debounce="250"
|
|
|
|
|
|
:value-key="'label'"
|
|
|
|
|
|
:fetch-suggestions="(q, c) => querySearchAsync(q, c, index, 'protectionSettings')"
|
|
|
|
|
|
@select="(v) => handleSelect(v, index, 'protectionSettings')"
|
|
|
|
|
|
@blur="() => fillPointMetaByPoint(item.point, index, 'protectionSettings')"
|
|
|
|
|
|
></el-autocomplete>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="field-block">
|
|
|
|
|
|
<div class="field-label">故障比较符</div>
|
|
|
|
|
|
<el-select v-model="item.faultOperator" placeholder="请选择">
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
v-for="(value, key) in comparisonOperatorOptions"
|
|
|
|
|
|
:key="key + 'faultOperator'"
|
|
|
|
|
|
:label="key"
|
|
|
|
|
|
:value="value"
|
|
|
|
|
|
></el-option>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="field-block">
|
|
|
|
|
|
<div class="field-label">故障值</div>
|
|
|
|
|
|
<el-input placeholder="请输入故障值" v-model="item.faultValue"></el-input>
|
2025-10-26 22:44:04 +08:00
|
|
|
|
</div>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="field-block">
|
|
|
|
|
|
<div class="field-label">释放比较符</div>
|
|
|
|
|
|
<el-select v-model="item.releaseOperator" placeholder="请选择">
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
v-for="(value, key) in comparisonOperatorOptions"
|
|
|
|
|
|
:key="key + 'releaseOperator'"
|
|
|
|
|
|
:label="key"
|
|
|
|
|
|
:value="value"
|
|
|
|
|
|
></el-option>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="field-block">
|
|
|
|
|
|
<div class="field-label">释放值</div>
|
|
|
|
|
|
<el-input placeholder="请输入释放值" v-model="item.releaseValue"></el-input>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="field-block">
|
|
|
|
|
|
<div class="field-label">关系</div>
|
|
|
|
|
|
<el-select v-model="item.relationNext" placeholder="请选择">
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
v-for="(value, key) in relationWithPoint"
|
|
|
|
|
|
:key="key + 'relation'"
|
|
|
|
|
|
:label="key"
|
|
|
|
|
|
:value="value"
|
|
|
|
|
|
></el-option>
|
|
|
|
|
|
</el-select>
|
2025-10-26 22:44:04 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
<div class="row-action">
|
|
|
|
|
|
<el-button @click.native.prevent="deleteRow(index, 'protectionSettings')" type="warning" size="mini">
|
|
|
|
|
|
删除
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
2025-10-26 22:44:04 +08:00
|
|
|
|
</div>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
|
|
|
|
|
<el-empty v-if="protectionSettings.length === 0" description="暂无保护前提,请先添加"></el-empty>
|
2025-10-26 22:44:04 +08:00
|
|
|
|
</div>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="plan-section">
|
|
|
|
|
|
<div class="section-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="section-title">保护方案</div>
|
|
|
|
|
|
<div class="section-desc">设置触发后的目标写入点位和值</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="section-actions">
|
|
|
|
|
|
<el-button @click.native.prevent="addRow('protectionPlan')" type="primary" size="mini">
|
|
|
|
|
|
新增保护方案
|
|
|
|
|
|
</el-button>
|
2025-10-26 22:44:04 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="row-card" v-for="(item, index) in protectionPlan" :key="'protectionPlan' + index">
|
|
|
|
|
|
<div class="row-index">{{ index + 1 }}</div>
|
|
|
|
|
|
<div class="row-grid plan-grid">
|
|
|
|
|
|
<div class="field-block field-point">
|
|
|
|
|
|
<div class="field-label">点位</div>
|
|
|
|
|
|
<el-autocomplete
|
|
|
|
|
|
v-model="item.point"
|
|
|
|
|
|
placeholder="请输入点位ID/描述"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
:trigger-on-focus="false"
|
|
|
|
|
|
:debounce="250"
|
|
|
|
|
|
:value-key="'label'"
|
|
|
|
|
|
:fetch-suggestions="(q, c) => querySearchAsync(q, c, index, 'protectionPlan')"
|
|
|
|
|
|
@select="(v) => handleSelect(v, index, 'protectionPlan')"
|
|
|
|
|
|
@blur="() => fillPointMetaByPoint(item.point, index, 'protectionPlan')"
|
|
|
|
|
|
></el-autocomplete>
|
2025-10-26 22:44:04 +08:00
|
|
|
|
</div>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="field-block eq-block">
|
|
|
|
|
|
<div class="field-label">比较</div>
|
|
|
|
|
|
<div class="eq-text">=</div>
|
2025-10-26 22:44:04 +08:00
|
|
|
|
</div>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="field-block">
|
|
|
|
|
|
<div class="field-label">故障值</div>
|
|
|
|
|
|
<el-input placeholder="请输入故障值" v-model="item.value"></el-input>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="row-action">
|
|
|
|
|
|
<el-button @click.native.prevent="deleteRow(index, 'protectionPlan')" type="warning" size="mini">
|
|
|
|
|
|
删除
|
|
|
|
|
|
</el-button>
|
2025-10-26 22:44:04 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
|
|
|
|
|
<el-empty v-if="protectionPlan.length === 0" description="暂无保护方案,请先添加"></el-empty>
|
2025-10-26 22:44:04 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</el-form>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
|
|
|
|
|
<div slot="footer" class="dialog-footer">
|
2025-10-26 22:44:04 +08:00
|
|
|
|
<el-button @click="closeDialog">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="saveDialog">确定</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</template>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
2025-10-26 22:44:04 +08:00
|
|
|
|
<script>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
import { mapState } from "vuex";
|
|
|
|
|
|
import { validText } from "@/utils/validate";
|
|
|
|
|
|
import { addProtectPlan, getPointMatchList, getProtectPlan, updateProtectPlan } from "@/api/ems/site";
|
2025-12-19 21:49:19 +08:00
|
|
|
|
|
2025-10-26 22:44:04 +08:00
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
const validateText = (rule, value, callback) => {
|
|
|
|
|
|
if (value !== "" && !validText(value)) {
|
|
|
|
|
|
callback(new Error("只能输入中文、英文、数字和特殊字符!"));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
callback();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
return {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
mode: "",
|
2025-10-26 22:44:04 +08:00
|
|
|
|
loading: 0,
|
|
|
|
|
|
protectionSettings: [],
|
|
|
|
|
|
protectionPlan: [],
|
|
|
|
|
|
dialogTableVisible: false,
|
|
|
|
|
|
formData: {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
id: "",
|
|
|
|
|
|
siteId: "",
|
|
|
|
|
|
faultName: "",
|
|
|
|
|
|
isAlert: 0,
|
|
|
|
|
|
faultLevel: 1,
|
|
|
|
|
|
faultDelaySeconds: "",
|
|
|
|
|
|
releaseDelaySeconds: "",
|
|
|
|
|
|
description: "",
|
2025-10-26 22:44:04 +08:00
|
|
|
|
},
|
|
|
|
|
|
rules: {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
faultName: [{ required: true, message: "请输入设备保护名称", trigger: "blur" }],
|
|
|
|
|
|
isAlert: [{ required: true, message: "请选择是否告警", trigger: "blur" }],
|
2025-10-26 22:44:04 +08:00
|
|
|
|
description: [
|
2026-02-15 16:24:29 +08:00
|
|
|
|
{ required: true, message: "请输入设备描述", trigger: "blur" },
|
|
|
|
|
|
{ validator: validateText, trigger: "blur" },
|
2025-10-31 21:34:52 +08:00
|
|
|
|
],
|
2025-10-26 22:44:04 +08:00
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
...mapState({
|
2026-02-15 16:24:29 +08:00
|
|
|
|
comparisonOperatorOptions: (state) => state?.ems?.comparisonOperatorOptions || {},
|
2025-10-26 22:44:04 +08:00
|
|
|
|
relationWithPoint: (state) => state?.ems?.relationWithPoint || {},
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
2025-10-31 21:34:52 +08:00
|
|
|
|
methods: {
|
2025-12-19 21:49:19 +08:00
|
|
|
|
open(id, siteId) {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
const selectedSiteId = siteId || "";
|
|
|
|
|
|
this.formData.siteId = selectedSiteId;
|
|
|
|
|
|
this.dialogTableVisible = true;
|
2025-12-19 21:49:19 +08:00
|
|
|
|
if (id) {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
this.formData.id = id;
|
|
|
|
|
|
this.mode = "edit";
|
|
|
|
|
|
getProtectPlan(id).then((response) => {
|
|
|
|
|
|
const data = response?.data || {};
|
2025-10-31 21:34:52 +08:00
|
|
|
|
this.formData = {
|
|
|
|
|
|
id,
|
2026-02-15 16:24:29 +08:00
|
|
|
|
siteId: selectedSiteId || data?.siteId || "",
|
|
|
|
|
|
faultName: data?.faultName || "",
|
|
|
|
|
|
isAlert: data?.isAlert || 0,
|
|
|
|
|
|
faultLevel: data?.faultLevel || 1,
|
|
|
|
|
|
faultDelaySeconds: data?.faultDelaySeconds || "",
|
|
|
|
|
|
releaseDelaySeconds: data?.releaseDelaySeconds || "",
|
|
|
|
|
|
description: data?.description || "",
|
|
|
|
|
|
};
|
|
|
|
|
|
const plan = JSON.parse(data?.protectionPlan || "[]");
|
|
|
|
|
|
const settings = JSON.parse(data?.protectionSettings || "[]");
|
2025-12-19 21:49:19 +08:00
|
|
|
|
this.$nextTick(() => {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
this.protectionPlan.splice(0, 0, ...plan);
|
|
|
|
|
|
this.protectionSettings.splice(0, 0, ...settings);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2025-12-19 21:49:19 +08:00
|
|
|
|
} else {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
this.mode = "add";
|
2025-10-31 21:34:52 +08:00
|
|
|
|
}
|
2025-10-26 22:44:04 +08:00
|
|
|
|
},
|
|
|
|
|
|
addRow(type) {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
const item =
|
|
|
|
|
|
type === "protectionSettings"
|
|
|
|
|
|
? {
|
|
|
|
|
|
deviceId: "",
|
|
|
|
|
|
deviceName: "",
|
|
|
|
|
|
deviceCategory: "",
|
|
|
|
|
|
categoryName: "",
|
|
|
|
|
|
point: "",
|
|
|
|
|
|
pointName: "",
|
|
|
|
|
|
faultValue: "",
|
|
|
|
|
|
releaseValue: "",
|
|
|
|
|
|
faultOperator: "",
|
|
|
|
|
|
releaseOperator: "",
|
|
|
|
|
|
relationNext: "",
|
|
|
|
|
|
}
|
|
|
|
|
|
: {
|
|
|
|
|
|
deviceId: "",
|
|
|
|
|
|
deviceName: "",
|
|
|
|
|
|
deviceCategory: "",
|
|
|
|
|
|
categoryName: "",
|
|
|
|
|
|
point: "",
|
|
|
|
|
|
pointName: "",
|
|
|
|
|
|
value: "",
|
|
|
|
|
|
};
|
|
|
|
|
|
this[type].splice(this[type].length, 0, item);
|
2025-10-26 22:44:04 +08:00
|
|
|
|
},
|
|
|
|
|
|
deleteRow(index, type) {
|
|
|
|
|
|
this[type].splice(index, 1);
|
|
|
|
|
|
},
|
|
|
|
|
|
querySearchAsync(query, cb, index, type) {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
if (!this.formData.siteId) {
|
2025-10-26 22:44:04 +08:00
|
|
|
|
this.$message({
|
|
|
|
|
|
type: "warning",
|
2026-02-15 16:24:29 +08:00
|
|
|
|
message: "请先选择站点",
|
2025-10-26 22:44:04 +08:00
|
|
|
|
});
|
|
|
|
|
|
return cb([]);
|
|
|
|
|
|
}
|
2026-02-15 16:24:29 +08:00
|
|
|
|
getPointMatchList({
|
|
|
|
|
|
siteId: this.formData.siteId,
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 100,
|
|
|
|
|
|
pointId: query || "",
|
|
|
|
|
|
pointDesc: query || "",
|
2025-10-31 21:34:52 +08:00
|
|
|
|
})
|
2026-02-15 16:24:29 +08:00
|
|
|
|
.then((response) => {
|
|
|
|
|
|
const data = response?.rows || [];
|
|
|
|
|
|
cb(
|
|
|
|
|
|
data.map((item) => {
|
|
|
|
|
|
const pointLabel = item.pointId || item.pointName || item.dataKey || "";
|
|
|
|
|
|
const desc = item.pointDesc || "";
|
|
|
|
|
|
return {
|
|
|
|
|
|
value: pointLabel,
|
|
|
|
|
|
pointName: item.pointName || pointLabel,
|
|
|
|
|
|
pointId: item.pointId || pointLabel,
|
|
|
|
|
|
pointDesc: desc,
|
|
|
|
|
|
deviceId: item.deviceId || "",
|
|
|
|
|
|
deviceName: item.deviceName || "",
|
|
|
|
|
|
deviceCategory: item.deviceCategory || "",
|
|
|
|
|
|
categoryName: item.deviceCategory || "",
|
|
|
|
|
|
label: desc ? `${pointLabel}(${desc})` : pointLabel,
|
|
|
|
|
|
};
|
2025-10-28 18:15:10 +08:00
|
|
|
|
})
|
2026-02-15 16:24:29 +08:00
|
|
|
|
);
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => cb([]));
|
2025-10-28 18:15:10 +08:00
|
|
|
|
},
|
2026-02-15 16:24:29 +08:00
|
|
|
|
fillPointMetaByPoint(point, index, type) {
|
|
|
|
|
|
if (!this.formData.siteId || !point) {
|
|
|
|
|
|
return;
|
2025-12-19 21:49:19 +08:00
|
|
|
|
}
|
2026-02-15 16:24:29 +08:00
|
|
|
|
getPointMatchList({
|
2025-12-19 21:49:19 +08:00
|
|
|
|
siteId: this.formData.siteId,
|
2026-02-15 16:24:29 +08:00
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 1,
|
|
|
|
|
|
pointId: point,
|
2025-12-19 21:49:19 +08:00
|
|
|
|
}).then((response) => {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
const row = (response?.rows || [])[0];
|
|
|
|
|
|
if (!row) return;
|
|
|
|
|
|
const line = Object.assign({}, this[type][index], {
|
|
|
|
|
|
point: row.pointId || point,
|
|
|
|
|
|
pointName: row.pointName || row.pointId || point,
|
|
|
|
|
|
deviceId: row.deviceId || this[type][index].deviceId,
|
|
|
|
|
|
deviceName: row.deviceName || this[type][index].deviceName,
|
|
|
|
|
|
deviceCategory: row.deviceCategory || this[type][index].deviceCategory,
|
|
|
|
|
|
categoryName: row.deviceCategory || this[type][index].categoryName,
|
|
|
|
|
|
});
|
|
|
|
|
|
this[type].splice(index, 1, line);
|
|
|
|
|
|
});
|
2025-10-28 18:15:10 +08:00
|
|
|
|
},
|
2026-02-15 16:24:29 +08:00
|
|
|
|
handleSelect(data, index, type) {
|
|
|
|
|
|
const line = Object.assign({}, this[type][index], {
|
|
|
|
|
|
point: data.pointId || data.value,
|
|
|
|
|
|
pointName: data.pointName || data.pointId || data.value,
|
|
|
|
|
|
deviceId: data.deviceId || this[type][index].deviceId,
|
|
|
|
|
|
deviceName: data.deviceName || this[type][index].deviceName,
|
|
|
|
|
|
deviceCategory: data.deviceCategory || this[type][index].deviceCategory,
|
|
|
|
|
|
categoryName: data.categoryName || data.deviceCategory || this[type][index].categoryName,
|
|
|
|
|
|
});
|
|
|
|
|
|
this[type].splice(index, 1, line);
|
2025-10-26 22:44:04 +08:00
|
|
|
|
},
|
|
|
|
|
|
saveDialog() {
|
2025-12-19 21:49:19 +08:00
|
|
|
|
function getToastMsg(name, type, index) {
|
2025-10-31 21:34:52 +08:00
|
|
|
|
return {
|
2025-12-19 21:49:19 +08:00
|
|
|
|
protectionSettings: {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
deviceId: `请选择保护前提第${index}行的设备`,
|
|
|
|
|
|
deviceCategory: `请选择保护前提第${index}行的设备类型`,
|
|
|
|
|
|
categoryName: `请选择保护前提第${index}行的设备类型`,
|
|
|
|
|
|
point: `请选择保护前提第${index}行的点位`,
|
|
|
|
|
|
pointName: `请选择保护前提第${index}行的点位`,
|
|
|
|
|
|
faultValue: `请输入保护前提第${index}行的故障值`,
|
|
|
|
|
|
releaseValue: `请输入保护前提第${index}行的释放值`,
|
|
|
|
|
|
faultOperator: `请选择保护前提第${index}行的故障值比较关系`,
|
|
|
|
|
|
releaseOperator: `请选择保护前提第${index}行的释放值比较关系`,
|
|
|
|
|
|
relationNext: `请选择保护前提第${index}行与下一个点位的关系`,
|
2025-10-31 21:34:52 +08:00
|
|
|
|
},
|
2025-12-19 21:49:19 +08:00
|
|
|
|
protectionPlan: {
|
|
|
|
|
|
deviceId: `请选择保护方案第${index}行的设备`,
|
2026-02-15 16:24:29 +08:00
|
|
|
|
deviceCategory: `请选择保护方案第${index}行的设备类型`,
|
|
|
|
|
|
categoryName: `请选择保护方案第${index}行的设备类型`,
|
2025-10-31 21:34:52 +08:00
|
|
|
|
point: `请选择保护方案第${index}行的点位`,
|
2025-12-19 21:49:19 +08:00
|
|
|
|
pointName: `请选择保护方案第${index}行的点位`,
|
2026-02-15 16:24:29 +08:00
|
|
|
|
value: `请输入保护方案第${index}行的故障值`,
|
|
|
|
|
|
},
|
|
|
|
|
|
}[type][name];
|
2025-10-31 21:34:52 +08:00
|
|
|
|
}
|
2025-12-19 21:49:19 +08:00
|
|
|
|
|
2025-10-26 22:44:04 +08:00
|
|
|
|
this.$refs.addTempForm.validate((valid) => {
|
|
|
|
|
|
if (!valid) return;
|
|
|
|
|
|
const {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
id = "",
|
|
|
|
|
|
siteId = "",
|
|
|
|
|
|
faultName = "",
|
|
|
|
|
|
isAlert = 0,
|
|
|
|
|
|
faultLevel = 1,
|
|
|
|
|
|
faultDelaySeconds = "",
|
|
|
|
|
|
releaseDelaySeconds = "",
|
|
|
|
|
|
description = "",
|
2025-10-26 22:44:04 +08:00
|
|
|
|
} = this.formData;
|
2026-02-15 16:24:29 +08:00
|
|
|
|
const { protectionSettings, protectionPlan } = this;
|
|
|
|
|
|
let protectionSettingsValidateStatus = true;
|
|
|
|
|
|
let protectionPlanValidateStatus = true;
|
|
|
|
|
|
const settingRequiredFields = ["point", "faultOperator", "faultValue", "releaseOperator", "releaseValue"];
|
|
|
|
|
|
const planRequiredFields = ["point", "value"];
|
|
|
|
|
|
|
2025-12-19 21:49:19 +08:00
|
|
|
|
for (let i = 0; i < protectionSettings.length; i++) {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
const row = protectionSettings[i] || {};
|
|
|
|
|
|
for (let inner = 0; inner < settingRequiredFields.length; inner++) {
|
|
|
|
|
|
const key = settingRequiredFields[inner];
|
|
|
|
|
|
const value = row[key];
|
|
|
|
|
|
if (![0, "0"].includes(value) && !value) {
|
|
|
|
|
|
this.$message.error(getToastMsg(key, "protectionSettings", i + 1));
|
|
|
|
|
|
protectionSettingsValidateStatus = false;
|
|
|
|
|
|
break;
|
2025-10-31 21:34:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-15 16:24:29 +08:00
|
|
|
|
if (!protectionSettingsValidateStatus) break;
|
|
|
|
|
|
if (!row.deviceId) {
|
|
|
|
|
|
this.$message.error(`请选择保护前提第${i + 1}行的点位(需从点位列表中选择)`);
|
|
|
|
|
|
protectionSettingsValidateStatus = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (protectionSettings[i + 1] && !row.relationNext) {
|
|
|
|
|
|
this.$message.error(getToastMsg("relationNext", "protectionSettings", i + 1));
|
|
|
|
|
|
protectionSettingsValidateStatus = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2025-10-31 21:34:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 21:49:19 +08:00
|
|
|
|
for (let i = 0; i < protectionPlan.length; i++) {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
const row = protectionPlan[i] || {};
|
|
|
|
|
|
for (let inner = 0; inner < planRequiredFields.length; inner++) {
|
|
|
|
|
|
const key = planRequiredFields[inner];
|
|
|
|
|
|
const value = row[key];
|
|
|
|
|
|
if (![0, "0"].includes(value) && !value) {
|
|
|
|
|
|
this.$message.error(getToastMsg(key, "protectionPlan", i + 1));
|
|
|
|
|
|
protectionPlanValidateStatus = false;
|
|
|
|
|
|
break;
|
2025-10-31 21:34:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-15 16:24:29 +08:00
|
|
|
|
if (!protectionPlanValidateStatus) break;
|
|
|
|
|
|
if (!row.deviceId) {
|
|
|
|
|
|
this.$message.error(`请选择保护方案第${i + 1}行的点位(需从点位列表中选择)`);
|
|
|
|
|
|
protectionPlanValidateStatus = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2025-10-31 21:34:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-15 16:24:29 +08:00
|
|
|
|
if (!protectionSettingsValidateStatus || !protectionPlanValidateStatus) return;
|
|
|
|
|
|
|
|
|
|
|
|
const settings = protectionSettings.map((item) => Object.assign({}, item));
|
|
|
|
|
|
const plan = protectionPlan.map((item) => Object.assign({}, item));
|
2025-10-31 21:34:52 +08:00
|
|
|
|
this.loading += 1;
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
2025-12-19 21:49:19 +08:00
|
|
|
|
const params = {
|
2025-10-31 21:34:52 +08:00
|
|
|
|
siteId,
|
|
|
|
|
|
faultName,
|
|
|
|
|
|
isAlert,
|
|
|
|
|
|
faultLevel,
|
|
|
|
|
|
faultDelaySeconds,
|
|
|
|
|
|
releaseDelaySeconds,
|
|
|
|
|
|
description,
|
2025-12-19 21:49:19 +08:00
|
|
|
|
protectionSettings: JSON.stringify(settings),
|
|
|
|
|
|
protectionPlan: JSON.stringify(plan),
|
2026-02-15 16:24:29 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-31 21:34:52 +08:00
|
|
|
|
if (this.mode === "add") {
|
|
|
|
|
|
addProtectPlan(params)
|
2026-02-15 16:24:29 +08:00
|
|
|
|
.then((response) => {
|
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
|
this.$emit("update");
|
|
|
|
|
|
this.closeDialog();
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
this.loading -= 1;
|
|
|
|
|
|
});
|
2025-10-26 22:44:04 +08:00
|
|
|
|
} else {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
params.id = id;
|
2025-10-31 21:34:52 +08:00
|
|
|
|
updateProtectPlan(params)
|
2026-02-15 16:24:29 +08:00
|
|
|
|
.then((response) => {
|
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
|
this.$emit("update");
|
|
|
|
|
|
this.closeDialog();
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
this.loading -= 1;
|
|
|
|
|
|
});
|
2025-10-26 22:44:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
closeDialog() {
|
|
|
|
|
|
this.$emit("clear");
|
2025-12-19 21:49:19 +08:00
|
|
|
|
for (let key in this.formData) {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
this.formData[key] = key === "isAlert" ? 0 : key === "faultLevel" ? 1 : "";
|
2025-10-28 18:15:10 +08:00
|
|
|
|
}
|
2025-10-26 22:44:04 +08:00
|
|
|
|
this.$refs.addTempForm.resetFields();
|
2026-02-15 16:24:29 +08:00
|
|
|
|
this.$set(this, "protectionSettings", []);
|
|
|
|
|
|
this.$set(this, "protectionPlan", []);
|
2025-10-26 22:44:04 +08:00
|
|
|
|
this.dialogTableVisible = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
2025-10-26 22:44:04 +08:00
|
|
|
|
<style scoped lang="scss">
|
2026-02-15 16:24:29 +08:00
|
|
|
|
.plan-form {
|
|
|
|
|
|
background: #f5f8ff;
|
|
|
|
|
|
border: 1px solid #e8edf7;
|
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.base-panel {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1px solid #e7ecf6;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-title {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #1f2d3d;
|
|
|
|
|
|
margin-bottom: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.base-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
|
gap: 8px 18px;
|
|
|
|
|
|
|
|
|
|
|
|
.span-1 {
|
|
|
|
|
|
grid-column: span 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.span-2 {
|
|
|
|
|
|
grid-column: span 2;
|
2025-10-26 22:44:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-19 21:49:19 +08:00
|
|
|
|
|
2026-02-15 16:24:29 +08:00
|
|
|
|
.plan-section {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1px solid #e7ecf6;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
margin-bottom: 14px;
|
|
|
|
|
|
}
|
2025-12-19 21:49:19 +08:00
|
|
|
|
|
2026-02-15 16:24:29 +08:00
|
|
|
|
.section-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #1f2d3d;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-desc {
|
|
|
|
|
|
color: #7b8999;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
2025-12-19 21:49:19 +08:00
|
|
|
|
|
2026-02-15 16:24:29 +08:00
|
|
|
|
.row-card {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
border: 1px solid #e9edf5;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #d5e2fb;
|
|
|
|
|
|
box-shadow: 0 4px 14px rgba(30, 70, 140, 0.08);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.row-index {
|
|
|
|
|
|
width: 30px;
|
|
|
|
|
|
height: 30px;
|
|
|
|
|
|
border-radius: 15px;
|
|
|
|
|
|
background: #eaf1ff;
|
|
|
|
|
|
color: #2d5ee8;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.row-grid {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.setting-grid {
|
|
|
|
|
|
grid-template-columns: 2fr repeat(5, minmax(120px, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.plan-grid {
|
|
|
|
|
|
grid-template-columns: 2fr 80px 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.field-block {
|
|
|
|
|
|
.field-label {
|
|
|
|
|
|
color: #66788c;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.el-select,
|
|
|
|
|
|
.el-autocomplete,
|
|
|
|
|
|
.el-input {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.eq-block {
|
|
|
|
|
|
.eq-text {
|
|
|
|
|
|
height: 36px;
|
|
|
|
|
|
border: 1px solid #dfe5f0;
|
|
|
|
|
|
border-radius: 4px;
|
2025-10-26 22:44:04 +08:00
|
|
|
|
display: flex;
|
2026-02-15 16:24:29 +08:00
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
color: #4a5b6f;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
background: #f8faff;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-19 21:49:19 +08:00
|
|
|
|
|
2026-02-15 16:24:29 +08:00
|
|
|
|
.row-action {
|
|
|
|
|
|
width: 72px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
2025-12-19 21:49:19 +08:00
|
|
|
|
|
2026-02-15 16:24:29 +08:00
|
|
|
|
.dialog-footer {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
2025-12-19 21:49:19 +08:00
|
|
|
|
|
2026-02-15 16:24:29 +08:00
|
|
|
|
@media (max-width: 1366px) {
|
|
|
|
|
|
.setting-grid {
|
|
|
|
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.plan-grid {
|
|
|
|
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 992px) {
|
|
|
|
|
|
.base-grid {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
|
|
|
|
|
|
.span-1,
|
|
|
|
|
|
.span-2 {
|
|
|
|
|
|
grid-column: span 1;
|
2025-10-26 22:44:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-19 21:49:19 +08:00
|
|
|
|
|
2026-02-15 16:24:29 +08:00
|
|
|
|
.section-head {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-actions {
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.row-card {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.setting-grid,
|
|
|
|
|
|
.plan-grid {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.row-action {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
justify-content: flex-end;
|
2025-10-26 22:44:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|