十三.视频监控
前言
重点介绍海康威视摄像头,页面监控软件在 vue 中的使用
1.index.vue
vue
<template>
<div>
<header-breadcrumb :breadcrumbList="breadcrumbList" />
<main>
<div class="monitor-content" v-if="token">
<centent-grid
:token="token"
v-if="!(bigState.length || fullState.length)"
:camera-data="cameradata"
/>
<centent-carousel
:token="token"
v-if="bigState.length"
:data="bigState"
/>
</div>
</main>
</div>
<full-screen
:token="token"
v-if="token && fullState.length"
:data="fullState"
/>
</template>
<script lang="ts">
import {
defineComponent,
onBeforeMount,
reactive,
toRefs,
computed,
provide,
} from "vue"
import cententGrid from "./grid.vue"
import cententCarousel from "./carousel.vue"
import fullScreen from "./full.vue"
import headerBreadcrumb from "@/components/headerBreadcrumb/headerBreadcrumb.vue"
import { getNow } from "@/utils/index"
import { getYingShiAccessToken } from "@/service/condition/index"
export default defineComponent({
components: { headerBreadcrumb, cententGrid, cententCarousel, fullScreen },
setup() {
const breadcrumbList = [
{
name: "AAA",
path: "/#",
},
{
name: "BBB",
path: "#/condition/monitor",
},
]
const state = reactive({
cententType: "grid",
full: true,
token: "",
data: [
{
name: "监控1",
deviceSerial: "G55261897",
template: "simple",
state: "init",
isSuspending: true,
},
{
name: "监控2",
deviceSerial: "G55261893",
template: "simple",
state: "init",
isSuspending: true,
},
{
name: "监控3",
deviceSerial: "G55261933",
template: "simple",
state: "init",
isSuspending: true,
},
{
name: "监控4",
deviceSerial: "G55261975",
template: "simple",
state: "init",
isSuspending: true,
},
],
})
const bigState = computed(() =>
state.data.filter((item) => item.state === "big")
)
const fullState = computed(() =>
state.data.filter((item) => item.state === "full")
)
const changeData = (deviceSerial: string, key: string, value: string) => {
state.data.forEach((item: any) => {
if (item.deviceSerial === deviceSerial) {
item[key] = value
console.log(item, 11111)
}
})
}
provide("cameraFn", changeData)
provide("cameraData", state.data)
const handleChangeScreen = () => {
state.full = !state.full
}
onBeforeMount(async () => {
let req = {
appKey: "XXXXXX",
appSecret: "XXXXX",
}
let res = await getYingShiAccessToken(req)
if (res.code === "200" && res.data) {
state.token = res.data.accessToken
}
})
return {
bigState,
fullState,
handleChangeScreen,
...toRefs(state),
breadcrumbList,
getNow,
}
},
})
</script>
<style lang="scss" scoped>
main {
padding: 20px;
background: #f2f2f2;
margin-top: 63px;
}
</style>
2.grid.vue
vue
<template>
<div class="centent-grid">
<div
class="timeout-box"
ref="timeoutBox"
:style="`transform: translateY(${-post}px)`"
>
<cameraItem
:token="token"
v-for="(item, key) in cameraData"
:key="key"
:cameraInfo="item"
:id="key"
/>
</div>
</div>
</template>
<script>
import cameraItem from "./camera"
export default {
props: {
token: {
type: String,
},
},
components: {
cameraItem,
},
inject: {
cameraData: {
type: Array,
required: true,
default: () => [],
},
},
data() {
return {
post: 0,
timeOut: null,
timeType: "add",
}
},
mounted() {
this.init()
},
methods: {
init() {
if (this.cameraData.length > 9) {
this.setPostion()
}
},
setPostion() {
this.timeOut = setTimeout(() => {
if (this.timeType === "add") {
const post = (this.post += 304)
if (
document.documentElement.offsetHeight + post >
this.$refs.timeoutBox.scrollHeight
) {
this.post =
this.$refs.timeoutBox.scrollHeight -
(document.documentElement.offsetHeight - 150)
this.timeType = "min"
} else {
this.post = post
}
} else {
const post = (this.post -= 304)
if (post < 0) {
this.post = 0
this.timeType = "add"
} else {
this.post = post
}
}
this.setPostion()
}, 1000 * 5)
},
},
beforeUnmount() {
clearTimeout(this.timeOut)
},
}
</script>
<style lang="scss" scoped>
.centent-grid {
box-sizing: border-box;
clear: both;
min-width: 1220px;
height: calc(100vh - 106px);
.camera {
float: left;
margin-right: 16px;
margin-bottom: 16px;
}
}
.timeout-box {
transition: transform 0.4s ease-in-out;
height: 100%;
}
</style>
3.carousel.vue
vue
<template>
<div class="centent-carousel">
<div class="zoom-win">
<cameraItem
:token="token"
:cameraInfo="dataBig[fullIndex]"
id="full"
style="width: 100%"
/>
</div>
</div>
</template>
<script>
import cameraItem from "./camera"
export default {
props: {
data: {
type: Array,
required: true,
default: () => [],
},
token: {
type: String,
},
},
data() {
return {
dataBig: this.data,
fullIndex: 0,
}
},
components: {
cameraItem,
},
}
</script>
<style lang="scss" scoped>
.centent-carousel {
height: calc(100vh - 106px);
.zoom-win {
margin-bottom: 12px;
height: 100%;
.camera {
height: 100%;
}
}
}
</style>
4.full.vue
vue
<template>
<camera-item :token="token" ref="root" :cameraInfo="fullInfo" />
</template>
<script>
import cameraItem from "./camera"
import { defineComponent, onMounted, computed, ref, inject } from "vue"
export default defineComponent({
props: {
data: {
type: Array,
required: true,
default: () => [],
},
token: {
type: String,
},
},
components: {
cameraItem,
},
setup(props) {
const root = ref(null)
const cameraFn = inject("cameraFn")
let fullInfo = computed(() => {
return props.data[0]
})
onMounted(() => {
document.body.appendChild(root.value.$el)
root.value.$el.requestFullscreen()
window.addEventListener("fullscreenchange", (e) => {
var ch = document.body.clientHeight
var sh = document.body.scrollHeight
if (ch != sh) {
cameraFn(props.data[0].deviceSerial, "state", "init")
}
})
})
// onUnmounted(() => {
// if (root.value.$el && root.value.$el.parentNode) {
// root.value.$el.parentNode.removeChild(root.value.$el)
// document.cancelFullScreen()
// }
// })
return {
fullInfo,
root,
}
},
})
</script>
<style lang="scss" scoped>
.camera-item--full {
position: fixed;
left: 0;
top: 0;
z-index: 1000;
}
</style>
5.camera.vue
vue
<template>
<div class="camera">
<div class="camera-myicon">
<div v-show="cameraInfo.state !== 'init' && !cameraInfo.isSuspending">
<div class="handle">
<span
@click="handleClick(item.direction)"
v-for="item in handleButton"
:key="item.direction"
:style="`transform: rotate(${item.rotate}deg)`"
class="myicon-top myicon-row"
></span>
</div>
<div class="focal-length">
<img
@click="handleClick(8, 200)"
class="add"
:src="require(`@/assets/imgs/add.svg`)"
/>
<img
@click="handleClick(9, 200)"
class="subtraction"
:src="require(`@/assets/imgs/subtraction.svg`)"
/>
</div>
</div>
<div v-show="cameraInfo.isSuspending">
<img src="@/assets/imgs/camera-back.png" alt="" />
</div>
<div :class="!cameraInfo.isSuspending ? 'play' : 'suspend'">
<img
style="width: 110px"
v-show="showIco"
@click="handlePlay"
:src="
require(`@/assets/imgs/${
cameraInfo.isSuspending ? 'play' : 'start'
}.svg`)
"
/>
</div>
<div class="" :id="`video-container${id}`" ref="videoContainer"></div>
</div>
<div
:class="{ 'camera-opt': true, 'background-top': cameraInfo.isSuspending }"
></div>
<div :class="{ zoom: true, 'background-bottom': true }">
<div class="name">{{ cameraInfo.name }}</div>
<span class="myicon-big-screen" @click="handleChangeBig"></span>
<span class="myicon-full-screen" @click="handleFull"></span>
</div>
</div>
</template>
<script>
import EZUIKit from "ezuikit-js"
import { ptzStopControl, ptzDirectionControl } from "@/service/condition/index"
import { defineComponent, onMounted, reactive, toRefs, inject } from "vue"
export default defineComponent({
props: {
id: {
type: String,
},
cameraInfo: {
type: Object,
},
token: {
type: String,
},
},
setup(props, context) {
let player = null
const state = reactive({
showIco: true,
accessToken: "",
handleButton: [
{ direction: 0, rotate: 0 },
{ direction: 6, rotate: 45 },
{ direction: 3, rotate: 90 },
{ direction: 7, rotate: 135 },
{ direction: 1, rotate: 180 },
{ direction: 5, rotate: 225 },
{ direction: 2, rotate: 270 },
{ direction: 4, rotate: 315 },
],
})
const cameraFn = inject("cameraFn")
const handleClick = async (direction, time = 500) => {
let paramsControl = {
accessToken: props.token,
deviceSerial: props.cameraInfo.deviceSerial,
channelNo: "1",
direction,
}
let resControl = await ptzStopControl(paramsControl)
if (resControl.code !== "200") return
let paramsDirection = {
...paramsControl,
speed: 1,
}
let resDirection = await ptzDirectionControl(paramsDirection)
if (resDirection.code !== "200") return
setTimeout(async () => {
let resStop = await ptzStopControl(paramsControl)
if (resStop) {
console.log(resStop, 1111)
}
}, time)
}
const handlePlay = () => {
console.log(props.cameraInfo, 333333)
if (props.cameraInfo.isSuspending) {
player.play()
} else {
player.stop()
}
cameraFn(
props.cameraInfo.deviceSerial,
"isSuspending",
!props.cameraInfo.isSuspending
)
}
const init = () => {
let { offsetWidth, offsetHeight } =
document.querySelector(".monitor-content")
let style = {
width: offsetWidth / 2 - 10,
height: offsetHeight / 2 - 10,
}
if (props.cameraInfo.state === "big") {
style = {
width: offsetWidth,
height: offsetHeight,
}
} else if (props.cameraInfo.state === "full") {
let { offsetWidth, offsetHeight } = document.documentElement
style = {
width: offsetWidth,
height: offsetHeight,
}
}
player = new EZUIKit.EZUIKitPlayer({
autoplay: false,
id: `video-container${props.id}`,
accessToken: props.token,
url: `ezopen://open.ys7.com/${props.cameraInfo.deviceSerial}/1.hd.live`,
template: props.cameraInfo.template, // simple - 极简版;standard-标准版;security - 安防版(预览回放);voice-语音版;
// 视频上方头部控件
// header: ["capturePicture", "save", "zoom"], // 如果templete参数不为simple,该字段将被覆盖
// plugin: ['talk'], // 加载插件,talk-对讲
// 视频下方底部控件
// footer: ["talk", "broadcast", "hd", "fullScreen"], // 如果template参数不为simple,该字段将被覆盖
// audio: 1, // 是否默认开启声音 0 - 关闭 1 - 开启
// openSoundCallBack: data => console.log("开启声音回调", data),
// closeSoundCallBack: data => console.log("关闭声音回调", data),
// startSaveCallBack: data => console.log("开始录像回调", data),
// stopSaveCallBack: data => console.log("录像回调", data),
// capturePictureCallBack: data => console.log("截图成功回调", data),
// fullScreenCallBack: data => console.log("全屏回调", data),
// getOSDTimeCallBack: data => console.log("获取OSDTime回调", data),
...style,
})
setTimeout(() => {
if (!props.cameraInfo.isSuspending) {
player.play()
}
}, 1000)
}
const handleFull = () => {
let targetObj = {
init: "full",
big: "full",
full: "init",
}
cameraFn(
props.cameraInfo.deviceSerial,
"state",
targetObj[props.cameraInfo.state]
)
}
const handleChangeBig = () => {
let targetObj = {
init: "big",
big: "init",
full: "big",
}
cameraFn(
props.cameraInfo.deviceSerial,
"state",
targetObj[props.cameraInfo.state]
)
}
onMounted(() => {
setTimeout(() => {
init()
}, 100)
})
return {
handleClick,
handlePlay,
init,
handleFull,
handleChangeBig,
cameraFn,
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
@import "@/assets/css/_svg.scss";
.camera {
position: relative;
&:nth-child(2n) {
margin-right: 0 !important;
}
}
.camera-myicon {
position: relative;
height: 100%;
.handle {
position: absolute;
height: 202px;
width: 202px;
z-index: 100000;
border-radius: 50%;
background: #000000;
opacity: 0.5;
right: 24px;
bottom: 208px;
&:before {
content: "";
width: 51px;
height: 51px;
background: #ffffff;
opacity: 0.2;
position: absolute;
margin: auto;
left: 0;
right: 0;
bottom: 0;
top: 0;
border-radius: 50%;
}
.myicon-row {
left: 50%;
margin-left: -15px;
width: 30px;
height: 101px;
position: absolute;
transform-origin: 50% 100%;
&:before {
position: absolute;
top: 20px;
font-size: 30px;
color: #fff;
cursor: pointer;
}
}
}
.focal-length {
position: absolute;
width: 200px;
height: 60px;
z-index: 10000;
bottom: 100px;
right: 24px;
img {
width: 60px;
cursor: pointer;
}
.subtraction {
right: 0;
}
}
img {
position: absolute;
z-index: 1000;
width: 100%;
height: 100%;
}
.play {
position: absolute;
width: 110px;
height: 110px;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 10000;
img {
display: none;
}
&:hover img {
display: block;
}
}
.suspend {
position: absolute;
width: 110px;
height: 110px;
margin: auto;
cursor: pointer;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 10000;
}
.big-play {
width: 140px;
}
}
.camera-opt {
position: absolute;
top: 0;
left: 0;
height: 100px;
width: 100%;
z-index: 1000;
}
.background-top {
background: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
}
.zoom {
cursor: pointer;
position: absolute;
height: 100px;
width: 100%;
z-index: 1000;
bottom: 0;
left: 0;
.name {
position: absolute;
bottom: 0;
left: 0;
font-size: 20px;
color: #fff;
padding: 24px;
}
}
.background-bottom {
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 1));
}
.myicon-full-screen {
margin-left: 32px;
}
.myicon-big-screen,
.myicon-full-screen {
font-size: 24px;
position: absolute;
right: 24px;
bottom: 24px;
}
.myicon-big-screen {
right: 72px;
}
</style>