<template>
|
<div class="h-full flex">
|
<!-- <div class="h-full flex-grow-0 flex-shrink-0 w-1/5 bg-[#277ad3] p-8 !text-white"></div> -->
|
|
<div class="h-full flex-auto flex" style="flex-direction: column">
|
<div class="items-center flex-grow-0 flex-shrink-0 flex space-x-2 ml-auto mr-5 mt-2">
|
<!-- <el-switch title="显示/隐藏值" v-model="isShowValue" inline-prompt :active-icon="Check" :inactive-icon="Close"> </el-switch> -->
|
<el-icon title="适应窗口" class="cursor-pointer" size="30" @click="fitContentClick"><ScaleToOriginal /></el-icon>
|
</div>
|
<div class="flex-auto">
|
<div v-loading="graphLoading" class="w-full h-full" ref="containerRef"></div>
|
</div>
|
<div
|
class="z-40 fixed p-2 bg-[#ebebeb]"
|
style="transform-origin: center top"
|
:style="{
|
left: `${hoverState.position.x}px`,
|
top: `${hoverState.position.y}px`,
|
visibility: hoverState.isShow ? 'visible' : 'hidden',
|
}"
|
>
|
<div class="font-bold mb-1">{{ hoverState.data?.title }}</div>
|
<div class="w-full space-y-1">
|
<div v-if="hoverState.data?.originData" class="flex">
|
<div class="w-32">SCADA ID</div>
|
<div class="before:content-[':'] before:pr-0.5">{{ hoverState.data?.originData?.ScadaId }}</div>
|
</div>
|
<div class="flex">
|
<div class="w-32">模型对象ID</div>
|
<div class="before:content-[':'] before:pr-0.5">{{ hoverState.data?.modelId }}</div>
|
</div>
|
<template v-if="hoverState.data?.originData">
|
<div class="flex">
|
<div class="w-32">检测值</div>
|
<div class="before:content-[':'] before:pr-0.5">
|
{{
|
hoverState.data?.originData?.Unit
|
? `${hoverState.data?.originData?.ScadaValue ?? '--'} ${hoverState.data?.originData?.Unit}`
|
: hoverState.data?.originData?.ScadaValue ?? '--'
|
}}
|
</div>
|
</div>
|
<div class="flex">
|
<div class="w-32">计算值</div>
|
<div class="before:content-[':'] before:pr-0.5">
|
{{
|
hoverState.data?.originData?.Unit
|
? `${hoverState.data?.originData?.ModelValue ?? '--'} ${hoverState.data?.originData?.Unit}`
|
: hoverState.data?.originData?.ModelValue ?? '--'
|
}}
|
</div>
|
</div>
|
<div
|
class="flex"
|
v-if="
|
(hoverState.data?.originData?.ModelValue || hoverState.data?.originData?.ModelValue === 0) &&
|
(hoverState.data?.originData?.ScadaValue || hoverState.data?.originData?.ScadaValue === 0)
|
"
|
>
|
<div class="w-32">差值</div>
|
<div class="before:content-[':'] before:pr-0.5">
|
{{
|
hoverState.data?.originData?.Unit
|
? `${hoverState.data.dValue} ${hoverState.data?.originData?.Unit}`
|
: hoverState.data.dValue
|
}}
|
</div>
|
</div>
|
</template>
|
</div>
|
</div>
|
</div>
|
<div
|
class="h-full flex-grow-0 flex-shrink-0 flex flex-col"
|
style="background: linear-gradient(90deg, rgba(45, 129, 217, 0.6), rgba(6, 85, 180, 0.6)); width: 450px"
|
>
|
<el-table
|
v-loading="tableLoading"
|
border
|
class="!h-full table-attr"
|
row-key="id"
|
:cellStyle="{ ...tableCellCenterExceptColumn(), background: 'revert' }"
|
:cell-class-name="getCellClassName"
|
:data="tableData"
|
:header-cell-style="{
|
textAlign: 'center',
|
color: '#fff',
|
}"
|
style="background: linear-gradient(90deg, rgba(45, 129, 217, 0.6), rgba(6, 85, 180, 0.6)) !important"
|
>
|
<el-table-column prop="id" label="名称" fixed="left" show-overflow-tooltip> </el-table-column>
|
<el-table-column prop="value" width="130" label="检测值" show-overflow-tooltip>
|
<template #default="scope">
|
<div class="flex">
|
<span class="overflow-hidden flex-auto text-ellipsis">
|
{{ scope.row.value ?? '--' }}
|
</span>
|
<span class="w-3 mr-3 flex-shrink-0">
|
{{ scope.row.unit }}
|
</span>
|
</div>
|
</template>
|
</el-table-column>
|
</el-table>
|
<el-form
|
class="mt-10"
|
style="padding-left: 10px; padding-right: 10px"
|
:model="dialogFormValue"
|
ref="dialogFormRef"
|
:rules="dialogFormRules"
|
label-width="130"
|
>
|
<el-form-item class="w-full" label="1输水目标流量" prop="totalFlow1">
|
<el-input-number class="!w-full" :controls="false" v-model="dialogFormValue.totalFlow1"></el-input-number>
|
</el-form-item>
|
<el-form-item class="w-full" label="1输水目标压力" prop="totalPressure1">
|
<el-input-number class="!w-full" :controls="false" v-model="dialogFormValue.totalPressure1"></el-input-number>
|
</el-form-item>
|
|
<el-form-item class="w-full" label="2输水目标流量" prop="totalFlow2">
|
<el-input-number class="!w-full" :controls="false" v-model="dialogFormValue.totalFlow2"></el-input-number>
|
</el-form-item>
|
<el-form-item class="w-full" label="2输水目标压力" prop="totalPressure2">
|
<el-input-number class="!w-full" :controls="false" v-model="dialogFormValue.totalPressure2"></el-input-number>
|
</el-form-item>
|
<div class="" style="margin-bottom: 18px">
|
<div>
|
<el-button type="primary" color="#77a6ec" @click="submitFormValue" class="!text-white w-full">计算</el-button>
|
</div>
|
</div>
|
</el-form>
|
</div>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import './model/shape/index';
|
import type { Graph } from '@antv/x6';
|
import { ScaleToOriginal } from '@element-plus/icons-vue';
|
import { ElLoadingService, type FormInstance, type FormRules } from 'element-plus';
|
import { debounce } from 'lodash';
|
import { onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
import { REFRESH_INTERVAL } from './model/constants';
|
import { ModelGraph, type ScheduleValidationParams } from './model/graph';
|
import { HIGH_LIGHT_LINE_COLOR, LINE_COLOR } from './model/shape/common/baseConfig';
|
import { LINE_NODE_SHAPE, ModelShape } from './model/shape/common/types';
|
import type { ScadaItemRes, ScheduleValidationRes } from '/@/api/dispatch/hydraulicModel';
|
import { GetScadaList } from '/@/api/dispatch/hydraulicModel';
|
import { tableCellCenterExceptColumn } from '/@/utils/util';
|
|
//#region ====================== 调度计算 ======================
|
|
const dialogFormRef = ref<FormInstance>(null);
|
|
// const dialogFormValue = ref<ScheduleValidationParams>({
|
// totalFlow1: 23394.0,
|
// totalFlow2: 38997.2,
|
// totalPressure1: 0.3355,
|
// totalPressure2: 0.1745,
|
// });
|
const dialogFormValue = ref<ScheduleValidationParams>({
|
totalFlow1: null,
|
totalFlow2: null,
|
totalPressure1: null,
|
totalPressure2: null,
|
});
|
const dialogFormRules = ref<FormRules>({
|
totalFlow1: [{ required: true, message: '请输入1输水目标流量', trigger: 'blur' }],
|
totalFlow2: [{ required: true, message: '请输入2输水目标流量', trigger: 'blur' }],
|
totalPressure1: [{ required: true, message: '请输入1输水目标压力', trigger: 'blur' }],
|
totalPressure2: [{ required: true, message: '请输入2输水目标压力', trigger: 'blur' }],
|
});
|
|
const handleModelData = (data: ScheduleValidationRes) => {
|
if (!data.ScheduleValues) return;
|
const nodeData = data.ScheduleValues.map((item) => ({
|
id: item.ModelId,
|
value: item.ModelValue,
|
}));
|
if (isShowValue.value) {
|
modelGraph.showNodesValue(nodeData);
|
}
|
};
|
|
const handleScadaData = (data: ScheduleValidationRes) => {
|
if (!data.ScheduleValues) return;
|
for (const item of data.ScheduleValues) {
|
const { ScadaId, ScadaValue, Unit } = item;
|
const foundIndex = tableData.value.findIndex((item) => item.id === ScadaId);
|
tableData.value[foundIndex].value = ScadaValue;
|
tableData.value[foundIndex].unit = Unit;
|
}
|
};
|
|
const submitFormValue = async () => {
|
const valid = await dialogFormRef.value.validate().catch(() => {});
|
if (!valid) return;
|
const loading = ElLoadingService({
|
text: '加载中...',
|
});
|
const data = await ModelGraph.scheduleValidation(dialogFormValue.value).finally(() => {
|
loading.close();
|
});
|
modelGraph.loadData(data);
|
};
|
|
//#endregion
|
|
//#region ====================== 获取 ScadaList ======================
|
type ScadaItemLogic = {
|
id: string;
|
value: number;
|
unit: string;
|
model: ScadaItemRes;
|
};
|
const tableData = ref<ScadaItemLogic[]>([]);
|
const getScadaList = async () => {
|
tableLoading.value = true;
|
const res = await GetScadaList().finally(() => {
|
tableLoading.value = false;
|
});
|
const resData = (res?.Data || []) as ScadaItemRes[];
|
tableData.value = resData.map((item) => ({
|
get id() {
|
return this.model.Id;
|
},
|
get value() {
|
return this.model.Value;
|
},
|
|
set value(val) {
|
this.model.Value = val;
|
},
|
unit: '',
|
model: item,
|
}));
|
};
|
//#endregion
|
|
//#region ====================== 工具栏 ======================
|
const isShowValue = ref(true);
|
watch(
|
() => isShowValue.value,
|
(val) => {
|
val ? modelGraph.showNodesValue() : modelGraph.hideNodesValue();
|
}
|
);
|
//#endregion
|
|
//#region ====================== 设置表格颜色 ======================
|
|
const getCellClassName = ({ column, columnIndex }) => {
|
if (columnIndex === 1) {
|
// 第二列的索引为1
|
return 'cursor-pointer';
|
}
|
};
|
|
//#endregion
|
let modelGraph: ModelGraph = null;
|
const graphLoading = ref(false);
|
const tableLoading = ref(false);
|
const containerRef = ref<HTMLDivElement>();
|
let graph: Graph = null;
|
|
const hoverState = reactive({
|
isShow: false,
|
position: {
|
x: 0,
|
y: 0,
|
},
|
data: null,
|
});
|
|
const offsetHoverPanel = 10;
|
|
const listenMouseHover = () => {
|
graph.on('cell:mouseenter', ({ cell, e }) => {
|
if (!cell.data) return;
|
const { clientX, clientY } = e;
|
hoverState.isShow = true;
|
hoverState.position.x = clientX + offsetHoverPanel;
|
hoverState.position.y = clientY + offsetHoverPanel;
|
hoverState.data = cell.data;
|
|
if ([ModelShape.Pipe].includes(cell.shape as any)) {
|
cell.attr('line/stroke', HIGH_LIGHT_LINE_COLOR);
|
} else if (!LINE_NODE_SHAPE.includes(cell.shape as any)) {
|
cell.attr('rect/style/visibility', 'visible');
|
} else {
|
if (LINE_NODE_SHAPE.includes(cell.shape as any)) {
|
const children = cell.getChildren();
|
for (const item of children) {
|
if (item.shape === ModelShape.LinkEdge) {
|
item.attr('line/stroke', HIGH_LIGHT_LINE_COLOR);
|
}
|
}
|
} else {
|
const parent = cell.getParent();
|
const children = parent.getChildren();
|
for (const item of children) {
|
if (item.shape === ModelShape.LinkEdge) {
|
item.attr('line/stroke', HIGH_LIGHT_LINE_COLOR);
|
}
|
}
|
}
|
}
|
});
|
|
graph.on('cell:mouseleave', ({ cell }) => {
|
hoverState.isShow = false;
|
|
if ([ModelShape.Pipe].includes(cell.shape as any)) {
|
cell.attr('line/stroke', LINE_COLOR);
|
} else if (!LINE_NODE_SHAPE.includes(cell.shape as any)) {
|
cell.attr('rect/style/visibility', 'hidden');
|
} else {
|
if (LINE_NODE_SHAPE.includes(cell.shape as any)) {
|
const children = cell.getChildren();
|
for (const item of children) {
|
if (item.shape === ModelShape.LinkEdge) {
|
item.attr('line/stroke', LINE_COLOR);
|
}
|
}
|
} else {
|
const parent = cell.getParent();
|
|
const children = parent.getChildren();
|
for (const item of children) {
|
if (item.shape === ModelShape.LinkEdge) {
|
item.attr('line/stroke', LINE_COLOR);
|
}
|
}
|
}
|
}
|
});
|
};
|
let refreshTimer = null;
|
|
const fitContentClick = () => {
|
modelGraph.zoomToFit();
|
};
|
onMounted(async () => {
|
getScadaList();
|
|
modelGraph = new ModelGraph({
|
container: containerRef.value!,
|
});
|
|
graph = modelGraph.graph;
|
|
graphLoading.value = true;
|
|
await modelGraph.loadNode().finally(() => {
|
graphLoading.value = false;
|
});
|
|
// graph.on('cell:click', ({ cell }) => {
|
// });
|
listenMouseHover();
|
|
// modelGraph.openDevTools();
|
|
refreshTimer = setInterval(getScadaList, REFRESH_INTERVAL);
|
});
|
|
onBeforeUnmount(() => {
|
clearInterval(refreshTimer);
|
});
|
</script>
|
|
|
<style scoped lang="css">
|
:deep(.el-input__wrapper) {
|
background-color: #1667c4;
|
}
|
:deep(.el-input__inner) {
|
color: white;
|
}
|
|
:deep(.el-form-item__label) {
|
color: white;
|
}
|
.el-table{
|
--el-table-text-color:white
|
}
|
|
.table-attr :deep(.el-icon svg) {
|
color: white;
|
}
|
|
:deep(.el-form-item__error){
|
color:#b50000
|
}
|
|
:deep(.el-table__row) {
|
background: linear-gradient(0deg, rgba(45, 129, 217, 0.3), rgba(6, 85, 180, 0.3)) !important;
|
}
|
|
:deep(.el-table th) {
|
background-color: #8db9e7 !important;
|
}
|
|
:deep(.el-table tbody tr) {
|
pointer-events: none;
|
}
|
</style>
|