Files
bigscreenWeb/src/views/SagTraceResult_WX/components/manage/iframeDia.vue
2025-10-27 08:55:54 +08:00

588 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="container">
<!-- 使用 v-for 遍历四个角落 -->
<div
v-for="corner in corners"
:key="corner.id"
v-show="corner.show"
:class="['corner', corner.className]"
>
<div class="content">
<div class="title">{{ corner.title }}</div>
<el-descriptions :column="1" size="small" label-width="70px" border>
<el-descriptions-item
v-for="(item, index) in corner.data"
:key="index"
:label="item.label"
>
<!-- {{ item.value }} -->
<div v-html="item.value" v-if="item.label !== '暂降次数'"></div>
<!-- 跑马灯 -->
<div v-else style="display: flex">
<div style="width: 30px">{{ corner.raceLists.length }}</div>
<div class="simple-marquee">
<div class="marquee-content">
<span
style="margin-right: 15px"
v-for="(event, index) in corner.raceLists"
:key="index"
@click="goToRace(event)"
>{{
index +
1 +
"、" +
event.startTime +
"发生" +
event.eventType +
"," +
"残余电压:" +
(event.featureAmplitude * 100).toFixed(2) +
"%" +
"," +
"持续时间:" +
event.duration +
"S"
}}</span
>
</div>
</div>
</div>
</el-descriptions-item>
</el-descriptions>
</div>
<span class="close-btn" @click="closeCorner(corner.id)">
<Close />
</span>
</div>
<DipDetail ref="dipDetail"></DipDetail>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick, reactive } from "vue";
import { clickImage, realTimeData } from "@/api/manage_wx";
import { Close } from "@element-plus/icons-vue";
import socketClient from "@/utils/webSocketClient";
import DipDetail from "../eventStatistics/dipDetail.vue";
import { useStore } from "vuex";
// 定义接收的 props
const props = defineProps<{
eventList?: [];
}>();
//开始创建webSocket客户端
const dataSocket = reactive({
socketServe: socketClient.Instance,
});
// 定义四个角落的数据
const corners = ref([
{
id: "topLeft",
title: "左上",
className: "top-left",
show: false,
data: [] as { label: string; value: string }[],
elementId: "", // 记录该角落对应的元素ID
raceLists: [] as any[], // 为每个角落添加独立的跑马灯数据存储
},
{
id: "topRight",
title: "右上",
className: "top-right",
show: false,
data: [] as { label: string; value: string }[],
elementId: "",
raceLists: [] as any[], // 为每个角落添加独立的跑马灯数据存储
},
// {
// id: "bottomLeft",
// title: "左下",
// className: "bottom-left",
// show: false,
// data: [] as { label: string; value: string }[],
// elementId: "",
// },
// {
// id: "bottomRight",
// title: "右下",
// className: "bottom-right",
// show: false,
// data: [] as { label: string; value: string }[],
// elementId: "",
// },
]);
const steadyStateList = ref([]);
const store = useStore();
const selectedId = ref("");
// 内部响应式数据
const eventList = ref([]);
// 点击跑马灯展示弹框
const dipDetail = ref(null);
const handleClickImage = async (elementId: string) => {
// 检查 elementId 是否有值,没有值则直接返回空数组
if (!elementId) {
eventList.value = [];
return;
}
try {
// 发送点击图片请求
const res = await clickImage({
lineId: elementId,
searchBeginTime: store.state.timeValue[0],
searchEndTime: store.state.timeValue[1],
});
// 确保返回的数据是数组格式,并且过滤掉 null/undefined 元素
let dataToStore: any[] = [];
if (Array.isArray(res.data)) {
dataToStore = res.data.filter(
(item) => item !== null && item !== undefined
);
} else if (res.data && Array.isArray(res.data.records)) {
dataToStore = res.data.records.filter(
(item) => item !== null && item !== undefined
);
} else if (res.data) {
// 如果是单个对象且不为 null
if (res.data !== null && res.data !== undefined) {
dataToStore = [res.data];
}
}
eventList.value = dataToStore;
} catch (error) {
console.error("调用 clickImage 接口出错:", error);
// 出错时设置为空数组,避免后续处理出错
eventList.value = [];
}
};
// 监听 props 变化
// watch(
// () => props.eventList,
// (newVal) => {
// if (newVal && Array.isArray(newVal)) {
// eventList.value = [...newVal];
// dataLoaded.value = true;
// } else {
// dataLoaded.value = false;
// }
// },
// { immediate: true, deep: true }
// );
// 记录显示顺序,用于循环替换
const displayOrder = ref<number[]>([]);
// 计算跑马灯动画时长(毫秒)
const calculateMarqueeDuration = (corner) => {
if (!corner.raceLists || corner.raceLists.length === 0) {
return 25000; // 默认25秒
}
// 计算所有事件文本的总长度
let totalTextLength = 0;
corner.raceLists.forEach((event, index) => {
const text = `${index + 1}${event.startTime}发生${
event.eventType
},残余电压:${(event.featureAmplitude * 100).toFixed(2)}%,持续时间:${
event.duration
}S`;
totalTextLength += text.length;
});
// 添加分隔符长度和边距
const separatorLength =
corner.raceLists.length > 1 ? (corner.raceLists.length - 1) * 15 : 0; // margin-right: 15px
totalTextLength += separatorLength;
// 根据文本长度计算时长(减慢速度)
// 将每字符需要的时间从50ms增加到80ms最少8秒最多90秒
const duration = Math.min(Math.max(totalTextLength * 80, 8000), 90000);
return duration;
};
// 更新指定角落数据的函数
const updateCornerData = (
cornerIndex: number,
dataItem: any,
elementId: string
) => {
// 更新标题为 objName
if (dataItem.objName) {
corners.value[cornerIndex].title = dataItem.objName;
} else {
corners.value[cornerIndex].title = dataItem.stationName;
}
// 格式化数据
corners.value[cornerIndex].data = [
{ label: "监测点", value: dataItem.lineName },
// {
// label: "暂降次数",
// value: dataItem.eventIds.length,
// },
{
label: "暂降次数",
value: ``,
},
// { label: "稳态指标", value: "Ua:65.5 Ub:65.02 Uc:65.27 Uac:112.85 Uab:112.67 Ubc:112.85" },
{
label: "稳态指标",
value: ``,
},
];
// 记录该角落对应的元素ID
corners.value[cornerIndex].elementId = elementId;
corners.value[cornerIndex].show = true;
// 设置动画时长
nextTick(() => {
const duration = calculateMarqueeDuration(corners.value[cornerIndex]);
const marqueeElement = document.querySelector(
`.${corners.value[cornerIndex].className} .marquee-content`
);
if (marqueeElement) {
(marqueeElement as HTMLElement).style.setProperty(
"--marquee-duration",
`${duration}ms`
);
}
});
};
// 显示下一个角落的函数
const showNextCorner = (elementId: string) => {
// 检查该元素ID是否已经显示过
const existingCornerIndex = corners.value.findIndex(
(corner) => corner.elementId === elementId && corner.show
);
if (existingCornerIndex !== -1) {
// 如果该元素已经显示过,不更新数据
return;
}
// 确保 eventList.value 是数组并且过滤掉 null/undefined 元素
if (!Array.isArray(eventList.value)) {
console.warn("eventList.value 不是数组格式:", eventList.value);
return;
}
// 过滤掉 null 和 undefined 元素,然后查找匹配项
const validItems = eventList.value.filter(
(item) => item !== null && item !== undefined
);
const dataItem = validItems.find((item) => item.lineId === elementId);
// 如果没有找到匹配的数据项,则不更新数据
if (!dataItem) {
console.warn("未找到匹配的数据项:", elementId);
return;
}
// 查找一个未显示的角落
const availableCornerIndex = corners.value.findIndex(
(corner) => !corner.show
);
if (availableCornerIndex !== -1) {
// 有空闲角落,显示在该角落
updateCornerData(availableCornerIndex, dataItem, elementId);
// 将事件数据存储到该角落
corners.value[availableCornerIndex].raceLists = dataItem.eventList || [];
// 记录显示顺序
displayOrder.value.push(availableCornerIndex);
} else {
// 没有空闲角落,按顺序替换角落
// 获取需要替换的角落索引(循环替换)
const replaceIndex = displayOrder.value.shift() || 0;
updateCornerData(replaceIndex, dataItem, elementId);
// 将事件数据存储到该角落
corners.value[replaceIndex].raceLists = dataItem.eventList || [];
// 将替换的索引重新加入队列末尾
displayOrder.value.push(replaceIndex);
}
};
// 关闭指定角落的函数
const closeCorner = (id: string) => {
const cornerIndex = corners.value.findIndex((c) => c.id === id);
if (cornerIndex !== -1) {
corners.value[cornerIndex].show = false;
corners.value[cornerIndex].elementId = ""; // 清空元素ID记录
// 从显示顺序中移除该角落索引
const orderIndex = displayOrder.value.indexOf(cornerIndex);
if (orderIndex !== -1) {
displayOrder.value.splice(orderIndex, 1);
}
}
send();
};
// 关闭所有角落的函数
const closeAllCorners = () => {
corners.value.forEach((corner) => {
corner.show = false;
corner.elementId = "";
});
displayOrder.value = [];
};
// 组件挂载后初始化监听器
onMounted(() => {
init();
// 初始化时不显示任何内容
});
// 连接webSocket客户端
const init = () => {
if (!dataSocket.socketServe) {
console.error("WebSocket 客户端实例不存在");
return;
}
dataSocket.socketServe.connect(new Date().getTime());
dataSocket.socketServe.registerCallBack("message", (res: any) => {
if (res.type == 1) {
//稳态指标数据
let steadyState = JSON.parse(res.message);
// console.log(steadyState, "8990hhhhh");
if (steadyState == null || steadyState.length == 0) return;
steadyStateList.value = steadyState;
corners.value.forEach((corner, index) => {
let str = ``;
steadyState
.filter((item) => item.lineId == corner.elementId)
.forEach((item) => {
if (item.value == 3.1415926) {
str += `<div>${item.statisticalName}/</div>`;
} else {
str += `<div>${item.statisticalName}${item.value}${item.unit}</div>`;
}
});
corner.data.length > 0
? (corner.data[2].value = `<div style="max-height: 100px;overflow-y: auto;">${str} </div>`)
: "";
// 更新跑马灯动画时长
nextTick(() => {
const duration = calculateMarqueeDuration(corner);
const marqueeElement = document.querySelector(
`.${corner.className} .marquee-content`
);
if (marqueeElement) {
(marqueeElement as HTMLElement).style.setProperty(
"--marquee-duration",
`${duration}ms`
);
}
});
});
}
});
};
const time = ref(null);
// 推送消息
const send = () => {
dataSocket.socketServe.send({
pageId: selectedId.value,
lineIdList: corners.value
.filter((item) => item.show == true)
.map((corner) => corner.elementId),
});
if (time.value) {
clearTimeout(time.value);
}
if (
corners.value
.filter((item) => item.show == true)
.map((corner) => corner.elementId).length == 0
)
return;
time.value = setInterval(() => {
dataSocket.socketServe.send({
pageId: selectedId.value,
lineIdList: corners.value
.filter((item) => item.show == true)
.map((corner) => corner.elementId),
});
}, 1000 * 60);
};
const goToRace = (row) => {
console.log(row.measurementPointId, "1222");
dipDetail.value.open(row.measurementPointId);
};
// 监听来自 iframe 的消息
window.addEventListener("message", async function (event) {
// 安全起见可以验证消息来源origin
// if (event.origin !== 'https://trusted-origin.com') return;
// 处理从 iframe 发送过来的消息
if (event.data.action === "coreClick") {
const clickedElementId = event.data.coreId;
selectedId.value = event.data.selectedId;
// 调用接口获取最新数据
await handleClickImage(clickedElementId);
// 根据接收到的元素LineId显示对应数据
await showNextCorner(clickedElementId);
await send();
}
});
// 页面卸载时清除定时器
onBeforeUnmount(() => {
clearTimeout(time.value);
dataSocket.socketServe?.closeWs();
});
</script>
<style>
/* 走马灯样式 - 支持逐条显示 */
.simple-marquee {
width: 230px;
height: 24px;
overflow: hidden;
position: relative;
white-space: nowrap;
}
.marquee-content {
display: inline-block;
padding-left: 100%;
/* animation: marquee-single 15s linear infinite; */
animation: marquee-single var(--marquee-duration, 25s) linear infinite;
line-height: 24px;
text-decoration: underline;
text-decoration-color: #4877f6;
}
@keyframes marquee-single {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
/* 鼠标悬停时暂停动画 */
.simple-marquee:hover .marquee-content {
animation-play-state: paused;
cursor: pointer;
}
</style>
<style scoped lang="scss">
.container {
position: relative;
}
.corner {
width: 340px;
/* height: 135px; */
background-color: #2b2d3a90;
position: absolute;
color: white;
/* font-weight: bold; */
/* 添加弹出动画 */
opacity: 0;
transform: scale(0.3);
transition: all 0.4s ease-out;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
/* 显示状态的样式 */
.corner:not([style*="display: none"]):not([style*="display:none"]) {
opacity: 1;
transform: scale(1);
}
.top-left {
top: 10px;
left: 10px;
}
.top-right {
top: 10px;
right: 10px;
}
.bottom-left {
top: 170px;
left: 10px;
}
.bottom-right {
top: 170px;
right: 10px;
}
.content {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.title {
font-size: 16px;
/* text-align: center; */
/* margin-bottom: 8px; */
padding: 8px;
/* color: #409eff; */
border-bottom: 1px solid #444;
background-color: #21232b;
border-radius: 8px 8px 0 0;
}
.data-item {
display: flex;
margin-bottom: 4px;
font-size: 12px;
}
.label {
/* font-weight: bold; */
width: 55px;
flex-shrink: 0;
}
.value {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 关闭按钮样式 */
.close-btn {
position: absolute;
top: 10px;
right: 10px;
width: 14px;
color: white;
font-size: 14px;
font-weight: bold;
cursor: pointer;
}
.indicator {
display: flex;
}
</style>