Files
svgeditor2.0/src/components/mt-preview/index.vue

1157 lines
38 KiB
Vue
Raw Normal View History

2025-09-25 11:34:55 +08:00
<template>
<div
style="overflow: hidden; display: flex; flex-direction: column; height: 100vh"
v-loading="useData.loading"
element-loading-background="#343849c7"
:style="{ backgroundColor: useData.display ? '' : canvas_cfg.color }"
>
<el-button type="primary" class="backBtn" @click="onBack" v-if="!useData.display">返回</el-button>
<!-- 缩放控制按钮 (默认注释需要时可开启) -->
<!-- <div class="zoom-controls">
<el-button icon="ZoomIn" size="small" @click="zoomIn"></el-button>
<el-button icon="ZoomOut" size="small" @click="zoomOut"></el-button>
<el-button icon="RefreshLeft" size="small" @click="resetTransform"></el-button>
</div>
-->
<!-- <el-scrollbar ref="elScrollbarRef" class="w-1/1 h-1/1" @scroll="onScroll" > -->
<div
ref="canvasAreaRef"
:class="`canvasArea ${isDragging ? 'cursor-grabbing' : mtPreviewProps.canDrag ? 'cursor-grab' : ''} `"
style=""
@mousedown="onMouseDown"
@mousemove="onMouseMove"
@mouseup="onMouseUp"
@mouseleave="onMouseUp"
@wheel="onMouseWheel"
:style="canvasStyle"
>
<render-core
v-model:done-json="done_json"
:canvas-cfg="canvas_cfg"
:grid-cfg="grid_cfg"
:show-ghost-dom="false"
:canvas-dom="canvasAreaRef"
:global-lock="false"
:preivew-mode="true"
:show-popover="mtPreviewProps.showPopover"
@setDoneJson="setDoneJson"
@element-click="handleElementClick"
></render-core>
</div>
<drag-canvas
ref="dragCanvasRef"
:scale-ratio="canvas_cfg.scale"
@drag-canvas-mouse-down="dragCanvasMouseDown"
@drag-canvas-mouse-move="dragCanvasMouseMove"
@drag-canvas-mouse-up="dragCanvasMouseUp"
></drag-canvas>
<!-- </el-scrollbar> -->
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch, computed, onUnmounted, nextTick } from 'vue'
import RenderCore from '@/components/mt-edit/components/render-core/index.vue'
import type { IExportJson, IExportDoneJson } from '../mt-edit/components/types'
import { useExportJsonToDoneJson } from '../mt-edit/composables'
import type { IDoneJson } from '../mt-edit/store/types'
import { getItemAttr, previewCompareVal, setItemAttr } from '../mt-edit/utils'
import { ElScrollbar, ElMessage, ElMessageBox, ElButton } from 'element-plus'
import DragCanvas from '@/components/mt-edit/components/drag-canvas/index.vue'
import { useDataStore } from '@/stores/menuList'
import { globalStore } from '../mt-edit/store/global'
import type { IDoneJsonEventList } from '../mt-edit/store/types'
import MQTT from '@/utils/mqtt'
const tableData = [
{
date: '2023-05-03 13:33:44:853',
name: '1#主变分支I',
address: '发生暂降,特征幅值:39.87%,持续时间:10.019s'
},
{
date: '2023-05-02 13:31:50:155',
name: '1#主变分支I',
address: '发生暂降,特征幅值:39.95%,持续时间:5.02s'
}
// {
// date: '2016-05-04',
// name: '110kV杨镇变',
// address: 'No. 189, Grove St, Los Angeles'
// },
// {
// date: '2016-05-01',
// name: '110kV加工区变',
// address: 'No. 189, Grove St, Los Angeles'
// },
// {
// date: '2016-05-01',
// name: '110kV加工区变',
// address: 'No. 189, Grove St, Los Angeles'
// }
]
const showDetail = ref(false)
const showDetailClick = () => {
showDetail.value = !showDetail.value
}
// 节流函数实现 (替代lodash减少依赖)
const throttle = (func: (...args: any[]) => void, wait: number) => {
let lastTime = 0
return (...args: any[]) => {
const now = Date.now()
if (now - lastTime >= wait) {
func.apply(this, args)
lastTime = now
}
}
}
type MtPreviewProps = {
exportJson?: IExportJson
canZoom?: boolean
canDrag?: boolean
showPopover?: boolean
}
const mtPreviewProps = withDefaults(defineProps<MtPreviewProps>(), {
canDrag: true,
canZoom: true,
showPopover: true
})
const emits = defineEmits(['onEventCallBack'])
const useData = useDataStore()
const canvasAreaRef = ref<HTMLDivElement | null>(null)
const savedExportJson = ref<IExportJson>()
// 画布配置 - 扩展支持平移
const canvas_cfg = ref({
// width: 1920,
// height: 1080,
width: globalStore.canvasCfg.width,
height: globalStore.canvasCfg.height,
scale: 0.73,
color: '',
img: '',
guide: true,
adsorp: true,
adsorp_diff: 3,
transform_origin: {
x: 0,
y: 0
},
drag_offset: {
x: 0,
y: 0
},
// 平移属性
pan: {
x: 0,
y: 0
}
})
const grid_cfg = ref({
enabled: true,
align: true,
size: 10
})
const done_json = ref<IDoneJson[]>([])
const elScrollbarRef = ref<InstanceType<typeof ElScrollbar>>()
const dragCanvasRef = ref<InstanceType<typeof DragCanvas>>()
const scroll_info = reactive({
begin_left: 0,
begin_top: 0,
left: 0,
top: 0
})
// 拖拽状态变量
const isDragging = ref(false)
const startPos = ref({ x: 0, y: 0 })
// 非响应式临时变量,减少响应式更新频率
const tempPan = { x: 0, y: 0 }
let animationFrameId: number | null = null
// 计算画布样式
const canvasStyle = computed(() => ({
transform: `translate(${canvas_cfg.value.pan.x}px, ${canvas_cfg.value.pan.y}px) scale(${canvas_cfg.value.scale})`,
transformOrigin: '0 0',
// width: `100vw`,
// height: `100vh`,
width: canvas_cfg.value.width + 'px',
height: canvas_cfg.value.height + 'px',
backgroundColor: useData.display ? '' : canvas_cfg.value.color,
backgroundImage: canvas_cfg.value.img ? `url(${canvas_cfg.value.img})` : 'none',
backgroundSize: '100% 100%',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
}))
const onScroll = ({ scrollLeft, scrollTop }: { scrollLeft: number; scrollTop: number }) => {
scroll_info.left = scrollLeft
scroll_info.top = scrollTop
}
// 鼠标按下事件 - 开始拖拽
const onMouseDown = (e: MouseEvent) => {
if (mtPreviewProps.canDrag && e.button === 0) {
// 仅响应左键
e.preventDefault()
e.stopPropagation()
isDragging.value = true
startPos.value = {
x: e.clientX - canvas_cfg.value.pan.x,
y: e.clientY - canvas_cfg.value.pan.y
}
// 初始化临时位置
tempPan.x = canvas_cfg.value.pan.x
tempPan.y = canvas_cfg.value.pan.y
// 隐藏默认拖拽行为
if (canvasAreaRef.value) {
canvasAreaRef.value.style.userSelect = 'none'
}
// 启动动画帧同步
if (!animationFrameId) {
const syncPosition = () => {
if (isDragging.value) {
canvas_cfg.value.pan.x = tempPan.x
canvas_cfg.value.pan.y = tempPan.y
animationFrameId = requestAnimationFrame(syncPosition)
} else {
animationFrameId = null
}
}
animationFrameId = requestAnimationFrame(syncPosition)
}
}
}
// 节流处理鼠标移动事件 (16ms约等于60fps)
const throttledMouseMove = throttle((e: MouseEvent) => {
if (isDragging.value && mtPreviewProps.canDrag) {
e.preventDefault()
e.stopPropagation()
// 只更新临时变量,通过动画帧同步到响应式对象
tempPan.x = e.clientX - startPos.value.x
tempPan.y = e.clientY - startPos.value.y
}
}, 16)
// 鼠标移动事件 - 处理拖拽
const onMouseMove = throttledMouseMove
// 鼠标释放事件 - 结束拖拽
const onMouseUp = (e: MouseEvent) => {
if (isDragging.value) {
isDragging.value = false
// 恢复选择功能
if (canvasAreaRef.value) {
canvasAreaRef.value.style.userSelect = ''
}
// 取消动画帧
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
}
}
// 缩放控制函数
const zoomIn = () => {
if (mtPreviewProps.canZoom) {
canvas_cfg.value.scale = Math.min(canvas_cfg.value.scale + 0.1, 3) // 最大放大到3倍
}
}
const zoomOut = () => {
if (mtPreviewProps.canZoom) {
canvas_cfg.value.scale = Math.max(canvas_cfg.value.scale - 0.1, 0.3) // 最小缩小到0.3倍
}
}
// 重置变换
const resetTransform = () => {
// 定义重置变换的函数
canvas_cfg.value.scale = useData.display
? 0.73
: document.documentElement.clientHeight / globalStore.canvasCfg.height
canvas_cfg.value.pan = { x: 0, y: 0 }
tempPan.x = 0
tempPan.y = 0
}
// 鼠标滚轮事件 - 缩放
const onMouseWheel = (e: WheelEvent) => {
if (mtPreviewProps.canZoom) {
e.preventDefault()
e.stopPropagation()
// 计算缩放因子
const delta = e.deltaY < 0 ? 0.1 : -0.1
const newScale = Math.max(0.3, Math.min(3, canvas_cfg.value.scale + delta))
// 如果缩放中心是鼠标位置,计算新的平移值以保持视觉中心
if (canvasAreaRef.value) {
const rect = canvasAreaRef.value.getBoundingClientRect()
const mouseX = e.clientX - rect.left
const mouseY = e.clientY - rect.top
// 计算缩放前后的鼠标位置差异,调整平移量
const scaleRatio = newScale / canvas_cfg.value.scale
canvas_cfg.value.pan.x = mouseX - (mouseX - canvas_cfg.value.pan.x) * scaleRatio
canvas_cfg.value.pan.y = mouseY - (mouseY - canvas_cfg.value.pan.y) * scaleRatio
// 同步到临时变量
tempPan.x = canvas_cfg.value.pan.x
tempPan.y = canvas_cfg.value.pan.y
// 应用新缩放值
canvas_cfg.value.scale = newScale
}
}
}
const dragCanvasMouseDown = () => {
scroll_info.begin_left = scroll_info.left
scroll_info.begin_top = scroll_info.top
}
const dragCanvasMouseMove = (move_x: number, move_y: number) => {
let new_left = scroll_info.begin_left - move_x
let new_top = scroll_info.begin_top - move_y
elScrollbarRef.value?.setScrollLeft(new_left)
elScrollbarRef.value?.setScrollTop(new_top)
}
/**
* 画布拖动结束事件
*/
const dragCanvasMouseUp = () => {}
const setItemAttrByID = (id: string, key: string, val: any) => {
return setItemAttr(id, key, val, done_json.value)
}
const setItemAttrs = (info: { id: string; key: string; val: any }[]) => {
info.forEach(f => {
setItemAttr(f.id, f.key, f.val, done_json.value)
})
}
const getItemAttrByID = (id: string, key: string, val: any) => {
return getItemAttr(id, key, done_json.value)
}
const setItemAttrByIDAsync = (id: string, key: string, val: any) => {
// 通过改变属性的事件去设置值时 需要转换成宏任务 不然多个事件判断会有问题
setTimeout(() => {
setItemAttrByID(id, key, val)
}, 0)
}
;(window as any).$mtElMessage = ElMessage
;(window as any).$mtElMessageBox = ElMessageBox
;(window as any).$setItemAttrByID = (id: string, key: string, val: any) => setItemAttrByIDAsync(id, key, val)
;(window as any).$getItemAttrByID = getItemAttrByID
;(window as any).$previewCompareVal = previewCompareVal
;(window as any).$mtEventCallBack = (type: string, item_id: string, ...args: any[]) =>
emits('onEventCallBack', type, item_id, ...args)
onMounted(async () => {
// 启动消息监听 iframe传过来的参数
receiveMessage()
if (mtPreviewProps.exportJson) {
await setImportJson(mtPreviewProps.exportJson)
}
await sendTableData()
if (useData.graphicDisplay == 'zl') {
// // 治理项目连接mqtt
await setMqtt()
}
// window.parent.postMessage({ action: useData, data: tableData }, '*') // 发送数据给父页面
})
// 无锡大屏通过iframe传过来的数据
// let keyList = [
// '5f99b9ba4e563439ec8490a0c598da8d',
// '401a696fe9918bb4cce7d9393d0d5df7',
// '74203b186428c1684aa450ad05257b06'
// ]
// 无锡大屏通过iframe传过来的数据 lineId
let keyList = ref<any>([])
// 闪烁点 lineId
let list = ref<any>([])
// 最大点闪烁点lineId
let listMax = ref<any>([])
// 初始颜色
let sendColor = ref<any>('')
// 最大监测点颜色
let sendMaxColor = ref<any>('')
// 背景色
let backgroundPointId = ref<any>([])
let backgroundPointColor = ref<any>('')
// let list = [
// 'c53cccb8c65201c192d8c57fbdb4d993-RdNsoqHYOZ',
// 'c53cccb8c65201c192d8c57fbdb4d993-O4jAyCBz1A',
// // 'c53cccb8c65201c192d8c57fbdb4d993-XBd70oZ3kH',
// 'c53cccb8c65201c192d8c57fbdb4d993-LH9I0qD86s'
// ]
const sendTableData = () => {
try {
// 确保只传输可序列化的数据
const cleanData = tableData.map(item => ({
name: item.name,
date: item.date,
address: item.address
}))
// 只发送 useData 中需要的可序列化数据
const serializableUseData = {
display: useData.display,
loading: useData.loading
// 添加其他需要的简单属性
}
window.parent.postMessage(
{
action: 'securityDetailData',
data: cleanData
// data1: serializableUseData
},
'*'
)
} catch (error) {
console.error('数据传输失败:', error)
}
}
let message = [
{ id: 'c53cccb8c65201c192d8c57fbdb4d993-RdNsoqHYOZ', text: '发生时刻:2023-07-05 12:00:00' },
{ id: 'c53cccb8c65201c192d8c57fbdb4d993-O4jAyCBz1A', text: '传输中1111......' }
// { id: 'c53cccb8c65201c192d8c57fbdb4d993-XBd70oZ3kH', text: '发生时刻:2023-07-06 14:20:00' }
]
// 传输设备相关的连接线ID列表
let list_sys = ref<any>([])
let transmissionDeviceIds: string[] = []
let eventListAll = ref<any>([])
let flagValue = ref('')
const receiveMessage = () => {
// 在 iframe 内的页面中
window.addEventListener('message', function (event) {
// 验证消息来源(在生产环境中应该验证 origin
// if (event.origin !== 'trusted-origin') return;
const { type, payload } = event.data
if (type === 'ANALYSIS_KEYS') {
// 在处理新数据前,先清理现有的动态文字
if (savedExportJson.value) {
savedExportJson.value.json =
savedExportJson.value.json?.filter(item => !item.id?.startsWith('auto-text-')) || []
done_json.value = done_json.value.filter(item => !item.id?.startsWith('auto-text-'))
}
// console.log('payload1111', payload)
// 处理接收到的 keyList 数据
keyList.value = payload.eventList
list.value = payload.eventList
sendColor.value = payload.color
// 点击行的全部数据
if (payload.eventListAll) {
eventListAll.value = payload.eventListAll
// 在数据接收后重新更新文本
setTimeout(() => {
if (savedExportJson.value) {
addTextNextToTransport()
setImportJson(savedExportJson.value)
}
}, 100)
} else {
eventListAll.value = []
}
if (payload.maxColor) {
sendMaxColor.value = payload.maxColor
} else {
sendMaxColor.value = ''
}
if (payload.maxResponsibilityMonitorId) {
listMax.value = payload.maxResponsibilityMonitorId
} else {
listMax.value = []
}
if (payload.backgroundPointId) {
backgroundPointId.value = payload.backgroundPointId
} else {
backgroundPointId.value = []
}
if (payload.backgroundPointColor) {
backgroundPointColor.value = payload.backgroundPointColor
} else {
backgroundPointColor.value = ''
}
if (payload.flagValue) {
flagValue.value = payload.flagValue
} else {
flagValue.value = ''
}
}
init()
})
}
//根据 lineId 查找传输设备 ID 的函数
const findTransmissionDeviceIdsByKeyList = (array: any) => {
if (!savedExportJson.value?.json) return []
const deviceIds = savedExportJson.value.json.filter(item => array.includes(item.lineId ?? '')).map(item => item.id)
// console.log('传输设备 ID 列表:', deviceIds)
return deviceIds
}
// 为每个图元动态添加点击事件
const addClickEventsToElements = () => {
if (savedExportJson.value && savedExportJson.value.json) {
savedExportJson.value.json.forEach(item => {
// 检查是否已经有点击事件
const hasClickEvent = item.events && item.events.some(event => event.type === 'click')
if (!hasClickEvent) {
// 创建点击事件对象
const clickEvent: IDoneJsonEventList = {
id: generateRandomId(), // 生成随机ID
type: 'click', // 明确指定为字面量类型
action: 'customCode', // 自定义操作
jump_to: '',
change_attr: [],
custom_code: '', // 可以在这里添加自定义代码
trigger_rule: {
value: null
}
}
// 如果图元还没有events数组则创建一个
if (!item.events) {
item.events = []
}
// 添加点击事件
item.events.push(clickEvent)
}
})
}
}
// 生成随机ID的辅助函数
const generateRandomId = () => {
return Math.random().toString(36).substr(2, 10)
}
const setImportJson = (exportJson: IExportJson) => {
// 保存exportJson供后续使用
savedExportJson.value = exportJson
// 定义要执行的操作函数
const executeOperations = () => {
const { canvasCfg, gridCfg, importDoneJson } = useExportJsonToDoneJson(savedExportJson.value)
// 保留现有的平移和缩放设置
const currentPan = canvas_cfg.value.pan
const currentScale = canvas_cfg.value.scale
canvas_cfg.value = {
...canvasCfg,
pan: currentPan,
scale: currentScale
}
grid_cfg.value = gridCfg
done_json.value = importDoneJson
// 为图元添加点击事件
addClickEventsToElements()
// 首页初始化的时候
setTimeout(() => {
done_json.value.forEach(item => {
//报警设备闪烁
if (findTransmissionDeviceIdsByKeyList(list.value).includes(item.id)) {
// item.props.fill.val = '#fcfc57'
item.props.fill.val = sendColor.value
item.common_animations.val = 'flash'
if (findTransmissionDeviceIdsByKeyList(listMax.value).includes(item.id)) {
item.props.fill.val = sendMaxColor.value
item.common_animations.val = 'flash'
}
// 查找与该设备连接的线
const connectedLines = searchDevicesConnect([item.id!])
// console.log('该设备连接的线:', connectedLines)
// 通过连接的线找到开关,再找到开关连接的所有线
const allRelatedLines: string[] = [...connectedLines]
for (const lineId of connectedLines) {
// 通过线找到连接的开关
const switchId = findSwitchByLineEndpoint(lineId)
if (switchId) {
// console.log('找到连接的开关:', switchId)
// 找到开关连接的所有线
const switchLines = findLinesBySwitchId(switchId)
// console.log('开关连接的线:', switchLines)
// 合并所有相关的线
switchLines.forEach(line => {
if (!allRelatedLines.includes(line)) {
allRelatedLines.push(line)
}
})
}
}
//console.log('所有相关的线:', allRelatedLines)
// 高亮显示所有相关的线
//只有当 flagValue 不等于 '2' 时才高亮显示所有相关的线
if (flagValue.value !== '2') {
highlightLines(allRelatedLines)
}
} else {
item.common_animations.val = ''
}
if (findTransmissionDeviceIdsByKeyList(backgroundPointId.value).includes(item.id)) {
item.props.fill.val = backgroundPointColor.value
item.common_animations.val = 'flash'
}
// 报警设备连线电流
// if (list_sys.value.includes(item.id)) {
// item.props.ani_type.val = 'electricity'
// item.props.ani_color.val = '#d0e20a'
// }
// if(item.id=='sys-line-vertical-v9oPMlvQL1'){
// item.props.ani_color.val = '#8c0ae2'
// }
})
}, 1000)
}
if (!useData.loading) {
if (exportJson == null) {
setDoneJson(useData.dataTree[0].kId)
publish(useData.dataTree[0].id)
} else {
executeOperations()
}
} else {
// 如果不是true添加监听
const stopWatch = watch(
() => useData.loading,
newVal => {
if (newVal === false) {
// 当loading变为true时执行操作
if (exportJson == null) {
setDoneJson(useData.dataTree[0].kId)
publish(useData.dataTree[0].id)
} else {
executeOperations()
}
// 执行后停止监听
stopWatch()
}
}
)
}
return true
}
// 根据线的起点或终点查找连接的开关
const findSwitchByLineEndpoint = (lineId: string): string | null => {
if (!savedExportJson.value?.json) return null
const line = savedExportJson.value.json.find(item => item.id === lineId)
if (!line || !line.props?.bind_anchors) return null
const bindAnchors = line.props.bind_anchors as { start?: { id: string }; end?: { id: string } } | undefined
if (!bindAnchors) return null
// 查找起点连接的开关
const startId = bindAnchors.start?.id
if (startId) {
const startElement = savedExportJson.value.json.find(item => item.id === startId)
if (startElement && startElement.title?.includes('开关')) {
return startElement.id!
}
}
// 如果起点不是开关,查找终点连接的开关
const endId = bindAnchors.end?.id
if (endId) {
const endElement = savedExportJson.value.json.find(item => item.id === endId)
if (endElement && endElement.title?.includes('开关')) {
return endElement.id!
}
}
return null
}
// 根据开关ID查找所有连接的线
const findLinesBySwitchId = (switchId: string): string[] => {
if (!savedExportJson.value?.json) return []
const lineElements = savedExportJson.value.json.filter(item => item.type === 'sys-line' && item.props?.bind_anchors)
const connectedLines: string[] = []
for (const line of lineElements) {
const bindAnchors = line.props.bind_anchors as { start?: { id: string }; end?: { id: string } } | undefined
if (!bindAnchors) continue
const startId = bindAnchors.start?.id
const endId = bindAnchors.end?.id
if (startId === switchId || endId === switchId) {
connectedLines.push(line.id!)
}
}
// console.log('连接的线:', connectedLines)
return connectedLines
}
// 高亮显示连线的函数
const highlightLines = (lineIds: string[]) => {
lineIds.forEach(id => {
const line = done_json.value.find(item => item.id === id)
if (line) {
line.props.ani_type.val = 'electricity'
line.props.ani_color.val = '#d0e20a'
// 置顶显示 - 将连线元素移到数组末尾(因为渲染顺序是按数组顺序)
const index = done_json.value.findIndex(item => item.id === id)
if (index !== -1) {
const [item] = done_json.value.splice(index, 1)
done_json.value.push(item)
}
}
})
}
// 添加一个新的 ref 来存储当前点击的设备ID
const currentClickedElementId = ref<string | null>(null)
// 图纸的kId
let storedSelectedId = ''
storedSelectedId = localStorage.getItem('selectedId') || ''
// 当前点击的元素lineId 通过mt-edit/render-core/index.vue传过来的click事件
const handleElementClick = (elementId: string) => {
// 保存当前点击的设备ID
currentClickedElementId.value = elementId
const item = done_json.value.find(item => item.lineId === elementId)
if (item && item.events && item.events.some(event => event.type === 'click')) {
// 发送设备ID到父级iframe
window.parent.postMessage(
{
action: 'coreClick',
coreId: elementId.toString(), // 确保是字符串
selectedId: storedSelectedId // 新增的字段
// elementData: item // 可以发送整个元素数据
},
'*'
)
}
}
const searchDevicesConnect = (transmissionDeviceIds: string[]) => {
// 确保 savedExportJson.value 存在
if (!savedExportJson.value?.json) {
console.warn('savedExportJson.value 或 json 未定义')
return []
}
// 查找所有连线元素
const lineElements = savedExportJson.value.json.filter(item => item.type === 'sys-line' && item.props?.bind_anchors)
// 查找所有开关元素
const switchElements = savedExportJson.value.json.filter(item => item.title?.includes('开关'))
// 存储连接线的ID
const connectedLineIds: string[] = []
// 存储找到的开关ID
const switchIds: string[] = []
// 首先找出所有传输设备连接的开关
for (const deviceId of transmissionDeviceIds) {
for (const line of lineElements) {
const bindAnchors = line.props.bind_anchors as { start?: { id: string }; end?: { id: string } } | undefined
if (!bindAnchors) continue
const startId = bindAnchors.start?.id
const endId = bindAnchors.end?.id
// 检查连线是否连接传输设备和开关
if (startId === deviceId && switchElements.some(s => s.id === endId)) {
if (endId && !switchIds.includes(endId)) {
switchIds.push(endId)
}
if (!connectedLineIds.includes(line.id!)) {
connectedLineIds.push(line.id!)
}
} else if (endId === deviceId && switchElements.some(s => s.id === startId)) {
if (startId && !switchIds.includes(startId)) {
switchIds.push(startId)
}
if (!connectedLineIds.includes(line.id!)) {
connectedLineIds.push(line.id!)
}
}
}
}
// 然后找出开关之间的连线
for (const line of lineElements) {
const bindAnchors = line.props.bind_anchors as { start?: { id: string }; end?: { id: string } } | undefined
if (!bindAnchors) continue
const startId = bindAnchors.start?.id
const endId = bindAnchors.end?.id
// 检查连线是否连接两个开关
if (startId && endId && switchIds.includes(startId) && switchIds.includes(endId)) {
if (!connectedLineIds.includes(line.id!)) {
connectedLineIds.push(line.id!)
}
}
}
// console.log('连接的连线ID列表:', connectedLineIds)
return connectedLineIds
}
// 添加针对传输设备旁边添加文字的方法
const addTextNextToTransport = () => {
// 确保 savedExportJson.value 存在后再进行操作
if (!savedExportJson.value) {
console.warn('savedExportJson.value is undefined')
return []
}
let transportDevices = []
// 查找传输设备图元并添加文字图元
transportDevices =
savedExportJson.value.json?.filter(item =>
findTransmissionDeviceIdsByKeyList(keyList.value).includes(item.id)
) || []
// 为每个传输设备添加旁边的文字图元
const textElementsToAdd: IExportDoneJson[] = []
const idsToRemove: string[] = []
transportDevices.forEach((device, index) => {
// 构造预期的旧文本图元 ID 模式 (基于设备 ID)
const expectedIdPrefix = `auto-text-${device.id}-`
// 查找所有与当前设备关联的现有文本图元
const existingTextElements =
savedExportJson.value?.json?.filter(item => item.id?.startsWith(expectedIdPrefix)) || []
// 将这些旧图元的 ID 添加到待删除列表
idsToRemove.push(...existingTextElements.map(item => item.id!))
// 获取对应的消息文本
const deviceMessage =
eventListAll.value[0].find((m: any) => m.monitorId === device.lineId)?.responsibilityData || 0
// 创建新的文本图元
const textElement: IExportDoneJson = {
id: `auto-text-${device.id}-${index}`, // 使用时间戳确保唯一性
title: '动态文字',
type: 'vue',
tag: 'text-vue',
props: {
// text: deviceMessage.toFixed(1) + '%',
text: '占比:' + Math.floor(deviceMessage * 10) / 10 + '%',
fontFamily: '黑体',
fontSize: 14,
fill: 'red',
vertical: false
},
common_animations: {
val: '',
delay: 'delay-0s',
speed: 'slow',
repeat: 'infinite'
},
binfo: {
left: (device.binfo?.left || 0) + (device.binfo?.width / 2 || 0) + 10,
//top: (device.binfo?.top || 0) + (device.binfo?.height || 0) / 2 - 10 + (index % 2 === 0 ? 20 : -20),
top: (device.binfo?.top || 0) + (device.binfo?.height || 0) - 10,
width: 200,
height: 50,
angle: 0
},
resize: true,
rotate: true,
lock: false,
active: false,
hide: false,
UIDName: '',
UIDNames: [],
events: []
}
textElementsToAdd.push(textElement)
})
// 从 savedExportJson.value.json 中移除旧的文本图元
if (savedExportJson.value.json) {
done_json.value = savedExportJson.value.json.filter(item => !idsToRemove.includes(item.id!))
}
// 合并原始图元和新增的文字图元
const updatedJson = [...done_json.value, ...textElementsToAdd]
// 更新 savedExportJson.value
if (savedExportJson.value) {
savedExportJson.value.json = updatedJson
}
return updatedJson
}
// 预览时候绑定指标等的点击事件
const setDoneJson = async (kId: string) => {
const filteredItems = useData.dataTree.filter(item => item.kId == kId)
if (filteredItems.length === 0) {
console.error(`No item found with kId: ${kId}`)
return
}
const item = filteredItems[0]
// 根据传过来的kId找到所在的id
const foundId = item.id
if (foundId) {
// 将当前选中的行数据存储到本地存储
localStorage.setItem('selectedId', foundId)
}
storedSelectedId = localStorage.getItem('selectedId') || ''
if (!item.path) {
console.error(`Item with kId: ${kId} does not have a path property`)
return
}
setImportJson(JSON.parse(item.path))
}
const init = async () => {
setTimeout(() => {
// 执行动态添加文本的操作
// const updatedDoneJson = addTextNextToTransport()
// 调用函数获取传输设备 ID
transmissionDeviceIds = findTransmissionDeviceIdsByKeyList(keyList.value)
//传入参数: transmissionDeviceIds - 一个包含传输设备ID的数组
// 功能: 查找与这些传输设备相关的连接线(包括直接连接和间接连接的线)
list_sys.value = searchDevicesConnect(transmissionDeviceIds)
// 将结果赋值给 savedExportJson.value
// if (savedExportJson.value) {
// // 创建一个新的对象,避免直接修改原始对象
// savedExportJson.value = {
// ...savedExportJson.value,
// json: updatedDoneJson
// }
// } else {
// // 如果 savedExportJson.value 不存在,创建一个新的对象
// savedExportJson.value = {
// canvasCfg: canvas_cfg.value,
// gridCfg: grid_cfg.value,
// json: updatedDoneJson
// }
// }
// 重新设置导入的JSON以触发界面更新
setImportJson(savedExportJson.value)
}, 100)
}
const timer = ref()
// 连接mqtt
const mqttClient = ref()
const setMqtt = async () => {
mqttClient.value = new MQTT('/zl/rtData/#')
// 设置消息接收回调
try {
await mqttClient.value.init()
// 订阅主题
await mqttClient.value.subscribe('/zl/rtData/#')
// 设置消息接收回调
mqttClient.value.onMessage((subscribe: string, message: any) => {
const msg: any = uint8ArrayToObject(message)
console.log('🚀 ~ 接受消息:', msg)
setTimeout(() => {
done_json.value.forEach(item => {
msg.forEach((msgValue: any) => {
if (item.id == msgValue.id) {
item.props.text.val = item.props.text.val.replace(/#{3}/g, msgValue.value) //'B相负载电流-CP95:31'
}
})
})
}, 100)
})
} catch (error) {
console.error('MQTT 初始化失败:', error)
}
}
const publish = async (id: string) => {
if (useData.graphicDisplay != 'zl') return //质量项目进入
if (mqttClient.value) {
await mqttClient.value.subscribe('zl/askRtData/' + id)
// 发送消息
await mqttClient.value.publish('/zl/askRtData/' + id, '{}')
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
timer.value = setInterval(
() => {
mqttClient.value.publish('/zl/askRtData/' + id, '{}')
},
3 * 60 * 1000
)
}
}
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
})
// 方法1: 使用 mqtt消息转换
function uint8ArrayToObject(uint8Array: Uint8Array) {
try {
// 将 Uint8Array 解码为字符串
const decoder = new TextDecoder('utf-8')
const jsonString = decoder.decode(uint8Array)
// 将 JSON 字符串解析为对象
return JSON.parse(jsonString)
} catch (error) {
console.error('转换失败:', error)
return null
}
}
const onBack = () => {
window.close()
}
watch(
() => useData.display,
newVal => {
if (newVal) {
canvas_cfg.value.scale = 0.73
canvas_cfg.value.pan = {
x: 0,
y: 50
}
} else {
canvas_cfg.value.scale = document.documentElement.clientHeight / globalStore.canvasCfg.height
}
},
{ immediate: true }
)
defineExpose({
setItemAttrByID,
setImportJson,
setItemAttrs,
zoomIn,
zoomOut,
resetTransform
})
</script>
<style scoped>
.canvasArea {
position: relative;
/* 移除过渡效果,避免拖拽延迟 */
will-change: transform;
/* 提示浏览器优化transform属性 */
}
.backBtn {
position: absolute;
top: 20px;
right: 10px;
z-index: 2;
}
.zoom-controls {
position: fixed;
top: 20px;
left: 20px;
z-index: 1;
display: flex;
gap: 5px;
}
.cursor-grab {
cursor: grab;
}
.cursor-grabbing {
cursor: grabbing;
}
.el-table {
/* --el-table-border-color: #0a73ff;
--el-table-row-hover-bg-color: #3d4862;
--el-table-header-bg-color: #2a3b62;
--el-table-bg-color: #343849c7; */
--el-table-border-color: #0a73ff;
--el-table-row-hover-bg-color: #0a73ff20;
--el-table-header-bg-color: #0a73ff40;
--el-table-bg-color: #ffffff00;
text-align: center;
color: #ffffff;
}
:deep(.el-table tr) {
/* background-color: #242936; */
background-color: #00000090 !important;
}
:deep(.el-table .cell) {
color: #ffffff;
text-align: center;
}
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell) {
/* background-color: #303b54; */
background-color: #5aa1ff29;
}
</style>