dedsudiyu 1 settimana fa
parent
commit
93fc69331e

+ 13 - 0
src/api/auth.js

@@ -11,3 +11,16 @@ export function loginApi(data) {
     data
   })
 }
+
+/**
+ * 获取验证码
+ * GET /auth/captcha
+ * 返回 { captcha: Boolean, image: String(base64), uuid: String }
+ */
+export function getCaptcha(id = 0) {
+  return request({
+    url: '/auth/captcha',
+    method: 'get',
+    params: { id }
+  })
+}

+ 61 - 2
src/api/screen.js

@@ -69,16 +69,64 @@ export function getDeviceCategoryStat() {
 
 /**
  * 7. 风险预警 - 触发预案信息查询
- * GET /laboratory/large/selectTriggerInfo
+ * GET /laboratory/labScreen/selectTriggerInfo
  */
 export function selectTriggerInfo() {
   return request({
-    url: '/laboratory/large/selectTriggerInfo',
+    url: '/laboratory/labScreen/selectTriggerInfo',
     method: 'get'
   })
 }
 
 /**
+ * 11. 耗材相关二级学院下拉列表
+ * POST /laboratory/labScreen/deptDropList
+ */
+export function getDeptDropList(data = {}) {
+  return request({
+    url: '/laboratory/labScreen/deptDropList',
+    method: 'post',
+    data
+  })
+}
+
+/**
+ * 12. 根据条件查询楼道实验室信息
+ * POST /laboratory/labScreen/rooms
+ */
+export function getRooms(data = {}) {
+  return request({
+    url: '/laboratory/labScreen/rooms',
+    method: 'post',
+    data
+  })
+}
+
+/**
+ * 13. 实验室分级配置列表
+ * POST /laboratory/labScreen/getLevelTitleList
+ */
+export function getLevelTitleList() {
+  return request({
+    url: '/laboratory/labScreen/getLevelTitleList',
+    method: 'post',
+    data: {}
+  })
+}
+
+/**
+ * 14. 获取摄像头播放地址
+ * GET /laboratory/labScreen/getPreviewURLs
+ */
+export function getPreviewURLs(params) {
+  return request({
+    url: '/laboratory/labScreen/getPreviewURLs',
+    method: 'get',
+    params
+  })
+}
+
+/**
  * 8. 实时监控 - 楼栋楼层树
  * POST /laboratory/labScreen/monitorTree
  */
@@ -124,3 +172,14 @@ export function getWeather() {
     method: 'get'
   })
 }
+
+/**
+ * 11. 根据设备ID查询视频流
+ * GET /iot/camera/getPreviewURLs
+ */
+export function iotCameraGetPreviewURLs() {
+  return request({
+    url: '/iot/camera/getPreviewURLs',
+    method: 'get'
+  })
+}

BIN
src/assets/icon_0.png


BIN
src/assets/icon_1.png


+ 2 - 2
src/components/EquipmentStats.vue

@@ -142,7 +142,7 @@ export default {
           itemGap: 22,
           textStyle: { color: '#a8cce8', fontSize: 22 },
           formatter: function(name) {
-            return '{nm|' + name + '}  {vl|' + countMap[name] + '台}'
+            return name + ' ' + countMap[name] + '台'
           },
           rich: {
             nm: { fontSize: 22, color: '#a8cce8', width: 90 },
@@ -218,7 +218,7 @@ export default {
           itemGap: 40,
           textStyle: { color: '#a8cce8', fontSize: 28 },
           formatter: function(name) {
-            return '{nm|' + name + '}  {vl|' + countMap[name] + '台}'
+            return name + ' ' + countMap[name] + '台'
           },
           rich: {
             nm: { fontSize: 28, color: '#a8cce8', width: 90 },

+ 11 - 11
src/components/EventStats.vue

@@ -50,8 +50,8 @@
           <!-- Endpoint glow -->
           <circle cx="160" cy="24" r="6" fill="#1e90ff" opacity="0.9"/>
           <!-- Center number -->
-          <text x="160" y="145" text-anchor="middle" fill="#ffd740" font-size="72" font-weight="900" font-family="Arial,sans-serif" letter-spacing="-2">{{ total }}</text>
-          <text x="160" y="190" text-anchor="middle" fill="rgba(168,204,232,0.75)" font-size="26" font-family="Arial,sans-serif" letter-spacing="4">间</text>
+          <text x="160" y="148" text-anchor="middle" fill="#ffd740" font-size="46" font-weight="900" font-family="Arial,sans-serif" letter-spacing="-2">{{ total }}</text>
+          <text x="160" y="178" text-anchor="middle" fill="rgba(168,204,232,0.75)" font-size="20" font-family="Arial,sans-serif" letter-spacing="4">间</text>
         </svg>
         <!-- Labels -->
         <div class="gauge-label">
@@ -210,28 +210,28 @@ export default {
           formatter: '{b}: {c}间 ({d}%)',
           backgroundColor: 'rgba(3,14,42,0.92)',
           borderColor: 'rgba(30,144,255,0.3)',
-          textStyle: { color: '#a8cce8', fontSize: 28 }
+          textStyle: { color: '#a8cce8', fontSize: 13 }
         },
         graphic: [
           {
             type: 'text',
             left: 'center',
-            top: '40%',
+            top: '38%',
             style: {
               text: String(this.total),
               fill: '#ffd740',
-              font: 'bold 80px Arial',
+              font: 'bold 38px Arial',
               textAlign: 'center'
             }
           },
           {
             type: 'text',
             left: 'center',
-            top: '58%',
+            top: '56%',
             style: {
               text: '实验室总数(间)',
               fill: 'rgba(168,204,232,0.65)',
-              font: '22px Arial',
+              font: '13px Arial',
               textAlign: 'center'
             }
           }
@@ -252,9 +252,9 @@ export default {
               return '{lvl|' + params.name + '}\n{cnt|' + params.value + '间}\n{pct|' + params.percent + '%}'
             },
             rich: {
-              lvl: { fontSize: 24, fontWeight: 700, color: '#ddf0ff', lineHeight: 36 },
-              cnt: { fontSize: 30, fontWeight: 900, color: '#ffd740', lineHeight: 42 },
-              pct: { fontSize: 22, color: 'rgba(168,204,232,0.6)', lineHeight: 32 }
+              lvl: { fontSize: 13, fontWeight: 700, color: '#ddf0ff', lineHeight: 22 },
+              cnt: { fontSize: 16, fontWeight: 900, color: '#ffd740', lineHeight: 26 },
+              pct: { fontSize: 12, color: 'rgba(168,204,232,0.6)', lineHeight: 20 }
             },
             distanceToLabelLine: 8
           },
@@ -279,7 +279,7 @@ export default {
             scale: true,
             scaleSize: 8,
             itemStyle: { shadowBlur: 30, shadowColor: 'rgba(30,144,255,0.6)' },
-            label: { fontSize: 28 }
+            label: { fontSize: 14 }
           }
         }]
       }

+ 3 - 3
src/components/H5PlayerVideo/H5PlayerVideo.vue

@@ -185,12 +185,12 @@
     }
     position: relative;
     .full-screen-button {
-      background: url("../../assets/ZDimages/basicsModules/icon_0.png");
+      background: url("../../assets/icon_0.png");
       background-size: 100%;
       position: absolute;
       font-size: 20px;
-      height: 30px;
-      width: 30px;
+      height: 60px;
+      width: 60px;
       line-height: 30px;
       text-align: center;
       top: 0;

+ 449 - 47
src/components/SecurityMonitor.vue

@@ -16,30 +16,51 @@
       <div class="monitor-left">
         <div class="search-box">
           <span class="search-icon">&#x1F50D;</span>
-          <input
-            v-model="searchText"
-            type="text"
-            placeholder="&#x641C;&#x7D22;&#x697C;&#x680B; / &#x697C;&#x5C42; / &#x623F;&#x95F4;&#x2026;"
-          />
+          <el-input class="video-select" 
+            @keyup.enter.native="handleEnter"
+            @clear="handleEnter"
+            clearable
+            v-model="queryParams.searchValue" 
+            type="text" placeholder="搜索楼栋/楼层...." >
+          </el-input>
         </div>
 
-        <select v-model="selectedUnit" class="filter-select">
-          <option value="">&#x5168;&#x90E8;&#x4E8C;&#x7EA7;&#x5355;&#x4F4D;</option>
-          <option>&#x5316;&#x5B66;&#x7814;&#x7A76;&#x6240;</option>
-          <option>&#x7269;&#x7406;&#x7814;&#x7A76;&#x6240;</option>
-          <option>&#x751F;&#x7269;&#x7814;&#x7A76;&#x6240;</option>
-          <option>&#x6750;&#x6599;&#x7814;&#x7A76;&#x6240;</option>
-          <option>&#x5DE5;&#x7A0B;&#x7814;&#x7A76;&#x6240;</option>
-        </select>
+        <el-select class="filter-select" 
+          @change="handleEnter"
+          @clear="handleEnter"
+          clearable
+          v-model="queryParams.deptId" 
+          placeholder="请选择二级单位">
+          <el-option
+            v-for="item in options"
+            :key="item.deptId"
+            :label="item.deptName"
+            :value="item.deptId">
+          </el-option>
+        </el-select>
 
         <div class="tree-wrap">
-          <tree-node
-            v-if="treeData"
-            :node="treeData"
-            :depth="0"
-            :selected-label="selectedTreeLabel"
-            @select="onTreeNodeSelect"
-          />
+          <el-tree
+            class="dark-tree"
+            @node-click="nodeClickButton"
+            :current-node-key="treeId"
+            :check-on-click-node="false"
+            check-strictly
+            highlight-current
+            :default-expanded-keys="defaultKey"
+            node-key="treeId"
+            :data="deptOptions"
+            :props="defaultProps"
+            ref="tree"
+            :load="loadNode"
+            accordion
+            lazy>
+            <template #default="{ node, data }">
+              <span class="tree-node-label">
+                <span>{{ data.deptName }}</span>
+              </span>
+            </template>
+          </el-tree>
         </div>
       </div>
 
@@ -47,9 +68,7 @@
       <div class="monitor-right">
         <div class="camera-grid-header">
           <div class="camera-breadcrumb">
-            <span>&#x5B89;&#x79D1;&#x9662;&#x4E3B;&#x56ED;&#x533A;</span> &#x203A;
-            <span>&#x7EFC;&#x5408;&#x5B9E;&#x9A8C;&#x697C;A</span> &#x203A;
-            <span>3&#x5C42;</span>
+            <span>{{breadcrumb}}</span>
           </div>
           <div class="camera-pager">
             <button class="pager-btn" @click="prevPage">&lsaquo;</button>
@@ -59,38 +78,33 @@
         </div>
 
         <div class="camera-grid">
-          <div
+          <H5PlayerVideo v-for="(item,index) in videoList" :key="index" :videoProps="item"></H5PlayerVideo>
+          <!-- <div
             v-for="(cam, idx) in cameras"
             :key="'cam-' + idx"
             class="camera-cell"
             :class="{ 'ai-cam': idx === 0 }"
           >
-            <!-- AI badge (first cell only) -->
             <div v-if="idx === 0" class="camera-ai-badge">&#x1F916; AI&#x68C0;&#x6D4B;</div>
-
-            <!-- REC indicator -->
             <div class="camera-rec">REC</div>
-
-            <!-- Video placeholder -->
             <div class="camera-placeholder-text">CCTV</div>
-
-            <!-- AI detection box (first cell only) -->
             <div v-if="idx === 0" class="ai-detection-box" style="left:28%;top:18%;width:22%;height:38%">
               <div class="ai-detection-label">&#x5371;&#x9669;&#x884C;&#x4E3A;: &#x672A;&#x4F69;&#x6234;&#x9632;&#x62A4;</div>
             </div>
-
-            <!-- Camera label -->
             <div class="camera-label">{{ cam }}</div>
-          </div>
+          </div> -->
         </div>
       </div>
     </div>
+    <fullH5PlayerVideo v-if="fullVideoType" :fullVideoProps="fullVideoProps"></fullH5PlayerVideo>
   </div>
 </template>
 
 <script>
-import { getCameraList } from '@/api/screen'
+import { getCameraList,getMonitorTree,getRooms,getDeptDropList,getCameraStream } from '@/api/screen'
 
+import H5PlayerVideo from '@/components/H5PlayerVideo/H5PlayerVideo.vue'
+import fullH5PlayerVideo from '@/components/fullH5PlayerVideo/fullH5PlayerVideo.vue'
 const TreeNode = {
   name: 'TreeNode',
   props: {
@@ -100,7 +114,7 @@ const TreeNode = {
   },
   data() {
     return {
-      expanded: this.depth === 0
+      expanded: this.depth === 0,
     }
   },
   computed: {
@@ -113,6 +127,9 @@ const TreeNode = {
     },
     isSelected() {
       return this.node.label === this.selectedLabel
+    },
+    totalPages() {
+      return Math.ceil(this.videoTotal / 9) || 1
     }
   },
   methods: {
@@ -157,7 +174,7 @@ const TreeNode = {
 export default {
   name: 'SecurityMonitor',
   components: {
-    TreeNode
+    TreeNode,H5PlayerVideo,fullH5PlayerVideo
   },
   data() {
     return {
@@ -166,7 +183,7 @@ export default {
       selectedTreeLabel: '',
       currentPage: 1,
       totalPages: 3,
-      cameras: [],
+      cameras: [{},{},{},{},{},{},{},{},{},],
       treeData: {
         label: '\u5B89\u79D1\u9662\u4E3B\u56ED\u533A',
         children: [
@@ -194,13 +211,318 @@ export default {
             ]
           }
         ]
-      }
+      },
+      //左侧部分
+      defaultProps: {
+        children: "childTreeList",
+        label: "deptName",
+        isLeaf:"leaf",
+      },
+      queryParams:{
+        searchValue: "",
+        deptId: '',
+      },
+      deptOptions: [], 
+      options:[],
+      treeId:null,
+      defaultKey:null,
+      // 地址部分
+      breadcrumb:'',
+      // 视频部分
+      videoQueryParams:{
+        page:1,
+        pageSize:4,
+        streamType:1,
+        source:0,
+      },
+      videoType:false,
+      videoList:[],
+      videoTotal:0,
+      //全屏视频参数
+      fullVideoProps:{},
+      fullVideoType:false,
     }
   },
   created() {
-    this.fetchCameras()
+    // this.fetchCameras()
+  },
+  mounted() {
+    this.getMonitorTree();
+    this.getDeptDropList();
   },
   methods: {
+    
+    handleEnter(){
+      this.getMonitorTree();
+    },
+    //二级学院下拉列表
+    async getDeptDropList() {
+      let self = this;
+      try {
+        const res = await getDeptDropList({})
+        this.$set(this,'options',res.data[0].child);
+      } catch (e) {
+        console.error('BuildingNav:', e)
+      }
+    },
+    async getMonitorTree() {
+      let self = this;
+      let obj = JSON.parse(JSON.stringify(this.queryParams))
+      try {
+        const res = await getMonitorTree(obj)
+        let list = this.forTreeId(res.data);
+        this.$set(self,'deptOptions',list)
+        this.$nextTick(()=>{
+          let checkData = []
+          if(this.queryParams.searchValue){
+            checkData = list[0].childTreeList[0].childTreeList[0];
+            //展开列表
+            self.$set(self,'defaultKey',[list[0].treeId,list[0].childTreeList[0].treeId]);
+            setTimeout(function(){
+              self.$set(self,'defaultKey',[list[0].treeId,list[0].childTreeList[0].childTreeList[0].treeId]);
+            },200)
+          }else{
+            checkData = list[0].childTreeList[0];
+            //展开列表
+            self.$set(self,'defaultKey',[list[0].treeId,list[0].childTreeList[0].treeId]);
+          }
+          setTimeout(function(){
+            //选中列表
+            self.$refs.tree.setCurrentKey(checkData.treeId);
+            self.$set(self,'treeId',checkData.treeId);
+            //当前位置展示
+            self.checkAddress(checkData.treeId);
+            //父级视屏数据
+            self.getSubId(checkData);
+          },600);
+        })
+      } catch (e) {
+        console.error('BuildingNav:', e)
+      }
+    },
+    nodeClickButton(e,data){
+      console.log('e===>',e);
+      console.log('data===>',data);
+      let self = this;
+      this.$nextTick(() => {
+        if (!e.level||e.level == 2||e.level == 3) {
+          if(this.treeId != e.treeId){
+            this.treeId = e.treeId;
+            //等待后续逻辑-面板展示-实验室信息-视屏信息
+            //当前位置展示
+            this.checkAddress(e.treeId);
+            //查询楼栋/楼层下所有实验室ID
+            this.getSubId(e);
+          }
+        }
+        this.$refs.tree.setCurrentKey(this.treeId);
+      });
+    },
+    forTreeId(list){
+      let self = this;
+      list.forEach((item,index)=>{
+        item.treeId = item.id;
+        item.deptName = item.name;
+        item.level = item.type;
+        item.childTreeList = item.buildFloorVoList;
+        delete item.id
+        delete item.name
+        delete item.type
+        delete item.buildFloorVoList
+        if(item.childTreeList[0]){
+          self.forTreeId(item.childTreeList);
+        }
+      })
+      return list
+    },
+    //手动加载
+    loadNode(node, resolve) {
+      console.log('node',node);
+      console.log('resolve',resolve);
+      let self = this;
+      if (node.data){
+        if(node.data.level == 3){
+          let obj = {
+            deptId:this.queryParams.deptId,
+            searchValue:this.queryParams.searchValue,
+          }
+          obj.floorId = node.data.treeId;
+          getRooms(obj).then(response => {
+            for(let i=0;i<response.data.length;i++){
+              response.data[i].deptName = response.data[i].formatRoomName;
+              response.data[i].leaf = true;
+            }
+            resolve(response.data[0]?response.data:[]);
+          })
+        }else{
+          if(node.data.childTreeList){
+            if(node.data.childTreeList[0]){
+              if(node.data.level != 2){
+                node.data.childTreeList.forEach((item)=>{
+                  if(item.childTreeList){
+                    if(!item.childTreeList[0]){
+                      item.leaf = true;
+                    }
+                  }else{
+                    item.leaf = true;
+                  }
+                })
+              }
+              resolve(node.data.childTreeList);
+            }else{
+              resolve([]);
+            }
+          }else{
+            resolve([]);
+          }
+        }
+      }
+
+    },
+    //实验室ID查询
+    getSubId(e){
+      let self = this;
+      let obj = {
+        deptId:this.queryParams.deptId,
+        levelIds:[],
+      }
+      if(!e.level){
+        //实验室
+        // this.$parent.setVideoData([e.treeId]);
+        this.getVideoData([e.treeId])
+      }else if(e.level == 2){
+        //楼栋
+        obj.buildingId = e.treeId;
+        getRooms(obj).then(response => {
+          let  list = [];
+          for(let i=0;i<response.data.length;i++){
+            list.push(response.data[i].treeId)
+          }
+          // this.$parent.setVideoData(list);
+          this.getVideoData(list)
+        })
+      }else if(e.level == 3){
+        //楼层
+        obj.floorId = e.treeId;
+        getRooms(obj).then(response => {
+          let  list = [];
+          for(let i=0;i<response.data.length;i++){
+            list.push(response.data[i].treeId)
+          }
+          // this.$parent.setVideoData(list);
+          this.getVideoData(list)
+        })
+      }
+    },
+    //选中位置联查
+    checkAddress(id){
+      let list = this.forAddress(this.$refs.tree._data.root.childNodes,id);
+      // this.$parent.setAddress(list);
+      let text = '';
+      for(let i=0;i<list.length;i++){
+        if(i==0){
+          text = text + list[i];
+        }else{
+          text = text + ' - ' + list[i];
+        }
+      }
+      this.$set(this,'breadcrumb',text);
+    },
+    forAddress(list,id){
+      let self = this;
+      let nameList = [];
+      let name = []
+      for(let i=0;i<list.length;i++){
+        if(list[i].data.treeId == id){
+          return list[i].data.deptName
+        }else{
+          if(list[i].childNodes){
+            if(list[i].childNodes[0]){
+              name = self.forAddress(list[i].childNodes,id);
+              if(name){
+                nameList = [list[i].data.deptName].concat(name)
+                return nameList
+              }
+            }
+          }
+        }
+      }
+    },
+    //视频部分
+    //刷新视屏
+    getVideoData(data){
+      let obj = {
+        page:1,
+        pageSize:9,
+        passageway:'',
+        protocol:window.location.href.indexOf('https') !== -1?'wss':'ws',
+        streamType:1,
+        source:4,
+        subIds:data,
+      };
+      this.$set(this,'videoQueryParams',obj);
+      this.$nextTick(()=>{
+        this.videoInitialize()
+      })
+    },
+    async videoInitialize() {
+      let self = this;
+      self.$set(self, 'videoType', false);
+      self.$set(self, 'videoList', []);
+      try {
+        const res = await getCameraStream(this.videoQueryParams)
+        let list = [];
+        res.data.total = 100;
+        res.data.records = [
+          {streamUrl:'1', deviceNo:'1'},
+          {streamUrl:'2', deviceNo:'2'},
+          {streamUrl:'3', deviceNo:'3'},
+          {streamUrl:'4', deviceNo:'4'},
+          {streamUrl:'5', deviceNo:'5'},
+          {streamUrl:'6', deviceNo:'6'},
+          {streamUrl:'7', deviceNo:'7'},
+          {streamUrl:'8', deviceNo:'8'},
+          {streamUrl:'9', deviceNo:'9'},
+        ];
+        for(let i=0;i<res.data.records.length;i++){
+          list.push(
+            {
+              width: 1214, //(宽度:非必传-默认600)
+              height: 792, //(高度:非必传-默认338)
+              url: res.data.records[i].streamUrl,
+              cameraIndexCode: res.data.records[i].deviceNo,
+            }
+          )
+        }
+        this.$set(this,'videoList',list)
+        this.$set(this,'videoTotal',res.data.total);
+        this.$nextTick(()=>{
+          setTimeout(function(){
+            self.$set(self, 'videoType', true);
+          },1000);
+        })
+      } catch (e) {
+        console.error('VideoGrid:', e)
+      }
+    },
+    changePage(delta) {
+      this.videoQueryParams.page += delta
+      this.videoInitialize()
+    },
+    //全屏开启-关闭轮播
+    stopTime(cameraIndexCode){
+      this.$set(this,'fullVideoProps',{cameraIndexCode:cameraIndexCode});
+      this.$set(this,'fullVideoType',true);
+    },
+    //全屏关闭-开启轮播
+    outFullScreen(){
+      let self = this;
+      this.$set(this,'fullVideoType',false);
+      this.$set(this,'fullVideoProps',{});
+    },
+
+    //旧
+
     async fetchCameras() {
       try {
         const res = await getCameraList({ page: 1, pageSize: 9 })
@@ -239,7 +561,29 @@ export default {
   }
 }
 </script>
-
+<style lang="scss">
+.el-select-dropdown{
+  background-color: rgba(8, 55, 106, 0.8);
+  border:1px solid rgba(8, 55, 106, 1);
+  color:#A8CCE8;
+  .el-select-dropdown__item{
+    color:#A8CCE8;
+    height:62px;
+    line-height:62px;
+    font-size:28px;
+  }
+  .el-select-dropdown__item.hover, .el-select-dropdown__item:hover{
+    background-color: rgba(8, 55, 106, 0.5);
+    color:#fff;
+  }
+  .el-scrollbar{
+    height: 500px;
+    .el-select-dropdown__wrap{
+      max-height:500px;
+    }
+  }
+}
+</style>
 <style lang="scss" scoped>
 .security-monitor {
   display: flex;
@@ -309,7 +653,6 @@ export default {
   display: flex;
   align-items: center;
   gap: 18px;
-  padding: 15px 25px;
   border-radius: 10px;
   background: $bg-card;
   border: 1px solid $border;
@@ -318,9 +661,10 @@ export default {
   .search-icon {
     font-size: 32px;
     color: $text-dim;
+    margin:15px 0 15px 25px;
   }
 
-  input {
+  .video-select {
     flex: 1;
     background: none;
     border: none;
@@ -333,10 +677,25 @@ export default {
       color: $text-dim;
     }
   }
+  ::v-deep .el-input__inner{
+    height:70px;
+    color: #a8cce8;
+    font-size: 30px;
+    background: rgba(7, 22, 54, 0);
+    border: none;
+    padding-left:0;
+  }
+  ::v-deep .el-input__inner::placeholder{
+    color: rgba(110, 165, 210, 0.55);
+  }
+  ::v-deep .el-input .el-input__clear{
+    font-size:24px;
+    margin-right:12px;
+  }
 }
 
 .filter-select {
-  padding: 12px 25px;
+  // padding: 12px 25px;
   border-radius: 10px;
   width: 100%;
   flex-shrink: 0;
@@ -348,11 +707,28 @@ export default {
   outline: none;
   cursor: pointer;
   appearance: none;
-  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M0 0l6 8 6-8z' fill='rgba(30,144,255,0.6)'/%3E%3C/svg%3E");
+  // background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M0 0l6 8 6-8z' fill='rgba(30,144,255,0.6)'/%3E%3C/svg%3E");
   background-repeat: no-repeat;
   background-position: right 25px center;
+  ::v-deep .el-input__inner{
+    height:70px;
+    color: #a8cce8;
+    font-size: 30px;
+    background: rgba(7, 22, 54, 0);
+    border: none;
+    padding-left: 28px;
+  }
+  ::v-deep .el-input__icon{
+    margin-right:13px;
+    color: #a8cce8;
+  }
+  .el-input__inner::placeholder{
+    color: rgba(110, 165, 210, 0.55);
+  }
+}
+::v-deep .el-select .el-input .el-select__caret{
+  font-size:24px;
 }
-
 // ========== Tree ==========
 .tree-wrap {
   flex: 1;
@@ -368,8 +744,32 @@ export default {
     background: $border;
     border-radius: 5px;
   }
+  .el-tree{
+    background: rgba(7, 22, 54, 0);
+    color:#A8CCE8;
+  }
+  ::v-deep .el-tree-node__content{
+    height:62px;
+    font-size:28px;
+  }
+  ::v-deep .el-tree-node__expand-icon{
+    font-size:26px;
+    color:#3E6185;
+  }
+  ::v-deep .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{
+    background-color: rgba(8, 55, 106, 0.5);
+  }
+  ::v-deep .el-tree-node__content:hover{
+    background-color: rgba(8, 55, 106, 0.5);
+  }
+  ::v-deep .tree-node-label:hover{
+    background-color: rgba(8, 55, 106, 0);
+    color:#A8CCE8;
+  }
+  ::v-deep .el-tree-node:focus>.el-tree-node__content{
+    background-color: rgba(8, 55, 106, 0.5);
+  }
 }
-
 ::v-deep .tree-node-label {
   display: flex;
   align-items: center;
@@ -591,4 +991,6 @@ export default {
     box-shadow: 0 0 45px rgba(255, 59, 59, 0.7);
   }
 }
+
+
 </style>

+ 2 - 2
src/components/fullH5PlayerVideo/fullH5PlayerVideo.vue

@@ -6,7 +6,7 @@
 </template>
 
 <script>
-import { iotCameraGetPreviewURLs } from '@/api/basicsModules/index'
+import { iotCameraGetPreviewURLs } from '@/api/screen'
   export default {
     name: 'fullH5PlayerVideo',
     props: {
@@ -200,7 +200,7 @@ import { iotCameraGetPreviewURLs } from '@/api/basicsModules/index'
     }
     /*position: relative;*/
     .full-failed-button {
-      background: url("../../assets/ZDimages/basicsModules/icon_1.png");
+      background: url("../../assets/icon_1.png");
       background-size: 100%;
       position: fixed;
       font-size: 20px;

+ 45 - 12
src/views/Login.vue

@@ -36,7 +36,8 @@
               prefix-icon="el-icon-key"
             />
             <div class="captcha-img" @click="refreshCaptcha">
-              {{ captchaText }}
+              <img v-if="captchaImage" :src="captchaImage" />
+              <span v-else>{{ captchaLoading ? '加载中…' : '点击刷新' }}</span>
             </div>
           </div>
         </el-form-item>
@@ -57,7 +58,7 @@
 
 <script>
 import md5 from 'js-md5'
-import { loginApi } from '@/api/auth'
+import { loginApi, getCaptcha } from '@/api/auth'
 
 export default {
   name: 'Login',
@@ -80,13 +81,36 @@ export default {
         ]
       },
       loading: false,
-      captchaText: '0000',
-      captchaUuid: 'captcha-uuid-default'
+      needCaptcha: true,
+      captchaLoading: false,
+      captchaImage: '',
+      captchaUuid: ''
     }
   },
+  created() {
+    this.loadCaptcha()
+  },
   methods: {
+    async loadCaptcha() {
+      this.captchaLoading = true
+      this.captchaImage = ''
+      try {
+        const res = await getCaptcha()
+        if (res.code === 200) {
+          this.needCaptcha = res.data.captcha
+          if (res.data.captcha) {
+            this.captchaImage = 'data:image/jpeg;base64,' + res.data.image
+            this.captchaUuid  = res.data.uuid
+          }
+        }
+      } catch (e) {
+        // 失败保持"点击刷新"状态
+      } finally {
+        this.captchaLoading = false
+      }
+    },
     refreshCaptcha() {
-      this.captchaText = '0000'
+      this.loadCaptcha()
     },
     handleLogin() {
       this.$refs.loginForm.validate(async (valid) => {
@@ -95,10 +119,10 @@ export default {
         this.loading = true
         try {
           const res = await loginApi({
-            account: this.loginForm.account,
+            account:  this.loginForm.account,
             password: md5(this.loginForm.password),
-            code: this.loginForm.captcha,
-            uuid: this.captchaUuid
+            code:     this.needCaptcha ? this.loginForm.captcha : undefined,
+            uuid:     this.needCaptcha ? this.captchaUuid        : undefined
           })
 
           if (res.code === 200) {
@@ -107,9 +131,10 @@ export default {
             this.$router.push('/screen')
           } else {
             this.$message.error(res.message || '登录失败')
+            if (this.needCaptcha) this.loadCaptcha()
           }
         } catch (err) {
-          // 错误已由拦截器处理
+          if (this.needCaptcha) this.loadCaptcha()
         } finally {
           this.loading = false
         }
@@ -202,17 +227,25 @@ export default {
   .captcha-img {
     width: 120px;
     height: 44px;
+    flex-shrink: 0;
     background: rgba(6, 40, 90, 0.8);
     border: 1px solid rgba(46, 120, 210, 0.3);
     border-radius: 4px;
     display: flex;
     align-items: center;
     justify-content: center;
-    font-size: 20px;
-    color: $cyan;
-    letter-spacing: 8px;
     cursor: pointer;
     user-select: none;
+    overflow: hidden;
+    color: $cyan;
+    font-size: 13px;
+
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: contain;
+      display: block;
+    }
   }
 }