931 lines
30 KiB
Vue
931 lines
30 KiB
Vue
<template>
|
||
<div
|
||
:style="{ backgroundColor: useData.display ? '' : canvas_cfg.color }"
|
||
@mousedown="onMouseDown"
|
||
@mousemove="onMouseMove"
|
||
@mouseup="onMouseUp"
|
||
@mouseleave="onMouseUp"
|
||
@wheel="onMouseWheel"
|
||
style="height: 100vh; overflow: hidden"
|
||
>
|
||
<div
|
||
v-loading="useData.loading"
|
||
:style="canvasStyle"
|
||
style="overflow: hidden"
|
||
:class="`canvasArea ${isDragging ? 'cursor-grabbing' : mtPreviewProps.canDrag ? 'cursor-grab' : ''} `"
|
||
>
|
||
<!-- <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">
|
||
<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>
|
||
</div>
|
||
</div>
|
||
<!-- 弹框 -->
|
||
<iframeDia :steadyState="dataList" ref="iframeDiaRef" @lineListChange="indicator"></iframeDia>
|
||
</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 IframeDia from './iframeDia.vue'
|
||
import MQTT from '@/utils/mqtt'
|
||
const iframeDiaRef = ref<any>(null)
|
||
|
||
const dataList = ref([])
|
||
|
||
// 节流函数实现 (替代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: 1,
|
||
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',
|
||
marginLeft: (document.documentElement.clientWidth - canvas_cfg.value.width * canvas_cfg.value.scale) / 2 + 'px'
|
||
}))
|
||
|
||
// 鼠标按下事件 - 开始拖拽
|
||
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 ? 1 : 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)
|
||
}
|
||
|
||
// 连接mqtt
|
||
await setMqtt()
|
||
// await sendTableData()
|
||
})
|
||
|
||
// mqtt推过来的 lineId
|
||
let keyList = ref<any>([])
|
||
|
||
// 实时数据表格
|
||
const tableData = ref()
|
||
|
||
const sendTableData = () => {
|
||
try {
|
||
// 类型检查,确保 tableData.value 是数组
|
||
if (!Array.isArray(tableData.value)) {
|
||
console.warn('tableData is not an array, current value:', tableData.value)
|
||
return
|
||
}
|
||
|
||
// 确保只传输可序列化的数据
|
||
const cleanData = tableData.value.map(item => ({
|
||
name: item.name,
|
||
date: item.timeId,
|
||
address: item.eventDesc
|
||
}))
|
||
|
||
window.parent.postMessage(
|
||
{
|
||
action: 'securityDetailData',
|
||
data: cleanData
|
||
},
|
||
'*'
|
||
)
|
||
} catch (error) {
|
||
console.error('数据传输失败:', error)
|
||
}
|
||
}
|
||
|
||
let transmissionDeviceIds: string[] = []
|
||
|
||
let eventListAll = ref<any>([])
|
||
|
||
const receiveMessage = () => {
|
||
// 在 iframe 内的页面中
|
||
window.addEventListener('message', function (event) {
|
||
// 验证消息来源(在生产环境中应该验证 origin)
|
||
// if (event.origin !== 'trusted-origin') return;
|
||
|
||
const { type, payload } = event.data
|
||
|
||
if (type === 'RESET_EVENT') {
|
||
// 处理复位事件
|
||
handleResetEvent()
|
||
} else if (type === 'ANALYSIS_KEYS') {
|
||
// 处理 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-'))
|
||
}
|
||
|
||
init()
|
||
}
|
||
// 对于其他类型的消息,我们仍然调用 init()
|
||
else if (type) {
|
||
init()
|
||
}
|
||
})
|
||
}
|
||
|
||
// 复位事件处理函数
|
||
const handleResetEvent = () => {
|
||
// 清空或重置 表格数据
|
||
// if (tableData.value && Array.isArray(tableData.value)) {
|
||
// tableData.value = []
|
||
// } else {
|
||
// // 确保 tableData 是一个空数组
|
||
// tableData.value = []
|
||
// }
|
||
|
||
// 接线图数据
|
||
keyList.value = []
|
||
|
||
setTimeout(() => {
|
||
// 表格数据
|
||
sendTableData()
|
||
// 接线图数据
|
||
if (savedExportJson.value) {
|
||
setImportJson(savedExportJson.value)
|
||
}
|
||
}, 100)
|
||
|
||
console.log('执行复位操作完成')
|
||
}
|
||
|
||
//根据 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
|
||
console.log(11111111111)
|
||
// 为图元添加点击事件
|
||
addClickEventsToElements()
|
||
|
||
// 首页初始化的时候
|
||
setTimeout(() => {
|
||
console.log(22222222222222)
|
||
|
||
done_json.value.forEach(item => {
|
||
//报警设备闪烁
|
||
if (findTransmissionDeviceIdsByKeyList(keyList.value).includes(item.id)) {
|
||
item.props.fill.val = 'red'
|
||
item.common_animations.val = 'flash'
|
||
} else {
|
||
item.common_animations.val = ''
|
||
}
|
||
})
|
||
}, 1000)
|
||
}
|
||
|
||
if (!useData.loading) {
|
||
if (exportJson == null) {
|
||
setDoneJson(useData.dataTree[0].kId)
|
||
} else {
|
||
executeOperations()
|
||
publish(useData.dataTree[0])
|
||
singlePublish(useData.dataTree[0])
|
||
}
|
||
} else {
|
||
// 如果不是true,添加监听
|
||
const stopWatch = watch(
|
||
() => useData.loading,
|
||
newVal => {
|
||
if (newVal === false) {
|
||
// 当loading变为true时执行操作
|
||
if (exportJson == null) {
|
||
setDoneJson(useData.dataTree[0].kId)
|
||
} else {
|
||
executeOperations()
|
||
singlePublish(useData.dataTree[0])
|
||
publish(useData.dataTree[0])
|
||
}
|
||
// 执行后停止监听
|
||
stopWatch()
|
||
}
|
||
}
|
||
)
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// 添加一个新的 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, lineName: string) => {
|
||
iframeDiaRef.value.showNextCorner(elementId, lineName)
|
||
// 保存当前点击的设备ID
|
||
// indicator(['00B78D0171091', '00B78D0171092'])
|
||
// 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 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)
|
||
|
||
// 重新设置导入的JSON以触发界面更新
|
||
setImportJson(savedExportJson.value)
|
||
}, 100)
|
||
}
|
||
const timer = ref()
|
||
const list: any = ref([])
|
||
// 连接mqtt
|
||
const mqttClient = ref()
|
||
const setMqtt = async () => {
|
||
mqttClient.value = new MQTT('/zl/TemperData/#')
|
||
|
||
// 设置消息接收回调
|
||
try {
|
||
await mqttClient.value.init()
|
||
|
||
// 订阅主题
|
||
await mqttClient.value.subscribe('/zl/rtData/#')
|
||
await mqttClient.value.subscribe('/zl/TemperData/#') //实时数据
|
||
await mqttClient.value.subscribe('/zl/csConfigRtData/#') //指标
|
||
// 设置消息接收回调
|
||
mqttClient.value.onMessage((subscribe: string, message: any) => {
|
||
const msg: any = uint8ArrayToObject(message)
|
||
// console.log('🚀 ~ setMqtt ~ msg:', msg)
|
||
|
||
if (subscribe.split('/')[2] === 'rtData') {
|
||
// 指标数据
|
||
|
||
// await setImportJson(savedExportJson.value)
|
||
const list = [
|
||
...new Set(msg.filter((item: any) => item.devStatus === 1).map((item: any) => item.lineId))
|
||
]
|
||
setTimeout(() => {
|
||
if (done_json.value) {
|
||
done_json.value?.forEach(item => {
|
||
msg.forEach((msgValue: any) => {
|
||
if (item.id == msgValue.id) {
|
||
const unit = item?.unit && Array.isArray(item.unit) ? item.unit[0] : ''
|
||
item.props.text.val = item.props.text.val.replace(
|
||
/#{3}/g,
|
||
msgValue.value == 3.1415926 ? '暂无数据' : msgValue.value + unit
|
||
) //'B相负载电流-CP95:31'
|
||
}
|
||
})
|
||
list.forEach((listValue: any) => {
|
||
if (listValue == item.lineId && item.type == 'svg') {
|
||
item.props.fill.val = '#ff0000'
|
||
// item.common_animations.val = 'flash'
|
||
}
|
||
})
|
||
})
|
||
}
|
||
}, 500)
|
||
}
|
||
if (subscribe.split('/')[2] === 'csConfigRtData') {
|
||
// 指标数据
|
||
dataList.value = JSON.parse(msg.message)
|
||
//console.log('🚀 ~ setMqtt ~ dataList:', dataList.value)
|
||
// keyList.value = JSON.parse(list.path).canvasCfg.lineList
|
||
} else if (subscribe.split('/')[2] === 'TemperData') {
|
||
// 表格数据
|
||
tableData.value = []
|
||
tableData.value = JSON.parse(msg.message)
|
||
//console.log('🚀 ~ setMqtt ~ tableData:', tableData.value)
|
||
// 闪烁点
|
||
// if (Array.isArray(tableData.value) && tableData.value.length > 0) {
|
||
// // 提取所有的 id 并去重(使用 Set 方式,性能更好)
|
||
// const uniqueIds = [
|
||
// ...new Set(
|
||
// tableData.value
|
||
// .filter(item => item.id) // 确保 id 存在
|
||
// .map(item => item.id) // 提取 id
|
||
// )
|
||
// ]
|
||
|
||
// keyList.value = uniqueIds
|
||
// console.log('提取的唯一ID列表:', keyList.value)
|
||
// }
|
||
sendTableData()
|
||
}
|
||
})
|
||
} catch (error) {
|
||
console.error('MQTT 初始化失败:', error)
|
||
}
|
||
}
|
||
const singlePublish = async (id: string) => {
|
||
if (mqttClient.value) {
|
||
await mqttClient.value.subscribe('zl/askRtData/' + storedSelectedId)
|
||
// 发送消息
|
||
await mqttClient.value.publish('/zl/askRtData/' + storedSelectedId, '{}')
|
||
if (timer.value) {
|
||
clearInterval(timer.value)
|
||
timer.value = null
|
||
}
|
||
timer.value = setInterval(
|
||
() => {
|
||
mqttClient.value.publish('/zl/askRtData/' + storedSelectedId, '{}')
|
||
},
|
||
3 * 60 * 1000
|
||
)
|
||
}
|
||
}
|
||
const publish = async (list: any) => {
|
||
// console.log('🚀 ~ publish ~ list:', JSON.parse(list.path).canvasCfg.lineList)
|
||
if (mqttClient.value) {
|
||
// 发送消息
|
||
await mqttClient.value.publish(
|
||
'/zl/askCSConfigWarnData/' + storedSelectedId,
|
||
`[${JSON.parse(list.path).canvasCfg.lineList}]`
|
||
)
|
||
if (timer.value) {
|
||
clearInterval(timer.value)
|
||
timer.value = null
|
||
}
|
||
timer.value = setInterval(
|
||
() => {
|
||
mqttClient.value.publish(
|
||
'/zl/askCSConfigWarnData/' + storedSelectedId,
|
||
`[${JSON.parse(list.path).canvasCfg.lineList}]`
|
||
)
|
||
},
|
||
3 * 60 * 1000
|
||
)
|
||
}
|
||
}
|
||
// 绑定指标
|
||
const indicator = async (ids: string[]) => {
|
||
if (mqttClient.value) {
|
||
// await mqttClient.value.subscribe('zl/askCSConfigRtData/' + useData.dataTree[0].id)
|
||
// 发送消息
|
||
await mqttClient.value.publish('/zl/askCSConfigRtData/' + storedSelectedId, `[${ids}]`)
|
||
}
|
||
}
|
||
|
||
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 = 1
|
||
canvas_cfg.value.pan = {
|
||
x: 0,
|
||
y: 0
|
||
}
|
||
} 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;
|
||
}
|
||
/* .aaaa{
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0px;
|
||
z-index: 111123;
|
||
height: 100px;
|
||
width: 100px;
|
||
background-color: #ccc;
|
||
} */
|
||
</style>
|