目录代码整合
This commit is contained in:
		
							
								
								
									
										566
									
								
								packages/device/IntelligentSecurity/components/AiSlwVideo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										566
									
								
								packages/device/IntelligentSecurity/components/AiSlwVideo.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,566 @@ | ||||
| <template> | ||||
|   <div class="slw" :id="videoId" v-loading="isLoading" element-loading-background="rgba(0, 0, 0, 0.6)"> | ||||
|     <div class="slw-title"> | ||||
|       <h2>{{ name }}</h2> | ||||
|       <div class="slw-title__close" @click="removeMonitor"> | ||||
|         <i class="el-icon-circle-close"></i> | ||||
|         <span>关闭视频</span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <iframe v-if="isShow" :id="iframeId" allow="autoplay *; microphone *; fullscreen *" allowfullscreen allowtransparency key="" allowusermedia frameBorder="no" style="width: 100%; height: 100%;" :src="`https://cdn.cunwuyun.cn/slw2.0/index.html?url=${src}`"> | ||||
|     </iframe> | ||||
|     <div class="slw-bottom" v-if="isShowBar"> | ||||
|       <Timeline class="Timeline" v-if="times.length" :times="times" @replay="onReplay" :isLiveing="isLiveing" :width="width" ref="timeline" :style="{width: width}"></Timeline> | ||||
|       <div class="action-bar"> | ||||
|         <div class="left"> | ||||
|           <div class="left-btns"> | ||||
|             <el-tooltip effect="dark" :content="isPause ? '播放' : '暂停'" placement="top"> | ||||
|               <img :src="isPause ? 'https://cdn.cunwuyun.cn/slw2.0/images/play.png' : 'https://cdn.cunwuyun.cn/slw2.0/images/pause.png'" @click="changePlayStatus"> | ||||
|             </el-tooltip> | ||||
|           </div> | ||||
|           <div class="volume" @mouseleave.stop="isShowVolume = false"> | ||||
|             <img @mouseenter.stop="isShowVolume = true" src="https://cdn.cunwuyun.cn/slw2.0/images/sound.png"> | ||||
|             <div class="volume-slider" :class="[isShowVolume ? 'active' : '']"> | ||||
|               <el-slider input-size="mini" v-model="volume" vertical @change="onVolume" height="80px"> | ||||
|               </el-slider> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="play-status"> | ||||
|             <div class="live"> | ||||
|               <span class="label" v-if="isLiveing"></span> | ||||
|               <i v-if="isLiveing">直播中</i> | ||||
|               <em>{{ date }}</em> | ||||
|             </div> | ||||
|             <div v-if="!isLiveing" class="back-btn" @click="backLiveing">回到直播</div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="right"> | ||||
|           <el-tooltip effect="dark" content="选择日期" placement="top"> | ||||
|             <img src="https://cdn.cunwuyun.cn/slw2.0/images/date.png" @click="isShowDate = true"> | ||||
|           </el-tooltip> | ||||
|           <el-tooltip effect="dark" content="截屏" placement="top"> | ||||
|             <img src="https://cdn.cunwuyun.cn/slw2.0/images/screenshots.png" @click="screenshots"> | ||||
|           </el-tooltip> | ||||
|           <el-tooltip effect="dark" :content="isFullscreen ? '退出全屏' : '全屏'" placement="top"> | ||||
|             <img src="https://cdn.cunwuyun.cn/slw2.0/images/full-screen.png" @click="fullscreen"> | ||||
|           </el-tooltip> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <ai-dialog title="选择日期" :visible.sync="isShowDate" width="520px" @onConfirm="onConfirm"> | ||||
|       <el-form class="ai-form" ref="form" :model="form" label-width="80px" size="small"> | ||||
|         <el-form-item label="选择日期" prop="date" :rules="[{ required: true, message: '请选择日期', trigger: 'change' }]"> | ||||
|           <el-date-picker value-format="yyyy-MM-dd" v-model="form.date" type="date" :picker-options="pickerOptions" placeholder="选择日期"> | ||||
|           </el-date-picker> | ||||
|         </el-form-item> | ||||
|       </el-form> | ||||
|     </ai-dialog> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   import Timeline from './Timeline' | ||||
|  | ||||
|   export default { | ||||
|     props: ['name', 'isShowBar', 'instance', 'id', 'playbackUrls'], | ||||
|  | ||||
|     name: 'slwVideo', | ||||
|  | ||||
|     components: { | ||||
|       Timeline | ||||
|     }, | ||||
|  | ||||
|     data () { | ||||
|       return { | ||||
|         pickerOptions: { | ||||
|           disabledDate(time) { | ||||
|             return time.getTime() > Date.now(); | ||||
|           } | ||||
|         }, | ||||
|         currIndex: 0, | ||||
|         isShowDate: false, | ||||
|         isShowPlayBtn: false, | ||||
|         isShow: true, | ||||
|         isShowVolume: false, | ||||
|         isLiveing: true, | ||||
|         form: { | ||||
|           date: '' | ||||
|         }, | ||||
|         isLoading: false, | ||||
|         times: [], | ||||
|         date: '', | ||||
|         isPause: false, | ||||
|         width: '', | ||||
|         volume: 100, | ||||
|         videoId: `slwvideo-${new Date().getTime()}`, | ||||
|         iframeId: `video-${new Date().getTime()}`, | ||||
|         isFullscreen: false, | ||||
|         replayUrl: '', | ||||
|         liveingUrl: '' | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     computed: { | ||||
|       src () { | ||||
|         if (this.playbackUrls.length) { | ||||
|           const arr =  this.playbackUrls.filter(v => v.id === this.id) | ||||
|           return arr.length ? arr[0].playbackUrl : [] | ||||
|         } | ||||
|  | ||||
|         if (this.isLiveing) { | ||||
|           return this.liveingUrl | ||||
|         } | ||||
|  | ||||
|         return this.replayUrl | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     watch: { | ||||
|       src: { | ||||
|         handler (val) { | ||||
|           if (val) { | ||||
|             this.isShow = false | ||||
|  | ||||
|             this.$nextTick(() => { | ||||
|               this.isShow = true | ||||
|             }) | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     mounted () { | ||||
|       this.date = this.$moment(new Date()).format('YYYY-MM-DD') | ||||
|       this.form.date = this.$moment(new Date()).format('YYYY-MM-DD') | ||||
|       this.$nextTick(() => { | ||||
|         this.width = document.querySelector(`#${this.videoId}`).offsetWidth + 'px' | ||||
|  | ||||
|         document.addEventListener('fullscreenchange', this.fullScreenChange) | ||||
|       }) | ||||
|  | ||||
|       this.getSlwPlaybackTime() | ||||
|  | ||||
|       if (this.id) { | ||||
|         this.getLiveingUrl() | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     methods: { | ||||
|       destroyed () { | ||||
|         document.removeEventListener('fullscreenchange', this.fullScreenChange) | ||||
|       }, | ||||
|  | ||||
|       backLiveing () { | ||||
|         this.form.date = this.$moment(new Date()).format('YYYY-MM-DD') | ||||
|         this.date = this.$moment(new Date()).format('YYYY-MM-DD') | ||||
|         this.getLiveingUrl() | ||||
|         this.getSlwPlaybackTime() | ||||
|       }, | ||||
|  | ||||
|       getLiveingUrl () { | ||||
|         this.isLoading = true | ||||
|         this.instance.post(`/app/appzyvideoequipment/getWebSdkUrl?deviceId=${this.id}`).then(res => { | ||||
|           if (res.data) { | ||||
|             this.liveingUrl = res.data | ||||
|             this.isLiveing = true | ||||
|           } | ||||
|  | ||||
|           this.isLoading = false | ||||
|         }).catch(() => { | ||||
|           this.isLoading = false | ||||
|         }) | ||||
|       }, | ||||
|  | ||||
|       onReplay (e) { | ||||
|         this.isLoading = true | ||||
|         this.instance.post(`/app/appzyvideoequipment/getSlwPlaybackUrl`, null, { | ||||
|           params: { | ||||
|             ids: this.id, | ||||
|             startTime: `${this.form.date} ${e}`, | ||||
|             endTime: this.form.date + ` ${Number(e.substr(0, 2)) + 6 > 9 ? Number(e.substr(0, 2)) + 6 : '0' + (Number(e.substr(0, 2)) + 6)}:00:00`, | ||||
|             nvrCodes: '' | ||||
|           } | ||||
|         }).then(res => { | ||||
|           if (res.code == 0) { | ||||
|             if (res.data && res.data.length) { | ||||
|               this.replayUrl = res.data[0].playbackUrl | ||||
|               this.isLiveing = false | ||||
|             } | ||||
|             this.isLoading = false | ||||
|           } | ||||
|         }).catch(() => { | ||||
|           this.isLoading = false | ||||
|         }) | ||||
|       }, | ||||
|  | ||||
|       getSlwPlaybackTime () { | ||||
|         this.isLoading = true | ||||
|         this.instance.post(`/app/appzyvideoequipment/getSlwPlaybackTime`, null, { | ||||
|           params: { | ||||
|             ids: this.id, | ||||
|             startTime: this.date + ' 00:00:00', | ||||
|             endTime: this.date + ' 23:59:59', | ||||
|           } | ||||
|         }).then(res => { | ||||
|           if (res.code == 0) { | ||||
|             if (res.data && res.data.length) { | ||||
|               const times = res.data[0].times | ||||
|  | ||||
|               this.times = times.map(item => { | ||||
|                 const startTime = (item.startTime - new Date(this.date + ' 00:00:00').getTime()) / 1000 | ||||
|                 const endTime = (item.endTime - new Date(this.date + ' 00:00:00').getTime()) / 1000 | ||||
|  | ||||
|                 return { | ||||
|                   startTime: Number(startTime.toFixed(0)), | ||||
|                   endTime: Number(endTime.toFixed(0)) | ||||
|                 } | ||||
|               }).sort((a, b) => { | ||||
|                 return a.startTime - b.startTime | ||||
|               }) | ||||
|             } | ||||
|  | ||||
|             this.isLoading = false | ||||
|           } | ||||
|         }).catch(() => { | ||||
|           this.isLoading = false | ||||
|         }) | ||||
|       }, | ||||
|  | ||||
|       fullScreenChange () { | ||||
|         if (document.fullscreenElement) { | ||||
|           this.reset() | ||||
|         } else { | ||||
|           this.reset() | ||||
|         } | ||||
|       }, | ||||
|       exitFullscreen () { | ||||
|         if (document.exitFullscreen) { | ||||
|           document.exitFullscreen() | ||||
|         } else if (document.mozCancelFullScreen) { | ||||
|           document.mozCancelFullScreen() | ||||
|         } else if (document.webkitExitFullscreen) { | ||||
|           document.webkitExitFullscreen() | ||||
|         } else if (document.msExitFullscreen) { | ||||
|           window.top.document.msExitFullscreen() | ||||
|         } | ||||
|       }, | ||||
|  | ||||
|       changePlayStatus () { | ||||
|         const subPage = document.querySelector(`#${this.iframeId}`).contentWindow | ||||
|         subPage.postMessage({ | ||||
|           type: 'play', | ||||
|           value: this.isPause | ||||
|         }, '*') | ||||
|          | ||||
|         this.isPause = !this.isPause | ||||
|       }, | ||||
|  | ||||
|       onConfirm () { | ||||
|         this.$refs.form.validate((valid) => { | ||||
|           if (valid) { | ||||
|             this.date = this.form.date | ||||
|             this.isShowDate = false | ||||
|             this.getSlwPlaybackTime() | ||||
|           } | ||||
|         }) | ||||
|       }, | ||||
|  | ||||
|       onVolume (e) { | ||||
|         const v = (e / 100).toFixed(1) | ||||
|         const subPage = document.querySelector(`#${this.iframeId}`).contentWindow | ||||
|         subPage.postMessage({ | ||||
|           type: 'volume', | ||||
|           value: Number(v) | ||||
|         }, '*') | ||||
|       }, | ||||
|  | ||||
|       fullscreen () { | ||||
|         if (this.isFullscreen) { | ||||
|           this.exitFullscreen() | ||||
|         } else { | ||||
|           this.requestFullScreen(document.querySelector(`#${this.videoId}`)) | ||||
|         } | ||||
|  | ||||
|         this.isFullscreen = !this.isFullscreen | ||||
|         this.reset() | ||||
|       }, | ||||
|  | ||||
|       reset () { | ||||
|         setTimeout(() => { | ||||
|           this.width = document.querySelector(`#${this.videoId}`).offsetWidth + 'px' | ||||
|           this.$nextTick(() => { | ||||
|             this.$refs.timeline && this.$refs.timeline.init() | ||||
|           }) | ||||
|         }, 100) | ||||
|       }, | ||||
|  | ||||
|       screenshots () { | ||||
|         const subPage = document.querySelector(`#${this.iframeId}`).contentWindow | ||||
|         subPage.postMessage({ | ||||
|           type: 'screenshot' | ||||
|         }, '*') | ||||
|       }, | ||||
|  | ||||
|       removeMonitor () { | ||||
|         this.$emit('close') | ||||
|       }, | ||||
|  | ||||
|       requestFullScreen (elem) { | ||||
|         if (elem.requestFullscreen) { | ||||
|           elem.requestFullscreen() | ||||
|         } else if (elem.mozRequestFullScreen) { | ||||
|           elem.mozRequestFullScreen() | ||||
|         } else if (elem.webkitRequestFullscreen) { | ||||
|           elem.webkitRequestFullscreen() | ||||
|         } else if (elem.msRequestFullscreen) { | ||||
|           elem = window.top.document.body | ||||
|           elem.msRequestFullscreen() | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   .slw { | ||||
|     position: relative; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     overflow: hidden; | ||||
|  | ||||
|     iframe { | ||||
|       border: none; | ||||
|     } | ||||
|  | ||||
|     .slw-bottom { | ||||
|       position: absolute; | ||||
|       bottom: 0; | ||||
|       left: 0; | ||||
|       z-index: 1; | ||||
|       width: 100%; | ||||
|       transition: all ease-in-out 0.5s; | ||||
|       transform: translateY(100%); | ||||
|     } | ||||
|  | ||||
|     &:hover { | ||||
|       .slw-title { | ||||
|         transform: translateY(0%); | ||||
|       } | ||||
|       .slw-bottom { | ||||
|         transform: translateY(0%); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .slw-title { | ||||
|       display: flex; | ||||
|       position: absolute; | ||||
|       align-items: center; | ||||
|       justify-content: space-between; | ||||
|       top: 0; | ||||
|       left: 0; | ||||
|       z-index: 1; | ||||
|       width: 100%; | ||||
|       height: 40px; | ||||
|       line-height: 40px; | ||||
|       padding: 0 16px; | ||||
|       background: rgba(0, 0, 0, 0.8); | ||||
|       transition: all ease 0.5s; | ||||
|       transform: translateY(-100%); | ||||
|  | ||||
|       h2 { | ||||
|         max-width: 70%; | ||||
|         font-size: 16px; | ||||
|         color: #fff; | ||||
|         font-weight: normal; | ||||
|         white-space: nowrap; | ||||
|         overflow: hidden; | ||||
|         text-overflow: ellipsis;   | ||||
|       } | ||||
|  | ||||
|       .slw-title__close { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|         width: 84px; | ||||
|         height: 32px; | ||||
|         background: linear-gradient(180deg, #2E3447 0%, #151825 100%); | ||||
|         border-radius: 2px; | ||||
|         cursor: pointer; | ||||
|         font-size: 12px; | ||||
|         color: #fff; | ||||
|  | ||||
|         &:hover { | ||||
|           opacity: 0.9; | ||||
|         } | ||||
|  | ||||
|         span { | ||||
|           margin-left: 4px; | ||||
|         } | ||||
|  | ||||
|         i { | ||||
|           position: relative; | ||||
|           font-size: 16px; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .action-bar { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       justify-content: space-between; | ||||
|       width: 100%; | ||||
|       height: 40px; | ||||
|       padding: 0 16px; | ||||
|       background: rgba(0, 0, 0, 0.8); | ||||
|  | ||||
|       .left { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|  | ||||
|         .play-status { | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|           margin-left: 12px; | ||||
|  | ||||
|           em { | ||||
|             margin-left: 12px; | ||||
|             font-style: normal; | ||||
|             color: #fff; | ||||
|             font-size: 12px; | ||||
|           } | ||||
|  | ||||
|           .back-btn { | ||||
|             padding: 4px 10px; | ||||
|             border-radius: 6px; | ||||
|             color: #ddd; | ||||
|             font-size: 12px; | ||||
|             cursor: pointer; | ||||
|             background: #343747; | ||||
|  | ||||
|             &:hover { | ||||
|               opacity: 0.6; | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           .live { | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             line-height: 1; | ||||
|             padding: 2px 5px; | ||||
|             color: rgba(0,255,0,.8); | ||||
|  | ||||
|             i { | ||||
|               font-size: 12px; | ||||
|               font-style: normal; | ||||
|             } | ||||
|  | ||||
|             .label { | ||||
|               width: 6px; | ||||
|               height: 6px; | ||||
|               margin-right: 12px; | ||||
|               background: rgba(0,255,0,.8); | ||||
|               border-radius: 50%; | ||||
|               position: relative; | ||||
|  | ||||
|               &:after { | ||||
|                 content: ""; | ||||
|                 width: 12px; | ||||
|                 height: 12px; | ||||
|                 position: absolute; | ||||
|                 left: 50%; | ||||
|                 top: 50%; | ||||
|                 -webkit-transform: translate(-50%,-50%); | ||||
|                 transform: translate(-50%,-50%); | ||||
|                 border-radius: 50%; | ||||
|                 border: 4px solid rgba(0,255,0,.2); | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         .volume { | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|           position: relative; | ||||
|           height: 100%; | ||||
|  | ||||
|           .volume-slider { | ||||
|             display: none; | ||||
|             position: absolute; | ||||
|             bottom: 15px; | ||||
|             left: 50%; | ||||
|             z-index: -1; | ||||
|             opacity: 0; | ||||
|             padding: 20px 0 10px; | ||||
|             background-color: rgba(0, 0,0,.8); | ||||
|             transform: translate(-50%, 0); | ||||
|             transition: all ease 0.3s; | ||||
|  | ||||
|             &.active { | ||||
|               display: block; | ||||
|               z-index: 1; | ||||
|               opacity: 1; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         img { | ||||
|           cursor: pointer; | ||||
|         } | ||||
|  | ||||
|         .left-btns { | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|           margin-right: 10px; | ||||
|  | ||||
|           span { | ||||
|             flex: 1; | ||||
|             height: 100%; | ||||
|             line-height: 24px; | ||||
|             background: #222838; | ||||
|             color: #c9c9c9; | ||||
|             font-size: 12px; | ||||
|             cursor: pointer; | ||||
|  | ||||
|             &.active { | ||||
|               color: #fff; | ||||
|               background: linear-gradient(180deg, #28B2EB 0%, #193D91 100%); | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       .right { | ||||
|         color: #c9c9c9; | ||||
|         font-size: 12px; | ||||
|  | ||||
|         span { | ||||
|           margin-right: 32px; | ||||
|           cursor: pointer; | ||||
|  | ||||
|           &:hover { | ||||
|             opacity: 0.6; | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         img { | ||||
|           margin-right: 16px; | ||||
|           cursor: pointer; | ||||
|  | ||||
|           &:last-child { | ||||
|             margin-right: 0; | ||||
|           } | ||||
|  | ||||
|           &:hover { | ||||
|             opacity: 0.6; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       & > div { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
							
								
								
									
										129
									
								
								packages/device/IntelligentSecurity/components/PlaybackTime.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								packages/device/IntelligentSecurity/components/PlaybackTime.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| <template> | ||||
|   <div :class="wrapper" class="canvas" v-if="isInit"> | ||||
|     <canvas | ||||
|       :id="id" | ||||
|       :style="{height: '10px'}" | ||||
|       v-if="canvasWidth" | ||||
|       :width="canvasWidth" | ||||
|       height="10"> | ||||
|     </canvas> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   export default { | ||||
|     props: ['times', 'deviceId'], | ||||
|  | ||||
|     data () { | ||||
|       return { | ||||
|         ctx: null, | ||||
|         canvasWidth: '', | ||||
|         canvasHeight: '', | ||||
|         isInit: false, | ||||
|         wrapper: `canvas-${new Date().getTime()}`, | ||||
|         id: `timeline-${new Date().getTime()}-${this.deviceId}`, | ||||
|         timer: null | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     watch: { | ||||
|       times: { | ||||
|         deep: true, | ||||
|         handler (v) { | ||||
|           if (v.length && this.ctx) { | ||||
|             this.init() | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     mounted () { | ||||
|       this.$nextTick(() => { | ||||
|         this.init() | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     destroyed () { | ||||
|       clearInterval(this.timer) | ||||
|     }, | ||||
|  | ||||
|     methods: { | ||||
|       init () { | ||||
|         if (this.ctx) { | ||||
|           this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight) | ||||
|         } | ||||
|         this.isInit = false | ||||
|         this.$nextTick(() => { | ||||
|           this.isInit = true | ||||
|           this.$nextTick(() => { | ||||
|             this.canvasWidth = document.querySelector(`.${this.wrapper}`).offsetWidth | ||||
|             this.canvasHeight = document.querySelector(`.${this.wrapper}`).offsetHeight | ||||
|  | ||||
|             this.$nextTick(() => { | ||||
|               const el = document.querySelector(`#${this.id}`) | ||||
|               this.ctx = el.getContext('2d') | ||||
|               this.ctx.width  = document.querySelector('.canvas').offsetWidth | ||||
|               this.ctx.height  = document.querySelector('.canvas').offsetHeight | ||||
|               this.renderPlayback() | ||||
|             }) | ||||
|           }) | ||||
|         }) | ||||
|       }, | ||||
|  | ||||
|       countdown () { | ||||
|         this.timer = setInterval(() => { | ||||
|           if (this.isLiveing) { | ||||
|             this.initNowTime() | ||||
|           } else { | ||||
|             this.x = this.x + this.canvasWidth / (24 * 60 * 60) | ||||
|           } | ||||
|         }, 1000) | ||||
|       }, | ||||
|  | ||||
|       initNowTime () { | ||||
|         const date = new Date() | ||||
|         const seconds = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() | ||||
|  | ||||
|         this.x = this.canvasWidth / (24 * 60 * 60) * seconds | ||||
|       }, | ||||
|  | ||||
|       drawLine(ctx, options) { | ||||
|         const { beginX, beginY, endX, endY, lineColor, lineWidth } = options | ||||
|         ctx.beginPath() | ||||
|         ctx.lineWidth = lineWidth | ||||
|         ctx.moveTo(beginX, beginY) | ||||
|         ctx.lineTo(endX, endY) | ||||
|         ctx.strokeStyle = lineColor | ||||
|         ctx.stroke() | ||||
|       }, | ||||
|  | ||||
|       renderPlayback () { | ||||
|         const ctx = this.ctx | ||||
|         const unit = this.canvasWidth / (24 * 60 * 60) | ||||
|         this.times.forEach(item => { | ||||
|           this.drawLine(ctx, { | ||||
|             beginX: item.startTime * unit, | ||||
|             beginY: 28, | ||||
|             endX: item.startTime * unit, | ||||
|             endY: 0, | ||||
|             lineColor: 'rgba(0, 156, 255, 1)', | ||||
|             lineWidth: item.endTime * unit - item.startTime * unit | ||||
|           }) | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   .canvas { | ||||
|     position: relative; | ||||
|     width: 100%; | ||||
|     height: 10px; | ||||
|  | ||||
|     canvas { | ||||
|       width: 100%; | ||||
|       height: 10px; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
							
								
								
									
										683
									
								
								packages/device/IntelligentSecurity/components/Synergy.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										683
									
								
								packages/device/IntelligentSecurity/components/Synergy.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,683 @@ | ||||
| <template> | ||||
|   <div class="synergr" :id="videoId" v-if="isInit" @mouseleave="isHide = true" @mousemove.stop="onMousemove" @mouseup="onMouseUp"> | ||||
|     <canvas id="synergr-canvas" :style="{height: '28px'}" v-if="canvasWidth" @click="onClick" :width="canvasWidth" height="28"> | ||||
|     </canvas> | ||||
|     <div class="time" v-show="!isHide && left > 100" :style="{left: (left) + 'px'}">{{ time }}</div> | ||||
|     <img @mousedown="onDragDown" class="drag-img" :style="{left: (x) + 'px'}" src="https://cdn.cunwuyun.cn/slw2.0/images/drag.png" /> | ||||
|     <div class="slw-bottom"> | ||||
|       <div class="action-bar"> | ||||
|         <div class="left"> | ||||
|           <!-- <div | ||||
|             class="volume" | ||||
|             @mouseleave.stop="isShowVolume = false"> | ||||
|             <img  | ||||
|               @mouseenter.stop="isShowVolume = true" | ||||
|               src="https://cdn.cunwuyun.cn/slw2.0/images/sound.png"> | ||||
|             <div class="volume-slider" :class="[isShowVolume ? 'active' : '']"> | ||||
|               <el-slider | ||||
|                 input-size="mini" | ||||
|                 v-model="volume" | ||||
|                 vertical | ||||
|                 @change="onVolume" | ||||
|                 height="80px"> | ||||
|               </el-slider> | ||||
|             </div> | ||||
|           </div> --> | ||||
|           <div class="play-status"> | ||||
|             <div class="live"> | ||||
|               <span class="label" v-if="isLiveing"></span> | ||||
|               <i v-if="isLiveing">直播中</i> | ||||
|               <em>{{ date }}</em> | ||||
|             </div> | ||||
|             <div v-if="!isLiveing" class="back-btn" @click="backLiveing">回到直播</div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="right"> | ||||
|           <el-tooltip effect="dark" content="选择日期" placement="top"> | ||||
|             <img src="https://cdn.cunwuyun.cn/slw2.0/images/date.png" @click="isShowDate = true"> | ||||
|           </el-tooltip> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="playback"> | ||||
|       <div class="synergr-more" @click="isShowTimeline = !isShowTimeline" :class="[isShowTimeline ? 'active' : '']"> | ||||
|         <img :title="isShowTimeline ? '收起' : '展开'" src="https://cdn.cunwuyun.cn/slw2.0/images/arrow.png" /> | ||||
|       </div> | ||||
|       <div class="playback-list" v-if="isShowTimeline"> | ||||
|         <el-checkbox-group v-model="checked" @change="onCheckChange"> | ||||
|           <div class="playback-item" v-for="(item, index) in times" :key="index"> | ||||
|             <el-checkbox :label="item.id"> | ||||
|               <span>通道{{ index + 1 }}</span> | ||||
|             </el-checkbox> | ||||
|             <PlaybackTime class="playback-item__timeline" :key="'PlaybackTime' + index" v-if="item.times.length" :deviceId="item.id" :times="item.times"></PlaybackTime> | ||||
|             <i :style="{left: (x - 17) + 'px'}"></i> | ||||
|           </div> | ||||
|         </el-checkbox-group> | ||||
|       </div> | ||||
|     </div> | ||||
|     <ai-dialog title="选择日期" :visible.sync="isShowDate" width="520px" @onConfirm="onConfirm"> | ||||
|       <el-form class="ai-form" ref="form" :model="form" label-width="80px" size="small"> | ||||
|         <el-form-item label="选择日期" prop="date" :rules="[{ required: true, message: '请选择日期', trigger: 'change' }]"> | ||||
|           <el-date-picker value-format="yyyy-MM-dd" v-model="form.date" type="date" :picker-options="pickerOptions" placeholder="选择日期"> | ||||
|           </el-date-picker> | ||||
|         </el-form-item> | ||||
|       </el-form> | ||||
|     </ai-dialog> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   import PlaybackTime from './PlaybackTime' | ||||
|  | ||||
|   export default { | ||||
|     props: ['ids', 'instance', 'isLoading'], | ||||
|  | ||||
|     name: 'Synergy', | ||||
|  | ||||
|     components: { | ||||
|       PlaybackTime | ||||
|     }, | ||||
|  | ||||
|     data () { | ||||
|       return { | ||||
|         canvasWidth: 0, | ||||
|         currIndex: 0, | ||||
|         checked: [], | ||||
|         isShowDate: false, | ||||
|         isShow: true, | ||||
|         isShowVolume: false, | ||||
|         isLiveing: true, | ||||
|         form: { | ||||
|           date: '' | ||||
|         }, | ||||
|         pickerOptions: { | ||||
|           disabledDate(time) { | ||||
|             return time.getTime() > Date.now(); | ||||
|           } | ||||
|         }, | ||||
|         times: [], | ||||
|         isShowTimeline: true, | ||||
|         checkList: [], | ||||
|         isInit: false, | ||||
|         left: 0, | ||||
|         date: '', | ||||
|         scale: 3, | ||||
|         width: '', | ||||
|         isHide: false, | ||||
|         x: 0, | ||||
|         time: '', | ||||
|         videoId: `synergr-${new Date().getTime()}`, | ||||
|         isFullscreen: false, | ||||
|         timer: null | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     watch: { | ||||
|       ids: { | ||||
|         handler (val) { | ||||
|           if (val) { | ||||
|             this.checked = this.ids.split(',') | ||||
|             this.getSlwPlaybackTime() | ||||
|           } | ||||
|         }, | ||||
|         immediate: false, | ||||
|         deep: true | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     mounted () { | ||||
|       this.form.date = this.$moment(new Date()).format('YYYY-MM-DD') | ||||
|       this.date = this.$moment(new Date()).format('YYYY-MM-DD') | ||||
|  | ||||
|       this.getSlwPlaybackTime() | ||||
|       this.checked = this.ids.split(',') | ||||
|       this.$nextTick(() => { | ||||
|         this.init() | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     methods: { | ||||
|       onMousemove (e) { | ||||
|         const canvasInfo = document.querySelector(`#synergr-canvas`).getBoundingClientRect() | ||||
|         const seconds = 24 * 60 * 60 | ||||
|         const x = e.clientX - canvasInfo.left + 100 | ||||
|  | ||||
|         if (x < 100 || x > this.canvasWidth + 100) { | ||||
|           this.isHide = true | ||||
|  | ||||
|           return false | ||||
|         } | ||||
|  | ||||
|         const unit = seconds / this.canvasWidth * (x - 100) | ||||
|         this.left = x | ||||
|         this.time = this.secTotime(unit) | ||||
|         this.isHide = false | ||||
|  | ||||
|         if (!this.isChoose) return | ||||
|         this.x = e.clientX - canvasInfo.left + 100 | ||||
|         this.ratioW = this.x / this.canvasWidth | ||||
|       }, | ||||
|  | ||||
|       onDragDown () { | ||||
|         this.isChoose = true | ||||
|       }, | ||||
|  | ||||
|       onCheckChange (e) { | ||||
|         this.$emit('checkChange', e) | ||||
|       }, | ||||
|  | ||||
|       backLiveing () { | ||||
|         this.$emit('backLiveing') | ||||
|         this.isLiveing = true | ||||
|         this.initNowTime() | ||||
|         this.form.date = this.$moment(new Date()).format('YYYY-MM-DD') | ||||
|         this.date = this.$moment(new Date()).format('YYYY-MM-DD') | ||||
|         this.getSlwPlaybackTime() | ||||
|       }, | ||||
|  | ||||
|       onMouseUp () { | ||||
|         if (!this.isChoose) return | ||||
|  | ||||
|         clearInterval(this.timer) | ||||
|         this.timer = null | ||||
|         this.isChoose = false | ||||
|         const time = this.secTotime((24 * 60 * 60) / this.canvasWidth * (this.x - 100)) | ||||
|         this.$emit('replay', { | ||||
|           startTime: `${this.form.date} ${time}`, | ||||
|           endTime: this.form.date + ` ${Number(time.substr(0, 2)) + 6 > 9 ? Number(time.substr(0, 2)) + 6 : '0' + (Number(time.substr(0, 2)) + 6)}:59:59` | ||||
|         }) | ||||
|  | ||||
|         this.isLiveing = false | ||||
|       }, | ||||
|  | ||||
|       init () { | ||||
|         this.$nextTick(() => { | ||||
|           this.isInit = true | ||||
|           this.$nextTick(() => { | ||||
|             this.canvasWidth = document.querySelector(`#${this.videoId}`).offsetWidth - 116 | ||||
|             this.canvasHeight = document.querySelector(`#${this.videoId}`).offsetHeight | ||||
|  | ||||
|             this.$nextTick(() => { | ||||
|               const el = document.querySelector(`#synergr-canvas`) | ||||
|               this.ctx = el.getContext('2d') | ||||
|               this.ctx.width  = document.querySelector(`#${this.videoId}`).offsetWidth - 116 | ||||
|               this.ctx.height  = document.querySelector(`#${this.videoId}`).offsetHeight | ||||
|  | ||||
|               if (this.x > 0) { | ||||
|                 this.x = this.canvasWidth * this.ratioW | ||||
|               } else { | ||||
|                 this.initNowTime() | ||||
|               } | ||||
|  | ||||
|               this.renderTimeLine() | ||||
|               this.countdown() | ||||
|             }) | ||||
|           }) | ||||
|         }) | ||||
|       }, | ||||
|  | ||||
|       countdown () { | ||||
|         this.timer = setInterval(() => { | ||||
|           this.initNowTime() | ||||
|         }, 1000) | ||||
|       }, | ||||
|  | ||||
|       drawLine(ctx, options) { | ||||
|         const { beginX, beginY, endX, endY, lineColor, lineWidth } = options | ||||
|         ctx.beginPath() | ||||
|         ctx.lineWidth = lineWidth | ||||
|         ctx.moveTo(beginX, beginY) | ||||
|         ctx.lineTo(endX, endY) | ||||
|         ctx.strokeStyle = lineColor | ||||
|         ctx.stroke() | ||||
|       }, | ||||
|  | ||||
|       onClick (e) { | ||||
|         const canvasInfo = document.querySelector(`#synergr-canvas`).getBoundingClientRect() | ||||
|         this.x = e.clientX - canvasInfo.left + 100 | ||||
|         clearInterval(this.timer) | ||||
|         this.timer = null | ||||
|         const time = this.secTotime((24 * 60 * 60) / this.canvasWidth * (this.x - 100)) | ||||
|         this.$emit('replay', { | ||||
|           startTime: `${this.form.date} ${time}`, | ||||
|           endTime: this.form.date + ` ${Number(time.substr(0, 2)) + 6 > 9 ? Number(time.substr(0, 2)) + 6 : '0' + (Number(time.substr(0, 2)) + 6)}:00:00` | ||||
|         }) | ||||
|         this.isLiveing = false | ||||
|       }, | ||||
|  | ||||
|       renderTimeLine () { | ||||
|         const ctx = this.ctx | ||||
|         ctx.fillStyle = 'rgba(40, 43, 58, 1)' | ||||
|         ctx.fillRect(0, 0, this.canvasWidth, 28) | ||||
|          | ||||
|         ctx.fillStyle = '#fff' | ||||
|         ctx.font = '12px Arial' | ||||
|         const w = this.canvasWidth / 24  | ||||
|  | ||||
|         for (let i = 0; i < 25; i ++) { | ||||
|           this.drawLine(ctx, { | ||||
|             beginX: i * w, | ||||
|             beginY: 28, | ||||
|             endX: i * w, | ||||
|             endY: (i % this.scale === 0 || i === 0) ? 22 : 24, | ||||
|             lineColor: (i % this.scale === 0 || i === 0) ? '#000' : '#000', | ||||
|             lineWidth: (i % this.scale === 0 || i === 0) ? 1 : 1 | ||||
|           }) | ||||
|  | ||||
|           if ((i % this.scale === 0 || i === 0)) { | ||||
|             const text = (i < 10 ? '0' + i : i) + ': 00' | ||||
|               const textWidth = ctx.measureText(text).width | ||||
|             if (i === 24) { | ||||
|               ctx.fillText(text, i * w - textWidth, 21) | ||||
|             } else if (i === 0) { | ||||
|               ctx.fillText(text, 0, 21) | ||||
|             } else { | ||||
|               ctx.fillText(text, i * w - textWidth / 2, 21) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|  | ||||
|       initNowTime () { | ||||
|         const date = new Date() | ||||
|         const seconds = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() | ||||
|  | ||||
|         this.x = seconds / (24 * 60 * 60) * this.canvasWidth + 100 | ||||
|       }, | ||||
|  | ||||
|       secTotime (s) { | ||||
|         let second = parseInt(s) | ||||
|         let minute = 0 | ||||
|         let hour = 0 | ||||
|         if (second > 60) { | ||||
|           minute = parseInt(second / 60) | ||||
|           second = parseInt(second % 60) | ||||
|           if (minute > 60) { | ||||
|             hour = parseInt(minute / 60) | ||||
|             minute = parseInt(minute % 60) | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         hour = `${parseInt(hour) > 9 ? parseInt(hour) : '0' + parseInt(hour)}` | ||||
|         minute = `${parseInt(minute) > 9 ? parseInt(minute) : '0' + parseInt(minute)}` | ||||
|         second = `${parseInt(second) > 9 ? parseInt(second) : '0' + parseInt(second)}` | ||||
|         return `${hour}:${minute}:${second}` | ||||
|       }, | ||||
|  | ||||
|       onConfirm () { | ||||
|         this.$refs.form.validate((valid) => { | ||||
|           if (valid) { | ||||
|             this.isShowDate = false | ||||
|             this.date = this.form.date | ||||
|             this.getSlwPlaybackTime() | ||||
|           } | ||||
|         }) | ||||
|       }, | ||||
|  | ||||
|       getSlwPlaybackTime () { | ||||
|         this.$emit('update:isLoading', true) | ||||
|         this.instance.post(`/app/appzyvideoequipment/getSlwPlaybackTime`, null, { | ||||
|           params: { | ||||
|             ids: this.ids, | ||||
|             startTime: this.date + ' 00:00:00', | ||||
|             endTime: this.date + ' 23:59:59', | ||||
|           } | ||||
|         }).then(res => { | ||||
|           if (res.code == 0) { | ||||
|             if (res.data && res.data.length) { | ||||
|               this.times = res.data.map(v => { | ||||
|                 return { | ||||
|                   id: v.id, | ||||
|                   times: v.times.map(item => { | ||||
|                     const startTime = (item.startTime - new Date(this.date + ' 00:00:00').getTime()) / 1000 | ||||
|                     const endTime = (item.endTime - new Date(this.date + ' 00:00:00').getTime()) / 1000 | ||||
|  | ||||
|                     return { | ||||
|                       startTime: Number(startTime.toFixed(0)), | ||||
|                       endTime: Number(endTime.toFixed(0)) | ||||
|                     } | ||||
|                   }).sort((a, b) => { | ||||
|                     return a.startTime - b.startTime | ||||
|                   }) | ||||
|                 } | ||||
|               }) | ||||
|             } | ||||
|  | ||||
|             this.$emit('update:isLoading', false) | ||||
|           } | ||||
|         }).catch(() => { | ||||
|           this.$emit('update:isLoading', false) | ||||
|         }) | ||||
|       }, | ||||
|  | ||||
|       onVolume (e) { | ||||
|         const v = (e / 100).toFixed(1) | ||||
|         const subPage = document.querySelector(`#${this.id}`).contentWindow | ||||
|         subPage.postMessage({ | ||||
|           type: 'volume', | ||||
|           value: Number(v) | ||||
|         }, '*') | ||||
|       }, | ||||
|  | ||||
|       reset () { | ||||
|         setTimeout(() => { | ||||
|           this.init() | ||||
|         }, 60) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss"> | ||||
|   .synergr { | ||||
|     position: relative; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     font-size: 0; | ||||
|     background: rgba(40, 43, 58, 1); | ||||
|  | ||||
|     #synergr-canvas { | ||||
|       margin: 0 0 0 100px; | ||||
|     } | ||||
|  | ||||
|     .playback { | ||||
|       position: absolute; | ||||
|       top: 0; | ||||
|       left: 50%; | ||||
|       z-index: 1; | ||||
|       width: 100%; | ||||
|       padding-top: 6px; | ||||
|       text-align: center; | ||||
|       transform: translate(-50%, -100%); | ||||
|       background: #202330; | ||||
|  | ||||
|       .synergr-more { | ||||
|         width: 80px; | ||||
|         height: 16px; | ||||
|         margin: 0 auto; | ||||
|         cursor: pointer; | ||||
|         background: url(https://cdn.cunwuyun.cn/slw2.0/images/more.png); | ||||
|         color: #fff; | ||||
|  | ||||
|         img { | ||||
|           position: relative; | ||||
|           transition: all ease 0.5s; | ||||
|           transform: rotate(180deg); | ||||
|         } | ||||
|  | ||||
|         &.active img { | ||||
|           transform: rotate(0); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       .playback-list { | ||||
|         padding: 8px 16px; | ||||
|         background: rgba(22, 24, 33, 1); | ||||
|         font-size: 12px; | ||||
|         color: #FFFFFF; | ||||
|  | ||||
|         .playback-item { | ||||
|           position: relative; | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|           width: 100%; | ||||
|           margin-bottom: 4px; | ||||
|  | ||||
|           i { | ||||
|             position: absolute; | ||||
|             top: 50%; | ||||
|             z-index: 1; | ||||
|             width: 2px; | ||||
|             height: 12px; | ||||
|             background: #FFC916; | ||||
|             transform: translateY(-50%); | ||||
|           } | ||||
|  | ||||
|           &:last-child { | ||||
|             margin-bottom: 0; | ||||
|           } | ||||
|  | ||||
|           .playback-item__timeline { | ||||
|             flex: 1; | ||||
|             height: 12px; | ||||
|             line-height: 1; | ||||
|             border-radius: 6px; | ||||
|           } | ||||
|  | ||||
|           .el-checkbox { | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             // width: 100%; | ||||
|  | ||||
|             .el-checkbox__label { | ||||
|               display: flex; | ||||
|               align-items: center; | ||||
|               flex: 1; | ||||
|  | ||||
|               span { | ||||
|                 width: 60px; | ||||
|                 color: #fff; | ||||
|                 text-align: left; | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .time-scale { | ||||
|       display: flex; | ||||
|       position: absolute; | ||||
|       align-items: center; | ||||
|       justify-content: center; | ||||
|       left: 0; | ||||
|       top: 28px; | ||||
|       z-index: 1; | ||||
|       user-select: none; | ||||
|       width: 12px; | ||||
|       height: 24px; | ||||
|  | ||||
|       span { | ||||
|         width: 2px; | ||||
|         height: 24px; | ||||
|         background: rgba(255, 255, 255, 0.8); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .drag-img { | ||||
|       position: absolute; | ||||
|       left: 0; | ||||
|       top: 0; | ||||
|       z-index: 1; | ||||
|       user-select: none; | ||||
|       cursor: e-resize; | ||||
|       -webkit-user-drag: none; | ||||
|       transform: translateX(-50%); | ||||
|     } | ||||
|  | ||||
|     .time { | ||||
|       position: absolute; | ||||
|       bottom: 40px; | ||||
|       left: 0; | ||||
|       z-index: 1; | ||||
|       padding: 2px 4px; | ||||
|       font-size: 12px; | ||||
|       color: #fff; | ||||
|       background: rgba(0, 0, 0, 1); | ||||
|       transform: translate(-50%, 100%); | ||||
|     } | ||||
|  | ||||
|     .slw-bottom { | ||||
|       width: 100%; | ||||
|       height: 40px; | ||||
|     } | ||||
|  | ||||
|     &:hover { | ||||
|       .slw-title { | ||||
|         transform: translateY(0%); | ||||
|       } | ||||
|       .slw-bottom { | ||||
|         transform: translateY(0%); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .action-bar { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       justify-content: space-between; | ||||
|       width: 100%; | ||||
|       height: 40px; | ||||
|       padding: 0 16px; | ||||
|       transition: all ease-in-out 0.5s; | ||||
|       background: rgba(46, 53, 75, 1); | ||||
|  | ||||
|       .left { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|  | ||||
|         .play-status { | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|  | ||||
|           em { | ||||
|             margin-left: 12px; | ||||
|             font-style: normal; | ||||
|             color: #fff; | ||||
|             font-size: 12px; | ||||
|           } | ||||
|  | ||||
|           .back-btn { | ||||
|             padding: 4px 10px; | ||||
|             border-radius: 6px; | ||||
|             color: #ddd; | ||||
|             font-size: 12px; | ||||
|             cursor: pointer; | ||||
|             background: #343747; | ||||
|  | ||||
|             &:hover { | ||||
|               opacity: 0.6; | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           .live { | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             line-height: 1; | ||||
|             padding: 2px 5px; | ||||
|             color: rgba(0,255,0,.8); | ||||
|  | ||||
|             i { | ||||
|               font-size: 12px; | ||||
|               font-style: normal; | ||||
|             } | ||||
|  | ||||
|             .label { | ||||
|               width: 6px; | ||||
|               height: 6px; | ||||
|               margin-right: 12px; | ||||
|               background: rgba(0,255,0,.8); | ||||
|               border-radius: 50%; | ||||
|               position: relative; | ||||
|  | ||||
|               &:after { | ||||
|                 content: ""; | ||||
|                 width: 12px; | ||||
|                 height: 12px; | ||||
|                 position: absolute; | ||||
|                 left: 50%; | ||||
|                 top: 50%; | ||||
|                 -webkit-transform: translate(-50%,-50%); | ||||
|                 transform: translate(-50%,-50%); | ||||
|                 border-radius: 50%; | ||||
|                 border: 4px solid rgba(0,255,0,.2); | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         .volume { | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|           position: relative; | ||||
|           height: 100%; | ||||
|  | ||||
|           .volume-slider { | ||||
|             display: none; | ||||
|             position: absolute; | ||||
|             bottom: 20px; | ||||
|             left: 50%; | ||||
|             z-index: -1; | ||||
|             opacity: 0; | ||||
|             padding: 20px 0 10px; | ||||
|             background-color: rgba(0, 0,0,.8); | ||||
|             transform: translate(-50%, 0); | ||||
|             transition: all ease 0.3s; | ||||
|  | ||||
|             &.active { | ||||
|               display: block; | ||||
|               z-index: 1; | ||||
|               opacity: 1; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         img { | ||||
|           cursor: pointer; | ||||
|         } | ||||
|  | ||||
|         .left-btns { | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|           margin-right: 10px; | ||||
|  | ||||
|           span { | ||||
|             flex: 1; | ||||
|             height: 100%; | ||||
|             line-height: 24px; | ||||
|             background: #222838; | ||||
|             color: #c9c9c9; | ||||
|             font-size: 12px; | ||||
|             cursor: pointer; | ||||
|  | ||||
|             &.active { | ||||
|               color: #fff; | ||||
|               background: linear-gradient(180deg, #28B2EB 0%, #193D91 100%); | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       .right { | ||||
|         color: #c9c9c9; | ||||
|         font-size: 12px; | ||||
|  | ||||
|         span { | ||||
|           margin-right: 32px; | ||||
|           cursor: pointer; | ||||
|  | ||||
|           &:hover { | ||||
|             opacity: 0.6; | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         img { | ||||
|           margin-right: 16px; | ||||
|           cursor: pointer; | ||||
|  | ||||
|           &:last-child { | ||||
|             margin-right: 0; | ||||
|           } | ||||
|  | ||||
|           &:hover { | ||||
|             opacity: 0.6; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       & > div { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
							
								
								
									
										293
									
								
								packages/device/IntelligentSecurity/components/Timeline.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								packages/device/IntelligentSecurity/components/Timeline.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,293 @@ | ||||
| <template> | ||||
|   <div :class="wrapper" class="canvas" @click="onClick" @mousemove.stop="onMousemove" @mouseup="onMouseUp" @mouseleave="isHide = true" v-if="isInit"> | ||||
|     <canvas :id="id" :style="{height: '52px'}" v-if="canvasWidth" :width="canvasWidth" height="52"> | ||||
|     </canvas> | ||||
|     <div class="time" v-show="!isHide" :style="{left: left + 'px'}">{{ time }}</div> | ||||
|     <div class="time-scale" :style="{left: x + 'px'}"> | ||||
|       <span></span> | ||||
|     </div> | ||||
|     <img @mousedown="onDragDown" class="drag-img" :style="{left: x + 'px'}" src="https://cdn.cunwuyun.cn/slw2.0/images/drag.png" /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   export default { | ||||
|     props: ['isLiveing', 'times'], | ||||
|  | ||||
|     data () { | ||||
|       return { | ||||
|         ctx: null, | ||||
|         canvasWidth: '', | ||||
|         canvasHeight: '', | ||||
|         scale: 4, | ||||
|         time: '', | ||||
|         left: 0, | ||||
|         x: 0, | ||||
|         ratioW: '', | ||||
|         isHide: true, | ||||
|         isInit: false, | ||||
|         isChoose: false, | ||||
|         wrapper: `canvas-${new Date().getTime()}`, | ||||
|         id: `timeline-${new Date().getTime()}`, | ||||
|         timer: null | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     watch: { | ||||
|       isLiveing () { | ||||
|         this.countdown() | ||||
|       }, | ||||
|  | ||||
|       times: { | ||||
|         deep: true, | ||||
|         handler (v) { | ||||
|           if (v.length && this.ctx) { | ||||
|             this.init() | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     mounted () { | ||||
|       this.$nextTick(() => { | ||||
|         this.init() | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     destroyed () { | ||||
|       clearInterval(this.timer) | ||||
|     }, | ||||
|  | ||||
|     methods: { | ||||
|       onMousemove (e) { | ||||
|         const canvasInfo = document.querySelector(`#${this.id}`).getBoundingClientRect() | ||||
|         const seconds = 24 * 60 * 60 | ||||
|  | ||||
|         if (e.clientY - canvasInfo.top < 29) { | ||||
|           const x = e.clientX - canvasInfo.left | ||||
|           const unit = seconds / this.canvasWidth * x | ||||
|           this.left = x | ||||
|           this.time = this.secTotime(unit) | ||||
|           this.isHide = false | ||||
|  | ||||
|           if (!this.isChoose) return | ||||
|           this.x = e.clientX - canvasInfo.left | ||||
|           this.ratioW = this.x / this.canvasWidth | ||||
|         } else { | ||||
|           this.isHide = true | ||||
|         } | ||||
|       }, | ||||
|  | ||||
|       onClick (e) { | ||||
|         const canvasInfo = document.querySelector(`#${this.id}`).getBoundingClientRect() | ||||
|  | ||||
|         if (e.clientY - canvasInfo.top < 29) { | ||||
|           this.x = e.clientX - canvasInfo.left | ||||
|           clearInterval(this.timer) | ||||
|           this.timer = null | ||||
|           const time = this.secTotime((24 * 60 * 60) / this.canvasWidth * this.x) | ||||
|  | ||||
|           this.$emit('replay', time) | ||||
|         } | ||||
|       }, | ||||
|  | ||||
|       onDragDown () { | ||||
|         this.isChoose = true | ||||
|       }, | ||||
|  | ||||
|       onMouseUp () { | ||||
|         if (!this.isChoose) return | ||||
|  | ||||
|         clearInterval(this.timer) | ||||
|         this.timer = null | ||||
|         this.isChoose = false | ||||
|         const time = this.secTotime((24 * 60 * 60) / this.canvasWidth * this.x) | ||||
|         this.$emit('replay', time) | ||||
|       }, | ||||
|  | ||||
|       secTotime (s) { | ||||
|         let second = parseInt(s) | ||||
|         let minute = 0 | ||||
|         let hour = 0 | ||||
|         if (second > 60) { | ||||
|           minute = parseInt(second / 60) | ||||
|           second = parseInt(second % 60) | ||||
|           if (minute > 60) { | ||||
|             hour = parseInt(minute / 60) | ||||
|             minute = parseInt(minute % 60) | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         hour = `${parseInt(hour) > 9 ? parseInt(hour) : '0' + parseInt(hour)}` | ||||
|         minute = `${parseInt(minute) > 9 ? parseInt(minute) : '0' + parseInt(minute)}` | ||||
|         second = `${parseInt(second) > 9 ? parseInt(second) : '0' + parseInt(second)}` | ||||
|         return `${hour}:${minute}:${second}` | ||||
|       }, | ||||
|  | ||||
|       init () { | ||||
|         if (this.timer) { | ||||
|           clearInterval(this.timer) | ||||
|           this.timer = null | ||||
|         } | ||||
|            | ||||
|         this.ratioW = this.x / this.canvasWidth | ||||
|         this.$nextTick(() => { | ||||
|           this.isInit = true | ||||
|           this.$nextTick(() => { | ||||
|             this.canvasWidth = document.querySelector(`.${this.wrapper}`).offsetWidth | ||||
|             this.canvasHeight = document.querySelector(`.${this.wrapper}`).offsetHeight | ||||
|  | ||||
|             this.$nextTick(() => { | ||||
|               const el = document.querySelector(`#${this.id}`) | ||||
|               this.ctx = el.getContext('2d') | ||||
|               this.ctx.width  = document.querySelector('.canvas').offsetWidth | ||||
|               this.ctx.height  = document.querySelector('.canvas').offsetHeight | ||||
|  | ||||
|               if (this.x > 0) { | ||||
|                 this.x = this.ratioW * this.canvasWidth | ||||
|               } else { | ||||
|                 this.initNowTime() | ||||
|               } | ||||
|  | ||||
|               this.renderTimeLine() | ||||
|               this.renderPlayback() | ||||
|  | ||||
|               this.countdown() | ||||
|             }) | ||||
|           }) | ||||
|         }) | ||||
|       }, | ||||
|  | ||||
|       countdown () { | ||||
|         this.timer = setInterval(() => { | ||||
|           if (this.isLiveing) { | ||||
|             this.initNowTime() | ||||
|           } else { | ||||
|             this.x = this.x + this.canvasWidth / (24 * 60 * 60) | ||||
|           } | ||||
|         }, 1000) | ||||
|       }, | ||||
|  | ||||
|       initNowTime () { | ||||
|         const date = new Date() | ||||
|         const seconds = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() | ||||
|  | ||||
|         this.x = this.canvasWidth / (24 * 60 * 60) * seconds | ||||
|       }, | ||||
|  | ||||
|       drawLine(ctx, options) { | ||||
|         const { beginX, beginY, endX, endY, lineColor, lineWidth } = options | ||||
|         ctx.lineWidth = lineWidth | ||||
|         ctx.strokeStyle = lineColor | ||||
|         ctx.beginPath() | ||||
|         ctx.moveTo(beginX, beginY) | ||||
|         ctx.lineTo(endX, endY) | ||||
|         ctx.closePath() | ||||
|         ctx.stroke() | ||||
|       }, | ||||
|  | ||||
|       renderPlayback () { | ||||
|         const ctx = this.ctx | ||||
|         const unit = this.canvasWidth / (24 * 60 * 60) | ||||
|         this.times.forEach(item => { | ||||
|           this.drawLine(ctx, { | ||||
|             beginX: item.startTime * unit, | ||||
|             beginY: 28, | ||||
|             endX: item.startTime * unit, | ||||
|             endY: 0, | ||||
|             lineColor: 'rgba(0, 156, 255, 1)', | ||||
|             lineWidth: item.endTime * unit - item.startTime * unit | ||||
|           }) | ||||
|         }) | ||||
|       }, | ||||
|  | ||||
|       renderTimeLine () { | ||||
|         const ctx = this.ctx | ||||
|         ctx.fillStyle = 'rgba(51, 60, 83, 0.8)' | ||||
|         ctx.fillRect(0, 0, this.canvasWidth, 28) | ||||
|         ctx.fillStyle = 'rgba(32, 40, 61, 0.8)' | ||||
|         ctx.fillRect(0, 28, this.canvasWidth, 24) | ||||
|  | ||||
|          | ||||
|         ctx.fillStyle = '#fff' | ||||
|         ctx.font = '12px Arial' | ||||
|         const w = this.canvasWidth / 24  | ||||
|  | ||||
|         for (let i = 1; i < 25; i ++) { | ||||
|           this.drawLine(ctx, { | ||||
|             beginX: i * w, | ||||
|             beginY: 28, | ||||
|             endX: i * w, | ||||
|             endY: i % this.scale === 0 ? 16 : 20, | ||||
|             lineColor: i % this.scale === 0 ? 'red' : '#000', | ||||
|             lineWidth: i % this.scale === 0 ? 1 : 1 | ||||
|           }) | ||||
|  | ||||
|           if (i % this.scale === 0) { | ||||
|             const text = (i < 10 ? '0' + i : i) + ': 00' | ||||
|             const textWidth = ctx.measureText(text).width | ||||
|             if (i === 24) { | ||||
|               ctx.fillText(text, i * w - textWidth, 44) | ||||
|             } else { | ||||
|               ctx.fillText(text, i * w - textWidth / 2, 44) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         if (!this.times.length) { | ||||
|         ctx.stroke() | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   .canvas { | ||||
|     position: relative; | ||||
|     width: 100%; | ||||
|     height: 52px; | ||||
|  | ||||
|     .drag-img { | ||||
|       position: absolute; | ||||
|       left: 0; | ||||
|       top: 0; | ||||
|       z-index: 1; | ||||
|       user-select: none; | ||||
|       cursor: e-resize; | ||||
|       -webkit-user-drag: none; | ||||
|       // transform: translateX(-50%); | ||||
|     } | ||||
|  | ||||
|     .time-scale { | ||||
|       display: flex; | ||||
|       position: absolute; | ||||
|       align-items: center; | ||||
|       justify-content: center; | ||||
|       left: 0; | ||||
|       bottom: 0; | ||||
|       z-index: 1; | ||||
|       user-select: none; | ||||
|       width: 12px; | ||||
|       height: 24px; | ||||
|  | ||||
|       span { | ||||
|         width: 2px; | ||||
|         height: 24px; | ||||
|         background: rgba(255, 255, 255, 0.8); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .time { | ||||
|       position: absolute; | ||||
|       bottom: 22px; | ||||
|       left: 0; | ||||
|       z-index: 1; | ||||
|       padding: 2px 4px; | ||||
|       font-size: 12px; | ||||
|       color: #fff; | ||||
|       background: rgba(0, 0, 0, 1); | ||||
|       transform: translate(-50%, 100%); | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
							
								
								
									
										319
									
								
								packages/device/IntelligentSecurity/components/deviceSlider.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								packages/device/IntelligentSecurity/components/deviceSlider.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,319 @@ | ||||
| <template> | ||||
|   <section class="deviceSlider"> | ||||
|     <div class="mainPane" v-if="show"> | ||||
|       <div flex overview> | ||||
|         <b>监控设备</b> | ||||
|         <div> | ||||
|           <div>设备总数:{{ overview.total }}</div> | ||||
|           <div flex>在线设备:<p v-text="overview.online"/></div> | ||||
|         </div> | ||||
|         <el-progress type="circle" :width="40" :percentage="overview.percent" color="#19D286" :stroke-width="4"/> | ||||
|       </div> | ||||
|       <div flex search> | ||||
|         <el-select v-model="search.bind" size="mini" placeholder="全部" clearable @change="onChange"> | ||||
|           <el-option v-for="(op,i) in dict.getDict('deviceStatus')" :key="i" :value="op.dictValue" | ||||
|                      :label="op.dictName"/> | ||||
|         </el-select> | ||||
|         <el-input | ||||
|             v-model="search.name" | ||||
|             size="mini" | ||||
|             placeholder="设备名称" | ||||
|             v-throttle="handleTreeFilter" | ||||
|             prefix-icon="el-icon-search" | ||||
|             @clear="search.name = '', handleTreeFilter()" clearable/> | ||||
|       </div> | ||||
|       <div title>设备列表</div> | ||||
|       <div fill class="deviceList"> | ||||
|         <el-tree ref="deviceTree" highlight-current :render-content="renderItem" :data="treeData" :props="propsConfig" | ||||
|                  @node-click="handleNodeClick" @node-contextmenu="nodeContextmenu" | ||||
|                  :filter-node-method="handleFilter"/> | ||||
|         <ul | ||||
|           v-if="isShowMenu && menuInfo.node.type==1 && permissions('video_config')" | ||||
|           class="el-dropdown-menu el-popper" | ||||
|           :style="{top: menuInfo.y + 'px', left: menuInfo.x + 'px', position: 'fixed', zIndex: 2023}" | ||||
|           x-placement="top-end"> | ||||
|           <li class="el-dropdown-menu__item" @click="handleTreeCommand('edit', menuInfo.node)">修改名称</li> | ||||
|           <li class="el-dropdown-menu__item" @click="handleTreeCommand('locate', menuInfo.node)">地图标绘</li> | ||||
|         </ul> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="rightBtn" :class="{show}" @click="handleShow"> | ||||
|       <i class="iconfont iconArrow_Right"/> | ||||
|     </div> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   name: "deviceSlider", | ||||
|   props: { | ||||
|     show: Boolean, | ||||
|     ins: Function, | ||||
|     dict: Object, | ||||
|     permissions: Function, | ||||
|     renderItem: Function | ||||
|   }, | ||||
|   computed: { | ||||
|     overview() { | ||||
|       let total = this.list?.length || 0, | ||||
|           online = this.list?.filter(e => e.deviceStatus == 1)?.length || 0 | ||||
|       return { | ||||
|         total, online, | ||||
|         percent: Math.ceil(online / total * 100) || 0 | ||||
|       } | ||||
|     }, | ||||
|     propsConfig() { | ||||
|       return { | ||||
|         label: 'name', | ||||
|         children: 'children' | ||||
|       } | ||||
|     }, | ||||
|     treeData() { | ||||
|       let {list, noArea, staData} = this | ||||
|       let meta = [staData?.reduce((t, e) => { | ||||
|         return t.type <= e.type ? t : e | ||||
|       }, {name: '读取中...'})] | ||||
|       meta.map(p => this.addChild(p, [...staData, ...list].map(s => ({ | ||||
|         ...s, | ||||
|         parentId: s.areaId || s.parent_id | ||||
|       })))) | ||||
|       return [...meta, { | ||||
|         id: 'no_area', | ||||
|         name: '未知区划', | ||||
|         children: noArea | ||||
|       }] | ||||
|     } | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       list: [], | ||||
|       noArea: [], | ||||
|       staData: [], | ||||
|       name: '', | ||||
|       isShowMenu: false, | ||||
|       search: { | ||||
|         bind: '' | ||||
|       }, | ||||
|       menuInfo: { | ||||
|         x: '', | ||||
|         y: '', | ||||
|         node: {} | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     handleShow() { | ||||
|       this.$emit('update:show', !this.show) | ||||
|     }, | ||||
|  | ||||
|     bindEvent() { | ||||
|       this.isShowMenu = false | ||||
|     }, | ||||
|     getDevices() { | ||||
|       this.ins.post("/app/appzyvideoequipment/tree", null, { | ||||
|         params: {size: 999} | ||||
|       }).then(res => { | ||||
|         if (res?.data) { | ||||
|           this.staData = res.data.count | ||||
|           this.list = res.data.list | ||||
|           this.noArea = res.data.noArea | ||||
|           this.$emit('list', this.list) | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     handleTreeCommand(e, node) { | ||||
|       this.$emit('treeCommand', { | ||||
|         type: e, | ||||
|         node | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     nodeContextmenu(e, node) { | ||||
|       this.isShowMenu = true | ||||
|       let y = e.y + 6 | ||||
|       if (y + 202 > document.body.clientHeight) { | ||||
|         y = y - 202 | ||||
|       } | ||||
|       this.menuInfo = { | ||||
|         x: e.x + 16, y, | ||||
|         node | ||||
|       } | ||||
|     }, | ||||
|     handleNodeClick(data) { | ||||
|       this.isShowMenu = false | ||||
|       this.$emit('select', data) | ||||
|     }, | ||||
|     handleFilter(v, data) { | ||||
|       if (!v) { | ||||
|         return !this.search.bind ? true : data.deviceStatus === this.search.bind | ||||
|       } | ||||
|  | ||||
|       return data?.name?.indexOf(v) > -1 && (!this.search.bind ? true : data.deviceStatus === this.search.bind) | ||||
|     }, | ||||
|     handleTreeFilter() { | ||||
|       this.$refs.deviceTree?.filter(this.search.name) | ||||
|     }, | ||||
|  | ||||
|     onChange() { | ||||
|       this.$refs.deviceTree?.filter(this.search.name) | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.dict.load("deviceStatus") | ||||
|     this.getDevices() | ||||
|   }, | ||||
|  | ||||
|   mounted() { | ||||
|     document.querySelector('html').addEventListener('click', this.bindEvent) | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .deviceSlider { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   flex-shrink: 0; | ||||
|   color: #fff; | ||||
|   overflow: hidden; | ||||
|  | ||||
|   div[flex] { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|   } | ||||
|  | ||||
|   .deviceList { | ||||
|     overflow: auto; | ||||
|  | ||||
|     ::v-deep .el-tree { | ||||
|       width: -webkit-fit-content; | ||||
|       width: -moz-fit-content; | ||||
|       width: fit-content; | ||||
|       min-width: 100%; | ||||
|     } | ||||
|  | ||||
|     &::-webkit-scrollbar { | ||||
|       width: 10px; | ||||
|       height: 15px; | ||||
|     } | ||||
|  | ||||
|     &::-webkit-scrollbar-thumb { | ||||
|       box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.2); | ||||
|       background: #535353; | ||||
|     } | ||||
|  | ||||
|     &::-webkit-scrollbar-track { | ||||
|       box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.2); | ||||
|       background: #fff; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   div[fill] { | ||||
|     flex: 1; | ||||
|     min-width: 0; | ||||
|     min-height: 0; | ||||
|   } | ||||
|  | ||||
|   .mainPane { | ||||
|     width: 280px; | ||||
|     height: 100%; | ||||
|     background: #333C53; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     padding-top: 16px; | ||||
|     overflow: hidden; | ||||
|     box-sizing: border-box; | ||||
|  | ||||
|     b { | ||||
|       font-size: 18px; | ||||
|     } | ||||
|  | ||||
|     div[overview], div[search] { | ||||
|       box-sizing: border-box; | ||||
|       font-size: 12px; | ||||
|       justify-content: space-between; | ||||
|       padding: 0 16px; | ||||
|       gap: 4px; | ||||
|       margin-bottom: 16px; | ||||
|  | ||||
|       ::v-deep.el-input__inner { | ||||
|         color: #fff; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     div[title] { | ||||
|       height: 28px; | ||||
|       background: #3E4A69; | ||||
|       padding: 0 16px; | ||||
|       line-height: 28px; | ||||
|     } | ||||
|  | ||||
|     ::v-deep.deviceList { | ||||
|       padding: 0 8px; | ||||
|  | ||||
|       .el-scrollbar { | ||||
|         height: 100%; | ||||
|  | ||||
|         .el-scrollbar__wrap { | ||||
|           box-sizing: content-box; | ||||
|           padding-bottom: 17px; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     ::v-deep .el-progress__text, p { | ||||
|       color: #19D286; | ||||
|     } | ||||
|  | ||||
|     ::v-deep .el-input__inner { | ||||
|       background: #282F45; | ||||
|       border: none; | ||||
|     } | ||||
|  | ||||
|     ::v-deep .el-tree { | ||||
|       background: transparent; | ||||
|       color: #fff; | ||||
|  | ||||
|       .el-tree-node__content { | ||||
|         background: transparent!important; | ||||
|       } | ||||
|        | ||||
|       .el-tree-node__children .is-current .el-tree-node__content { | ||||
|         background: linear-gradient(90deg, #299FFF 0%, #0C61FF 100%)!important; | ||||
|       } | ||||
|  | ||||
|       .el-tree-node__content:hover { | ||||
|         background: transparent; | ||||
|       } | ||||
|  | ||||
|       .el-tree-node__content { | ||||
|         height: 32px; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     ::v-deep .el-input__icon { | ||||
|       color: #89b; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .rightBtn { | ||||
|     width: 16px; | ||||
|     height: 80px; | ||||
|     background: url("https://cdn.cunwuyun.cn/monitor/drawerBtn.png"); | ||||
|     color: #fff; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|  | ||||
|     .iconfont { | ||||
|       transition: transform 0.2s; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     &.show > .iconfont { | ||||
|       transform: rotate(180deg); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										177
									
								
								packages/device/IntelligentSecurity/components/locateDialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								packages/device/IntelligentSecurity/components/locateDialog.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | ||||
| <template> | ||||
|   <section class="locateDialog"> | ||||
|     <ai-dialog :visible.sync="dialog" title="标绘" @closed="$emit('visible',false),selected={}" | ||||
|                @opened="$nextTick(()=>initMap())" | ||||
|                @onConfirm="handleConfirm"> | ||||
|       <ai-t-map :map.sync="map" :lib.sync="TMap"/> | ||||
|       <div class="poi"> | ||||
|         <el-autocomplete ref="poiInput" v-model="search" size="small" clearable :fetch-suggestions="handleSearch" | ||||
|                          placeholder="请输入地点" @select="handleSelect" :trigger-on-focus="false"> | ||||
|           <template slot-scope="{item}"> | ||||
|             <span style="direction: rtl" v-text="`${item.title}(${item.address})`"/> | ||||
|           </template> | ||||
|         </el-autocomplete> | ||||
|       </div> | ||||
|       <el-form class="selected" v-if="!!selected.location" id="result" size="mini" label-suffix=":" | ||||
|                label-position="left"> | ||||
|         <div class="header"> | ||||
|           <i class="iconfont iconLocation"/> | ||||
|           <span v-html="[selected.location.lng, selected.location.lat].join(',')"/> | ||||
|         </div> | ||||
|         <el-form-item label="地点">{{ selected.name || "未知地名" }}</el-form-item> | ||||
|         <el-form-item label="类型" v-if="!!selected.type">{{ selected.type }}</el-form-item> | ||||
|         <el-form-item label="地址" v-if="!!selected.address">{{ selected.address }}</el-form-item> | ||||
|       </el-form> | ||||
|     </ai-dialog> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import {mapState} from "vuex"; | ||||
|  | ||||
| export default { | ||||
|   name: "locateDialog", | ||||
|   model: { | ||||
|     prop: "visible", | ||||
|     event: "visible", | ||||
|   }, | ||||
|   props: ['latlng', 'visible'], | ||||
|   data() { | ||||
|     return { | ||||
|       dialog: false, | ||||
|       search: "", | ||||
|       map: null, | ||||
|       selected: {}, | ||||
|       TMap: null | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState(['user']) | ||||
|   }, | ||||
|   watch: { | ||||
|     visible(v) { | ||||
|       this.dialog = v | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     initMap(count = 0) { | ||||
|       let {map, TMap} = this | ||||
|       if (map) { | ||||
|         if (!!this.latlng?.lat) { | ||||
|           let position = new TMap.LatLng(this.latlng.lat, this.latlng.lng) | ||||
|           map.setCenter(position) | ||||
|           this.selected.marker = new TMap.MultiMarker({map, geometries: [{position}]}) | ||||
|         } | ||||
|         map.on('click', res => { | ||||
|           let {poi, latLng: location} = res, name = poi?.name || "" | ||||
|           this.selected.marker?.setMap(null) | ||||
|           this.selected = {location, name} | ||||
|           this.selected.marker = new TMap.MultiMarker({map, geometries: [{position: location}]}) | ||||
|         }) | ||||
|       } else { | ||||
|         if (count < 5) { | ||||
|           count++ | ||||
|           setTimeout(() => this.initMap(count), 500) | ||||
|         } else { | ||||
|           console.error("地图渲染失败") | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     handleSearch(keyword, cb) { | ||||
|       let {TMap} = this | ||||
|       if (keyword && TMap) { | ||||
|         let poi = new TMap.service.Search({pageSize: 10}) | ||||
|         poi.searchRegion({ | ||||
|           keyword, radius: 5000, cityName: this.user.info?.areaId?.substring(0, 6) || "" | ||||
|         }).then(res => { | ||||
|           if (res?.data?.length > 0) { | ||||
|             cb(res.data) | ||||
|           } else this.$message.error("未查到有效地点") | ||||
|         }) | ||||
|       } | ||||
|     }, | ||||
|     handleConfirm() { | ||||
|       if (this.selected?.location) { | ||||
|         this.$emit('confirm', this.selected) | ||||
|       } else { | ||||
|         this.$message.error('请先选择坐标位置') | ||||
|       } | ||||
|     }, | ||||
|     handleSelect(res) { | ||||
|       let {map, TMap} = this | ||||
|       if (map) { | ||||
|         let {title: name, location} = res | ||||
|         this.selected.marker?.setMap(null) | ||||
|         this.selected = {location, name} | ||||
|         this.selected.marker = new TMap.MultiMarker({map, geometries: [{position: location}]}) | ||||
|         map.setCenter(location) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.dialog = this.visible | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .locateDialog { | ||||
|   .color-999 { | ||||
|     color: #999; | ||||
|   } | ||||
|  | ||||
|   ::v-deep .el-dialog__body { | ||||
|     padding: 0; | ||||
|     height: 480px; | ||||
|     position: relative; | ||||
|  | ||||
|     .ai-dialog__content--wrapper { | ||||
|       padding: 0 !important; | ||||
|     } | ||||
|  | ||||
|     .poi { | ||||
|       position: absolute; | ||||
|       left: 10px; | ||||
|       top: 10px; | ||||
|       display: flex; | ||||
|       height: 32px; | ||||
|       flex-direction: column; | ||||
|       z-index: 202203281016; | ||||
|       width: 400px; | ||||
|  | ||||
|       div { | ||||
|         flex-shrink: 0; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .selected { | ||||
|       position: absolute; | ||||
|       right: 16px; | ||||
|       top: 16px; | ||||
|       background: #fff; | ||||
|       min-width: 200px; | ||||
|       box-sizing: border-box; | ||||
|       box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); | ||||
|  | ||||
|       .header { | ||||
|         color: #fff; | ||||
|         background: #26f; | ||||
|         text-align: center; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         height: 32px; | ||||
|         font-size: 14px; | ||||
|         gap: 4px; | ||||
|         padding: 0 8px; | ||||
|       } | ||||
|  | ||||
|       .el-form-item { | ||||
|         padding: 0 8px; | ||||
|         margin: 0; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| </style> | ||||
| @@ -0,0 +1,91 @@ | ||||
| <template> | ||||
|   <section class="settingDialog"> | ||||
|     <ai-dialog :visible.sync="dialog" title="基础设置" @close="$emit('visible',false)"> | ||||
|       <el-form ref="deviceForm" size="small" label-width="140px"> | ||||
|         <el-form-item label="设备名称" class="full"> | ||||
|           <el-input v-model="form.name" clearable placeholder="设备名称"/> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="摄像头状态"> | ||||
|           <el-radio v-model="form.status" label="开启"/> | ||||
|           <el-radio v-model="form.status" label="关闭"/> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="高清视频"> | ||||
|           <el-radio v-model="form.status" label="开启"/> | ||||
|           <el-radio v-model="form.status" label="关闭"/> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="摄像头麦克风"> | ||||
|           <el-radio v-model="form.status" label="开启"/> | ||||
|           <el-radio v-model="form.status" label="关闭"/> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="状态指示灯"> | ||||
|           <el-radio v-model="form.status" label="开启"/> | ||||
|           <el-radio v-model="form.status" label="关闭"/> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="夜视"> | ||||
|           <el-radio v-model="form.status" label="自动"/> | ||||
|           <el-radio v-model="form.status" label="开启"/> | ||||
|           <el-radio v-model="form.status" label="关闭"/> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="旋转180°"> | ||||
|           <el-radio v-model="form.status" label="开启"/> | ||||
|           <el-radio v-model="form.status" label="关闭"/> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="WIFI网络" class="full">-</el-form-item> | ||||
|         <el-form-item label="MAC地址">(34:75:6b:c9:10)</el-form-item> | ||||
|         <el-form-item label="摄像头型号">C71</el-form-item> | ||||
|         <el-form-item label="固件">20.0326.251.2486</el-form-item> | ||||
|         <el-form-item label="嵌入式应用">2.3.37.8954</el-form-item> | ||||
|         <el-form-item label="IMEI">110003953100302</el-form-item> | ||||
|       </el-form> | ||||
|     </ai-dialog> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   name: "settingDialog", | ||||
|   model: { | ||||
|     prop: "visible", | ||||
|     event: "visible", | ||||
|   }, | ||||
|   props: { | ||||
|     visible: Boolean, | ||||
|     detail: {default: () => ({})} | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       dialog: false, | ||||
|       form: {} | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     visible(v) { | ||||
|       this.dialog = v | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.form = JSON.parse(JSON.stringify(this.form)) | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .settingDialog { | ||||
|   .el-form { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|   } | ||||
|  | ||||
|   ::v-deep .el-form-item { | ||||
|     width: 50%; | ||||
|  | ||||
|     .el-form-item__label { | ||||
|       padding-right: 40px; | ||||
|     } | ||||
|  | ||||
|     &.full { | ||||
|       width: 100%; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
		Reference in New Issue
	
	Block a user