From 8c1b3d5463802ce43400fc83479641af9a298a38 Mon Sep 17 00:00:00 2001
From: donghao <donghao@supervision.ltd>
Date: Thu, 15 Aug 2024 18:00:23 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=BE=E5=A4=87=E5=85=B3=E8=81=94?=
 =?UTF-8?q?=E5=B8=83=E7=82=B9=E6=8E=A5=E5=8F=A3=E8=81=94=E8=B0=83=E5=AE=8C?=
 =?UTF-8?q?=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/api/workshops.ts                          |  16 +-
 src/core/plugin/ControlsPlugin.ts             |  14 +-
 src/core/plugin/DeleteHotKeyPlugin.ts         |   3 +-
 .../deviceSetting/components/deviceAttr.vue   | 104 +++++++++----
 .../deviceSetting/components/deviceSelect.vue | 140 +++++++++++-------
 .../deviceSetting/hooks/useDeviceObject.ts    |  47 +++---
 .../deviceSetting/hooks/usePointObject.ts     |  23 ++-
 src/views/deviceSetting/index.scss            |  15 +-
 src/views/deviceSetting/index.vue             | 131 +++++++++++-----
 9 files changed, 338 insertions(+), 155 deletions(-)

diff --git a/src/api/workshops.ts b/src/api/workshops.ts
index 642716d..e5bda0a 100644
--- a/src/api/workshops.ts
+++ b/src/api/workshops.ts
@@ -2,7 +2,7 @@
  * @Author: donghao donghao@supervision.ltd
  * @Date: 2024-08-02 10:40:49
  * @LastEditors: donghao donghao@supervision.ltd
- * @LastEditTime: 2024-08-14 17:09:46
+ * @LastEditTime: 2024-08-15 16:14:24
  * @FilePath: \General-AI-Platform-Web-Client\src\api\workshops.ts
  * @Description: 布点
  */
@@ -75,3 +75,17 @@ export const addWorkshopDevicesApi = (data?: object) => {
     data
   });
 };
+
+/** 编辑布点设备 data有id */
+export const editWorkshopDevicesApi = (data?: object) => {
+  return http.request<Result>("post", baseUrlApi("workshop_devices/"), {
+    data
+  });
+};
+
+/** 删除布点设备 status: 1 */
+export const deleteWorkshopDevicesApi = (data?: object) => {
+  return http.request<Result>("post", baseUrlApi("workshop_devices/"), {
+    data: { ...data, status: 1 }
+  });
+};
diff --git a/src/core/plugin/ControlsPlugin.ts b/src/core/plugin/ControlsPlugin.ts
index 13df367..284b8cf 100644
--- a/src/core/plugin/ControlsPlugin.ts
+++ b/src/core/plugin/ControlsPlugin.ts
@@ -2,7 +2,7 @@
  * @Author: 秦少卫
  * @Date: 2023-06-13 23:00:43
  * @LastEditors: donghao donghao@supervision.ltd
- * @LastEditTime: 2024-08-07 17:17:54
+ * @LastEditTime: 2024-08-15 16:28:40
  * @Description: 控制条插件
  */
 
@@ -174,9 +174,15 @@ function deleteControl(canvas: fabric.Canvas) {
     if (target.action === "rotate") return true;
     const activeObject = canvas.getActiveObjects();
     if (activeObject) {
-      activeObject.map(item => canvas.remove(item));
-      canvas.requestRenderAll();
-      canvas.discardActiveObject();
+      // 发送删除事件
+      canvas.fire("object:deleteObject", {
+        ...activeObject,
+        deleteCallback: () => {
+          activeObject.map(item => canvas.remove(item));
+          canvas.requestRenderAll();
+          canvas.discardActiveObject();
+        }
+      });
     }
     return true;
   }
diff --git a/src/core/plugin/DeleteHotKeyPlugin.ts b/src/core/plugin/DeleteHotKeyPlugin.ts
index c839510..29fb60b 100644
--- a/src/core/plugin/DeleteHotKeyPlugin.ts
+++ b/src/core/plugin/DeleteHotKeyPlugin.ts
@@ -2,12 +2,13 @@
  * @Author: 秦少卫
  * @Date: 2023-06-20 12:57:35
  * @LastEditors: donghao donghao@supervision.ltd
- * @LastEditTime: 2024-08-06 17:14:38
+ * @LastEditTime: 2024-08-15 15:51:16
  * @Description: 删除快捷键
  */
 
 import { fabric } from "fabric";
 import Editor from "../core";
+
 type IEditor = Editor;
 // import { v4 as uuid } from 'uuid';
 
diff --git a/src/views/deviceSetting/components/deviceAttr.vue b/src/views/deviceSetting/components/deviceAttr.vue
index 32f385e..3b604ca 100644
--- a/src/views/deviceSetting/components/deviceAttr.vue
+++ b/src/views/deviceSetting/components/deviceAttr.vue
@@ -6,34 +6,45 @@ import { textTypeConf } from "@/config/attribute/baseType";
 import video_type_1 from "@/assets/modelSetting/video_type_1.png";
 import video_type_2 from "@/assets/modelSetting/video_type_2.png";
 import video_type_3 from "@/assets/modelSetting/video_type_3.png";
+import {
+  deleteWorkshopDevicesApi,
+  editWorkshopDevicesApi
+} from "@/api/workshops";
+import { isSuccessApi } from "@/utils/forApi";
+
+const props = defineProps({
+  pointInfo: {
+    type: Object as Record<string, any>
+  }
+});
 
 const event = inject("event");
 const update = getCurrentInstance();
 const { mixinState, canvasEditor } = useSelect();
 /**业务属性 */
 const formData = ref({
-  iconType: 1
+  icon: 1
 });
 
 const rules = {
-  iconType: [{ required: true, message: "请选择图标类型", trigger: "change" }]
+  icon: [{ required: true, message: "请选择图标类型", trigger: "change" }]
 };
 
 const iconOptions = ref([
   {
-    value: 1,
+    value: "1",
     label: "图标1",
     type: "1",
     url: video_type_1
   },
   {
-    value: 2,
+    value: "2",
     label: "图标2",
     type: "2",
     url: video_type_2
   },
   {
-    value: 3,
+    value: "3",
     label: "图标3",
     type: "3",
     url: video_type_3
@@ -181,10 +192,43 @@ const getObjectAttr = e => {
       const animateObject = activeObject.get("animation");
       animationAttr.type = animateObject.type;
     }
+    // 设备相关属性
+    if (activeObject.get("deviceInfo")) {
+      formData.value = activeObject.get("deviceInfo");
+    }
   }
 };
+// 删除当前集合
+async function deleteDeviceObject(deleteObject) {
+  const currDeviceInfo = deleteObject[0].get("deviceInfo");
+  console.log(currDeviceInfo, "deleteDeviceObject", deleteObject);
+  const resp = await deleteWorkshopDevicesApi({
+    id: currDeviceInfo.id
+  });
+  if (isSuccessApi(resp)) {
+    deleteObject.deleteCallback();
+  }
+}
 
-const selectCancel = () => {
+// 编辑当前集合
+async function editDeviceObject(callback) {
+  const activeObject = canvasEditor.canvas.getActiveObject();
+  const currDeviceInfo = activeObject.get("deviceInfo");
+  console.log(activeObject, "editDeviceObject_activeObject", currDeviceInfo);
+  const resp = await editWorkshopDevicesApi({
+    id: currDeviceInfo.id,
+    device_id: currDeviceInfo.device_id,
+    workshop_id: currDeviceInfo.workshop_id,
+    x_ordinate: activeObject.get("left"),
+    y_ordinate: activeObject.get("top"),
+    icon: formData.value.icon
+  });
+  if (isSuccessApi(resp)) {
+    console.log("编辑完成");
+    callback(resp);
+  }
+}
+const selectCancel = e => {
   baseAttr.fill = "";
   update?.proxy?.$forceUpdate();
 };
@@ -193,10 +237,15 @@ const init = () => {
   // 获取字体数据
   event.on("selectCancel", selectCancel);
   event.on("selectOne", getObjectAttr);
+  canvasEditor.canvas.on("object:deleteObject", deleteDeviceObject);
+  canvasEditor.canvas.on("mouse:up", () => {
+    editDeviceObject();
+  });
   canvasEditor.canvas.on("object:modified", getObjectAttr);
   // 监听 object:moving 事件以限制移动范围
   canvasEditor.canvas.on("object:moving", e => {
     const activeObject = e.target;
+    // console.log(activeObject, "activeObject_moving");
     if (activeObject) {
       constrainObjectWithinCanvas(activeObject);
       canvasEditor.canvas.renderAll();
@@ -238,7 +287,7 @@ const changeCommon = (key, value) => {
     canvasEditor.canvas.renderAll();
     return;
   }
-  if (key === "iconType") {
+  if (key === "icon") {
     // 在 group 对象中查找唯一的 fabric.Image 对象
     const imageObject = activeObject._objects[1];
 
@@ -250,16 +299,18 @@ const changeCommon = (key, value) => {
 
       if (targetIcon) {
         // 修改图片路径
-        imageObject.setSrc(
-          targetIcon.url,
-          () => {
-            // 刷新画布
-            console.log("Image loaded successfully activeObject_iconType");
-            activeObject.addWithUpdate(); // 更新对象的坐标和边界框
-            canvasEditor.canvas.renderAll();
-          },
-          { crossOrigin: "anonymous" }
-        );
+        editDeviceObject(() => {
+          imageObject.setSrc(
+            targetIcon.url,
+            () => {
+              // 刷新画布
+              console.log("Image loaded successfully activeObject_iconType");
+              activeObject.addWithUpdate(); // 更新对象的坐标和边界框
+              canvasEditor.canvas.renderAll();
+            },
+            { crossOrigin: "anonymous" }
+          );
+        });
 
         // 加载新的图片并替换旧的图片对象
         // fabric.Image.fromURL(
@@ -302,12 +353,14 @@ onMounted(init);
 onBeforeUnmount(() => {
   event.off("selectCancel", selectCancel);
   event.off("selectOne", getObjectAttr);
+  canvasEditor.canvas.off("object:deleteObject");
+  canvasEditor.canvas.off("mouse:up");
   canvasEditor.canvas.off("object:modified", getObjectAttr);
   canvasEditor.canvas.off("object:moving");
 });
 </script>
 <template>
-  <div class="box" v-if="mixinState.mSelectMode === 'one'">
+  <div class="deviceAtrr_toolbar" v-if="mixinState.mSelectMode === 'one'">
     <!-- 字体属性 -->
     <div v-show="textTypeConf.includes(mixinState.mSelectOneType)">
       <!-- 字体属性 -->
@@ -334,20 +387,21 @@ onBeforeUnmount(() => {
       class="demo-form-inline"
       label-position="top"
     >
-      <el-form-item label="选择图标" class="w-full" prop="iconType">
+      <el-form-item label="选择图标" class="w-full" prop="icon">
         <el-radio-group
-          v-model="formData.iconType"
-          @change="changeCommon('iconType', formData.iconType)"
+          v-model="formData.icon"
+          @change="changeCommon('icon', formData.icon)"
         >
           <el-radio
             v-for="option in iconOptions"
             :key="option.value"
             :label="option.value"
           >
-            <i>
-              <!-- 图标 -->
-            </i>
-            {{ option.label }}
+            <el-image
+              :src="option?.url || ''"
+              :fit="'contain'"
+              class="w-[24px]"
+            />
           </el-radio>
         </el-radio-group>
       </el-form-item>
diff --git a/src/views/deviceSetting/components/deviceSelect.vue b/src/views/deviceSetting/components/deviceSelect.vue
index 380dcb2..15c3150 100644
--- a/src/views/deviceSetting/components/deviceSelect.vue
+++ b/src/views/deviceSetting/components/deviceSelect.vue
@@ -1,40 +1,10 @@
 <!--
  * @Description: 设备选择
 -->
-
-<template>
-  <div v-if="!mixinState.mSelectMode">
-    <div class="px-[12px] deviceSelect_toolbar">
-      <div>
-        <h4 class="hf-1 py-[12px]">设备列表</h4>
-        <p class="pf-2">
-          可直接点击拖拽设备名称至图中目标位置,鼠标悬停可查看设备详情
-        </p>
-        <ul class="mt-[8px] deviceSelect_list">
-          <li
-            class="flex items-center px-[16px] mb-[12px]"
-            :class="detailInfo?.id === info.id ? 'active' : ''"
-            v-for="(info, i) in props.deviceList"
-            :key="`${i}-logo1-button`"
-            :draggable="true"
-            @click="addItem(info)"
-            @dragend="event => dragItem(event, info)"
-          >
-            <span>
-              {{ info.name }}
-            </span>
-          </li>
-        </ul>
-      </div>
-    </div>
-  </div>
-</template>
-
 <script setup name="CanvasSize" lang="ts">
 // import { Modal } from "view-ui-plus";
 import useSelect from "@/hooks/select";
 // import { cloneDeep } from "lodash-es";
-import { v4 as uuid } from "uuid";
 // import { useI18n } from "vue-i18n";
 import { useDeviceObject } from "../hooks/useDeviceObject";
 
@@ -48,6 +18,8 @@ const props = defineProps({
   }
 });
 
+const emit = defineEmits(["addDevice"]);
+
 const { fabric, mixinState, canvasEditor } = useSelect();
 const { initDeviceGroupObjects } = useDeviceObject();
 // const { t } = useI18n();
@@ -104,6 +76,7 @@ canvasEditor.getMaterialType("svg").then((list: materialTypeI[]) => {
 function fetchDetail(record) {
   detailInfo.value = record;
   console.log(detailInfo.value, "fetchDetail");
+  emit("addDevice", record);
 }
 
 /**
@@ -126,7 +99,6 @@ const dragItem = (event, deviceItem) => {
         ...defaultPosition,
         shadow: "",
         fontFamily: "arial",
-        id: uuid(),
         name: "svg元素",
         ...initDeviceGroupObjects(deviceItem)
       }
@@ -143,7 +115,8 @@ const dragItem = (event, deviceItem) => {
       // var canvas = new fabric.Canvas('canvas-id');
       // canvas.add(group);
       canvasEditor.dragAddItem(event, item);
-      fetchDetail(deviceItem);
+      console.log(event, item, "dragItem");
+      // fetchDetail(deviceItem);
       // 更新Canvas以确保更改生效
       // canvas.renderAll();
     }
@@ -155,16 +128,15 @@ const addItem = deviceItem => {
   if (isValidAdd(deviceItem)) {
     return;
   }
+  console.log(deviceItem, "addItem_deviceItem");
   fabric.util.enlivenObjects(
     [
       {
         ...defaultPosition,
-        id: uuid(),
         // name: "svg元素",
         ...initDeviceGroupObjects(deviceItem)
       }
     ],
-
     function (objects) {
       // objects 包含从JSON还原的对象,这里我们期望是一个Group对象
       const item = objects[0];
@@ -189,13 +161,43 @@ const addItem = deviceItem => {
   //   const item = fabric.util.groupSVGElements(objects, {
   //     ...options,
   //     ...defaultPosition,
-  //     id: uuid(),
   //     name: "svg元素"
   //   });
 
   // });
 };
 
+// 渲染设备到点位图上
+function renderDeviceToCanvas(record) {
+  const startArr = record;
+
+  const finalArr = [];
+  startArr.map(deviceItem => {
+    const fullDeviceItem = {
+      ...defaultPosition,
+      ...deviceItem,
+      left: Number(deviceItem.x_ordinate),
+      top: Number(deviceItem.y_ordinate),
+      name: deviceItem.device_name
+    };
+    finalArr.push({
+      ...fullDeviceItem,
+      ...initDeviceGroupObjects(fullDeviceItem)
+    });
+  });
+  fabric.util.enlivenObjects([...finalArr], function (enlivenedObjects) {
+    enlivenedObjects.forEach(groupObject => {
+      if (groupObject.type === "group") {
+        // 如果对象是 group 类型,将其添加到 canvas 中
+        canvasEditor.canvas.add(groupObject);
+      }
+    });
+    // objects 包含从JSON还原的对象,这里我们期望是一个Group对象
+    const item = enlivenedObjects[0];
+    canvasEditor.canvas.setActiveObject(item);
+    canvasEditor.canvas.requestRenderAll();
+  });
+}
 // const DefaultSize = {
 //   width: 1200,
 //   height: 900
@@ -234,17 +236,16 @@ const addItem = deviceItem => {
 //     height: 1200
 //   }
 // ]);
-const DefaultSize = {
-  width: 947,
-  height: 642
-};
+// const DefaultSize = {
+//   width: 977,
+//   height: 670
+// };
 onMounted(() => {
-  canvasEditor.setSize(DefaultSize.width, DefaultSize.height);
-  canvasEditor.on("sizeChange", (width, height) => {
-    width.value = width;
-    height.value = height;
-  });
-
+  // canvasEditor.setSize(DefaultSize.width, DefaultSize.height);
+  // canvasEditor.on("sizeChange", (width, height) => {
+  //   width.value = width;
+  //   height.value = height;
+  // });
   // canvas.editor.editorWorkspace.setSize(width.value, height.value);
   // canvas.editor.editorWorkspace = new EditorWorkspace(canvas.c, {
   //   width: width.value,
@@ -272,17 +273,45 @@ onMounted(() => {
 //   handleClose();
 // };
 
-watch(
-  () => props.deviceList,
-  () => {
-    console.log(props.deviceList, "watch_deviceList");
-  },
-  {
-    immediate: true,
-    deep: true
-  }
-);
+// watch(
+//   () => props.deviceList,
+//   () => {
+//     console.log(props.deviceList, "watch_deviceList");
+//   },
+//   {
+//     immediate: true,
+//     deep: true
+//   }
+// );
+defineExpose({ renderDeviceToCanvas });
 </script>
+<template>
+  <div v-if="!mixinState.mSelectMode">
+    <div class="px-[12px] deviceSelect_toolbar">
+      <div>
+        <h4 class="hf-1 py-[12px]">设备列表</h4>
+        <p class="pf-2">
+          可直接点击拖拽设备名称至图中目标位置,鼠标悬停可查看设备详情
+        </p>
+        <ul class="mt-[8px] deviceSelect_list">
+          <li
+            class="flex items-center px-[16px] mb-[12px]"
+            :class="detailInfo?.id === info.id ? 'active' : ''"
+            v-for="(info, i) in props.deviceList"
+            :key="`${i}-logo1-button`"
+            :draggable="true"
+            @click="addItem(info)"
+            @dragend="event => dragItem(event, info)"
+          >
+            <span>
+              {{ info.name }}
+            </span>
+          </li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</template>
 
 <style scoped lang="scss">
 .search-box {
@@ -312,4 +341,3 @@ watch(
   margin-bottom: 10px;
 }
 </style>
-../hooks/useWatchModels../hooks/useDeviceObject
diff --git a/src/views/deviceSetting/hooks/useDeviceObject.ts b/src/views/deviceSetting/hooks/useDeviceObject.ts
index 2ffe938..945b9e6 100644
--- a/src/views/deviceSetting/hooks/useDeviceObject.ts
+++ b/src/views/deviceSetting/hooks/useDeviceObject.ts
@@ -4,31 +4,32 @@
  */
 // https://t7.baidu.com/it/u=2757924858,1404466263&fm=193
 import video_type_1 from "@/assets/modelSetting/video_type_1.png";
+import video_type_2 from "@/assets/modelSetting/video_type_2.png";
+import video_type_3 from "@/assets/modelSetting/video_type_3.png";
+import { v4 as uuid } from "uuid";
 
 export const useDeviceObject = () => {
-  const initDeviceGroupObjects: Record<string, any> = (record: {
-    id: string;
-    value: string;
-  }) => {
+  const initDeviceGroupObjects: Record<string, any> = record => {
     console.log(record, "initDeviceGroupObjects");
     // const { value } = record;
-    // let watchIconObject = watchIcon2;
-    // switch (value) {
-    //   case "watchError":
-    //     watchIconObject = watchIcon1;
-    //     break;
-    //   case "watchOnline":
-    //     watchIconObject = watchIcon2;
-    //     break;
-    //   case "watchOutline":
-    //     watchIconObject = watchIcon3;
-    //     break;
-    //   case "watchWarn":
-    //   default:
-    //     watchIconObject = watchIcon4;
-    //     break;
-    // }
+    let iconObjectSrc = video_type_1;
+    switch (record?.icon) {
+      case "3":
+        iconObjectSrc = video_type_3;
+        break;
+      case "2":
+        iconObjectSrc = video_type_2;
+        break;
+      case "1":
+      default:
+        iconObjectSrc = video_type_1;
+        break;
+    }
     return {
+      id: uuid(),
+      deviceInfo: record, // 设备信息
+      left: record?.x_ordinate ? Number(record?.x_ordinate) : 0,
+      top: record?.y_ordinate ? Number(record?.y_ordinate) : 0,
       selectable: true,
       hasControls: true,
       lockUniScaling: true, // 当设置为true,Object将无法被锁定比例进行缩放。默认值为false。
@@ -39,8 +40,7 @@ export const useDeviceObject = () => {
       version: "5.3.0",
       originX: "left",
       originY: "top",
-      left: 87.4695,
-      top: 85.8002,
+
       width: 113,
       height: 66,
       fill: "rgb(0,0,0)",
@@ -66,7 +66,6 @@ export const useDeviceObject = () => {
       globalCompositeOperation: "source-over",
       skewX: 0,
       skewY: 0,
-      id: "520579bf-b8b3-451b-b3aa-5b8a9d6cd1a8",
       objects: [
         {
           type: "image",
@@ -144,7 +143,7 @@ export const useDeviceObject = () => {
           cropY: 0,
           selectable: false,
           hasControls: false,
-          src: video_type_1,
+          src: iconObjectSrc,
           crossOrigin: null,
           filters: []
         },
diff --git a/src/views/deviceSetting/hooks/usePointObject.ts b/src/views/deviceSetting/hooks/usePointObject.ts
index 23352de..30b610b 100644
--- a/src/views/deviceSetting/hooks/usePointObject.ts
+++ b/src/views/deviceSetting/hooks/usePointObject.ts
@@ -1,5 +1,14 @@
+/*
+ * @Author: donghao donghao@supervision.ltd
+ * @Date: 2024-08-14 11:26:47
+ * @LastEditors: donghao donghao@supervision.ltd
+ * @LastEditTime: 2024-08-15 13:25:48
+ * @FilePath: \General-AI-Platform-Web-Client\src\views\deviceSetting\hooks\usePointObject.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
 export const usePointObject = () => {
-  function getPointObject(record) {
+  function getPointObject(record, objectOptions) {
+    const { width, height } = objectOptions;
     const { picture } = record;
     // TODO: 获取点位对象
     return JSON.stringify({
@@ -12,9 +21,9 @@ export const usePointObject = () => {
           originY: "top",
           left: 0,
           top: 0,
-          width: 947,
-          height: 610,
-          fill: "rgba(255,35,255,1)",
+          width: width,
+          height: height,
+          fill: "rgba(21, 77, 221, 0.1)",
           stroke: null,
           strokeWidth: 0,
           strokeDashArray: null,
@@ -48,8 +57,10 @@ export const usePointObject = () => {
           version: "5.3.0",
           originX: "left",
           originY: "top",
-          width: 947,
-          height: 610,
+          left: 0,
+          top: 0,
+          width: width,
+          height: height,
           fill: "rgb(0,0,0)",
           stroke: null,
           strokeWidth: 0,
diff --git a/src/views/deviceSetting/index.scss b/src/views/deviceSetting/index.scss
index a6f29a0..189a67f 100644
--- a/src/views/deviceSetting/index.scss
+++ b/src/views/deviceSetting/index.scss
@@ -60,12 +60,12 @@
       padding-top: 40px;
       .bg_preview {
         height: 412px;
-        background-color: red;
+        border: 1px dashed #ddd;
       }
     }
     .point_detail_wrap {
       .deviceOfPoint_wrap {
-        background-color: goldenrod;
+        border: 1px dashed #ddd;
         height: calc(100vh - 290px);
       }
       .footer_btns {
@@ -78,8 +78,8 @@
   .right-bar {
     margin-left: 16px;
     width: 241px;
-    height: 100%;
-    overflow-y: auto;
+    height: calc(100vh - 300px);
+    overflow-y: scroll;
   }
   #workspace {
     flex: 1;
@@ -100,3 +100,10 @@
     }
   }
 }
+/* 设备属性栏位 */
+.deviceAtrr_toolbar {
+  .el-radio__label {
+    display: flex;
+    align-items: center;
+  }
+}
diff --git a/src/views/deviceSetting/index.vue b/src/views/deviceSetting/index.vue
index 5eacb40..aa4ee13 100644
--- a/src/views/deviceSetting/index.vue
+++ b/src/views/deviceSetting/index.vue
@@ -2,7 +2,7 @@
  * @Author: donghao donghao@supervision.ltd
  * @Date: 2024-08-02 10:52:32
  * @LastEditors: donghao donghao@supervision.ltd
- * @LastEditTime: 2024-08-14 17:10:19
+ * @LastEditTime: 2024-08-15 17:11:29
  * @FilePath: \General-AI-Platform-Web-Client\src\views\deviceSetting\index.vue
  * @Description: 设备点位管理设置
  @ 交互说明
@@ -19,7 +19,13 @@ import DeviceSettingAdd from "./components/add.vue";
 import DeviceSelect from "./components/deviceSelect.vue";
 import { IsDelete } from "@/components/Action";
 import { usePointObject } from "./hooks/usePointObject";
-import { getWorkshopsApi, deleteWorkshopsApi } from "@/api/workshops";
+
+import {
+  getWorkshopsApi,
+  deleteWorkshopsApi,
+  addWorkshopDevicesApi,
+  getWorkshopDevicesApi
+} from "@/api/workshops";
 import { getLinkDevicesApi } from "@/api/device";
 import { isSuccessApi } from "@/utils/forApi";
 
@@ -68,30 +74,37 @@ const state = reactive({
  * @设备点位
  */
 const { getPointObject } = usePointObject();
+
 const pointList = ref([]);
 const deviceSettingAddRef = ref("");
+const deviceSelectRef = ref("");
 const activePointId = ref("");
 const activePoint = ref({
   name: ""
 });
-const deviceList = ref([]);
+const deviceList = ref([]); // 所有设备列表
+const currDeviceList = ref([]); // 当前点位下设备列表
 const isDeleteVisible = ref(false);
-// 插入文件
-const initFile = () => {
-  // console.log("canvasEditor");
-  canvasEditor.insertSvgFile(getPointObject(toRaw(activePoint.value)));
+// 插入文件绘制图
+const refreshCanvas = () => {
+  const currWorkSpace = document.getElementById("workspace");
+  canvasEditor.insertSvgFile(
+    getPointObject(toRaw(activePoint.value), {
+      width: currWorkSpace.clientWidth,
+      height: currWorkSpace.clientHeight
+    })
+  );
+  fetchDeviceByPoint();
+
+  console.log("插入文件");
 };
 // 初始化fabric&编辑器
 const initFabric = () => {
   const canvas = new fabric.Canvas("canvas", {
-    fireRightClick: true, // 启用右键,button的数字为3
+    fireRightClick: false, // 启用右键,button的数字为3
     stopContextMenu: true, // 禁止默认右键菜单
     controlsAboveOverlay: true // 超出clipPath后仍然展示控制条
   });
-  canvas.loadFromJSON(
-    getPointObject(toRaw(activePoint.value)),
-    canvas.renderAll.bind(canvas)
-  );
   // 初始化编辑器
   canvasEditor.init(canvas);
   canvasEditor.use(DringPlugin);
@@ -116,7 +129,7 @@ const initFabric = () => {
   event.init(canvas);
   state.showFabric = true;
   nextTick(() => {
-    initFile();
+    refreshCanvas();
   });
 };
 
@@ -157,10 +170,12 @@ async function fetchPointList() {
 }
 // 获取设备列表
 async function fetchDeviceList() {
-  const { data } = await getLinkDevicesApi();
-  // TODO 未使用判空和loading效果
-  deviceList.value = data;
-  console.log(deviceList.value, "fetchDeviceList_data", data);
+  const { data, ...resp } = await getLinkDevicesApi();
+  if (isSuccessApi(resp)) {
+    // TODO 未使用loading效果
+    deviceList.value = data;
+    console.log(deviceList.value, "fetchDeviceList_data", data);
+  }
 }
 // 切换点位
 function tabPoint(tab) {
@@ -168,7 +183,7 @@ function tabPoint(tab) {
   activePoint.value = selectedTab;
   console.log(tab.props.name, "tabPoint", activePoint.value);
   nextTick(() => {
-    initFile();
+    refreshCanvas();
   });
 }
 
@@ -185,7 +200,7 @@ function afterFinishAdd(record) {
   activePoint.value = record;
   console.log("afterFinishAdd", record);
   nextTick(() => {
-    initFile();
+    refreshCanvas();
   });
 }
 
@@ -193,11 +208,11 @@ function afterFinishAdd(record) {
 function editPoint() {
   deviceSettingAddRef.value?.openDialog();
 }
-// 删除点位
+// 开始删除点位
 function beforeDeletePoint() {
   isDeleteVisible.value = true;
 }
-
+// 完成删除点位
 function afterFinishDelete() {
   // 查找目标对象在 pointList 中的索引
   const currPointList = toRaw(pointList.value);
@@ -220,10 +235,10 @@ function afterFinishDelete() {
     activePointId.value = currPointList[0].id;
   }
   nextTick(() => {
-    initFile();
+    refreshCanvas();
   });
 }
-
+// 删除点位
 async function deletePoint() {
   const resp = await deleteWorkshopsApi({
     id: activePointId.value
@@ -235,6 +250,53 @@ async function deletePoint() {
   // 执行删除操作
 }
 
+// 获取点位下的已绑定设备
+async function fetchDeviceByPoint() {
+  const resp = await getWorkshopDevicesApi({
+    workshop_id: activePointId.value,
+    is_binding: true
+  });
+  if (isSuccessApi(resp)) {
+    currDeviceList.value = resp.data.results;
+  }
+  console.log("fetchDeviceByPoint_resp", resp);
+  deviceSelectRef.value?.renderDeviceToCanvas(resp.data.results);
+}
+
+// 新增设备
+async function addDevice(record) {
+  const resp = await addWorkshopDevicesApi({
+    workshop_id: activePointId.value,
+    device_id: record.id,
+    x_ordinate: record?.x_ordinate || 0,
+    y_ordinate: record?.y_ordinate || 0,
+    icon: record?.type || 1
+  });
+  if (isSuccessApi(resp)) {
+    // isAddDeviceVisible.value = false;
+    // afterFinishAddDevice();
+  }
+}
+
+// //
+// async function deleteDevice(record) {
+//   const resp = await deleteWorkshopDeviceApi({
+//     id: record.id
+//   });
+//   if (isSuccessApi(resp)) {
+//     isDeleteDeviceVisible.value = false;
+//     afterFinishDeleteDevice();
+//   }
+// }
+
+// //
+// async function updateDevice(record) {
+//   const resp = await updateWorkshopDeviceApi({
+//     id: record.id,
+
+//   });
+// }
+
 // 有点位了再初始化
 watch(
   () => pointList.value,
@@ -254,7 +316,7 @@ watch(
 //     console.log("activePoint", activePoint);
 //     if (activePoint.value?.id) {
 //       nextTick(() => {
-//         initFile();
+//         refreshCanvas();
 //       });
 //     }
 //   }
@@ -311,20 +373,20 @@ provide("canvasEditor", canvasEditor);
         <div class="flex w-full h-full">
           <!-- 左侧画布区域 -->
           <div id="workspace" class="h-full deviceOfPoint_wrap">
-            <div class="canvas-box">
-              <!-- <div class="inside-shadow"></div> -->
-              <canvas
-                id="canvas"
-                :class="state.ruler ? 'design-stage-grid' : ''"
-              />
-              <!-- <dragMode v-if="state.showFabric"></dragMode>
+            <!-- <div class="inside-shadow"></div> -->
+            <canvas id="canvas" />
+            <!-- <dragMode v-if="state.showFabric"></dragMode>
             <zoom></zoom> -->
-              <!-- <mouseMenu></mouseMenu> -->
-            </div>
+            <!-- <mouseMenu></mouseMenu> -->
           </div>
           <!-- 右侧属性区域-->
           <div class="right-bar" v-if="state.showFabric">
-            <DeviceSelect :deviceList="deviceList" />
+            <DeviceSelect
+              ref="deviceSelectRef"
+              :deviceList="deviceList"
+              @addDevice="addDevice"
+              :pointInfo="activePoint"
+            />
             <DeviceAttr />
           </div>
         </div>
@@ -339,6 +401,7 @@ provide("canvasEditor", canvasEditor);
             v-if="pointList.length"
             >删除位置</el-button
           >
+          <!-- //TODO 完善删除交互效果 -->
           <IsDelete
             v-model="isDeleteVisible"
             @update:visible="val => (isDeleteVisible = val)"