ah_jjzhgd_webservice/ah-jjzhgd-web/src/views/risk/dailyTask/components/RemoteWatchModal.vue

551 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-dialog title="远程巡视" :visible.sync="visible" fullscreen custom-class="modal" @closed="handleCloseModal">
<el-container class="wrap">
<el-aside width="70%" class="aside">
<div class="watch-operate">
<div class="watch-operate__top">
<div class="operate-left">
<!-- <div>-->
<!-- <img :src="operateIcon.videoLocal">-->
<!-- <p>本地录像</p>-->
<!-- </div>-->
<div>
<img :src="operateIcon.videoRemote" @mousedown="handleControl('video')">
<p>录像</p>
</div>
</div>
<div class="operate-center">
<div class="item" />
<div class="item">
<img :src="operateIcon.top" @mousedown="handleControl('up')" @mouseup="handleControl('stopTurn')">
</div>
<div class="item" />
<div class="item">
<img :src="operateIcon.left" @mousedown="handleControl('left')" @mouseup="handleControl('stopTurn')">
</div>
<div class="item" />
<div class="item">
<img :src="operateIcon.right" @mousedown="handleControl('right')" @mouseup="handleControl('stopTurn')">
</div>
<div class="item" />
<div class="item">
<img :src="operateIcon.bottom" @mousedown="handleControl('down')" @mouseup="handleControl('stopTurn')">
</div>
<div class="item" />
</div>
<div class="operate-right">
<!-- <div>-->
<!-- <img :src="operateIcon.photoLocal">-->
<!-- <p>本地抓拍</p>-->
<!-- </div>-->
<div>
<img :src="operateIcon.photoRemote" @click="handleControl('capture')">
<p>抓拍</p>
</div>
</div>
</div>
<div class="watch-operate__bottom">
<div>
<img :src="operateIcon.nearFocus" @mousedown="handleControl('near')" @mouseup="handleControl('stopFocus')">
<p>近焦</p>
</div>
<div>
<img :src="operateIcon.farFocus" @mousedown="handleControl('far')" @mouseup="handleControl('stopFocus')">
<p>远焦</p>
</div>
<div>
<img :src="operateIcon.shrink" @mousedown="handleControl('shrink')" @mouseup="handleControl('stopZoom')">
<p>缩小</p>
</div>
<div>
<img :src="operateIcon.amplify" @mousedown="handleControl('amplify')" @mouseup="handleControl('stopZoom')">
<p>放大</p>
</div>
</div>
<div class="watch-operate__keyboard">
<h3 class="item-title">球机当日上线记录 &emsp;&emsp;<a href="#" style="color: #1890ff">快捷键设置</a></h3>
<el-divider />
<div>
<div class="filter-container">
<el-date-picker
v-model="listQuery.workDay"
class="filter-item"
style="width: 50%"
type="date"
value-format="yyyy-MM-dd"
placeholder="选择日期"
/>
<el-button
style="margin-left: 40px"
class="filter-item"
type="primary"
@click="handleFilter"
>
查询
</el-button>
</div>
<el-table
:data="list"
border
fit
highlight-current-row
style="width: 100%"
>
<el-table-column label="开机时间" align="center" prop="upTime" />
<el-table-column label="关机时间" align="center" prop="downTime" />
<el-table-column label="在线时长" align="center" prop="hours" />
</el-table>
</div>
</div>
</div>
<div class="watch-play">
<div id="video-watch" class="video-player-wrap" style="pointer-events: none;">
<video
id="videoPlayer"
ref="videoPlayer"
class="video-player"
autoplay
controls
style="object-fit: fill;"
/>
</div>
</div>
</el-aside>
<el-main class="main">
<div class="top-nav">
<el-button type="primary">班组评价</el-button>
<el-button type="primary">杆塔信息</el-button>
<el-button type="primary">设置守望位</el-button>
</div>
<div class="detail-content">
<h3 class="item-title">站班会详情({{ detail.sgStatus }} <a href="#" class="fr" style="color: #1890ff">{{ detail.ticketNo }}</a></h3>
<el-divider />
<p>标段工程名称:{{ detail.bidName }}</p>
<p>施工单位:{{ detail.sgdw }}</p>
<p>监理单位:{{ detail.jldw }}</p>
<p>施工日期:{{ detail.workDay }}</p>
<p>开具站班会时间:{{ detail.startTime }}</p>
<p>作业类型、工序及部位:{{ detail.workContent }}</p>
<p>工作负责人:{{ detail.workManager }}</p>
<p>班组长手机号:{{ detail.workManagerPhone }}</p>
<p>安全监护:{{ detail.safetyManager }}</p>
<h3 class="item-title">参与施工人员签名</h3>
<el-divider />
<div>
<el-tag v-for="item in workerList" :key="item.id">{{ item.userName }}</el-tag>
</div>
<h3 class="item-title">关键点措施照片</h3>
<el-divider />
<h3 class="item-title">施工方案</h3>
<el-divider />
</div>
</el-main>
</el-container>
</el-dialog>
</template>
<script>
import bottomBtn from '@/assets/images/video/bottomBtn.png'
import topBtn from '@/assets/images/video/topBtn.png'
import leftBtn from '@/assets/images/video/leftBtn.png'
import rightBtn from '@/assets/images/video/rightBtn.png'
import videoLocal from '@/assets/images/video/videoLocal.png'
import videoRemote from '@/assets/images/video/videoRemote.png'
import photoLocal from '@/assets/images/video/photoLocal.png'
import photoRemote from '@/assets/images/video/photoRemote.png'
import nearFocus from '@/assets/images/video/nearFocus.png'
import farFocus from '@/assets/images/video/farFocus.png'
import amplify from '@/assets/images/video/amplify.png'
import shrink from '@/assets/images/video/shrink.png'
import { getRemoteWatchDetail, getRemoteWatchTime } from '@/api/risk/dailyTask'
import _ from 'lodash/fp'
import flvjs from 'flv.js'
import { videoConfig } from '@/views/risk/dailyTask/config/video'
import moment from 'moment'
const tmp = {
bidName: '',
sgStatus: '',
workManager: '',
jldw: '',
workContent: '',
changes: '',
workManagerPhone: '',
workDay: '',
safetyManager: '',
classId: '',
sgdw: '',
ticketNo: '',
currentControl: '',
startTime: '',
controll: '',
ticketId: ''
}
export default {
name: 'RemoteWatchModal',
props: ['componentVisible', 'currentId'],
data() {
return {
// 视频播放实例
videoPlayer: null,
startVideoStatus: false,
mediaRecorder: null,
recordedChunks: [],
videoUrl: null,
//
list: [],
listQuery: {
classId: '',
workDay: moment().format('YYYY-MM-DD')
},
detail: _.cloneDeep(tmp),
workerList: [],
operateIcon: {
top: topBtn,
left: leftBtn,
right: rightBtn,
bottom: bottomBtn,
videoLocal,
videoRemote,
photoLocal,
photoRemote,
nearFocus,
farFocus,
amplify,
shrink
}
}
},
computed: {
visible: {
get() {
return this.componentVisible
},
set(val) {
this.$emit('update:componentVisible', val)
}
}
},
watch: {
visible(val) {
if (val) {
this.getList()
this.initVideo()
}
}
},
mounted() {
// this.initVideo()
},
methods: {
getList() {
getRemoteWatchDetail({ classId: this.currentId }).then(res => {
const { classDetails, workPeopleLists } = res.data
this.detail = classDetails
this.workerList = workPeopleLists
})
},
handleCloseModal() {
this.workerList = []
this.list = []
this.detail = _.cloneDeep(tmp)
this.closeVideo()
},
handleFilter() {
this.listQuery.classId = this.currentId
getRemoteWatchTime(this.listQuery).then(res => {
this.list = res.data
})
},
// 初始化视频监控
initVideo() {
this.$nextTick(() => {
const videoElement = this.$refs.videoPlayer
const url = `${videoConfig.url}/stream.flv?puid=${videoConfig.puid}&idx=${videoConfig.idx}&stream=${videoConfig.stream}&token=${videoConfig.token}`
try {
this.videoPlayer = flvjs.createPlayer({
type: 'flv',
url: url,
isLive: true,
hasAudio: false
}, {
enableWorker: false,
autoCleanupSourceBuffer: true, // 清理缓冲区
enableStashBuffer: false,
stashInitialSize: 128, // 减少首桢显示等待时长
statisticsInfoReportInterval: 600
})
this.videoPlayer.attachMediaElement(videoElement)
this.videoPlayer.load()
setTimeout(() => {
this.videoPlayer.play()
}, 200)
} catch (err) {
console.log(err)
}
})
},
// 销毁视频监控
closeVideo() {
if (this.videoPlayer) {
this.videoPlayer.pause()
this.videoPlayer.unload()
this.videoPlayer.detachMediaElement()
this.videoPlayer.destroy()
}
},
// 抓拍
getPic() {
const video = this.$refs.videoPlayer
const canvas = document.createElement('canvas')
canvas.width = video.videoWidth
canvas.height = video.videoHeight
const context = canvas.getContext('2d')
context.drawImage(video, 0, 0, canvas.width, canvas.height)
const dataURL = canvas.toDataURL('image/png')
const link = document.createElement('a')
link.download = 'screenshot.png'
link.href = dataURL
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
},
// 录像
handlePhoto() {
this.startVideoStatus = !this.startVideoStatus
if (this.startVideoStatus) { // 开始录像
this.$message({
showClose: true,
message: '录制中···',
type: 'success',
duration: 2000
})
this.startRecording()
} else { // 结束录像
this.$message({
showClose: true,
message: '结束录制并下载文件中···',
type: 'success',
duration: 2000
})
this.stopRecording()
}
},
startRecording() {
const videoPlayer = this.$refs.videoPlayer
const stream = videoPlayer.captureStream()
this.mediaRecorder = new MediaRecorder(stream)
this.mediaRecorder.ondataavailable = event => {
if (event.data.size > 0) {
this.recordedChunks.push(event.data)
}
}
this.mediaRecorder.onstop = () => {
const blob = new Blob(this.recordedChunks, { type: 'video/mp4' })
this.recordedChunks = []
this.videoUrl = URL.createObjectURL(blob)
this.downloadVideo() // 自动触发下载
}
this.mediaRecorder.start()
},
stopRecording() {
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
this.mediaRecorder.stop()
}
},
downloadVideo() {
const link = document.createElement('a')
link.href = this.videoUrl
link.download = 'recorded_video.mp4'
link.style.display = 'none'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
},
// 监控操作
handleControl(type) {
switch (type) {
case 'up':
videoConfig.handle(type, (res) => {
console.log(res)
})
break
case 'down':
videoConfig.handle(type, (res) => {
console.log(res)
})
break
case 'left':
videoConfig.handle(type, (res) => {
console.log(res)
})
break
case 'right':
videoConfig.handle(type, (res) => {
console.log(res)
})
break
case 'stopTurn':
videoConfig.handle(type, (res) => {
console.log(res)
})
break
case 'near':
videoConfig.handle(type, (res) => {
console.log(res)
})
break
case 'far':
videoConfig.handle(type, (res) => {
console.log(res)
})
break
case 'stopFocus':
videoConfig.handle(type, (res) => {
console.log(res)
})
break
case 'shrink':
videoConfig.handle(type, (res) => {
console.log(res)
})
break
case 'amplify':
videoConfig.handle(type, (res) => {
console.log(res)
})
break
case 'stopZoom':
videoConfig.handle(type, (res) => {
console.log(res)
})
break
case 'capture':
this.getPic()
break
case 'video':
this.handlePhoto()
break
default:
break
}
}
}
}
</script>
<style scoped lang="scss">
::v-deep .modal {
display: flex;
flex-direction: column;
.el-dialog__body {
flex: 1 !important;
padding: unset !important;
}
}
.wrap {
height: 100%;
.aside {
background-color: #ffffff;
height: inherit;
box-sizing: border-box;
display: flex;
.watch-operate {
width: 30%;
height: 100%;
display: flex;
flex-direction: column;
img {
cursor: pointer;
}
p {
margin: 0;
padding: 0;
font-size: 12px;
}
.watch-operate__top {
height: 25%;
width: 100%;
display: flex;
box-sizing: border-box;
.operate-left, .operate-right {
width: 15%;
text-align: center;
box-sizing: border-box;
}
.operate-center {
display: flex;
width: 70%;
box-sizing: border-box;
flex-wrap: wrap;
.item {
width: 33.33%;
box-sizing: border-box;
padding: 10px;
height: 40px;
}
}
}
.watch-operate__bottom {
height: 100px;
box-sizing: border-box;
width: 100%;
display: flex;
padding-top: 10px;
> div {
width: 25%;
text-align: center;
}
}
.watch-operate__keyboard {
flex: 1;
box-sizing: border-box;
padding-right: 10px;
}
}
.watch-play {
flex: 1;
height: 100%;
.video-player-wrap {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #000000;
.video-player {
width: 100%;
}
}
}
}
.main {
height: inherit;
padding: 15px;
box-sizing: border-box;
}
}
.item-title {
border-left: 10px solid #1890ff;
padding-left: 10px;
margin-top: 20px;
}
.el-divider {
margin: 10px 0 !important;
}
</style>