From 706eabba4750cf92282378ae5d2414f497a4578c Mon Sep 17 00:00:00 2001
From: Junjie <DELL@qq.com>
Date: 星期四, 08 一月 2026 12:51:23 +0800
Subject: [PATCH] #
---
src/main/resources/mapper/BasStationDeviceMapper.xml | 14 +
src/main/webapp/static/js/basStationDevice/basStationDevice.js | 214 +++++++++++++++++
src/main/java/com/zy/asrs/controller/BasStationDeviceController.java | 110 +++++++++
src/main/java/com/zy/asrs/service/impl/BasStationDeviceServiceImpl.java | 11
src/main/webapp/views/basStationDevice/basStationDevice.html | 269 ++++++++++++++++++++++
src/main/java/com/zy/asrs/utils/Utils.java | 33 ++
src/main/java/com/zy/asrs/entity/BasStationDevice.java | 41 +++
src/main/java/com/zy/asrs/mapper/BasStationDeviceMapper.java | 11
src/main/java/com/zy/asrs/service/BasStationDeviceService.java | 7
9 files changed, 710 insertions(+), 0 deletions(-)
diff --git a/src/main/java/com/zy/asrs/controller/BasStationDeviceController.java b/src/main/java/com/zy/asrs/controller/BasStationDeviceController.java
new file mode 100644
index 0000000..700b00b
--- /dev/null
+++ b/src/main/java/com/zy/asrs/controller/BasStationDeviceController.java
@@ -0,0 +1,110 @@
+package com.zy.asrs.controller;
+
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.zy.asrs.entity.BasCrnp;
+import com.zy.asrs.entity.BasDevp;
+import com.zy.asrs.entity.BasDualCrnp;
+import com.zy.asrs.entity.BasStation;
+import com.zy.asrs.entity.BasStationDevice;
+import com.zy.asrs.service.BasCrnpService;
+import com.zy.asrs.service.BasDevpService;
+import com.zy.asrs.service.BasDualCrnpService;
+import com.zy.asrs.service.BasStationDeviceService;
+import com.zy.asrs.service.BasStationService;
+import com.core.annotations.ManagerAuth;
+import com.core.common.R;
+import com.zy.common.web.BaseController;
+import com.zy.core.enums.SlaveType;
+import com.zy.core.model.StationObjModel;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/basStationDevice")
+public class BasStationDeviceController extends BaseController {
+
+ @Autowired
+ private BasStationDeviceService basStationDeviceService;
+ @Autowired
+ private BasDevpService basDevpService;
+ @Autowired
+ private BasStationService basStationService;
+ @Autowired
+ private BasCrnpService basCrnpService;
+ @Autowired
+ private BasDualCrnpService basDualCrnpService;
+
+ @RequestMapping("/list/auth")
+ @ManagerAuth
+ public R list() {
+ return R.ok(basStationDeviceService.selectList(new EntityWrapper<>()));
+ }
+
+ @RequestMapping("/save/auth")
+ @ManagerAuth
+ public R save(@RequestBody List<BasStationDevice> list) {
+ // Full replacement logic for simplicity in this specific UI case
+ // First delete all existing, then insert new ones.
+ // Note: In a production environment with high concurrency, this should be transactional and more granular.
+ // But for a configuration page, this is acceptable.
+
+ // However, to be safer, we should probably only delete for the stations involved or delete all if it's a full save.
+ // Let's assume the UI sends the full current state of configuration.
+ basStationDeviceService.delete(new EntityWrapper<>());
+ if (list != null && !list.isEmpty()) {
+ basStationDeviceService.insertBatch(list);
+ }
+ return R.ok();
+ }
+
+ @RequestMapping("/data/auth")
+ @ManagerAuth
+ public R getData() {
+ Map<String, Object> data = new HashMap<>();
+
+ List<Integer> stationList = new ArrayList<>();
+ List<BasDevp> devps = basDevpService.selectList(new EntityWrapper<BasDevp>().eq("status", 1));
+ for (BasDevp devp : devps) {
+ for (StationObjModel stationObjModel : devp.getBarcodeStationList$()) {
+ stationList.add(stationObjModel.getStationId());
+ }
+ }
+
+ List<BasStation> stations = basStationService.selectList(new EntityWrapper<BasStation>().in("station_id", stationList));
+ data.put("stations", stations);
+
+ // Get Devices (Crn and DualCrn)
+ List<Map<String, Object>> devices = new ArrayList<>();
+
+ List<BasCrnp> crns = basCrnpService.selectList(new EntityWrapper<BasCrnp>().eq("status", 1));
+ for (BasCrnp crn : crns) {
+ Map<String, Object> d = new HashMap<>();
+ d.put("deviceNo", crn.getCrnNo());
+ d.put("type", SlaveType.Crn.toString());
+ d.put("name", "鍫嗗灈鏈� " + crn.getCrnNo());
+ devices.add(d);
+ }
+
+ List<BasDualCrnp> dualCrns = basDualCrnpService.selectList(new EntityWrapper<BasDualCrnp>().eq("status", 1));
+ for (BasDualCrnp dualCrn : dualCrns) {
+ Map<String, Object> d = new HashMap<>();
+ d.put("deviceNo", dualCrn.getCrnNo());
+ d.put("type", SlaveType.DualCrn.toString());
+ d.put("name", "鍙屽伐浣嶅爢鍨涙満 " + dualCrn.getCrnNo());
+ devices.add(d);
+ }
+
+ data.put("devices", devices);
+
+ // Get existing relations
+ List<BasStationDevice> relations = basStationDeviceService.selectList(new EntityWrapper<>());
+ data.put("relations", relations);
+
+ return R.ok(data);
+ }
+}
diff --git a/src/main/java/com/zy/asrs/entity/BasStationDevice.java b/src/main/java/com/zy/asrs/entity/BasStationDevice.java
new file mode 100644
index 0000000..d2f529a
--- /dev/null
+++ b/src/main/java/com/zy/asrs/entity/BasStationDevice.java
@@ -0,0 +1,41 @@
+package com.zy.asrs.entity;
+
+import com.baomidou.mybatisplus.annotations.TableField;
+import com.baomidou.mybatisplus.annotations.TableId;
+import com.baomidou.mybatisplus.annotations.TableName;
+import com.baomidou.mybatisplus.enums.IdType;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+@TableName("asr_bas_station_device")
+public class BasStationDevice implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 绔欑偣缂栧彿
+ */
+ @ApiModelProperty(value= "绔欑偣缂栧彿")
+ @TableField("station_id")
+ private Integer stationId;
+
+ /**
+ * 璁惧缂栧彿
+ */
+ @ApiModelProperty(value= "璁惧缂栧彿")
+ @TableField("device_no")
+ private Integer deviceNo;
+
+ /**
+ * 璁惧绫诲瀷 (Crn/DualCrn)
+ */
+ @ApiModelProperty(value= "璁惧绫诲瀷")
+ @TableField("device_type")
+ private String deviceType;
+}
diff --git a/src/main/java/com/zy/asrs/mapper/BasStationDeviceMapper.java b/src/main/java/com/zy/asrs/mapper/BasStationDeviceMapper.java
new file mode 100644
index 0000000..41e9265
--- /dev/null
+++ b/src/main/java/com/zy/asrs/mapper/BasStationDeviceMapper.java
@@ -0,0 +1,11 @@
+package com.zy.asrs.mapper;
+
+import com.baomidou.mybatisplus.mapper.BaseMapper;
+import com.zy.asrs.entity.BasStationDevice;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface BasStationDeviceMapper extends BaseMapper<BasStationDevice> {
+}
diff --git a/src/main/java/com/zy/asrs/service/BasStationDeviceService.java b/src/main/java/com/zy/asrs/service/BasStationDeviceService.java
new file mode 100644
index 0000000..98c5fba
--- /dev/null
+++ b/src/main/java/com/zy/asrs/service/BasStationDeviceService.java
@@ -0,0 +1,7 @@
+package com.zy.asrs.service;
+
+import com.baomidou.mybatisplus.service.IService;
+import com.zy.asrs.entity.BasStationDevice;
+
+public interface BasStationDeviceService extends IService<BasStationDevice> {
+}
diff --git a/src/main/java/com/zy/asrs/service/impl/BasStationDeviceServiceImpl.java b/src/main/java/com/zy/asrs/service/impl/BasStationDeviceServiceImpl.java
new file mode 100644
index 0000000..6cca0f8
--- /dev/null
+++ b/src/main/java/com/zy/asrs/service/impl/BasStationDeviceServiceImpl.java
@@ -0,0 +1,11 @@
+package com.zy.asrs.service.impl;
+
+import com.baomidou.mybatisplus.service.impl.ServiceImpl;
+import com.zy.asrs.entity.BasStationDevice;
+import com.zy.asrs.mapper.BasStationDeviceMapper;
+import com.zy.asrs.service.BasStationDeviceService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class BasStationDeviceServiceImpl extends ServiceImpl<BasStationDeviceMapper, BasStationDevice> implements BasStationDeviceService {
+}
diff --git a/src/main/java/com/zy/asrs/utils/Utils.java b/src/main/java/com/zy/asrs/utils/Utils.java
index 6fd6c90..f2c7d43 100644
--- a/src/main/java/com/zy/asrs/utils/Utils.java
+++ b/src/main/java/com/zy/asrs/utils/Utils.java
@@ -11,9 +11,11 @@
import com.core.exception.CoolException;
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.BasDualCrnp;
+import com.zy.asrs.entity.BasStationDevice;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasCrnpService;
import com.zy.asrs.service.BasDualCrnpService;
+import com.zy.asrs.service.BasStationDeviceService;
import com.zy.asrs.service.WrkMastService;
import com.zy.common.model.NavigateNode;
import com.zy.common.utils.NavigateUtils;
@@ -168,6 +170,18 @@
NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
WrkMastService wrkMastService = SpringUtils.getBean(WrkMastService.class);
BasCrnpService basCrnpService = SpringUtils.getBean(BasCrnpService.class);
+ BasStationDeviceService basStationDeviceService = SpringUtils.getBean(BasStationDeviceService.class);
+
+ List<BasStationDevice> stationDevices = basStationDeviceService.selectList(new EntityWrapper<BasStationDevice>().eq("station_id", stationId));
+ boolean hasConfig = !stationDevices.isEmpty();
+ List<Integer> allowedCrnNos = new ArrayList<>();
+ if (hasConfig) {
+ for (BasStationDevice sd : stationDevices) {
+ if (SlaveType.Crn.toString().equals(sd.getDeviceType())) {
+ allowedCrnNos.add(sd.getDeviceNo());
+ }
+ }
+ }
Integer currentCircleTaskCrnNo = null;
Object object = redisUtil.get(RedisKeyType.CURRENT_CIRCLE_TASK_CRN_NO.key);
@@ -216,6 +230,9 @@
List<BasCrnp> enabledCrnps = new ArrayList<>();
for (BasCrnp basCrnp : baseList) {
+ if (hasConfig && !allowedCrnNos.contains(basCrnp.getCrnNo())) {
+ continue;
+ }
CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
if (crnThread == null) {
continue;
@@ -277,6 +294,18 @@
NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
WrkMastService wrkMastService = SpringUtils.getBean(WrkMastService.class);
BasDualCrnpService basDualCrnpService = SpringUtils.getBean(BasDualCrnpService.class);
+ BasStationDeviceService basStationDeviceService = SpringUtils.getBean(BasStationDeviceService.class);
+
+ List<BasStationDevice> stationDevices = basStationDeviceService.selectList(new EntityWrapper<BasStationDevice>().eq("station_id", stationId));
+ boolean hasConfig = !stationDevices.isEmpty();
+ List<Integer> allowedCrnNos = new ArrayList<>();
+ if (hasConfig) {
+ for (BasStationDevice sd : stationDevices) {
+ if (SlaveType.Crn.toString().equals(sd.getDeviceType())) {
+ allowedCrnNos.add(sd.getDeviceNo());
+ }
+ }
+ }
Wrapper<BasDualCrnp> wrapper = new EntityWrapper<BasDualCrnp>()
.eq("in_enable", "Y")
@@ -304,6 +333,10 @@
List<BasDualCrnp> enabledCrnps = new ArrayList<>();
for (BasDualCrnp basDualCrnp : baseList) {
+ if (hasConfig && !allowedCrnNos.contains(basDualCrnp.getCrnNo())) {
+ continue;
+ }
+
DualCrnThread dualCrnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, basDualCrnp.getCrnNo());
if (dualCrnThread == null) {
continue;
diff --git a/src/main/resources/mapper/BasStationDeviceMapper.xml b/src/main/resources/mapper/BasStationDeviceMapper.xml
new file mode 100644
index 0000000..d745190
--- /dev/null
+++ b/src/main/resources/mapper/BasStationDeviceMapper.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zy.asrs.mapper.BasStationDeviceMapper">
+
+ <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+ <resultMap id="BaseResultMap" type="com.zy.asrs.entity.BasStationDevice">
+ <id column="id" property="id" />
+ <result column="station_id" property="stationId" />
+ <result column="device_no" property="deviceNo" />
+ <result column="device_type" property="deviceType" />
+
+ </resultMap>
+
+</mapper>
diff --git a/src/main/webapp/static/js/basStationDevice/basStationDevice.js b/src/main/webapp/static/js/basStationDevice/basStationDevice.js
new file mode 100644
index 0000000..8b19f8e
--- /dev/null
+++ b/src/main/webapp/static/js/basStationDevice/basStationDevice.js
@@ -0,0 +1,214 @@
+var app = new Vue({
+ el: '#app',
+ data: {
+ stations: [],
+ devices: [],
+ relations: [], // {stationId, deviceNo, deviceType}
+
+ lines: [], // {x1, y1, x2, y2, stationId, deviceNo}
+
+ draggingItem: null, // Currently dragged item
+ draggingLine: null, // Temporary line for visual feedback
+
+ saving: false,
+
+ containerOffset: { top: 0, left: 0 }
+ },
+ mounted: function() {
+ this.loadData();
+
+ // Listen to window resize to redraw lines
+ window.addEventListener('resize', this.updateLines);
+
+ // Listen to scroll events on lists to redraw lines
+ this.$refs.stationList.addEventListener('scroll', this.updateLines);
+ this.$refs.deviceList.addEventListener('scroll', this.updateLines);
+ },
+ beforeDestroy: function() {
+ window.removeEventListener('resize', this.updateLines);
+ },
+ methods: {
+ loadData: function() {
+ var that = this;
+ var loading = this.$loading({ lock: true, text: '鍔犺浇鏁版嵁涓�...', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)' });
+
+ $.ajax({
+ url: baseUrl + '/basStationDevice/data/auth',
+ headers: { 'token': localStorage.getItem('token') },
+ method: 'GET',
+ success: function(res) {
+ loading.close();
+ if (res.code === 200) {
+ that.stations = res.data.stations || [];
+ that.devices = res.data.devices || [];
+ that.relations = res.data.relations || [];
+
+ // Delay to wait for DOM rendering
+ that.$nextTick(function() {
+ that.updateLines();
+ });
+ } else if (res.code === 403) {
+ top.location.href = baseUrl + "/";
+ } else {
+ that.$message.error('鍔犺浇鏁版嵁澶辫触: ' + res.msg);
+ }
+ },
+ error: function() {
+ loading.close();
+ that.$message.error('鍔犺浇璇锋眰寮傚父');
+ }
+ });
+ },
+
+ saveData: function() {
+ var that = this;
+ this.saving = true;
+
+ $.ajax({
+ url: baseUrl + '/basStationDevice/save/auth',
+ type: 'POST',
+ headers: { 'token': localStorage.getItem('token') },
+ contentType: 'application/json',
+ data: JSON.stringify(this.relations),
+ success: function (res) {
+ that.saving = false;
+ if (res.code === 200) {
+ that.$message.success('淇濆瓨鎴愬姛');
+ that.loadData();
+ } else {
+ that.$message.error('淇濆瓨澶辫触: ' + res.msg);
+ }
+ },
+ error: function () {
+ that.saving = false;
+ that.$message.error('淇濆瓨璇锋眰寮傚父');
+ }
+ });
+ },
+
+ isStationConnected: function(stationId) {
+ return this.relations.some(function(r) { return r.stationId == stationId; });
+ },
+
+ isDeviceConnected: function(deviceNo, type) {
+ return this.relations.some(function(r) { return r.deviceNo == deviceNo && r.deviceType == type; });
+ },
+
+ // --- Drag and Drop Logic ---
+
+ onDragStart: function(event, item, type) {
+ this.draggingItem = { item: item, type: type };
+ event.dataTransfer.effectAllowed = 'link';
+ // Optional: set custom drag image
+ },
+
+ onDragEnd: function() {
+ this.draggingItem = null;
+ },
+
+ onDrop: function(event, targetDevice) {
+ if (!this.draggingItem || this.draggingItem.type !== 'station') {
+ return;
+ }
+
+ var station = this.draggingItem.item;
+
+ // Check if exists
+ var exists = this.relations.some(function(r) {
+ return r.stationId == station.stationId && r.deviceNo == targetDevice.deviceNo && r.deviceType == targetDevice.type;
+ });
+
+ if (exists) {
+ this.$message.warning('璇ュ叧鑱斿凡瀛樺湪');
+ return;
+ }
+
+ // Add relation
+ this.relations.push({
+ stationId: station.stationId,
+ deviceNo: targetDevice.deviceNo,
+ deviceType: targetDevice.type
+ });
+
+ this.$message.success('宸插缓绔嬪叧鑱�: 绔欑偣 ' + station.stationId + ' -> ' + targetDevice.name);
+
+ this.$nextTick(this.updateLines);
+ },
+
+ confirmDelete: function(index) {
+ var that = this;
+ var link = this.relations[index]; // Note: lines index must match relations index? No.
+
+ // We need to map line click to actual relation.
+ // The lines array is rebuilt from relations, so we can store the index or relation object in the line object.
+ // But let's check updateLines logic first.
+
+ // In updateLines, we iterate relations. So index matches if filtered correctly?
+ // Wait, updateLines maps relations 1-to-1 to lines IF DOM exists.
+ // If DOM doesn't exist (scrolled out?), line might not exist.
+ // But we should draw all lines?
+ // Actually, if scrolled out, we might want to hide lines or point to edge.
+ // For simplicity, we only draw lines for visible nodes or calculate positions for all.
+ // Since lists are scrollable, calculating positions for invisible nodes is tricky (top/bottom are relative to viewport).
+
+ // Better approach: Store the relation object in the line object.
+
+ var rel = this.lines[index].relation;
+
+ this.$confirm('纭畾鍒犻櫎璇ュ叧鑱斿悧?', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(function() {
+ // Find index in relations array
+ var idx = that.relations.indexOf(rel);
+ if (idx > -1) {
+ that.relations.splice(idx, 1);
+ that.$nextTick(that.updateLines);
+ that.$message.success('宸插垹闄�');
+ }
+ }).catch(function() {});
+ },
+
+ updateLines: function() {
+ var that = this;
+ var newLines = [];
+
+ // Get SVG offset
+ var svgElement = this.$el.querySelector('svg');
+ if (!svgElement) return;
+ var svgRect = svgElement.getBoundingClientRect();
+
+ // We need positions relative to the SVG
+
+ this.relations.forEach(function(rel) {
+ var sDom = document.getElementById('station-' + rel.stationId);
+ var dDom = document.getElementById('device-' + rel.deviceType + '-' + rel.deviceNo);
+
+ if (sDom && dDom) {
+ var sRect = sDom.getBoundingClientRect();
+ var dRect = dDom.getBoundingClientRect();
+
+ // Coordinates relative to SVG
+ var x1 = sRect.right - svgRect.left;
+ var y1 = sRect.top + sRect.height / 2 - svgRect.top;
+
+ var x2 = dRect.left - svgRect.left;
+ var y2 = dRect.top + dRect.height / 2 - svgRect.top;
+
+ newLines.push({
+ x1: x1,
+ y1: y1,
+ x2: x2,
+ y2: y2,
+ relation: rel,
+ stationId: rel.stationId,
+ deviceNo: rel.deviceNo
+ });
+ }
+ });
+
+ this.lines = newLines;
+ }
+ }
+});
diff --git a/src/main/webapp/views/basStationDevice/basStationDevice.html b/src/main/webapp/views/basStationDevice/basStationDevice.html
new file mode 100644
index 0000000..3346886
--- /dev/null
+++ b/src/main/webapp/views/basStationDevice/basStationDevice.html
@@ -0,0 +1,269 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>绔欑偣璁惧鍏崇郴閰嶇疆</title>
+ <meta name="renderer" content="webkit">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+ <!-- Element UI CSS -->
+ <link rel="stylesheet" href="../../static/vue/element/element.css">
+ <link rel="stylesheet" href="../../static/css/cool.css">
+ <style>
+ html, body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ background-color: #f0f2f5;
+ }
+ #app {
+ height: 100%;
+ padding: 20px;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ }
+ .main-card {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ }
+ .flow-container {
+ flex: 1;
+ display: flex;
+ position: relative;
+ background: #fff;
+ border: 1px solid #ebeef5;
+ overflow: hidden; /* Hide scrollbars of container */
+ }
+
+ .column {
+ width: 300px;
+ display: flex;
+ flex-direction: column;
+ border-right: 1px solid #ebeef5;
+ background-color: #fcfcfc;
+ z-index: 10;
+ }
+ .column.right {
+ border-right: none;
+ border-left: 1px solid #ebeef5;
+ position: absolute;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ }
+
+ .column-header {
+ padding: 15px;
+ text-align: center;
+ font-weight: bold;
+ background-color: #f5f7fa;
+ border-bottom: 1px solid #ebeef5;
+ color: #606266;
+ }
+
+ .list-container {
+ flex: 1;
+ overflow-y: auto;
+ padding: 10px;
+ }
+
+ .node-item {
+ padding: 12px;
+ margin-bottom: 10px;
+ background-color: #fff;
+ border: 1px solid #dcdfe6;
+ border-radius: 4px;
+ cursor: grab;
+ transition: all 0.3s;
+ position: relative;
+ color: #606266;
+ font-size: 14px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+ .node-item:hover {
+ box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
+ border-color: #409eff;
+ }
+ .node-item.active {
+ background-color: #ecf5ff;
+ border-color: #409eff;
+ color: #409eff;
+ }
+
+ /* Specific styles for Station nodes */
+ .station-node {
+ border-left: 4px solid #67c23a;
+ }
+
+ /* Specific styles for Device nodes */
+ .device-node {
+ border-left: 4px solid #e6a23c;
+ }
+
+ /* Middle canvas area */
+ .canvas-area {
+ flex: 1;
+ position: relative;
+ margin-right: 300px; /* Space for right column */
+ background-color: #fafafa;
+ overflow: hidden;
+ }
+
+ svg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none; /* Let clicks pass through */
+ }
+
+ svg line {
+ stroke: #909399;
+ stroke-width: 2;
+ cursor: pointer;
+ pointer-events: stroke; /* Capture events on the stroke itself */
+ transition: stroke-width 0.2s, stroke 0.2s;
+ }
+ svg line:hover {
+ stroke: #f56c6c;
+ stroke-width: 4;
+ }
+
+ /* Drag ghost image customization if needed */
+ .dragging {
+ opacity: 0.5;
+ }
+
+ /* Connection Point Indicators */
+ .connector {
+ width: 10px;
+ height: 10px;
+ background-color: #909399;
+ border-radius: 50%;
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+ .station-node .connector {
+ right: -16px; /* Position outside the box */
+ display: none; /* Only show when necessary or active */
+ }
+ .device-node .connector {
+ left: -16px;
+ display: none;
+ }
+
+ .toolbar {
+ padding: 10px 0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+ </style>
+</head>
+<body>
+
+<div id="app" v-cloak>
+ <el-card class="main-card" :body-style="{ padding: '0px', display: 'flex', flexDirection: 'column', height: '100%' }">
+ <div slot="header" class="clearfix">
+ <span style="font-weight: bold; font-size: 16px;">绔欑偣涓庡爢鍨涙満浠诲姟鍏宠仈閰嶇疆</span>
+ <div style="float: right;">
+ <el-button type="primary" icon="el-icon-check" size="small" @click="saveData" :loading="saving">淇濆瓨閰嶇疆</el-button>
+ <el-button icon="el-icon-refresh" size="small" @click="loadData">鍒锋柊</el-button>
+ </div>
+ </div>
+
+ <div class="toolbar" style="padding: 10px 20px; border-bottom: 1px solid #ebeef5;">
+ <el-alert
+ title="鎿嶄綔璇存槑锛氭嫋鎷藉乏渚х殑銆愮珯鐐广�戝埌鍙充晶鐨勩�愯澶囥�戜笂鍗冲彲寤虹珛鍏宠仈銆傜偣鍑昏繛绾垮彲鍒犻櫎鍏宠仈銆�"
+ type="info"
+ show-icon
+ :closable="false">
+ </el-alert>
+ </div>
+
+ <div class="flow-container" ref="flowContainer">
+ <!-- Left: Stations -->
+ <div class="column">
+ <div class="column-header">绔欑偣鍒楄〃 ({{ stations.length }})</div>
+ <div class="list-container" ref="stationList">
+ <div
+ v-for="item in stations"
+ :key="'s-' + item.stationId"
+ :id="'station-' + item.stationId"
+ class="node-item station-node"
+ :class="{ active: isStationConnected(item.stationId) }"
+ draggable="true"
+ @dragstart="onDragStart($event, item, 'station')"
+ @dragend="onDragEnd"
+ >
+ <span>{{ item.stationId }}</span>
+ </div>
+ </div>
+ </div>
+
+ <!-- Middle: Canvas -->
+ <div class="canvas-area">
+ <svg width="100%" height="100%">
+ <line
+ v-for="(link, index) in lines"
+ :key="index"
+ :x1="link.x1"
+ :y1="link.y1"
+ :x2="link.x2"
+ :y2="link.y2"
+ @click="confirmDelete(index)"
+ >
+ <title>鐐瑰嚮鍒犻櫎鍏宠仈: 绔欑偣 {{ link.stationId }} -> 璁惧 {{ link.deviceNo }}</title>
+ </line>
+
+ <!-- Dragging Line Preview -->
+ <line
+ v-if="draggingLine"
+ :x1="draggingLine.x1"
+ :y1="draggingLine.y1"
+ :x2="draggingLine.x2"
+ :y2="draggingLine.y2"
+ stroke="#409eff"
+ stroke-dasharray="5,5"
+ stroke-width="2"
+ />
+ </svg>
+ </div>
+
+ <!-- Right: Devices -->
+ <div class="column right">
+ <div class="column-header">鍫嗗灈鏈鸿澶� ({{ devices.length }})</div>
+ <div class="list-container" ref="deviceList">
+ <div
+ v-for="item in devices"
+ :key="'d-' + item.type + '-' + item.deviceNo"
+ :id="'device-' + item.type + '-' + item.deviceNo"
+ class="node-item device-node"
+ :class="{ active: isDeviceConnected(item.deviceNo, item.type) }"
+ @dragover.prevent
+ @drop="onDrop($event, item)"
+ >
+ <span>{{ item.name }}</span>
+ <el-tag size="mini" :type="item.type === 'Crn' ? 'success' : 'warning'">{{ item.type === 'Crn' ? '鍫嗗灈鏈�' : '鍙屽伐浣�' }}</el-tag>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-card>
+</div>
+
+<!-- Libraries -->
+<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
+<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
+<script type="text/javascript" src="../../static/vue/element/element.js"></script>
+<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
+<script type="text/javascript" src="../../static/js/basStationDevice/basStationDevice.js"></script>
+</body>
+</html>
--
Gitblit v1.9.1