<template>
|
<div class="real-time-simulation">
|
<div class="left-top">
|
<span>实时模拟</span>
|
<span class="clickable-text" @click="toggleDetails">{{
|
isCollapsed ? "展开" : "收起"
|
}}</span>
|
</div>
|
|
<el-form
|
:model="formData"
|
label-width="auto"
|
style="max-width: 600px; padding-right: 10px; box-sizing: border-box"
|
>
|
<el-collapse-transition style="margin-top: 10px">
|
<div v-show="!isCollapsed">
|
<el-form-item label="方案名称:">
|
<el-input
|
v-model="formData.name"
|
type="text"
|
placeholder="请输入"
|
></el-input>
|
</el-form-item>
|
<el-form-item label="雨量数据:">
|
<el-select
|
@change="handleChange"
|
v-model="formData.selectedRainfall"
|
placeholder="请选择"
|
popper-class="mySelectStyle"
|
>
|
<el-option
|
v-for="item in options"
|
:key="item.id"
|
:label="item.name"
|
:value="item.name"
|
></el-option>
|
</el-select>
|
</el-form-item>
|
<el-form-item>
|
<div class="table-container">
|
<div
|
class="table-row"
|
v-for="(item, index) in shgList"
|
:key="index"
|
>
|
<input type="checkbox" v-model="item.selected" />
|
<span>{{ item.deviceName }}</span>
|
</div>
|
</div>
|
</el-form-item>
|
</div>
|
</el-collapse-transition>
|
</el-form>
|
|
<!-- <div style="margin-top: 10px;">
|
<label>仿真参数:</label>
|
<div style="width: 100%; height: 60px; background-color: #fff;"></div>
|
</div> -->
|
<div class="buttons">
|
<el-button type="primary" @click="saveSim">保存方案</el-button>
|
<el-button type="success" @click="startPlay">开始模拟</el-button>
|
<el-button type="success" @click="futurePredictions">未来预测</el-button>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import {
|
ref,
|
watch,
|
defineProps,
|
computed,
|
inject,
|
reactive,
|
onMounted,
|
onUnmounted,
|
} from "vue";
|
import { ElMessage } from "element-plus";
|
import { initeWaterPrimitiveView } from "@/utils/water";
|
import { SimAPIStore } from "@/store/simAPI";
|
import { useSimStore } from "@/store/simulation.js";
|
import { EventBus } from "@/eventBus"; // 引入事件总线
|
import { getDeviceInfoSHG, getYLJData } from "@/api/hpApi";
|
import { getSimStart, getSimDataById, getSimresult } from "@/api/trApi";
|
import { ControlSchemeType } from "@/assets/js/lib-pixelstreamingfrontend.esm";
|
|
// 获取 Store 实例
|
const simAPIStore = SimAPIStore();
|
|
const simStore = useSimStore();
|
|
// 表单数据
|
const formData = reactive({
|
name: "方案名称",
|
selectedRainfall: "",
|
type: 2,
|
gauges: [],
|
});
|
|
// 接收父组件传递的 props
|
const props = defineProps({
|
selectedArea: {
|
type: Object,
|
required: true,
|
},
|
});
|
|
const tableData = ref({}); // 表格数据(按雨量数据分组)
|
const isCollapsed = ref(false); // 控制展开/收起状态
|
|
// 雨量计下拉框
|
const options = ref([
|
{ id: "1", name: "气象实时数据" },
|
{ id: "2", name: "雨量计实时数据" },
|
]);
|
|
// 雨量计列表
|
const shgList = ref([]);
|
// 所有的雨量计列表
|
const rainListNoFilter = ref([]);
|
|
// 获取所有雨量计数据(来自接口)
|
const getRainListAll = () => {
|
// 雨量计类型id
|
const ids = "1917487171642212354";
|
getDeviceInfoSHG(ids).then((res) => {
|
rainListNoFilter.value = res.data.pageData;
|
// 根据当前选择的区域自动过滤
|
updateShgListByArea();
|
});
|
};
|
|
// 根据 props.selectedArea.label 过滤雨量计列表,并设置 selected: true
|
const updateShgListByArea = () => {
|
const currentArea = props.selectedArea?.label;
|
|
if (!currentArea) {
|
shgList.value = [];
|
return;
|
}
|
|
shgList.value = rainListNoFilter.value
|
.filter((item) => item.deviceName?.includes(currentArea))
|
.map((device) => ({
|
...device,
|
selected: true, // 默认选中
|
}));
|
|
console.log(shgList.value, "shgList.valueshgList.value");
|
};
|
|
// 下拉框选中值的表格数据变化
|
const handleChange = async (item) => {
|
formData.selectedRainfall = item;
|
|
if (item === "雨量计实时数据") {
|
if (!props.selectedArea) {
|
ElMessage.warning("请先选择区域");
|
shgList.value = [];
|
return;
|
}
|
// 如果还没有加载过数据,则先请求接口加载
|
if (rainListNoFilter.value.length === 0) {
|
getRainListAll(); // 加载全部数据后会自动过滤
|
} else {
|
updateShgListByArea(); // 已有数据就直接过滤
|
}
|
} else {
|
shgList.value = [];
|
}
|
};
|
|
// 监听区域变化,重新过滤数据
|
watch(
|
() => props.selectedArea,
|
(newArea) => {
|
if (!newArea) {
|
ElMessage.warning("请选择一个区域");
|
shgList.value = [];
|
} else if (formData.selectedRainfall === "雨量计实时数据") {
|
handleChange(formData.selectedRainfall);
|
}
|
},
|
{ immediate: true }
|
);
|
|
// 重置表单
|
const resetForm = () => {
|
formData.name = "";
|
formData.selectedRainfall = "";
|
shgList.value = [];
|
};
|
|
const updateSelectedGauges = () => {
|
formData.gauges = shgList.value
|
.filter((item) => item.selected)
|
.map((item) => ({
|
id: item.deviceCode,
|
name: item.deviceName,
|
x: item.longitude,
|
y: item.latitude,
|
r: 10000,
|
}));
|
};
|
|
// 保存方案
|
const saveSim = async () => {
|
try {
|
// getYLJData("1101160300070101")
|
updateSelectedGauges();
|
formData.geom = props.selectedArea;
|
await simAPIStore.addSimCheme(formData);
|
resetForm();
|
EventBus.emit("close-selectArea");
|
} catch (err) {}
|
};
|
|
// 注入模拟操作方法
|
const { startSimulate, endSimulate } = inject("simulateActions");
|
|
// 实时模拟定时器
|
let pollingInterval = null;
|
// 用于记录上次数据条数
|
let lastDataLength = 0;
|
|
async function startPlay() {
|
// 开始模拟前需要先保存方案
|
updateSelectedGauges();
|
|
formData.geom = props.selectedArea;
|
|
// 保存方案
|
const resApi = await simAPIStore.addSimCheme(formData);
|
const schemeId = resApi.data?.data?.id;
|
|
if (!schemeId) {
|
ElMessage.error("方案保存失败,未获取到有效 ID");
|
return;
|
}
|
|
EventBus.emit("close-selectArea");
|
|
// 显示加载中提示
|
const loadingMessage = ElMessage({
|
type: "info",
|
message: "正在启动模拟...",
|
duration: 0,
|
offset: 80,
|
});
|
|
try {
|
// 启动模拟
|
await getSimStart(schemeId);
|
|
// 首次请求延迟 90s
|
setTimeout(async () => {
|
try {
|
const res = await getSimresult(schemeId);
|
console.log(res.data, "实时模拟 - 初始结果");
|
|
if (res.data.length > 0) {
|
handleNewData(res.data, schemeId);
|
}
|
|
// 显示结果并开始轮询
|
loadingMessage.close();
|
startPolling(schemeId);
|
} catch (error) {
|
console.error("首次请求模拟结果失败", error);
|
loadingMessage.close();
|
}
|
}, 3 * 60 * 1000); // 1.5 分钟后第一次请求
|
} catch (error) {
|
loadingMessage.close();
|
ElMessage.error("请求失败:" + (error.message || "未知错误"));
|
console.error("调用 getSimStart 出错:", error);
|
}
|
}
|
|
// 定时五分钟请求
|
function startPolling(schemeId) {
|
stopPolling(); // 确保不会重复启动
|
|
pollingInterval = setInterval(async () => {
|
try {
|
const res = await getSimresult(schemeId);
|
|
if (res.data && res.data.length > 0) {
|
if (res.data.length === lastDataLength) {
|
console.log("主轮询:无新数据,切换为 10 秒高频轮询");
|
|
clearInterval(pollingInterval);
|
pollingInterval = null;
|
|
startFastPolling(schemeId); // 启动高频轮询
|
} else {
|
handleNewData(res.data, schemeId);
|
}
|
}
|
} catch (error) {
|
console.error("轮询获取模拟结果失败", error);
|
}
|
}, 5.6 * 60 * 1000); // 每 5.5 分钟执行一次
|
}
|
|
let fastPollingInterval = null;
|
// 如果五分钟没拿到最新的数据,则开启十秒钟调用一次,拿到最新的数据就停止
|
function startFastPolling(schemeId) {
|
fastPollingInterval = setInterval(async () => {
|
try {
|
const res = await getSimresult(schemeId);
|
|
if (res.data && res.data.length > 0) {
|
if (res.data.length !== lastDataLength) {
|
console.log("高频轮询:检测到新数据,恢复主轮询");
|
|
clearInterval(fastPollingInterval);
|
fastPollingInterval = null;
|
|
handleNewData(res.data, schemeId);
|
|
startPolling(schemeId); // 重新启动主轮询
|
}
|
}
|
} catch (error) {
|
console.error("高频轮询获取模拟结果失败", error);
|
}
|
}, 10 * 1000); // 每 10 秒执行一次
|
}
|
|
// 拿取最新的layer.json存储到pinia中
|
async function handleNewData(dataArray, schemeId) {
|
// 拿服务名称
|
const res = await getSimDataById(schemeId);
|
simStore.setSelectedScheme(res.data[0]); // 更新方案数据
|
|
const latestItem = dataArray[dataArray.length - 1];
|
const currentLength = dataArray.length;
|
|
if (currentLength <= lastDataLength) {
|
console.log("本轮无新数据(长度未变化)");
|
return;
|
}
|
|
// 更新标识
|
lastDataLength = currentLength;
|
|
// 执行更新逻辑
|
console.log("检测到新数据,更新中...");
|
console.log(latestItem, "last");
|
simStore.layerDate = latestItem;
|
initeWaterPrimitiveView();
|
|
try {
|
startSimulate();
|
} catch (error) {
|
console.error("调用 startSimulate 出错:", error);
|
}
|
}
|
|
// 停止轮询函数
|
function stopPolling() {
|
if (pollingInterval) {
|
clearInterval(pollingInterval);
|
pollingInterval = null;
|
}
|
|
if (fastPollingInterval) {
|
clearInterval(fastPollingInterval);
|
fastPollingInterval = null;
|
}
|
|
console.log("轮询已停止");
|
}
|
|
EventBus.on("close-time", () => {
|
stopPolling();
|
});
|
|
const toggleDetails = () => {
|
isCollapsed.value = !isCollapsed.value;
|
};
|
|
const futurePredictions = () => {
|
console.log("未来预测按钮被点击");
|
};
|
|
onUnmounted(() => {
|
EventBus.off("close-time");
|
stopPolling();
|
});
|
</script>
|
|
<style scoped>
|
.custom-dialog {
|
z-index: 3000 !important;
|
}
|
.real-time-simulation {
|
margin-bottom: 20px;
|
}
|
|
.clickable-text {
|
margin-left: 160px;
|
cursor: pointer;
|
font-size: 14px;
|
color: #61f7d4;
|
}
|
|
.details {
|
margin-top: 10px;
|
transition: height 0.3s ease, opacity 0.3s ease;
|
overflow: hidden;
|
}
|
|
.hidden {
|
height: 0;
|
opacity: 0;
|
}
|
|
.input-group {
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
padding-right: 10px;
|
box-sizing: border-box;
|
}
|
|
.input-item {
|
display: flex;
|
align-items: center;
|
}
|
|
label {
|
text-align: left;
|
white-space: nowrap;
|
margin-right: 10px;
|
}
|
|
.el-select {
|
flex: 4;
|
text-align: left;
|
margin-bottom: 10px;
|
}
|
|
.table-container {
|
font-size: 12px;
|
height: 120px;
|
width: 96%;
|
overflow-y: auto;
|
border: 1px solid #ddd;
|
border-radius: 4px;
|
padding: 5px;
|
}
|
|
.table-row {
|
display: flex;
|
justify-content: space-between;
|
padding: 5px 0;
|
border-bottom: 1px solid #ddd;
|
}
|
|
.table-row:last-child {
|
border-bottom: none;
|
}
|
|
.table-row span {
|
flex: 1;
|
text-align: left;
|
}
|
|
.table-row input[type="checkbox"] {
|
margin-right: 10px;
|
}
|
|
.buttons {
|
margin-top: 20px;
|
display: flex;
|
justify-content: flex-end;
|
gap: 10px;
|
padding-right: 10px;
|
box-sizing: border-box;
|
}
|
|
.el-button {
|
flex: 1;
|
}
|
</style>
|