@@ -1,539 +0,0 @@
< 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" > { { 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" >
< 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 >
< / 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'
// 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 => {
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 : 230 px ;
height : 24 px ;
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 , 25 s ) linear infinite ;
line - height : 24 px ;
text - decoration : underline ;
text - decoration - color : # 4877 f6 ;
}
@ 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 : 240 px ;
/* height: 135px; */
background - color : # 2 b2d3a90 ;
position : absolute ;
color : 000 ;
/* font-weight: bold; */
/* 添加弹出动画 */
opacity : 0 ;
transform : scale ( 0.3 ) ;
transition : all 0.4 s ease - out ;
border - radius : 8 px ;
box - shadow : 0 4 px 8 px rgba ( 0 , 0 , 0 , 0.3 ) ;
}
/* 显示状态的样式 */
. corner : not ( [ style *= 'display: none' ] ) : not ( [ style *= 'display:none' ] ) {
opacity : 1 ;
transform : scale ( 1 ) ;
}
. top - left {
top : 10 px ;
left : 10 px ;
}
. top - right {
top : 10 px ;
right : 10 px ;
}
. bottom - left {
top : 170 px ;
left : 10 px ;
}
. bottom - right {
top : 170 px ;
right : 10 px ;
}
. content {
width : 100 % ;
height : 100 % ;
box - sizing : border - box ;
display : flex ;
flex - direction : column ;
}
. title {
font - size : 16 px ;
/* text-align: center; */
/* margin-bottom: 8px; */
padding : 8 px ;
/* color: #409eff; */
border - bottom : 1 px solid # fff ;
background - color : # fff ;
border - radius : 8 px 8 px 0 0 ;
}
. data - item {
display : flex ;
margin - bottom : 4 px ;
font - size : 12 px ;
}
. label {
/* font-weight: bold; */
width : 55 px ;
flex - shrink : 0 ;
}
. value {
flex : 1 ;
overflow : hidden ;
text - overflow : ellipsis ;
white - space : nowrap ;
}
/* 关闭按钮样式 */
. close - btn {
position : absolute ;
top : 10 px ;
right : 10 px ;
width : 14 px ;
color : white ;
font - size : 14 px ;
font - weight : bold ;
cursor : pointer ;
}
. indicator {
display : flex ;
}
< / style >