接线图角落弹框

This commit is contained in:
stt
2025-10-23 15:47:56 +08:00
parent f3252f8a15
commit b3154e894a
5 changed files with 6601 additions and 5035 deletions

View File

@@ -0,0 +1,498 @@
<template>
<div class="container">
<!-- 使用 v-for 遍历四个角落 -->
<div v-for="corner in corners" :key="corner.id" :class="['corner', corner.className]">
<div class="content">
<div class="title" style="display: flex">
<img src="@/assets/img/lightning.png" style="width: 25px; height: 25px; margin-right: 10px" />
<span>{{ corner.title }}</span>
</div>
<table class="table">
<thead>
<tr>
<th>名称</th>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in corner.data" :key="index">
<td>{{ item.name }}</td>
<td>{{ item.a }}</td>
<td>{{ item.b }}</td>
<td>{{ item.c }}</td>
</tr>
</tbody>
</table>
<!-- <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"
></el-descriptions-item>
</el-descriptions> -->
</div>
<span class="close-btn" @click="closeCorner(corner.id)">
<Close />
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick, reactive } from 'vue'
// import { clickImage } from "@/api/manage_wx";
import { Close } from '@element-plus/icons-vue'
import socketClient from '@/utils/webSocketClient'
//开始创建webSocket客户端
const dataSocket = reactive({
socketServe: socketClient.Instance
})
// 定义四个角落的数据
const corners = ref([
{
id: 'topLeft',
title: '左上',
className: 'top-left',
show: false,
data: [
{ name: '电压', a: 1098, b: 2080, c: 3006 },
{ name: '电流', a: 15, b: 25, c: 35 },
{ name: '电压畸变率', a: 12, b: 22, c: 32 },
{ name: '电流畸变率', a: 18, b: 28, c: 38 },
{ name: '电压偏值', a: 12, b: 22, c: 32 },
{ name: '电压不平衡', a: 18, b: 28, c: 38 }
] as any[],
elementId: '', // 记录该角落对应的元素ID
raceLists: [] as any[] // 为每个角落添加独立的跑马灯数据存储
},
{
id: 'topRight',
title: '右上',
className: 'top-right',
show: false,
data: [] as any[],
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: "",
// },
])
interface DataTableItem {
name: string
a: number
b: number
c: number
}
const tableData = ref<DataTableItem[]>([
{ name: '电压', a: 1098, b: 2080, c: 3006 },
{ name: '电流', a: 15, b: 25, c: 35 },
{ name: '电压畸变率', a: 12, b: 22, c: 32 },
{ name: '电流畸变率', a: 18, b: 28, c: 38 },
{ name: '电压偏值', a: 12, b: 22, c: 32 },
{ name: '电压不平衡', a: 18, b: 28, c: 38 }
])
const steadyStateList = ref([])
const selectedId = ref('')
// 内部响应式数据
const eventList = ref<any[]>([])
// const handleClickImage = async (elementId: string) => {
// // 检查 elementId 是否有值,没有值则直接返回空数组
// if (!elementId) {
// eventList.value = [];
// return;
// }
// try {
// // 发送点击图片请求
// const res = await clickImage({ lineId: elementId });
// // 确保返回的数据是数组格式,并且过滤掉 null/undefined 元素
// let dataToStore: any[] = [];
// if (Array.isArray(res.data)) {
// dataToStore = res.data.filter(
// (item:any) => item !== null && item !== undefined
// );
// } else if (res.data && Array.isArray(res.data.records)) {
// dataToStore = res.data.records.filter(
// (item:any) => 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 = [];
// }
// };
// 记录显示顺序,用于循环替换
const displayOrder = ref<number[]>([])
// 更新指定角落数据的函数
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
}
// 显示下一个角落的函数
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: any) => item.lineId == corner.elementId)
.forEach((item: any) => {
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>`)
: ''
})
}
})
}
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)
}
// 监听来自 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: 180px;
/* height: 135px; */
background-color: #fff;
position: absolute;
color: #000;
/* 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;
padding: 5px;
/* color: #409eff; */
border-bottom: 1px solid #444;
background-color: #81d3f8;
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;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th,
.table td {
border: 1px solid #000;
text-align: center;
font-size: 12px;
}
</style>