Files
pqs-9100_client/frontend/src/views/home/components/deviceConnectionPopup.vue

510 lines
16 KiB
Vue
Raw Normal View History

<template>
2025-08-26 20:52:24 +08:00
<!-- <el-dialog title="设备通道配对" v-model="dialogVisible" v-bind="dialogBig">
2025-08-20 20:02:22 +08:00
<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>
2025-08-26 20:52:24 +08:00
</el-dialog> -->
<!-- devIdList.value
pqStandardDevList.value
planIdKey.value -->
2025-08-20 20:02:22 +08:00
<!-- 手动检测-勾选检测项弹窗 -->
<SelectTestItemPopup ref="selectTestItemPopupRef" @openTestDialog="openTestDialog"></SelectTestItemPopup>
2025-08-26 20:52:24 +08:00
<CompareTestPopup
ref="testPopup"
v-if="CompareTestVisible"
:devIdList="devIdList"
:pqStandardDevList="pqStandardDevList"
:planIdKey="planIdKey"
></CompareTestPopup>
</template>
<script lang="ts" setup>
2025-08-05 10:37:40 +08:00
import { h, ref } from 'vue'
import { VueFlow, useVueFlow } from '@vue-flow/core'
2025-08-20 20:02:22 +08:00
import { dialogBig } from '@/utils/elementBind'
2025-08-05 10:37:40 +08:00
import { Platform, Flag } from '@element-plus/icons-vue'
2025-08-20 20:02:22 +08:00
import { Device } from '@/api/device/interface/device'
import { StandardDevice } from '@/api/device/interface/standardDevice'
import SelectTestItemPopup from '@/views/home/components/selectTestItemPopup.vue'
2025-08-05 10:37:40 +08:00
import CompareTestPopup from './compareTestPopup.vue'
2025-08-20 20:02:22 +08:00
import { ElMessage } from 'element-plus'
2025-08-05 10:37:40 +08:00
import CustomEdge from './RemoveableEdge.vue' // 导入自定义连接线组件
2025-08-20 20:02:22 +08:00
import { jwtUtil } from '@/utils/jwtUtil'
import { useCheckStore } from '@/stores/modules/check'
2025-08-14 15:02:58 +08:00
2025-08-20 20:02:22 +08:00
const checkStore = useCheckStore()
const dialogVisible = ref(false)
2025-08-05 10:37:40 +08:00
const selectTestItemPopupRef = ref<InstanceType<typeof SelectTestItemPopup>>()
const testPopup = ref()
const dialogTitle = ref('手动检测')
2025-08-07 14:43:56 +08:00
// 计算对话框高度
const dialogHeight = ref(600)
2025-08-21 09:33:13 +08:00
const CompareTestVisible = ref(false)
2025-08-05 10:37:40 +08:00
// 初始化 VueFlow注册自定义连线类型
const { edges, setViewport } = useVueFlow({
2025-08-20 20:02:22 +08:00
edgeTypes: {
default: CustomEdge
}
2025-08-05 10:37:40 +08:00
})
2025-08-05 10:37:40 +08:00
// 初始化时锁定画布位置
const onPaneReady = () => {
2025-08-20 20:02:22 +08:00
setViewport({ x: 0, y: 0, zoom: 1 })
2025-08-05 10:37:40 +08:00
}
// 提取公共的label渲染函数
2025-08-05 10:37:40 +08:00
const createLabel = (text: string, type: string) => {
2025-08-20 20:02:22 +08:00
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) => {
2025-08-20 20:02:22 +08:00
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
}
2025-08-05 10:37:40 +08:00
const handleConnectStart = (params: any) => {
2025-08-20 20:02:22 +08:00
onPaneReady()
2025-08-05 10:37:40 +08:00
}
const handleConnectEnd = (params: any) => {
2025-08-20 20:02:22 +08:00
onPaneReady()
2025-08-05 10:37:40 +08:00
}
const handleConnect = (params: any) => {
2025-08-20 20:02:22 +08:00
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
2025-08-06 15:18:27 +08:00
}
2025-08-20 20:02:22 +08:00
// 过滤掉当前连接,检查是否还有重复的
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
}
2025-08-06 15:18:27 +08:00
}
}
2025-08-05 10:37:40 +08:00
// 删除不合法连接
const removeEdge = (params: any) => {
2025-08-20 20:02:22 +08:00
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([])
2025-08-07 15:17:04 +08:00
const planId = ref('')
const devIds = ref<string[]>()
const standardDevIds = ref<string[]>()
2025-08-26 20:52:24 +08:00
const devIdList = ref<Device.ResPqDev[]>([])
const pqStandardDevList = ref<StandardDevice.ResPqStandardDevice[]>([])
const planIdKey = ref<string>('')
const deviceMonitor = ref<Map<string, any[]>>();
2025-08-20 20:02:22 +08:00
const open = async (
2025-08-20 20:02:22 +08:00
device: Device.ResPqDev[],
standardDev: StandardDevice.ResPqStandardDevice[],
fatherPlanId: string,
2025-09-17 15:42:05 +08:00
DeviceMonitoringMap: Map<string, any[]>,
checkType: string
2025-08-20 20:02:22 +08:00
) => {
2025-08-27 11:17:13 +08:00
CompareTestVisible.value = false
2025-09-17 15:42:05 +08:00
2025-08-26 20:52:24 +08:00
devIdList.value = device
pqStandardDevList.value = standardDev
planIdKey.value = fatherPlanId
deviceMonitor.value = DeviceMonitoringMap
2025-09-17 15:42:05 +08:00
if(checkType == "一键检测"){
openTestDialog()
}else{
selectTestItemPopupRef.value?.open()
}
2025-08-26 20:52:24 +08:00
// 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()
}
2025-08-20 20:02:22 +08:00
const handleNext = async () => {
if (edges.value.length === 0) {
ElMessage.warning('请先完成通道配对')
return
}
// const sourceKey = edge.source.replace('被检通道-', '').replace('-', '_');
2025-08-22 15:33:57 +08:00
let chnNumList: string[] = []
2025-08-20 20:02:22 +08:00
await edges.value.forEach(edge => {
const match = edge.source.split('-')
if (match) {
2025-08-22 15:33:57 +08:00
chnNumList.push(match[2])
2025-08-20 20:02:22 +08:00
}
})
await checkStore.setChnNum(chnNumList)
2025-08-21 09:33:13 +08:00
CompareTestVisible.value = false
2025-08-20 20:02:22 +08:00
dialogVisible.value = false
2025-08-21 09:33:13 +08:00
2025-08-20 20:02:22 +08:00
selectTestItemPopupRef.value?.open()
2025-08-05 10:37:40 +08:00
}
2025-07-24 08:29:03 +08:00
2025-08-07 14:43:56 +08:00
const openTestDialog = async () => {
2025-08-21 09:33:13 +08:00
CompareTestVisible.value = true
2025-08-20 20:02:22 +08:00
// 转换连接信息只保留设备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()
2025-08-21 09:33:13 +08:00
setTimeout(() => {
2025-08-21 09:33:13 +08:00
testPopup.value?.open(
dialogTitle.value,
channelMapping.value,
planId.value,
jwtUtil.getLoginName(),
devIds.value,
standardDevIds.value,
connections,
deviceMonitor.value
2025-08-21 09:33:13 +08:00
)
}, 100)
2025-08-08 13:18:01 +08:00
}
// 转换 edges.value 为 channelMapping 格式
const channelMapping = ref<Record<string, Record<string, string>>>({})
// 生成映射关系的方法
const generateChannelMapping = () => {
2025-08-20 20:02:22 +08:00
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
2025-08-05 10:37:40 +08:00
}
2025-07-24 08:29:03 +08:00
2025-08-05 10:37:40 +08:00
const createNodes = (device: Device.ResPqDev[], standardDev: StandardDevice.ResPqStandardDevice[]) => {
2025-08-20 20:02:22 +08:00
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
}
2025-08-05 10:37:40 +08:00
defineExpose({ open })
</script>
<style>
.flow-container {
2025-08-20 20:02:22 +08:00
width: 100%;
2025-08-27 11:17:13 +08:00
2025-08-20 20:02:22 +08:00
overflow: hidden;
}
.vue-flow__node.no-handle-node .vue-flow__handle {
2025-08-20 20:02:22 +08:00
display: none !important;
}
2025-08-20 20:02:22 +08:00
</style>