Files
svgeditor2.0/src/components/mt-preview/index.vue
2025-10-14 20:38:59 +08:00

1174 lines
38 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
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 targetKeywords = ['开关', '器', '阀门', '控制']
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()
// 首页初始化的时候
nextTick(() => {
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'
// }
})
})
}
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 && targetKeywords.some(keyword => startElement.title?.includes(keyword))) {
return startElement.id!
}
}
// 如果起点不是开关,查找终点连接的开关
const endId = bindAnchors.end?.id
if (endId) {
const endElement = savedExportJson.value.json.find(item => item.id === endId)
if (endElement && targetKeywords.some(keyword => endElement.title?.includes(keyword))) {
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 bindList = ref<string[]>([])
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)
bindList.value = [
...new Set(
lineElements
.map(item => {
return [item.props?.bind_anchors.start?.id, item.props?.bind_anchors.end?.id]
})
.flat()
)
]
// 查找所有开关元素
const switchElements = savedExportJson.value.json.filter(item =>
targetKeywords.some(keyword => item.title?.includes(keyword))
)
// const switchElements = savedExportJson.value.json.filter(item =>
// bindList.value.some(keyword => item.id?.includes(keyword) && item.lineId=='')
// )
// 存储连接线的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>