Browse Source

feat: 道闸控制优先选择MQTT独立道闸设备

- /parking/gate/devices 返回 connect_type,前端可区分读卡器与独立道闸
- 工作台自动选择与通道监控卡片的道闸设备优先取 mqtt 设备:
  单一通道同时绑定读卡器(TCP继电器开闸)与 MQTT 道闸时,
  手动开/关闸不再误发读卡器继电器指令(此前无任何 UI 途径选中 MQTT 道闸)
- 实测:工作台关闸 → MQTT cmd → 盒子 ack success,闭环 624ms
lq 4 giờ trước cách đây
mục cha
commit
d65ecb8daa
2 tập tin đã thay đổi với 23 bổ sung13 xóa
  1. 19 13
      frontend/src/view/parking/entryExit.vue
  2. 4 0
      internal/service/parking/gate.go

+ 19 - 13
frontend/src/view/parking/entryExit.vue

@@ -532,22 +532,27 @@ const channelMonitorCards = computed(() => {
   const cards = []
   const cardsByChannel = new Map()
   const usedCameraCodes = new Set()
+  // 每通道的道闸设备优先取 MQTT 道闸(独立道闸控制器),读卡器继电器设备次之
+  const gatePreference = device => (device.connect_type === 'mqtt' ? 0 : 1) + (device.connected ? 0 : 2)
+  const devicesByChannel = new Map()
   gateDevices.value.forEach(device => {
     const channelKey = String(device.channel_id || device.channel_code || device.device_code)
-    const camera = cameras.value.find(item => String(item.channel_id) === String(device.channel_id)) || null
+    const list = devicesByChannel.get(channelKey) || []
+    list.push(device)
+    devicesByChannel.set(channelKey, list)
+  })
+  devicesByChannel.forEach((list, channelKey) => {
+    const camera = cameras.value.find(item => String(item.channel_id) === String(channelKey)) || null
     if (camera) usedCameraCodes.add(camera.device_code)
-    const existing = cardsByChannel.get(channelKey)
-    if (!existing || (!existing.device?.connected && device.connected)) {
-      const card = {
-        key: `channel-${channelKey}`,
-        channelName: device.channel_name || device.channel_code || `通道 ${device.channel_id || '-'}`,
-        device,
-        camera
-      }
-      cardsByChannel.set(channelKey, card)
-      if (!existing) cards.push(card)
-      else cards[cards.indexOf(existing)] = card
+    const device = [...list].sort((a, b) => gatePreference(a) - gatePreference(b))[0]
+    const card = {
+      key: `channel-${channelKey}`,
+      channelName: device.channel_name || device.channel_code || `通道 ${device.channel_id || '-'}`,
+      device,
+      camera
     }
+    cardsByChannel.set(channelKey, card)
+    cards.push(card)
   })
   cameras.value.filter(camera => !usedCameraCodes.has(camera.device_code)).forEach(camera => {
     cards.push({
@@ -794,7 +799,8 @@ function assignGateDevice(action = contextAction.value) {
   const matches = device => device.connected && (!direction || device.direction === direction || device.direction === 'inout') &&
     (action !== 'entry' || !entryForm.parking_lot_id || !device.parking_lot_id || device.parking_lot_id === entryForm.parking_lot_id)
   if (current && matches(current)) return
-  const next = gateDevices.value.find(matches)
+  // 通道上同时有读卡器(继电器接线开闸)与独立 MQTT 道闸时,道闸控制优先选 MQTT 设备
+  const next = gateDevices.value.find(d => matches(d) && d.connect_type === 'mqtt') || gateDevices.value.find(matches)
   selectedGateDeviceCode.value = next?.device_code || ''
   if (action === 'entry' && next?.parking_lot_id) {
     entryForm.parking_lot_id = next.parking_lot_id

+ 4 - 0
internal/service/parking/gate.go

@@ -41,6 +41,9 @@ type GateDeviceOption struct {
 	ParkingLotID uint   `json:"parking_lot_id"`
 	Connected    bool   `json:"connected"`
 	Simulated    bool   `json:"simulated"`
+	// ConnectType 设备接入方式(serial/tcp/mqtt)。通道上同时有读卡器(继电器
+	// 接线开闸)和独立 MQTT 道闸时,前端道闸控制优先选 mqtt 设备。
+	ConnectType string `json:"connect_type"`
 }
 
 // GateRuntimeStatus 表示统一控制器中的设备实时状态。
@@ -253,6 +256,7 @@ func (s *PassageService) ListGateDevices() ([]GateDeviceOption, error) {
 			ParkingLotID: parkingLotID,
 			Connected:    runtimeStatus.Connected,
 			Simulated:    runtimeStatus.Simulated,
+			ConnectType:  string(reader.ConnectType),
 		})
 	}
 	return options, nil