Jelajahi Sumber

新增开关布局模块

pm 2 bulan lalu
induk
melakukan
2118bbf954

+ 59 - 0
src/api/mes/switchmanagement/switchmanagement.js

@@ -0,0 +1,59 @@
+import request from '@/utils/request'
+
+// 查询开关布局列表
+export function getIsLotoSwitchMapPage(query) {
+  return request({
+    url: '/iscs/switchmap/getIsLotoSwitchMapPage',
+    method: 'get',
+    params: query
+  })
+}
+// 解绑的数据集合
+export function updatePointsBindingSwitchMap(data) {
+  return request({
+    url: '/iscs/switchmap/updatePointsBindingSwitchMap',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询开关布局详细
+export function selectIsLotoSwitchMapById(switchMapId) {
+  return request({
+    url: '/iscs/switchmap/selectIsLotoSwitchMapById?switchMapId=' + switchMapId,
+    method: 'get'
+  })
+}
+// 查询开关布局地图转换数据
+export function selectLotoMapById(switchMapId) {
+  return request({
+    url: '/iscs/station/selectLotoMapById?switchMapId=' + switchMapId,
+    method: 'get'
+  })
+}
+
+// 新增开关布局
+export function insertIsLotoSwitchMap(data) {
+  return request({
+    url: '/iscs/switchmap/insertIsLotoSwitchMap',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改开关布局
+export function updateIsLotoSwitchMap(data) {
+  return request({
+    url: '/iscs/switchmap/updateIsLotoSwitchMap',
+    method: 'post',
+    data: data
+  })
+}
+
+// 删除开关布局
+export function deleteIsLotoSwitchMapBySwitchMapIds(switchMapIds) {
+  return request({
+    url: '/iscs/switchmap/deleteIsLotoSwitchMapBySwitchMapIds?switchMapIds=' + switchMapIds,
+    method: 'post'
+  })
+}

+ 15 - 0
src/router/index.js

@@ -263,6 +263,21 @@ export const dynamicRoutes = [
       }
     ]
   },
+  // 开关分布详情
+  {
+    path: '/mes/dv/switchmanagement/index',
+    component: Layout,
+    hidden: true,
+    permissions: ['iscs:switch:list'],
+    children: [
+      {
+        path: 'LookDetail',
+        component: () => import('@/views/mes/dv/switchmanagement/LookDetail'),
+        name: 'LookDetail',
+        meta: { title: '预览', activeMenu: '/mes/dv/switchmanagement/LookDetail' }
+      }
+    ]
+  },
   // 硬件锁柜管理详情
   {
     path: '/mes/hw/lockCabinet',

+ 44 - 8
src/views/mes/dv/segregationpoint/index.vue

@@ -227,6 +227,8 @@
       </el-table-column>
       <el-table-column label="锁定站" align="center" prop="lotoName">
       </el-table-column>
+      <el-table-column label="布局地图" align="center" prop="switchMapName">
+      </el-table-column>
       <el-table-column label="隔离点序列号" align="center" prop="pointSerialNumber">
       </el-table-column>
       <el-table-column label="作用" align="center" prop="remark">
@@ -411,14 +413,14 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="隔离点NFC" prop="pointNfc" >
+            <el-form-item label="隔离点NFC" prop="rfidId" >
 <!--              <el-input-->
 <!--                style="width: 300px"-->
 <!--                v-model="form.pointNfc"-->
 <!--                placeholder="请输入隔离点NFC"-->
 <!--                maxlength="16"-->
 <!--              />-->
-              <el-select v-model="form.pointNfc" style="width: 300px">
+              <el-select v-model="form.rfidId" style="width: 300px">
                 <el-option v-for="dict in this.RfidTokenData" :label="dict.label" :value="dict.value"></el-option>
               </el-select>
             </el-form-item>
@@ -455,14 +457,22 @@
 
           </el-col>
           <el-col :span="11">
-            <el-form-item label="作用" prop="remark">
-              <el-input
+            <el-form-item label="布局地图" prop="switchMapId">
+              <el-select
                 style="width: 300px"
-                v-model="form.remark"
-                placeholder="请输入作用"
-              />
+                v-model="form.switchMapId"
+                placeholder="请选择布局地图"
+              >
+                <el-option
+                  v-for="dict in switchMapOptions"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
             </el-form-item>
 
+
           </el-col>
           <!--          <el-col :span="11">-->
           <!--            <el-form-item label="挂锁类型" prop="lockTypeId">-->
@@ -525,6 +535,17 @@
           </el-form-item>
         </el-col>
         </el-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="作用" prop="remark">
+              <el-input
+                style="width: 300px"
+                v-model="form.remark"
+                placeholder="请输入作用"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
         <el-row>
           <el-col :span="12">
             <el-form-item label="隔离点图标" prop="pointIcon">
@@ -561,6 +582,7 @@
             </el-form-item>
           </el-col>
         </el-row>
+
         <!--        <el-row>-->
         <!--          <el-col :span="8">-->
         <!--            <el-form-item label="挂锁类型图" prop="lockTypeImg">-->
@@ -616,6 +638,7 @@ import { listWorkarea } from '@/api/mes/wa/workarea'
 import { listLockType } from '@/api/mes/locktype/locktype'
 import { listPadLockTypeAPI } from '@/api/mes/padLockType/padLockType'
 import { listLoto } from '@/api/mes/lotoStation/lotoStation'
+import { getIsLotoSwitchMapPage } from '@/api/mes/switchmanagement/switchmanagement'
 import { listMarsDept } from '@/api/system/marsdept'
 import { listTechnology } from '@/api/system/machinery'
 import { getIsSystemAttributeByKey, selectIsSystemAttributeById } from '@/api/system/configuration'
@@ -710,6 +733,7 @@ export default {
       padLockTypeOptions: [],//挂锁类型下拉
 
       lotoOptions: [],//电柜下拉
+      switchMapOptions: [],//布局地图下拉
       // 表单校验
       rules: {
         pointCode: [
@@ -733,8 +757,11 @@ export default {
         lockTypeId: [
           { required: true, message: '挂锁类型不能为空', trigger: 'blur' }
         ],
-        pointNfc: [
+        rfidId: [
           { required: true, message: '隔离点NFC不能为空', trigger: 'blur' }
+        ],
+        switchMapId:[
+          {required: true, message: '布局地图不能为空', trigger: 'blur'}
         ]
       },
       imageMap: {
@@ -865,6 +892,15 @@ export default {
           }
         })
       })
+    //   所属开关布局地图 switchMapOptions
+      getIsLotoSwitchMapPage(data).then(response => {
+        this.switchMapOptions= response.data.records.map((item) => {
+          return {
+            value: item.switchMapId,
+            label: item.switchMapName
+          }
+        })
+      })
     },
     /** 转换部门数据结构 */
     normalizer(node) {

+ 55 - 0
src/views/mes/dv/switchmanagement/LookDetail.vue

@@ -0,0 +1,55 @@
+<template>
+  <div class="container">
+    <el-radio-group v-model="tabPosition" style="margin: 5px">
+      <el-radio-button label="first">开关布局</el-radio-button>
+      <el-radio-button label="second">开关状态</el-radio-button>
+      <el-radio-button label="third">开关点位列表</el-radio-button>
+    </el-radio-group>
+
+    <component :is="currentComponent" :switchMapId="this.switchMapId"/>
+  </div>
+</template>
+
+<script>
+import MapData from "./MapData";
+import PointList from "./PointList";
+import SwitchStatus from "./SwitchStatus";
+
+export default {
+  components: {
+    MapData,
+    PointList,
+    SwitchStatus
+  },
+  data() {
+    return {
+      tabPosition: 'first',
+      switchMapId: '',
+    }
+  },
+  computed: {
+    currentComponent() {
+      const components = {
+        third: 'PointList',
+        first: 'MapData',
+        second: 'SwitchStatus'
+      }
+      return components[this.tabPosition]
+    }
+  },
+  mounted() {
+    this.switchMapId = this.$route.query.switchMapId;
+    console.log(this.switchMapId,'this.switchMapId');
+  },
+  methods: {}
+}
+</script>
+
+<style scoped>
+.container {
+  width: 100%;
+  height: 700px;
+  padding: 20px;
+  box-sizing: border-box;
+}
+</style>

+ 1166 - 0
src/views/mes/dv/switchmanagement/MapData.vue

@@ -0,0 +1,1166 @@
+<template>
+  <div class="mapdata">
+    <div id="container" ref="container" style="width: 1600px"></div>
+    <div class="left">
+      <div
+        class="bottombtn"
+        style="width: 100%; height: 35px; padding: 10px;display: flex;flex-direction: column;"
+      >
+        <!--        <el-button-->
+        <!--          v-no-more-click-->
+        <!--          @click="close"-->
+        <!--          type="primary"-->
+        <!--          icon="el-icon-close"-->
+        <!--          style="align-self: flex-end;margin-top: 10px"-->
+        <!--        >关闭-->
+        <!--        </el-button>-->
+
+        <el-button
+          v-no-more-click
+          @click="save"
+          type="primary"
+          icon="el-icon-check"
+          style="align-self: flex-end;margin-top: 10px"
+        >保存
+        </el-button>
+
+        <el-button
+          v-no-more-click
+          @click="reset"
+          type="primary"
+          icon="el-icon-setting"
+          style="align-self: flex-end;margin-top: 10px"
+        >重置
+        </el-button>
+
+      </div>
+    </div>
+
+
+  </div>
+</template>
+
+<script>
+import Konva from 'konva'
+import {
+  selectLotoMapById,
+  selectIsLotoSwitchMapById,
+  updateIsLotoSwitchMap,
+  updatePointsBindingSwitchMap
+} from '@/api/mes/switchmanagement/switchmanagement'
+import { getIsIsolationPointPage } from '@/api/mes/spm/segregationPoint'
+import { getIsMapPointPage, selectIsMapPointById, updateMapPointList } from '@/api/system/mappoint'
+import { selectIsMapById } from '@/api/system/mapconfig'
+
+export default {
+  name: 'KonvaExample',
+  data() {
+    return {
+      stage: null,
+      layer: null,
+      selectedStates: [], // 用于存储每个元素的选中状态
+      selectedText: [], // 用于存储未选中的元素文本
+      rects: [], // 白色rect合集
+      texts: [], // 白色text合集
+      redrects: [], // 红色rect合集
+      redtexts: [], // 白色text合集
+      value: '',
+      form: {}, //拿到单个数据
+      originData: null, //原始数据
+      filterData: null, //用来过滤掉已经渲染出来的隔离点
+      leftPoints: [], //绑定的但未指定位置的集合
+      orgLeftPoints: [], //原始左边数据
+      rightPoints: [], //解绑的数据集合
+      orgRightPoints: [], //原始右边数据
+      groups: [], //组移动数据
+      bindingPointIds: [], //存放从未绑定中放入物资柜的数据 id集合
+      unbindPointIds: [], //解绑的数据接口参数 id集合
+      isSave: true,
+      isInitialized: false, // 添加初始化标志
+      imageUrl: '',//获取底图
+      width: '',//底图宽
+      height: '',//底图高
+      x: '',//底图横坐标
+      y: '',//底图纵坐标
+      mapId: null,//地图Id
+      mapType: 3,//地图类型
+      pointList: null,//接口给的所有点位数据
+      bindingPoints: [],//给地图点位界面更新的绑定隔离点
+      movePoints: [],//给地图点位界面更新位置
+      unbindingPoints: []//给地图点位界面更新解绑数据
+
+    }
+  },
+  // watch: {
+  //   bindingPointIds(newVal, oldVal) {
+  //     if (newVal) {
+  //       this.isSave = false;
+  //     }
+  //   },
+  //   unbindPointIds(newVal, oldVal) {
+  //     if (newVal) {
+  //       this.isSave = false;
+  //     }
+  //   },
+  //   // value: {
+  //   //   handler(newVal, oldVal) {
+  //   //     if (this.isInitialized && newVal) {
+  //   //       // 只有在初始化后才监听 value 变化
+  //   //       const parsedValue = JSON.parse(newVal);
+  //   //       console.log(parsedValue, "deep watch for value");
+  //   //       this.isSave = false;
+  //   //     }
+  //   //   },
+  //   //   deep: true,
+  //   // },
+  // },
+
+  created() {
+    // this.getIsIsolationPointPage()
+    this.isInitialized = true
+  },
+  beforeRouteEnter(to, from, next) {
+    next((vm) => {
+      // vm.getIsIsolationPointPage()
+      vm.addPointsToRightPointsBox()
+    })
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.getInfo()
+      this.getIsIsolationPointPage()
+      this.addPointsToRightPointsBox()
+    })
+    console.log(this.$route.query.switchMapId, 'switchMapId')
+  },
+
+  methods: {
+    getInfo() {
+      const switchMapId = this.$route.query.switchMapId
+      selectIsLotoSwitchMapById(switchMapId).then((response) => {
+        console.log(response, '作业区域信息')
+        this.form = response.data
+        this.mapId = response.data.mapId
+
+        // 获取不同底图 如地图或者柜子
+        selectIsMapById(response.data.mapId).then((response) => {
+          console.log(response, '获取底图')
+          if (response.data) {
+            try {
+              this.value = JSON.stringify(response.data.pointList, null, 4)
+
+              this.originData = this.value
+            } catch (err) {
+              console.error(err)
+            }
+          }
+          this.imageUrl = response.data.imageUrl
+          this.width = response.data.width
+          this.height = response.data.height
+          this.x = response.data.x
+          this.y = response.data.y
+          this.pointList = response.data.pointList
+
+          const data = {
+            current: 1,
+            size: -1,
+            switchMapId: this.$route.query.switchMapId
+          }
+          console.log('调用getIsIsolationPointPage之前')
+          getIsIsolationPointPage(data).then((res) => {
+            console.log(res, '调用getIsIsolationPointPage之后')
+            const data1 = res.data.records // 该柜子或地图所有点
+            const data2 = this.pointList   // 该柜子里 json 拿到的点位(已渲染)
+
+            console.log(data1, '该柜子或地图所有点')
+            console.log(data2, '柜子里json拿到的点位')
+
+            // 当前柜子已经存在的点位 id
+            const pointListIds = new Set(this.pointList.map((item) => item.pointId))
+
+            // 过滤掉已经在 pointList 里的点
+            const filterData = data1.filter(item => !pointListIds.has(item.pointId))
+
+            console.log([...pointListIds], '已有点位 ID')
+            console.log(filterData, 'filterData-需要显示在左侧的数据')
+
+            // 左侧需要显示的数据
+            this.leftPoints = filterData.map((item) => {
+              return {
+                pointId: item.pointId,
+                entityId: item.pointId,
+                entityName: item.pointName,
+                pointName: item.pointName,
+                remark: item.remark,
+                prePointId: item.prePointId,
+                pointType: item.pointType,
+                pointTypeName: item.pointTypeName,
+                powerType: item.powerType,
+                powerTypeName: item.powerTypeName,
+                pointIcon: item.pointIcon,
+                status: false,
+                pointPicture: item.pointPicture,
+                mapImg: null,
+                mapId: this.form.mapId,
+                mapType: this.mapType,
+                mapName: '你好4',
+                x: 0,
+                y: 0
+              }
+            })
+
+            // 调用你已有的渲染逻辑
+            this.addPointsToLeftPointsBox(filterData)
+
+            // 保存完整数据
+            this.orgLeftPoints = res.data.records.map((item) => {
+              return {
+                pointId: item.pointId,
+                entityId: item.pointId,
+                entityName: item.pointName,
+                pointName: item.pointName,
+                remark: item.remark,
+                prePointId: item.prePointId,
+                pointType: item.pointType,
+                pointTypeName: item.pointTypeName,
+                powerType: item.powerType,
+                powerTypeName: item.powerTypeName,
+                pointIcon: item.pointIcon,
+                status: false,
+                pointPicture: item.pointPicture,
+                mapImg: null,
+                mapId: this.form.mapId,
+                mapType: this.form.mapType
+              }
+            })
+          })
+
+          this.initKonva()
+        })
+      })
+    },
+
+    // 获取未绑定的所有隔离点
+    getIsIsolationPointPage() {
+
+      //   拿到解绑的隔离点数据
+      const data1 = {
+        current: 1,
+        size: -1,
+        switchMapId: 0
+      }
+      getIsIsolationPointPage(data1).then((res) => {
+        this.rightPoints = res.data.records.map((item) => {
+          return {
+            entityId: item.pointId,
+            entityName: item.pointName,
+            pointId: item.pointId,
+            pointName: item.pointName,
+            remark: item.remark,
+            prePointId: item.prePointId,
+            pointType: item.pointType,
+            pointTypeName: item.pointTypeName,
+            powerType: item.powerType,
+            powerTypeName: item.powerTypeName,
+            pointIcon: item.pointIcon,
+            status: false,
+            pointPicture: item.pointPicture,
+            mapImg: null,
+            mapId: this.mapId,
+            mapType: this.mapType
+          }
+        })
+        this.orgRightPoints = res.data.records.map((item) => {
+          return {
+            entityId: item.pointId,
+            entityName: item.pointName,
+            pointId: item.pointId,
+            pointName: item.pointName,
+            remark: item.remark,
+            prePointId: item.prePointId,
+            pointType: item.pointType,
+            pointTypeName: item.pointTypeName,
+            powerType: item.powerType,
+            powerTypeName: item.powerTypeName,
+            pointIcon: item.pointIcon,
+            status: false,
+            pointPicture: item.pointPicture,
+            mapImg: null,
+            mapId: this.mapId,
+            mapType: this.mapType
+          }
+        })
+      })
+    },
+    // 重置
+    reset() {
+      this.value = this.originData
+      // 清空并重新赋值
+      this.rightPoints = JSON.parse(JSON.stringify(this.orgRightPoints)) // 深拷贝
+      this.leftPoints = JSON.parse(JSON.stringify(this.orgLeftPoints)) // 深拷贝
+      this.initKonva() // 重新初始化 Konva
+    },
+
+    close() {
+      // this.$router.push('/mes/dv/lotoStation')
+      this.getInfo()
+      // this.initKonva()
+    },
+    save() {
+      this.$confirm('请确认是否保存修改内容', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(() => {
+          // 校验 this.value 是否为有效的 JSON
+          if (this.isJson(this.value)) {
+            const mapData =
+              typeof this.value === 'string'
+                ? this.value
+                : JSON.stringify(this.value)
+            const formData = {
+              ...this.form,
+              map: mapData
+            }
+            console.log(formData, 'map')
+
+            updateIsLotoSwitchMap(formData).then((response) => {
+              console.log(response, '修改车间区域地图')
+              this.$message({
+                type: 'success',
+                message: '保存成功!'
+              })
+            })
+
+            let dataMap = {
+              bindingPoints: this.leftPoints.filter(p => this.bindingPointIds.includes(p.pointId)),
+              movePoints: this.movePoints,
+              unbindingPoints: this.rightPoints
+            }
+            console.log(dataMap, '先拿到数据看看再说')
+            updateMapPointList(dataMap).then((res) => {
+              console.log(res, '拿到的新绑定数据')
+            })
+            const data = {
+              bindingPointIds: this.bindingPointIds,
+              switchMapId: this.$route.query.switchMapId,
+              unbindPointIds: this.unbindPointIds
+            }
+            console.log(data, '解绑与绑定数据参数')
+            updatePointsBindingSwitchMap(data).then((res) => {
+              console.log(res, '解绑接口返回值')
+              this.bindingPointIds = []
+              this.unbindPointIds = []
+            })
+            // this.close()
+          } else {
+            this.$message({
+              type: 'error',
+              message: '地图数据格式不正确,请输入有效的 JSON 格式!'
+            })
+          }
+        })
+        .catch(() => {
+          // 取消操作
+        })
+    },
+
+    // 校验字符串是否为有效的 JSON
+    isJson(str) {
+      try {
+        JSON.parse(str)
+        return true
+      } catch (e) {
+        return false
+      }
+    },
+    initKonva() {
+
+      // 创建舞台
+      this.stage = new Konva.Stage({
+        container: this.$refs.container, // 容器元素
+        width: 1600,
+        height: 860
+      })
+
+      // 创建图层
+      this.layer = new Konva.Layer()
+      // 绘制隔离点等其他内容
+      this.drawGrid(50, 50, '#e0e0e0') // 每个单元格50x50,浅灰色网格
+      // 创建物资柜底图
+      const bgImage = new Image()
+      const imageConfig = {
+        x: this.x,
+        y: this.y,
+        width: this.width,
+        height: this.height,
+        draggable: false
+      }
+      bgImage.src = this.imageUrl
+      bgImage.onload = () => {
+        const knovaImage = new Konva.Image({
+          ...imageConfig,
+          image: bgImage
+        })
+        this.layer.add(knovaImage)
+
+        // 创建背景图并添加到图层
+        // 创建所有隔离点父盒子 放置于网格线上
+        const rightPointsBox = new Konva.Rect({
+          x: 1100,
+          y: 15,
+          width: 200,
+          height: 800,
+          cornerRadius: 5,
+          stroke: 'black',
+          strokeWidth: 2,
+          fill: 'white'
+        })
+        const rightnoLoto = new Konva.Text({
+          x: 1110, // 调整位置以适应网格
+          y: 20, // 调整位置以适应网格
+          text: '未绑定开关分布的点位数据',
+          fontSize: 15,
+          fill: 'black'
+        })
+
+        this.layer.add(rightPointsBox)
+        this.layer.add(rightnoLoto)
+
+        // 将隔离点添加到 rightPointsBox
+        this.addPointsToRightPointsBox(rightPointsBox)
+
+        // 渲染数据
+        const imageSrc = require('@/assets/images/localSetIcon.jpg') // 图片路径
+        this.renderGrid(imageSrc, 6, 3, 450, 100, 120, 100, 50, 50, 60, 25)
+
+        // 将图层添加到舞台
+        this.stage.add(this.layer)
+        this.layer.draw()
+      }
+
+      // 禁止舞台拖拽
+      this.stage.draggable(false)
+    },
+
+    // 绘制无限网格
+    drawGrid(cellWidth, cellHeight, gridColor) {
+      const width = 1600
+      const height = 860
+
+      // 绘制竖线
+      for (let i = 0; i <= width; i += cellWidth) {
+        const verticalLine = new Konva.Line({
+          points: [i, 0, i, height],
+          stroke: gridColor,
+          strokeWidth: 1
+        })
+        this.layer.add(verticalLine)
+      }
+
+      // 绘制横线
+      for (let j = 0; j <= height; j += cellHeight) {
+        const horizontalLine = new Konva.Line({
+          points: [0, j, width, j],
+          stroke: gridColor,
+          strokeWidth: 1
+        })
+        this.layer.add(horizontalLine)
+      }
+
+      // 添加网格坐标文本
+      // for (let row = 0; row < height / cellHeight; row++) {
+      //   for (let col = 0; col < width / cellWidth; col++) {
+      //     const text = new Konva.Text({
+      //       x: col * cellWidth + 5, // 调整位置以适应网格
+      //       y: row * cellHeight + 5, // 调整位置以适应网格
+      //       text: `(${col},${row})`,
+      //       fontSize: 10,
+      //       fill: 'gray',
+      //     });
+      //     this.layer.add(text);
+      //   }
+      // }
+
+      this.layer.draw()
+    },
+
+    // 绘制柜内所有点
+    renderGrid(imageSrc) {
+      this.selectedStates = []
+      this.rects = []
+      this.texts = []
+      this.bgrects = {}
+      this.redrects = []
+      this.redtexts = []
+      this.selectedText = []
+
+      this.rightPoints = []
+      this.leftPoints = []
+      this.bindingPointIds = []
+      this.unbindPointIds = []
+      this.movePoints = []
+
+      const positions = (this.pointList || []).map(item => ({
+        row: item.x,
+        col: item.y,
+        id: item.id,
+        pointId: item.entityId,
+        pointName: item.entityName,
+        entityId: item.entityId,
+        entityName: item.entityName,
+        mapId: item.mapId,
+        mapType: parseInt(item.mapType),
+        x: item.x,
+        y: item.y,
+        remark: item.remark,
+        pointIcon: item.pointIcon,
+        pointPicture: item.pointPicture
+      }))
+      // console.log(positions,'positions')
+
+      const rightBoxBounds = { x: 1100, y: 15, width: 200, height: 800 }
+
+      positions.forEach(pos => {
+        const x = pos.x * 50
+        const y = pos.y * 50
+        const labelText = pos.entityName
+
+        const point = new Image()
+        point.src = pos.pointIcon
+
+        point.onload = () => {
+          const group = new Konva.Group({
+            x: x,
+            y: y,
+            draggable: true
+          })
+
+          const bgrect = new Konva.Rect({ x: -1, y: -5, width: 50, height: 78, cornerRadius: 5, stroke: 'white', strokeWidth: 2, fill: 'white' })
+          const rect = new Konva.Rect({ x: 1, y: -1, width: 45, height: 70, cornerRadius: 5, stroke: 'red', strokeWidth: 2, fill: 'white' })
+          const knovaImage = new Konva.Image({ x: 1, y: 0, image: point, width: 45, height: 45 })
+          const text = new Konva.Text({ x: 8, y: 50, fontSize: 17, text: labelText, fontFamily: 'Calibri', fill: 'red' })
+
+          group.add(bgrect)
+          group.add(rect)
+          group.add(knovaImage)
+          group.add(text)
+          this.layer.add(group)
+
+          group.on('dragend', () => {
+
+            const groupPos = group.getAbsolutePosition()
+            console.log(groupPos,'我在那里')
+            const isInRightBox =
+              groupPos.x >= rightBoxBounds.x &&
+              groupPos.x <= rightBoxBounds.x + rightBoxBounds.width &&
+              groupPos.y >= rightBoxBounds.y &&
+              groupPos.y <= rightBoxBounds.y + rightBoxBounds.height
+
+            let pointData = positions.find(p => p.entityName === labelText)
+            let fromRight = false
+
+            // 如果拖动的点是从右侧来的
+            if (!pointData) {
+              const rightIndex = this.rightPoints.findIndex(p => p.entityName === labelText)
+              if (rightIndex !== -1) {
+                pointData = this.rightPoints.splice(rightIndex, 1)[0]
+                fromRight = true
+              }
+            }
+            if (!pointData) return
+
+            const newRow = Math.max(0, Math.min(Math.round(groupPos.x / 50), Math.floor(1200 / 50) - 1))
+            const newCol = Math.max(0, Math.min(Math.round(groupPos.y / 50), Math.floor(860 / 50) - 1))
+
+            const updatedPoint = {
+              ...pointData,
+              row: newRow,
+              col: newCol,
+              x: newRow,
+              y: newCol,
+              mapId: this.form.mapId,
+              mapType: this.form.mapType
+            }
+
+            if (isInRightBox) {
+              console.log(fromRight,'我走这里')
+              // ✅ 无论是0,0点还是正常点,只要拖到右侧就是解绑
+              if (!this.rightPoints.some(p => p.entityName == updatedPoint.entityName)) {
+                this.rightPoints.push(updatedPoint)
+              }
+              if (!this.unbindPointIds.includes(updatedPoint.entityId)) {
+                this.unbindPointIds.push(updatedPoint.entityId)
+              }
+              // 解绑了,就要从绑定中去掉
+              this.bindingPointIds = this.bindingPointIds.filter(id => id != updatedPoint.entityId)
+
+              const posIndex = positions.findIndex(p => p.entityName == updatedPoint.entityName)
+              if (posIndex !== -1) positions.splice(posIndex, 1)
+
+            } else {
+              if (fromRight) {
+                console.log(fromRight,'我走这里')
+                // ✅ 只有从右侧拖回画布,才算新增绑定
+                positions.push(updatedPoint)
+                if (!this.leftPoints.some(p => p.entityName === updatedPoint.entityName)) {
+                  this.leftPoints.push(updatedPoint)
+                }
+                if (!this.bindingPointIds.includes(updatedPoint.entityId)) {
+                  this.bindingPointIds.push(updatedPoint.entityId)
+                }
+                this.unbindPointIds = this.unbindPointIds.filter(id => id != updatedPoint.entityId)
+              } else {
+                // ✅ 普通拖动(0,0点 或 已在画布上的点),只更新位置
+                const posIndex = positions.findIndex(p => p.entityName == updatedPoint.entityName)
+                if (posIndex != -1) positions[posIndex] = updatedPoint
+              }
+            }
+
+            // ✅ 无论如何都要记录 movePoints(方便保存)
+            const existIdx = this.movePoints.findIndex(p => p.pointId == updatedPoint.pointId)
+            if (existIdx !== -1) {
+              this.movePoints.splice(existIdx, 1, updatedPoint)
+            } else {
+              this.movePoints.push(updatedPoint)
+            }
+
+            // 同步 JSON
+            this.updatePointInJson(updatedPoint, groupPos)
+
+            this.rightPoints = this.rightPoints.filter(Boolean)
+            this.leftPoints = this.leftPoints.filter(Boolean)
+
+            this.layer.draw()
+            console.log('Updated positions:', positions)
+            console.log('MovePoints:', this.movePoints)
+          })
+
+        }
+      })
+    },
+
+
+    // 左侧的列表 现在左侧列表通过地图点位接口获取pointList里直接有左侧的数据 不用再去隔离点管理接口拿数据
+    addPointsToLeftPointsBox(filterData) {
+      // 获取接口返回的 leftPoints 数据
+      const pointsData = filterData
+
+      let row = 1 // 当前行
+      let col = 1 // 当前列
+
+      // 遍历 pointsData 并根据是否存在于 this.value 中来决定位置
+      pointsData.forEach((point) => {
+        const existingPoint = JSON.parse(this.value).find(
+          (item) => item.pointId == point.pointId
+        )
+
+        // 如果该点在 this.value 中,使用它的原始位置
+        if (existingPoint) {
+          point.row = existingPoint.row
+          point.col = existingPoint.col
+        } else {
+          // 否则,按顺序从 (0, 0) 位置开始,每行三个点
+          point.row = 0
+          point.col = 0
+
+        }
+        // 渲染该点
+        this.renderPoint(point)
+      })
+    },
+
+    // 渲染每个点
+    // 渲染每个点
+    renderPoint(point) {
+      const x = point.col * 50; // 每个单元格宽度为50
+      const y = point.row * 50; // 每个单元格高度为50
+      const labelText = point.pointName;
+
+      const pointImage = new Image();
+      pointImage.src = point.pointIcon;
+
+      pointImage.onload = () => {
+        const group = new Konva.Group({
+          x,
+          y,
+          draggable: true
+        });
+
+        // 背景矩形
+        const bgrect = new Konva.Rect({
+          x: -6,
+          y: -5,
+          width: 62,
+          height: 80,
+          cornerRadius: 5,
+          stroke: 'white',
+          strokeWidth: 2,
+          fill: 'white'
+        });
+
+        // 普通矩形
+        const rect = new Konva.Rect({
+          x: 0,
+          y: -1,
+          width: 50,
+          height: 72,
+          cornerRadius: 5,
+          stroke: 'red',
+          strokeWidth: 2,
+          fill: 'white'
+        });
+
+        // 图片
+        const knovaImage = new Konva.Image({
+          x: 0,
+          y: 0,
+          image: pointImage,
+          width: 50,
+          height: 50
+        });
+
+        // 文字
+        const text = new Konva.Text({
+          x: 8,
+          y: 50,
+          fontSize: 17,
+          text: labelText,
+          fontFamily: 'Calibri',
+          fill: 'red'
+        });
+
+        group.add(bgrect, rect, knovaImage, text);
+        this.layer.add(group);
+
+        // 右侧盒子范围
+        const rightBoxBounds = { x: 1100, y: 15, width: 200, height: 800 };
+
+        group.on('dragend', () => {
+          const groupPos = group.getAbsolutePosition();
+          const isInRightBox =
+            groupPos.x >= rightBoxBounds.x &&
+            groupPos.x <= rightBoxBounds.x + rightBoxBounds.width &&
+            groupPos.y >= rightBoxBounds.y &&
+            groupPos.y <= rightBoxBounds.y + rightBoxBounds.height;
+
+          // 更新行列坐标
+          const newRow = Math.max(0, Math.round(groupPos.x / 50));
+          const newCol = Math.max(0, Math.round(groupPos.y / 50));
+
+          const updatedPoint = {
+            ...point,
+            row: newRow,
+            col: newCol,
+            x: newRow,
+            y: newCol,
+            mapId: this.form.mapId,
+            mapType: this.mapType,
+            entityId: point.pointId,
+            entityName: point.pointName
+          };
+
+          if (isInRightBox) {
+            // 👉 拖进右侧:解绑
+            if (!this.rightPoints.some(p => p.pointId === updatedPoint.pointId)) {
+              this.rightPoints.push(updatedPoint);
+            }
+            if (!this.unbindPointIds.includes(updatedPoint.pointId)) {
+              this.unbindPointIds.push(updatedPoint.pointId);
+            }
+            this.bindingPointIds = this.bindingPointIds.filter(id => id !== updatedPoint.pointId);
+            this.leftPoints = this.leftPoints.filter(p => p.pointId !== updatedPoint.pointId);
+          } else {
+            // 👉 拖在左侧画布
+            if (!this.leftPoints.some(p => p.pointId === updatedPoint.pointId)) {
+              this.leftPoints.push(updatedPoint);
+            }
+
+            // 只有从右侧拖回左侧时,才算 binding
+            if (this.rightPoints.some(p => p.pointId === updatedPoint.pointId)) {
+              if (!this.bindingPointIds.includes(updatedPoint.pointId)) {
+                this.bindingPointIds.push(updatedPoint.pointId);
+              }
+              this.unbindPointIds = this.unbindPointIds.filter(id => id !== updatedPoint.pointId);
+              this.rightPoints = this.rightPoints.filter(p => p.pointId !== updatedPoint.pointId);
+            }
+
+          }
+
+          // ✅ 始终更新 movePoints(包括 0,0 拖动)
+          const existIdx = this.movePoints.findIndex(p => p.pointId === updatedPoint.pointId);
+          if (existIdx !== -1) {
+            this.movePoints.splice(existIdx, 1, updatedPoint);
+          } else {
+            this.movePoints.push(updatedPoint);
+          }
+
+          // 更新 JSON
+          let positions = JSON.parse(this.value || '[]');
+          const posIndex = positions.findIndex(p => p.pointId === updatedPoint.pointId);
+          if (posIndex !== -1) {
+            positions[posIndex] = updatedPoint;
+          } else {
+            positions.push(updatedPoint);
+          }
+          this.value = JSON.stringify(positions, null, 4);
+
+          this.layer.draw();
+
+          console.log('Updated movePoints:', this.movePoints);
+          console.log('bindingPointIds:', this.bindingPointIds);
+          console.log('unbindPointIds:', this.unbindPointIds);
+        });
+
+        this.layer.draw();
+      };
+    },
+
+    // 从 json 删除对应的点
+    removePointFromJson(point) {
+      // 更新 leftPoints 和 rightPoints
+      this.rightPoints.push(point)
+      this.unbindPointIds.push(point.pointId) // 给接口传递需要解绑的数据Id
+
+      // 删除 JSON 中对应的点
+      const updatedData = JSON.parse(this.value).filter(
+        (item) => item.pointId !== point.pointId
+      )
+      this.value = JSON.stringify(updatedData, null, 4)
+
+      // console.log('Updated value after removal:', this.value)
+      console.log('removePointFromJson', updatedData)
+    },
+
+    // 更新 JSON 中对应点的位置
+    updatePointInJson(point, groupPos) {
+      // 计算新的位置
+      const newCol = Math.round(groupPos.x / 50)
+      const newRow = Math.round(groupPos.y / 50)
+
+      // 更新 positions 数组中的点位
+      const updatedPosition = {
+        row: newRow,
+        col: newCol,
+        x: newRow,
+        y: newCol,
+        pointId: point.pointId,
+        pointName: point.pointName,
+        entityId: point.pointId,
+        entityName: point.pointName,
+        remark: point.remark,
+        prePointId: point.prePointId,
+        pointType: point.pointType,
+        pointTypeName: point.pointTypeName,
+        powerType: point.powerType,
+        powerTypeName: point.powerTypeName,
+        state: point.state,
+        pointIcon: point.pointIcon,
+        pointPicture: point.pointPicture,
+        mapImg: point.mapImg,
+        mapId: this.form.mapId,
+        mapType: this.form.mapType,
+        mapName: '你好5'
+      }
+
+      let positions = JSON.parse(this.value)
+      const index = positions.findIndex(
+        (item) => item.pointId === point.pointId
+      )
+      if (index !== -1) {
+        // positions[index] = updatedPosition;
+        // this.value = JSON.stringify(positions, null, 4);
+        const updatedPositionCopy = JSON.parse(JSON.stringify(updatedPosition))
+        positions[index] = updatedPositionCopy
+
+        console.log(updatedPositionCopy, positions[index], 'updatedPosition-1')
+        this.value = JSON.stringify(positions, null, 4)
+      } else {
+        // 如果点位不在 this.value 中,则重新插入
+        positions.push(updatedPosition)
+        this.value = JSON.stringify(positions, null, 4)
+      }
+
+      // console.log('Updated value after update:', this.value)
+    },
+    // 解绑隔离点函数
+    addPointsToRightPointsBox(rightPointsBox) {
+      if (this.rightPoints && this.rightPoints.length > 0) {
+        const boxWidth = rightPointsBox.width()
+        const boxHeight = rightPointsBox.height()
+        const boxX = rightPointsBox.x()
+        const boxY = rightPointsBox.y()
+        const padding = 10 // 每个隔离点之间的间距
+        const pointWidth = 50 // 每个隔离点的宽度
+        const pointHeight = 70 // 每个隔离点的高度(包括图片和文字)
+
+        let currentX = boxX + padding
+        let currentY = boxY + padding
+        const rightBoxBounds = {
+          x: 1100,
+          y: 15,
+          width: 200,
+          height: 800
+        }
+        const cabinetBounds = {
+          x: 330,
+          y: 10,
+          width: 500,
+          height: 790
+        }
+
+        this.rightPoints.forEach((point) => {
+          // 创建一个组来组合红色边框、图片和文字
+          const group = new Konva.Group({
+            x: currentX,
+            y: currentY + 14,
+            draggable: true // 启用拖拽功能
+          })
+
+          // 创建红色边框
+          const borderRect = new Konva.Rect({
+            x: 0,
+            y: 0,
+            width: pointWidth,
+            height: pointHeight,
+            cornerRadius: 5,
+            stroke: 'red',
+            strokeWidth: 2,
+            fill: 'white'
+          })
+          group.add(borderRect)
+
+          // 创建图片
+          const image = new Image()
+          image.src = point.pointIcon
+          image.onload = () => {
+            const knovaImage = new Konva.Image({
+              x: 1, // 图片在组内的位置
+              y: 5, // 图片在组内的位置
+              image: image,
+              width: 50, // 图片宽度
+              height: 50 // 图片高度
+            })
+            group.add(knovaImage)
+
+            // 创建文字
+            const pointText = new Konva.Text({
+              x: 12, // 文字在组内的位置
+              y: 53, // 文字在组内的位置
+              text: point.pointName,
+              fontSize: 12,
+              fill: 'red'
+            })
+            group.add(pointText)
+
+            // 将组添加到图层
+            this.layer.add(group)
+            this.groups[point.pointName] = group // 用文字作为键存储
+
+            // 监听组的拖拽移动事件
+            group.on('dragmove', () => {
+              // 获取当前组的位置
+              const groupPos = group.getAbsolutePosition()
+              // 更新组的位置
+              group.x(groupPos.x)
+              group.y(groupPos.y)
+            })
+
+            // 监听组的拖拽结束事件
+            group.on('dragend', () => {
+              const gridX = 50 // 网格单元格宽度
+              const gridY = 50 // 网格单元格高度
+
+              // 计算最近的网格点位置
+              const snappedX = Math.round(group.x() / gridX) * gridX
+              const snappedY = Math.round(group.y() / gridY) * gridY
+
+              // 设置组到最近的网格点位置
+              group.x(snappedX)
+              group.y(snappedY)
+
+              // 计算网格坐标
+              const row = Math.floor(snappedY / gridY)
+              const col = Math.floor(snappedX / gridX)
+
+              // 更新点位数据
+              const updatedPointData = {
+                row: row,
+                col: col,
+                pointId: point.pointId,
+                entityId: point.entityId,
+                entityName: point.entityName,
+                pointName: point.pointName,
+                remark: point.remark,
+                prePointId: point.prePointId,
+                pointType: point.pointType,
+                pointTypeName: point.pointTypeName,
+                powerType: point.powerType,
+                powerTypeName: point.powerTypeName,
+                state: point.status,
+                pointIcon: point.pointIcon,
+                pointPicture: point.pointPicture,
+                mapImg: null,
+                mapId: point.mapId,
+                mapName: '你好6',
+                mapType: point.mapType
+              }
+
+              // 解析 this.value 为数组
+              let valueArray = []
+              try {
+                valueArray = JSON.parse(this.value)
+              } catch (e) {
+                console.error('Failed to parse value:', e)
+              }
+
+              // 判断拖拽目标区域
+              if (
+                snappedX >= rightBoxBounds.x &&
+                snappedX <= rightBoxBounds.x + rightBoxBounds.width &&
+                snappedY >= rightBoxBounds.y &&
+                snappedY <= rightBoxBounds.y + rightBoxBounds.height
+              ) {
+                // 进入右侧盒子区域
+                console.log('进入右侧盒子区域')
+// 更新 point 对象的 col 和 row 值
+                point.row = col
+                point.col = row
+                point.x = col
+                point.y = row
+                // 如果之前已在右侧区域,需要移除值,并更新绑定点ID
+                const index = valueArray.findIndex(
+                  (item) => item.pointId === point.pointId
+                )
+                if (index !== -1) {
+                  valueArray.splice(index, 1) // 从 valueArray 中删除该点
+                }
+                this.value = JSON.stringify(valueArray, null, 4)
+
+                // 删除绑定点
+                this.bindingPointIds = this.bindingPointIds.filter(
+                  (id) => id !== point.pointId
+                )
+
+                // 将该点加入解绑点ID
+                this.unbindPointIds.push(point.pointId)
+
+                // 将点重新添加到 rightPoints
+                this.rightPoints.push(point)
+              }
+                //   这里的if判断是为了 移动在物资柜内部才做的操作 现在我只要离开右侧 就做这个操作
+                //    if (
+                //   snappedX >= cabinetBounds.x &&
+                //   snappedX <= cabinetBounds.x + cabinetBounds.width &&
+                //   snappedY >= cabinetBounds.y &&
+                //   snappedY <= cabinetBounds.y + cabinetBounds.height
+              // )
+              else if (snappedX < rightBoxBounds.x ||
+                snappedX > rightBoxBounds.x + rightBoxBounds.width ||
+                snappedY < rightBoxBounds.y ||
+                snappedY > rightBoxBounds.y + rightBoxBounds.height
+              ) {
+
+                // 进入物资柜区域
+                console.log('进入物资柜区域')
+                // 更新 point 对象的 col 和 row 值
+                point.row = col
+                point.col = row
+                point.x = col
+                point.y = row
+                // 检查点是否已经存在于 valueArray 中
+                const index = valueArray.findIndex(
+                  (item) => item.pointId === point.pointId
+                )
+                if (index === -1) {
+                  // 如果点位不存在,则新增
+                  valueArray.push(updatedPointData)
+                  this.value = JSON.stringify(valueArray, null, 4)
+                }
+
+                // 添加到绑定点ID数组
+                this.bindingPointIds.push(point.pointId)
+
+                // 从右侧盒子移除点
+                this.rightPoints = this.rightPoints.filter(
+                  (item) => item.pointId !== point.pointId
+                )
+                this.leftPoints.push(point)
+                console.log(point, '进入左侧区域进行隔离点绑定操作!')
+              } else {
+                // 未进入任何目标区域,保持原状态
+                console.log('未进入目标区域,保持原状态')
+                // 更新 point 对象的 col 和 row 值
+                point.row = col
+                point.col = row
+                point.x = col
+                point.y = row
+                // 如果点不在目标区域,从 valueArray 中移除
+                // this.value = JSON.stringify(
+                //   valueArray.filter((item) => item.pointId !== point.pointId),
+                //   null,
+                //   4
+                // );
+                // 检查点是否已经存在于 valueArray 中
+                const index = valueArray.findIndex(
+                  (item) => item.pointId === point.pointId
+                )
+                if (index === -1) {
+                  // 如果点位不存在,则新增
+                  valueArray.push(updatedPointData)
+                  this.value = JSON.stringify(valueArray, null, 4)
+                }
+
+                // 添加到绑定点ID数组
+                this.bindingPointIds.push(point.pointId)
+
+                // 从右侧盒子移除点
+                this.rightPoints = this.rightPoints.filter(
+                  (item) => item.pointId !== point.pointId
+                )
+                // 如果点不在目标区域,重新添加到 rightPoints
+                this.rightPoints.push(point)
+              }
+
+              // 重新绘制图层
+              this.layer.draw()
+            })
+
+            // 重新绘制图层
+            this.layer.draw()
+          }
+
+          // 更新下一个隔离点的位置
+          currentX += pointWidth + padding
+          if (currentX + pointWidth > boxX + boxWidth) {
+            currentX = boxX + padding
+            currentY += pointHeight + padding
+          }
+        })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+#container {
+  width: 100%;
+  height: 100%;
+}
+
+.mapdata {
+  width: 100%;
+  height: 100%;
+  display: flex;
+}
+
+.left {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+  margin-bottom: 20px;
+}
+</style>

+ 637 - 0
src/views/mes/dv/switchmanagement/PointList.vue

@@ -0,0 +1,637 @@
+<template>
+  <div class="app-container">
+    <el-table
+      height="740"
+      v-loading="loading"
+      :data="isolationList"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" align="center"/>
+      <!-- <el-table-column label="隔离点ID" align="center" prop="pointId">
+        <template slot-scope="scope">
+          <el-button v-no-more-click
+            type="text"
+            @click="handleView(scope.row)"
+            v-hasPermi="['mes:md:seg:query']"
+            >{{ scope.row.pointId }}
+          </el-button>
+        </template>
+      </el-table-column> -->
+      <el-table-column label="隔离点编号" align="center" prop="pointCode" width="100">
+        <template slot-scope="scope">
+          <el-button
+            v-no-more-click
+            type="text"
+            @click="handleView(scope.row)"
+            v-hasPermi="['iscs:point:list']"
+          >{{ scope.row.pointCode }}
+          </el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="隔离点名称" align="center" prop="pointName"/>
+      <el-table-column
+        label="隔离点图标"
+        align="center"
+        prop="pointIcon"
+        width="90"
+      >
+        <template slot-scope="scope">
+          <img
+            v-if="scope.row.pointIcon"
+            :src="scope.row.pointIcon"
+            alt=""
+            style="width: 50px; height: 50px"
+          />
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="开关状态" align="center" prop="switchStatus">
+        <template slot-scope="scope">
+          <!--          <dict-tag :options="dict.type.switch_status" :value="scope.row.switchStatus"></dict-tag>-->
+          <el-switch
+            style="pointer-events: none;"
+            v-if="scope.row.switchStatus!==null"
+            v-model="scope.row.switchStatus"
+            active-value="1"
+            inactive-value="0"
+            active-color="#13ce66"
+            inactive-color="grey"
+          ></el-switch>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="隔离点NFC" align="center" prop="pointNfc" ></el-table-column>
+      <el-table-column label="岗位" align="center" prop="workstationName">
+      </el-table-column>
+      <el-table-column label="设备/工艺" align="center" prop="machineryName">
+      </el-table-column>
+      <el-table-column label="锁定站" align="center" prop="lotoName">
+      </el-table-column>
+      <el-table-column label="布局地图" align="center" prop="switchMapName">
+      </el-table-column>
+      <el-table-column label="隔离点序列号" align="center" prop="pointSerialNumber">
+      </el-table-column>
+      <el-table-column label="作用" align="center" prop="remark">
+      </el-table-column>
+      <el-table-column
+        label="隔离点图片"
+        align="center"
+        prop="pointPicture"
+        width="90"
+      >
+        <template slot-scope="scope">
+          <img
+            v-if="scope.row.pointPicture"
+            :src="scope.row.pointPicture"
+            alt=""
+            style="width: 50px; height: 50px"
+          />
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column label="能量源" align="center" prop="powerType">
+        <template slot-scope="scope">
+          <dict-tag
+            :options="dict.type.power_type"
+            :value="scope.row.powerType"
+          />
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.current"
+      :limit.sync="queryParams.size"
+      @pagination="getList"
+    />
+
+  </div>
+</template>
+
+<script>
+import {
+  getIsIsolationPointPage,
+  addinsertIsIsolationPoint,
+  updateIsIsolationPoint,
+  workshoplistAll,
+  getIsWorkareaList,
+  selectIsIsolationPointById,
+  deleteIsIsolationPointByPointIds
+} from '@/api/mes/spm/segregationPoint'
+import { genCode } from '@/api/system/autocode/rule'
+import Treeselect from '@riophae/vue-treeselect'
+import '@riophae/vue-treeselect/dist/vue-treeselect.css'
+import { listWorkarea } from '@/api/mes/wa/workarea'
+import { listLockType } from '@/api/mes/locktype/locktype'
+import { listPadLockTypeAPI } from '@/api/mes/padLockType/padLockType'
+import { listLoto } from '@/api/mes/lotoStation/lotoStation'
+import { listMarsDept } from '@/api/system/marsdept'
+import { listTechnology } from '@/api/system/machinery'
+import { getIsSystemAttributeByKey, selectIsSystemAttributeById } from '@/api/system/configuration'
+
+export default {
+  name: 'Team',
+  components: { Treeselect },
+  props: {
+    switchMapId: {
+      type: String, // 或 Number,根据你的实际类型
+      required: true
+    }
+  },
+  dicts: ['power_type', 'point_type', 'lock_type','switch_status'],
+  data() {
+    return {
+      //自动生成编码
+      autoGenFlag: false,
+      optType: undefined,
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      codes: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 部门树选项
+      deptOptions: [],
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 班组表格数据
+      isolationList: [],
+      // 弹出层标题
+      title: '',
+      // 是否显示弹出层
+      open: false,
+      // 车间数据
+      workshopList: [],
+      // 作业区域数据
+      workareaList: [],
+      // 新增或修改
+      pointId: null,
+      // 查询参数
+      createTime: '',
+      queryParams: {
+        current: 1,
+        size: 10,
+        pointCode: '',
+        pointName: '',
+        delFlag: '',
+        pointType: '',
+        powerType: '',
+        startTime: '',
+        endTime: '',
+        switchMapId:null,
+      },
+
+      // 表单参数
+      form: {},
+      //工艺树
+      machineryOptions: [],
+      LockTypeOptions: [],//锁具机构类型下拉
+      padLockTypeOptions: [],//挂锁类型下拉
+
+      lotoOptions: [],//电柜下拉
+      // 表单校验
+      rules: {
+        pointCode: [
+          { required: true, message: '隔离点编号不能为空', trigger: 'blur' }
+        ],
+        pointName: [
+          { required: true, message: '隔离点名称不能为空', trigger: 'blur' }
+        ],
+        workshopId: [
+          { required: true, message: '所属车间不能为空', trigger: 'blur' }
+        ],
+        workareaId: [
+          { required: true, message: '作业区域不能为空', trigger: 'blur' }
+        ],
+        switchMapId: [
+          { required: true, message: '开关点位Id不能为空', trigger: 'blur' }
+        ],
+        pointType: [
+          { required: true, message: '隔离点类型不能为空', trigger: 'blur' }
+        ],
+        lockTypeId: [
+          { required: true, message: '挂锁类型不能为空', trigger: 'blur' }
+        ],
+        pointNfc: [
+          { required: true, message: '隔离点NFC不能为空', trigger: 'blur' }
+        ]
+      },
+      imageMap: {
+        0: '', //电能
+        1: '', //阀门
+        2: '', //空气能
+        3: '' //急停开关
+      },
+      selectedImageIndex: -1 // 用于记录选中的图片索引
+    }
+  },
+  watch:{
+    switchMapId(newVal) {
+      console.log('switchMapId changed:', newVal);
+      // 这里可以执行需要的逻辑
+      this.queryParams.switchMapId = newVal;
+      this.getList()
+    }
+  },
+  created() {
+    this.getList()
+    this.getworkShop()
+    this.getworkArea()
+  },
+
+  methods: {
+    // 格式化日期查询数据
+    formatDate(date) {
+      if (date && date instanceof Date && !isNaN(date)) {
+        // 使用本地时间
+        return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
+          2,
+          '0'
+        )}-${String(date.getDate()).padStart(2, '0')}`
+      }
+      return null
+    },
+    // 选择隔离点图标
+    selectIcon(imageUrl, index) {
+      this.form.pointIcon = imageUrl
+      this.selectedImageIndex = index
+    },
+    /** 查询隔离点信息列表 */
+    getList() {
+      this.loading = true
+      // 格式化日期并更新queryParams
+      if (Array.isArray(this.createTime) && this.createTime.length === 2) {
+        this.queryParams.startTime = this.formatDate(this.createTime[0])
+        this.queryParams.endTime = this.formatDate(this.createTime[1])
+      }
+      this.queryParams.switchMapId=this.switchMapId
+      console.log(this.queryParams,'查询参数')
+      getIsIsolationPointPage(this.queryParams).then((response) => {
+        // console.log(response, "接口返回结果");
+        this.isolationList = response.data.records
+        this.total = response.data.total
+        this.loading = false
+      })
+      // 获取隔离点图标组合
+      const sysAttrKey1 = 'sys.icon_set.isolation' // 隔离点集合图标
+
+// 获取 ID 列表
+      getIsSystemAttributeByKey(sysAttrKey1).then((response) => {
+        const Values = response.data.sysAttrValue.split(',').map(Value => Value.trim())
+        console.log(Values, 'Values')
+        // 创建一个 Promise 数组,每个 Promise 处理一个 ID
+        const promises = Values.map(Value => {
+          return getIsSystemAttributeByKey(Value)
+        })
+
+        // 等待所有请求完成
+        return Promise.all(promises)
+      }).then((responses) => {
+        // responses 是一个数组,包含每个请求的响应
+        responses.forEach((response, index) => {
+          // 假设每个响应包含一个图片地址
+          this.imageMap[index] = response.data.sysAttrValue // 根据实际响应结构调整
+        })
+
+        console.log(this.imageMap, 'imageMap')
+      }).catch(error => {
+        console.error('Error fetching images:', error)
+      })
+      // 获取作业区域数据
+      const data = {
+        current: 1,
+        size: -1
+      }
+      // listWorkarea(data).then((response) => {
+      //   this.deptOptions = this.handleTree(
+      //     response.data.records,
+      //     'workareaId',
+      //     'parentId',
+      //     'children'
+      //   )
+      // })
+      // 岗位
+      listMarsDept(data).then(response => {
+        this.deptOptions = this.handleTree(response.data.records, 'workstationId', 'parentId')
+      })
+      // 设备/工艺
+      listTechnology(data).then((response) => {
+        const data = response.data.records.filter((item) => item.machineryType == '工艺')
+        this.machineryOptions = this.handleTree(
+          data,
+          'machineryId',
+          'parentId'
+        )
+
+      })
+      // 锁具机构类型
+      listLockType(data).then((response) => {
+        this.LockTypeOptions = this.handleTree(response.data.records, 'locksetTypeId')
+      })
+      //   挂锁类型
+      listPadLockTypeAPI(data).then((response) => {
+        this.padLockTypeOptions = this.handleTree(response.data.records, 'lockTypeId')
+      })
+
+      //   所属电柜
+      listLoto(data).then((response) => {
+        console.log(response, '电柜数据')
+        this.lotoOptions = response.data.records.map((item) => {
+          return {
+            value: item.lotoId,
+            label: item.lotoName
+          }
+        })
+      })
+    },
+    /** 转换部门数据结构 */
+    normalizer(node) {
+      if (node.children && !node.children.length) {
+        delete node.children
+      }
+      return {
+        id: node.workstationId,
+        label: node.workstationName,
+        children: node.children
+      }
+    },
+
+    // 取消按钮
+    cancel() {
+      this.open = false
+      this.reset()
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        pointCode: null,
+        pointName: null,
+        pointIcon: null,
+        pointPicture: null,
+        pointType: null,
+        powerType: null,
+        createTime: null
+      }
+      this.autoGenFlag = false
+      this.resetForm('form')
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.current = 1
+      if (Array.isArray(this.createTime) && this.createTime.length === 2) {
+        this.queryParams.startTime = this.formatDate(this.createTime[0])
+        this.queryParams.endTime = this.formatDate(this.createTime[1])
+      }
+      getIsIsolationPointPage(this.queryParams).then((response) => {
+        // console.log(response, "接口返回结果");
+        this.isolationList = response.data.records
+        this.total = response.data.total
+        this.loading = false
+      })
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.createTime = '';
+      (this.queryParams.startTime = ''),
+        (this.queryParams.endTime = ''),
+        this.resetForm('queryForm')
+      if (Array.isArray(this.createTime) && this.createTime.length === 2) {
+        this.queryParams.startTime = this.formatDate(this.createTime[0])
+        this.queryParams.endTime = this.formatDate(this.createTime[1])
+      }
+      getIsIsolationPointPage(this.queryParams).then((response) => {
+        // console.log(response, "接口返回结果");
+        this.isolationList = response.data.records
+        this.total = response.data.total
+        this.loading = false
+      })
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map((item) => item.pointId)
+      this.codes = selection.map((item) => item.pointCode)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset()
+      this.open = true
+      this.title = '新增隔离点'
+      this.optType = 'add'
+      this.pointId = null
+      this.form.workshopId = 0
+      this.selectedImageIndex=null//为了初始化不选中任何图标
+      this.getworkShop()
+    },
+
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset()
+      this.pointId = row.pointId || this.ids
+      selectIsIsolationPointById(this.pointId).then((response) => {
+        this.form = response.data
+        // 确定选中的图片索引
+        this.selectedImageIndex = this.getImageIndexByIcon(response.data.pointIcon);
+        this.open = true
+        this.title = '修改隔离点信息'
+        this.optType = 'edit'
+      })
+    },
+    // 编辑的时候回显选中的隔离点图标
+    getImageIndexByIcon(iconUrl) {
+      for (const [index, imageUrl] of Object.entries(this.imageMap)) {
+        if (imageUrl === iconUrl) {
+          return index;
+        }
+      }
+      return -1; // 如果没有找到匹配的图片,返回 -1
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs['form'].validate((valid) => {
+        if (valid) {
+          console.log(this.pointId, 'this.pointId')
+          if (this.pointId != null) {
+            console.log(this.form, '隔离点修改form')
+            updateIsIsolationPoint(this.form).then((response) => {
+              this.$modal.msgSuccess('修改成功')
+              this.open = false
+              this.getList()
+            })
+          } else {
+            console.log(this.form, '新增参数')
+            addinsertIsIsolationPoint(this.form).then((response) => {
+              this.$modal.msgSuccess('新增成功')
+              this.open = false
+              this.getList()
+              this.pointId = null
+            })
+          }
+        }
+      })
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const teamIds = row.pointId || this.ids
+      // const pointcodes = row.pointCode || this.codes
+      // '是否确认删除隔离点编号为"' + pointcodes + '"的数据项?'
+      this.$modal
+        .confirm('是否确认删除所选数据项?')
+        .then(function() {
+          return deleteIsIsolationPointByPointIds(teamIds)
+        })
+        .then(() => {
+          this.getList()
+          this.$modal.msgSuccess('删除成功')
+        })
+        .catch(() => {
+        })
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      this.download(
+        'cal/team/export',
+        {
+          ...this.queryParams
+        },
+        `team_${new Date().getTime()}.xlsx`
+      )
+    },
+    //自动生成编码
+    handleAutoGenChange(autoGenFlag) {
+      if (autoGenFlag) {
+        genCode('ISOLATION_POINT_CODE').then((response) => {
+          this.form.pointCode = response
+        })
+      } else {
+        this.form.pointCode = null
+      }
+    },
+    // 获取车间数据
+    getworkShop() {
+      workshoplistAll().then((response) => {
+        // console.log(response, "获取车间数据");
+        this.workshopList = response.data.map((item) => {
+          return {
+            label: item.workshopName,
+            value: item.workshopId,
+            key: item.workshopCode
+          }
+        })
+      })
+    },
+    // 所属车间下拉改变作业区域内容
+    workShopChange() {
+      this.getworkArea()
+    },
+    // 获取作业区域数据
+    getworkArea() {
+      const workshopId = this.form.workshopId
+
+      if (workshopId) {
+        getIsWorkareaList(workshopId).then((response) => {
+          console.log(response, '获取作业区域数据')
+          this.workareaList = response.data.map((item) => {
+            return {
+              label: item.workareaName,
+              value: item.workareaId,
+              key: item.workareaCode
+            }
+          })
+        })
+      }
+    },
+    normalizerLockset(node) {
+      if (node.children && !node.children.length) {
+        delete node.children
+      }
+      return {
+        id: node.locksetTypeId,
+        label: node.locksetTypeName,
+        children: node.children
+      }
+    },
+    normalizerpadLock(node) {
+      if (node.children && !node.children.length) {
+        delete node.children
+      }
+      return {
+        id: node.lockTypeId,
+        label: node.lockTypeName,
+        children: node.children
+      }
+    },
+    //图片上传成功
+    handleImgUplaoded(imgUrl) {
+      this.form.pointPicture = imgUrl[0].url
+    },
+    //图片移除
+    handleImgRemoved(imgUrl) {
+      this.form.pointPicture = null
+    },
+    //图标上传成功
+    handleIconUplaoded(imgUrl) {
+      this.form.pointIcon = imgUrl[0].url
+    },
+    // 图标移除
+    handleIconRemoved(imgUrl) {
+      this.form.pointIcon = null
+    },
+    //图标上传成功
+    lockTypeImgUplaoded(imgUrl) {
+      this.form.lockTypeImg = imgUrl[0].url
+    },
+    // 图标移除
+    lockTypeImgRemoved(imgUrl) {
+      this.form.lockTypeImg = null
+    },
+    //图标上传成功
+    locksetTypeImgUplaoded(imgUrl) {
+      this.form.locksetType = imgUrl[0].url
+    },
+    // 图标移除
+    locksetTypeImgRemoved(imgUrl) {
+      this.form.locksetType = null
+    },
+  }
+}
+</script>
+<style lang="scss" src="@/assets/styles/dialog-title.scss" scoped>
+.el-input-width {
+  width: 380px !important;
+}
+
+</style>
+<style lang="scss" scoped>
+
+.image-grid {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.image-item {
+  margin: 5px;
+  height: 55px;
+  //background: pink;
+  cursor: pointer;
+  border: 2px solid transparent;
+  transition: border-color 0.3s;
+  position: relative;
+}
+
+.image-item.selected {
+  height: 55px;
+  border-color: rgb(2, 86, 255);
+  border-width: 2px;
+}
+
+
+</style>

+ 1260 - 0
src/views/mes/dv/switchmanagement/SwitchStatus.vue

@@ -0,0 +1,1260 @@
+<template>
+  <div class="mapdata">
+    <div id="container" ref="container" style="width: 1600px"></div>
+    <div class="left">
+      <div
+        class="bottombtn"
+        style="width: 100%; height: 35px; padding: 10px;display: flex;flex-direction: column;"
+      >
+        <!--        <el-button-->
+        <!--          v-no-more-click-->
+        <!--          @click="close"-->
+        <!--          type="primary"-->
+        <!--          icon="el-icon-close"-->
+        <!--          style="align-self: flex-end;margin-top: 10px"-->
+        <!--        >关闭-->
+        <!--        </el-button>-->
+
+        <el-button
+          v-no-more-click
+          @click="save"
+          type="primary"
+          icon="el-icon-check"
+          style="align-self: flex-end;margin-top: 10px"
+        >保存
+        </el-button>
+
+        <el-button
+          v-no-more-click
+          @click="reset"
+          type="primary"
+          icon="el-icon-setting"
+          style="align-self: flex-end;margin-top: 10px"
+        >重置
+        </el-button>
+
+      </div>
+    </div>
+
+
+  </div>
+</template>
+
+<script>
+import Konva from 'konva'
+import {
+  selectLotoMapById,
+  selectIsLotoSwitchMapById,
+  updateIsLotoSwitchMap,
+  updatePointsBindingSwitchMap
+} from '@/api/mes/switchmanagement/switchmanagement'
+import {getIsIsolationPointPage} from '@/api/mes/spm/segregationPoint'
+import {getIsMapPointPage, selectIsMapPointById, updateMapPointList} from '@/api/system/mappoint'
+import {selectIsMapById} from '@/api/system/mapconfig'
+
+export default {
+  name: 'KonvaExample',
+  data() {
+    return {
+      stage: null,
+      layer: null,
+      selectedStates: [], // 用于存储每个元素的选中状态
+      selectedText: [], // 用于存储未选中的元素文本
+      rects: [], // 白色rect合集
+      texts: [], // 白色text合集
+      redrects: [], // 红色rect合集
+      redtexts: [], // 白色text合集
+      value: '',
+      form: {}, //拿到单个数据
+      originData: null, //原始数据
+      filterData: null, //用来过滤掉已经渲染出来的隔离点
+      leftPoints: [], //绑定的但未指定位置的集合
+      orgLeftPoints: [], //原始左边数据
+      rightPoints: [], //解绑的数据集合
+      orgRightPoints: [], //原始右边数据
+      groups: [], //组移动数据
+      bindingPointIds: [], //存放从未绑定中放入物资柜的数据 id集合
+      unbindPointIds: [], //解绑的数据接口参数 id集合
+      isSave: true,
+      isInitialized: false, // 添加初始化标志
+      imageUrl: '',//获取底图
+      width: '',//底图宽
+      height: '',//底图高
+      x: '',//底图横坐标
+      y: '',//底图纵坐标
+      mapId: null,//地图Id
+      mapType: 2,//地图类型
+      pointList: null,//接口给的所有点位数据
+      bindingPoints: [],//给地图点位界面更新的绑定隔离点
+      movePoints: [],//给地图点位界面更新位置
+      unbindingPoints: [],//给地图点位界面更新解绑数据
+      blinkLights : [], // 所有需要闪烁的 light 节点
+      globalBlinkTimer : null
+    }
+  },
+  // watch: {
+  //   bindingPointIds(newVal, oldVal) {
+  //     if (newVal) {
+  //       this.isSave = false;
+  //     }
+  //   },
+  //   unbindPointIds(newVal, oldVal) {
+  //     if (newVal) {
+  //       this.isSave = false;
+  //     }
+  //   },
+  //   // value: {
+  //   //   handler(newVal, oldVal) {
+  //   //     if (this.isInitialized && newVal) {
+  //   //       // 只有在初始化后才监听 value 变化
+  //   //       const parsedValue = JSON.parse(newVal);
+  //   //       console.log(parsedValue, "deep watch for value");
+  //   //       this.isSave = false;
+  //   //     }
+  //   //   },
+  //   //   deep: true,
+  //   // },
+  // },
+
+  created() {
+    // this.getIsIsolationPointPage()
+    this.isInitialized = true
+  },
+  beforeRouteEnter(to, from, next) {
+    next((vm) => {
+      // vm.getIsIsolationPointPage()
+      vm.addPointsToRightPointsBox()
+    })
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.getInfo()
+      this.getIsIsolationPointPage()
+      this.addPointsToRightPointsBox()
+    })
+    console.log(this.$route.query.switchMapId, 'switchMapId')
+  },
+
+  methods: {
+    getInfo() {
+      const switchMapId = this.$route.query.switchMapId
+      selectIsLotoSwitchMapById(switchMapId).then((response) => {
+        console.log(response, '作业区域信息')
+        this.form = response.data
+        this.mapId = response.data.mapId
+
+        // 获取不同底图 如地图或者柜子
+        selectIsMapById(response.data.mapId).then((response) => {
+          console.log(response, '获取底图')
+          if (response.data) {
+            try {
+              this.value = JSON.stringify(response.data.pointList, null, 4)
+
+              this.originData = this.value
+            } catch (err) {
+              console.error(err)
+            }
+          }
+          this.imageUrl = response.data.imageUrl
+          this.width = response.data.width
+          this.height = response.data.height
+          this.x = response.data.x
+          this.y = response.data.y
+          this.pointList = response.data.pointList
+
+          const data = {
+            current: 1,
+            size: -1,
+            switchMapId: this.$route.query.switchMapId
+          }
+
+          getIsIsolationPointPage(data).then((res) => {
+            const data1 = res.data.records // 该柜子或地图所有点
+            const data2 = this.pointList   // 该柜子里 json 拿到的点位(已渲染)
+
+            console.log(data1, '该柜子或地图所有点')
+            console.log(data2, '柜子里json拿到的点位')
+
+            // 当前柜子已经存在的点位 id
+            const pointListIds = new Set(this.pointList.map((item) => item.pointId))
+
+            // 过滤掉已经在 pointList 里的点
+            const filterData = data1.filter(item => !pointListIds.has(item.pointId))
+
+            console.log([...pointListIds], '已有点位 ID')
+            console.log(filterData, 'filterData-需要显示在左侧的数据')
+
+            // 左侧需要显示的数据
+            // this.leftPoints = filterData.map((item) => {
+            //   return {
+            //     pointId: item.pointId,
+            //     entityId: item.pointId,
+            //     entityName: item.pointName,
+            //     pointName: item.pointName,
+            //     remark: item.remark,
+            //     prePointId: item.prePointId,
+            //     pointType: item.pointType,
+            //     pointTypeName: item.pointTypeName,
+            //     powerType: item.powerType,
+            //     powerTypeName: item.powerTypeName,
+            //     pointIcon: item.pointIcon,
+            //     status: false,
+            //     pointPicture: item.pointPicture,
+            //     mapImg: null,
+            //     mapId:this.form.mapId,
+            //     mapType: 2,
+            //     mapName:'你好4',
+            //     x: 0,
+            //     y: 0,
+            //   }
+            // })
+
+            // 调用你已有的渲染逻辑
+            // this.addPointsToLeftPointsBox(filterData)
+
+            // 保存完整数据
+            this.orgLeftPoints = res.data.records.map((item) => {
+              return {
+                pointId: item.pointId,
+                entityId: item.pointId,
+                entityName: item.pointName,
+                pointName: item.pointName,
+                remark: item.remark,
+                prePointId: item.prePointId,
+                pointType: item.pointType,
+                pointTypeName: item.pointTypeName,
+                powerType: item.powerType,
+                powerTypeName: item.powerTypeName,
+                pointIcon: item.pointIcon,
+                status: false,
+                pointPicture: item.pointPicture,
+                mapImg: null,
+                mapId:this.form.mapId,
+                mapType: this.form.mapType,
+              }
+            })
+          })
+
+          this.initKonva()
+        })
+      })
+    },
+
+    // 获取未绑定的所有隔离点
+    getIsIsolationPointPage() {
+
+      //   拿到解绑的隔离点数据
+      const data1 = {
+        current: 1,
+        size: -1,
+        switchMapId: 0
+      }
+      getIsIsolationPointPage(data1).then((res) => {
+        this.rightPoints = res.data.records.map((item) => {
+          return {
+            entityId: item.pointId,
+            entityName: item.pointName,
+            pointId: item.pointId,
+            pointName: item.pointName,
+            remark: item.remark,
+            prePointId: item.prePointId,
+            pointType: item.pointType,
+            pointTypeName: item.pointTypeName,
+            powerType: item.powerType,
+            powerTypeName: item.powerTypeName,
+            pointIcon: item.pointIcon,
+            status: false,
+            pointPicture: item.pointPicture,
+            mapImg: null,
+            mapId: this.mapId,
+            mapType: this.mapType
+          }
+        })
+        this.orgRightPoints = res.data.records.map((item) => {
+          return {
+            entityId: item.pointId,
+            entityName: item.pointName,
+            pointId: item.pointId,
+            pointName: item.pointName,
+            remark: item.remark,
+            prePointId: item.prePointId,
+            pointType: item.pointType,
+            pointTypeName: item.pointTypeName,
+            powerType: item.powerType,
+            powerTypeName: item.powerTypeName,
+            pointIcon: item.pointIcon,
+            status: false,
+            pointPicture: item.pointPicture,
+            mapImg: null,
+            mapId: this.mapId,
+            mapType: this.mapType
+          }
+        })
+      })
+    },
+    // 重置
+    reset() {
+      this.value = this.originData
+      // 清空并重新赋值
+      this.rightPoints = JSON.parse(JSON.stringify(this.orgRightPoints)) // 深拷贝
+      this.leftPoints = JSON.parse(JSON.stringify(this.orgLeftPoints)) // 深拷贝
+      this.initKonva() // 重新初始化 Konva
+    },
+
+    close() {
+      // this.$router.push('/mes/dv/lotoStation')
+      this.getInfo()
+      // this.initKonva()
+    },
+    save() {
+      this.$confirm('请确认是否保存修改内容', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(() => {
+          // 校验 this.value 是否为有效的 JSON
+          if (this.isJson(this.value)) {
+            const mapData =
+              typeof this.value === 'string'
+                ? this.value
+                : JSON.stringify(this.value)
+            const formData = {
+              ...this.form,
+              map: mapData
+            }
+            console.log(formData, 'map')
+
+            updateIsLotoSwitchMap(formData).then((response) => {
+              console.log(response, '修改车间区域地图')
+              this.$message({
+                type: 'success',
+                message: '保存成功!'
+              })
+            })
+
+            let dataMap = {
+              bindingPoints: this.leftPoints,
+              movePoints: this.movePoints,
+              unbindingPoints: this.rightPoints
+            }
+            console.log(dataMap, '先拿到数据看看再说')
+            updateMapPointList(dataMap).then((res) => {
+              console.log(res, '拿到的新绑定数据')
+            })
+            const data = {
+              bindingPointIds: this.bindingPointIds,
+              switchMapId: this.$route.query.switchMapId,
+              unbindPointIds: this.unbindPointIds
+            }
+            console.log(data, '解绑与绑定数据参数')
+            updatePointsBindingSwitchMap(data).then((res) => {
+              console.log(res, '解绑接口返回值')
+              this.bindingPointIds = []
+              this.unbindPointIds = []
+            })
+            // this.close()
+          } else {
+            this.$message({
+              type: 'error',
+              message: '地图数据格式不正确,请输入有效的 JSON 格式!'
+            })
+          }
+        })
+        .catch(() => {
+          // 取消操作
+        })
+    },
+
+    // 校验字符串是否为有效的 JSON
+    isJson(str) {
+      try {
+        JSON.parse(str)
+        return true
+      } catch (e) {
+        return false
+      }
+    },
+    initKonva() {
+
+      // 创建舞台
+      this.stage = new Konva.Stage({
+        container: this.$refs.container, // 容器元素
+        width: 1600,
+        height: 860
+      })
+
+      // 创建图层
+      this.layer = new Konva.Layer()
+      // 绘制隔离点等其他内容
+      this.drawGrid(50, 50, '#e0e0e0') // 每个单元格50x50,浅灰色网格
+      // 创建物资柜底图
+      const bgImage = new Image()
+      const imageConfig = {
+        x: this.x,
+        y: this.y,
+        width: this.width,
+        height: this.height,
+        draggable: false
+      }
+      bgImage.src = this.imageUrl
+      bgImage.onload = () => {
+        const knovaImage = new Konva.Image({
+          ...imageConfig,
+          image: bgImage
+        })
+        this.layer.add(knovaImage)
+
+        // 创建背景图并添加到图层
+        // 创建所有隔离点父盒子 放置于网格线上
+        // const rightPointsBox = new Konva.Rect({
+        //   x: 1100,
+        //   y: 15,
+        //   width: 200,
+        //   height: 800,
+        //   cornerRadius: 5,
+        //   stroke: 'black',
+        //   strokeWidth: 2,
+        //   fill: 'white'
+        // })
+        // const rightnoLoto = new Konva.Text({
+        //   x: 1110, // 调整位置以适应网格
+        //   y: 20, // 调整位置以适应网格
+        //   text: '未绑定锁定站的隔离点数据',
+        //   fontSize: 15,
+        //   fill: 'black'
+        // })
+
+        // this.layer.add(rightPointsBox)
+        // this.layer.add(rightnoLoto)
+
+        // 将隔离点添加到 rightPointsBox
+        // this.addPointsToRightPointsBox(rightPointsBox)
+
+        // 渲染数据
+        const imageSrc = require('@/assets/images/localSetIcon.jpg') // 图片路径
+        this.renderGrid(imageSrc, 6, 3, 450, 100, 120, 100, 50, 50, 60, 25)
+
+        // 将图层添加到舞台
+        this.stage.add(this.layer)
+        this.layer.draw()
+      }
+
+      // 禁止舞台拖拽
+      this.stage.draggable(false)
+    },
+
+    // 绘制无限网格
+    drawGrid(cellWidth, cellHeight, gridColor) {
+      const width = 1600
+      const height = 860
+
+      // 绘制竖线
+      for (let i = 0; i <= width; i += cellWidth) {
+        const verticalLine = new Konva.Line({
+          points: [i, 0, i, height],
+          stroke: gridColor,
+          strokeWidth: 1
+        })
+        this.layer.add(verticalLine)
+      }
+
+      // 绘制横线
+      for (let j = 0; j <= height; j += cellHeight) {
+        const horizontalLine = new Konva.Line({
+          points: [0, j, width, j],
+          stroke: gridColor,
+          strokeWidth: 1
+        })
+        this.layer.add(horizontalLine)
+      }
+
+      // 添加网格坐标文本
+      // for (let row = 0; row < height / cellHeight; row++) {
+      //   for (let col = 0; col < width / cellWidth; col++) {
+      //     const text = new Konva.Text({
+      //       x: col * cellWidth + 5, // 调整位置以适应网格
+      //       y: row * cellHeight + 5, // 调整位置以适应网格
+      //       text: `(${col},${row})`,
+      //       fontSize: 10,
+      //       fill: 'gray',
+      //     });
+      //     this.layer.add(text);
+      //   }
+      // }
+
+      this.layer.draw()
+    },
+
+    // 绘制柜内所有点
+    renderGrid(imageSrc) {
+
+      this.selectedStates = [] // 用数组来存储选中状态
+      this.rects = []
+      this.texts = []
+      this.bgrects = {}
+      this.redrects = []
+      this.redtexts = []
+      this.selectedText = []
+
+      // ✅ 每次渲染前清空
+      this.rightPoints = []
+      this.leftPoints = []
+      this.bindingPointIds = []
+      this.unbindPointIds = []
+      this.movePoints = []
+
+      // 点位数据
+      console.log(this.pointList, '点位数据')
+      const positions = (this.pointList || []).map(item => ({
+        row: item.x,
+        col: item.y,
+        id: item.id,
+        pointId: item.entityId,
+        pointName: item.entityName,
+        entityId: item.entityId,
+        entityName: item.entityName,
+        mapId: item.mapId,
+        mapType: parseInt(item.mapType),
+        x: item.x,
+        y: item.y,
+        remark: item.remark,
+        pointIcon: item.pointIcon,
+        pointPicture: item.pointPicture,
+      }))
+      console.log(positions, 'positions')
+
+      positions.forEach((pos) => {
+        const x = pos.x * 50
+        const y = pos.y * 50
+        const labelText = pos.entityName
+
+        const point = new Image()
+        point.src = pos.pointIcon
+
+        point.onload = () => {
+          const group = new Konva.Group({
+            x: x,
+            y: y,
+            draggable: true
+          })
+
+          const bgrect = new Konva.Rect({
+            x: -1,
+            y: -5,
+            width: 50,
+            height: 78,
+            cornerRadius: 5,
+            stroke: 'white',
+            strokeWidth: 2,
+            fill: 'white'
+          })
+
+          const rect = new Konva.Rect({
+            x: 1,
+            y: -1,
+            width: 45,
+            height: 70,
+            cornerRadius: 5,
+            stroke: 'red',
+            strokeWidth: 2,
+            fill: 'white'
+          })
+          // 确定灯的颜色
+          let lightColor, shadowColor, stroke;
+
+          if (pos.switchStatus == "0") {
+            // lightColor = '#ff000d';     // Red
+            // shadowColor = '#ffcae8';
+            // stroke = '#ffcae8';
+            lightColor = '#e0e0e0';     // Gray (unknown)
+            shadowColor = '#e0e0e0';
+            stroke = '#e0e0e0';
+          } else if (pos.switchStatus == "1") {
+            lightColor = '#0ea562';     // Green
+            shadowColor = '#3ab890';
+            stroke = '#3ab890';
+          } else {
+            lightColor = 'white';
+            shadowColor="white";
+            stroke="white"
+            // lightColor = '#e0e0e0';     // Gray (unknown)
+            // shadowColor = '#e0e0e0';
+            // stroke = '#e0e0e0';
+          }
+          const isRed = pos.switchStatus == "0";
+          const isGrey = pos.switchStatus == null;
+// Create the light
+          const light = new Konva.Circle({
+            x: 22.5, // Circle center position
+            y: 25,  // Circle center position
+            radius: 12,
+            fill: lightColor,
+            stroke: stroke,
+            strokeWidth: 2,
+            shadow: {
+              color: shadowColor,
+              blur: 5,
+              offset: {x: 0, y: 0},
+              opacity: 0.8
+            }
+          });
+          // const knovaImage = new Konva.Image({
+          //   x: 1,
+          //   y: 0,
+          //   image: point,
+          //   width: 45,
+          //   height: 45
+          // })
+
+          const text = new Konva.Text({
+            x: 8,
+            y: 50,
+            fontSize: 17,
+            text: labelText,
+            fontFamily: 'Calibri',
+            fill: 'red'
+          })
+
+          group.add(bgrect)
+          group.add(rect)
+          // group.add(knovaImage)
+          group.add(light)
+          group.add(text)
+
+          this.layer.add(group)
+
+          // 定义右侧盒子的范围
+          // const rightBoxBounds = { x: 1100, y: 15, width: 200, height: 800 }
+
+          // group.on('dragend', () => {
+          //   const groupPos = group.getAbsolutePosition()
+          //   const movedLabel = labelText
+          //
+          //   const isInRightBox =
+          //     groupPos.x >= rightBoxBounds.x &&
+          //     groupPos.x <= rightBoxBounds.x + rightBoxBounds.width &&
+          //     groupPos.y >= rightBoxBounds.y &&
+          //     groupPos.y <= rightBoxBounds.y + rightBoxBounds.height
+          //
+          //   const indexToRemove = positions ? positions.findIndex(item => item.entityName === movedLabel) : -1
+          //
+          //   if (indexToRemove !== -1) {
+          //     const movedPoint = positions[indexToRemove]
+          //     if (isInRightBox) {
+          //       // 移动到右侧
+          //       positions.splice(indexToRemove, 1)
+          //       if (!this.rightPoints.some(p => p.entityName === movedPoint.entityName)) {
+          //         this.rightPoints.push(movedPoint)
+          //         this.unbindPointIds.push(movedPoint.entityId)
+          //         this.bindingPointIds = this.bindingPointIds.filter(id => id !== movedPoint.entityId)
+          //       }
+          //     } else {
+          //       // 在柜子内/外更新位置
+          //       const newCol = Math.round(groupPos.y / 50)
+          //       const newRow = Math.round(groupPos.x / 50)
+          //
+          //       const boundedCol = Math.max(0, Math.min(newCol, Math.floor(860 / 50) - 1))
+          //       const boundedRow = Math.max(0, Math.min(newRow, Math.floor(1200 / 50) - 1))
+          //
+          //       const updatedPoint = {
+          //         ...movedPoint,
+          //         row: boundedRow,
+          //         col: boundedCol,
+          //         x: boundedRow,
+          //         y: boundedCol,
+          //         mapId: this.form.mapId,
+          //         mapType: this.form.mapType,
+          //       }
+          //
+          //       positions[indexToRemove] = updatedPoint
+          //
+          //       // ✅ 只有位置不为 0 才加入 movePoints
+          //       if (!(updatedPoint.row == 0 && updatedPoint.col == 0 && updatedPoint.x == 0 && updatedPoint.y == 0)) {
+          //         if (!this.movePoints) this.movePoints = []
+          //         // 防止重复 push,先找一下
+          //         const existIdx = this.movePoints.findIndex(p => p.pointId === updatedPoint.pointId)
+          //         if (existIdx !== -1) {
+          //           this.movePoints.splice(existIdx, 1, updatedPoint) // 覆盖旧的
+          //         } else {
+          //           this.movePoints.push(updatedPoint)
+          //         }
+          //       }
+          //     }
+          //   } else {
+          //     // movedLabel 不在 positions,可能是从右侧拖出来
+          //     const rightIndex = this.rightPoints.findIndex(item => item.entityName === movedLabel)
+          //     if (rightIndex !== -1) {
+          //       const movedPoint = this.rightPoints.splice(rightIndex, 1)[0]
+          //       const newCol = Math.round(groupPos.y / 50)
+          //       const newRow = Math.round(groupPos.x / 50)
+          //       const boundedCol = Math.max(0, Math.min(newCol, Math.floor(860 / 50) - 1))
+          //       const boundedRow = Math.max(0, Math.min(newRow, Math.floor(1200 / 50) - 1))
+          //
+          //       const newPoint = {
+          //         ...movedPoint,
+          //         row: boundedRow,
+          //         col: boundedCol,
+          //         x: boundedRow,
+          //         y: boundedCol,
+          //         mapId: this.form.mapId,
+          //         mapType: this.form.mapType,
+          //       }
+          //
+          //       positions.push(newPoint)
+          //
+          //       if (!this.leftPoints.some(p => p.entityName === movedPoint.entityName)) {
+          //         this.leftPoints.push(movedPoint)
+          //       }
+          //       this.bindingPointIds.push(movedPoint.entityId)
+          //       this.unbindPointIds = this.unbindPointIds.filter(id => id !== movedPoint.entityId)
+          //
+          //       // ✅ 判断 row/col/x/y 不为 0 再 push
+          //       if (!(newPoint.row == 0 && newPoint.col == 0 && newPoint.x == 0 && newPoint.y == 0)) {
+          //         if (!this.movePoints) this.movePoints = []
+          //         const existIdx = this.movePoints.findIndex(p => p.pointId === newPoint.pointId)
+          //         if (existIdx !== -1) {
+          //           this.movePoints.splice(existIdx, 1, newPoint)
+          //         } else {
+          //           this.movePoints.push(newPoint)
+          //         }
+          //       }
+          //     }
+          //   }
+          //
+          //   // 清理无效数据
+          //   this.rightPoints = (this.rightPoints || []).filter(Boolean)
+          //   this.leftPoints = (this.leftPoints || []).filter(Boolean)
+          //
+          //   this.value = JSON.stringify(positions, null, 4)
+          //   this.layer.draw()
+          //
+          //   console.log('Updated positions:', positions)
+          //   console.log('MovePoints:', this.movePoints)
+          // })
+// 添加绿灯或红灯闪烁动画
+          this.addBlinkAnimation(light, isRed,isGrey);
+        }
+      })
+    },
+    // 全局控制闪烁频率同步函数
+    startGlobalBlinkTimer() {
+      if (this.globalBlinkTimer) return; // 已经启动了
+
+      this.globalBlinkTimer = setInterval(() => {
+        const currentSecond = Math.floor(Date.now() / 200) % 2;
+        const isOn = currentSecond === 1;
+
+        this.blinkLights.forEach(light => {
+          light.opacity(isOn ? 1 : 0.6);
+          light.scale({ x: isOn ? 1 : 1.1, y: isOn ? 1 : 1.1 });
+        });
+
+        if (this.blinkLights.length > 0) {
+          this.blinkLights[0].getLayer().batchDraw(); // 统一刷新一次就行
+        }
+      }, 50);
+    },
+    // 绿灯动画
+    addBlinkAnimation(light, isRed, isGrey) {
+      if (!isGrey&&!isRed) {
+        if (!this.blinkLights.includes(light)) {
+          this.blinkLights.push(light);
+        }
+        this.startGlobalBlinkTimer();
+      }
+    },
+
+    // 左侧的列表 现在左侧列表通过地图点位接口获取pointList里直接有左侧的数据 不用再去隔离点管理接口拿数据
+    addPointsToLeftPointsBox(filterData) {
+      // 获取接口返回的 leftPoints 数据
+      const pointsData = filterData
+
+      let row = 1 // 当前行
+      let col = 1 // 当前列
+
+      // 遍历 pointsData 并根据是否存在于 this.value 中来决定位置
+      pointsData.forEach((point) => {
+        const existingPoint = JSON.parse(this.value).find(
+          (item) => item.pointId == point.pointId
+        )
+
+        // 如果该点在 this.value 中,使用它的原始位置
+        if (existingPoint) {
+          point.row = existingPoint.row
+          point.col = existingPoint.col
+        } else {
+          // 否则,按顺序从 (0, 0) 位置开始,每行三个点
+          point.row = 0
+          point.col = 0
+
+        }
+        // 渲染该点
+        this.renderPoint(point)
+      })
+    },
+
+    // 渲染每个点
+    renderPoint(point) {
+
+      const x = point.col * 50 // 每个单元格宽度为50
+      const y = point.row * 50 // 每个单元格高度为50
+
+      const labelText = point.pointName // 对应的文字
+      const pointImage = new Image()
+      pointImage.src = point.pointIcon
+
+      pointImage.onload = () => {
+        // 创建一个新的 Group 来包含整个隔离点
+        const group = new Konva.Group({
+          x: x,
+          y: y,
+          draggable: true // 设置为可拖拽
+        })
+
+        // 背景矩形
+        const bgrect = new Konva.Rect({
+          x: -6,
+          y: -5,
+          width: 62,
+          height: 80,
+          cornerRadius: 5,
+          stroke: 'white',
+          strokeWidth: 2,
+          fill: 'white'
+        })
+
+        // 普通矩形
+        const rect = new Konva.Rect({
+          x: 0,
+          y: -1,
+          width: 50,
+          height: 72,
+          cornerRadius: 5,
+          stroke: 'red',
+          strokeWidth: 2,
+          fill: 'white'
+        })
+
+        // 图片
+        const knovaImage = new Konva.Image({
+          x: 0,
+          y: 0,
+          image: pointImage,
+          width: 50,
+          height: 50
+        })
+
+        // 文字
+        const text = new Konva.Text({
+          x: 8,
+          y: 50,
+          fontSize: 17,
+          text: labelText,
+          fontFamily: 'Calibri',
+          fill: 'red'
+        })
+
+        // 将所有元素添加到 group 中
+        group.add(bgrect)
+        group.add(rect)
+        group.add(knovaImage)
+        group.add(text)
+
+        // 将 group 添加到 layer
+        this.layer.add(group)
+
+        // 定义右侧盒子的范围
+        const rightBoxBounds = {
+          x: 1100,
+          y: 15,
+          width: 200,
+          height: 800
+        }
+
+        // 定义物资柜的范围
+        const cabinetBounds = {
+          x: 330,
+          y: 10,
+          width: 500,
+          height: 790
+        }
+
+        // 处理拖拽事件
+        // group.on('dragend', () => {
+        //   const groupPos = group.getAbsolutePosition()
+        //   const isInRightBox =
+        //     groupPos.x >= rightBoxBounds.x &&
+        //     groupPos.x <= rightBoxBounds.x + rightBoxBounds.width &&
+        //     groupPos.y >= rightBoxBounds.y &&
+        //     groupPos.y <= rightBoxBounds.y + rightBoxBounds.height
+        //
+        //   const isInCabinet =
+        //     groupPos.x >= cabinetBounds.x &&
+        //     groupPos.x <= cabinetBounds.x + cabinetBounds.width &&
+        //     groupPos.y >= cabinetBounds.y &&
+        //     groupPos.y <= cabinetBounds.y + cabinetBounds.height
+        //
+        //   // 如果点进入右侧列表,执行删除操作
+        //   if (isInRightBox) {
+        //     this.removePointFromJson(point)
+        //   } else if (isInCabinet) {
+        //     // 如果点回到物资柜,执行更新操作
+        //     this.updatePointInJson(point, groupPos)
+        //   } else if (!isInCabinet && !isInRightBox) {
+        //     // 如果点位在物资柜外但不在右侧列表中,进行位置更新
+        //     this.updatePointInJson(point, groupPos)
+        //   }
+        // })
+
+        this.layer.draw()
+      }
+    },
+
+    // 从 json 删除对应的点
+    removePointFromJson(point) {
+      // 更新 leftPoints 和 rightPoints
+      this.rightPoints.push(point)
+      this.unbindPointIds.push(point.pointId) // 给接口传递需要解绑的数据Id
+
+      // 删除 JSON 中对应的点
+      const updatedData = JSON.parse(this.value).filter(
+        (item) => item.pointId !== point.pointId
+      )
+      this.value = JSON.stringify(updatedData, null, 4)
+
+      // console.log('Updated value after removal:', this.value)
+      console.log('removePointFromJson', updatedData)
+    },
+
+    // 更新 JSON 中对应点的位置
+    updatePointInJson(point, groupPos) {
+      // 计算新的位置
+      const newCol = Math.round(groupPos.x / 50)
+      const newRow = Math.round(groupPos.y / 50)
+
+      // 更新 positions 数组中的点位
+      const updatedPosition = {
+        row: newRow,
+        col: newCol,
+        x: newRow,
+        y: newCol,
+        pointId: point.pointId,
+        pointName: point.pointName,
+        entityId: point.pointId,
+        entityName: point.pointName,
+        remark: point.remark,
+        prePointId: point.prePointId,
+        pointType: point.pointType,
+        pointTypeName: point.pointTypeName,
+        powerType: point.powerType,
+        powerTypeName: point.powerTypeName,
+        state: point.state,
+        pointIcon: point.pointIcon,
+        pointPicture: point.pointPicture,
+        mapImg: point.mapImg,
+        mapId: this.form.mapId,
+        mapType: this.form.mapType,
+        mapName:'你好5'
+      }
+
+      let positions = JSON.parse(this.value)
+      const index = positions.findIndex(
+        (item) => item.pointId === point.pointId
+      )
+      if (index !== -1) {
+        // positions[index] = updatedPosition;
+        // this.value = JSON.stringify(positions, null, 4);
+        const updatedPositionCopy = JSON.parse(JSON.stringify(updatedPosition))
+        positions[index] = updatedPositionCopy
+
+        console.log(updatedPositionCopy, positions[index], 'updatedPosition-1')
+        this.value = JSON.stringify(positions, null, 4)
+      } else {
+        // 如果点位不在 this.value 中,则重新插入
+        positions.push(updatedPosition)
+        this.value = JSON.stringify(positions, null, 4)
+      }
+
+      // console.log('Updated value after update:', this.value)
+    },
+    // 解绑隔离点函数
+    addPointsToRightPointsBox(rightPointsBox) {
+      if (this.rightPoints && this.rightPoints.length > 0) {
+        const boxWidth = rightPointsBox.width()
+        const boxHeight = rightPointsBox.height()
+        const boxX = rightPointsBox.x()
+        const boxY = rightPointsBox.y()
+        const padding = 10 // 每个隔离点之间的间距
+        const pointWidth = 50 // 每个隔离点的宽度
+        const pointHeight = 70 // 每个隔离点的高度(包括图片和文字)
+
+        let currentX = boxX + padding
+        let currentY = boxY + padding
+        const rightBoxBounds = {
+          x: 1100,
+          y: 15,
+          width: 200,
+          height: 800
+        }
+        const cabinetBounds = {
+          x: 330,
+          y: 10,
+          width: 500,
+          height: 790
+        }
+
+        this.rightPoints.forEach((point) => {
+          // 创建一个组来组合红色边框、图片和文字
+          const group = new Konva.Group({
+            x: currentX,
+            y: currentY + 14,
+            draggable: true // 启用拖拽功能
+          })
+
+          // 创建红色边框
+          const borderRect = new Konva.Rect({
+            x: 0,
+            y: 0,
+            width: pointWidth,
+            height: pointHeight,
+            cornerRadius: 5,
+            stroke: 'red',
+            strokeWidth: 2,
+            fill: 'white'
+          })
+          group.add(borderRect)
+
+          // 创建图片
+          const image = new Image()
+          image.src = point.pointIcon
+          image.onload = () => {
+            const knovaImage = new Konva.Image({
+              x: 1, // 图片在组内的位置
+              y: 5, // 图片在组内的位置
+              image: image,
+              width: 50, // 图片宽度
+              height: 50 // 图片高度
+            })
+            group.add(knovaImage)
+
+            // 创建文字
+            const pointText = new Konva.Text({
+              x: 12, // 文字在组内的位置
+              y: 53, // 文字在组内的位置
+              text: point.pointName,
+              fontSize: 12,
+              fill: 'red'
+            })
+            group.add(pointText)
+
+            // 将组添加到图层
+            this.layer.add(group)
+            this.groups[point.pointName] = group // 用文字作为键存储
+
+            // 监听组的拖拽移动事件
+            group.on('dragmove', () => {
+              // 获取当前组的位置
+              const groupPos = group.getAbsolutePosition()
+              // 更新组的位置
+              group.x(groupPos.x)
+              group.y(groupPos.y)
+            })
+
+            // 监听组的拖拽结束事件
+            group.on('dragend', () => {
+              const gridX = 50 // 网格单元格宽度
+              const gridY = 50 // 网格单元格高度
+
+              // 计算最近的网格点位置
+              const snappedX = Math.round(group.x() / gridX) * gridX
+              const snappedY = Math.round(group.y() / gridY) * gridY
+
+              // 设置组到最近的网格点位置
+              group.x(snappedX)
+              group.y(snappedY)
+
+              // 计算网格坐标
+              const row = Math.floor(snappedY / gridY)
+              const col = Math.floor(snappedX / gridX)
+
+              // 更新点位数据
+              const updatedPointData = {
+                row: row,
+                col: col,
+                pointId: point.pointId,
+                entityId: point.entityId,
+                entityName: point.entityName,
+                pointName: point.pointName,
+                remark: point.remark,
+                prePointId: point.prePointId,
+                pointType: point.pointType,
+                pointTypeName: point.pointTypeName,
+                powerType: point.powerType,
+                powerTypeName: point.powerTypeName,
+                state: point.status,
+                pointIcon: point.pointIcon,
+                pointPicture: point.pointPicture,
+                mapImg: null,
+                mapId: point.mapId,
+                mapName:'你好6',
+                mapType: point.mapType,
+              }
+
+              // 解析 this.value 为数组
+              let valueArray = []
+              try {
+                valueArray = JSON.parse(this.value)
+              } catch (e) {
+                console.error('Failed to parse value:', e)
+              }
+
+              // 判断拖拽目标区域
+              if (
+                snappedX >= rightBoxBounds.x &&
+                snappedX <= rightBoxBounds.x + rightBoxBounds.width &&
+                snappedY >= rightBoxBounds.y &&
+                snappedY <= rightBoxBounds.y + rightBoxBounds.height
+              ) {
+                // 进入右侧盒子区域
+                console.log('进入右侧盒子区域')
+// 更新 point 对象的 col 和 row 值
+                point.row = col
+                point.col = row
+                point.x = col
+                point.y = row
+                // 如果之前已在右侧区域,需要移除值,并更新绑定点ID
+                const index = valueArray.findIndex(
+                  (item) => item.pointId === point.pointId
+                )
+                if (index !== -1) {
+                  valueArray.splice(index, 1) // 从 valueArray 中删除该点
+                }
+                this.value = JSON.stringify(valueArray, null, 4)
+
+                // 删除绑定点
+                this.bindingPointIds = this.bindingPointIds.filter(
+                  (id) => id !== point.pointId
+                )
+
+                // 将该点加入解绑点ID
+                this.unbindPointIds.push(point.pointId)
+
+                // 将点重新添加到 rightPoints
+                this.rightPoints.push(point)
+              }
+                //   这里的if判断是为了 移动在物资柜内部才做的操作 现在我只要离开右侧 就做这个操作
+                //    if (
+                //   snappedX >= cabinetBounds.x &&
+                //   snappedX <= cabinetBounds.x + cabinetBounds.width &&
+                //   snappedY >= cabinetBounds.y &&
+                //   snappedY <= cabinetBounds.y + cabinetBounds.height
+              // )
+              else if (snappedX < rightBoxBounds.x ||
+                snappedX > rightBoxBounds.x + rightBoxBounds.width ||
+                snappedY < rightBoxBounds.y ||
+                snappedY > rightBoxBounds.y + rightBoxBounds.height
+              ) {
+
+                // 进入物资柜区域
+                console.log('进入物资柜区域')
+                // 更新 point 对象的 col 和 row 值
+                point.row = col
+                point.col = row
+                point.x = col
+                point.y = row
+                // 检查点是否已经存在于 valueArray 中
+                const index = valueArray.findIndex(
+                  (item) => item.pointId === point.pointId
+                )
+                if (index === -1) {
+                  // 如果点位不存在,则新增
+                  valueArray.push(updatedPointData)
+                  this.value = JSON.stringify(valueArray, null, 4)
+                }
+
+                // 添加到绑定点ID数组
+                this.bindingPointIds.push(point.pointId)
+
+                // 从右侧盒子移除点
+                this.rightPoints = this.rightPoints.filter(
+                  (item) => item.pointId !== point.pointId
+                )
+                this.leftPoints.push(point)
+                console.log(point, '进入左侧区域进行隔离点绑定操作!')
+              } else {
+                // 未进入任何目标区域,保持原状态
+                console.log('未进入目标区域,保持原状态')
+                // 更新 point 对象的 col 和 row 值
+                point.row = col
+                point.col = row
+                point.x = col
+                point.y = row
+                // 如果点不在目标区域,从 valueArray 中移除
+                // this.value = JSON.stringify(
+                //   valueArray.filter((item) => item.pointId !== point.pointId),
+                //   null,
+                //   4
+                // );
+                // 检查点是否已经存在于 valueArray 中
+                const index = valueArray.findIndex(
+                  (item) => item.pointId === point.pointId
+                )
+                if (index === -1) {
+                  // 如果点位不存在,则新增
+                  valueArray.push(updatedPointData)
+                  this.value = JSON.stringify(valueArray, null, 4)
+                }
+
+                // 添加到绑定点ID数组
+                this.bindingPointIds.push(point.pointId)
+
+                // 从右侧盒子移除点
+                this.rightPoints = this.rightPoints.filter(
+                  (item) => item.pointId !== point.pointId
+                )
+                // 如果点不在目标区域,重新添加到 rightPoints
+                this.rightPoints.push(point)
+              }
+
+              // 重新绘制图层
+              this.layer.draw()
+            })
+
+            // 重新绘制图层
+            this.layer.draw()
+          }
+
+          // 更新下一个隔离点的位置
+          currentX += pointWidth + padding
+          if (currentX + pointWidth > boxX + boxWidth) {
+            currentX = boxX + padding
+            currentY += pointHeight + padding
+          }
+        })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+#container {
+  width: 100%;
+  height: 100%;
+}
+
+.mapdata {
+  width: 100%;
+  height: 100%;
+  display: flex;
+}
+
+.left {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+  margin-bottom: 20px;
+}
+</style>

+ 507 - 0
src/views/mes/dv/switchmanagement/index.vue

@@ -0,0 +1,507 @@
+<template>
+  <div class="app-container">
+    <el-form
+      :model="queryParams"
+      ref="queryForm"
+      size="small"
+      :inline="true"
+      v-show="showSearch"
+      label-width="100px"
+      @submit.native.prevent
+    >
+      <el-row>
+        <el-form-item label="布局名称" prop="lotoName">
+          <el-input
+            v-model="queryParams.lotoName"
+            placeholder="请输入布局名称"
+            clearable
+            @keyup.enter.native="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="岗位" prop="workstationId" >
+          <treeselect style="width: 200px" v-model="queryParams.workstationId" :options="marsOptions" :normalizer="Marsnormalizer" placeholder="请选择岗位"/>
+        </el-form-item>
+
+        <el-form-item style="margin-left: 20px">
+          <el-button v-no-more-click
+                     type="primary"
+                     icon="el-icon-search"
+                     size="mini"
+                     @click="handleQuery"
+          >搜索
+          </el-button>
+          <el-button v-no-more-click icon="el-icon-refresh" size="mini" @click="resetQuery"
+          >重置
+          </el-button>
+        </el-form-item>
+      </el-row>
+    </el-form>
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button v-no-more-click
+                   type="primary"
+                   plain
+                   icon="el-icon-plus"
+                   size="mini"
+                   @click="handleAdd"
+                   v-hasPermi="['iscs:switchmap:add']"
+        >新增
+        </el-button>
+      </el-col>
+
+      <el-col :span="1.5">
+        <el-button v-no-more-click
+                   type="danger"
+                   plain
+                   icon="el-icon-delete"
+                   size="mini"
+                   :disabled="multiple"
+                   @click="handleDelete"
+                   v-hasPermi="['iscs:switchmap:remove']"
+        >批量删除
+        </el-button>
+      </el-col>
+      <right-toolbar
+        :showSearch.sync="showSearch"
+        @queryTable="getList"
+      ></right-toolbar>
+    </el-row>
+
+    <el-table
+      v-loading="loading"
+      :data="repairList"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" align="center" />
+<!--      <el-table-column-->
+<!--        label="布局编码"-->
+<!--        width="150px"-->
+<!--        align="center"-->
+<!--        prop="lotoCode"-->
+<!--      />-->
+      <el-table-column
+        label="布局名称"
+        width="180px"
+        align="center"
+        prop="switchMapName"
+      />
+      <el-table-column
+        label="排序"
+        width="180px"
+        align="center"
+        prop="orderNum"
+      />
+      <el-table-column label="岗位" align="center" prop="workstationName" />
+      <el-table-column label="地图名称" align="center" prop="mapName" />
+      <el-table-column label="所属硬件序列号" align="center" prop="serialNumber"></el-table-column>
+      <el-table-column
+        label="布局详情"
+        align="center"
+        prop="create"
+        width="200"
+      >
+        <template slot-scope="scope">
+          <el-button
+            v-no-more-click
+            size="mini"
+            type="text"
+            @click="look(scope.row)"
+          >查看
+          </el-button>
+        </template>
+      </el-table-column>
+
+      <el-table-column
+        label="操作"
+        align="center"
+        class-name="small-padding fixed-width"
+        fixed="right"
+      >
+        <template slot-scope="scope">
+          <el-button v-no-more-click
+                     size="mini"
+                     type="text"
+                     icon="el-icon-edit"
+                     @click="handleUpdate(scope.row)"
+                     v-hasPermi="['iscs:switchmap:edit','iscs:switchmap:query']"
+          >修改
+          </el-button>
+          <el-button v-no-more-click
+                     size="mini"
+                     type="text"
+                     icon="el-icon-delete"
+                     @click="handleDelete(scope.row)"
+                     v-hasPermi="['iscs:switchmap:remove']"
+          >删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.current"
+      :limit.sync="queryParams.size"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改设备维修单对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="450px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+        <el-form-item label="布局名称" prop="switchMapName">
+          <el-input v-model="form.switchMapName" placeholder="请输入布局名称" />
+        </el-form-item>
+
+        <el-form-item label="排序" prop="orderNum">
+          <el-input-number type="number" v-model="form.orderNum" />
+        </el-form-item>
+        <el-form-item label="岗位" prop="workstationId" >
+          <treeselect v-model="form.workstationId" :options="marsOptions" :normalizer="Marsnormalizer" placeholder="选择岗位"/>
+        </el-form-item>
+        <el-form-item label="所属硬件" prop="serialNumber">
+          <el-select
+            v-model="form.serialNumber"
+            placeholder="请选择所属硬件"
+            clearable
+          >
+            <el-option
+              v-for="dict in this.hardWareList"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="地图名称" prop="mapId">
+          <el-select style="width:290px" v-model="form.mapId" placeholder="地图名称">
+            <el-option v-for="item in this.MapOptions" :key="item.id" :label="item.name" :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="布局信息" prop="map">
+          <el-input
+            v-model="form.map"
+            placeholder="请输入布局信息"
+
+          />
+        </el-form-item>
+
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button v-no-more-click type="primary" @click="submitForm">确 定</el-button>
+        <el-button v-no-more-click @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  getIsLotoSwitchMapPage,
+  selectIsLotoSwitchMapById,
+  insertIsLotoSwitchMap,
+  updateIsLotoSwitchMap,
+  deleteIsLotoSwitchMapBySwitchMapIds
+} from "@/api/mes/switchmanagement/switchmanagement";
+import { genCode } from "@/api/system/autocode/rule";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import { listMarsDept } from '@/api/system/marsdept'
+import { listTechnology } from '@/api/system/machinery'
+import { getIsMapPage } from '@/api/system/mapconfig'
+import { listHardware } from '@/api/mes/hw/hardwareinfo'
+export default {
+  name: "lock",
+  dicts: ["hardware_status"],
+  components: { Treeselect },
+  data() {
+    return {
+      autoGenFlag: false,
+      optType: undefined,
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      codes: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // mars树选项
+      marsOptions: [],
+      // 总条数
+      total: 0,
+      // 设备维修单表格数据
+      repairList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      createTime: "",
+      queryParams: {
+        current: 1,
+        size: 10,
+        switchMapCode: null,
+        switchMapName: null,
+
+      },
+      // 表单参数
+      form: {
+      },
+      // 地图名称 下拉
+      MapOptions: null,
+      hardWareList:[],//所属硬件
+      // 表单校验
+      rules: {
+        switchMapCode: [
+          { required: true, message: "布局编码不能为空", trigger: "blur" },
+        ],
+        switchMapName: [
+          { required: true, message: "布局名称不能为空", trigger: "blur" },
+        ],
+      },
+      // 日期选择
+      pickerOptions: {
+        shortcuts: [
+          {
+            text: "最近一周",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+              picker.$emit("pick", [start, end]);
+            },
+          },
+          {
+            text: "最近一个月",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+              picker.$emit("pick", [start, end]);
+            },
+          },
+          {
+            text: "最近三个月",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+              picker.$emit("pick", [start, end]);
+            },
+          },
+        ],
+      },
+      EditId: 0, //修改判断
+      machinerytypeOptions: [], //锁具机构类型
+      machineryOptions:[],//查询 设备工艺数据
+    };
+  },
+  created() {
+    this.getList();
+    this.getOtherList()
+  },
+  methods: {
+
+    //自动生成编码
+    handleAutoGenChange(autoGenFlag) {
+      if (autoGenFlag) {
+        genCode("LOTO_STATION_CODE").then((response) => {
+          this.form.lotoCode = response;
+        });
+      } else {
+        this.form.lotoCode = null;
+      }
+    },
+    /** 查询设备维修单列表 */
+
+    getList() {
+      this.loading = true;
+
+      // 布局数据
+      getIsLotoSwitchMapPage(this.queryParams).then((response) => {
+        console.log(response,'布局')
+        this.repairList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    getOtherList(){
+      const data={
+        current:1,
+        size:-1
+      }
+      // 获取地图类型 布局or地图
+      getIsMapPage(data).then(response => {
+        this.MapOptions = response.data.records
+      })
+      // 岗位
+      listMarsDept(data).then(response => {
+        this.marsOptions = this.handleTree(response.data.records,"workstationId","parentId")
+      })
+
+      // 设备/工艺
+      listTechnology(data).then((response) => {
+        const data=response.data.records.filter((item) =>item.machineryType=='工艺')
+        this.machineryOptions = this.handleTree(
+          data,
+          "machineryId",
+          "parentId"
+        );
+
+      });
+      listHardware(data).then((response) => {
+        this.hardWareList = response.data.records.map((item) => {
+          return {
+            value: item.serialNumber,
+            label: item.hardwareName,
+          };
+        });
+      })
+
+    },
+    /** 转换部门数据结构 */
+    Marsnormalizer(node) {
+      if (node.children && !node.children.length) {
+        delete node.children
+      }
+      return {
+        id: node.workstationId,
+        label: node.workstationName,
+        children: node.children
+      }
+    },
+
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        lotoName: null,
+        workstationId: null,
+        orderNum: null,
+        map:null,
+        lotoSerialNumber: null,
+      };
+
+      this.autoGenFlag = false;
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.current = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.queryParams.switchMapName = "";
+      this.queryParams.switchMapId = "";
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map((item) => item.switchMapId);
+      this.codes = selection.map((item) => item.switchMapName);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.EditId = null;
+      this.title = "新增布局信息";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      this.EditId = row.switchMapId || this.ids;
+      selectIsLotoSwitchMapById(this.EditId).then((response) => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改布局信息";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate((valid) => {
+        if (valid) {
+          if (this.EditId != null) {
+            updateIsLotoSwitchMap(this.form).then((response) => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            // console.log(this.form, "form");
+            insertIsLotoSwitchMap(this.form).then((response) => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.EditId = null;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    // 地图预览
+    look(row) {
+      console.log(row, "row预览");
+      const data = row.switchMapId;
+      this.$router.push(`/mes/dv/switchmanagement/index/LookDetail?switchMapId=${data}`);
+      // this.dialogVisibleMap = true; // 显示地图预览弹框
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const repairIds = row.switchMapId || this.ids;
+      const repairCodes = row.switchMapName || this.codes;
+      this.$modal
+        .confirm('是否确认删除所选数据项?')
+        .then(function () {
+          return deleteIsLotoSwitchMapBySwitchMapIds(repairIds);
+        })
+        .then(() => {
+          this.getList();
+          this.$modal.msgSuccess("删除成功");
+        })
+        .catch(() => {});
+    },
+  },
+};
+</script>
+<style scoped>
+.imgstatus {
+  position: relative;
+  top: 1px;
+  left: 0px;
+}
+
+/deep/ .el-radio__inner {
+  border-radius: 2px;
+}
+
+/deep/ .el-radio__input.is-checked .el-radio__inner::after {
+  content: "";
+  width: 8px;
+  height: 3px;
+  border: 1px solid white;
+  border-top: transparent;
+  border-right: transparent;
+  text-align: center;
+  display: block;
+  position: absolute;
+  top: 3px;
+  left: 2px;
+  transform: rotate(-45deg);
+  border-radius: 0pc;
+  background: none;
+}
+</style>

+ 1 - 1
vue.config.js

@@ -35,7 +35,7 @@ module.exports = {
     proxy: {
       // detail: https://cli.vuejs.org/config/#devserver-proxy
       [process.env.VUE_APP_BASE_API]: {
-        // http://36.133.174.236:9090  http://192.168.1.127:9090 http://192.168.31.110:9090(动感光波) 192.168.28.97:9090(车总GRKJ) 120.27.232.27:9190(东上阿里云)
+        // http://36.133.174.236:9090  http://192.168.0.10:9190 http://192.168.31.110:9090(动感光波) 192.168.28.97:9090(车总GRKJ) 120.27.232.27:9190(东上阿里云)
         target: `http://192.168.0.10:9190`,
         changeOrigin: true,
         pathRewrite: {