feat/联动管理页面,平面图效果优化
This commit is contained in:
parent
6084d9832f
commit
1f77b00c58
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760607440762" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9824" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M737.28 506.42944c68.28032 109.24032 102.4 183.13216 102.4 221.67552 0 58.5728-45.8752 106.00448-102.4 106.00448S634.88 786.67776 634.88 728.10496c0-38.5024 34.11968-112.4352 102.4-221.67552z m45.75232-312.60672a102.4 102.4 0 0 1 0 144.83456L471.04 650.69056a143.36 143.36 0 1 1-143.36-144.26112l-2.12992 0.04096 312.68864-312.64768a102.4 102.4 0 0 1 144.7936 0zM737.28 634.88c-13.63968 42.76224-20.48 62.42304-20.48 76.75904 0 21.504 9.17504 38.912 20.48 38.912s20.48-17.408 20.48-38.912c0-14.336-6.84032-33.9968-20.48-76.75904z m-10.32192-383.71328a20.48 20.48 0 0 0-28.95872 0l-343.40864 343.36768a61.44 61.44 0 1 0 28.75392 29.20448l343.61344-343.6544a20.48 20.48 0 0 0 0-28.91776z" fill="#444444" p-id="9825"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760607371164" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5823" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 981.333333c259.072-0.341333 468.992-210.261333 469.333333-469.333333-0.341333-259.072-210.261333-468.992-469.333333-469.333333-259.072 0.341333-468.992 210.261333-469.333333 469.333333 0.341333 259.072 210.261333 468.992 469.333333 469.333333z m0 42.666667C229.354667 1023.68 0.32 794.645333 0 512 0.32 229.354667 229.354667 0.32 512 0c282.645333 0.32 511.68 229.354667 512 512-0.32 282.645333-229.354667 511.68-512 512z" fill="#333333" p-id="5824"></path><path d="M325.333333 325.610667a168.32 168.32 0 0 0 168.256 168.32h25.941334V157.376h-25.941334a168.32 168.32 0 0 0-168.256 168.256z m96.32 454.186666a168.32 168.32 0 0 0 61.632-229.930666l-12.949333-22.378667-291.626667 168.256 12.928 22.442667a168.32 168.32 0 0 0 229.952 61.610666h0.064z m349.376-318.677333a168.32 168.32 0 0 0-229.866666 61.632l-12.928 22.421333 291.477333 168.32 12.928-22.421333a168.32 168.32 0 0 0-61.610667-229.952z" fill="#333333" p-id="5825"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760607420456" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8784" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M888.207 759.168l-328.02-550.935a55.244 55.244 0 0 0-75.975-20.727 56 56 0 0 0-20.44 20.727L135.457 759.168c-15.333 27.166-6.051 61.807 20.755 77.352a55.342 55.342 0 0 0 27.671 7.5h656.408c42.568-0.514 69.7-47.269 48-84.847h-0.084z m-376.36 20.769c-26.236 0.007-47.507-21.54-47.514-48.116s21.238-48.158 47.467-48.165h0.047c26.223 0.007 47.481 21.575 47.467 48.165-0.011 26.562-21.262 48.095-47.467 48.116z m48.848-181.128c-1.032 27.368-23.757 48.706-50.751 47.657-25.553-0.986-46.046-21.755-47.018-47.657V396.981c1.032-27.361 23.75-48.706 50.758-47.657 25.539 0.986 46.032 21.755 47.011 47.657v201.828z" fill="#DADADA" p-id="8785"></path></svg>
|
||||||
|
After Width: | Height: | Size: 977 B |
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760607389345" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6872" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M949.333333 170.666667c-17.066667 0-32 14.933333-32 32v10.666666H512c-164.266667 0-298.666667 134.4-298.666667 298.666667 0 38.4 6.4 72.533333 19.2 106.666667H106.666667v-10.666667c0-17.066667-14.933333-32-32-32S42.666667 590.933333 42.666667 608v213.333333c0 17.066667 14.933333 32 32 32S106.666667 838.4 106.666667 821.333333V810.666667h405.333333c164.266667 0 298.666667-134.4 298.666667-298.666667 0-38.4-6.4-72.533333-19.2-106.666667H917.333333v10.666667c0 17.066667 14.933333 32 32 32s32-14.933333 32-32v-213.333333c0-17.066667-14.933333-32-32-32zM512 682.666667c-93.866667 0-170.666667-76.8-170.666667-170.666667s76.8-170.666667 170.666667-170.666667 170.666667 76.8 170.666667 170.666667-76.8 170.666667-170.666667 170.666667z" p-id="6873"></path><path d="M608 497.066667l17.066667-17.066667c6.4-6.4 6.4-17.066667 0-23.466667-6.4-6.4-17.066667-6.4-23.466667 0l-40.533333 40.533334h-32v-32l40.533333-40.533334c6.4-6.4 6.4-17.066667 0-23.466666s-17.066667-6.4-23.466667 0l-17.066666 17.066666v-14.933333c0-8.533333-8.533333-17.066667-17.066667-17.066667s-17.066667 8.533333-17.066667 17.066667v14.933333l-17.066666-17.066666c-6.4-6.4-17.066667-6.4-23.466667 0-6.4 6.4-6.4 17.066667 0 23.466666l40.533333 40.533334v32h-32l-40.533333-40.533334c-6.4-6.4-17.066667-6.4-23.466667 0-6.4 6.4-6.4 17.066667 0 23.466667l17.066667 17.066667h-14.933333c-8.533333 0-17.066667 8.533333-17.066667 17.066666s8.533333 17.066667 17.066667 17.066667h14.933333l-17.066667 17.066667c-2.133333 2.133333-4.266667 8.533333-4.266666 12.8 0 8.533333 8.533333 17.066667 17.066666 17.066666 4.266667 0 8.533333-2.133333 12.8-4.266666l40.533334-40.533334h32v32l-40.533334 40.533334c-6.4 6.4-6.4 17.066667 0 23.466666 6.4 6.4 17.066667 6.4 23.466667 0l17.066667-17.066666v14.933333c0 8.533333 8.533333 17.066667 17.066666 17.066667s17.066667-8.533333 17.066667-17.066667v-14.933333l17.066667 17.066666c4.266667 4.266667 8.533333 4.266667 12.8 4.266667 4.266667 0 8.533333-2.133333 12.8-4.266667 6.4-6.4 6.4-17.066667 0-23.466666L533.333333 565.333333V533.333333h32l40.533334 40.533334c4.266667 4.266667 8.533333 4.266667 12.8 4.266666 4.266667 0 8.533333-2.133333 12.8-4.266666 6.4-6.4 6.4-17.066667 0-23.466667l-17.066667-17.066667h14.933333c8.533333 0 17.066667-8.533333 17.066667-17.066666s-8.533333-17.066667-17.066667-17.066667h-21.333333z" p-id="6874"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -76,8 +76,8 @@ export const constantRoutes = [
|
||||||
path: 'index',
|
path: 'index',
|
||||||
component: () => import('@/views/index'),
|
component: () => import('@/views/index'),
|
||||||
name: 'Index',
|
name: 'Index',
|
||||||
meta: { title: '工作台', icon: 'index', affix: true }
|
meta: { title: '工作台', icon: 'index' }
|
||||||
// meta: { title: '首页', icon: '@/assets/treeIcons/index.png', affix: true }
|
// meta: { title: '首页', icon: '@/assets/treeIcons/index.png', affix: true } affix 是否常驻
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -90,7 +90,7 @@ export const constantRoutes = [
|
||||||
path: 'plan',
|
path: 'plan',
|
||||||
component: () => import('@/views/plan'),
|
component: () => import('@/views/plan'),
|
||||||
name: 'Plan',
|
name: 'Plan',
|
||||||
meta: { title: '平面图', icon: 'plan', affix: true }
|
meta: { title: '平面图', icon: 'plan' }
|
||||||
},
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
@ -234,6 +234,71 @@ export const constantRoutes = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "linkConfig",
|
||||||
|
alwaysShow: true,
|
||||||
|
component: Layout,
|
||||||
|
hidden: false,
|
||||||
|
name: "linkConfig",
|
||||||
|
path: "/linkConfig",
|
||||||
|
redirect: "noRedirect",
|
||||||
|
meta: {
|
||||||
|
icon: "link",
|
||||||
|
link: null,
|
||||||
|
noCache: false,
|
||||||
|
title: "联动配置管理",
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
component: () => import('@/views/linkConfig/alarmConfig'),
|
||||||
|
hidden: false,
|
||||||
|
name: "alarmConfig",
|
||||||
|
path: "alarmConfig",
|
||||||
|
meta: {
|
||||||
|
icon: "gjdj",
|
||||||
|
link: null,
|
||||||
|
noCache: false,
|
||||||
|
title: "报警等级配置",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: () => import('@/views/linkConfig/fanLinkConfig'),
|
||||||
|
hidden: false,
|
||||||
|
name: "fanLinkConfig",
|
||||||
|
path: "fanLinkConfig",
|
||||||
|
meta: {
|
||||||
|
icon: "fan",
|
||||||
|
link: null,
|
||||||
|
noCache: false,
|
||||||
|
title: "风机联动阈值配置",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: () => import('@/views/linkConfig/pumpLinkConfig'),
|
||||||
|
hidden: false,
|
||||||
|
name: "pumpLinkConfig",
|
||||||
|
path: "pumpLinkConfig",
|
||||||
|
meta: {
|
||||||
|
icon: "pump",
|
||||||
|
link: null,
|
||||||
|
noCache: false,
|
||||||
|
title: "水泵联动阈值配置",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: () => import('@/views/linkConfig/sensorConfig'),
|
||||||
|
hidden: false,
|
||||||
|
name: "sensorConfig",
|
||||||
|
path: "sensorConfig",
|
||||||
|
meta: {
|
||||||
|
icon: "cgq",
|
||||||
|
link: null,
|
||||||
|
noCache: false,
|
||||||
|
title: "传感器报警阈值",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "Equipment",
|
name: "Equipment",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,222 @@
|
||||||
|
<template>
|
||||||
|
<div class="alarm-config-page">
|
||||||
|
<el-card shadow="never" class="mb-16">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">报警等级配置</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-form :model="formLevel" label-width="100px" class="w-100p">
|
||||||
|
<el-table :data="alarmTypes" border style="width: 100%">
|
||||||
|
<el-table-column prop="label" label="报警类型" width="180" />
|
||||||
|
<el-table-column label="报警等级">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-select
|
||||||
|
v-model="formLevel[row.value]"
|
||||||
|
placeholder="请选择等级"
|
||||||
|
style="width: 200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="lvl in levelOptions"
|
||||||
|
:key="lvl.value"
|
||||||
|
:label="lvl.label"
|
||||||
|
:value="lvl.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div class="mt-16">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!isLevelValid"
|
||||||
|
@click="saveLevels"
|
||||||
|
>保存等级配置</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">报警消弭时间配置</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-form :model="formElimination" label-width="180px" class="w-100p">
|
||||||
|
<el-form-item label="一级消弭时间(分钟)">
|
||||||
|
<el-input-number
|
||||||
|
v-model="formElimination.times.level1"
|
||||||
|
:min="1"
|
||||||
|
:max="1440"
|
||||||
|
:step="1"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="二级消弭时间(分钟)">
|
||||||
|
<el-input-number
|
||||||
|
v-model="formElimination.times.level2"
|
||||||
|
:min="1"
|
||||||
|
:max="1440"
|
||||||
|
:step="1"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="三级消弭时间(分钟)">
|
||||||
|
<el-input-number
|
||||||
|
v-model="formElimination.times.level3"
|
||||||
|
:min="1"
|
||||||
|
:max="1440"
|
||||||
|
:step="1"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<div class="mt-16">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!isTimesValid"
|
||||||
|
@click="saveEliminationTimes"
|
||||||
|
>保存时间配置</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, computed, onMounted, ref } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
// import { getSensorConfigs } from "@/api/index";
|
||||||
|
|
||||||
|
const alarmTypes = [
|
||||||
|
{ label: "环境报警", value: "huanjing" },
|
||||||
|
{ label: "入侵报警", value: "ruqin" },
|
||||||
|
{ label: "设备报警", value: "shebei" },
|
||||||
|
{ label: "消防报警", value: "xiaofang" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const levelOptions = [
|
||||||
|
{ label: "一级", value: "一级" },
|
||||||
|
{ label: "二级", value: "二级" },
|
||||||
|
{ label: "三级", value: "三级" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 等级配置(按类型)
|
||||||
|
const formLevel = ref({});
|
||||||
|
|
||||||
|
// 消弭时间配置(按等级)
|
||||||
|
const formElimination = reactive({
|
||||||
|
times: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isLevelValid = computed(() => {
|
||||||
|
return alarmTypes.every((t) =>
|
||||||
|
["一级", "二级", "三级"].includes(formLevel.value[t.value])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isTimesValid = computed(() => {
|
||||||
|
const { level1, level2, level3 } = formElimination.times;
|
||||||
|
|
||||||
|
return level1 && level2 && level3;
|
||||||
|
});
|
||||||
|
|
||||||
|
const saveLevels = async () => {
|
||||||
|
let params = {
|
||||||
|
config: "alarmlevel",
|
||||||
|
option: "editlevel",
|
||||||
|
};
|
||||||
|
let dataP = {
|
||||||
|
id: formLevel.value.id,
|
||||||
|
data: JSON.stringify({
|
||||||
|
huanjing: formLevel.value.huanjing,
|
||||||
|
ruqin: formLevel.value.ruqin,
|
||||||
|
shebei: formLevel.value.shebei,
|
||||||
|
xiaofang: formLevel.value.xiaofang,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = await getSensorConfigs(params, dataP);
|
||||||
|
if (res && res.code == 0) {
|
||||||
|
ElMessage.success("等级配置已保存");
|
||||||
|
} else {
|
||||||
|
ElMessage.error("等级配置失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveEliminationTimes = async () => {
|
||||||
|
let params = {
|
||||||
|
config: "alarmlevel",
|
||||||
|
option: "edittime",
|
||||||
|
};
|
||||||
|
let dataP = {
|
||||||
|
id: formElimination.times.id,
|
||||||
|
data: JSON.stringify({
|
||||||
|
level1: formElimination.times.level1,
|
||||||
|
level2: formElimination.times.level2,
|
||||||
|
level3: formElimination.times.level3,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = await getSensorConfigs(params, dataP);
|
||||||
|
if (res && res.code == 0) {
|
||||||
|
ElMessage.success("等级配置已保存");
|
||||||
|
} else {
|
||||||
|
ElMessage.error("等级配置失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAlarmLevel = async () => {
|
||||||
|
let params = {
|
||||||
|
config: "alarmlevel",
|
||||||
|
option: "querylevel",
|
||||||
|
};
|
||||||
|
let res = await getSensorConfigs(params);
|
||||||
|
if (res && res.code == 0) {
|
||||||
|
formLevel.value = res.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAlarmTimes = async () => {
|
||||||
|
let params = {
|
||||||
|
config: "alarmlevel",
|
||||||
|
option: "querytime",
|
||||||
|
};
|
||||||
|
let res = await getSensorConfigs(params);
|
||||||
|
if (res && res.code == 0) {
|
||||||
|
formElimination.times = res.data;
|
||||||
|
if (formElimination.times.level1) {
|
||||||
|
formElimination.times.level1 = Number(formElimination.times.level1);
|
||||||
|
}
|
||||||
|
if (formElimination.times.level2) {
|
||||||
|
formElimination.times.level2 = Number(formElimination.times.level2);
|
||||||
|
}
|
||||||
|
if (formElimination.times.level3) {
|
||||||
|
formElimination.times.level3 = Number(formElimination.times.level3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// getAlarmLevel();
|
||||||
|
// getAlarmTimes();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.alarm-config-page {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.mb-16 {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.mt-16 {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.w-100p {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.card-header {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,830 @@
|
||||||
|
<template>
|
||||||
|
<div class="pump-threshold-config-page">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-header-left">
|
||||||
|
<h2>水泵联动液位阈值配置</h2>
|
||||||
|
<p class="page-description">
|
||||||
|
配置水泵联动受液位影响的阈值配置,
|
||||||
|
<span class="value-start">绿色</span>为启动水泵阈值,
|
||||||
|
<span class="value-stop">橙色</span>为停止水泵阈值.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="page-header-actions">
|
||||||
|
<el-button type="primary" @click="openBatchDialog">批量配置</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分区卡片展示 -->
|
||||||
|
<div v-loading="loading" class="zones-grid">
|
||||||
|
<el-card
|
||||||
|
v-for="zone in zoneConfigs"
|
||||||
|
:key="zone.id"
|
||||||
|
class="zone-card"
|
||||||
|
shadow="hover"
|
||||||
|
@click="openConfigDialog(zone)"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="zone-card-header">
|
||||||
|
<div class="zone-title">
|
||||||
|
<el-tag type="success" size="large">
|
||||||
|
{{ zone.name }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="zone-config-preview">
|
||||||
|
<!-- 1分区特殊显示两个配置 -->
|
||||||
|
<template v-if="zone.id === 1">
|
||||||
|
<div class="config-item">
|
||||||
|
<div class="config-label">
|
||||||
|
<el-icon><Watermelon /></el-icon>
|
||||||
|
<span>基于1分区液位</span>
|
||||||
|
</div>
|
||||||
|
<div class="config-values">
|
||||||
|
<span class="value-start"
|
||||||
|
>{{ zone.thresholds.self.start }}cm</span
|
||||||
|
>
|
||||||
|
<span class="value-separator">/</span>
|
||||||
|
<span class="value-stop"
|
||||||
|
>{{ zone.thresholds.self.stop }}cm</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-item">
|
||||||
|
<div class="config-label">
|
||||||
|
<el-icon><Connection /></el-icon>
|
||||||
|
<span>基于2分区液位</span>
|
||||||
|
</div>
|
||||||
|
<div class="config-values">
|
||||||
|
<span class="value-start"
|
||||||
|
>{{ zone.thresholds.zone2.start }}cm</span
|
||||||
|
>
|
||||||
|
<span class="value-separator">/</span>
|
||||||
|
<span class="value-stop"
|
||||||
|
>{{ zone.thresholds.zone2.stop }}cm</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 其他分区正常显示 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="config-item">
|
||||||
|
<div class="config-label">
|
||||||
|
<el-icon><Watermelon /></el-icon>
|
||||||
|
<span>液位高度</span>
|
||||||
|
</div>
|
||||||
|
<div class="config-values">
|
||||||
|
<span class="value-start"
|
||||||
|
>{{ zone.thresholds.self.start }}cm</span
|
||||||
|
>
|
||||||
|
<span class="value-separator">/</span>
|
||||||
|
<span class="value-stop"
|
||||||
|
>{{ zone.thresholds.self.stop }}cm</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="zone-card-footer">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click.stop="openConfigDialog(zone)"
|
||||||
|
>
|
||||||
|
配置设置
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分区配置对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="showConfigDialog"
|
||||||
|
:title="`${currentZone?.name} - 液位阈值配置`"
|
||||||
|
width="600px"
|
||||||
|
top="50px"
|
||||||
|
>
|
||||||
|
<div class="config-dialog-content">
|
||||||
|
<el-form :model="currentZone.thresholds" label-width="140px">
|
||||||
|
<!-- 1分区特殊配置 -->
|
||||||
|
<template v-if="currentZone.id === 1">
|
||||||
|
<el-card class="config-section" shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="section-header">
|
||||||
|
<el-icon><Watermelon /></el-icon>
|
||||||
|
<span>基于1分区液位配置 (cm)</span>
|
||||||
|
<el-tooltip
|
||||||
|
content="启动水泵阈值应大于停止水泵阈值"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-icon><QuestionFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="启动水泵阈值">
|
||||||
|
<el-input-number
|
||||||
|
v-model="currentZone.thresholds.self.start"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:step="1"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="validateSelfThreshold(currentZone)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="停止水泵阈值">
|
||||||
|
<el-input-number
|
||||||
|
v-model="currentZone.thresholds.self.stop"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:step="1"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="validateSelfThreshold(currentZone)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<div v-if="currentZone.errors.self" class="error-message">
|
||||||
|
{{ currentZone.errors.self }}
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="config-section" shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="section-header">
|
||||||
|
<el-icon><Connection /></el-icon>
|
||||||
|
<span>基于2分区液位配置 (cm)</span>
|
||||||
|
<el-tooltip
|
||||||
|
content="启动水泵阈值应小于停止水泵阈值"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-icon><QuestionFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="启动水泵阈值">
|
||||||
|
<el-input-number
|
||||||
|
v-model="currentZone.thresholds.zone2.start"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:step="1"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="validateZone2Threshold(currentZone)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="停止水泵阈值">
|
||||||
|
<el-input-number
|
||||||
|
v-model="currentZone.thresholds.zone2.stop"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:step="1"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="validateZone2Threshold(currentZone)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<div v-if="currentZone.errors.zone2" class="error-message">
|
||||||
|
{{ currentZone.errors.zone2 }}
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 其他分区配置 -->
|
||||||
|
<template v-else>
|
||||||
|
<el-card class="config-section" shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="section-header">
|
||||||
|
<el-icon><Watermelon /></el-icon>
|
||||||
|
<span>液位配置 (cm)</span>
|
||||||
|
<el-tooltip
|
||||||
|
content="启动水泵阈值应大于停止水泵阈值"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-icon><QuestionFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="启动水泵阈值">
|
||||||
|
<el-input-number
|
||||||
|
v-model="currentZone.thresholds.self.start"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:step="1"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="validateSelfThreshold(currentZone)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="停止水泵阈值">
|
||||||
|
<el-input-number
|
||||||
|
v-model="currentZone.thresholds.self.stop"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:step="1"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="validateSelfThreshold(currentZone)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<div v-if="currentZone.errors.self" class="error-message">
|
||||||
|
{{ currentZone.errors.self }}
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="showConfigDialog = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="saveCurrentZoneConfig"
|
||||||
|
:disabled="!currentZone?.isValid"
|
||||||
|
>
|
||||||
|
保存配置
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 批量配置对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="showBatchDialog"
|
||||||
|
title="批量配置液位阈值"
|
||||||
|
width="700px"
|
||||||
|
top="20px"
|
||||||
|
>
|
||||||
|
<div class="config-dialog-content">
|
||||||
|
<el-form :model="batchForm" label-width="140px">
|
||||||
|
<!-- 分区选择 -->
|
||||||
|
<el-form-item label="选择分区(多选)" class="sp-item">
|
||||||
|
<el-select
|
||||||
|
v-model="batchSelectedZones"
|
||||||
|
multiple
|
||||||
|
collapse-tags
|
||||||
|
placeholder="请选择分区"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in zoneConfigs"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- 液位配置 -->
|
||||||
|
<el-card class="config-section" shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="section-header">
|
||||||
|
<el-icon><Watermelon /></el-icon>
|
||||||
|
<span>液位配置 (cm)</span>
|
||||||
|
<el-tooltip
|
||||||
|
content="启动水泵阈值应大于停止水泵阈值"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-icon><QuestionFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="启动水泵阈值">
|
||||||
|
<el-input-number
|
||||||
|
v-model="batchForm.self.start"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:step="1"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="validateBatchThreshold"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="停止水泵阈值">
|
||||||
|
<el-input-number
|
||||||
|
v-model="batchForm.self.stop"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:step="1"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="validateBatchThreshold"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<div v-if="batchErrors.self" class="error-message">
|
||||||
|
{{ batchErrors.self }}
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-divider />
|
||||||
|
|
||||||
|
<!-- 提示信息 -->
|
||||||
|
<el-alert title="提示" type="info" :closable="false" show-icon>
|
||||||
|
<template #default>
|
||||||
|
<p>• 请至少选择一个分区进行配置</p>
|
||||||
|
<p>• 启动水泵阈值应大于停止水泵阈值</p>
|
||||||
|
<p>• 批量配置只会设置各分区基于自身液位的阈值</p>
|
||||||
|
<p>• 1分区的基于2分区液位配置需要单独设置</p>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="showBatchDialog = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="applyBatchAndSave"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
应用配置并保存
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, ref, computed, onMounted } from "vue";
|
||||||
|
// import { getSensorConfigs } from "@/api/index";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import {
|
||||||
|
QuestionFilled,
|
||||||
|
Watermelon,
|
||||||
|
Connection,
|
||||||
|
} from "@element-plus/icons-vue";
|
||||||
|
|
||||||
|
// 默认阈值配置
|
||||||
|
const defaultThresholds = {
|
||||||
|
self: { start: 0, stop: 0 }, // 基于自身液位的配置
|
||||||
|
zone2: { start: 0, stop: 0 }, // 1分区特有的基于2分区液位的配置
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成14个分区的配置
|
||||||
|
const generateZoneConfigs = () => {
|
||||||
|
return zones;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const zoneConfigs = ref([]);
|
||||||
|
const showConfigDialog = ref(false);
|
||||||
|
const currentZone = ref(null);
|
||||||
|
const showBatchDialog = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
// 批量配置相关
|
||||||
|
const batchForm = reactive({
|
||||||
|
self: { start: 0, stop: 0 },
|
||||||
|
});
|
||||||
|
const batchErrors = reactive({ self: "" });
|
||||||
|
const batchSelectedZones = ref([]);
|
||||||
|
|
||||||
|
// 验证阈值逻辑
|
||||||
|
const validateSelfThreshold = (zone) => {
|
||||||
|
const threshold = zone.thresholds.self;
|
||||||
|
let isValid = true;
|
||||||
|
let errorMessage = "";
|
||||||
|
|
||||||
|
if (threshold.start < threshold.stop) {
|
||||||
|
isValid = false;
|
||||||
|
errorMessage = "启动阈值应大于停止阈值";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新验证状态
|
||||||
|
updateZoneValidity(zone, "self", isValid, errorMessage);
|
||||||
|
return isValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateZone2Threshold = (zone) => {
|
||||||
|
const threshold = zone.thresholds.zone2;
|
||||||
|
let isValid = true;
|
||||||
|
let errorMessage = "";
|
||||||
|
|
||||||
|
// 更新验证状态
|
||||||
|
updateZoneValidity(zone, "zone2", isValid, errorMessage);
|
||||||
|
return isValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateZoneValidity = (zone, thresholdType, isValid, errorMessage) => {
|
||||||
|
zone.errors[thresholdType] = errorMessage;
|
||||||
|
|
||||||
|
// 检查所有阈值配置是否都有效
|
||||||
|
let overallValid = true;
|
||||||
|
if (zone.id === 1) {
|
||||||
|
// 1分区需要检查两个配置
|
||||||
|
overallValid = !zone.errors.self && !zone.errors.zone2;
|
||||||
|
} else {
|
||||||
|
// 其他分区只检查自身配置
|
||||||
|
overallValid = !zone.errors.self;
|
||||||
|
}
|
||||||
|
|
||||||
|
zone.isValid = overallValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 验证批量配置阈值
|
||||||
|
const validateBatchThreshold = () => {
|
||||||
|
const threshold = batchForm.self;
|
||||||
|
let isValid = true;
|
||||||
|
let errorMessage = "";
|
||||||
|
|
||||||
|
if (threshold.start < threshold.stop) {
|
||||||
|
isValid = false;
|
||||||
|
errorMessage = "启动阈值应大于停止阈值";
|
||||||
|
}
|
||||||
|
|
||||||
|
batchErrors.self = errorMessage;
|
||||||
|
return isValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开配置对话框
|
||||||
|
const openConfigDialog = (zone) => {
|
||||||
|
currentZone.value = JSON.parse(JSON.stringify(zone));
|
||||||
|
showConfigDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openBatchDialog = () => {
|
||||||
|
batchSelectedZones.value = [];
|
||||||
|
batchForm.self = { start: 0, stop: 0 };
|
||||||
|
batchErrors.self = "";
|
||||||
|
showBatchDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 应用批量配置并保存
|
||||||
|
const applyBatchAndSave = async () => {
|
||||||
|
if (batchSelectedZones.value.length === 0) {
|
||||||
|
ElMessage.error("请至少选择一个分区");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证批量阈值
|
||||||
|
if (!validateBatchThreshold()) {
|
||||||
|
ElMessage.error("批量阈值设置不合理");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有设置值
|
||||||
|
if (batchForm.self.start === 0 && batchForm.self.stop === 0) {
|
||||||
|
ElMessage.error("请至少设置启动或停止阈值");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新选中的分区配置
|
||||||
|
batchSelectedZones.value.forEach((zoneId) => {
|
||||||
|
const zone = zoneConfigs.value.find((z) => z.id === zoneId);
|
||||||
|
if (zone) {
|
||||||
|
// 只更新基于自身液位的配置
|
||||||
|
zone.thresholds.self = { ...batchForm.self };
|
||||||
|
// 重新验证
|
||||||
|
validateSelfThreshold(zone);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 模拟API调用
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
console.log(batchSelectedZones.value.join(","), batchForm.self);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let params = {
|
||||||
|
START_THRESHOLD: batchForm.self.start,
|
||||||
|
STOP_THRESHOLD: batchForm.self.stop,
|
||||||
|
};
|
||||||
|
let res = await getSensorConfigs(
|
||||||
|
{
|
||||||
|
config: "pumprule",
|
||||||
|
option: "batchedit",
|
||||||
|
areaid: batchSelectedZones.value.join(","),
|
||||||
|
},
|
||||||
|
{ data: JSON.stringify(params) }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res && res.code == 0) {
|
||||||
|
ElMessage.success("批量配置保存成功");
|
||||||
|
showBatchDialog.value = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("保存失败");
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存当前分区配置
|
||||||
|
const saveCurrentZoneConfig = async () => {
|
||||||
|
if (!currentZone.value || !currentZone.value.isValid) {
|
||||||
|
ElMessage.error("配置有误,请检查阈值设置");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证所有阈值
|
||||||
|
let hasError = false;
|
||||||
|
if (currentZone.value.id === 1) {
|
||||||
|
if (
|
||||||
|
!validateSelfThreshold(currentZone.value) ||
|
||||||
|
!validateZone2Threshold(currentZone.value)
|
||||||
|
) {
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!validateSelfThreshold(currentZone.value)) {
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
ElMessage.error("存在配置错误,请检查后重试");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新原始数据
|
||||||
|
const originalZone = zoneConfigs.value.find(
|
||||||
|
(z) => z.id === currentZone.value.id
|
||||||
|
);
|
||||||
|
if (originalZone) {
|
||||||
|
originalZone.thresholds = JSON.parse(
|
||||||
|
JSON.stringify(currentZone.value.thresholds)
|
||||||
|
);
|
||||||
|
originalZone.isValid = currentZone.value.isValid;
|
||||||
|
originalZone.errors = { ...currentZone.value.errors };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟API调用
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
let params = {
|
||||||
|
START_THRESHOLD: currentZone.value.thresholds.self.start,
|
||||||
|
STOP_THRESHOLD: currentZone.value.thresholds.self.stop,
|
||||||
|
SPECIAL_START: currentZone.value.thresholds.zone2.start,
|
||||||
|
SPECIAL_STOP: currentZone.value.thresholds.zone2.stop,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = await getSensorConfigs(
|
||||||
|
{
|
||||||
|
config: "pumprule",
|
||||||
|
option: "edit",
|
||||||
|
areaid: currentZone.value.id,
|
||||||
|
},
|
||||||
|
{ data: JSON.stringify(params) }
|
||||||
|
);
|
||||||
|
loading.value = false;
|
||||||
|
|
||||||
|
if (res && res.code == 0) {
|
||||||
|
showConfigDialog.value = false;
|
||||||
|
ElMessage({
|
||||||
|
message: "保存配置成功",
|
||||||
|
type: "success",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
message: "保存配置失败",
|
||||||
|
type: "error",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getConfig = async () => {
|
||||||
|
// let res = await getSensorConfigs({
|
||||||
|
// config: "pumprule",
|
||||||
|
// option: "queryall",
|
||||||
|
// });
|
||||||
|
|
||||||
|
// let configs = res.data || [];
|
||||||
|
let configs = [];
|
||||||
|
|
||||||
|
const zones = [];
|
||||||
|
for (let i = 1; i <= 14; i++) {
|
||||||
|
let _areaConfig = configs.filter((d) => {
|
||||||
|
return d.AreaId == i;
|
||||||
|
});
|
||||||
|
if (_areaConfig && _areaConfig[0]) {
|
||||||
|
let area_config = {
|
||||||
|
self: {
|
||||||
|
start: _areaConfig[0].START_THRESHOLD,
|
||||||
|
stop: _areaConfig[0].STOP_THRESHOLD,
|
||||||
|
}, // 基于自身液位的配置
|
||||||
|
zone2: {
|
||||||
|
start: _areaConfig[0].SPECIAL_START,
|
||||||
|
stop: _areaConfig[0].SPECIAL_STOP,
|
||||||
|
}, // 1分区特有的基于2分区液位的配置
|
||||||
|
};
|
||||||
|
zones.push({
|
||||||
|
id: i,
|
||||||
|
name: `分区${i}`,
|
||||||
|
thresholds: area_config,
|
||||||
|
isValid: true,
|
||||||
|
errors: {},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
zones.push({
|
||||||
|
id: i,
|
||||||
|
name: `分区${i}`,
|
||||||
|
thresholds: JSON.parse(JSON.stringify(defaultThresholds)),
|
||||||
|
isValid: true,
|
||||||
|
errors: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zoneConfigs.value = zones;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化配置
|
||||||
|
onMounted(() => {
|
||||||
|
// 这里可以加载初始数据
|
||||||
|
getConfig();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pump-threshold-config-page {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分区网格布局 */
|
||||||
|
.zones-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
grid-template-rows: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分区卡片样式 */
|
||||||
|
.zone-card {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.15);
|
||||||
|
border-color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 配置预览样式 */
|
||||||
|
.zone-config-preview {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-values {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-start {
|
||||||
|
color: #67c23a;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-separator {
|
||||||
|
color: #909399;
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-stop {
|
||||||
|
color: #e6a23c;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone-card-footer {
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
background-color: #fef0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 3px solid #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-card__body {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
::v-deep .el-card__header {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h2 {
|
||||||
|
color: #303133;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 30px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.page-header p {
|
||||||
|
color: #000;
|
||||||
|
align-items: end;
|
||||||
|
line-height: 36px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.page-header-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form-item {
|
||||||
|
height: 32px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .sp-item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .sp-item .el-form-item__label {
|
||||||
|
text-align: left !important;
|
||||||
|
width: 120px !important;
|
||||||
|
justify-content: start !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,356 @@
|
||||||
|
<template>
|
||||||
|
<div class="sensor-threshold-config">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<h2>传感器报警阈值配置</h2>
|
||||||
|
<p class="page-description">配置传感器上下限报警阈值</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 传感器列表 -->
|
||||||
|
<div class="sensor-list">
|
||||||
|
<el-card class="sensor-card" v-for="sensor in sensors" :key="sensor.id">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="sensor-info">
|
||||||
|
<h3>{{ sensor.name }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 阈值配置表单 -->
|
||||||
|
<el-form
|
||||||
|
:model="sensor"
|
||||||
|
:rules="thresholdRules"
|
||||||
|
ref="formRefs"
|
||||||
|
label-width="100px"
|
||||||
|
class="threshold-form"
|
||||||
|
>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item :label="sensor.TypeId == 31 ? '上限阈值' : '限值一'" prop="upperLimit">
|
||||||
|
<el-input-number
|
||||||
|
v-model="sensor.upperLimit"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
:placeholder="`请输入上限阈值${sensor.unit}`"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item :label="sensor.TypeId == 31 ? '下限阈值' : '限值二'" prop="lowerLimit">
|
||||||
|
<el-input-number
|
||||||
|
v-model="sensor.lowerLimit"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
:placeholder="`请输入下限阈值${sensor.unit}`"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="saveThresholds(sensor)"
|
||||||
|
:loading="saving"
|
||||||
|
>
|
||||||
|
保存配置
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
// import { handlerData } from "@/api/index";
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const saving = ref(false);
|
||||||
|
const formRefs = ref({});
|
||||||
|
|
||||||
|
// 阈值验证规则
|
||||||
|
const thresholdRules = {
|
||||||
|
upperLimit: [
|
||||||
|
{ required: true, message: "请输入上限阈值", trigger: "blur" },
|
||||||
|
{
|
||||||
|
type: "number",
|
||||||
|
min: 0,
|
||||||
|
message: "上限阈值必须大于等于0",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
lowerLimit: [
|
||||||
|
{ required: true, message: "请输入下限阈值", trigger: "blur" },
|
||||||
|
{
|
||||||
|
type: "number",
|
||||||
|
min: 0,
|
||||||
|
message: "下限阈值必须大于等于0",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 传感器数据
|
||||||
|
const sensors = ref([
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
TypeId: 26,
|
||||||
|
name: "温度传感器",
|
||||||
|
upperLimit: 0,
|
||||||
|
lowerLimit: 0,
|
||||||
|
unit: "℃",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
TypeId: 30,
|
||||||
|
name: "湿度传感器",
|
||||||
|
upperLimit: 0,
|
||||||
|
lowerLimit: 0,
|
||||||
|
unit: "%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
TypeId: 31,
|
||||||
|
name: "氧气传感器",
|
||||||
|
upperLimit: 0,
|
||||||
|
lowerLimit: 0,
|
||||||
|
unit: "%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
TypeId: 32,
|
||||||
|
name: "液位传感器",
|
||||||
|
upperLimit: 0,
|
||||||
|
lowerLimit: 0,
|
||||||
|
unit: "cm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
TypeId: 34,
|
||||||
|
name: "硫化氢传感器",
|
||||||
|
upperLimit: 0,
|
||||||
|
lowerLimit: 0,
|
||||||
|
unit: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
TypeId: 35,
|
||||||
|
name: "甲烷传感器",
|
||||||
|
upperLimit: 0,
|
||||||
|
lowerLimit: 0,
|
||||||
|
unit: "ppm",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const getThresholds = async () => {
|
||||||
|
let params = {
|
||||||
|
action: "GetDeviceTypeLimitAll",
|
||||||
|
};
|
||||||
|
let res = await handlerData(params);
|
||||||
|
if (res && res.data && res.data.length) {
|
||||||
|
sensors.value.forEach((d) => {
|
||||||
|
let _data = res.data.filter((c) => {
|
||||||
|
return c.TypeId === d.TypeId;
|
||||||
|
});
|
||||||
|
d.upperLimit = Number(_data[0].UpperLimitValue);
|
||||||
|
d.lowerLimit = Number(_data[0].LowerLimitValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
const saveThresholds = async (sensor) => {
|
||||||
|
try {
|
||||||
|
// 验证表单
|
||||||
|
const formRef = formRefs.value[sensor.id];
|
||||||
|
if (formRef) {
|
||||||
|
await formRef.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证阈值逻辑
|
||||||
|
if (sensor.upperLimit <= sensor.lowerLimit) {
|
||||||
|
ElMessage.error("上限阈值必须大于下限阈值");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saving.value = true;
|
||||||
|
|
||||||
|
let params = {
|
||||||
|
action: "UpdateDeviceTypeLimitValue",
|
||||||
|
TypeId: sensor.TypeId,
|
||||||
|
UpperLimitValue: sensor.upperLimit,
|
||||||
|
LowerLimitValue: sensor.lowerLimit,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = await handlerData(params);
|
||||||
|
if (res && res.message == 'success') {
|
||||||
|
ElMessage.success(`${sensor.name} 阈值配置保存成功`);
|
||||||
|
} else {
|
||||||
|
ElMessage.error("保存失败,请检查输入值");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("保存失败,请检查输入值");
|
||||||
|
} finally {
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生命周期
|
||||||
|
onMounted(() => {
|
||||||
|
// getThresholds();
|
||||||
|
console.log("传感器阈值配置页面已加载");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sensor-threshold-config {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h2 {
|
||||||
|
color: #303133;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-description {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sensor-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sensor-card {
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sensor-card:hover {
|
||||||
|
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sensor-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sensor-info h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #303133;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-label {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.normal-value {
|
||||||
|
color: #67c23a;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-value {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.threshold-form {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sensor-threshold-config {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sensor-list {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sensor-info {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片样式优化 */
|
||||||
|
:deep(.el-card__header) {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单样式优化 */
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input-number) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input-number .el-input__inner) {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮样式优化 */
|
||||||
|
:deep(.el-button) {
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-button--primary) {
|
||||||
|
background-color: #409eff;
|
||||||
|
border-color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-button--primary:hover) {
|
||||||
|
background-color: #66b1ff;
|
||||||
|
border-color: #66b1ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -70,10 +70,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="canvas-wrapper">
|
<div class="canvas-wrapper">
|
||||||
<!-- 底层Canvas:仅绘制背景图(静态内容) -->
|
<!-- 底层Canvas:仅绘制背景图(静态内容) -->
|
||||||
<canvas
|
<canvas ref="bgCanvasRef" class="bg-canvas"></canvas>
|
||||||
ref="bgCanvasRef"
|
|
||||||
class="bg-canvas"
|
|
||||||
></canvas>
|
|
||||||
<!-- 上层Canvas:绘制图表、标记点等动态内容 -->
|
<!-- 上层Canvas:绘制图表、标记点等动态内容 -->
|
||||||
<canvas
|
<canvas
|
||||||
ref="canvasRef"
|
ref="canvasRef"
|
||||||
|
|
@ -90,6 +87,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="charts-content">
|
||||||
|
<div ref="chartRef" class="line-chart"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="progress-container spslider">
|
<div class="progress-container spslider">
|
||||||
|
|
@ -158,6 +158,7 @@ import {
|
||||||
import typesDictionary from "../utils/equipmentType"; //设备类型字典
|
import typesDictionary from "../utils/equipmentType"; //设备类型字典
|
||||||
import videoEle from "./components/videoEle.vue";
|
import videoEle from "./components/videoEle.vue";
|
||||||
import tableEle from "./components/tableEle.vue";
|
import tableEle from "./components/tableEle.vue";
|
||||||
|
import * as echarts from "echarts";
|
||||||
|
|
||||||
const tableModelValue = ref(false);
|
const tableModelValue = ref(false);
|
||||||
// 新增:底层背景Canvas引用
|
// 新增:底层背景Canvas引用
|
||||||
|
|
@ -248,7 +249,7 @@ const MOUSE_CURRENT_Y = ref(0);
|
||||||
const activeMarker = ref({ target: "" });
|
const activeMarker = ref({ target: "" });
|
||||||
const Shrink = ref(false);
|
const Shrink = ref(false);
|
||||||
|
|
||||||
const SELECT_ACTION_TYPE = ref('');
|
const SELECT_ACTION_TYPE = ref("");
|
||||||
|
|
||||||
//背景图片/标记图片等(保持不变)
|
//背景图片/标记图片等(保持不变)
|
||||||
const backgroundImage = new URL("../assets/gl.png", import.meta.url).href;
|
const backgroundImage = new URL("../assets/gl.png", import.meta.url).href;
|
||||||
|
|
@ -276,7 +277,10 @@ const icons = {
|
||||||
};
|
};
|
||||||
|
|
||||||
icons.baiye.src = new URL("../assets/icon/baiye.png", import.meta.url).href;
|
icons.baiye.src = new URL("../assets/icon/baiye.png", import.meta.url).href;
|
||||||
icons.dianbiao.src = new URL("../assets/icon/dianbiao.png", import.meta.url).href;
|
icons.dianbiao.src = new URL(
|
||||||
|
"../assets/icon/dianbiao.png",
|
||||||
|
import.meta.url
|
||||||
|
).href;
|
||||||
icons.hongwai.src = new URL("../assets/icon/hongwai.png", import.meta.url).href;
|
icons.hongwai.src = new URL("../assets/icon/hongwai.png", import.meta.url).href;
|
||||||
icons.jiawan.src = new URL("../assets/icon/jiawan.png", import.meta.url).href;
|
icons.jiawan.src = new URL("../assets/icon/jiawan.png", import.meta.url).href;
|
||||||
icons.jingai.src = new URL("../assets/icon/jingai.png", import.meta.url).href;
|
icons.jingai.src = new URL("../assets/icon/jingai.png", import.meta.url).href;
|
||||||
|
|
@ -303,6 +307,170 @@ const updateTableModelValue = (value) => {
|
||||||
tableModelValue.value = value;
|
tableModelValue.value = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 图表引用和实例
|
||||||
|
const chartRef = ref(null);
|
||||||
|
let chartInstance = null;
|
||||||
|
|
||||||
|
// 状态变量
|
||||||
|
const darkMode = ref(true);
|
||||||
|
const chartData = ref({
|
||||||
|
xAxis: [
|
||||||
|
"1月",
|
||||||
|
"2月",
|
||||||
|
"3月",
|
||||||
|
"4月",
|
||||||
|
"5月",
|
||||||
|
"6月",
|
||||||
|
"7月",
|
||||||
|
"8月",
|
||||||
|
"9月",
|
||||||
|
"10月",
|
||||||
|
"11月",
|
||||||
|
"12月",
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "产品A",
|
||||||
|
data: [120, 132, 101, 134, 90, 230, 210, 230, 180, 230, 210, 250],
|
||||||
|
color: "#42b983", // 柔和的绿色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "产品B",
|
||||||
|
data: [220, 182, 191, 234, 290, 330, 310, 330, 380, 330, 310, 350],
|
||||||
|
color: "#3498db", // 柔和的蓝色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "产品C",
|
||||||
|
data: [150, 232, 201, 154, 190, 330, 410, 330, 380, 430, 410, 450],
|
||||||
|
color: "#f39c12", // 柔和的橙色
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
// 销毁已有实例
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新实例
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
|
||||||
|
// 设置图表选项
|
||||||
|
const option = {
|
||||||
|
backgroundColor: darkMode.value
|
||||||
|
? "rgba(30, 30, 30, 0.7)"
|
||||||
|
: "rgba(255, 255, 255, 0.7)",
|
||||||
|
tooltip: {
|
||||||
|
trigger: "axis",
|
||||||
|
backgroundColor: darkMode.value
|
||||||
|
? "rgba(40, 40, 40, 0.9)"
|
||||||
|
: "rgba(255, 255, 255, 0.9)",
|
||||||
|
borderColor: darkMode.value ? "#555" : "#ddd",
|
||||||
|
textStyle: {
|
||||||
|
color: darkMode.value ? "#fff" : "#333",
|
||||||
|
},
|
||||||
|
padding: 10,
|
||||||
|
borderRadius: 6,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: chartData.value.series.map((item) => item.name),
|
||||||
|
top: 10,
|
||||||
|
textStyle: {
|
||||||
|
color: darkMode.value ? "#eee" : "#555",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: "3%",
|
||||||
|
right: "4%",
|
||||||
|
bottom: "3%",
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: "category",
|
||||||
|
boundaryGap: false,
|
||||||
|
data: chartData.value.xAxis,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: darkMode.value ? "#555" : "#ddd",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: darkMode.value ? "#bbb" : "#666",
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: darkMode.value
|
||||||
|
? "rgba(255, 255, 255, 0.05)"
|
||||||
|
: "rgba(0, 0, 0, 0.05)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: "value",
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: darkMode.value ? "#555" : "#ddd",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: darkMode.value ? "#bbb" : "#666",
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: darkMode.value
|
||||||
|
? "rgba(255, 255, 255, 0.05)"
|
||||||
|
: "rgba(0, 0, 0, 0.05)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: chartData.value.series.map((item) => ({
|
||||||
|
name: item.name,
|
||||||
|
type: "line",
|
||||||
|
data: item.data,
|
||||||
|
symbol: "circle",
|
||||||
|
symbolSize: 6,
|
||||||
|
emphasis: {
|
||||||
|
symbolSize: 8,
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
color: item.color,
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: item.color,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: darkMode.value ? "#333" : "#fff",
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: "linear",
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: item.color + "80", // 透明度80%
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: item.color + "00", // 透明度0%
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置选项
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
};
|
||||||
|
|
||||||
const pointsToPartiData = (data) => {
|
const pointsToPartiData = (data) => {
|
||||||
let _partiData = {};
|
let _partiData = {};
|
||||||
let areaId = 0;
|
let areaId = 0;
|
||||||
|
|
@ -409,7 +577,7 @@ const initCanvas = () => {
|
||||||
|
|
||||||
// 加载背景图(加载完成后绘制底层背景)
|
// 加载背景图(加载完成后绘制底层背景)
|
||||||
loadImage();
|
loadImage();
|
||||||
})
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIcon = (id) => {
|
const getIcon = (id) => {
|
||||||
|
|
@ -481,7 +649,14 @@ const getCurrentArea = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const isCGQ = (type) => {
|
const isCGQ = (type) => {
|
||||||
if (type == 26 || type == 30 || type == 31 || type == 32 || type == 34 || type == 35) {
|
if (
|
||||||
|
type == 26 ||
|
||||||
|
type == 30 ||
|
||||||
|
type == 31 ||
|
||||||
|
type == 32 ||
|
||||||
|
type == 34 ||
|
||||||
|
type == 35
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -516,7 +691,7 @@ const drawTooltip = () => {
|
||||||
const canvasY = activeMarker.value.y * BL.value * scale.value + offsetY.value;
|
const canvasY = activeMarker.value.y * BL.value * scale.value + offsetY.value;
|
||||||
|
|
||||||
let x = canvasX - tooltipWidth / 2;
|
let x = canvasX - tooltipWidth / 2;
|
||||||
let y = canvasY - tooltipHeight - (20 * scale.value);
|
let y = canvasY - tooltipHeight - 20 * scale.value;
|
||||||
|
|
||||||
const canvasWidth = canvasRef.value.width;
|
const canvasWidth = canvasRef.value.width;
|
||||||
const canvasHeight = canvasRef.value.height;
|
const canvasHeight = canvasRef.value.height;
|
||||||
|
|
@ -536,7 +711,13 @@ const drawTooltip = () => {
|
||||||
ctxTooltip.lineTo(x + tooltipWidth - radius, y);
|
ctxTooltip.lineTo(x + tooltipWidth - radius, y);
|
||||||
ctxTooltip.arcTo(x + tooltipWidth, y, x + tooltipWidth, y + radius, radius);
|
ctxTooltip.arcTo(x + tooltipWidth, y, x + tooltipWidth, y + radius, radius);
|
||||||
ctxTooltip.lineTo(x + tooltipWidth, y + tooltipHeight - radius);
|
ctxTooltip.lineTo(x + tooltipWidth, y + tooltipHeight - radius);
|
||||||
ctxTooltip.arcTo(x + tooltipWidth, y + tooltipHeight, x + tooltipWidth - radius, y + tooltipHeight, radius);
|
ctxTooltip.arcTo(
|
||||||
|
x + tooltipWidth,
|
||||||
|
y + tooltipHeight,
|
||||||
|
x + tooltipWidth - radius,
|
||||||
|
y + tooltipHeight,
|
||||||
|
radius
|
||||||
|
);
|
||||||
ctxTooltip.lineTo(x + radius, y + tooltipHeight);
|
ctxTooltip.lineTo(x + radius, y + tooltipHeight);
|
||||||
ctxTooltip.arcTo(x, y + tooltipHeight, x, y + tooltipHeight - radius, radius);
|
ctxTooltip.arcTo(x, y + tooltipHeight, x, y + tooltipHeight - radius, radius);
|
||||||
ctxTooltip.lineTo(x, y + radius);
|
ctxTooltip.lineTo(x, y + radius);
|
||||||
|
|
@ -613,8 +794,11 @@ const draw = (alpha = 1) => {
|
||||||
for (const key in points) {
|
for (const key in points) {
|
||||||
let _points = points[key];
|
let _points = points[key];
|
||||||
_points.forEach((point) => {
|
_points.forEach((point) => {
|
||||||
if(SELECT_ACTION_TYPE.value && point.TypeId !== SELECT_ACTION_TYPE.value) {
|
if (
|
||||||
return
|
SELECT_ACTION_TYPE.value &&
|
||||||
|
point.TypeId !== SELECT_ACTION_TYPE.value
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
let status = getIconStatus(point.TypeId);
|
let status = getIconStatus(point.TypeId);
|
||||||
|
|
||||||
|
|
@ -655,19 +839,78 @@ const draw = (alpha = 1) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加文字传感器数值
|
// 辅助函数:绘制圆角矩形
|
||||||
if(point.TypeId == 26) {
|
function roundedRect(ctx, x, y, width, height, radius, isStroke = false) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x + radius, y);
|
||||||
|
ctx.lineTo(x + width - radius, y);
|
||||||
|
ctx.arcTo(x + width, y, x + width, y + radius, radius);
|
||||||
|
ctx.lineTo(x + width, y + height - radius);
|
||||||
|
ctx.arcTo(
|
||||||
|
x + width,
|
||||||
|
y + height,
|
||||||
|
x + width - radius,
|
||||||
|
y + height,
|
||||||
|
radius
|
||||||
|
);
|
||||||
|
ctx.lineTo(x + radius, y + height);
|
||||||
|
ctx.arcTo(x, y + height, x, y + height - radius, radius);
|
||||||
|
ctx.lineTo(x, y + radius);
|
||||||
|
ctx.arcTo(x, y, x + radius, y, radius);
|
||||||
|
ctx.closePath();
|
||||||
|
|
||||||
ctx.value.font = `10px Arial`; // 设置字体和大小
|
if (isStroke) {
|
||||||
ctx.value.fillStyle = "#FFF"; // 设置文字颜色
|
ctx.stroke();
|
||||||
// 计算文字位置(图标右侧10px处,垂直居中对齐)
|
} else {
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加文字传感器数值(带圆角边框和半透明背景)
|
||||||
|
if (point.TypeId == 26) {
|
||||||
|
// 设置字体样式
|
||||||
|
ctx.value.font = `10px Arial`;
|
||||||
|
const textColor = "#FFF"; // 白色字体
|
||||||
|
const borderColor = "rgba(100, 180, 255, 0.9)"; // 浅蓝色边框
|
||||||
|
const bgColor = "rgba(100, 180, 255, 0.4)"; // 同色系半透明背景
|
||||||
|
|
||||||
let _value = point.Data[0].Value; // 传感器数值value
|
let _value = point.Data[0].Value; // 传感器数值value
|
||||||
|
|
||||||
const textX = point.x * BL.value + (iSize * BL.value) / scale.value / 2 + 1;
|
// 计算文字位置(图标右侧10px处,垂直居中对齐)
|
||||||
const textY = point.y * BL.value + 15; // +5是为了视觉上垂直居中
|
const textX = point.x * BL.value;
|
||||||
ctx.value.fillText(_value, textX, textY); // 绘制文字
|
const textY = point.y * BL.value + 20; // 垂直居中调整
|
||||||
|
|
||||||
|
// 测量文本宽度,用于计算背景框大小
|
||||||
|
const textMetrics = ctx.value.measureText(_value);
|
||||||
|
const padding = 3; // 文字周围的内边距
|
||||||
|
const borderRadius = 3; // 圆角半径
|
||||||
|
|
||||||
|
// 计算背景框的位置和大小
|
||||||
|
const bgX = textX - padding - textMetrics.width / 2;
|
||||||
|
const bgY = textY - 10; // 基于10px字体的位置调整
|
||||||
|
const bgWidth = textMetrics.width + padding * 2;
|
||||||
|
const bgHeight = 14; // 适合10px字体的高度
|
||||||
|
|
||||||
|
// 绘制圆角背景
|
||||||
|
ctx.value.fillStyle = bgColor;
|
||||||
|
roundedRect(ctx.value, bgX, bgY + 10, bgWidth, bgHeight, borderRadius);
|
||||||
|
|
||||||
|
// 绘制圆角边框
|
||||||
|
ctx.value.strokeStyle = borderColor;
|
||||||
|
ctx.value.lineWidth = 1;
|
||||||
|
roundedRect(
|
||||||
|
ctx.value,
|
||||||
|
bgX,
|
||||||
|
bgY + 10,
|
||||||
|
bgWidth,
|
||||||
|
bgHeight,
|
||||||
|
borderRadius,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// 绘制文字(确保在最上层)
|
||||||
|
ctx.value.fillStyle = textColor;
|
||||||
|
ctx.value.fillText(_value, textX - textMetrics.width / 2, textY + 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (point.target == "device" && point.IsOpen) {
|
if (point.target == "device" && point.IsOpen) {
|
||||||
|
|
@ -682,7 +925,11 @@ const draw = (alpha = 1) => {
|
||||||
ctx.value.strokeStyle = window.customConfigUrl.faultColor;
|
ctx.value.strokeStyle = window.customConfigUrl.faultColor;
|
||||||
ctx.value.lineWidth = (4 / scale.value) * BL.value;
|
ctx.value.lineWidth = (4 / scale.value) * BL.value;
|
||||||
ctx.value.stroke();
|
ctx.value.stroke();
|
||||||
} else if (point.target == "device" && !isCGQ(point.TypeId) && point.TypeId != 66) {
|
} else if (
|
||||||
|
point.target == "device" &&
|
||||||
|
!isCGQ(point.TypeId) &&
|
||||||
|
point.TypeId != 66
|
||||||
|
) {
|
||||||
ctx.value.strokeStyle = window.customConfigUrl.closeColor;
|
ctx.value.strokeStyle = window.customConfigUrl.closeColor;
|
||||||
ctx.value.lineWidth = (4 / scale.value) * BL.value;
|
ctx.value.lineWidth = (4 / scale.value) * BL.value;
|
||||||
ctx.value.stroke();
|
ctx.value.stroke();
|
||||||
|
|
@ -763,7 +1010,8 @@ const onMouseMove = (e) => {
|
||||||
activeReferenceLine.value = closestLine;
|
activeReferenceLine.value = closestLine;
|
||||||
if (closestLine !== null) {
|
if (closestLine !== null) {
|
||||||
const easingFactor = 0.2;
|
const easingFactor = 0.2;
|
||||||
currentPoint.y = currentPoint.y + (closestLine - currentPoint.y) * easingFactor;
|
currentPoint.y =
|
||||||
|
currentPoint.y + (closestLine - currentPoint.y) * easingFactor;
|
||||||
if (Math.abs(currentPoint.y - closestLine) < 10) {
|
if (Math.abs(currentPoint.y - closestLine) < 10) {
|
||||||
currentPoint.y = closestLine;
|
currentPoint.y = closestLine;
|
||||||
}
|
}
|
||||||
|
|
@ -934,7 +1182,10 @@ const handleWheel = (e) => {
|
||||||
|
|
||||||
const zoomIntensity = 0.1;
|
const zoomIntensity = 0.1;
|
||||||
const wheel = e.deltaY < 0 ? 1 : -1;
|
const wheel = e.deltaY < 0 ? 1 : -1;
|
||||||
const newScale = Math.max(0.75, Math.min(2, scale.value + wheel * zoomIntensity));
|
const newScale = Math.max(
|
||||||
|
0.75,
|
||||||
|
Math.min(2, scale.value + wheel * zoomIntensity)
|
||||||
|
);
|
||||||
|
|
||||||
scalePercent.value = Math.round(newScale * 100);
|
scalePercent.value = Math.round(newScale * 100);
|
||||||
|
|
||||||
|
|
@ -960,7 +1211,9 @@ const handleCanvasClick = (e) => {
|
||||||
const originalY = (y - offsetY.value) / scale.value;
|
const originalY = (y - offsetY.value) / scale.value;
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`点击坐标: (${(originalX / BL.value).toFixed(1)}, ${(originalY / BL.value).toFixed(1)})`
|
`点击坐标: (${(originalX / BL.value).toFixed(1)}, ${(
|
||||||
|
originalY / BL.value
|
||||||
|
).toFixed(1)})`
|
||||||
);
|
);
|
||||||
|
|
||||||
let clickedPoint = false;
|
let clickedPoint = false;
|
||||||
|
|
@ -1137,6 +1390,7 @@ const upDialogZindex = (e) => {
|
||||||
// 初始化
|
// 初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getJson();
|
getJson();
|
||||||
|
initChart();
|
||||||
window.addEventListener("resize", resizeCanvas);
|
window.addEventListener("resize", resizeCanvas);
|
||||||
initCanvas();
|
initCanvas();
|
||||||
// animate(); // 根据原代码注释,按需启用
|
// animate(); // 根据原代码注释,按需启用
|
||||||
|
|
@ -1282,7 +1536,8 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 新增:两层Canvas样式,确保完全重叠 */
|
/* 新增:两层Canvas样式,确保完全重叠 */
|
||||||
.bg-canvas, .main-canvas {
|
.bg-canvas,
|
||||||
|
.main-canvas {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -1383,9 +1638,8 @@ button:active {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin-top: 15px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 30px;
|
top: 30px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
box-shadow: 0 4px 10px rgba(255, 255, 255, 0.1);
|
box-shadow: 0 4px 10px rgba(255, 255, 255, 0.1);
|
||||||
|
|
@ -1407,6 +1661,24 @@ button:active {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.charts-content {
|
||||||
|
width: 800px;
|
||||||
|
height: 240px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
border-radius: 4px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
box-shadow: 0 4px 10px rgba(255, 255, 255, 0.1);
|
||||||
|
display: flex;
|
||||||
|
.line-chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/deep/ .el-slider__button {
|
/deep/ .el-slider__button {
|
||||||
height: 10px !important;
|
height: 10px !important;
|
||||||
width: 10px !important;
|
width: 10px !important;
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,9 @@
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item style="float: right;margin: 0;">
|
||||||
|
<el-button type="primary" @click="exportFun" style="margin: 0;">导出</el-button>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<!-- 操作记录表格:边框 + 斑马纹 -->
|
<!-- 操作记录表格:边框 + 斑马纹 -->
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue