重构
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-loading="loading"
|
||||
width="92%"
|
||||
width="42%"
|
||||
:visible.sync="dialogTableVisible"
|
||||
class="ems-dialog plan-dialog"
|
||||
title="保护方案"
|
||||
@ -18,7 +18,7 @@
|
||||
class="plan-form"
|
||||
>
|
||||
<div class="base-panel">
|
||||
<div class="panel-title">基础信息</div>
|
||||
<div class="card-legend">基础信息</div>
|
||||
<div class="base-grid">
|
||||
<el-form-item label="设备保护名称" prop="faultName" class="span-1">
|
||||
<el-input v-model="formData.faultName" placeholder="请输入" clearable></el-input>
|
||||
@ -26,7 +26,7 @@
|
||||
<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-form-item label="告警等级" prop="faultLevel" class="span-2 level-row">
|
||||
<el-radio-group v-model="formData.faultLevel" :disabled="mode === 'edit'">
|
||||
<el-radio :label="1">等级1</el-radio>
|
||||
<el-radio :label="2">等级2</el-radio>
|
||||
@ -46,34 +46,43 @@
|
||||
</div>
|
||||
|
||||
<div class="plan-section">
|
||||
<div class="card-legend">故障保护</div>
|
||||
<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 @click.native.prevent="addRow('faultProtectionSettings')" type="primary" size="mini">
|
||||
新增故障保护
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-card" v-for="(item, index) in protectionSettings" :key="'protectionSettings' + index">
|
||||
<div class="row-card" v-for="(item, index) in faultProtectionSettings" :key="'faultProtectionSettings' + index">
|
||||
<div class="row-index">{{ index + 1 }}</div>
|
||||
<div class="row-grid setting-grid">
|
||||
<div class="row-grid trigger-grid">
|
||||
<div class="field-block field-point">
|
||||
<div class="field-label">点位</div>
|
||||
<el-autocomplete
|
||||
<el-select
|
||||
v-model="item.point"
|
||||
placeholder="请输入点位ID/描述"
|
||||
filterable
|
||||
remote
|
||||
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>
|
||||
reserve-keyword
|
||||
placeholder="支持关键字搜索,展开可查看点位列表"
|
||||
:no-data-text="'暂无匹配点位'"
|
||||
:loading="item.pointLoading"
|
||||
@change="(value) => handlePointChange(index, 'faultProtectionSettings', value)"
|
||||
@visible-change="(visible) => handlePointDropdownVisible(index, 'faultProtectionSettings', visible)"
|
||||
:remote-method="(query) => remotePointSearch(index, 'faultProtectionSettings', query)"
|
||||
>
|
||||
<el-option
|
||||
v-for="pointItem in item.pointOptions"
|
||||
:key="'fault-point-' + index + '-' + pointItem.value"
|
||||
:label="pointItem.label"
|
||||
:value="pointItem.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="field-block">
|
||||
@ -93,6 +102,68 @@
|
||||
<el-input placeholder="请输入故障值" v-model="item.faultValue"></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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-action">
|
||||
<el-button @click.native.prevent="deleteRow(index, 'faultProtectionSettings')" type="warning" size="mini">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-if="faultProtectionSettings.length === 0" description="暂无故障保护,请先添加"></el-empty>
|
||||
</div>
|
||||
|
||||
<div class="plan-section">
|
||||
<div class="card-legend">释放保护</div>
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<div class="section-desc">配置故障释放的判定条件和关系</div>
|
||||
</div>
|
||||
<div class="section-actions">
|
||||
<el-button @click.native.prevent="addRow('releaseProtectionSettings')" type="primary" size="mini">
|
||||
新增释放保护
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-card" v-for="(item, index) in releaseProtectionSettings" :key="'releaseProtectionSettings' + index">
|
||||
<div class="row-index">{{ index + 1 }}</div>
|
||||
<div class="row-grid trigger-grid">
|
||||
<div class="field-block field-point">
|
||||
<div class="field-label">点位</div>
|
||||
<el-select
|
||||
v-model="item.point"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
placeholder="支持关键字搜索,展开可查看点位列表"
|
||||
:no-data-text="'暂无匹配点位'"
|
||||
:loading="item.pointLoading"
|
||||
@change="(value) => handlePointChange(index, 'releaseProtectionSettings', value)"
|
||||
@visible-change="(visible) => handlePointDropdownVisible(index, 'releaseProtectionSettings', visible)"
|
||||
:remote-method="(query) => remotePointSearch(index, 'releaseProtectionSettings', query)"
|
||||
>
|
||||
<el-option
|
||||
v-for="pointItem in item.pointOptions"
|
||||
:key="'release-point-' + index + '-' + pointItem.value"
|
||||
:label="pointItem.label"
|
||||
:value="pointItem.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="field-block">
|
||||
<div class="field-label">释放比较符</div>
|
||||
<el-select v-model="item.releaseOperator" placeholder="请选择">
|
||||
@ -115,7 +186,7 @@
|
||||
<el-select v-model="item.relationNext" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(value, key) in relationWithPoint"
|
||||
:key="key + 'relation'"
|
||||
:key="key + 'releaseRelation'"
|
||||
:label="key"
|
||||
:value="value"
|
||||
></el-option>
|
||||
@ -123,24 +194,24 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-action">
|
||||
<el-button @click.native.prevent="deleteRow(index, 'protectionSettings')" type="warning" size="mini">
|
||||
<el-button @click.native.prevent="deleteRow(index, 'releaseProtectionSettings')" type="warning" size="mini">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-if="protectionSettings.length === 0" description="暂无保护前提,请先添加"></el-empty>
|
||||
<el-empty v-if="releaseProtectionSettings.length === 0" description="暂无释放保护,请先添加"></el-empty>
|
||||
</div>
|
||||
|
||||
<div class="plan-section">
|
||||
<div class="card-legend">执行保护</div>
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<div class="section-title">保护方案</div>
|
||||
<div class="section-desc">设置触发后的目标写入点位和值</div>
|
||||
<div class="section-desc">设置触发后的执行动作(降功率、停机、禁充放等)</div>
|
||||
</div>
|
||||
<div class="section-actions">
|
||||
<el-button @click.native.prevent="addRow('protectionPlan')" type="primary" size="mini">
|
||||
新增保护方案
|
||||
新增执行保护
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -148,29 +219,26 @@
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="field-block eq-block">
|
||||
<div class="field-label">比较</div>
|
||||
<div class="eq-text">=</div>
|
||||
</div>
|
||||
|
||||
<div class="field-block">
|
||||
<div class="field-label">故障值</div>
|
||||
<el-input placeholder="请输入故障值" v-model="item.value"></el-input>
|
||||
<div class="field-label">执行动作</div>
|
||||
<el-select
|
||||
v-model="item.action"
|
||||
clearable
|
||||
placeholder="请选择执行动作"
|
||||
@change="(value) => handleProtectionActionChange(index, value)"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in protectionCapabilityOptions"
|
||||
:key="'plan-action-' + index + '-' + opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="field-block" v-if="isDerateAction(item.action)">
|
||||
<div class="field-label">降功率比例(%)</div>
|
||||
<el-input placeholder="请输入0-100" v-model="item.value"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-action">
|
||||
@ -180,7 +248,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-if="protectionPlan.length === 0" description="暂无保护方案,请先添加"></el-empty>
|
||||
<el-empty v-if="protectionPlan.length === 0" description="暂无执行保护,请先添加"></el-empty>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
@ -208,8 +276,19 @@ export default {
|
||||
return {
|
||||
mode: "",
|
||||
loading: 0,
|
||||
protectionSettings: [],
|
||||
faultProtectionSettings: [],
|
||||
releaseProtectionSettings: [],
|
||||
protectionPlan: [],
|
||||
protectionCapabilityOptions: [
|
||||
{ label: "降功率", value: "derate", requireValue: true },
|
||||
{ label: "关机/停机/切断", value: "shutdown", requireValue: false },
|
||||
{ label: "禁止充电", value: "forbid_charge", requireValue: false },
|
||||
{ label: "允许放电", value: "allow_discharge", requireValue: false },
|
||||
{ label: "禁止放电", value: "forbid_discharge", requireValue: false },
|
||||
{ label: "允许充电", value: "allow_charge", requireValue: false },
|
||||
{ label: "禁止充放电", value: "forbid_charge_discharge", requireValue: false },
|
||||
{ label: "待机", value: "standby", requireValue: false },
|
||||
],
|
||||
dialogTableVisible: false,
|
||||
formData: {
|
||||
id: "",
|
||||
@ -257,140 +336,313 @@ export default {
|
||||
releaseDelaySeconds: data?.releaseDelaySeconds || "",
|
||||
description: data?.description || "",
|
||||
};
|
||||
const plan = JSON.parse(data?.protectionPlan || "[]");
|
||||
const settings = JSON.parse(data?.protectionSettings || "[]");
|
||||
const planRaw = JSON.parse(data?.protectionPlan || "[]");
|
||||
const plan = Array.isArray(planRaw) ? planRaw : planRaw ? [planRaw] : [];
|
||||
const settingsRaw = JSON.parse(data?.protectionSettings || "[]");
|
||||
const settings =
|
||||
Array.isArray(settingsRaw)
|
||||
? {
|
||||
faultSettings: settingsRaw.map((item) => Object.assign({}, item)),
|
||||
releaseSettings: settingsRaw.map((item) => Object.assign({}, item)),
|
||||
}
|
||||
: {
|
||||
faultSettings: Array.isArray(settingsRaw?.faultSettings) ? settingsRaw.faultSettings : [],
|
||||
releaseSettings: Array.isArray(settingsRaw?.releaseSettings) ? settingsRaw.releaseSettings : [],
|
||||
};
|
||||
this.$nextTick(() => {
|
||||
this.protectionPlan.splice(0, 0, ...plan);
|
||||
this.protectionSettings.splice(0, 0, ...settings);
|
||||
this.protectionPlan.splice(0, 0, ...plan.map((item) => this.normalizeProtectionPlanItem(item)));
|
||||
this.faultProtectionSettings.splice(0, 0, ...settings.faultSettings.map((item) => this.enhancePointRow(item)));
|
||||
this.releaseProtectionSettings.splice(
|
||||
0,
|
||||
0,
|
||||
...settings.releaseSettings.map((item) => this.enhancePointRow(item))
|
||||
);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.mode = "add";
|
||||
}
|
||||
},
|
||||
formatPointLabel(item = {}) {
|
||||
const pointId = item?.pointId || "";
|
||||
const pointName = item?.pointName || "";
|
||||
const pointDesc = item?.pointDesc || "";
|
||||
return `${pointId || "-"}-${pointName || "-"}(${pointDesc || "-"})`;
|
||||
},
|
||||
normalizePointOptions(data = []) {
|
||||
return (data || []).map((item) => {
|
||||
const pointId = item?.pointId || item?.point || item?.value || "";
|
||||
const pointName = item?.pointName || pointId || "";
|
||||
const pointDesc = item?.pointDesc || "";
|
||||
return {
|
||||
value: pointId,
|
||||
label: this.formatPointLabel({ pointId, pointName, pointDesc }),
|
||||
pointId,
|
||||
pointName,
|
||||
pointDesc,
|
||||
deviceId: item?.deviceId || "",
|
||||
deviceName: item?.deviceName || "",
|
||||
deviceCategory: item?.deviceCategory || "",
|
||||
categoryName: item?.categoryName || item?.deviceCategory || "",
|
||||
};
|
||||
});
|
||||
},
|
||||
enhancePointRow(row = {}) {
|
||||
const nextRow = Object.assign(
|
||||
{
|
||||
deviceId: "",
|
||||
deviceName: "",
|
||||
deviceCategory: "",
|
||||
categoryName: "",
|
||||
point: "",
|
||||
pointName: "",
|
||||
pointOptions: [],
|
||||
pointOptionsCache: {},
|
||||
pointRequestId: 0,
|
||||
pointLoading: false,
|
||||
},
|
||||
row || {}
|
||||
);
|
||||
const selectedPointId = String(nextRow.point || "").trim();
|
||||
if (selectedPointId) {
|
||||
const hasSelected = (nextRow.pointOptions || []).some((item) => item?.value === selectedPointId);
|
||||
if (!hasSelected) {
|
||||
const selectedOption = this.normalizePointOptions([
|
||||
{
|
||||
pointId: selectedPointId,
|
||||
pointName: nextRow.pointName || selectedPointId,
|
||||
pointDesc: "",
|
||||
deviceId: nextRow.deviceId || "",
|
||||
deviceName: nextRow.deviceName || "",
|
||||
deviceCategory: nextRow.deviceCategory || "",
|
||||
categoryName: nextRow.categoryName || "",
|
||||
},
|
||||
])[0];
|
||||
nextRow.pointOptions = [...(nextRow.pointOptions || []), selectedOption];
|
||||
}
|
||||
}
|
||||
return nextRow;
|
||||
},
|
||||
getRow(type, index) {
|
||||
return (this[type] || [])[index];
|
||||
},
|
||||
setRow(type, index, row) {
|
||||
this[type].splice(index, 1, row);
|
||||
},
|
||||
addRow(type) {
|
||||
const item =
|
||||
type === "protectionSettings"
|
||||
? {
|
||||
deviceId: "",
|
||||
deviceName: "",
|
||||
deviceCategory: "",
|
||||
categoryName: "",
|
||||
point: "",
|
||||
pointName: "",
|
||||
faultValue: "",
|
||||
releaseValue: "",
|
||||
faultOperator: "",
|
||||
releaseOperator: "",
|
||||
relationNext: "",
|
||||
}
|
||||
: {
|
||||
deviceId: "",
|
||||
deviceName: "",
|
||||
deviceCategory: "",
|
||||
categoryName: "",
|
||||
point: "",
|
||||
pointName: "",
|
||||
value: "",
|
||||
};
|
||||
type === "protectionPlan"
|
||||
? this.normalizeProtectionPlanItem({})
|
||||
: this.enhancePointRow(this.createProtectionSettingItem(type));
|
||||
this[type].splice(this[type].length, 0, item);
|
||||
},
|
||||
getCapabilityLabel(action) {
|
||||
const target = (this.protectionCapabilityOptions || []).find((item) => item.value === action);
|
||||
return target?.label || "";
|
||||
},
|
||||
isDerateAction(action) {
|
||||
return action === "derate";
|
||||
},
|
||||
inferProtectionAction(raw = {}) {
|
||||
const marker = `${raw?.actionName || ""} ${raw?.action || ""} ${raw?.pointName || ""} ${raw?.point || ""}`.toLowerCase();
|
||||
const mapping = [
|
||||
{ value: "forbid_charge_discharge", markers: ["禁止充放电", "forbid_charge_discharge", "disable_charge_discharge"] },
|
||||
{ value: "forbid_charge", markers: ["禁止充电", "forbid_charge", "disable_charge"] },
|
||||
{ value: "allow_discharge", markers: ["允许放电", "allow_discharge"] },
|
||||
{ value: "forbid_discharge", markers: ["禁止放电", "forbid_discharge", "disable_discharge"] },
|
||||
{ value: "allow_charge", markers: ["允许充电", "allow_charge"] },
|
||||
{ value: "shutdown", markers: ["关机", "停机", "切断", "shutdown", "stop"] },
|
||||
{ value: "standby", markers: ["待机", "standby"] },
|
||||
{ value: "derate", markers: ["降功率", "derate", "power_limit", "powerlimit"] },
|
||||
];
|
||||
for (let i = 0; i < mapping.length; i++) {
|
||||
const item = mapping[i];
|
||||
if (item.markers.some((mk) => marker.includes(mk))) {
|
||||
return item.value;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
},
|
||||
normalizeDerateValue(rawValue) {
|
||||
if (rawValue === null || rawValue === undefined || rawValue === "") return "";
|
||||
const num = Number(rawValue);
|
||||
if (Number.isNaN(num)) return rawValue;
|
||||
if (num > 0 && num <= 1) {
|
||||
return String((num * 100).toFixed(2)).replace(/\.?0+$/, "");
|
||||
}
|
||||
return String(rawValue);
|
||||
},
|
||||
normalizeProtectionPlanItem(raw = {}) {
|
||||
const action = raw?.action || this.inferProtectionAction(raw);
|
||||
const label = raw?.actionName || this.getCapabilityLabel(action);
|
||||
const value = this.isDerateAction(action) ? this.normalizeDerateValue(raw?.value) : "";
|
||||
return {
|
||||
action,
|
||||
actionName: label,
|
||||
point: raw?.point || action || "",
|
||||
pointName: raw?.pointName || label || "",
|
||||
value,
|
||||
};
|
||||
},
|
||||
handleProtectionActionChange(index, action) {
|
||||
const row = this.getRow("protectionPlan", index);
|
||||
if (!row) return;
|
||||
const label = this.getCapabilityLabel(action);
|
||||
this.setRow(
|
||||
"protectionPlan",
|
||||
index,
|
||||
Object.assign({}, row, {
|
||||
action: action || "",
|
||||
actionName: label || "",
|
||||
point: action || "",
|
||||
pointName: label || "",
|
||||
value: this.isDerateAction(action) ? row.value : "",
|
||||
})
|
||||
);
|
||||
},
|
||||
createProtectionSettingItem(type) {
|
||||
const isFault = type === "faultProtectionSettings";
|
||||
return this.enhancePointRow({
|
||||
deviceId: "",
|
||||
deviceName: "",
|
||||
deviceCategory: "",
|
||||
categoryName: "",
|
||||
point: "",
|
||||
pointName: "",
|
||||
faultValue: isFault ? "" : null,
|
||||
releaseValue: isFault ? null : "",
|
||||
faultOperator: isFault ? "" : null,
|
||||
releaseOperator: isFault ? null : "",
|
||||
relationNext: "",
|
||||
});
|
||||
},
|
||||
deleteRow(index, type) {
|
||||
this[type].splice(index, 1);
|
||||
},
|
||||
querySearchAsync(query, cb, index, type) {
|
||||
if (!this.formData.siteId) {
|
||||
this.$message({
|
||||
type: "warning",
|
||||
message: "请先选择站点",
|
||||
});
|
||||
return cb([]);
|
||||
setPointOptions(index, type, data = []) {
|
||||
const row = this.getRow(type, index);
|
||||
if (!row) return;
|
||||
const normalized = this.normalizePointOptions(data);
|
||||
const selectedPoint = row.point
|
||||
? this.normalizePointOptions([
|
||||
{
|
||||
pointId: row.point,
|
||||
pointName: row.pointName || row.point,
|
||||
pointDesc: "",
|
||||
deviceId: row.deviceId || "",
|
||||
deviceName: row.deviceName || "",
|
||||
deviceCategory: row.deviceCategory || "",
|
||||
categoryName: row.categoryName || "",
|
||||
},
|
||||
])
|
||||
: [];
|
||||
const merged = [...normalized, ...(row.pointOptions || []), ...selectedPoint];
|
||||
const optionMap = {};
|
||||
const uniqueOptions = merged.filter((item) => {
|
||||
if (!item?.value || optionMap[item.value]) return false;
|
||||
optionMap[item.value] = true;
|
||||
return true;
|
||||
});
|
||||
this.setRow(type, index, Object.assign({}, row, { pointOptions: uniqueOptions }));
|
||||
},
|
||||
fetchPointOptions(index, type, query = "", { force = false } = {}) {
|
||||
const row = this.getRow(type, index);
|
||||
if (!row || !this.formData.siteId) return Promise.resolve([]);
|
||||
const normalizedQuery = String(query || "").trim();
|
||||
const cacheKey = `${this.formData.siteId}_${normalizedQuery}`;
|
||||
if (!force && row.pointOptionsCache?.[cacheKey]) {
|
||||
this.setPointOptions(index, type, row.pointOptionsCache[cacheKey]);
|
||||
return Promise.resolve(row.pointOptionsCache[cacheKey]);
|
||||
}
|
||||
getPointMatchList({
|
||||
const requestId = Number(row.pointRequestId || 0) + 1;
|
||||
this.setRow(type, index, Object.assign({}, row, { pointRequestId: requestId, pointLoading: true }));
|
||||
return getPointMatchList({
|
||||
siteId: this.formData.siteId,
|
||||
pageNum: 1,
|
||||
pageSize: 100,
|
||||
pointId: query || "",
|
||||
pointDesc: query || "",
|
||||
pointId: normalizedQuery,
|
||||
pointDesc: normalizedQuery,
|
||||
})
|
||||
.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,
|
||||
};
|
||||
})
|
||||
);
|
||||
const latestRow = this.getRow(type, index);
|
||||
if (!latestRow || latestRow.pointRequestId !== requestId) return [];
|
||||
const result = response?.rows || [];
|
||||
const cache = Object.assign({}, latestRow.pointOptionsCache || {}, { [cacheKey]: result });
|
||||
this.setRow(type, index, Object.assign({}, latestRow, { pointOptionsCache: cache }));
|
||||
this.setPointOptions(index, type, result);
|
||||
return result;
|
||||
})
|
||||
.catch(() => cb([]));
|
||||
.finally(() => {
|
||||
const latestRow = this.getRow(type, index);
|
||||
if (!latestRow || latestRow.pointRequestId !== requestId) return;
|
||||
this.setRow(type, index, Object.assign({}, latestRow, { pointLoading: false }));
|
||||
});
|
||||
},
|
||||
fillPointMetaByPoint(point, index, type) {
|
||||
if (!this.formData.siteId || !point) {
|
||||
remotePointSearch(index, type, query) {
|
||||
this.fetchPointOptions(index, type, query);
|
||||
},
|
||||
handlePointDropdownVisible(index, type, visible) {
|
||||
if (!visible) return;
|
||||
this.fetchPointOptions(index, type, "");
|
||||
},
|
||||
handlePointChange(index, type, value) {
|
||||
const row = this.getRow(type, index);
|
||||
if (!row) return;
|
||||
if (!value) {
|
||||
this.setRow(
|
||||
type,
|
||||
index,
|
||||
Object.assign({}, row, {
|
||||
point: "",
|
||||
pointName: "",
|
||||
deviceId: "",
|
||||
deviceName: "",
|
||||
deviceCategory: "",
|
||||
categoryName: "",
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
getPointMatchList({
|
||||
siteId: this.formData.siteId,
|
||||
pageNum: 1,
|
||||
pageSize: 1,
|
||||
pointId: point,
|
||||
}).then((response) => {
|
||||
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);
|
||||
});
|
||||
},
|
||||
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);
|
||||
const selectedOption = (row.pointOptions || []).find((item) => item?.value === value) || {};
|
||||
this.setRow(
|
||||
type,
|
||||
index,
|
||||
Object.assign({}, row, {
|
||||
point: selectedOption.pointId || selectedOption.value || value,
|
||||
pointName: selectedOption.pointName || selectedOption.pointId || selectedOption.value || value,
|
||||
deviceId: selectedOption.deviceId || "",
|
||||
deviceName: selectedOption.deviceName || "",
|
||||
deviceCategory: selectedOption.deviceCategory || "",
|
||||
categoryName: selectedOption.categoryName || selectedOption.deviceCategory || "",
|
||||
})
|
||||
);
|
||||
},
|
||||
saveDialog() {
|
||||
function getToastMsg(name, type, index) {
|
||||
return {
|
||||
protectionSettings: {
|
||||
deviceId: `请选择保护前提第${index}行的设备`,
|
||||
deviceCategory: `请选择保护前提第${index}行的设备类型`,
|
||||
categoryName: `请选择保护前提第${index}行的设备类型`,
|
||||
point: `请选择保护前提第${index}行的点位`,
|
||||
pointName: `请选择保护前提第${index}行的点位`,
|
||||
faultValue: `请输入保护前提第${index}行的故障值`,
|
||||
releaseValue: `请输入保护前提第${index}行的释放值`,
|
||||
faultOperator: `请选择保护前提第${index}行的故障值比较关系`,
|
||||
releaseOperator: `请选择保护前提第${index}行的释放值比较关系`,
|
||||
relationNext: `请选择保护前提第${index}行与下一个点位的关系`,
|
||||
faultProtectionSettings: {
|
||||
deviceId: `请选择故障保护第${index}行的设备`,
|
||||
deviceCategory: `请选择故障保护第${index}行的设备类型`,
|
||||
categoryName: `请选择故障保护第${index}行的设备类型`,
|
||||
point: `请选择故障保护第${index}行的点位`,
|
||||
pointName: `请选择故障保护第${index}行的点位`,
|
||||
faultValue: `请输入故障保护第${index}行的故障值`,
|
||||
faultOperator: `请选择故障保护第${index}行的故障值比较关系`,
|
||||
relationNext: `请选择故障保护第${index}行与下一个点位的关系`,
|
||||
},
|
||||
releaseProtectionSettings: {
|
||||
deviceId: `请选择释放保护第${index}行的设备`,
|
||||
deviceCategory: `请选择释放保护第${index}行的设备类型`,
|
||||
categoryName: `请选择释放保护第${index}行的设备类型`,
|
||||
point: `请选择释放保护第${index}行的点位`,
|
||||
pointName: `请选择释放保护第${index}行的点位`,
|
||||
releaseValue: `请输入释放保护第${index}行的释放值`,
|
||||
releaseOperator: `请选择释放保护第${index}行的释放值比较关系`,
|
||||
relationNext: `请选择释放保护第${index}行与下一个点位的关系`,
|
||||
},
|
||||
protectionPlan: {
|
||||
deviceId: `请选择保护方案第${index}行的设备`,
|
||||
deviceCategory: `请选择保护方案第${index}行的设备类型`,
|
||||
categoryName: `请选择保护方案第${index}行的设备类型`,
|
||||
point: `请选择保护方案第${index}行的点位`,
|
||||
pointName: `请选择保护方案第${index}行的点位`,
|
||||
value: `请输入保护方案第${index}行的故障值`,
|
||||
action: `请选择执行保护第${index}行的动作`,
|
||||
value: `请输入执行保护第${index}行的降功率比例(0-100)`,
|
||||
},
|
||||
}[type][name];
|
||||
}
|
||||
@ -407,32 +659,58 @@ export default {
|
||||
releaseDelaySeconds = "",
|
||||
description = "",
|
||||
} = this.formData;
|
||||
const { protectionSettings, protectionPlan } = this;
|
||||
let protectionSettingsValidateStatus = true;
|
||||
const { faultProtectionSettings, releaseProtectionSettings, protectionPlan } = this;
|
||||
let faultSettingsValidateStatus = true;
|
||||
let releaseSettingsValidateStatus = true;
|
||||
let protectionPlanValidateStatus = true;
|
||||
const settingRequiredFields = ["point", "faultOperator", "faultValue", "releaseOperator", "releaseValue"];
|
||||
const planRequiredFields = ["point", "value"];
|
||||
const faultSettingRequiredFields = ["point", "faultOperator", "faultValue"];
|
||||
const releaseSettingRequiredFields = ["point", "releaseOperator", "releaseValue"];
|
||||
const planRequiredFields = ["action"];
|
||||
|
||||
for (let i = 0; i < protectionSettings.length; i++) {
|
||||
const row = protectionSettings[i] || {};
|
||||
for (let inner = 0; inner < settingRequiredFields.length; inner++) {
|
||||
const key = settingRequiredFields[inner];
|
||||
for (let i = 0; i < faultProtectionSettings.length; i++) {
|
||||
const row = faultProtectionSettings[i] || {};
|
||||
for (let inner = 0; inner < faultSettingRequiredFields.length; inner++) {
|
||||
const key = faultSettingRequiredFields[inner];
|
||||
const value = row[key];
|
||||
if (![0, "0"].includes(value) && !value) {
|
||||
this.$message.error(getToastMsg(key, "protectionSettings", i + 1));
|
||||
protectionSettingsValidateStatus = false;
|
||||
this.$message.error(getToastMsg(key, "faultProtectionSettings", i + 1));
|
||||
faultSettingsValidateStatus = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!protectionSettingsValidateStatus) break;
|
||||
if (!faultSettingsValidateStatus) break;
|
||||
if (!row.deviceId) {
|
||||
this.$message.error(`请选择保护前提第${i + 1}行的点位(需从点位列表中选择)`);
|
||||
protectionSettingsValidateStatus = false;
|
||||
this.$message.error(`请选择故障保护第${i + 1}行的点位(需从点位列表中选择)`);
|
||||
faultSettingsValidateStatus = false;
|
||||
break;
|
||||
}
|
||||
if (protectionSettings[i + 1] && !row.relationNext) {
|
||||
this.$message.error(getToastMsg("relationNext", "protectionSettings", i + 1));
|
||||
protectionSettingsValidateStatus = false;
|
||||
if (faultProtectionSettings[i + 1] && !row.relationNext) {
|
||||
this.$message.error(getToastMsg("relationNext", "faultProtectionSettings", i + 1));
|
||||
faultSettingsValidateStatus = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < releaseProtectionSettings.length; i++) {
|
||||
const row = releaseProtectionSettings[i] || {};
|
||||
for (let inner = 0; inner < releaseSettingRequiredFields.length; inner++) {
|
||||
const key = releaseSettingRequiredFields[inner];
|
||||
const value = row[key];
|
||||
if (![0, "0"].includes(value) && !value) {
|
||||
this.$message.error(getToastMsg(key, "releaseProtectionSettings", i + 1));
|
||||
releaseSettingsValidateStatus = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!releaseSettingsValidateStatus) break;
|
||||
if (!row.deviceId) {
|
||||
this.$message.error(`请选择释放保护第${i + 1}行的点位(需从点位列表中选择)`);
|
||||
releaseSettingsValidateStatus = false;
|
||||
break;
|
||||
}
|
||||
if (releaseProtectionSettings[i + 1] && !row.relationNext) {
|
||||
this.$message.error(getToastMsg("relationNext", "releaseProtectionSettings", i + 1));
|
||||
releaseSettingsValidateStatus = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -449,17 +727,35 @@ export default {
|
||||
}
|
||||
}
|
||||
if (!protectionPlanValidateStatus) break;
|
||||
if (!row.deviceId) {
|
||||
this.$message.error(`请选择保护方案第${i + 1}行的点位(需从点位列表中选择)`);
|
||||
if (this.isDerateAction(row.action)) {
|
||||
const derate = Number(row.value);
|
||||
if (Number.isNaN(derate) || derate < 0 || derate > 100) {
|
||||
this.$message.error(getToastMsg("value", "protectionPlan", i + 1));
|
||||
protectionPlanValidateStatus = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!row.pointName) {
|
||||
this.$message.error(getToastMsg("action", "protectionPlan", i + 1));
|
||||
protectionPlanValidateStatus = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!protectionSettingsValidateStatus || !protectionPlanValidateStatus) return;
|
||||
if (!faultSettingsValidateStatus || !releaseSettingsValidateStatus || !protectionPlanValidateStatus) return;
|
||||
|
||||
const settings = protectionSettings.map((item) => Object.assign({}, item));
|
||||
const plan = protectionPlan.map((item) => Object.assign({}, item));
|
||||
const settings = {
|
||||
faultSettings: faultProtectionSettings.map((item) => Object.assign({}, item)),
|
||||
releaseSettings: releaseProtectionSettings.map((item) => Object.assign({}, item)),
|
||||
};
|
||||
const plan = protectionPlan.map((item) =>
|
||||
Object.assign({}, item, {
|
||||
actionName: this.getCapabilityLabel(item.action),
|
||||
point: item.action || "",
|
||||
pointName: this.getCapabilityLabel(item.action) || "",
|
||||
value: this.isDerateAction(item.action) ? item.value : null,
|
||||
})
|
||||
);
|
||||
this.loading += 1;
|
||||
|
||||
const params = {
|
||||
@ -506,7 +802,8 @@ export default {
|
||||
this.formData[key] = key === "isAlert" ? 0 : key === "faultLevel" ? 1 : "";
|
||||
}
|
||||
this.$refs.addTempForm.resetFields();
|
||||
this.$set(this, "protectionSettings", []);
|
||||
this.$set(this, "faultProtectionSettings", []);
|
||||
this.$set(this, "releaseProtectionSettings", []);
|
||||
this.$set(this, "protectionPlan", []);
|
||||
this.dialogTableVisible = false;
|
||||
},
|
||||
@ -516,25 +813,28 @@ export default {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.plan-form {
|
||||
background: #f5f8ff;
|
||||
border: 1px solid #e8edf7;
|
||||
border-radius: 14px;
|
||||
padding: 16px;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.base-panel {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border: 1px solid #e7ecf6;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
margin: 14px 0 16px;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
.card-legend {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 14px;
|
||||
transform: translateY(-50%);
|
||||
padding: 0 10px;
|
||||
background: #fff;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1f2d3d;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.base-grid {
|
||||
@ -551,12 +851,34 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.level-row {
|
||||
::v-deep .el-form-item__content {
|
||||
min-height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
::v-deep .el-radio-group {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
::v-deep .el-radio {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.plan-section {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border: 1px solid #e7ecf6;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 14px;
|
||||
margin: 14px 0;
|
||||
}
|
||||
|
||||
.section-head {
|
||||
@ -567,14 +889,6 @@ export default {
|
||||
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;
|
||||
@ -622,8 +936,8 @@ export default {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.setting-grid {
|
||||
grid-template-columns: 2fr repeat(5, minmax(120px, 1fr));
|
||||
.trigger-grid {
|
||||
grid-template-columns: 2fr repeat(3, minmax(120px, 1fr));
|
||||
}
|
||||
|
||||
.plan-grid {
|
||||
@ -672,7 +986,7 @@ export default {
|
||||
}
|
||||
|
||||
@media (max-width: 1366px) {
|
||||
.setting-grid {
|
||||
.trigger-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
@ -704,7 +1018,7 @@ export default {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.setting-grid,
|
||||
.trigger-grid,
|
||||
.plan-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@ -31,13 +31,13 @@
|
||||
<template slot-scope="scope">{{ scope.row.isAlert === 1 ? '是' : '否' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="处理方案描述" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="protectionSettings" label="保护前提" min-width="360" show-overflow-tooltip>
|
||||
<el-table-column prop="protectionSettings" label="故障/释放保护" min-width="360" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<div class="rich-lines" v-html="handleProtectionSettings(scope.row.protectionSettings)"></div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="faultDelaySeconds" label="前提延时(s)" width="110" />
|
||||
<el-table-column prop="protectionPlan" label="保护方案" min-width="260" show-overflow-tooltip>
|
||||
<el-table-column prop="protectionPlan" label="执行保护" min-width="260" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<div class="rich-lines" v-html="handleProtectionPlan(scope.row.protectionPlan)"></div>
|
||||
</template>
|
||||
@ -96,37 +96,76 @@ export default {
|
||||
this.getData();
|
||||
},
|
||||
handleProtectionSettings(data) {
|
||||
if (!data || !JSON.parse(data)) return;
|
||||
const arr = JSON.parse(data);
|
||||
const str = arr.map((item, index) => {
|
||||
if (!data) return;
|
||||
let parsed = null;
|
||||
try {
|
||||
parsed = JSON.parse(data);
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
const faultSettings = Array.isArray(parsed) ? parsed : parsed?.faultSettings || [];
|
||||
const releaseSettings = Array.isArray(parsed) ? parsed : parsed?.releaseSettings || [];
|
||||
|
||||
const buildLine = (item, index, total, key, value, relationKey) => {
|
||||
const {
|
||||
categoryName = "",
|
||||
deviceId = "",
|
||||
point = "",
|
||||
faultOperator = "",
|
||||
faultValue = "",
|
||||
releaseOperator = "",
|
||||
releaseValue = "",
|
||||
[key]: operator = "",
|
||||
[value]: val = "",
|
||||
relationNext = "",
|
||||
} = item;
|
||||
return `<div>${index + 1}、 <span>${categoryName ? categoryName + "-" : ""}${
|
||||
deviceId ? deviceId + "-" : ""
|
||||
}${point || ""}</span> <span>故障:${faultOperator || ""}${faultValue || ""}</span> <span>释放:${
|
||||
releaseOperator || ""
|
||||
}${releaseValue || ""}</span> ${
|
||||
arr[index + 1] ? "<span>关系:" + (relationNext || "") + "</span>" : ""
|
||||
}${point || ""}</span> <span>${relationKey}:${operator || ""}${val || ""}</span> ${
|
||||
total[index + 1] ? "<span>关系:" + (relationNext || "") + "</span>" : ""
|
||||
}</div>`;
|
||||
});
|
||||
return str.join("");
|
||||
};
|
||||
|
||||
const faultStr = faultSettings.map((item, index) =>
|
||||
buildLine(item, index, faultSettings, "faultOperator", "faultValue", "故障")
|
||||
);
|
||||
const releaseStr = releaseSettings.map((item, index) =>
|
||||
buildLine(item, index, releaseSettings, "releaseOperator", "releaseValue", "释放")
|
||||
);
|
||||
const groups = [];
|
||||
if (faultStr.length) {
|
||||
groups.push(`<div><strong>故障保护</strong></div>${faultStr.join("")}`);
|
||||
}
|
||||
if (releaseStr.length) {
|
||||
groups.push(`<div><strong>释放保护</strong></div>${releaseStr.join("")}`);
|
||||
}
|
||||
return groups.join("");
|
||||
},
|
||||
handleProtectionPlan(data) {
|
||||
if (!data || !JSON.parse(data)) return;
|
||||
const arr = JSON.parse(data);
|
||||
if (!data) return;
|
||||
let arr = [];
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
arr = Array.isArray(parsed) ? parsed : parsed ? [parsed] : [];
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
const actionLabelMap = {
|
||||
derate: "降功率",
|
||||
shutdown: "关机/停机/切断",
|
||||
forbid_charge: "禁止充电",
|
||||
allow_discharge: "允许放电",
|
||||
forbid_discharge: "禁止放电",
|
||||
allow_charge: "允许充电",
|
||||
forbid_charge_discharge: "禁止充放电",
|
||||
standby: "待机",
|
||||
};
|
||||
const str = arr.map((item, index) => {
|
||||
const { categoryName = "", deviceId = "", point = "", value = "" } = item;
|
||||
return `<div>${index + 1}、 <span>${categoryName ? categoryName + "-" : ""}${
|
||||
deviceId ? deviceId + "-" : ""
|
||||
}${point || ""}</span> <span>故障:=${value || ""}</span> </div>`;
|
||||
const action = item?.action || "";
|
||||
const point = item?.point || "";
|
||||
const pointName = item?.pointName || "";
|
||||
const actionName = item?.actionName || actionLabelMap[action] || pointName || point || "未配置";
|
||||
const value = item?.value;
|
||||
if ((action === "derate" || actionName.includes("降功率")) && value !== null && value !== undefined && value !== "") {
|
||||
return `<div>${index + 1}、 <span>动作:${actionName}</span> <span>比例:${value}%</span></div>`;
|
||||
}
|
||||
return `<div>${index + 1}、 <span>动作:${actionName}</span></div>`;
|
||||
});
|
||||
return str.join("");
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user