From e044cb7dedd0c171e6a38bed486bf46c8f8c58bb Mon Sep 17 00:00:00 2001
From: hanbaoshan <hanbaoshan@aiotlink.com>
Date: 星期三, 23 十二月 2020 15:20:36 +0800
Subject: [PATCH] 长春全景追踪应用更新(添加轨迹图查询,布局结构更新),场景轮播图算法图标不显示问题修复

---
 src/pages/panoramicView/components/LabelMark.vue       |  665 ++++++++++++++++++++
 src/pages/labelMark/components/RightSide.vue           |  122 +--
 src/pages/panoramicView/index/App.vue                  |   95 ++
 src/pages/labelMark/components/SlideCanvas.vue         |    9 
 src/pages/panoramicView/components/RelateCamera.vue    |  542 ++++++++++++++++
 src/pages/panoramicView/components/SlideCanvas.vue     |  231 +++++++
 src/pages/panoramicView/index/main.ts                  |   19 
 public/apps.json                                       |   23 
 src/api/panorama.ts                                    |   12 
 src/pages/cameraAccess/components/scene/SlideScene.vue |    6 
 src/pages/panoramicView/index/mixins.ts                |   14 
 src/pages/panoramicView/components/TracePlot.vue       |  153 ++++
 12 files changed, 1,805 insertions(+), 86 deletions(-)

diff --git a/public/apps.json b/public/apps.json
index 7e5d3b6..3467881 100644
--- a/public/apps.json
+++ b/public/apps.json
@@ -391,6 +391,29 @@
       "installed": true,
       "isUpgrade": false,
       "progressMsg": ""
+    },
+    {
+      "id": "fa5674ee-70cf-4e22-8a06-c17429fbb70b",
+      "name": "鍏ㄦ櫙杩借釜",
+      "package": "panoramicView",
+      "type": "1",
+      "url": "/view/panoramicView/",
+      "title": "鍏ㄦ櫙杩借釜",
+      "width": 1500,
+      "height": 750,
+      "iconBlob": "",
+      "icon": "../../images/app-mid/360.png",
+      "version": "1.0.0",
+      "create_time": "2020-10-09 14:00:08",
+      "create_by": "",
+      "update_time": "",
+      "update_by": "",
+      "isDelete": 0,
+      "isDefault": false,
+      "remoteVersion": "",
+      "installed": true,
+      "isUpgrade": false,
+      "progressMsg": ""
     }
   ],
   "msg": "璇锋眰澶勭悊鎴愬姛",
diff --git a/src/api/panorama.ts b/src/api/panorama.ts
new file mode 100644
index 0000000..e8a897e
--- /dev/null
+++ b/src/api/panorama.ts
@@ -0,0 +1,12 @@
+import request from '@/scripts/httpRequest';
+import qs from 'qs'
+export const getPanoramaPic = ()=>request({
+    url: '/data/api-v/panorama/show',
+    method: 'get'
+});
+
+export const putPanoramaPic = (data:any)=>request({
+    url:'/data/api-v/panorama/upload',
+    method:'post',
+    data
+})
\ No newline at end of file
diff --git a/src/pages/cameraAccess/components/scene/SlideScene.vue b/src/pages/cameraAccess/components/scene/SlideScene.vue
index ee755f0..969aae5 100644
--- a/src/pages/cameraAccess/components/scene/SlideScene.vue
+++ b/src/pages/cameraAccess/components/scene/SlideScene.vue
@@ -30,7 +30,7 @@
                   </svg>-->
                   <img
                     class="baseImg"
-                    :src="item.rules[0].icon_blob &&item.rules[0].icon_blob.indexOf(',')>0?item.rules[0].icon_blob:`data:image/png;base64,${rule.icon_blob}`"
+                    :src="item.rules[index].icon_blob &&item.rules[index].icon_blob.indexOf(',')>0?item.rules[index].icon_blob:`data:image/png;base64,${rule.icon_blob}`"
                     alt
                   />
                 </div>
@@ -42,7 +42,7 @@
                   </svg>-->
                   <img
                     class="baseImg"
-                    :src="item.rules[0].icon_blob &&item.rules[0].icon_blob.indexOf(',')>0?item.rules[0].icon_blob:`data:image/png;base64,${rule.icon_blob}`"
+                    :src="item.rules[index].icon_blob &&item.rules[index].icon_blob.indexOf(',')>0?item.rules[index].icon_blob:`data:image/png;base64,${rule.icon_blob}`"
                     alt
                   />
                 </div>
@@ -54,7 +54,7 @@
                   </svg>-->
                   <img
                     class="baseImg"
-                    :src="item.rules[0].icon_blob && item.rules[0].icon_blob.indexOf(',')>0?item.rules[0].icon_blob:`data:image/png;base64,${rule.icon_blob}`"
+                    :src="item.rules[index].icon_blob && item.rules[index].icon_blob.indexOf(',')>0?item.rules[index].icon_blob:`data:image/png;base64,${rule.icon_blob}`"
                     alt
                   />
                 </div>
diff --git a/src/pages/labelMark/components/RightSide.vue b/src/pages/labelMark/components/RightSide.vue
index 44808a9..d8a4478 100644
--- a/src/pages/labelMark/components/RightSide.vue
+++ b/src/pages/labelMark/components/RightSide.vue
@@ -9,7 +9,7 @@
         element-loading-text="鍔犺浇涓�"
         type="border-card"
       >
-        <el-tab-pane label="浣嶇疆鏍囨敞" name="1">
+        <el-tab-pane label="浣嶇疆鏍囧畾" name="1">
           <el-tabs type="border-card">
             <el-tab-pane label="鎽勫儚鏈烘爣娉�" name="11">
               <div class>
@@ -312,7 +312,7 @@
           </div>
         </el-tab-pane>
       </el-tabs>
-      <el-dialog class="dialog-group" title="鏂板缓鍒嗙粍" :visible.sync="groupModelVisible">
+      <el-dialog class="dialog-group" :title="groupForm.id?'缂栬緫鍒嗙粍':'鏂板缓鍒嗙粍'" :visible.sync="groupModelVisible">
         <el-form :model="groupForm" ref="groupForm">
           <el-form-item>
             <label>鍒嗙粍鍚嶇О</label>
@@ -347,7 +347,7 @@
 import { getCamerasByServer } from '@/api/pollConfig';
 import { getCameraMarks, updateCameraMarks, findCameraGroups, saveCameraGroupInfo, delCameraGroup } from '@/api/camera';
 import { getSearchList } from '@/api/search';
-import TreeDataPool from "@/Pool/TreeData";
+
 import { isNonnegativeInteger } from '@/scripts/validate';
 import { getAllPolygon, saveRelationPolygon, findByCamGroup, findByGroup, delRelation } from '@/api/polygon';
 import SlideCanvas from './SlideCanvas';
@@ -356,50 +356,42 @@
   components: { SlideCanvas },
   data () {
     return {
-      labels: [],
-      colorPick: '#79f2fb',
-      dotSize: 3,
-      isEdit: false,
-      isShowPop: false,
-      isNewLabel: false,
+      // labels: [],
+      // colorPick: '#79f2fb',
+      // dotSize: 3,
+      // isEdit: false,
+      // isShowPop: false,
+      // isNewLabel: false,
       // curLabel: {
       //   id: '',
-      //   posX: '',
-      //   posY: '',
-      //   x: '',
-      //   y: ''
+      //   x1: '',
+      //   y1: '',
+      //   x0: '',
+      //   y0: ''
       // },
-      curLabel: {
-        id: '',
-        x1: '',
-        y1: '',
-        x0: '',
-        y0: ''
-      },
-      rules: {
-        x1: [
-          { validator: isNonnegativeInteger, trigger: 'change' }
-        ],
-        y1: [
-          { validator: isNonnegativeInteger, trigger: 'change' }
-        ]
-      },
-      baseUrl: '',
-      snapshot_url: '',
-      userImg: '',
-      cameraData: [],
-      traceX: 0,
-      traceY: 0,
-      isShowCurPos: false,
-      actPage: '1',
-      loading: false,
-      spaceWidth: '',
-      spaceHeight: '',
-      curCameraData: {
-        cameraId: '',
-        coords: []
-      },
-      relativeCameras: [],
+      // rules: {
+      //   x1: [
+      //     { validator: isNonnegativeInteger, trigger: 'change' }
+      //   ],
+      //   y1: [
+      //     { validator: isNonnegativeInteger, trigger: 'change' }
+      //   ]
+      // },
+      // baseUrl: '',
+      // snapshot_url: '',
+      // userImg: '',
+      // cameraData: [],
+      // traceX: 0,
+      // traceY: 0,
+      // isShowCurPos: false,
+      // actPage: '1',
+      // loading: false,
+      // spaceWidth: '',
+      // spaceHeight: '',
+      // curCameraData: {
+      //   cameraId: '',
+      //   coords: []
+      // },
       relativeList: [],
       cameraAreas: [],
       groupModelVisible: false,
@@ -437,12 +429,7 @@
       },
       deep: true
     },
-    // 'TreeDataPool.selectedNodes': {
-    //   handler (n, o) {
 
-    //   },
-    //   deep: true
-    // },
     isEdit (n, o) {
       if (n) {
         this.$refs['editBoard'].addEventListener('click', this.bindListen);
@@ -453,7 +440,6 @@
   },
   methods: {
     polygonUpdate(){
-      debugger
       this.getAllGroups();
     },
     delRelation (item) {
@@ -506,13 +492,7 @@
         }
       })
     },
-    saveRelativeList () {
-      let params = {
-        groupId: this.curGroup.id,
 
-      }
-      //saveRelationPolygon().then()
-    },
     async findPolygonByIds (cameras) {
       for (var i = 0; i < cameras.length; i++) {
         let res = await getAllPolygon({ cameraId: cameras[i].id });
@@ -678,13 +658,8 @@
         cameras: []
       }
     },
-    changeTab () {
-      if (this.actPage == '1') {
-        this.$parent.$children[0].$el.parentNode.parentElement.style.display = 'block';
-      } else {
-        this.$parent.$children[0].$el.parentNode.parentElement.style.display = 'none';
-      }
-    },
+
+//labelMark start
     sure () {
       let _this = this;
       this.$refs['labelForm'].validate(valid => {
@@ -742,9 +717,6 @@
     onChange (file, fileList) {
       fileList = [file]
       this.isShowCurPos = false;
-      // this.traceX = e.offsetX;
-      // this.traceY = e.offsetY;
-      //fileList.push(file)
     },
     definedUpload (params) {
       let _file = params.file
@@ -767,18 +739,6 @@
         console.log(e)
       })
     },
-    // async getAllCameraData () {
-    //   let res = await getCamerasByServer();
-    //   this.cameraData = res.data;
-    //   let tempArr = [];
-    //   this.cameraData.forEach(camera=>{
-    //     getAllPolygon({ cameraId: camera.id }).then(res=>{
-    //       tempArr.push(res.data);
-    //     })
-    //   });
-    //   this.cameraAndPolygonData = tempArr;
-    //   console.log(this.cameraAndPolygonData)
-    // },
     bindListen (e) {
       this.newLabel(e);
     },
@@ -803,13 +763,11 @@
       this.isNewLabel = true;
     },
     editLabel (label) {
-      debugger
       if (!this.isEdit) return;
       this.isShowPop = true;
       this.$refs['labelForm'].clearValidate();
       this.curLabel = JSON.parse(JSON.stringify(label));
-      console.log(this.curLabel)
-      //this.curLabel = label;
+
     },
     cancle () {
       this.isShowPop = false;
@@ -1011,8 +969,6 @@
         }
         //color: #4966b7
       }
-    }
-    .img-card {
     }
     .upload-demo,
     .el-upload {
diff --git a/src/pages/labelMark/components/SlideCanvas.vue b/src/pages/labelMark/components/SlideCanvas.vue
index ba82824..14362f4 100644
--- a/src/pages/labelMark/components/SlideCanvas.vue
+++ b/src/pages/labelMark/components/SlideCanvas.vue
@@ -22,11 +22,13 @@
         <polygon-canvas
           class="polygon-canvas"
           :ref="`polygonCanvas_${camera.id}`"
+          :currentCameraId="camera.id"
           :snapshot_url="camera.canvasData.snapshot_url"
           :canvasDataShow="camera.canvasData"
           :canvasWidth="576"
           :canvasHeight="324"
           @fromCanvas="getCanvasData"
+          @refresh="refresh"
         ></polygon-canvas>
       </swiper-slide>
     </swiper>
@@ -79,6 +81,13 @@
     console.log(this.swiper)
   },
   methods: {
+    refresh(url,cameraId) {
+      this.$emit('polygonDataUpdate')
+
+      //let camera = this.cameras.find(one => one.id == cameraId);
+      //camera.canvasData.snapshot_url = url;
+
+    },
     getCanvasData(data) {
       let _this = this;
       savePolygon(data).then(rsp => {
diff --git a/src/pages/panoramicView/components/LabelMark.vue b/src/pages/panoramicView/components/LabelMark.vue
new file mode 100644
index 0000000..aaca3cc
--- /dev/null
+++ b/src/pages/panoramicView/components/LabelMark.vue
@@ -0,0 +1,665 @@
+<template>
+  <div class="label-mark">
+    <el-tabs type="border-card" v-model="actPage">
+      <el-tab-pane label="鎽勫儚鏈烘爣娉�" name="1">
+        <div class="mark-interface">
+          <div class="left-tree">
+            <div class="resize-bar"></div>
+            <div class="resize-line"></div>
+            <div class="resize-save">
+              <left-nav :appName="'Camera'" :height="screenHeight - 40"></left-nav>
+            </div>
+          </div>
+          <div class="tree-right">
+            <div class="action-bar">
+              <div class="tool-bar">
+                <div>
+                  <!-- <input type="color" ref="colorPicker" v-model="color"> -->
+                  <label for>鎷捐壊鍣�:</label>
+                  <el-color-picker v-model="colorPick" show-alpha size="mini"></el-color-picker>
+                </div>
+                <div style="width:250px;">
+                  <label for>绗旇Е:</label>
+                  <el-slider v-model="dotSize" :min="1" :max="20"></el-slider>
+                </div>
+                <div>
+                  <el-button
+                    v-if="!isEdit"
+                    class="drawboard-trigger"
+                    size="small"
+                    @click="editCameraData"
+                    icon="el-icon-edit"
+                  >缂栬緫</el-button>
+
+                  <el-button
+                    v-if="isEdit"
+                    class="drawboard-trigger save"
+                    size="small"
+                    @click="submitInfo"
+                    icon="el-icon-lock"
+                  >淇濆瓨</el-button>
+                </div>
+              </div>
+            </div>
+            <div class="drawboard shadow-box">
+              <div class="mask" :class="{'edit-status-mask':isEdit}" ref="editBoard">
+                <div
+                  class="label"
+                  @click="editLabel(item)"
+                  v-for="(item,index) in curCameraData.coords"
+                  :key="index"
+                  :style="{left:`${item.x0}px`, top:`${item.y0}px`, backgroundColor: colorPick, width: `${dotSize}px`, height: `${dotSize}px` }"
+                ></div>
+              </div>
+              <img v-show="snapshot_url" :src="`/httpImage/${snapshot_url}`" alt />
+              <div
+                class="popBox"
+                v-show="isShowPop"
+                :style="`top:${curLabel.y0 + 22}px;left:${curLabel.x0}px`"
+              >
+                <div class="title">鏍囨敞淇℃伅</div>
+                <div class="details">
+                  <el-form :model="curLabel" :rules="rules" ref="labelForm">
+                    <div class="detail-item">
+                      <div class="left">
+                        <el-form-item prop="x0">
+                          <label for>骞抽潰鍧愭爣X:</label>
+                          <span class="fix-width">{{curLabel.x0}}</span>
+                          <i>px</i>
+                        </el-form-item>
+                      </div>
+                      <span class="devide"></span>
+                      <div class="right">
+                        <el-form-item prop="x1">
+                          <label for>瀹為檯鍧愭爣X:</label>
+                          <el-input
+                            type="text"
+                            size="mini"
+                            style="width:90px"
+                            v-model.number="curLabel.x1"
+                          ></el-input>
+                        </el-form-item>
+                      </div>
+                    </div>
+                    <div class="detail-item">
+                      <div class="left">
+                        <el-form-item prop="y0">
+                          <label for>骞抽潰鍧愭爣Y:</label>
+                          <span class="fix-width">{{curLabel.y0}}</span>
+                          <i>px</i>
+                        </el-form-item>
+                      </div>
+                      <span class="devide"></span>
+                      <div class="right">
+                        <el-form-item prop="y1">
+                          <label for>瀹為檯鍧愭爣Y:</label>
+                          <el-input
+                            type="text"
+                            size="mini"
+                            style="width:90px"
+                            v-model.number="curLabel.y1"
+                          ></el-input>
+                        </el-form-item>
+                      </div>
+                    </div>
+                    <div class="btns">
+                      <el-button size="mini" type="danger" @click="deleteLabel">鍒犻櫎</el-button>
+                      <el-button size="mini" type="primary" @click="cancle">鍙栨秷</el-button>
+                      <el-button size="mini" type="success" @click="sure">纭畾</el-button>
+                    </div>
+                  </el-form>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </el-tab-pane>
+      <el-tab-pane label="瀹炴櫙鍧愭爣" name="2">
+        <div class="user-upload">
+          <div class="img-card">
+            <el-upload
+              class="upload-demo"
+              drag
+              action="https://jsonplaceholder.typicode.com/posts/"
+              :http-request="definedUpload"
+              :on-change="onChange"
+              :show-file-list="false"
+            >
+              <el-image
+                class="preview"
+                v-if="panoramaPath"
+                :src="panoramaPath"
+                fit="contain"
+                @mousemove="showCurPos"
+                @mouseout="isShowCurPos=false"
+              ></el-image>
+              <div class="el-upload__text">
+                灏嗘枃浠舵嫋鍒版澶勶紝鎴�
+                <em>鐐瑰嚮涓婁紶</em>
+              </div>
+            </el-upload>
+          </div>
+          <div class="info">
+            <div class="input-area">
+              <!-- <div>
+                <label for>绌洪棿瀹�:</label>
+                <el-input v-model="spaceWidth" placeholder="璇疯緭鍏ュ疄闄呯┖闂村" size="small"></el-input>
+              </div>
+              <div>
+                <label for>绌洪棿楂�:</label>
+                <el-input v-model="spaceHeight" placeholder="璇疯緭鍏ュ疄闄呯┖闂撮珮" size="small"></el-input>
+              </div> -->
+            </div>
+            <div class="pos" v-show="isShowCurPos">
+              褰撳墠浣嶇疆:
+              <b>{{traceX}}</b>,
+              <b>{{traceY}}</b>
+            </div>
+          </div>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import { getCamerasByServer } from '@/api/pollConfig';
+import { getCameraMarks, updateCameraMarks } from '@/api/camera';
+import { getPanoramaPic, putPanoramaPic } from '@/api/panorama';
+import { isNonnegativeInteger } from '@/scripts/validate';
+import LeftNav from "@/components/LeftNav";
+export default {
+  components: { LeftNav },
+  data () {
+    return {
+      screenHeight: 0,
+      actPage: 1,
+      labels: [],
+      colorPick: '#79f2fb',
+      dotSize: 3,
+      isEdit: false,
+      isShowPop: false,
+      isNewLabel: false,
+      curLabel: {
+        id: '',
+        x1: '',
+        y1: '',
+        x0: '',
+        y0: ''
+      },
+      rules: {
+        x1: [
+          { validator: isNonnegativeInteger, trigger: 'change' }
+        ],
+        y1: [
+          { validator: isNonnegativeInteger, trigger: 'change' }
+        ]
+      },
+      baseUrl: '',
+      snapshot_url: '',
+      panoramaPath: '',
+      cameraData: [],
+      traceX: 0,
+      traceY: 0,
+      isShowCurPos: false,
+      actPage: '1',
+      loading: false,
+      spaceWidth: '',
+      spaceHeight: '',
+      curCameraData: {
+        cameraId: '',
+        coords: []
+      },
+    }
+  },
+  mounted () {
+    this.getAllCameraData();
+    this.getPanorama();
+    this.screenHeight = document.documentElement.clientHeight - 20;
+    window.onresize = () => {
+      return (() => {
+        this.screenHeight = document.documentElement.clientHeight - 20;
+      })();
+    };
+  },
+  
+  watch: {
+    'TreeDataPool.selectedNode': {
+      handler (n, o) {
+        let curCamera = this.cameraData.find(item => item.id == n.id);
+        //璁剧疆鎽勫儚鏈哄簳鍥�
+        this.snapshot_url = curCamera.snapshot_url;
+        this.findCameraMarks(n.id);
+      },
+      deep: true
+    },
+    isEdit (n, o) {
+      if (n) {
+        this.$refs['editBoard'].addEventListener('click', this.bindListen);
+      } else {
+        this.$refs['editBoard'].removeEventListener('click', this.bindListen);
+      }
+    }
+  },
+  methods: {
+    getAllCameraData () {
+      let _this = this;
+      getCamerasByServer().then(res => {
+        if (res.success) {
+          _this.cameraData = res.data;
+          //_this.getAllGroups();
+        }
+      }).catch(e => {
+        console.log(e)
+      })
+    },
+    sure () {
+      let _this = this;
+      this.$refs['labelForm'].validate(valid => {
+        console.log(valid)
+        if (valid) {
+          _this.isShowPop = false;
+          //缂栬緫纭畾
+          if (_this.curLabel.id) {
+            let editedIndex = _this.curCameraData.coords.findIndex(one => one.id == _this.curLabel.id);
+            _this.curCameraData.coords[editedIndex] = JSON.parse(JSON.stringify(_this.curLabel));
+
+          }
+          console.log(_this.curCameraData.coords)
+          this.$refs['labelForm'].clearValidate();
+        }
+      });
+    },
+    //鑾峰彇鎽勫儚鏈烘爣娉�
+    findCameraMarks (id) {
+      getCameraMarks({ cameraId: id }).then(res => {
+        if (res.success) {
+          this.curCameraData.cameraId = id;
+          this.curCameraData.coords = res.data.map((item, index) => ({ id: 'm' + index, x0: item.x0, y0: item.y0, x1: item.x1, y1: item.y1 }));
+        }
+      }).catch(e => {
+        console.log(e)
+      });
+    },
+    editCameraData () {
+      if (!this.TreeDataPool.selectedNode.id) {
+        this.$notify({
+          message: '璇峰厛閫夋嫨鎽勫儚鏈�',
+          type: 'warning'
+        });
+        return;
+      }
+      this.isEdit = !this.isEdit;
+    },
+    async submitInfo () {
+      this.isEdit = false;
+      let res = await updateCameraMarks(this.curCameraData);
+      if (res.success) {
+        this.findCameraMarks(this.curCameraData.cameraId);
+      }
+    },
+    getPanorama(){
+      let _this = this;
+      getPanoramaPic().then(res=>{
+        _this.panoramaPath = res.data.panoramaPath
+      })
+    },
+    showCurPos (e) {
+      console.log(e);
+      this.isShowCurPos = true;
+      this.traceX = e.offsetX;
+      this.traceY = e.offsetY;
+    },
+    onChange (file, fileList) {
+      fileList = [file]
+      this.isShowCurPos = false;
+    },
+    definedUpload (params) {
+      let _this = this;
+      let _file = params.file;
+      let param = new FormData();
+      param.append('file', params.file)
+      putPanoramaPic(param).then(res=>{
+        _this.panoramaPath = res.data.panoramaPath+'?'+Math.random()
+        console.log(_this.panoramaPath)
+      })
+      // let fileReader = new FileReader()
+      // fileReader.onload = () => {
+      //   _this.file = fileReader.result;
+      // }
+      // if (_file) {
+      //   fileReader.readAsDataURL(_file)
+      // }
+    },
+
+    bindListen (e) {
+      this.newLabel(e);
+    },
+    newLabel (e) {
+      console.log('鐐瑰嚮浜嗙敾鏉�')
+      if (this.isShowPop) return;
+      //鑾峰彇榧犳爣鐩稿浜庣敾鏉跨殑瀹氫綅
+      console.log('鑾峰彇褰撳墠瀹氫綅淇℃伅');
+      this.$refs['labelForm'].resetFields();
+      let target = {
+        id: '',
+        x0: e.offsetX,
+        y0: e.offsetY,
+        x1: '',
+        y1: ''
+      };
+      target.id = 'n' + (this.curCameraData.coords.length - 1);
+      //this.labels.push(target);
+      this.curCameraData.coords.push(target);
+      this.curLabel = target;
+      this.isShowPop = true;
+      this.isNewLabel = true;
+    },
+    editLabel (label) {
+      if (!this.isEdit) return;
+      this.isShowPop = true;
+      this.$refs['labelForm'].clearValidate();
+      this.curLabel = JSON.parse(JSON.stringify(label));
+
+    },
+    cancle () {
+      this.isShowPop = false;
+      //濡傛灉鏄湭淇濆瓨杩囩殑label鐩存帴鍒犻櫎(鏈繚瀛樼殑灏辨槸labels鏁扮粍涓渶鍚庝竴涓�)
+      if (this.curLabel.id.startsWith('n')) {
+        //this.labels.pop();
+        this.curCameraData.coords.pop();
+      }
+    },
+    deleteLabel () {
+      if (this.curLabel.id) {
+        let index = this.curCameraData.coords.findIndex(item => item.id == this.curLabel.id);
+        this.curCameraData.coords.splice(index, 1);
+
+      } else {
+        //this.labels.pop();
+        this.curCameraData.coords.pop();
+      }
+      this.isShowPop = false;
+    },
+  }
+}
+</script>
+
+<style lang="scss">
+.resize-save {
+  position: absolute;
+  top: 0;
+  right: 5px;
+  bottom: 0;
+  left: 0;
+  padding: 14px;
+  overflow-x: hidden;
+}
+.resize-bar {
+  width: 310px;
+  //height: inherit;
+  resize: horizontal;
+  cursor: ew-resize;
+  opacity: 0;
+  overflow: scroll;
+  max-width: 500px; //璁惧畾鏈�澶ф媺浼搁暱搴�
+  min-width: 33px; //璁惧畾鏈�灏忓搴�
+}
+/* 鎷栨嫿绾� */
+.resize-line {
+  position: absolute;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  border-right: 2px solid #efefef;
+  border-left: 1px solid #e0e0e0;
+  pointer-events: none;
+}
+.resize-bar:hover ~ .resize-line,
+.resize-bar:active ~ .resize-line {
+  border-left: 1px dashed skyblue;
+}
+.resize-bar::-webkit-scrollbar {
+  width: 200px;
+  height: inherit;
+}
+
+/* Firefox鍙湁涓嬮潰涓�灏忓潡鍖哄煙鍙互鎷変几 */
+@supports (-moz-user-select: none) {
+  .resize-bar:hover ~ .resize-line,
+  .resize-bar:active ~ .resize-line {
+    border-left: 1px solid #bbb;
+  }
+  .resize-bar:hover ~ .resize-line::after,
+  .resize-bar:active ~ .resize-line::after {
+    content: '';
+    position: absolute;
+    width: 16px;
+    height: 16px;
+    bottom: 0;
+    right: -8px;
+    // background: url(./resize.svg);
+    background-size: 100% 100%;
+  }
+}
+.label-mark {
+  background: #d2dcea;
+  height: calc(100vh - 61px);
+  &>.el-tabs--border-card > .el-tabs__content {
+    background: #d2dcea;
+    padding: 0;
+    height: calc(100vh - 100px);
+    overflow: auto;
+    .mark-interface {
+      .left-tree {
+        position: relative;
+        float: left;
+        height: calc(100vh - 20px);
+        background: #fff;
+      }
+      .tree-right {
+        overflow: auto;
+      }
+    }
+  }
+  .tool-bar {
+    //width: 40px;
+    height: 100%;
+    padding: 10px 0 10px 20px;
+    box-sizing: border-box;
+    //background: rgb(250, 250, 250);
+    background: rgba(26, 45, 74, 0.6);
+    //margin-bottom: 40px;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    > div {
+      cursor: pointer;
+      //background: rgba(245, 245, 245, 0.3);
+      padding: 0 5px;
+      height: 40px;
+      margin: 7px;
+      display: flex;
+      align-items: center;
+      label {
+        margin-right: 10px;
+        color: rgb(161, 161, 161);
+        color: #fff;
+      }
+      .el-slider {
+        width: 110px;
+      }
+    }
+  }
+  .shadow-box {
+    box-shadow: 3px 3px 3px 1px rgba(0, 0, 0, 0.1);
+  }
+  .action-bar {
+    width: 960px;
+    margin: auto;
+    margin-top: 30px;
+    //margin-bottom: 20px;
+    text-align: right;
+    .drawboard-trigger {
+      background: transparent;
+      color: #fff;
+      border-color: rgba(255, 255, 255, 0.3);
+    }
+  }
+  .drawboard {
+    margin: auto;
+    width: 960px;
+    height: 540px;
+    margin-bottom: 130px;
+    position: relative;
+    //background: #fff;
+    background: #f0ffca;
+    //box-shadow: 3px 3px 3px 1px rgba(0, 0, 0, 0.1);
+    .mask {
+      position: absolute;
+      background: transparent;
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+      &.edit-status-mask {
+        background: rgba(20, 181, 255, 0.1);
+      }
+      .label {
+        position: absolute;
+        z-index: 2;
+        border-radius: 50%;
+      }
+    }
+    img {
+      width: 960px;
+      height: 540px;
+      background: #f0ffca;
+    }
+    .right-panel {
+      width: 150px;
+      height: 100%;
+      background: rgba(26, 45, 74, 0.7);
+    }
+    .popBox {
+      position: absolute;
+      z-index: 99;
+      padding: 14px;
+      border-radius: 3px;
+      color: #fff;
+      background: rgba(26, 45, 74, 0.7);
+      .title {
+        font-weight: bold;
+        text-align: left;
+        font-size: 15px;
+        margin-bottom: 14px;
+        letter-spacing: 2px;
+      }
+      .details {
+        .detail-item {
+          display: flex;
+          margin: 5px 0;
+          label {
+            color: #a9a9a9;
+            width: 65px;
+            display: inline-block;
+          }
+          .left {
+            width: 110px;
+            text-align: left;
+            line-height: 28px;
+            .fix-width {
+              display: inline-block;
+              width: 23px;
+            }
+          }
+          .right {
+            width: 160px;
+          }
+          .devide {
+            width: 10px;
+            height: 1px;
+            background: #a9a9a9;
+            margin: 14px 3px;
+          }
+        }
+        .btns {
+          margin-top: 10px;
+        }
+        .el-form-item {
+          margin-bottom: 12px;
+        }
+        .el-form-item__content {
+          font-size: 12px;
+          line-height: 30px;
+        }
+        .el-form-item__error {
+          left: 70px;
+          top: 94%;
+        }
+      }
+    }
+  }
+  .user-upload {
+    margin: auto;
+    padding: 50px;
+    display: flex;
+    .info {
+      margin-left: 20px;
+      margin-top: 20px;
+      text-align: left;
+      font-size: 15px;
+      .input-area {
+        width: 300px;
+        label {
+          width: 80px;
+          color: rgba(39, 68, 111, 0.67);
+        }
+        > div {
+          display: flex;
+          align-items: center;
+          height: 40px;
+        }
+      }
+      .pos {
+        margin-top: 10px;
+        text-align: left;
+        color: rgba(39, 68, 111, 0.67);
+        b {
+          font-style: italic;
+        }
+        //color: #4966b7
+      }
+    }
+    .upload-demo,
+    .el-upload {
+      height: 100%;
+      width: 100%;
+      margin: 0 auto;
+    }
+    .upload-demo .el-upload__input {
+      visibility: hidden;
+    }
+    .upload-demo .el-upload-dragger {
+      width: 100%;
+      height: 90%;
+      width: 962px;
+      height: 542px;
+      margin: 20px 0 0;
+      background: transparent;
+      /* border: none; */
+      //position: relative;
+      overflow: visible;
+      border: none;
+    }
+    .upload-demo .el-upload__text {
+      position: absolute;
+      top: -24px;
+      left: 50%;
+      margin-left: -91px;
+    }
+    .upload-demo .preview {
+      object-fit: contain;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/panoramicView/components/RelateCamera.vue b/src/pages/panoramicView/components/RelateCamera.vue
new file mode 100644
index 0000000..f196ec6
--- /dev/null
+++ b/src/pages/panoramicView/components/RelateCamera.vue
@@ -0,0 +1,542 @@
+<template>
+  <div class="relate-camera">
+    <div class="part">
+      <div class="title">
+        <span>鍏宠仈璁惧鍒嗙粍</span>
+        <el-button @click="newGroup" icon="el-icon-plus" size="mini" type="primary">鏂板缓鍒嗙粍</el-button>
+      </div>
+      <el-alert type="info" title="鎻愮ず:璇风偣鍑讳笂鏂�'鏂板缓鍒嗙粍'鎸夐挳寤虹珛鍒嗙粍" show-icon v-if="groupList.length==0"></el-alert>
+      <div class="flex-box" v-if="groupList.length!==0">
+        <div
+          class="group-card"
+          :class="{'checked':group.checked}"
+          v-for="(group,index) in groupList"
+          :key="index"
+          @click="checkCurrentGroup(group)"
+        >
+          <div class="top">
+            <div class="left">
+              <span class="icon el-icon-video-camera"></span>
+            </div>
+            <div class="right">
+              <div class="name">{{group.groupName}}</div>
+              <div class="details">
+                <label>鎽勫儚鏈�:</label>
+                <span
+                  class="sub"
+                  v-for="(camera,index) in group.cameras"
+                  :key="camera.id"
+                >{{camera.name}} {{index!==group.cameras.length-1?'/':''}}</span>
+              </div>
+            </div>
+          </div>
+          <div class="bottom">
+            <span @click.stop="editGroup(group)">缂栬緫鍒嗙粍</span>
+            <el-popconfirm title="纭畾鍒犻櫎璇ュ垎缁勫悧?" @onConfirm="removeGroup(group)">
+              <el-button slot="reference" type="text">鍒犻櫎鍒嗙粍</el-button>
+            </el-popconfirm>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- <div class="part" v-if="groupList.length"> -->
+    <div class="part" v-if="groupList.length!==0 && Object.keys(curGroup)">
+      <div class="title">缁樺埗鍖哄煙(鐢ㄤ簬绠楁硶鍒嗘瀽)</div>
+      <div class="relative-partment" v-if="curGroup.cameras&&curGroup.cameras.length">
+        <div class="area-wrap" v-for="i in 2" :key="'sc'+i">
+          <slide-canvas :cameras="curGroup.cameras" @polygonDataUpdate="polygonUpdate"></slide-canvas>
+        </div>
+      </div>
+    </div>
+    <div class="part relative-config" v-if="Object.keys(curGroup)">
+      <div class="title">
+        <div class="left">
+          <span>鍏宠仈鍖哄煙閰嶇疆</span>
+        </div>
+        <div class="right" v-if="curGroup.id">
+          <el-button icon="el-icon-plus" size="mini" type="primary" @click="addRelation">娣诲姞鍏宠仈</el-button>
+        </div>
+      </div>
+      <div class="relative-list">
+        <div class="relative-item" v-for="item in relativeList" :key="item.id">
+          <div class="left">
+            <el-select v-model="item.sourceObj" value-key="polygonId" size="small">
+              <el-option
+                v-for="area in cameraAreas"
+                :key="area.polygonId"
+                :label="area.name"
+                :value="area"
+              ></el-option>
+            </el-select>
+            <i class="el-icon-connection"></i>
+            <el-select v-model="item.targetObj" value-key="polygonId" size="small">
+              <el-option
+                v-for="area in cameraAreas"
+                :key="area.polygonId"
+                :label="area.name"
+                :value="area"
+              ></el-option>
+            </el-select>
+          </div>
+          <div class="right">
+            <el-button type="text" @click="saveRelativePolygon(item)">淇濆瓨</el-button>
+            <div class="btn-del" @click="delRelation(item)">
+              <i class="el-icon-delete"></i>
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- <div class="btns">
+                <el-button size="small" >鍙栨秷</el-button>
+                <el-button size="small" type="primary" @click="saveRelativeList">淇濆瓨</el-button>
+      </div>-->
+      <el-dialog
+        class="dialog-group"
+        :title="groupForm.id?'缂栬緫鍒嗙粍':'鏂板缓鍒嗙粍'"
+        :visible.sync="groupModelVisible"
+      >
+        <el-form :model="groupForm" ref="groupForm">
+          <el-form-item>
+            <label>鍒嗙粍鍚嶇О</label>
+            <div>
+              <el-input v-model="groupForm.groupName"></el-input>
+            </div>
+          </el-form-item>
+          <el-form-item>
+            <label>閫夋嫨鎽勫儚鏈�</label>
+            <div>
+              <el-select v-model="groupForm.cameras" value-key="id" multiple>
+                <el-option
+                  :label="camera.name"
+                  :value="camera"
+                  v-for="camera in cameraData"
+                  :key="camera.id"
+                ></el-option>
+              </el-select>
+            </div>
+          </el-form-item>
+          <div class="btns">
+            <el-button @click="groupModelVisible=false;">鍙栨秷</el-button>
+            <el-button type="primary" @click="confirmGroupDialog">纭畾</el-button>
+          </div>
+        </el-form>
+      </el-dialog>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getCamerasByServer } from '@/api/pollConfig';
+import { findCameraGroups, saveCameraGroupInfo, delCameraGroup } from '@/api/camera';
+import { getAllPolygon, saveRelationPolygon, findByCamGroup, findByGroup, delRelation } from '@/api/polygon';
+import SlideCanvas from './SlideCanvas';
+
+export default {
+  components: { SlideCanvas },
+  data () {
+    return {
+      cameraData: [],
+      relativeList: [],
+      cameraAreas: [],
+      groupModelVisible: false,
+      groupList: [],
+      curGroup: {},
+      groupForm: {
+        groupName: '',
+        cameras: []
+      },
+      groupCameras: [],
+      groupCamera: {
+
+      },
+      cameraAndPolygonData: []
+    }
+  },
+  mounted () {
+    this.getAllCameraData();
+  },
+  methods: {
+    getAllCameraData () {
+      let _this = this;
+      getCamerasByServer().then(res => {
+        if (res.success) {
+          _this.cameraData = res.data;
+          _this.getAllGroups();
+        }
+      }).catch(e => {
+        console.log(e)
+      })
+    },
+    polygonUpdate () {
+      this.getAllGroups();
+    },
+    delRelation (item) {
+      let _this = this;
+      delRelation(item.id).then(res => {
+        if (res.success) {
+          this.$notify({
+            type: 'success',
+            message: res.data
+          });
+          _this.findRelationByGroup();
+        }
+      })
+    },
+    findRelationByGroup () {
+      let _this = this;
+      findByGroup({ groupId: this.curGroup.id }).then(res => {
+
+        _this.relativeList = res.data.map(relation => {
+          let obj = { sourceObj: {}, targetObj: {} };
+          obj.sourceObj.cameraId = relation.source_camera_id;
+          obj.sourceObj.polygonId = relation.source_polygon_id;
+          obj.targetObj.cameraId = relation.target_camera_id;
+          obj.targetObj.polygonId = relation.target_polygon_id;
+          obj.sourceObj.name = relation.source_polygon.name;
+          obj.targetObj.name = relation.target_polygon.name;
+          obj.id = relation.id;
+          return obj;
+        })
+      })
+    },
+    saveRelativePolygon (item) {
+      debugger
+      let _this = this;
+      let params = {
+        groupId: this.curGroup.id,
+        source_camera_id: item.sourceObj.cameraId,
+        target_camera_id: item.targetObj.cameraId,
+        source_polygon_id: item.sourceObj.polygonId,
+        target_polygon_id: item.targetObj.polygonId,
+        id: item.id || ''
+      }
+
+      saveRelationPolygon(params).then(res => {
+        if (res.success) {
+          this.$notify({
+            type: 'success',
+            message: '淇濆瓨鎴愬姛',
+          });
+          _this.findRelationByGroup()
+        }
+      })
+    },
+
+    async findPolygonByIds (cameras) {
+      for (var i = 0; i < cameras.length; i++) {
+        let res = await getAllPolygon({ cameraId: cameras[i].id });
+        cameras[i].canvasData = res.data;
+      }
+      return cameras
+    },
+
+    async getAllGroupInfo () {
+      let _this = this;
+      let res = await findCameraGroups();
+      let groupArr = res.data.map(item => {
+        let obj = {}; //group
+        obj.groupName = item.groupName;
+        obj.id = item.id;
+        let cameras = []; //cameras
+        item.cameraIds.forEach(id => {
+          let camera = {};
+          _this.cameraData.find(one => {
+            if (one.id == id) {
+              camera.name = one.name;
+              camera.id = id;
+            }
+          });
+          cameras.push(camera)
+        });
+        //cameras [{id,name}]
+        obj.cameras = cameras;
+        return obj;
+
+      });
+      return groupArr;
+    },
+    // async getAllGroups () {
+    //   let _this = this;
+    //   let groups = await this.getAllGroupInfo();
+    //   let promiseArr = [];
+    //   for (var i = 0; i < groups.length; i++) {
+    //     let pro = new Promise(resolve => {
+    //       resolve(_this.findPolygonByIds(groups[i].cameras))
+    //     });
+    //     promiseArr.push(pro)
+    //   }
+    //   Promise.allSettled(promiseArr).then(camerasArr => {
+
+    //     for (var i = 0; i < camerasArr.length; i++) {
+    //       groups[i].cameras = camerasArr[i].value
+    //     }
+    //     _this.groupList = groups;
+    //     //閫変腑绗竴涓�
+    //     _this.checkCurrentGroup(_this.groupList[0]);
+    //   })
+    // },
+    async getAllGroups () {
+      let _this = this;
+      let groups = await this.getAllGroupInfo();
+      for (var i = 0; i < groups.length; i++) {
+        groups[i].cameras = await _this.findPolygonByIds(groups[i].cameras)
+      }
+      this.groupList = groups;
+      //閫変腑绗竴涓�
+      this.groupList.length && this.checkCurrentGroup(_this.groupList[0]);
+
+    },
+
+    addRelation () {
+      let obj = { cameraArea1: '', cameraArea2: '' };
+      this.relativeList.push(obj)
+    },
+
+    editGroup (group) {
+      this.groupModelVisible = true;
+      //this.$refs['groupForm'].resetFields();
+      this.groupForm = group;
+    },
+    removeGroup (group) {
+      let _this = this;
+      delCameraGroup(group.id).then(res => {
+        _this.getAllGroups()
+      })
+    },
+    checkCurrentGroup (group) {
+      this.groupList.forEach(group => {
+        group.checked = false;
+      })
+      this.curGroup = group;
+      this.curGroup.checked = true;
+      this.findRelationByGroup();
+      //鏌ヨ绗竴涓垎缁勪笅鎽勫儚鏈哄尯鍩�
+      findByCamGroup({ groupId: group.id }).then(res => {
+        let tempArr = [];
+        res.data.forEach(camera => {
+          let cameraArea = [];
+          camera.polygon.forEach(item => {
+            let area = {};
+            area.name = camera.camera_name + '' + item.name;
+            area.cameraId = camera.camera_id;
+            area.polygonId = item.id;
+            cameraArea.push(area);
+          });
+          camera.rect.forEach(item => {
+            let area = {};
+            area.name = camera.camera_name + '' + item.name;
+            area.cameraId = camera.camera_id;
+            area.polygonId = item.id;
+            cameraArea.push(area);
+          });
+          tempArr = tempArr.concat(cameraArea)
+        });
+        this.cameraAreas = tempArr;
+        console.log(this.cameraAreas)
+      })
+    },
+    confirmGroupDialog () {
+      //璇锋眰淇濆瓨鏂板缓鎴栫紪杈戝垎缁�
+      let _this = this;
+      let params = {
+        cameraIds: [],
+        groupName: '',
+        id: ''
+      };
+      if (!this.groupForm.groupName.trim()) {
+        this.$notify({
+          type: 'warning',
+          message: '璇疯緭鍏ュ垎缁勫悕绉�'
+        });
+        return
+      }
+      if (this.groupForm.cameras.length < 2) {
+        this.$notify({
+          type: 'warning',
+          message: '璇烽�夋嫨鑷冲皯涓や釜鎽勫儚鏈�'
+        });
+        return
+      }
+      params.groupName = this.groupForm.groupName;
+      params.id = this.groupForm.id || '';
+      this.groupForm.cameras.forEach(camera => {
+        params.cameraIds.push(camera.id);
+        //鏍规嵁鍒嗙粍鍐呮憚鍍忔満id鏌ュ悇鑷尯鍩�
+        getAllPolygon({ cameraId: camera.id }).then(res => {
+          _this.groupCameras.push(res.data);
+        }).catch(e => {
+          console.log(e)
+        });
+      })
+      //this.groupList.push(this.groupForm);
+      saveCameraGroupInfo(params).then(res => {
+        _this.getAllGroups();
+      })
+      this.groupModelVisible = false;
+
+    },
+    newGroup () {
+      this.groupModelVisible = true;
+      this.$nextTick(() => {
+        this.$refs['groupForm'].resetFields();
+      })
+      this.groupForm = {
+        groupName: '',
+        cameras: []
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.relate-camera {
+  padding: 20px 30px;
+  .el-alert--info {
+    background: rgba(230, 247, 255, 1);
+    border-color: rgba(145, 213, 255, 1);
+    color: #666;
+    .el-alert__icon.el-icon-info {
+      color: #1890ff;
+    }
+  }
+  .title {
+    text-align: left;
+    font-size: 15px;
+    line-height: 40px;
+    .el-button {
+      margin-left: 14px;
+    }
+  }
+  .group-card {
+    cursor: pointer;
+    background: #fff;
+    width: 293px;
+    height: 153px;
+    margin-right: 10px;
+    box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
+    border: 1px solid transparent;
+    &.checked {
+      border-color: #1890ff;
+    }
+    .top {
+      height: 104px;
+      display: flex;
+      padding: 20px 20px 0;
+      box-sizing: border-box;
+      //align-items: center;
+      .right {
+        text-align: left;
+        padding-left: 14px;
+      }
+      .icon {
+        font-size: 40px;
+      }
+      .name {
+        font-size: 16px;
+        margin-bottom: 10px;
+        font-weight: bold;
+      }
+      .details {
+        display: -webkit-box;
+        overflow: hidden;
+        text-overflow:ellipsis;
+        word-break:break-all;
+        -webkit-box-orient: vertical;
+        -webkit-line-clamp: 2;
+        //flex-wrap: wrap;
+        label {
+          width: 46px;
+        }
+        .sub {
+        }
+      }
+    }
+    .bottom {
+      border-top: 1px solid #e9e9e9;
+      height: 48px;
+      line-height: 48px;
+      background: #f7f9fa;
+      position: relative;
+      display: flex;
+      &:after {
+        content: '';
+        position: absolute;
+        font-size: 0;
+        width: 1px;
+        height: 14px;
+        background: #0000006d;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+      }
+      span {
+        cursor: pointer;
+        width: 50%;
+        font-size: 14px;
+        color: #0000006d;
+      }
+    }
+  }
+  .part {
+    margin-bottom: 20px;
+    .relative-partment {
+      width: 1200px;
+      margin: 0 auto;
+      display: flex;
+      justify-content: space-between;
+      .area-wrap {
+        width: 576px;
+      }
+    }
+  }
+  .relative-config {
+    width: 700px;
+    .title {
+      display: flex;
+      justify-content: space-between;
+    }
+    .relative-list {
+      .relative-item {
+        width: 680px;
+        height: 48px;
+        padding-left: 20px;
+        background: #ecf2f5;
+        margin-bottom: 6px;
+        display: flex;
+        align-items: center;
+        .left {
+          width: 650px;
+          text-align: left;
+        }
+        .right {
+          display: flex;
+          align-items: center;
+        }
+        i {
+          font-size: 20px;
+          padding: 0 10px;
+          cursor: pointer;
+        }
+      }
+    }
+  }
+  .dialog-group {
+    .el-form {
+      .el-form-item__content {
+        display: flex;
+        label {
+          width: 80px;
+          text-align: left;
+        }
+        & > div {
+          flex: 1;
+          .el-select {
+            width: 100%;
+          }
+        }
+      }
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/panoramicView/components/SlideCanvas.vue b/src/pages/panoramicView/components/SlideCanvas.vue
new file mode 100644
index 0000000..14362f4
--- /dev/null
+++ b/src/pages/panoramicView/components/SlideCanvas.vue
@@ -0,0 +1,231 @@
+<template>
+  <div class="swiper-box">
+    <p class="task-tip" v-if="cameras.length == 0 "></p>
+    <swiper
+      ref="cameraSwiper"
+      v-if="cameras.length>=1"
+      :options="swiperOption"
+      class="swiper-box-container"
+    >
+      <swiper-slide v-for="camera in cameras" :key="camera.id+'c'">
+        <div class="swiper-draw-box-title">
+          <b>{{camera.name}}</b>
+          <b style="margin-left:14px;">缁樺埗鍖哄煙</b>
+          <span
+            class="el-dropdown-link"
+            @click="drawBaseImg(camera.id)"
+            style="position: relative;top: 5px; cursor:pointer"
+          >
+            <i class="iconfont iconbianji1" style="font-size: 28px; "></i>
+          </span>
+        </div>
+        <polygon-canvas
+          class="polygon-canvas"
+          :ref="`polygonCanvas_${camera.id}`"
+          :currentCameraId="camera.id"
+          :snapshot_url="camera.canvasData.snapshot_url"
+          :canvasDataShow="camera.canvasData"
+          :canvasWidth="576"
+          :canvasHeight="324"
+          @fromCanvas="getCanvasData"
+          @refresh="refresh"
+        ></polygon-canvas>
+      </swiper-slide>
+    </swiper>
+    <div class="swiper-pre-border" slot="button-prev" @click="pre" >
+      <div class="icon-btn" >
+        <i class="iconfont iconzuo"></i>
+      </div>
+    </div>
+    <div class="swiper-next-border" slot="button-next" @click="next">
+      <div class="icon-btn" >
+        <i class="iconfont iconyou1"></i>
+      </div>
+    </div>
+
+  </div>
+</template>
+
+<script>
+import { chunkArr } from '@/scripts/util';
+import PolygonCanvas from '@/components/canvas';
+import { savePolygon } from "@/api/polygon";
+export default {
+  //cameras: [{ id: '',snapshot_url:'', canvasData: {} }]
+  props: [
+    'cameras',
+    //'swiperOption'
+  ],
+  watch:{
+    cameras:{
+      handler(n,o){
+        console.log('slidecanvas cameras',n)
+      },
+      deep: true
+    }
+  },
+  components: { PolygonCanvas },
+  data () {
+    return {
+      swiperOption: this.newOption(),
+      //mySwiper: {}
+    }
+  },
+  computed: {
+    swiper () {
+      return this.$refs['cameraSwiper'].swiper
+    }
+  },
+  mounted () {
+    //this.mySwiper = this.$refs.sceneSwiper.swiper;
+    console.log(this.swiper)
+  },
+  methods: {
+    refresh(url,cameraId) {
+      this.$emit('polygonDataUpdate')
+
+      //let camera = this.cameras.find(one => one.id == cameraId);
+      //camera.canvasData.snapshot_url = url;
+
+    },
+    getCanvasData(data) {
+      let _this = this;
+      savePolygon(data).then(rsp => {
+        _this.$emit('polygonDataUpdate')
+      });
+    },
+    newOption () {
+      return {
+        slidesPerView: 1,
+        spaceBetween: 0,
+        pagination: {
+          el: ".swiper-pagination",
+          clickable: true
+        },
+        observer: true,//淇敼swiper鑷繁鎴栧瓙鍏冪礌鏃讹紝鑷姩鍒濆鍖杝wiper
+        observeParents: true,//淇敼swiper鐨勭埗鍏冪礌鏃讹紝鑷姩鍒濆鍖杝wiper
+      }
+    },
+
+    pre () {
+      this.swiper.activeIndex--
+      this.swiper.slideTo(this.swiper.activeIndex);
+    },
+    next () {
+      this.swiper.activeIndex++
+      this.swiper.slideTo(this.swiper.activeIndex);
+    },
+    drawBaseImg (id) {
+      this.$refs[`polygonCanvas_${id}`][0].showModal();
+    }
+  }
+};
+</script>
+
+<style lang="scss">
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+.task-tip {
+  font-family: PingFangSC-Regular;
+  font-size: 12px;
+  color: #cccccc;
+  margin-top: 10%;
+}
+.swiper-box {
+  position: relative;
+}
+.swiper-pre-border,
+.swiper-next-border {
+  width: 40px;
+  height: 40px;
+  position: absolute;
+  background: #8888;
+  top: 50%;
+  margin-top: -20px;
+  z-index: 99;
+  border-radius: 4em;
+  outline: none;
+  .icon-btn {
+    color: rgb(255, 255, 255);
+    text-align: center;
+    line-height: 38px;
+    cursor: pointer;
+  }
+}
+.swiper-pre-border {
+  left: 10px;
+}
+.swiper-pre-border:hover {
+  background: #666;
+}
+.swiper-next-border {
+  right: 10px;
+}
+.swiper-next-border:hover {
+  background: #666;
+}
+.swiper-draw-box-title {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 24px;
+  b {
+    font-size: 14px;
+  }
+}
+.wrap-box {
+  width: 100%;
+  display: inline-block;
+  .inner {
+    width: 90%;
+    box-sizing: border-box;
+    position: relative;
+    font-size: 14px;
+    padding: 7px 0 48px;
+    transition: all 1s;
+    background: #ffffff;
+    border: 1px solid #e2e2e2;
+    box-shadow: 0 5px 12px 0 rgba(0, 0, 0, 0.07);
+    border-radius: 4px;
+    margin: auto;
+    &:hover {
+      .mask {
+        display: block;
+      }
+    }
+    .mask {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background: rgba(0, 0, 0, 0.65);
+      backdrop-filter: blur(1px) brightness(100%);
+      text-align: center;
+      z-index: 1;
+      border-radius: 3px;
+      display: none;
+      .tool {
+        position: absolute;
+        top: 49%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+        i {
+          font-size: 50px;
+        }
+        i:nth-of-type(1) {
+          margin-right: 30px;
+        }
+        i:nth-of-type(2) {
+          color: red;
+        }
+      }
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/panoramicView/components/TracePlot.vue b/src/pages/panoramicView/components/TracePlot.vue
new file mode 100644
index 0000000..f12095d
--- /dev/null
+++ b/src/pages/panoramicView/components/TracePlot.vue
@@ -0,0 +1,153 @@
+<template>
+  <div class="trace-plot">
+    <div class="filter-bar">
+      <el-date-picker
+        size="small"
+        v-model="searchTime"
+        @change="searchData"
+        type="datetimerange"
+        start-placeholder="寮�濮嬫棩鏈�"
+        end-placeholder="缁撴潫鏃ユ湡"
+        value-format="yyyy-MM-dd HH:mm:ss"
+        :default-time="['00:00:00','23:59:59']"
+
+      ></el-date-picker>
+      <el-button @click="searchData" size="small" type="primary" class="btn-search">鏌� 璇�</el-button>
+    </div>
+    <!-- <p class="p-date" style="width:19%;vertical-align: top;">
+      <el-date-picker
+        size="mini"
+        v-model="searchTime"
+        @change="searchData"
+        type="datetimerange"
+        start-placeholder="寮�濮嬫棩鏈�"
+        end-placeholder="缁撴潫鏃ユ湡"
+        value-format="yyyy-MM-dd HH:mm:ss"
+        :default-time="['00:00:00','23:59:59']"
+        style="width:99%;min-width:200px"
+      ></el-date-picker>
+    </p> -->
+    <canvas ref="trackArea" width="960" height="540" :style="{backgroundImage:`url(${panoramaPath})`}"></canvas>
+  </div>
+</template>
+
+<script>
+import { getSearchList } from '@/api/search';
+import { getPanoramaPic } from "@/api/panorama";
+export default {
+  data () {
+    return {
+      panoramaPath:'',
+      trackData: [],
+      activeObjHashMap: {},
+      actObj: {},
+      searchTime: [
+        this.$moment().format("YYYY-MM-DD 00:00:00"),
+        this.$moment().format("YYYY-MM-DD 23:59:59")
+        //this.$moment().format("YYYY-MM-DD HH:mm:ss")
+      ],
+      timer: null,
+    }
+  },
+  mounted () {
+    this.searchData();
+    this.getPanoramaPic();
+    this.timer = setInterval(()=>{
+      this.searchData();
+    },7000);
+  },
+  beforeDestroy(){
+    console.log('beforeDestroy')
+    clearInterval(this.timer);
+  },
+  methods: {
+    getPanoramaPic () {
+      let _this = this;
+      getPanoramaPic().then(res=>{
+        _this.panoramaPath = res.data.panoramaPath
+      })
+    },
+    drawTracePath () {
+      //let canvas = document.querySelector('#trackArea');
+      let canvas = this.$refs['trackArea'];
+      let ctx = canvas.getContext('2d');
+      ctx.clearRect(0,0,canvas.width,canvas.height);
+      ctx.strokeStyle = 'yellow';
+      ctx.fillStyle = 'aqua';
+      //ctx.globalAlpha=0.5;
+      ctx.lineWidth = 2;
+      //ctx.lineJoin='round';
+      ctx.lineCap = 'round';
+      for (var target in this.actObj) {
+        ctx.beginPath();
+        ctx.fillStyle = `rgb(${Math.floor(Math.random() * 20 + 220)},${Math.floor(Math.random() * 35 + 200)},${Math.floor(Math.random() * 55 + 200)})`;
+        ctx.strokeStyle = `rgb(${Math.floor(Math.random() * 20 + 220)},${Math.floor(Math.random() * 20 + 210)},${Math.floor(Math.random() * 55 + 200)})`;
+        ctx.fillRect(this.actObj[target][0].globalCoordX, this.actObj[target][0].globalCoordY, 9, 9);
+        ctx.moveTo(this.actObj[target][0].globalCoordX, this.actObj[target][0].globalCoordY);
+        for (var i = 1; i < this.actObj[target].length; i++) {
+          ctx.lineTo(this.actObj[target][i].globalCoordX, this.actObj[target][i].globalCoordY);
+          ctx.fillRect(this.actObj[target][i].globalCoordX, this.actObj[target][i].globalCoordY, 9, 9);
+        }
+        ctx.stroke();
+      }
+    },
+    searchData () {
+      let _this = this;
+      var param = {
+        page: 1,
+        size: 70,
+        searchTime: this.searchTime,
+        alarmlevel: [],
+        inputValue: '',
+        tabs: [],
+        tasks: [],
+        treeNodes: [],
+        isAll: true
+      };
+      getSearchList(param).then(res => {
+        let filterArr = [];
+        res.data.datalist&&res.data.datalist.forEach(obj => {
+          if (_this.activeObjHashMap[obj.activeObject.id] !== 1) {
+            _this.activeObjHashMap[obj.activeObject.id] = 1
+            filterArr.push(obj)
+          }
+        });
+        filterArr.forEach(item => {
+          item.activeObject.targetInfo.forEach(target => {
+            if (target.targetType == 'UniquelID') {
+              let attribute = JSON.parse(target.attribute)
+              if (_this.actObj[target.targetId]) {
+                _this.actObj[target.targetId].push({ globalCoordX: attribute.globalCoordX, globalCoordY: attribute.globalCoordY });
+              } else {
+                _this.actObj[target.targetId] = [{ globalCoordX: attribute.globalCoordX, globalCoordY: attribute.globalCoordY }];
+              }
+            }
+          })
+        });
+        console.log(_this.actObj)
+        _this.drawTracePath()
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.trace-plot {
+  .filter-bar{
+    width: 960px;
+    margin: 20px auto;
+    display: flex;
+    align-items: center;
+    flex-direction: end;
+    .btn-search{
+      margin-left:20px;
+    }
+  }
+  canvas {
+    background: lightsteelblue;
+    background-repeat: no-repeat;
+    
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/panoramicView/index/App.vue b/src/pages/panoramicView/index/App.vue
new file mode 100644
index 0000000..90550f6
--- /dev/null
+++ b/src/pages/panoramicView/index/App.vue
@@ -0,0 +1,95 @@
+<template>
+  <div class="panoramic-view">
+    <div class="top-nav">
+      <div class="logo">
+        <div class="icon"></div>
+        <a class="title">闀挎槬鍏ㄦ櫙杩借釜</a>
+      </div>
+      <el-menu
+        :default-active="actMenuIndex"
+        class="el-menu-demo"
+        mode="horizontal"
+        @select="checkMenu"
+      >
+        <el-menu-item index="1">浣嶇疆鏍囧畾</el-menu-item>
+        <el-menu-item index="2">杞ㄨ抗鍥�</el-menu-item>
+        <el-menu-item index="3">鍏宠仈鎽勫儚鏈�</el-menu-item>
+      </el-menu>
+    </div>
+    <div class="act-view">
+      <template v-if="actMenuIndex=='1'">
+        <label-mark></label-mark>
+      </template>
+      <template v-if="actMenuIndex=='2'">
+        <trace-plot></trace-plot>
+      </template>
+      <template v-if="actMenuIndex=='3'">
+        <relate-camera></relate-camera>
+      </template>
+    </div>
+  </div>
+</template>
+
+<script>
+import LabelMark from '../components/LabelMark';
+import TracePlot from '../components/TracePlot';
+import RelateCamera from '../components/RelateCamera';
+export default {
+  components: { LabelMark, TracePlot, RelateCamera },
+  data () {
+    return {
+      actMenuIndex: '1',
+    }
+  },
+  methods: {
+    checkMenu (key, keyPath) {
+      this.actMenuIndex = key;
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.panoramic-view {
+  min-width: 1399px;
+  overflow: hidden;
+  height: 100%;
+  .top-nav {
+    display: flex;
+    background: rgba(28, 26, 96, 1);
+    .logo {
+      width: 310px;
+      display: flex;
+      align-items: center;
+      .icon{
+        width: 100px;
+        
+      }
+      .title{
+        text-decoration: none;
+        margin-left: 10px;
+        font-size: 18px;
+        color: #fff;
+        letter-spacing: 2px;
+      }
+    }
+    .el-menu--horizontal {
+      border-bottom: 0;
+      background: rgba(28, 26, 96, 1);
+    }
+    .el-menu--horizontal > .el-menu-item {
+      border-bottom: 0;
+      color: #fff;
+      &:hover{
+        color: #fff;
+        background: rgb(24, 35, 182);
+      }
+    }
+    .el-menu--horizontal > .el-menu-item.is-active {
+      //border-bottom: 0;
+      background-color: rgba(61, 73, 225, 1);
+      color: #fff;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/panoramicView/index/main.ts b/src/pages/panoramicView/index/main.ts
new file mode 100644
index 0000000..bffdf32
--- /dev/null
+++ b/src/pages/panoramicView/index/main.ts
@@ -0,0 +1,19 @@
+import Vue from 'vue';
+import App from './App.vue';
+
+import moment from "moment";
+import ElementUI from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+import "@/assets/css/element-variables.scss";
+import VueAwesomeSwiper from "vue-awesome-swiper";
+import "swiper/dist/css/swiper.css";
+import Mixin from "./mixins";
+
+Vue.use(ElementUI);
+Vue.use(VueAwesomeSwiper as any);
+Vue.mixin(Mixin);
+Vue.prototype.$moment = moment;
+new Vue({
+  el: '#app',
+  render: h => h(App)
+});
\ No newline at end of file
diff --git a/src/pages/panoramicView/index/mixins.ts b/src/pages/panoramicView/index/mixins.ts
new file mode 100644
index 0000000..feda309
--- /dev/null
+++ b/src/pages/panoramicView/index/mixins.ts
@@ -0,0 +1,14 @@
+import TreeDataPool from "@/Pool/TreeData";
+
+/* eslint-disable */
+const onlyTreeDataPool = new TreeDataPool
+
+const mixin = {
+  data() {
+    return {
+      TreeDataPool: onlyTreeDataPool
+
+    };
+  },
+};
+export default mixin;
\ No newline at end of file

--
Gitblit v1.8.0