551 lines
16 KiB
Vue
551 lines
16 KiB
Vue
<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">球机当日上线记录   <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>
|