|
|
@@ -1,7 +1,9 @@
|
|
|
<template>
|
|
|
<div class="panel-box video-grid">
|
|
|
- <div class="corner-deco tl"></div><div class="corner-deco tr"></div>
|
|
|
- <div class="corner-deco bl"></div><div class="corner-deco br"></div>
|
|
|
+ <div class="corner-deco tl"></div>
|
|
|
+ <div class="corner-deco tr"></div>
|
|
|
+ <div class="corner-deco bl"></div>
|
|
|
+ <div class="corner-deco br"></div>
|
|
|
<div class="panel-title">
|
|
|
实时视频监控
|
|
|
<span class="video-count">共 {{ videoTotal }} 路</span>
|
|
|
@@ -48,373 +50,451 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { getVideoList } from '@/api'
|
|
|
-import H5PlayerVideo from '@/components/H5PlayerVideo/H5PlayerVideo.vue'
|
|
|
-import fullH5PlayerVideo from '@/components/fullH5PlayerVideo/fullH5PlayerVideo.vue'
|
|
|
-
|
|
|
-export default {
|
|
|
- name: 'VideoGrid',
|
|
|
- components: { H5PlayerVideo,fullH5PlayerVideo },
|
|
|
- data() {
|
|
|
- return {
|
|
|
- videoQueryParams:{
|
|
|
- page:1,
|
|
|
- pageSize:4,
|
|
|
- streamType:1,
|
|
|
- source:0,
|
|
|
- },
|
|
|
- videoType:false,
|
|
|
- videoList:[],
|
|
|
- videoTotal:0,
|
|
|
- //全屏视频参数
|
|
|
- fullVideoProps:{},
|
|
|
- fullVideoType:false,
|
|
|
- }
|
|
|
- },
|
|
|
- computed: {
|
|
|
- totalPages() {
|
|
|
- return Math.ceil(this.videoTotal / 9) || 1
|
|
|
- }
|
|
|
- },
|
|
|
- mounted() {
|
|
|
-
|
|
|
- },
|
|
|
- methods: {
|
|
|
- //刷新视屏
|
|
|
- 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 getVideoList(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: 510, //(宽度:非必传-默认600)
|
|
|
- height: 275, //(高度:非必传-默认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)
|
|
|
+ import {getVideoList} from '@/api'
|
|
|
+ import H5PlayerVideo from '@/components/H5PlayerVideo/H5PlayerVideo.vue'
|
|
|
+ import fullH5PlayerVideo from '@/components/fullH5PlayerVideo/fullH5PlayerVideo.vue'
|
|
|
+
|
|
|
+ export default {
|
|
|
+ name: 'VideoGrid',
|
|
|
+ components: {H5PlayerVideo, fullH5PlayerVideo},
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ videoQueryParams: {
|
|
|
+ page: 1,
|
|
|
+ pageSize: 9,
|
|
|
+ streamType: 1,
|
|
|
+ source: 0,
|
|
|
+ },
|
|
|
+ videoType: false,
|
|
|
+ videoList: [],
|
|
|
+ videoTotal: 0,
|
|
|
+ //全屏视频参数
|
|
|
+ fullVideoProps: {},
|
|
|
+ fullVideoType: false,
|
|
|
}
|
|
|
},
|
|
|
- changePage(delta) {
|
|
|
- this.videoQueryParams.page += delta
|
|
|
- this.videoInitialize()
|
|
|
- },
|
|
|
- //全屏开启-关闭轮播
|
|
|
- stopTime(cameraIndexCode){
|
|
|
- this.$set(this,'fullVideoProps',{cameraIndexCode:cameraIndexCode});
|
|
|
- this.$set(this,'fullVideoType',true);
|
|
|
+ computed: {
|
|
|
+ totalPages() {
|
|
|
+ return Math.ceil(this.videoTotal / 9) || 1
|
|
|
+ }
|
|
|
},
|
|
|
- //全屏关闭-开启轮播
|
|
|
- outFullScreen(){
|
|
|
- let self = this;
|
|
|
- this.$set(this,'fullVideoType',false);
|
|
|
- this.$set(this,'fullVideoProps',{});
|
|
|
+ mounted() {
|
|
|
+
|
|
|
},
|
|
|
+ methods: {
|
|
|
+ //刷新视屏
|
|
|
+ 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 getVideoList(this.videoQueryParams)
|
|
|
+
|
|
|
+ // 假数据开始
|
|
|
+ let res = {
|
|
|
+ data: {
|
|
|
+ total: 100,
|
|
|
+ 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'},
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ };
|
|
|
+ // 假数据结束
|
|
|
+
|
|
|
+ let list = [];
|
|
|
+ for (let i = 0; i < res.data.records.length; i++) {
|
|
|
+ list.push(
|
|
|
+ {
|
|
|
+ width: 510, //(宽度:非必传-默认600)
|
|
|
+ height: 275, //(高度:非必传-默认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', {});
|
|
|
+ },
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
-.video-grid {
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
-
|
|
|
- .panel-content {
|
|
|
- flex: 1;
|
|
|
+ .video-grid {
|
|
|
+ height: 100%;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
+
|
|
|
+ .panel-content {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-.video-count {
|
|
|
- font-size: 12px;
|
|
|
- color: $text-muted;
|
|
|
- font-weight: normal;
|
|
|
- margin-left: auto;
|
|
|
-}
|
|
|
-
|
|
|
-.grid-container {
|
|
|
- flex: 1;
|
|
|
- display: grid;
|
|
|
- grid-template-columns: repeat(3, 1fr);
|
|
|
- grid-template-rows: repeat(3, 1fr);
|
|
|
- gap: 10px;
|
|
|
-}
|
|
|
-
|
|
|
-/* ---- Camera Frame ---- */
|
|
|
-.cam-frame {
|
|
|
- position: relative;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- animation: camEntrance 0.5s ease-out both;
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes camEntrance {
|
|
|
- from { opacity: 0; transform: scale(0.92); }
|
|
|
- to { opacity: 1; transform: scale(1); }
|
|
|
-}
|
|
|
-
|
|
|
-.cam-inner {
|
|
|
- flex: 1;
|
|
|
- position: relative;
|
|
|
- background: rgba(6, 22, 56, 0.6);
|
|
|
- clip-path: polygon(
|
|
|
- 0 8px, 8px 0, calc(100% - 8px) 0, 100% 8px,
|
|
|
- 100% calc(100% - 8px), calc(100% - 8px) 100%, 8px 100%, 0 calc(100% - 8px)
|
|
|
- );
|
|
|
- overflow: hidden;
|
|
|
- border: 1px solid rgba(72, 215, 255, 0.15);
|
|
|
- transition: all 0.3s ease;
|
|
|
-
|
|
|
- &::before {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- inset: 0;
|
|
|
- border: 1px solid rgba(72, 215, 255, 0.12);
|
|
|
- clip-path: inherit;
|
|
|
- pointer-events: none;
|
|
|
- z-index: 2;
|
|
|
+
|
|
|
+ .video-count {
|
|
|
+ font-size: 12px;
|
|
|
+ color: $text-muted;
|
|
|
+ font-weight: normal;
|
|
|
+ margin-left: auto;
|
|
|
}
|
|
|
|
|
|
- &::after {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- inset: -2px;
|
|
|
- background: conic-gradient(from 0deg, transparent 0%, rgba(72,215,255,0.4) 10%, transparent 20%);
|
|
|
- animation: camFrameBorderFlow 6s linear infinite;
|
|
|
- clip-path: inherit;
|
|
|
- z-index: 1;
|
|
|
- opacity: 0;
|
|
|
- transition: opacity 0.3s ease;
|
|
|
+ .grid-container {
|
|
|
+ flex: 1;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ grid-template-rows: repeat(3, 1fr);
|
|
|
+ gap: 10px;
|
|
|
}
|
|
|
|
|
|
- .cam-frame:hover &::after {
|
|
|
- opacity: 1;
|
|
|
+ /* ---- Camera Frame ---- */
|
|
|
+ .cam-frame {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ animation: camEntrance 0.5s ease-out both;
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes camFrameBorderFlow {
|
|
|
- from { transform: rotate(0deg); }
|
|
|
- to { transform: rotate(360deg); }
|
|
|
-}
|
|
|
-
|
|
|
-/* Camera corner accents */
|
|
|
-.cam-corner {
|
|
|
- position: absolute;
|
|
|
- width: 12px;
|
|
|
- height: 12px;
|
|
|
- z-index: 3;
|
|
|
-
|
|
|
- &::before, &::after {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- background: $accent;
|
|
|
- box-shadow: 0 0 4px $accent;
|
|
|
+
|
|
|
+ @keyframes camEntrance {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: scale(0.92);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- &.tl { top: 0; left: 0;
|
|
|
- &::before { top: 0; left: 0; width: 12px; height: 1px; }
|
|
|
- &::after { top: 0; left: 0; width: 1px; height: 12px; }
|
|
|
+ .cam-inner {
|
|
|
+ flex: 1;
|
|
|
+ position: relative;
|
|
|
+ background: rgba(6, 22, 56, 0.6);
|
|
|
+ clip-path: polygon(
|
|
|
+ 0 8px, 8px 0, calc(100% - 8px) 0, 100% 8px,
|
|
|
+ 100% calc(100% - 8px), calc(100% - 8px) 100%, 8px 100%, 0 calc(100% - 8px)
|
|
|
+ );
|
|
|
+ overflow: hidden;
|
|
|
+ border: 1px solid rgba(72, 215, 255, 0.15);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ inset: 0;
|
|
|
+ border: 1px solid rgba(72, 215, 255, 0.12);
|
|
|
+ clip-path: inherit;
|
|
|
+ pointer-events: none;
|
|
|
+ z-index: 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ inset: -2px;
|
|
|
+ background: conic-gradient(from 0deg, transparent 0%, rgba(72, 215, 255, 0.4) 10%, transparent 20%);
|
|
|
+ animation: camFrameBorderFlow 6s linear infinite;
|
|
|
+ clip-path: inherit;
|
|
|
+ z-index: 1;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .cam-frame:hover &::after {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
}
|
|
|
- &.tr { top: 0; right: 0;
|
|
|
- &::before { top: 0; right: 0; width: 12px; height: 1px; }
|
|
|
- &::after { top: 0; right: 0; width: 1px; height: 12px; }
|
|
|
+
|
|
|
+ @keyframes camFrameBorderFlow {
|
|
|
+ from {
|
|
|
+ transform: rotate(0deg);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ transform: rotate(360deg);
|
|
|
+ }
|
|
|
}
|
|
|
- &.bl { bottom: 0; left: 0;
|
|
|
- &::before { bottom: 0; left: 0; width: 12px; height: 1px; }
|
|
|
- &::after { bottom: 0; left: 0; width: 1px; height: 12px; }
|
|
|
+
|
|
|
+ /* Camera corner accents */
|
|
|
+ .cam-corner {
|
|
|
+ position: absolute;
|
|
|
+ width: 12px;
|
|
|
+ height: 12px;
|
|
|
+ z-index: 3;
|
|
|
+
|
|
|
+ &::before, &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ background: $accent;
|
|
|
+ box-shadow: 0 0 4px $accent;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.tl {
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ &::before {
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 12px;
|
|
|
+ height: 1px;
|
|
|
+ }
|
|
|
+ &::after {
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 1px;
|
|
|
+ height: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &.tr {
|
|
|
+ top: 0;
|
|
|
+ right: 0;
|
|
|
+ &::before {
|
|
|
+ top: 0;
|
|
|
+ right: 0;
|
|
|
+ width: 12px;
|
|
|
+ height: 1px;
|
|
|
+ }
|
|
|
+ &::after {
|
|
|
+ top: 0;
|
|
|
+ right: 0;
|
|
|
+ width: 1px;
|
|
|
+ height: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &.bl {
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ &::before {
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 12px;
|
|
|
+ height: 1px;
|
|
|
+ }
|
|
|
+ &::after {
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 1px;
|
|
|
+ height: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &.br {
|
|
|
+ bottom: 0;
|
|
|
+ right: 0;
|
|
|
+ &::before {
|
|
|
+ bottom: 0;
|
|
|
+ right: 0;
|
|
|
+ width: 12px;
|
|
|
+ height: 1px;
|
|
|
+ }
|
|
|
+ &::after {
|
|
|
+ bottom: 0;
|
|
|
+ right: 0;
|
|
|
+ width: 1px;
|
|
|
+ height: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- &.br { bottom: 0; right: 0;
|
|
|
- &::before { bottom: 0; right: 0; width: 12px; height: 1px; }
|
|
|
- &::after { bottom: 0; right: 0; width: 1px; height: 12px; }
|
|
|
+
|
|
|
+ /* AI camera variant - orange accents */
|
|
|
+ .ai-cam {
|
|
|
+ .cam-corner::before, .cam-corner::after {
|
|
|
+ background: $accent-orange;
|
|
|
+ box-shadow: 0 0 4px $accent-orange;
|
|
|
+ }
|
|
|
+
|
|
|
+ .cam-inner {
|
|
|
+ border-color: rgba(255, 176, 32, 0.2);
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ background: conic-gradient(from 0deg, transparent 0%, rgba(255, 176, 32, 0.4) 10%, transparent 20%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .cam-scan-line {
|
|
|
+ background: linear-gradient(180deg, transparent, rgba(255, 176, 32, 0.15), transparent);
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-/* AI camera variant - orange accents */
|
|
|
-.ai-cam {
|
|
|
- .cam-corner::before, .cam-corner::after {
|
|
|
- background: $accent-orange;
|
|
|
- box-shadow: 0 0 4px $accent-orange;
|
|
|
+ .cam-video-area {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ position: relative;
|
|
|
}
|
|
|
|
|
|
- .cam-inner {
|
|
|
- border-color: rgba(255, 176, 32, 0.2);
|
|
|
+ .cam-icon {
|
|
|
+ font-size: 30px;
|
|
|
+ color: rgba(72, 215, 255, 0.12);
|
|
|
+ z-index: 1;
|
|
|
|
|
|
- &::after {
|
|
|
- background: conic-gradient(from 0deg, transparent 0%, rgba(255,176,32,0.4) 10%, transparent 20%);
|
|
|
+ .ai-cam & {
|
|
|
+ color: rgba(255, 176, 32, 0.12);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.cam-scan-line {
|
|
|
- background: linear-gradient(180deg, transparent, rgba(255,176,32,0.15), transparent);
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 40%;
|
|
|
+ background: linear-gradient(180deg, transparent, rgba(72, 215, 255, 0.08), transparent);
|
|
|
+ animation: gridScan 4s ease-in-out infinite;
|
|
|
+ pointer-events: none;
|
|
|
+ z-index: 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes gridScan {
|
|
|
+ 0% {
|
|
|
+ top: -40%;
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ top: 100%;
|
|
|
+ }
|
|
|
+ 50.01% {
|
|
|
+ top: -40%;
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ top: -40%;
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-.cam-video-area {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- position: relative;
|
|
|
-}
|
|
|
-
|
|
|
-.cam-icon {
|
|
|
- font-size: 30px;
|
|
|
- color: rgba(72, 215, 255, 0.12);
|
|
|
- z-index: 1;
|
|
|
-
|
|
|
- .ai-cam & {
|
|
|
- color: rgba(255, 176, 32, 0.12);
|
|
|
+
|
|
|
+ .cam-label {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ padding: 5px 8px;
|
|
|
+ font-size: 12px;
|
|
|
+ background: rgba(6, 22, 56, 0.8);
|
|
|
+ border-top: 1px solid rgba(72, 215, 255, 0.08);
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-.cam-scan-line {
|
|
|
- position: absolute;
|
|
|
- left: 0;
|
|
|
- width: 100%;
|
|
|
- height: 40%;
|
|
|
- background: linear-gradient(180deg, transparent, rgba(72,215,255,0.08), transparent);
|
|
|
- animation: gridScan 4s ease-in-out infinite;
|
|
|
- pointer-events: none;
|
|
|
- z-index: 2;
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes gridScan {
|
|
|
- 0% { top: -40%; }
|
|
|
- 50% { top: 100%; }
|
|
|
- 50.01% { top: -40%; }
|
|
|
- 100% { top: -40%; }
|
|
|
-}
|
|
|
-
|
|
|
-.cam-label {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 6px;
|
|
|
- padding: 5px 8px;
|
|
|
- font-size: 12px;
|
|
|
- background: rgba(6, 22, 56, 0.8);
|
|
|
- border-top: 1px solid rgba(72, 215, 255, 0.08);
|
|
|
- flex-shrink: 0;
|
|
|
-}
|
|
|
-
|
|
|
-.cam-name {
|
|
|
- color: $text-secondary;
|
|
|
- flex: 1;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- white-space: nowrap;
|
|
|
-}
|
|
|
-
|
|
|
-.cam-ai-badge {
|
|
|
- font-size: 10px;
|
|
|
- padding: 1px 5px;
|
|
|
- border-radius: 3px;
|
|
|
- color: $accent-orange;
|
|
|
- background: rgba(255, 176, 32, 0.15);
|
|
|
- border: 1px solid rgba(255, 176, 32, 0.3);
|
|
|
- font-weight: 700;
|
|
|
-}
|
|
|
-
|
|
|
-.cam-status {
|
|
|
- font-size: 10px;
|
|
|
- font-weight: 600;
|
|
|
-
|
|
|
- &.online {
|
|
|
- color: $accent-green;
|
|
|
- animation: recBlink 1.5s ease-in-out infinite;
|
|
|
+
|
|
|
+ .cam-name {
|
|
|
+ color: $text-secondary;
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
- &.offline {
|
|
|
- color: $text-muted;
|
|
|
+ .cam-ai-badge {
|
|
|
+ font-size: 10px;
|
|
|
+ padding: 1px 5px;
|
|
|
+ border-radius: 3px;
|
|
|
+ color: $accent-orange;
|
|
|
+ background: rgba(255, 176, 32, 0.15);
|
|
|
+ border: 1px solid rgba(255, 176, 32, 0.3);
|
|
|
+ font-weight: 700;
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes recBlink {
|
|
|
- 0%, 100% { opacity: 1; }
|
|
|
- 50% { opacity: 0.3; }
|
|
|
-}
|
|
|
-
|
|
|
-/* ---- Pagination ---- */
|
|
|
-.grid-pagination {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 14px;
|
|
|
- padding-top: 10px;
|
|
|
- flex-shrink: 0;
|
|
|
-}
|
|
|
-
|
|
|
-.page-btn {
|
|
|
- width: 30px;
|
|
|
- height: 30px;
|
|
|
- border-radius: 4px;
|
|
|
- background: rgba(72, 215, 255, 0.06);
|
|
|
- border: 1px solid rgba(72, 215, 255, 0.15);
|
|
|
- color: $text-secondary;
|
|
|
- cursor: pointer;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- transition: all 0.3s ease;
|
|
|
-
|
|
|
- &:hover:not(:disabled) {
|
|
|
- border-color: $accent;
|
|
|
- color: $accent;
|
|
|
- box-shadow: 0 0 8px rgba(72, 215, 255, 0.2);
|
|
|
+
|
|
|
+ .cam-status {
|
|
|
+ font-size: 10px;
|
|
|
+ font-weight: 600;
|
|
|
+
|
|
|
+ &.online {
|
|
|
+ color: $accent-green;
|
|
|
+ animation: recBlink 1.5s ease-in-out infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.offline {
|
|
|
+ color: $text-muted;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- &:disabled {
|
|
|
- opacity: 0.3;
|
|
|
- cursor: not-allowed;
|
|
|
+ @keyframes recBlink {
|
|
|
+ 0%, 100% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: 0.3;
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-.page-info {
|
|
|
- font-size: 13px;
|
|
|
- color: $text-secondary;
|
|
|
- font-family: 'Courier New', monospace;
|
|
|
-}
|
|
|
+ /* ---- Pagination ---- */
|
|
|
+ .grid-pagination {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ gap: 14px;
|
|
|
+ padding-top: 10px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-btn {
|
|
|
+ width: 30px;
|
|
|
+ height: 30px;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: rgba(72, 215, 255, 0.06);
|
|
|
+ border: 1px solid rgba(72, 215, 255, 0.15);
|
|
|
+ color: $text-secondary;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:hover:not(:disabled) {
|
|
|
+ border-color: $accent;
|
|
|
+ color: $accent;
|
|
|
+ box-shadow: 0 0 8px rgba(72, 215, 255, 0.2);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:disabled {
|
|
|
+ opacity: 0.3;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-info {
|
|
|
+ font-size: 13px;
|
|
|
+ color: $text-secondary;
|
|
|
+ font-family: 'Courier New', monospace;
|
|
|
+ }
|
|
|
</style>
|