接线图角落弹框
This commit is contained in:
10908
package-lock.json
generated
10908
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -26,8 +26,10 @@
|
||||
"element-plus": "^2.7.5",
|
||||
"exceljs": "v4.4.0",
|
||||
"file-saver": "v2.0.5",
|
||||
"grid-layout-plus": "^1.1.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jquery": "^3.7.1",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"jszip": "^3.10.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
@@ -38,20 +40,18 @@
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"qs": "^6.12.0",
|
||||
"screenfull": "^6.0.2",
|
||||
"splitpanes": "3.1.5",
|
||||
"splitpanes": "^3.1.5",
|
||||
"use-element-plus-theme": "^0.0.5",
|
||||
"vue": "^3.3.11",
|
||||
"vue-draggable-resizable": "3.0.0-beta.2",
|
||||
"vue-draggable-resizable": "^3.0.0-beta.2",
|
||||
"vue-i18n": "9.10.2",
|
||||
"vue-router": "4",
|
||||
"vue-types": "^5.1.1",
|
||||
"vxe-table": "^4.5.17",
|
||||
"vxe-table-plugin-export-xlsx": "^4.0.7",
|
||||
"grid-layout-plus": "^1.1.0",
|
||||
"vue-i18n": "9.10.2",
|
||||
"qs": "^6.12.0",
|
||||
"vue-types": "^5.1.1",
|
||||
"web-storage-cache": "^1.1.1",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"xe-utils": "^3.5.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -61,9 +61,9 @@
|
||||
"@types/splitpanes": "2.2.6",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"unplugin-auto-import": "^0.16.7",
|
||||
"sass": "^1.69.5",
|
||||
"typescript": "^5.2.2",
|
||||
"unplugin-auto-import": "^0.16.7",
|
||||
"vite": "^5.0.8",
|
||||
"vue-tsc": "^1.8.25"
|
||||
}
|
||||
|
||||
BIN
src/assets/img/lightning.png
Normal file
BIN
src/assets/img/lightning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
214
src/utils/webSocketClient.ts
Normal file
214
src/utils/webSocketClient.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import { ElMessage, EVENT_CODE } from "element-plus";
|
||||
|
||||
// 定义消息类型,用于类型检查
|
||||
type MessageType = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export default class SocketService {
|
||||
// 单例模式实例
|
||||
private static instance: SocketService | null = null;
|
||||
// 和服务端连接的socket对象
|
||||
private ws: WebSocket | null = null;
|
||||
// 存储回调函数
|
||||
private callBackMapping: {
|
||||
[key: string]: ((message: MessageType) => void) | null;
|
||||
} = {};
|
||||
// 标识是否连接成功
|
||||
private connected: boolean = false;
|
||||
// 记录重试的次数
|
||||
private sendRetryCount: number = 0;
|
||||
// 重新连接尝试的次数
|
||||
private connectRetryCount: number = 0;
|
||||
// Web Worker 实例
|
||||
private work: Worker | null = null;
|
||||
// 临时的 Blob URL
|
||||
private workerBlobUrl: string | null = null;
|
||||
// 上次活动时间戳
|
||||
private lastActivityTime: number = 0;
|
||||
// 最后一次收到心跳回复时间
|
||||
private lastResponseHeartTime: number = Date.now();
|
||||
// 重新连接延迟,单位毫秒
|
||||
private reconnectDelay: number = 5000;
|
||||
|
||||
// 单例模式获取实例
|
||||
public static get Instance(): SocketService {
|
||||
if (!this.instance) {
|
||||
this.instance = new SocketService();
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
// 定义连接服务器的方法
|
||||
public async connect(id: any) {
|
||||
if (!window.WebSocket) {
|
||||
console.log("您的浏览器不支持WebSocket");
|
||||
return;
|
||||
}
|
||||
let response = null;
|
||||
if (import.meta.env.VITE_NAME == "beijing") {
|
||||
response = await fetch("/vue/");
|
||||
} else if (import.meta.env.VITE_NAME == "wuxi") {
|
||||
response = await fetch("");
|
||||
}
|
||||
// const response = await fetch("/vue/");
|
||||
|
||||
const mqttUrl = response.headers.get("X-Mqtt-Url");
|
||||
console.log("🚀 ~ SocketService ~ connect ~ mqttUrl:", mqttUrl);
|
||||
setTimeout(() => {
|
||||
//"ws://10.156.193.182:18093/ws/screen" 北京
|
||||
//"ws://192.168.1.130:19001/ws/askRealTimeData/" 无锡
|
||||
const url = (mqttUrl || "ws://192.168.1.127:19001/ws/") + id;
|
||||
// const url = (mqttUrl || "ws://192.168.1.63:18093/ws/screen") + id;
|
||||
console.log("🚀 ~ SocketService ~ connect ~ url:", url);
|
||||
this.ws = new WebSocket(url);
|
||||
|
||||
this.ws.onopen = () => this.handleOpen();
|
||||
this.ws.onclose = () => this.handleClose();
|
||||
this.ws.onerror = () => this.handleError();
|
||||
this.ws.onmessage = (event) => this.handleMessage(event);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// 处理连接成功事件
|
||||
private handleOpen(): void {
|
||||
ElMessage.success("webSocket连接服务端成功了");
|
||||
console.log("连接服务端成功了");
|
||||
this.connected = true;
|
||||
this.connectRetryCount = 0;
|
||||
this.updateLastActivityTime();
|
||||
this.startHeartbeat();
|
||||
}
|
||||
|
||||
// 处理连接关闭事件
|
||||
private handleClose(): void {
|
||||
console.log("连接webSocket服务端关闭");
|
||||
this.connected = false;
|
||||
this.connectRetryCount++;
|
||||
this.clearHeartbeat();
|
||||
// 可根据需要添加重连逻辑
|
||||
// setTimeout(() => this.connect(), 500 * this.connectRetryCount);
|
||||
}
|
||||
|
||||
// 处理连接错误事件
|
||||
private handleError(): void {
|
||||
ElMessage.error("webSocket连接异常!");
|
||||
}
|
||||
|
||||
// 处理服务端发送过来的数据
|
||||
private handleMessage(event: MessageEvent): void {
|
||||
// console.log('🚀 ~ SocketService ~ handleMessage ~ event.data:', event.data)
|
||||
|
||||
if (event.data == "连接成功") {
|
||||
this.sendHeartbeat();
|
||||
} else if (event.data.length > 10) {
|
||||
let message: MessageType;
|
||||
try {
|
||||
// console.log(
|
||||
// 'Received message:', event.data)
|
||||
message = JSON.parse(event.data);
|
||||
this.callBackMapping["message"]!(message);
|
||||
} catch (e) {
|
||||
// console.error("消息解析失败", event.data, e);
|
||||
return;
|
||||
}
|
||||
// console.log("🚀 ~ SocketService ~ handleMessage ~ message:", message)
|
||||
|
||||
// this.callBackMapping["message"]!(message);
|
||||
} else {
|
||||
// ElMessage.error(event.data);
|
||||
}
|
||||
}
|
||||
|
||||
// 启动心跳检测
|
||||
private startHeartbeat(): void {
|
||||
this.lastResponseHeartTime = Date.now();
|
||||
const url = window.URL.createObjectURL(
|
||||
new Blob([
|
||||
"(function(e){setInterval(function(){this.postMessage(null)},30000)})()",
|
||||
])
|
||||
);
|
||||
this.workerBlobUrl = url;
|
||||
this.work = new Worker(url);
|
||||
this.work.onmessage = (e) => this.handleWorkerMessage(e);
|
||||
}
|
||||
|
||||
// 处理 Web Worker 消息
|
||||
private handleWorkerMessage(e: MessageEvent): void {
|
||||
// if (this.lastActivityTime - this.lastResponseHeartTime > 30000) {
|
||||
// // 说明已经三轮心跳没收到回复了,关闭检测,提示用户。
|
||||
// // ElMessage.error('业务主体模块发生未知异常,请尝试重新启动!')
|
||||
// this.clearHeartbeat()
|
||||
// return
|
||||
// }
|
||||
// console.log(123);
|
||||
|
||||
this.sendHeartbeat();
|
||||
}
|
||||
|
||||
// 发送心跳消息
|
||||
private sendHeartbeat(): void {
|
||||
// console.log(new Date() + '进入心跳消息发送。。。。。。。。。。。。。')
|
||||
if (this.ws) {
|
||||
this.ws.send("alive");
|
||||
this.updateLastActivityTime();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新活动时间
|
||||
private updateLastActivityTime(): void {
|
||||
this.lastActivityTime = Date.now();
|
||||
}
|
||||
|
||||
// 清除心跳检测
|
||||
private clearHeartbeat(): void {
|
||||
if (this.work) {
|
||||
this.work.terminate();
|
||||
this.work = null;
|
||||
}
|
||||
if (this.workerBlobUrl) {
|
||||
window.URL.revokeObjectURL(this.workerBlobUrl);
|
||||
this.workerBlobUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 回调函数的注册
|
||||
public registerCallBack(
|
||||
socketType: string,
|
||||
callBack: (message: MessageType) => void
|
||||
): void {
|
||||
this.callBackMapping[socketType] = callBack;
|
||||
}
|
||||
|
||||
// 取消某一个回调函数
|
||||
public unRegisterCallBack(socketType: string): void {
|
||||
this.callBackMapping[socketType] = null;
|
||||
}
|
||||
|
||||
// 发送数据的方法
|
||||
public send(data: any): void {
|
||||
if (this.connected) {
|
||||
this.sendRetryCount = 0;
|
||||
try {
|
||||
if (this.ws) {
|
||||
this.ws.send(JSON.stringify(data));
|
||||
}
|
||||
} catch (e) {
|
||||
if (this.ws) {
|
||||
this.ws.send(data);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.sendRetryCount++;
|
||||
setTimeout(() => this.send(data), this.sendRetryCount * 500);
|
||||
}
|
||||
}
|
||||
|
||||
// 断开方法
|
||||
public closeWs(): void {
|
||||
if (this.connected && this.ws) {
|
||||
this.ws.close();
|
||||
}
|
||||
console.log("执行WS关闭命令..");
|
||||
}
|
||||
}
|
||||
498
src/views/pqs/cockpit/realTimeData/iframeDia.vue
Normal file
498
src/views/pqs/cockpit/realTimeData/iframeDia.vue
Normal 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>
|
||||
Reference in New Issue
Block a user