Files
pqs-9100_client/frontend/src/views/home/components/deviceConnectionPopup.vue
2025-09-17 15:42:05 +08:00

510 lines
16 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>
<!-- <el-dialog title="设备通道配对" v-model="dialogVisible" v-bind="dialogBig">
<div
class="flow-container"
style="overflow: hidden; position: relative"
:style="{ height: dialogHeight + 'px' }"
>
<VueFlow
:nodes="nodes"
:edges="edges"
:connection-radius="30"
:nodes-draggable="false"
:dragging="false"
:zoom-on-scroll="false"
:pan-on-drag="false"
:disable-zoom-pan-on-connect="true"
:prevent-scrolling="true"
:fit-view="true"
:min-zoom="1"
:max-zoom="1"
:elements-selectable="false"
auto-connect
@connect="handleConnect"
@connect-start="handleConnectStart"
@connect-end="handleConnectEnd"
@pane-ready="onPaneReady"
v-on:pane-mouse-move="false"
></VueFlow>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleNext">下一步</el-button>
</div>
</template>
</el-dialog> -->
<!-- devIdList.value
pqStandardDevList.value
planIdKey.value -->
<!-- 手动检测-勾选检测项弹窗 -->
<SelectTestItemPopup ref="selectTestItemPopupRef" @openTestDialog="openTestDialog"></SelectTestItemPopup>
<CompareTestPopup
ref="testPopup"
v-if="CompareTestVisible"
:devIdList="devIdList"
:pqStandardDevList="pqStandardDevList"
:planIdKey="planIdKey"
></CompareTestPopup>
</template>
<script lang="ts" setup>
import { h, ref } from 'vue'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { dialogBig } from '@/utils/elementBind'
import { Platform, Flag } from '@element-plus/icons-vue'
import { Device } from '@/api/device/interface/device'
import { StandardDevice } from '@/api/device/interface/standardDevice'
import SelectTestItemPopup from '@/views/home/components/selectTestItemPopup.vue'
import CompareTestPopup from './compareTestPopup.vue'
import { ElMessage } from 'element-plus'
import CustomEdge from './RemoveableEdge.vue' // 导入自定义连接线组件
import { jwtUtil } from '@/utils/jwtUtil'
import { useCheckStore } from '@/stores/modules/check'
const checkStore = useCheckStore()
const dialogVisible = ref(false)
const selectTestItemPopupRef = ref<InstanceType<typeof SelectTestItemPopup>>()
const testPopup = ref()
const dialogTitle = ref('手动检测')
// 计算对话框高度
const dialogHeight = ref(600)
const CompareTestVisible = ref(false)
// 初始化 VueFlow注册自定义连线类型
const { edges, setViewport } = useVueFlow({
edgeTypes: {
default: CustomEdge
}
})
// 初始化时锁定画布位置
const onPaneReady = () => {
setViewport({ x: 0, y: 0, zoom: 1 })
}
// 提取公共的label渲染函数
const createLabel = (text: string, type: string) => {
return h(
'div',
{
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
fontSize: '15px',
textAlign: 'center',
border: '1px solid #ccc',
borderRadius: '8px',
padding: '8px',
backgroundColor: '#f9f9f9'
}
},
[
h(Platform, {
style: {
width: '20px',
marginBottom: '4px',
color: '#526ade'
}
}),
h('div', null, '设备名称:' + text),
h('div', null, '设备类型:' + type)
]
) as any
}
const createLabel3 = (text: string) => {
return h(
'div',
{
style: {
display: 'flex',
alignItems: 'center',
fontSize: '15px',
textAlign: 'center',
border: '1px solid #ccc',
borderRadius: '8px',
padding: '8px',
backgroundColor: '#f9f9f9'
}
},
[
h(Flag, {
style: {
width: '20px',
marginRight: '4px',
color: '#526ade'
}
}),
text
]
) as any
}
const handleConnectStart = (params: any) => {
onPaneReady()
}
const handleConnectEnd = (params: any) => {
onPaneReady()
}
const handleConnect = (params: any) => {
console.log('连接信息:', params)
const sourceNode = nodes.value.find(node => node.id === params.source)
const targetNode = nodes.value.find(node => node.id === params.target)
// 连接规则验证
const isValidConnection = sourceNode?.type === 'input' && targetNode?.type === 'output'
if (!isValidConnection) {
removeEdge(params)
ElMessage.warning('只能从被检通道连接到标准通道')
return
}
// 过滤掉当前连接,检查是否还有重复的
const existingEdges = edges.value.filter(edge => edge.source === params.source || edge.target === params.target)
// 如果同源或同目标的连接超过1个说明有重复
if (existingEdges.length > 1) {
const duplicateSource = existingEdges.filter(edge => edge.source === params.source).length > 1
const duplicateTarget = existingEdges.filter(edge => edge.target === params.target).length > 1
if (duplicateSource) {
removeEdge(params)
ElMessage.warning('该被检通道已经连接,不能重复连接')
return
}
if (duplicateTarget) {
removeEdge(params)
ElMessage.warning('该标准通道已经连接,不能重复连接')
return
}
}
}
// 删除不合法连接
const removeEdge = (params: any) => {
const edgeIndex = edges.value.findIndex(edge => edge.source === params.source && edge.target === params.target)
if (edgeIndex !== -1) {
edges.value.splice(edgeIndex, 1)
}
}
const nodes = ref([])
const planId = ref('')
const devIds = ref<string[]>()
const standardDevIds = ref<string[]>()
const devIdList = ref<Device.ResPqDev[]>([])
const pqStandardDevList = ref<StandardDevice.ResPqStandardDevice[]>([])
const planIdKey = ref<string>('')
const deviceMonitor = ref<Map<string, any[]>>();
const open = async (
device: Device.ResPqDev[],
standardDev: StandardDevice.ResPqStandardDevice[],
fatherPlanId: string,
DeviceMonitoringMap: Map<string, any[]>,
checkType: string
) => {
CompareTestVisible.value = false
devIdList.value = device
pqStandardDevList.value = standardDev
planIdKey.value = fatherPlanId
deviceMonitor.value = DeviceMonitoringMap
if(checkType == "一键检测"){
openTestDialog()
}else{
selectTestItemPopupRef.value?.open()
}
// edges.value = []
// devIds.value = device.map(d => d.id)
// standardDevIds.value = standardDev.map(d => d.id)
// planId.value = fatherPlanId
// nodes.value = createNodes(device, standardDev)
// edges.value = []
// devIds.value = device.map(d => d.id)
// standardDevIds.value = standardDev.map(d => d.id)
// planId.value = fatherPlanId
// nodes.value = createNodes(device, standardDev)
// dialogVisible.value = true
// onPaneReady()
}
const handleNext = async () => {
if (edges.value.length === 0) {
ElMessage.warning('请先完成通道配对')
return
}
// const sourceKey = edge.source.replace('被检通道-', '').replace('-', '_');
let chnNumList: string[] = []
await edges.value.forEach(edge => {
const match = edge.source.split('-')
if (match) {
chnNumList.push(match[2])
}
})
await checkStore.setChnNum(chnNumList)
CompareTestVisible.value = false
dialogVisible.value = false
selectTestItemPopupRef.value?.open()
}
const openTestDialog = async () => {
CompareTestVisible.value = true
// 转换连接信息只保留设备ID和通道号
const connections = edges.value.reduce(
(map, edge) => {
// 从source中提取设备ID和通道号: 被检通道-{deviceId}-{channelNum} => {deviceId}-{channelNum}
const sourceKey = edge.source.replace('被检通道-', '').replace('-', '_')
// 从target中提取设备ID和通道号: 标准通道-{deviceId}-{channelNum} => {deviceId}-{channelNum}
const targetValue = edge.target.replace('标准通道-', '').replace('-', '_')
map[sourceKey] = targetValue
return map
},
{} as Record<string, string>
)
generateChannelMapping()
setTimeout(() => {
testPopup.value?.open(
dialogTitle.value,
channelMapping.value,
planId.value,
jwtUtil.getLoginName(),
devIds.value,
standardDevIds.value,
connections,
deviceMonitor.value
)
}, 100)
}
// 转换 edges.value 为 channelMapping 格式
const channelMapping = ref<Record<string, Record<string, string>>>({})
// 生成映射关系的方法
const generateChannelMapping = () => {
const mapping: Record<string, Record<string, string>> = {}
edges.value.forEach(edge => {
// 解析 source 节点信息(被检通道)
const sourceParts = edge.source.split('-')
const sourceDeviceId = sourceParts[1]
const sourceChannel = sourceParts[2]
// 解析 target 节点信息(标准通道)
const targetParts = edge.target.split('-')
const targetDeviceId = targetParts[1]
const targetChannel = targetParts[2]
// 查找对应的节点以获取显示名称
const sourceDeviceNode = nodes.value.find(node => node.id === sourceDeviceId)
const targetDeviceNode = nodes.value.find(node => node.id === targetDeviceId)
if (sourceDeviceNode && targetDeviceNode) {
// 提取设备显示文本
const sourceDeviceText = sourceDeviceNode.data.label.children[1].children
const targetDeviceText = targetDeviceNode.data.label.children[1].children
// 构造键名 - 现在以标准设备为键
const targetKey = `${targetDeviceText}`.replace('设备名称:', '')
const sourceValue = `${sourceDeviceText}通道${sourceChannel}`.replace('设备名称:', '')
// 初始化对象
if (!mapping[targetKey]) {
mapping[targetKey] = {}
}
// 添加映射关系 - 标准设备通道 -> 被检设备信息
mapping[targetKey][`通道${targetChannel}`] = sourceValue
}
})
channelMapping.value = mapping
}
const createNodes = (device: Device.ResPqDev[], standardDev: StandardDevice.ResPqStandardDevice[]) => {
const channelCounts: Record<string, number> = {}
device.forEach(device => {
channelCounts[device.id] = device.devChns || 0
})
const inspectionDevices = device.map(d => ({
id: d.id,
name: d.name,
type: 'normal',
deviceType: d.devType
}))
const channelCounts2: Record<string, number> = {}
standardDev.forEach(dev => {
const channelList = dev.inspectChannel ? dev.inspectChannel.split(',') : []
channelCounts2[dev.id] = channelList.length
})
const standardDevices = standardDev.map(d => ({
id: d.id,
name: d.name,
type: 'normal',
deviceType: d.devType
}))
const newNodes: any[] = []
const deviceChannelGroups: { deviceId: string; centerY: number }[] = []
const standardChannelGroups: any[] = []
const deviceWidth = 0
const inputChannelX = 200
const outputChannelX = 800
const standardWidth = 950
const yPosition = ref(25)
const yPosition2 = ref(25)
// 添加被检通道
Object.entries(channelCounts).forEach(([deviceId, count]) => {
for (let i = 1; i <= count; i++) {
const channelId = `被检通道-${deviceId}-${i}`
newNodes.push({
id: channelId,
type: 'input',
data: { label: createLabel3(`被检通道${i}`) },
position: { x: inputChannelX, y: yPosition.value },
sourcePosition: 'right',
style: { width: '150px', border: 'none', boxShadow: 'none' }
})
// 计算设备节点Y坐标居中显示
if (i == 1 && count == 1) {
deviceChannelGroups.push({
deviceId,
centerY: yPosition.value - 25
})
} else if (i == 2 && count == 2) {
deviceChannelGroups.push({
deviceId,
centerY: yPosition.value - 50
})
} else if (i == 3 && count == 3) {
deviceChannelGroups.push({
deviceId,
centerY: yPosition.value - 75
})
} else if (i == 4 && count == 4) {
deviceChannelGroups.push({
deviceId,
centerY: yPosition.value - 100
})
}
yPosition.value += 50
}
yPosition.value += 50
})
// 添加标准通道
Object.entries(channelCounts2).forEach(([deviceId, count]) => {
for (let i = 1; i <= count; i++) {
const channelId = `标准通道-${deviceId}-${i}`
newNodes.push({
id: channelId,
type: 'output',
data: { label: createLabel3(`标准通道${i}`) },
position: { x: outputChannelX, y: yPosition2.value },
targetPosition: 'left',
style: { width: '150px', border: 'none', boxShadow: 'none' }
})
// 计算设备节点Y坐标居中显示
if (i == 1 && count == 1) {
standardChannelGroups.push({
deviceId,
centerY: yPosition2.value - 25
})
} else if (i == 2 && count == 2) {
standardChannelGroups.push({
deviceId,
centerY: yPosition2.value - 50
})
} else if (i == 3 && count == 3) {
standardChannelGroups.push({
deviceId,
centerY: yPosition2.value - 100
})
} else if (i == 4 && count == 4) {
standardChannelGroups.push({
deviceId,
centerY: yPosition2.value - 100
})
}
yPosition2.value += 50
}
yPosition2.value += 50
})
// 添加被检设备
deviceChannelGroups.forEach(({ deviceId, centerY }) => {
const device = inspectionDevices.find(d => d.id === deviceId)
if (device) {
newNodes.push({
id: device.id,
data: { label: createLabel(device.name, device.deviceType) },
position: { x: deviceWidth, y: centerY },
class: 'no-handle-node',
style: { width: '200px', border: 'none', boxShadow: 'none' }
})
}
})
// 添加标准设备
standardChannelGroups.forEach(({ deviceId, centerY }) => {
const device = standardDevices.find(d => d.id === deviceId)
if (device) {
newNodes.push({
id: device.id,
data: { label: createLabel(device.name, device.deviceType) },
position: { x: standardWidth, y: centerY },
class: 'no-handle-node',
style: { width: '200px', border: 'none', boxShadow: 'none' }
})
}
})
//页面高度取决于设备通道
dialogHeight.value = Math.max(yPosition.value, yPosition2.value)
return newNodes
}
defineExpose({ open })
</script>
<style>
.flow-container {
width: 100%;
overflow: hidden;
}
.vue-flow__node.no-handle-node .vue-flow__handle {
display: none !important;
}
</style>