From c397643ce84b62690c814fc1dbca04a110647d1a Mon Sep 17 00:00:00 2001
From: Junjie <DELL@qq.com>
Date: 星期一, 12 一月 2026 13:02:19 +0800
Subject: [PATCH] #

---
 src/main/java/com/zy/asrs/mapper/BasOutAreaMapper.java                   |   11 +
 src/main/java/com/zy/asrs/service/impl/BasOutAreaServiceImpl.java        |   11 +
 src/main/webapp/views/basOutStationArea/basOutStationArea.html           |   86 +++++++++
 src/main/resources/mapper/BasOutStationAreaMapper.xml                    |    9 +
 src/main/resources/docs/WCS外部HTTP API接口V1.4.docx                         |    0 
 src/main/java/com/zy/asrs/service/impl/BasOutStationAreaServiceImpl.java |   11 +
 src/main/resources/mapper/BasOutAreaMapper.xml                           |    9 +
 src/main/java/com/zy/asrs/domain/param/CreateOutTaskParam.java           |    9 
 src/main/java/com/zy/asrs/service/BasOutAreaService.java                 |    7 
 src/main/java/com/zy/asrs/controller/BasOutStationAreaController.java    |  106 +++++++++++
 src/main/java/com/zy/asrs/mapper/BasOutStationAreaMapper.java            |   11 +
 src/main/java/com/zy/asrs/entity/BasOutArea.java                         |   24 ++
 src/main/webapp/static/js/basOutStationArea/basOutStationArea.js         |  167 ++++++++++++++++++
 src/main/java/com/zy/asrs/service/BasOutStationAreaService.java          |    7 
 src/main/java/com/zy/asrs/entity/BasOutStationArea.java                  |   24 ++
 src/main/java/com/zy/common/service/CommonService.java                   |   23 ++
 16 files changed, 510 insertions(+), 5 deletions(-)

diff --git a/src/main/java/com/zy/asrs/controller/BasOutStationAreaController.java b/src/main/java/com/zy/asrs/controller/BasOutStationAreaController.java
new file mode 100644
index 0000000..8716cc3
--- /dev/null
+++ b/src/main/java/com/zy/asrs/controller/BasOutStationAreaController.java
@@ -0,0 +1,106 @@
+package com.zy.asrs.controller;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.core.annotations.ManagerAuth;
+import com.core.common.R;
+import com.zy.asrs.entity.BasDevp;
+import com.zy.asrs.entity.BasOutArea;
+import com.zy.asrs.entity.BasOutStationArea;
+import com.zy.asrs.entity.BasStation;
+import com.zy.asrs.service.BasDevpService;
+import com.zy.asrs.service.BasOutAreaService;
+import com.zy.asrs.service.BasOutStationAreaService;
+import com.zy.asrs.service.BasStationService;
+import com.zy.common.web.BaseController;
+import com.zy.core.model.StationObjModel;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/basOutStationArea")
+public class BasOutStationAreaController extends BaseController {
+
+    @Autowired
+    private BasOutAreaService basOutAreaService;
+    @Autowired
+    private BasOutStationAreaService basOutStationAreaService;
+    @Autowired
+    private BasDevpService basDevpService;
+    @Autowired
+    private BasStationService basStationService;
+
+    @RequestMapping("/data/auth")
+    @ManagerAuth
+    public R data() {
+        Map<String, Object> data = new HashMap<>();
+
+        List<Integer> stationIds = new ArrayList<>();
+        List<BasDevp> devps = basDevpService.selectList(new EntityWrapper<BasDevp>().eq("status", 1));
+        for (BasDevp devp : devps) {
+            for (StationObjModel stationObjModel : devp.getOutStationList$()) {
+                stationIds.add(stationObjModel.getStationId());
+            }
+        }
+        List<BasStation> stations = stationIds.isEmpty()
+                ? new ArrayList<>()
+                : basStationService.selectList(new EntityWrapper<BasStation>().in("station_id", stationIds));
+        data.put("stations", stations);
+
+        List<BasOutArea> areas = basOutAreaService.selectList(new EntityWrapper<>());
+        data.put("areas", areas);
+
+        List<BasOutStationArea> relations = basOutStationAreaService.selectList(new EntityWrapper<>());
+        data.put("relations", relations);
+
+        return R.ok(data);
+    }
+
+    @RequestMapping("/save/auth")
+    @ManagerAuth
+    public R save(@RequestBody JSONObject payload) {
+        JSONArray areas = payload.getJSONArray("areas");
+        JSONArray relations = payload.getJSONArray("relations");
+
+        basOutAreaService.delete(new EntityWrapper<>());
+        basOutStationAreaService.delete(new EntityWrapper<>());
+
+        if (areas != null && !areas.isEmpty()) {
+            List<BasOutArea> areaList = new ArrayList<>();
+            for (int i = 0; i < areas.size(); i++) {
+                JSONObject o = areas.getJSONObject(i);
+                BasOutArea a = new BasOutArea();
+                a.setAreaCode(o.getString("areaCode"));
+                a.setAreaName(o.getString("areaName"));
+                areaList.add(a);
+            }
+            if (!areaList.isEmpty()) {
+                basOutAreaService.insertBatch(areaList);
+            }
+        }
+
+        if (relations != null && !relations.isEmpty()) {
+            List<BasOutStationArea> relList = new ArrayList<>();
+            for (int i = 0; i < relations.size(); i++) {
+                JSONObject o = relations.getJSONObject(i);
+                BasOutStationArea r = new BasOutStationArea();
+                r.setStationId(o.getInteger("stationId"));
+                r.setAreaCode(o.getString("areaCode"));
+                relList.add(r);
+            }
+            if (!relList.isEmpty()) {
+                basOutStationAreaService.insertBatch(relList);
+            }
+        }
+
+        return R.ok();
+    }
+}
diff --git a/src/main/java/com/zy/asrs/domain/param/CreateOutTaskParam.java b/src/main/java/com/zy/asrs/domain/param/CreateOutTaskParam.java
index 84e6be3..a7ad9d5 100644
--- a/src/main/java/com/zy/asrs/domain/param/CreateOutTaskParam.java
+++ b/src/main/java/com/zy/asrs/domain/param/CreateOutTaskParam.java
@@ -8,13 +8,16 @@
     //wms浠诲姟鍙�
     private String taskNo;
 
-    //鐩爣绔�
-    private Integer staNo;
-
     //鍑哄簱搴撲綅
     private String locNo;
 
     //浠诲姟浼樺厛绾�
     private Integer taskPri;
 
+    //鐩爣绔�
+    private Integer staNo;
+
+    //鍑哄簱鍖哄煙
+    private String outArea;
+
 }
diff --git a/src/main/java/com/zy/asrs/entity/BasOutArea.java b/src/main/java/com/zy/asrs/entity/BasOutArea.java
new file mode 100644
index 0000000..7578ac4
--- /dev/null
+++ b/src/main/java/com/zy/asrs/entity/BasOutArea.java
@@ -0,0 +1,24 @@
+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 lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+@TableName("asr_bas_out_area")
+public class BasOutArea implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("area_code")
+    private String areaCode;
+
+    @TableField("area_name")
+    private String areaName;
+}
diff --git a/src/main/java/com/zy/asrs/entity/BasOutStationArea.java b/src/main/java/com/zy/asrs/entity/BasOutStationArea.java
new file mode 100644
index 0000000..1ff9b6f
--- /dev/null
+++ b/src/main/java/com/zy/asrs/entity/BasOutStationArea.java
@@ -0,0 +1,24 @@
+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 lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+@TableName("asr_bas_out_station_area")
+public class BasOutStationArea implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("station_id")
+    private Integer stationId;
+
+    @TableField("area_code")
+    private String areaCode;
+}
diff --git a/src/main/java/com/zy/asrs/mapper/BasOutAreaMapper.java b/src/main/java/com/zy/asrs/mapper/BasOutAreaMapper.java
new file mode 100644
index 0000000..f851741
--- /dev/null
+++ b/src/main/java/com/zy/asrs/mapper/BasOutAreaMapper.java
@@ -0,0 +1,11 @@
+package com.zy.asrs.mapper;
+
+import com.baomidou.mybatisplus.mapper.BaseMapper;
+import com.zy.asrs.entity.BasOutArea;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface BasOutAreaMapper extends BaseMapper<BasOutArea> {
+}
diff --git a/src/main/java/com/zy/asrs/mapper/BasOutStationAreaMapper.java b/src/main/java/com/zy/asrs/mapper/BasOutStationAreaMapper.java
new file mode 100644
index 0000000..df46427
--- /dev/null
+++ b/src/main/java/com/zy/asrs/mapper/BasOutStationAreaMapper.java
@@ -0,0 +1,11 @@
+package com.zy.asrs.mapper;
+
+import com.baomidou.mybatisplus.mapper.BaseMapper;
+import com.zy.asrs.entity.BasOutStationArea;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface BasOutStationAreaMapper extends BaseMapper<BasOutStationArea> {
+}
diff --git a/src/main/java/com/zy/asrs/service/BasOutAreaService.java b/src/main/java/com/zy/asrs/service/BasOutAreaService.java
new file mode 100644
index 0000000..40e364d
--- /dev/null
+++ b/src/main/java/com/zy/asrs/service/BasOutAreaService.java
@@ -0,0 +1,7 @@
+package com.zy.asrs.service;
+
+import com.baomidou.mybatisplus.service.IService;
+import com.zy.asrs.entity.BasOutArea;
+
+public interface BasOutAreaService extends IService<BasOutArea> {
+}
diff --git a/src/main/java/com/zy/asrs/service/BasOutStationAreaService.java b/src/main/java/com/zy/asrs/service/BasOutStationAreaService.java
new file mode 100644
index 0000000..b863717
--- /dev/null
+++ b/src/main/java/com/zy/asrs/service/BasOutStationAreaService.java
@@ -0,0 +1,7 @@
+package com.zy.asrs.service;
+
+import com.baomidou.mybatisplus.service.IService;
+import com.zy.asrs.entity.BasOutStationArea;
+
+public interface BasOutStationAreaService extends IService<BasOutStationArea> {
+}
diff --git a/src/main/java/com/zy/asrs/service/impl/BasOutAreaServiceImpl.java b/src/main/java/com/zy/asrs/service/impl/BasOutAreaServiceImpl.java
new file mode 100644
index 0000000..176429d
--- /dev/null
+++ b/src/main/java/com/zy/asrs/service/impl/BasOutAreaServiceImpl.java
@@ -0,0 +1,11 @@
+package com.zy.asrs.service.impl;
+
+import com.baomidou.mybatisplus.service.impl.ServiceImpl;
+import com.zy.asrs.entity.BasOutArea;
+import com.zy.asrs.mapper.BasOutAreaMapper;
+import com.zy.asrs.service.BasOutAreaService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class BasOutAreaServiceImpl extends ServiceImpl<BasOutAreaMapper, BasOutArea> implements BasOutAreaService {
+}
diff --git a/src/main/java/com/zy/asrs/service/impl/BasOutStationAreaServiceImpl.java b/src/main/java/com/zy/asrs/service/impl/BasOutStationAreaServiceImpl.java
new file mode 100644
index 0000000..31858d2
--- /dev/null
+++ b/src/main/java/com/zy/asrs/service/impl/BasOutStationAreaServiceImpl.java
@@ -0,0 +1,11 @@
+package com.zy.asrs.service.impl;
+
+import com.baomidou.mybatisplus.service.impl.ServiceImpl;
+import com.zy.asrs.entity.BasOutStationArea;
+import com.zy.asrs.mapper.BasOutStationAreaMapper;
+import com.zy.asrs.service.BasOutStationAreaService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class BasOutStationAreaServiceImpl extends ServiceImpl<BasOutStationAreaMapper, BasOutStationArea> implements BasOutStationAreaService {
+}
diff --git a/src/main/java/com/zy/common/service/CommonService.java b/src/main/java/com/zy/common/service/CommonService.java
index 8959e59..9420bc0 100644
--- a/src/main/java/com/zy/common/service/CommonService.java
+++ b/src/main/java/com/zy/common/service/CommonService.java
@@ -24,6 +24,7 @@
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.Random;
 
 @Slf4j
 @Service
@@ -43,6 +44,8 @@
     private NavigateUtils navigateUtils;
     @Autowired
     private RedisUtil redisUtil;
+    @Autowired
+    private BasOutStationAreaService basOutStationAreaService;
 
     /**
      * 鐢熸垚宸ヤ綔鍙�
@@ -302,7 +305,23 @@
         }
         Integer crnNo = findCrnResult.getCrnNo();
 
-        Integer sourceStationId = this.findOutStationId(findCrnResult, param.getStaNo());
+        Integer staNo = param.getStaNo();
+        String outArea = param.getOutArea();
+        if(Cools.isEmpty(staNo) && Cools.isEmpty(outArea)) {
+            throw new CoolException("璇蜂紶鍏ュ嚭搴撶珯鐐规垨鍑哄簱鍖哄煙");
+        }
+
+        if(Cools.isEmpty(staNo)) {
+            List<BasOutStationArea> areaList = basOutStationAreaService.selectList(new EntityWrapper<BasOutStationArea>().eq("area_code", outArea));
+            if (areaList.isEmpty()) {
+                throw new CoolException("鍑哄簱鍖哄煙涓嶅瓨鍦�");
+            }
+            int nextInt = new Random().nextInt(areaList.size());
+            BasOutStationArea basOutStationArea = areaList.get(nextInt);
+            staNo = basOutStationArea.getStationId();
+        }
+
+        Integer sourceStationId = this.findOutStationId(findCrnResult, staNo);
         if (sourceStationId == null) {
             throw new CoolException("鏈壘鍒拌緭閫佺洰鏍囩珯鐐瑰彲璧拌璺緞");
         }
@@ -318,7 +337,7 @@
         wrkMast.setIoPri(ioPri);
         wrkMast.setSourceLocNo(locMast.getLocNo()); // 婧愬簱浣�
         wrkMast.setSourceStaNo(sourceStationId);//婧愮珯
-        wrkMast.setStaNo(param.getStaNo());//鐩爣绔�
+        wrkMast.setStaNo(staNo);//鐩爣绔�
         wrkMast.setWmsWrkNo(param.getTaskNo());
         wrkMast.setBarcode(locMast.getBarcode());
         wrkMast.setAppeTime(now);
diff --git "a/src/main/resources/docs/WCS\345\244\226\351\203\250HTTP API\346\216\245\345\217\243V1.4.docx" "b/src/main/resources/docs/WCS\345\244\226\351\203\250HTTP API\346\216\245\345\217\243V1.4.docx"
new file mode 100644
index 0000000..a4e2af4
--- /dev/null
+++ "b/src/main/resources/docs/WCS\345\244\226\351\203\250HTTP API\346\216\245\345\217\243V1.4.docx"
Binary files differ
diff --git a/src/main/resources/mapper/BasOutAreaMapper.xml b/src/main/resources/mapper/BasOutAreaMapper.xml
new file mode 100644
index 0000000..79948ba
--- /dev/null
+++ b/src/main/resources/mapper/BasOutAreaMapper.xml
@@ -0,0 +1,9 @@
+<?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.BasOutAreaMapper">
+    <resultMap id="BaseResultMap" type="com.zy.asrs.entity.BasOutArea">
+        <id column="id" property="id"/>
+        <result column="area_code" property="areaCode"/>
+        <result column="area_name" property="areaName"/>
+    </resultMap>
+</mapper>
diff --git a/src/main/resources/mapper/BasOutStationAreaMapper.xml b/src/main/resources/mapper/BasOutStationAreaMapper.xml
new file mode 100644
index 0000000..9ecaf68
--- /dev/null
+++ b/src/main/resources/mapper/BasOutStationAreaMapper.xml
@@ -0,0 +1,9 @@
+<?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.BasOutStationAreaMapper">
+    <resultMap id="BaseResultMap" type="com.zy.asrs.entity.BasOutStationArea">
+        <id column="id" property="id"/>
+        <result column="station_id" property="stationId"/>
+        <result column="area_code" property="areaCode"/>
+    </resultMap>
+</mapper>
diff --git a/src/main/webapp/static/js/basOutStationArea/basOutStationArea.js b/src/main/webapp/static/js/basOutStationArea/basOutStationArea.js
new file mode 100644
index 0000000..c73f94f
--- /dev/null
+++ b/src/main/webapp/static/js/basOutStationArea/basOutStationArea.js
@@ -0,0 +1,167 @@
+var app = new Vue({
+    el: '#app',
+    data: {
+        stations: [],
+        areas: [],
+        relations: [],
+        lines: [],
+        draggingItem: null,
+        draggingLine: null,
+        saving: false,
+        newAreaCode: '',
+        newAreaName: ''
+    },
+    mounted: function() {
+        this.loadData();
+        window.addEventListener('resize', this.updateLines);
+        this.$nextTick(function() {
+            if (this.$refs.stationList) this.$refs.stationList.addEventListener('scroll', this.updateLines);
+            if (this.$refs.areaList) this.$refs.areaList.addEventListener('scroll', this.updateLines);
+        }.bind(this));
+    },
+    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 + '/basOutStationArea/data/auth',
+                headers: { 'token': localStorage.getItem('token') },
+                method: 'GET',
+                success: function(res) {
+                    loading.close();
+                    if (res.code === 200) {
+                        that.stations = res.data.stations || [];
+                        that.areas = res.data.areas || [];
+                        that.relations = res.data.relations || [];
+                        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;
+            var payload = {
+                areas: this.areas,
+                relations: this.relations
+            };
+            $.ajax({
+                url: baseUrl + '/basOutStationArea/save/auth',
+                type: 'POST',
+                headers: { 'token': localStorage.getItem('token') },
+                contentType: 'application/json',
+                data: JSON.stringify(payload),
+                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('淇濆瓨璇锋眰寮傚父');
+                }
+            });
+        },
+        addArea: function() {
+            var code = (this.newAreaCode || '').trim();
+            var name = (this.newAreaName || '').trim();
+            if (!code || !name) {
+                this.$message.warning('璇疯緭鍏ュ尯鍩熺紪鐮佸拰鍚嶇О');
+                return;
+            }
+            var exists = this.areas.some(function(a) { return a.areaCode === code; });
+            if (exists) {
+                this.$message.warning('鍖哄煙宸插瓨鍦�');
+                return;
+            }
+            this.areas.push({ areaCode: code, areaName: name });
+            this.newAreaCode = '';
+            this.newAreaName = '';
+            this.$nextTick(this.updateLines);
+        },
+        removeArea: function(areaCode) {
+            this.areas = this.areas.filter(function(a) { return a.areaCode !== areaCode; });
+            this.relations = this.relations.filter(function(r) { return r.areaCode !== areaCode; });
+            this.$nextTick(this.updateLines);
+        },
+        isStationBound: function(stationId) {
+            return this.relations.some(function(r) { return r.stationId == stationId; });
+        },
+        isAreaBound: function(areaCode) {
+            return this.relations.some(function(r) { return r.areaCode == areaCode; });
+        },
+        onDragStart: function(event, item, type) {
+            this.draggingItem = { item: item, type: type };
+            event.dataTransfer.effectAllowed = 'link';
+        },
+        onDragEnd: function() {
+            this.draggingItem = null;
+            this.draggingLine = null;
+        },
+        onDrop: function(event, targetArea) {
+            if (!this.draggingItem || this.draggingItem.type !== 'station') return;
+            var station = this.draggingItem.item;
+            var exists = this.relations.some(function(r) {
+                return r.stationId == station.stationId && r.areaCode == targetArea.areaCode;
+            });
+            if (exists) {
+                this.$message.warning('璇ョ粦瀹氬凡瀛樺湪');
+                return;
+            }
+            this.relations.push({
+                stationId: station.stationId,
+                areaCode: targetArea.areaCode
+            });
+            this.$message.success('宸插缓绔嬬粦瀹�: 绔欑偣 ' + station.stationId + ' -> 鍖哄煙 ' + (targetArea.areaName || targetArea.areaCode));
+            this.$nextTick(this.updateLines);
+        },
+        confirmDelete: function(index) {
+            var rel = this.lines[index] && this.lines[index].relation;
+            if (!rel) return;
+            this.relations = this.relations.filter(function(r) {
+                return !(r.stationId == rel.stationId && r.areaCode == rel.areaCode);
+            });
+            this.$nextTick(this.updateLines);
+        },
+        updateLines: function() {
+            var svg = document.querySelector('svg');
+            if (!svg) return;
+            var svgRect = svg.getBoundingClientRect();
+            var newLines = [];
+            this.relations.forEach(function(rel) {
+                var sDom = document.getElementById('station-' + rel.stationId);
+                var aDom = document.getElementById('area-' + rel.areaCode);
+                if (sDom && aDom) {
+                    var sRect = sDom.getBoundingClientRect();
+                    var aRect = aDom.getBoundingClientRect();
+                    var x1 = sRect.right - svgRect.left;
+                    var y1 = sRect.top + sRect.height / 2 - svgRect.top;
+                    var x2 = aRect.left - svgRect.left;
+                    var y2 = aRect.top + aRect.height / 2 - svgRect.top;
+                    newLines.push({
+                        x1: x1, y1: y1, x2: x2, y2: y2,
+                        relation: rel,
+                        stationId: rel.stationId,
+                        areaCode: rel.areaCode
+                    });
+                }
+            });
+            this.lines = newLines;
+        }
+    }
+});
diff --git a/src/main/webapp/views/basOutStationArea/basOutStationArea.html b/src/main/webapp/views/basOutStationArea/basOutStationArea.html
new file mode 100644
index 0000000..5df0d5a
--- /dev/null
+++ b/src/main/webapp/views/basOutStationArea/basOutStationArea.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<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">
+    <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; }
+        .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; }
+        .station-node { border-left: 4px solid #67c23a; }
+        .area-node { border-left: 4px solid #e6a23c; }
+        .canvas-area { flex: 1; position: relative; margin-right: 300px; background-color: #fafafa; overflow: hidden; }
+        svg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
+        svg line { stroke: #909399; stroke-width: 2; cursor: pointer; pointer-events: stroke; transition: stroke-width 0.2s, stroke 0.2s; }
+        svg line:hover { stroke: #f56c6c; stroke-width: 4; }
+        .toolbar { padding: 10px 0; display: flex; justify-content: space-between; align-items: center; }
+        .area-toolbar { padding: 10px; border-bottom: 1px solid #ebeef5; }
+    </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">
+            <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: isStationBound(item.stationId) }" draggable="true" @dragstart="onDragStart($event, item, 'station')" @dragend="onDragEnd">
+                        <span>{{ item.stationId }}</span>
+                    </div>
+                </div>
+            </div>
+            <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.areaCode }}</title>
+                    </line>
+                    <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>
+            <div class="column right">
+                <div class="column-header">鍑哄簱鍖哄煙 ({{ areas.length }})</div>
+                <div class="area-toolbar">
+                    <el-input size="small" placeholder="鍖哄煙缂栫爜" v-model="newAreaCode" style="width: 140px; margin-right: 8px;"></el-input>
+                    <el-input size="small" placeholder="鍖哄煙鍚嶇О" v-model="newAreaName" style="width: 140px; margin-right: 8px;"></el-input>
+                    <el-button size="small" type="primary" icon="el-icon-plus" @click="addArea">鏂板鍖哄煙</el-button>
+                </div>
+                <div class="list-container" ref="areaList">
+                    <div v-for="item in areas" :key="'a-' + item.areaCode" :id="'area-' + item.areaCode" class="node-item area-node" :class="{ active: isAreaBound(item.areaCode) }" @dragover.prevent @drop="onDrop($event, item)">
+                        <span>{{ item.areaCode }} - {{ item.areaName }}</span>
+                        <el-button type="text" size="mini" @click="removeArea(item.areaCode)">鍒犻櫎</el-button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </el-card>
+</div>
+<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/basOutStationArea/basOutStationArea.js"></script>
+</body>
+</html>

--
Gitblit v1.9.1