|
|
@@ -1,68 +1,89 @@
|
|
|
<template>
|
|
|
<div class="monitor-page" v-loading="pageLoading">
|
|
|
- <!-- 视频网格区域 -->
|
|
|
- <div
|
|
|
- class="grid-area"
|
|
|
- ref="gridArea"
|
|
|
- @scroll="handleScroll"
|
|
|
- >
|
|
|
- <!-- 视频网格 -->
|
|
|
+ <!-- 左侧区域树 -->
|
|
|
+ <div class="area-panel">
|
|
|
+ <div class="tree-title">区域列表</div>
|
|
|
+ <div class="area-tree-wrap">
|
|
|
+ <el-tree
|
|
|
+ ref="areaTree"
|
|
|
+ :data="areaTreeList"
|
|
|
+ :props="defaultProps"
|
|
|
+ node-key="id"
|
|
|
+ highlight-current
|
|
|
+ default-expand-all
|
|
|
+ :expand-on-click-node="false"
|
|
|
+ @node-click="handleAreaClick"
|
|
|
+ >
|
|
|
+ </el-tree>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧内容区 -->
|
|
|
+ <div class="content-panel">
|
|
|
+ <!-- 视频网格区域 -->
|
|
|
<div
|
|
|
- class="video-grid"
|
|
|
- :style="gridStyle"
|
|
|
+ class="grid-area"
|
|
|
+ ref="gridArea"
|
|
|
+ @scroll="handleScroll"
|
|
|
>
|
|
|
- <VideoCell
|
|
|
- v-for="(item, index) in deviceList"
|
|
|
- :key="item.deviceCode || item.code || index"
|
|
|
- ref="cells"
|
|
|
- :device="item"
|
|
|
- :isActive="selectedIndex === index"
|
|
|
- :url="playingUrls[item.deviceCode || item.code] || ''"
|
|
|
- @click="onCellClick(item, index)"
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <!-- 视频网格 -->
|
|
|
+ <div
|
|
|
+ class="video-grid"
|
|
|
+ :style="gridStyle"
|
|
|
+ >
|
|
|
+ <VideoCell
|
|
|
+ v-for="(item, index) in deviceList"
|
|
|
+ :key="item.deviceCode || item.code || index"
|
|
|
+ ref="cells"
|
|
|
+ :device="item"
|
|
|
+ :isActive="selectedIndex === index"
|
|
|
+ :url="playingUrls[item.deviceCode || item.code] || ''"
|
|
|
+ @click="onCellClick(item, index)"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- 加载提示 -->
|
|
|
- <div class="load-tip" v-if="loadingMore">
|
|
|
- <i class="el-icon-loading"></i> 加载中...
|
|
|
- </div>
|
|
|
- <div class="load-tip" v-else-if="!hasMore && deviceList.length">
|
|
|
- 没有更多设备了
|
|
|
- </div>
|
|
|
- <div class="load-tip" v-else-if="!deviceList.length && !pageLoading">
|
|
|
- 暂无设备
|
|
|
+ <!-- 加载提示 -->
|
|
|
+ <div class="load-tip" v-if="loadingMore">
|
|
|
+ <i class="el-icon-loading"></i> 加载中...
|
|
|
+ </div>
|
|
|
+ <div class="load-tip" v-else-if="!hasMore && deviceList.length">
|
|
|
+ 没有更多设备了
|
|
|
+ </div>
|
|
|
+ <div class="load-tip" v-else-if="!deviceList.length && !pageLoading">
|
|
|
+ 暂无设备
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <!-- 底部工具栏 -->
|
|
|
- <div class="toolbar">
|
|
|
- <div class="toolbar-left">
|
|
|
- <div class="layout-group">
|
|
|
- <div
|
|
|
- v-for="size in layoutOptions"
|
|
|
- :key="size"
|
|
|
- class="layout-btn"
|
|
|
- :class="{ active: gridSize === size }"
|
|
|
- :title="size + '分屏'"
|
|
|
- @click="changeLayout(size)"
|
|
|
- >
|
|
|
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
|
|
- <rect v-for="r in getLayoutRects(size)" :key="r.i" :x="r.x" :y="r.y" :width="r.w" :height="r.h" rx="0.5" fill="currentColor"/>
|
|
|
- </svg>
|
|
|
+ <!-- 底部工具栏 -->
|
|
|
+ <div class="toolbar">
|
|
|
+ <div class="toolbar-left">
|
|
|
+ <div class="layout-group">
|
|
|
+ <div
|
|
|
+ v-for="size in layoutOptions"
|
|
|
+ :key="size"
|
|
|
+ class="layout-btn"
|
|
|
+ :class="{ active: gridSize === size }"
|
|
|
+ :title="size + '分屏'"
|
|
|
+ @click="changeLayout(size)"
|
|
|
+ >
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
|
|
+ <rect v-for="r in getLayoutRects(size)" :key="r.i" :x="r.x" :y="r.y" :width="r.w" :height="r.h" rx="0.5" fill="currentColor"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <div class="toolbar-center">
|
|
|
- <el-button type="text" icon="el-icon-video-play" class="tool-btn" @click="batchPlay">批量播放</el-button>
|
|
|
- <el-button type="text" icon="el-icon-video-pause" class="tool-btn" @click="batchStop">取消拉流</el-button>
|
|
|
- </div>
|
|
|
+ <div class="toolbar-center">
|
|
|
+ <el-button type="text" icon="el-icon-video-play" class="tool-btn" @click="batchPlay">批量播放</el-button>
|
|
|
+ <el-button type="text" icon="el-icon-video-pause" class="tool-btn" @click="batchStop">取消拉流</el-button>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="toolbar-right">
|
|
|
- <el-button type="text" icon="el-icon-rank" class="tool-btn" @click="togglePanel">
|
|
|
- {{ panelVisible ? '隐藏控制台' : '控制台' }}
|
|
|
- </el-button>
|
|
|
- <el-button type="text" icon="el-icon-full-screen" class="tool-btn" @click="toggleFullscreen"></el-button>
|
|
|
+ <div class="toolbar-right">
|
|
|
+ <el-button type="text" icon="el-icon-rank" class="tool-btn" @click="togglePanel">
|
|
|
+ {{ panelVisible ? '隐藏控制台' : '控制台' }}
|
|
|
+ </el-button>
|
|
|
+ <el-button type="text" icon="el-icon-full-screen" class="tool-btn" @click="toggleFullscreen"></el-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -153,6 +174,8 @@ import direction from './components/direction.vue';
|
|
|
import PresetPositions from './components/Preset-positions.vue';
|
|
|
import * as realTime from '@/api/isp/ispRealtime/monitor/index';
|
|
|
import * as deviceApi from '@/api/isp/deviceManage/robot';
|
|
|
+import * as CameraApi from '@/api/ledgerAssets/camera';
|
|
|
+import { basicAreaPageAPI } from '@/api/factoryModel';
|
|
|
|
|
|
export default {
|
|
|
name: 'MonitorPage',
|
|
|
@@ -173,7 +196,14 @@ export default {
|
|
|
slider: 1,
|
|
|
screenshotVal: false,
|
|
|
PresetList: [],
|
|
|
- presetHeight: 200
|
|
|
+ presetHeight: 200,
|
|
|
+ // 区域树
|
|
|
+ currentArea: null,
|
|
|
+ areaTreeList: [],
|
|
|
+ defaultProps: {
|
|
|
+ children: 'children',
|
|
|
+ label: 'name'
|
|
|
+ }
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
|
@@ -203,11 +233,37 @@ export default {
|
|
|
},
|
|
|
mounted() {
|
|
|
this.loadCameraData();
|
|
|
+ this.getAreaTreeList();
|
|
|
},
|
|
|
beforeDestroy() {
|
|
|
this.batchStop();
|
|
|
},
|
|
|
methods: {
|
|
|
+ // ========== 区域树 ==========
|
|
|
+ /* 获取区域树 */
|
|
|
+ getAreaTreeList() {
|
|
|
+ basicAreaPageAPI({
|
|
|
+ pageNum: 1,
|
|
|
+ size: 9999
|
|
|
+ })
|
|
|
+ .then((list) => {
|
|
|
+ this.areaTreeList = this.$util.toTreeData({
|
|
|
+ data: list || [],
|
|
|
+ idField: 'id',
|
|
|
+ parentIdField: 'parentId'
|
|
|
+ });
|
|
|
+ })
|
|
|
+ .catch((e) => {
|
|
|
+ console.error('getAreaTreeList error', e);
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /* 点击区域节点查询 */
|
|
|
+ handleAreaClick(data) {
|
|
|
+ this.currentArea = data;
|
|
|
+ this.loadCameraData(false);
|
|
|
+ },
|
|
|
+
|
|
|
// ========== 布局相关 ==========
|
|
|
changeLayout(size) {
|
|
|
this.gridSize = size;
|
|
|
@@ -248,12 +304,16 @@ export default {
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- const res = await realTime.cameraPage({
|
|
|
+ const params = {
|
|
|
size: this.gridSize,
|
|
|
pageNum: this.dataPageNum
|
|
|
- });
|
|
|
- const list = Array.isArray(res.records) ? res.records : [];
|
|
|
- const total = res.total || 0;
|
|
|
+ };
|
|
|
+ if (this.currentArea && this.currentArea.id) {
|
|
|
+ params.areaId = this.currentArea.id;
|
|
|
+ }
|
|
|
+ const res = await CameraApi.getDevicePage(params);
|
|
|
+ const list = Array.isArray(res.data?.records) ? res.data.records : [];
|
|
|
+ const total = res.data?.total || 0;
|
|
|
this.deviceList = more ? this.deviceList.concat(list) : list;
|
|
|
this.dataPageNum++;
|
|
|
this.hasMore = this.deviceList.length < total;
|
|
|
@@ -455,6 +515,81 @@ export default {
|
|
|
background-color: #0b1a28;
|
|
|
overflow: hidden;
|
|
|
display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+}
|
|
|
+
|
|
|
+// ====== 左侧区域树 ======
|
|
|
+.area-panel {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 220px;
|
|
|
+ height: 100%;
|
|
|
+ background-color: #0d1f30;
|
|
|
+ border-right: 1px solid #1a3047;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-title {
|
|
|
+ flex-shrink: 0;
|
|
|
+ height: 46px;
|
|
|
+ padding: 0 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #e0ecf7;
|
|
|
+ border-bottom: 1px solid #1a3047;
|
|
|
+}
|
|
|
+
|
|
|
+.area-tree-wrap {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 8px 0;
|
|
|
+
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-thumb {
|
|
|
+ background-color: #2c3e5a;
|
|
|
+ border-radius: 3px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-track {
|
|
|
+ background: transparent;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .el-tree {
|
|
|
+ background: transparent;
|
|
|
+ color: rgba(255, 255, 255, 0.75);
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .el-tree-node__content {
|
|
|
+ height: 32px;
|
|
|
+ color: rgba(255, 255, 255, 0.75);
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: rgba(44, 138, 224, 0.1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .el-tree-node.is-current > .el-tree-node__content {
|
|
|
+ background: rgba(64, 200, 255, 0.15);
|
|
|
+ color: #40c8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .el-tree-node__expand-icon {
|
|
|
+ color: rgba(255, 255, 255, 0.5);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ====== 右侧内容区 ======
|
|
|
+.content-panel {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
flex-direction: column;
|
|
|
}
|
|
|
|