|
|
@@ -1,746 +1,712 @@
|
|
|
<template>
|
|
|
- <div class="inspection">
|
|
|
- <div class="left">
|
|
|
- <div class="tree">
|
|
|
- <monitorList
|
|
|
- @getClickId="getClickId"
|
|
|
- @getCameraList="getCameraList"
|
|
|
- ></monitorList>
|
|
|
+ <div class="monitor-page" v-loading="pageLoading">
|
|
|
+ <!-- 视频网格区域 -->
|
|
|
+ <div
|
|
|
+ class="grid-area"
|
|
|
+ ref="gridArea"
|
|
|
+ @scroll="handleScroll"
|
|
|
+ >
|
|
|
+ <!-- 视频网格 -->
|
|
|
+ <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>
|
|
|
</div>
|
|
|
|
|
|
- <div class="center">
|
|
|
- <div class="monitor">
|
|
|
- <myVideo
|
|
|
- :width="960"
|
|
|
- :height="540"
|
|
|
- show-reload-btn
|
|
|
- @setLoading="setLoading"
|
|
|
- ref="myVideoRef"
|
|
|
- />
|
|
|
+ <!-- 底部工具栏 -->
|
|
|
+ <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 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>
|
|
|
</div>
|
|
|
- <div style="height: 100%; padding: 0">
|
|
|
- <div class="deviceInfo">
|
|
|
- <div class="frame-48095487">
|
|
|
- <div class="div">设备信息</div>
|
|
|
- </div>
|
|
|
- <div class="row">
|
|
|
- <div class="div">摄像机名称</div>
|
|
|
- <div class="name">{{ camera.name }}</div>
|
|
|
- </div>
|
|
|
- <div class="row">
|
|
|
- <div class="div">摄像机编号</div>
|
|
|
- <div class="div">{{ camera.deviceCode }}</div>
|
|
|
- </div>
|
|
|
- <div class="row">
|
|
|
- <div class="div">设备品牌</div>
|
|
|
- <div class="div">{{ camera.brandName }}</div>
|
|
|
+
|
|
|
+ <!-- 右侧控制面板(默认隐藏) -->
|
|
|
+ <transition name="slide-panel">
|
|
|
+ <div v-if="panelVisible && selectedDevice" class="control-panel">
|
|
|
+ <div class="panel-header">
|
|
|
+ <span>设备控制</span>
|
|
|
+ <i class="el-icon-close panel-close" @click="panelVisible = false"></i>
|
|
|
</div>
|
|
|
- <div class="row">
|
|
|
- <div class="div">设备型号</div>
|
|
|
- <div class="div">{{ camera.modelName }}</div>
|
|
|
+
|
|
|
+ <!-- 设备信息 -->
|
|
|
+ <div class="panel-section">
|
|
|
+ <div class="section-title">设备信息</div>
|
|
|
+ <div class="info-row">
|
|
|
+ <label>摄像机名称</label>
|
|
|
+ <div>{{ selectedDevice.name }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="info-row">
|
|
|
+ <label>摄像机编号</label>
|
|
|
+ <div>{{ selectedDevice.deviceCode || selectedDevice.code }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="info-row" v-if="selectedDevice.brandName">
|
|
|
+ <label>设备品牌</label>
|
|
|
+ <div>{{ selectedDevice.brandName }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="info-row" v-if="selectedDevice.modelName">
|
|
|
+ <label>设备型号</label>
|
|
|
+ <div>{{ selectedDevice.modelName }}</div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <div class="handle">
|
|
|
- <direction @handle="handle" />
|
|
|
-
|
|
|
- <div class="slider">
|
|
|
- <el-slider
|
|
|
- v-model="slider"
|
|
|
- :min="1"
|
|
|
- :max="7"
|
|
|
- style="width: 154px"
|
|
|
- ></el-slider>
|
|
|
- <div class="sliderNum">{{ slider }}</div>
|
|
|
+ <!-- 云台方向控制 -->
|
|
|
+ <div class="panel-section">
|
|
|
+ <div class="section-title">云台控制</div>
|
|
|
+ <direction @handle="ptzHandle" />
|
|
|
+ <div class="speed-slider">
|
|
|
+ <span>速度</span>
|
|
|
+ <el-slider v-model="slider" :min="1" :max="7" style="flex:1; margin: 0 10px;" />
|
|
|
+ <span class="speed-val">{{ slider }}</span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="moreHandle">
|
|
|
- <div class="row">
|
|
|
- <div class="item normal">
|
|
|
- <el-tooltip effect="dark" content="开关灯" placement="top">
|
|
|
- <img
|
|
|
- :src="
|
|
|
- require('@/assets/svgs/isp/ispRealTime/video/light.svg')
|
|
|
- "
|
|
|
- alt=""
|
|
|
- @click="switchLamp(camera.deviceCode)"
|
|
|
- />
|
|
|
- </el-tooltip>
|
|
|
- </div>
|
|
|
- <div class="item">
|
|
|
- <img
|
|
|
- :src="require('@/assets/svgs/isp/ispRealTime/video/yushua.svg')"
|
|
|
- alt=""
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div class="item">
|
|
|
- <img
|
|
|
- :src="require('@/assets/svgs/isp/ispRealTime/video/jvjiao.svg')"
|
|
|
- alt=""
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div class="item">
|
|
|
- <img
|
|
|
- :src="
|
|
|
- require('@/assets/svgs/isp/ispRealTime/video/chushihua.svg')
|
|
|
- "
|
|
|
- alt=""
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div class="item">
|
|
|
+ <!-- 快捷操作 -->
|
|
|
+ <div class="panel-section">
|
|
|
+ <div class="section-title">快捷操作</div>
|
|
|
+ <div class="quick-actions">
|
|
|
+ <el-tooltip content="开关灯" placement="top">
|
|
|
<img
|
|
|
- :src="require('@/assets/svgs/isp/ispRealTime/video/menu.svg')"
|
|
|
+ :src="require('@/assets/svgs/isp/ispRealTime/video/light.svg')"
|
|
|
alt=""
|
|
|
+ @click="switchLamp(selectedDevice.deviceCode)"
|
|
|
+ class="action-icon"
|
|
|
/>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="line"></div>
|
|
|
-
|
|
|
- <div class="row">
|
|
|
- <div class="item normal" v-loading="screenshotVal">
|
|
|
- <el-tooltip effect="dark" content="截图" placement="top">
|
|
|
- <img
|
|
|
- @click="screenshot(camera.deviceCode)"
|
|
|
- :src="require('@/assets/svgs/isp/ispRealTime/video/cw.svg')"
|
|
|
- alt=""
|
|
|
- />
|
|
|
- </el-tooltip>
|
|
|
- </div>
|
|
|
- <div class="item">
|
|
|
+ </el-tooltip>
|
|
|
+ <el-tooltip content="截图" placement="top">
|
|
|
<img
|
|
|
- :src="require('@/assets/svgs/isp/ispRealTime/video/3d.svg')"
|
|
|
+ v-loading="screenshotVal"
|
|
|
+ :src="require('@/assets/svgs/isp/ispRealTime/video/cw.svg')"
|
|
|
alt=""
|
|
|
+ @click="screenshot(selectedDevice.deviceCode)"
|
|
|
+ class="action-icon"
|
|
|
/>
|
|
|
- </div>
|
|
|
- <div class="item"></div>
|
|
|
- <div class="item"></div>
|
|
|
- <div class="item"></div>
|
|
|
+ </el-tooltip>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <PresetPositions
|
|
|
- :PresetList="PresetList"
|
|
|
- :deviceCode="deviceCode"
|
|
|
- @getPreset="getPreset"
|
|
|
- ></PresetPositions>
|
|
|
+ <!-- 预置点 -->
|
|
|
+ <!-- <div class="panel-section preset-section">
|
|
|
+ <PresetPositions
|
|
|
+ :PresetList="PresetList"
|
|
|
+ :deviceCode="selectedDevice.deviceCode || selectedDevice.code"
|
|
|
+ :boxHeight="presetHeight"
|
|
|
+ @getPreset="getPreset"
|
|
|
+ />
|
|
|
+ </div> -->
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </transition>
|
|
|
+
|
|
|
+ <!-- 遮罩(点击关闭面板,滚轮穿透到网格区域) -->
|
|
|
+ <!-- <div v-if="panelVisible" class="panel-mask" @click="panelVisible = false" @wheel.prevent="onMaskWheel"></div> -->
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
- import PresetPositions from './components/Preset-positions.vue';
|
|
|
- import monitorList from './components/monitorList.vue';
|
|
|
- import direction from './components/direction.vue';
|
|
|
- import myVideo from '../components/video.vue';
|
|
|
- import * as realTime from '@/api/isp/ispRealtime/monitor/index';
|
|
|
- import * as deviceApi from '@/api/isp/deviceManage/robot';
|
|
|
- // import FLVPlayer from "./FLVPlayer.vue";
|
|
|
- export default {
|
|
|
- name: 'MonitorPage',
|
|
|
- components: {
|
|
|
- PresetPositions,
|
|
|
- monitorList,
|
|
|
- direction,
|
|
|
- myVideo
|
|
|
+import VideoCell from './components/video-cell.vue';
|
|
|
+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';
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'MonitorPage',
|
|
|
+ components: { VideoCell, direction, PresetPositions },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ layoutOptions: [9, 16, 25, 36],
|
|
|
+ gridSize: 9,
|
|
|
+ deviceList: [],
|
|
|
+ dataPageNum: 1,
|
|
|
+ hasMore: true,
|
|
|
+ loadingMore: false,
|
|
|
+ pageLoading: false,
|
|
|
+ playingUrls: {},
|
|
|
+ selectedIndex: -1,
|
|
|
+ panelVisible: false,
|
|
|
+ selectedDevice: null,
|
|
|
+ slider: 1,
|
|
|
+ screenshotVal: false,
|
|
|
+ PresetList: [],
|
|
|
+ presetHeight: 200
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ gridCols() {
|
|
|
+ return Math.sqrt(this.gridSize);
|
|
|
},
|
|
|
- data() {
|
|
|
+ gridStyle() {
|
|
|
return {
|
|
|
- layout: 1,
|
|
|
- loading: false,
|
|
|
- videoList: [],
|
|
|
- carouselIndex: 0,
|
|
|
- isShow: false,
|
|
|
- deviceCode: undefined,
|
|
|
- camera: {},
|
|
|
- stopList: [],
|
|
|
- slider: 1,
|
|
|
- screenshotVal: false,
|
|
|
- PresetList: [],
|
|
|
- codeList: [],
|
|
|
- monitorData: []
|
|
|
+ gridTemplateColumns: `repeat(${this.gridCols}, 1fr)`
|
|
|
};
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ gridSize() {
|
|
|
+ this.loadCameraData();
|
|
|
},
|
|
|
- beforeDestroy() {
|
|
|
- if (this.deviceCode) {
|
|
|
- realTime.delStreamProxy(this.deviceCode);
|
|
|
+ panelVisible(val) {
|
|
|
+ if (val) {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ const panel = document.querySelector('.control-panel');
|
|
|
+ if (panel) {
|
|
|
+ this.presetHeight = Math.max(200, panel.clientHeight - 420);
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.loadCameraData();
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ this.batchStop();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // ========== 布局相关 ==========
|
|
|
+ changeLayout(size) {
|
|
|
+ this.gridSize = size;
|
|
|
},
|
|
|
- computed: {
|
|
|
- maxLayout() {
|
|
|
- if (this.videoList.length > 8) {
|
|
|
- return 4;
|
|
|
- } else if (this.videoList.length > 3) {
|
|
|
- return 3;
|
|
|
- } else if (this.videoList.length > 1) {
|
|
|
- return 2;
|
|
|
- } else {
|
|
|
- return 1;
|
|
|
+ getLayoutRects(size) {
|
|
|
+ const n = Math.sqrt(size);
|
|
|
+ const gap = 2;
|
|
|
+ const cell = (24 - gap * (n - 1)) / n;
|
|
|
+ const rects = [];
|
|
|
+ for (let r = 0; r < n; r++) {
|
|
|
+ for (let c = 0; c < n; c++) {
|
|
|
+ rects.push({
|
|
|
+ i: r * n + c,
|
|
|
+ x: c * (cell + gap),
|
|
|
+ y: r * (cell + gap),
|
|
|
+ w: cell,
|
|
|
+ h: cell
|
|
|
+ });
|
|
|
}
|
|
|
- },
|
|
|
- count() {
|
|
|
- return this.videoList.length;
|
|
|
- },
|
|
|
- pageNum() {
|
|
|
- let num = this.count / (this.layout * this.layout);
|
|
|
- return Math.ceil(num);
|
|
|
- },
|
|
|
- itemStyle() {
|
|
|
- return {
|
|
|
- width: `calc((100% - 10px * (${this.layout} - 1)) / ${this.layout})`,
|
|
|
- height: `calc((100% - 10px * (${this.layout} - 1)) / ${this.layout})`
|
|
|
- };
|
|
|
}
|
|
|
+ return rects;
|
|
|
},
|
|
|
- watch: {
|
|
|
- deviceCode(val, oldVal) {
|
|
|
- if (oldVal) {
|
|
|
- realTime.delStreamProxy(oldVal);
|
|
|
- }
|
|
|
- this.getPreset();
|
|
|
+
|
|
|
+ // ========== 数据加载(滚动分页) ==========
|
|
|
+ async loadCameraData(more = false) {
|
|
|
+ if (this.loadingMore) return;
|
|
|
+ if (more && !this.hasMore) return;
|
|
|
+
|
|
|
+ this.loadingMore = true;
|
|
|
+ if (!more) {
|
|
|
+ this.pageLoading = true;
|
|
|
+ this.deviceList = [];
|
|
|
+ this.dataPageNum = 1;
|
|
|
+ this.hasMore = true;
|
|
|
+ this.playingUrls = {};
|
|
|
+ this.selectedIndex = -1;
|
|
|
+ this.selectedDevice = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await realTime.cameraPage({
|
|
|
+ size: this.gridSize,
|
|
|
+ pageNum: this.dataPageNum
|
|
|
+ });
|
|
|
+ const list = Array.isArray(res.records) ? res.records : [];
|
|
|
+ const total = res.total || 0;
|
|
|
+ this.deviceList = more ? this.deviceList.concat(list) : list;
|
|
|
+ this.dataPageNum++;
|
|
|
+ this.hasMore = this.deviceList.length < total;
|
|
|
+ } catch (e) {
|
|
|
+ console.error('loadCameraData error', e);
|
|
|
+ this.$message.error('获取设备列表失败');
|
|
|
+ if (!more) this.deviceList = [];
|
|
|
+ } finally {
|
|
|
+ this.loadingMore = false;
|
|
|
+ this.pageLoading = false;
|
|
|
}
|
|
|
},
|
|
|
- mounted() {},
|
|
|
- methods: {
|
|
|
- setLoading(loading) {
|
|
|
- this.loading = loading;
|
|
|
- },
|
|
|
- itemNum(pageIndex) {
|
|
|
- if (this.pageNum != 1 && pageIndex == this.pageNum - 1) {
|
|
|
- return this.count - this.layout * this.layout * (this.pageNum - 1);
|
|
|
- } else if (this.pageNum == 1) {
|
|
|
- return this.count;
|
|
|
- } else {
|
|
|
- return this.layout * this.layout;
|
|
|
- }
|
|
|
- },
|
|
|
- carouselChange(index) {
|
|
|
- this.carouselIndex = index;
|
|
|
- if (this.layout == 1) this.getClickId(this.monitorData[index]);
|
|
|
- },
|
|
|
- toggle(e) {
|
|
|
- this.isShow = !this.isShow;
|
|
|
- },
|
|
|
- getFocus() {
|
|
|
- const layout = document.getElementById('layout');
|
|
|
- if (layout) layout.focus();
|
|
|
- },
|
|
|
- blur() {
|
|
|
- this.isShow = false;
|
|
|
- },
|
|
|
- choose(index) {
|
|
|
- this.layout = index + 1;
|
|
|
- this.isShow = false;
|
|
|
- },
|
|
|
- home() {
|
|
|
- this.carouselIndex = 0;
|
|
|
- this.$refs.carouselRef && this.$refs.carouselRef.setActiveItem(0);
|
|
|
- },
|
|
|
- last() {
|
|
|
- if (this.carouselIndex > 0) {
|
|
|
- this.carouselIndex--;
|
|
|
- this.$refs.carouselRef &&
|
|
|
- this.$refs.carouselRef.setActiveItem(this.carouselIndex);
|
|
|
- } else {
|
|
|
- this.carouselIndex = this.pageNum - 1;
|
|
|
- this.$refs.carouselRef &&
|
|
|
- this.$refs.carouselRef.setActiveItem(this.pageNum - 1);
|
|
|
- }
|
|
|
- },
|
|
|
- next() {
|
|
|
- if (this.carouselIndex < this.pageNum - 1) {
|
|
|
- this.carouselIndex++;
|
|
|
- this.$refs.carouselRef &&
|
|
|
- this.$refs.carouselRef.setActiveItem(this.carouselIndex);
|
|
|
- } else {
|
|
|
- this.carouselIndex = 0;
|
|
|
- this.$refs.carouselRef && this.$refs.carouselRef.setActiveItem(0);
|
|
|
- }
|
|
|
- },
|
|
|
- end() {
|
|
|
- this.carouselIndex = this.pageNum - 1;
|
|
|
- this.$refs.carouselRef &&
|
|
|
- this.$refs.carouselRef.setActiveItem(this.pageNum - 1);
|
|
|
- },
|
|
|
- handleScreenshot(index, e) {
|
|
|
- const videoPlayback = document.getElementById('video' + index);
|
|
|
- if (!videoPlayback) return;
|
|
|
- const canvas = document.createElement('canvas');
|
|
|
- canvas.width = videoPlayback.videoWidth;
|
|
|
- canvas.height = videoPlayback.videoHeight;
|
|
|
- const ctx = canvas.getContext('2d');
|
|
|
- if (ctx) {
|
|
|
- ctx.drawImage(videoPlayback, 0, 0, canvas.width, canvas.height);
|
|
|
+
|
|
|
+ // ========== 滚动加载更多 ==========
|
|
|
+ handleScroll(e) {
|
|
|
+ const el = e.target;
|
|
|
+ const threshold = 100;
|
|
|
+ if (el.scrollHeight - el.scrollTop - el.clientHeight < threshold) {
|
|
|
+ this.loadCameraData(true);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // ========== 点击视频格 ==========
|
|
|
+ async onCellClick(device, index) {
|
|
|
+ this.selectedIndex = index;
|
|
|
+ this.selectedDevice = device;
|
|
|
+ await this.playDevice(device);
|
|
|
+ if (!this.panelVisible) {
|
|
|
+ this.panelVisible = true;
|
|
|
+ }
|
|
|
+ this.loadPreset(device);
|
|
|
+ },
|
|
|
+ async playDevice(device) {
|
|
|
+ const code = device.deviceCode || device.code;
|
|
|
+ if (!code) return;
|
|
|
+ try {
|
|
|
+ await realTime.delStreamProxy(code).catch(() => {});
|
|
|
+ const urls = await realTime.getCameraUrl([code]);
|
|
|
+ if (urls && urls[0] && urls[0].url) {
|
|
|
+ this.$set(this.playingUrls, code, urls[0].url);
|
|
|
+ console.log(this.playingUrls,'this.playingUrls')
|
|
|
}
|
|
|
- const img = new Image();
|
|
|
- img.src = canvas.toDataURL('image/png');
|
|
|
- const a = document.createElement('a');
|
|
|
- a.href = img.src;
|
|
|
- a.download = 'screenshot.png';
|
|
|
- document.body.appendChild(a);
|
|
|
- a.click();
|
|
|
- document.body.removeChild(a);
|
|
|
- e.stopPropagation();
|
|
|
- },
|
|
|
- async handle(SDK) {
|
|
|
- if (SDK == 'reset') {
|
|
|
- let params = {
|
|
|
- cameraCode: this.deviceCode,
|
|
|
- wPresetNum: 34,
|
|
|
- command: 'GOTO_PRESET'
|
|
|
- };
|
|
|
- const res = await realTime.oprPreset(params);
|
|
|
- if (res == 'SUCCESS') {
|
|
|
- this.$message.success('操作成功');
|
|
|
- } else {
|
|
|
- this.$message.error(res);
|
|
|
- }
|
|
|
- return;
|
|
|
+ } catch (e) {
|
|
|
+ console.error('playDevice error', e);
|
|
|
+ this.$message.error('播放失败');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // ========== 批量操作 ==========
|
|
|
+ async batchPlay() {
|
|
|
+ const list = this.deviceList.filter(Boolean);
|
|
|
+ if (!list.length) {
|
|
|
+ this.$message.warning('没有可播放的设备');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (const item of list) {
|
|
|
+ const code = item.deviceCode || item.code;
|
|
|
+ if (code && !this.playingUrls[code]) {
|
|
|
+ try {
|
|
|
+ await realTime.delStreamProxy(code).catch(() => {});
|
|
|
+ const urls = await realTime.getCameraUrl([code]);
|
|
|
+ if (urls && urls[0] && urls[0].url) {
|
|
|
+ this.$set(this.playingUrls, code, urls[0].url);
|
|
|
+ }
|
|
|
+ } catch (e) {}
|
|
|
}
|
|
|
- let res = await realTime.playControl({
|
|
|
- cameraCode: this.deviceCode,
|
|
|
- mill: this.slider * 1000,
|
|
|
- command: SDK
|
|
|
+ }
|
|
|
+ this.$message.success(`正在批量播放 ${list.length} 个设备`);
|
|
|
+ },
|
|
|
+ async batchStop() {
|
|
|
+ const codes = Object.keys(this.playingUrls);
|
|
|
+ if (!codes.length) {
|
|
|
+ this.$message.warning('没有正在播放的设备');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (const code of codes) {
|
|
|
+ try {
|
|
|
+ await realTime.delStreamProxy(code).catch(() => {});
|
|
|
+ } catch (e) {}
|
|
|
+ }
|
|
|
+ this.playingUrls = {};
|
|
|
+ this.$message.success('已停止所有拉流');
|
|
|
+ if (this.$refs.cells) {
|
|
|
+ this.$refs.cells.forEach(cell => {
|
|
|
+ if (cell && cell.destroyPlayer) cell.destroyPlayer();
|
|
|
});
|
|
|
- if (res == 'SUCCESS') {
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // ========== 全屏 ==========
|
|
|
+ toggleFullscreen() {
|
|
|
+ const el = document.querySelector('.monitor-page') || document.documentElement;
|
|
|
+ if (document.fullscreenElement) {
|
|
|
+ document.exitFullscreen();
|
|
|
+ } else {
|
|
|
+ el.requestFullscreen().catch(() => {});
|
|
|
+ }
|
|
|
+ },
|
|
|
+ togglePanel() {
|
|
|
+ this.panelVisible = !this.panelVisible;
|
|
|
+ },
|
|
|
+ onMaskWheel(e) {
|
|
|
+ const grid = this.$refs.gridArea;
|
|
|
+ if (grid) {
|
|
|
+ grid.scrollTop += e.deltaY;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // ========== 云台控制 ==========
|
|
|
+ async ptzHandle(SDK) {
|
|
|
+ const code = this.selectedDevice?.deviceCode || this.selectedDevice?.code;
|
|
|
+ if (!code) {
|
|
|
+ this.$message.warning('请先选择设备');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (SDK === 'reset') {
|
|
|
+ const params = {
|
|
|
+ cameraCode: code,
|
|
|
+ wPresetNum: 34,
|
|
|
+ command: 'GOTO_PRESET'
|
|
|
+ };
|
|
|
+ const res = await realTime.oprPreset(params);
|
|
|
+ if (res === 'SUCCESS') {
|
|
|
this.$message.success('操作成功');
|
|
|
} else {
|
|
|
- this.$message.error(res);
|
|
|
+ this.$message.error(res || '操作失败');
|
|
|
}
|
|
|
- },
|
|
|
- async switchLamp(code) {
|
|
|
- if (!code) return this.$message.warning('当前设备异常');
|
|
|
- const data = await deviceApi.playControl({
|
|
|
- cameraCode: code,
|
|
|
- mill: 1000,
|
|
|
- command: 'LIGHT_PWRON'
|
|
|
- });
|
|
|
- if (data == 'SUCCESS') {
|
|
|
- this.$message.success('指令下发成功');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const res = await realTime.playControl({
|
|
|
+ cameraCode: code,
|
|
|
+ mill: this.slider * 1000,
|
|
|
+ command: SDK
|
|
|
+ });
|
|
|
+ if (res === 'SUCCESS') {
|
|
|
+ this.$message.success('操作成功');
|
|
|
+ } else {
|
|
|
+ this.$message.error(res || '操作失败');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async switchLamp(code) {
|
|
|
+ if (!code) return this.$message.warning('当前设备异常');
|
|
|
+ const data = await deviceApi.playControl({
|
|
|
+ cameraCode: code,
|
|
|
+ mill: 1000,
|
|
|
+ command: 'LIGHT_PWRON'
|
|
|
+ });
|
|
|
+ if (data === 'SUCCESS') {
|
|
|
+ this.$message.success('指令下发成功');
|
|
|
+ } else {
|
|
|
+ this.$message.error('指令下发失败');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async screenshot(code) {
|
|
|
+ if (!code) return this.$message.warning('当前设备异常');
|
|
|
+ this.screenshotVal = true;
|
|
|
+ try {
|
|
|
+ const data = await deviceApi.catchPic(code);
|
|
|
+ if (data.size < 100) {
|
|
|
+ this.$message.warning('截图失败');
|
|
|
} else {
|
|
|
- this.$message.error('指令下发失败');
|
|
|
- }
|
|
|
- },
|
|
|
- async screenshot(code) {
|
|
|
- if (!code) return this.$message.warning('当前设备异常');
|
|
|
- this.screenshotVal = true;
|
|
|
- try {
|
|
|
- const data = await deviceApi.catchPic(code);
|
|
|
- if (data.size < 100) {
|
|
|
- this.$message.warning('截图失败');
|
|
|
- } else {
|
|
|
- const dlink = document.createElement('a');
|
|
|
- dlink.download = '截图.png';
|
|
|
- dlink.style.display = 'none';
|
|
|
- dlink.href = URL.createObjectURL(data);
|
|
|
- document.body.appendChild(dlink);
|
|
|
- dlink.click();
|
|
|
- URL.revokeObjectURL(dlink.href);
|
|
|
- document.body.removeChild(dlink);
|
|
|
- this.$message.success('截图下载成功');
|
|
|
- }
|
|
|
- } finally {
|
|
|
- this.screenshotVal = false;
|
|
|
+ const dlink = document.createElement('a');
|
|
|
+ dlink.download = '截图.png';
|
|
|
+ dlink.style.display = 'none';
|
|
|
+ dlink.href = URL.createObjectURL(data);
|
|
|
+ document.body.appendChild(dlink);
|
|
|
+ dlink.click();
|
|
|
+ URL.revokeObjectURL(dlink.href);
|
|
|
+ document.body.removeChild(dlink);
|
|
|
+ this.$message.success('截图下载成功');
|
|
|
}
|
|
|
- },
|
|
|
- playStop() {
|
|
|
- this.$refs.myVideoRef.playStop();
|
|
|
- // let index = Math.floor(
|
|
|
- // this.videoList.findIndex(
|
|
|
- // (item) => item.cameraCode == this.deviceCode
|
|
|
- // ) /
|
|
|
- // (this.layout * this.layout)
|
|
|
- // );
|
|
|
- // let videoElement = document.getElementById('video' + index);
|
|
|
- // if (videoElement && videoElement.paused) {
|
|
|
- // videoElement.play();
|
|
|
- // this.stopList = this.stopList.filter((item) => item != index);
|
|
|
- // } else {
|
|
|
- // if (videoElement) videoElement.pause();
|
|
|
- // this.stopList.push(index);
|
|
|
- // }
|
|
|
- },
|
|
|
- play(index) {
|
|
|
- this.$refs.myVideoRef.play();
|
|
|
-
|
|
|
- // let videoElement = document.getElementById('video' + index);
|
|
|
- // if (videoElement) videoElement.play();
|
|
|
- // this.stopList = this.stopList.filter((item) => item != index);
|
|
|
- },
|
|
|
- async getCameraList(list) {
|
|
|
- this.monitorData = list;
|
|
|
- list.forEach((item) => {
|
|
|
- this.codeList.push(item.deviceCode);
|
|
|
- });
|
|
|
-
|
|
|
- // this.getClickId(list[0]);
|
|
|
- },
|
|
|
- async getClickId(device) {
|
|
|
- this.camera = device;
|
|
|
- this.deviceCode = device.deviceCode;
|
|
|
- await this.getCameraUrl();
|
|
|
-
|
|
|
- let index = Math.floor(
|
|
|
- this.videoList.findIndex(
|
|
|
- (item) => item.cameraCode == device.deviceCode
|
|
|
- ) /
|
|
|
- (this.layout * this.layout)
|
|
|
- );
|
|
|
- this.$refs.carouselRef && this.$refs.carouselRef.setActiveItem(index);
|
|
|
- this.carouselIndex = index;
|
|
|
- },
|
|
|
- async getPreset() {
|
|
|
- const res = await realTime.getPreset(this.deviceCode);
|
|
|
- this.PresetList = res;
|
|
|
- },
|
|
|
- async getCameraUrl() {
|
|
|
- await realTime.delStreamProxy(this.deviceCode);
|
|
|
- const res = await realTime.getCameraUrl([this.deviceCode]);
|
|
|
- this.$refs.myVideoRef.startPlay(res[0].url);
|
|
|
- // this.videoList = res;
|
|
|
- console.log(this.videoList);
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('截图失败');
|
|
|
+ } finally {
|
|
|
+ this.screenshotVal = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadPreset(device) {
|
|
|
+ const code = device?.deviceCode || device?.code;
|
|
|
+ if (!code) return;
|
|
|
+ try {
|
|
|
+ this.PresetList = await realTime.getPreset(code);
|
|
|
+ } catch (e) {
|
|
|
+ this.PresetList = [];
|
|
|
}
|
|
|
+ },
|
|
|
+ getPreset() {
|
|
|
+ this.loadPreset(this.selectedDevice);
|
|
|
}
|
|
|
- };
|
|
|
+ }
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
- .inspection {
|
|
|
- display: flex;
|
|
|
- width: 100%;
|
|
|
- .left {
|
|
|
- padding-right: 0;
|
|
|
- height: 100%;
|
|
|
- width: 232px;
|
|
|
- }
|
|
|
- .center {
|
|
|
- width: calc(100%);
|
|
|
- padding-left: 10px;
|
|
|
- padding-right: 10px;
|
|
|
- padding-bottom: 20px;
|
|
|
- }
|
|
|
+.monitor-page {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: calc(100vh - 96px);
|
|
|
+ background-color: #0b1a28;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+// ====== 视频网格区域 ======
|
|
|
+.grid-area {
|
|
|
+ flex: 1;
|
|
|
+ position: relative;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ padding: 12px;
|
|
|
+
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 6px;
|
|
|
}
|
|
|
|
|
|
- .missonList,
|
|
|
- .missonList * {
|
|
|
- box-sizing: border-box;
|
|
|
+ &::-webkit-scrollbar-thumb {
|
|
|
+ background-color: #2c3e5a;
|
|
|
+ border-radius: 3px;
|
|
|
}
|
|
|
- .missonList {
|
|
|
- background: #fff;
|
|
|
- padding: 10px;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 4px;
|
|
|
- align-items: flex-start;
|
|
|
- justify-content: flex-start;
|
|
|
- align-self: stretch;
|
|
|
- flex-shrink: 0;
|
|
|
- position: relative;
|
|
|
- width: 232px;
|
|
|
- .list {
|
|
|
- width: 100%;
|
|
|
- ::v-deep .el-scrollbar__thumb {
|
|
|
- display: none;
|
|
|
- }
|
|
|
- }
|
|
|
- .title {
|
|
|
- background: #f2f4f5;
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- gap: 1px;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- align-self: stretch;
|
|
|
- flex-shrink: 0;
|
|
|
- height: 24px;
|
|
|
- position: relative;
|
|
|
- > div {
|
|
|
- color: #404446;
|
|
|
- text-align: left;
|
|
|
- font-family: 'Alibaba PuHuiTi 2.0', sans-serif;
|
|
|
- font-size: 14px;
|
|
|
- line-height: 16px;
|
|
|
- font-weight: 400;
|
|
|
- position: relative;
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- .row {
|
|
|
- padding: 4px 0px 4px 0px;
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- align-self: stretch;
|
|
|
- flex-shrink: 0;
|
|
|
- position: relative;
|
|
|
- &:hover {
|
|
|
- background: linear-gradient(
|
|
|
- 270deg,
|
|
|
- rgba(44, 138, 224, 0.44) 0%,
|
|
|
- rgba(44, 138, 224, 0.22) 119.59%
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
- .name {
|
|
|
- color: rgb(64, 68, 70);
|
|
|
- text-align: left;
|
|
|
- font-family: var(
|
|
|
- --small-none-regular-font-family,
|
|
|
- 'Alibaba PuHuiTi 2.0',
|
|
|
- sans-serif
|
|
|
- );
|
|
|
- font-size: var(--small-none-regular-font-size, 14px);
|
|
|
- line-height: var(--small-none-regular-line-height, 16px);
|
|
|
- font-weight: var(--small-none-regular-font-weight, 400);
|
|
|
- position: relative;
|
|
|
- }
|
|
|
- .done {
|
|
|
- color: #32a2d4;
|
|
|
- text-align: left;
|
|
|
- font-family: var(
|
|
|
- --small-none-regular-font-family,
|
|
|
- 'Alibaba PuHuiTi 2.0',
|
|
|
- sans-serif
|
|
|
- );
|
|
|
- font-size: var(--small-none-regular-font-size, 14px);
|
|
|
- line-height: var(--small-none-regular-line-height, 16px);
|
|
|
- font-weight: var(--small-none-regular-font-weight, 400);
|
|
|
- position: relative;
|
|
|
- }
|
|
|
- .doing {
|
|
|
- color: #ffb323;
|
|
|
- text-align: left;
|
|
|
- font-family: var(
|
|
|
- --small-none-regular-font-family,
|
|
|
- 'Alibaba PuHuiTi 2.0',
|
|
|
- sans-serif
|
|
|
- );
|
|
|
- font-size: var(--small-none-regular-font-size, 14px);
|
|
|
- line-height: var(--small-none-regular-line-height, 16px);
|
|
|
- font-weight: var(--small-none-regular-font-weight, 400);
|
|
|
- position: relative;
|
|
|
- }
|
|
|
- .todo {
|
|
|
- color: #ff4d4f;
|
|
|
- text-align: left;
|
|
|
- font-family: var(
|
|
|
- --small-none-regular-font-family,
|
|
|
- 'Alibaba PuHuiTi 2.0',
|
|
|
- sans-serif
|
|
|
- );
|
|
|
- font-size: var(--small-none-regular-font-size, 14px);
|
|
|
- line-height: var(--small-none-regular-line-height, 16px);
|
|
|
- font-weight: var(--small-none-regular-font-weight, 400);
|
|
|
- position: relative;
|
|
|
- }
|
|
|
+ &::-webkit-scrollbar-track {
|
|
|
+ background: transparent;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.video-grid {
|
|
|
+ display: grid;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+// ====== 加载提示 ======
|
|
|
+.load-tip {
|
|
|
+ text-align: center;
|
|
|
+ padding: 16px 0;
|
|
|
+ color: rgba(255, 255, 255, 0.4);
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+// ====== 底部工具栏 ======
|
|
|
+.toolbar {
|
|
|
+ flex-shrink: 0;
|
|
|
+ height: 48px;
|
|
|
+ background-color: #0d1f30;
|
|
|
+ border-top: 1px solid #1a3047;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.toolbar-left,
|
|
|
+.toolbar-center,
|
|
|
+.toolbar-right {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.toolbar-left { flex: 1; }
|
|
|
+.toolbar-center { justify-content: center; gap: 16px; }
|
|
|
+.toolbar-right { justify-content: flex-end; }
|
|
|
+
|
|
|
+.layout-group {
|
|
|
+ display: flex;
|
|
|
+ gap: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.layout-btn {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 3px;
|
|
|
+ border: 1px solid transparent;
|
|
|
+ color: rgba(255, 255, 255, 0.35);
|
|
|
+ transition: all 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: rgba(255, 255, 255, 0.65);
|
|
|
+ background: rgba(44, 138, 224, 0.1);
|
|
|
}
|
|
|
- .tree {
|
|
|
- background-color: #fff;
|
|
|
- margin-top: 16px;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ color: #40c8ff;
|
|
|
+ border-color: #40c8ff;
|
|
|
+ background: rgba(64, 200, 255, 0.08);
|
|
|
}
|
|
|
|
|
|
- .el-carousel {
|
|
|
- ::v-deep .el-carousel__container {
|
|
|
- height: calc(100% - 27px);
|
|
|
- }
|
|
|
+ svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
+.tool-btn {
|
|
|
+ color: rgba(255, 255, 255, 0.7) !important;
|
|
|
+ font-size: 14px;
|
|
|
|
|
|
- .deviceInfo,
|
|
|
- .deviceInfo * {
|
|
|
- box-sizing: border-box;
|
|
|
+ &:hover {
|
|
|
+ color: #40c8ff !important;
|
|
|
}
|
|
|
- .deviceInfo {
|
|
|
- background: #fff;
|
|
|
- padding: 10px;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 4px;
|
|
|
- align-items: flex-start;
|
|
|
- justify-content: flex-start;
|
|
|
- align-self: stretch;
|
|
|
- flex-shrink: 0;
|
|
|
- position: relative;
|
|
|
- width: 232px;
|
|
|
- .frame-48095487 {
|
|
|
- background: #f2f4f5;
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- gap: 1px;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- align-self: stretch;
|
|
|
- flex-shrink: 0;
|
|
|
- height: 24px;
|
|
|
- position: relative;
|
|
|
- }
|
|
|
- .div {
|
|
|
- color: rgb(64, 68, 70);
|
|
|
- text-align: left;
|
|
|
- font-family: 'Alibaba PuHuiTi 2.0', sans-serif;
|
|
|
- font-size: 14px;
|
|
|
- line-height: 16px;
|
|
|
- font-weight: 400;
|
|
|
- position: relative;
|
|
|
- }
|
|
|
- .row {
|
|
|
- padding: 4px 0px 4px 0px;
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- align-self: stretch;
|
|
|
- flex-shrink: 0;
|
|
|
- position: relative;
|
|
|
- }
|
|
|
- .name {
|
|
|
- color: #32a2d4;
|
|
|
- text-align: left;
|
|
|
- font-family: 'Alibaba PuHuiTi 2.0', sans-serif;
|
|
|
- font-size: 14px;
|
|
|
- line-height: 16px;
|
|
|
- font-weight: 400;
|
|
|
- position: relative;
|
|
|
- }
|
|
|
+}
|
|
|
+
|
|
|
+// ====== 右侧控制面板 ======
|
|
|
+.control-panel {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ right: 15px;
|
|
|
+ bottom: 48px;
|
|
|
+ width: 280px;
|
|
|
+ max-height: calc(100vh - 120px);
|
|
|
+ background-color: #0d1f30;
|
|
|
+ border-left: 1px solid #1a3047;
|
|
|
+ z-index: 100;
|
|
|
+ overflow-y: auto;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ box-shadow: -4px 0 20px rgba(0, 0, 0, 0.3);
|
|
|
+
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 5px;
|
|
|
}
|
|
|
- .handle {
|
|
|
- display: flex;
|
|
|
- padding: 10px;
|
|
|
- flex-direction: column;
|
|
|
- align-items: flex-start;
|
|
|
- gap: 16px;
|
|
|
- flex: 1 0 0;
|
|
|
- background: #fff;
|
|
|
- margin-top: 10px;
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-thumb {
|
|
|
+ background-color: #2c3e5a;
|
|
|
+ border-radius: 3px;
|
|
|
}
|
|
|
- .slider {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- align-self: stretch;
|
|
|
- padding-left: 10px;
|
|
|
- .sliderNum {
|
|
|
- display: flex;
|
|
|
- height: 17px;
|
|
|
- padding: 10px;
|
|
|
- flex-direction: column;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
- border: 1px solid #cdcfd0;
|
|
|
- width: 29px;
|
|
|
- box-sizing: border-box;
|
|
|
- ::v-deep .el-slider__button {
|
|
|
- background-color: #fff;
|
|
|
- border-color: #ff7b30;
|
|
|
- }
|
|
|
- }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-track {
|
|
|
+ background: transparent;
|
|
|
}
|
|
|
- .moreHandle {
|
|
|
- display: flex;
|
|
|
- width: 200px;
|
|
|
- padding: 5px 0px;
|
|
|
- flex-direction: column;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 4px;
|
|
|
- position: relative;
|
|
|
- border-radius: 4px;
|
|
|
- border: 2px solid #e3e5e6;
|
|
|
- .row {
|
|
|
- display: flex;
|
|
|
- align-items: flex-start;
|
|
|
- align-self: stretch;
|
|
|
- }
|
|
|
- .line {
|
|
|
- width: 180px;
|
|
|
- height: 1px;
|
|
|
- background: #e3e5e6;
|
|
|
- position: absolute;
|
|
|
- top: 50%;
|
|
|
- left: 50%;
|
|
|
- transform: translate(-50%, -50%);
|
|
|
- }
|
|
|
- .item {
|
|
|
- display: flex;
|
|
|
- height: 21px;
|
|
|
- padding: 0px 9px;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- width: 39px;
|
|
|
- cursor: not-allowed;
|
|
|
- border-left: 1px solid #e3e5e6;
|
|
|
- border-right: 1px solid #e3e5e6;
|
|
|
- &:first-child {
|
|
|
- border-left: 0;
|
|
|
- }
|
|
|
- &:last-child {
|
|
|
- border-right: 0;
|
|
|
- }
|
|
|
- }
|
|
|
- .normal {
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
+}
|
|
|
+
|
|
|
+.panel-header {
|
|
|
+ flex-shrink: 0;
|
|
|
+ height: 46px;
|
|
|
+ padding: 0 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ border-bottom: 1px solid #1a3047;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #e0ecf7;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-close {
|
|
|
+ cursor: pointer;
|
|
|
+ color: rgba(255, 255, 255, 0.45);
|
|
|
+ transition: color 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #fff;
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- .center-handle {
|
|
|
- display: flex;
|
|
|
- height: 42px;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- align-self: stretch;
|
|
|
- width: 100%;
|
|
|
- .layout-type {
|
|
|
- display: flex;
|
|
|
- width: 98px;
|
|
|
- align-items: center;
|
|
|
- position: relative;
|
|
|
- > div {
|
|
|
- display: flex;
|
|
|
- }
|
|
|
- }
|
|
|
+.panel-section {
|
|
|
+ padding: 12px 16px;
|
|
|
+ border-bottom: 1px solid #13243a;
|
|
|
+}
|
|
|
|
|
|
- .play {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- width: 100%;
|
|
|
- align-items: flex-start;
|
|
|
- gap: 24px;
|
|
|
- }
|
|
|
+.section-title {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #8ab4d6;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
|
|
|
- .video-handle {
|
|
|
- display: flex;
|
|
|
- align-items: flex-end;
|
|
|
- gap: 16px;
|
|
|
- }
|
|
|
+.info-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 5px 0;
|
|
|
+ font-size: 13px;
|
|
|
+
|
|
|
+ label {
|
|
|
+ color: rgba(255, 255, 255, 0.45);
|
|
|
}
|
|
|
- .monitor {
|
|
|
- height: 100%;
|
|
|
- padding: 10px;
|
|
|
+
|
|
|
+ value {
|
|
|
+ color: #e0ecf7;
|
|
|
+ max-width: 60%;
|
|
|
+ text-align: right;
|
|
|
+ word-break: break-all;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.speed-slider {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 10px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: rgba(255, 255, 255, 0.55);
|
|
|
+
|
|
|
+ ::v-deep .el-slider__runway {
|
|
|
+ background-color: #1a3047;
|
|
|
}
|
|
|
- .layout {
|
|
|
- width: 20px;
|
|
|
- height: 15px;
|
|
|
- padding: 1px;
|
|
|
- display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
- gap: 1px;
|
|
|
- border: 1px #404446 solid;
|
|
|
+
|
|
|
+ ::v-deep .el-slider__button {
|
|
|
+ border-color: #40c8ff;
|
|
|
}
|
|
|
- .group {
|
|
|
- display: flex;
|
|
|
- padding: 4px;
|
|
|
- align-items: flex-start;
|
|
|
- gap: 4px;
|
|
|
- position: absolute;
|
|
|
- top: 20px;
|
|
|
- border-radius: 4px;
|
|
|
- border: 0.5px solid #404446;
|
|
|
- background: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.speed-val {
|
|
|
+ width: 22px;
|
|
|
+ text-align: center;
|
|
|
+ color: #40c8ff;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.quick-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ margin-top: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.action-icon {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ cursor: pointer;
|
|
|
+ opacity: 0.75;
|
|
|
+ transition: opacity 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ opacity: 1;
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+.preset-section {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ border-bottom: none;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-mask {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 280px;
|
|
|
+ bottom: 48px;
|
|
|
+ z-index: 99;
|
|
|
+ background: transparent;
|
|
|
+}
|
|
|
+
|
|
|
+// ====== 动画 ======
|
|
|
+.slide-panel-enter-active,
|
|
|
+.slide-panel-leave-active {
|
|
|
+ transition: transform 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.slide-panel-enter,
|
|
|
+.slide-panel-leave-to {
|
|
|
+ transform: translateX(100%);
|
|
|
+}
|
|
|
</style>
|