mirror of
https://gitee.com/pan648540858/wvp-GB28181-pro.git
synced 2026-06-22 02:57:49 +08:00
拆分播放器组件和云台控制组件
This commit is contained in:
parent
b3c192a8a9
commit
f5494c0b95
@ -182,7 +182,7 @@ export function wiper([deviceId, channelDeviceId, command]) {
|
||||
})
|
||||
}
|
||||
|
||||
export function ptz([deviceId, channelId, command, horizonSpeed, verticalSpeed, zoomSpeed]) {
|
||||
export function ptz({ deviceId, channelId, command, horizonSpeed, verticalSpeed, zoomSpeed }) {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: `/api/front-end/ptz/${deviceId}/${channelId}`,
|
||||
|
||||
152
web/src/views/common/playerTabs.vue
Normal file
152
web/src/views/common/playerTabs.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="player-tabs-wrapper" ref="playerWrapper">
|
||||
<el-tabs v-if="playerCount > 1" v-model="activePlayer" type="card" :stretch="true" @tab-click="changePlayer">
|
||||
<el-tab-pane label="Jessibuca" name="jessibuca">
|
||||
<div v-if="activePlayer === 'jessibuca'" class="player-video-area">
|
||||
<jessibucaPlayer
|
||||
ref="jessibuca"
|
||||
style="width: 100%; height: 100%;"
|
||||
:video-url="videoUrl"
|
||||
:has-audio="hasAudio"
|
||||
:show-button="showButton"
|
||||
fluent autoplay live
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="WebRTC" name="webRTC">
|
||||
<div v-if="activePlayer === 'webRTC'" class="player-video-area">
|
||||
<rtc-player
|
||||
ref="webRTC"
|
||||
style="width: 100%; height: 100%;"
|
||||
:video-url="videoUrl"
|
||||
:has-audio="hasAudio"
|
||||
fluent autoplay live
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="h265web" name="h265web">
|
||||
<div v-if="activePlayer === 'h265web'" class="player-video-area">
|
||||
<h265web
|
||||
style="width: 100%; height: 100%;"
|
||||
ref="h265web"
|
||||
:video-url="videoUrl"
|
||||
:has-audio="hasAudio"
|
||||
:show-button="showButton"
|
||||
fluent autoplay live
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div v-if="playerCount <= 1" class="player-video-area">
|
||||
<jessibucaPlayer
|
||||
v-if="player.jessibuca"
|
||||
ref="jessibuca"
|
||||
style="width: 100%; height: 100%;"
|
||||
:video-url="videoUrl"
|
||||
:has-audio="hasAudio"
|
||||
:show-button="showButton"
|
||||
fluent autoplay live
|
||||
/>
|
||||
<rtc-player
|
||||
v-if="player.webRTC"
|
||||
ref="webRTC"
|
||||
style="width: 100%; height: 100%;"
|
||||
:video-url="videoUrl"
|
||||
:has-audio="hasAudio"
|
||||
fluent autoplay live
|
||||
/>
|
||||
<h265web
|
||||
v-if="player.h265web"
|
||||
ref="h265web"
|
||||
style="width: 100%; height: 100%;"
|
||||
:video-url="videoUrl"
|
||||
:has-audio="hasAudio"
|
||||
:show-button="showButton"
|
||||
fluent autoplay live
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import jessibucaPlayer from './jessibuca.vue'
|
||||
import rtcPlayer from './rtcPlayer.vue'
|
||||
import h265web from './h265web.vue'
|
||||
|
||||
export default {
|
||||
name: 'PlayerTabs',
|
||||
components: { jessibucaPlayer, rtcPlayer, h265web },
|
||||
props: {
|
||||
videoUrl: { type: String, default: '' },
|
||||
hasAudio: { type: Boolean, default: false },
|
||||
showButton: { type: Boolean, default: true },
|
||||
height: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activePlayer: 'jessibuca',
|
||||
player: { jessibuca: ['ws_flv', 'wss_flv'], webRTC: ['rtc', 'rtcs'], h265web: ['ws_flv', 'wss_flv'] }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
playerCount() {
|
||||
return Object.keys(this.player).length
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.playerCount === 1) {
|
||||
this.activePlayer = Object.keys(this.player)[0]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getUrlByStreamInfo(streamInfo) {
|
||||
const info = streamInfo || this.streamInfo
|
||||
if (!info) return ''
|
||||
const src = info.transcodeStream || info
|
||||
if (location.protocol === 'https:') {
|
||||
return src[this.player[this.activePlayer][1]]
|
||||
}
|
||||
return src[this.player[this.activePlayer][0]]
|
||||
},
|
||||
changePlayer(tab) {
|
||||
this.activePlayer = tab.name
|
||||
this.$emit('player-changed', this.activePlayer)
|
||||
},
|
||||
play(url) {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs[this.activePlayer]) {
|
||||
this.$refs[this.activePlayer].play(url)
|
||||
}
|
||||
})
|
||||
},
|
||||
stop() {
|
||||
if (this.$refs[this.activePlayer]) {
|
||||
this.$refs[this.activePlayer].pause()
|
||||
}
|
||||
},
|
||||
pause() {
|
||||
if (this.$refs[this.activePlayer]) {
|
||||
this.$refs[this.activePlayer].pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.player-tabs-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.player-tabs-wrapper .el-tabs {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.player-tabs-wrapper .el-tabs >>> .el-tabs__header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.player-video-area {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000;
|
||||
}
|
||||
</style>
|
||||
233
web/src/views/common/ptzControls.vue
Normal file
233
web/src/views/common/ptzControls.vue
Normal file
@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div class="ptz-section-inner">
|
||||
<div class="ptz-left">
|
||||
<div class="ptz-dpad">
|
||||
<div class="dpad-ring"></div>
|
||||
<button class="dpad-btn card card-up" @mousedown.prevent="$emit('ptz-move', { direction: 'up', speed: controSpeed })" @mouseup.prevent="$emit('ptz-stop')">▲</button>
|
||||
<button class="dpad-btn card card-right" @mousedown.prevent="$emit('ptz-move', { direction: 'right', speed: controSpeed })" @mouseup.prevent="$emit('ptz-stop')">▶</button>
|
||||
<button class="dpad-btn card card-down" @mousedown.prevent="$emit('ptz-move', { direction: 'down', speed: controSpeed })" @mouseup.prevent="$emit('ptz-stop')">▼</button>
|
||||
<button class="dpad-btn card card-left" @mousedown.prevent="$emit('ptz-move', { direction: 'left', speed: controSpeed })" @mouseup.prevent="$emit('ptz-stop')">◀</button>
|
||||
<button class="dpad-btn diag diag-upright" @mousedown.prevent="$emit('ptz-move', { direction: 'upright', speed: controSpeed })" @mouseup.prevent="$emit('ptz-stop')"><span style="display:inline-block;transform:rotate(45deg)">▲</span></button>
|
||||
<button class="dpad-btn diag diag-downright" @mousedown.prevent="$emit('ptz-move', { direction: 'downright', speed: controSpeed })" @mouseup.prevent="$emit('ptz-stop')"><span style="display:inline-block;transform:rotate(135deg)">▲</span></button>
|
||||
<button class="dpad-btn diag diag-downleft" @mousedown.prevent="$emit('ptz-move', { direction: 'downleft', speed: controSpeed })" @mouseup.prevent="$emit('ptz-stop')"><span style="display:inline-block;transform:rotate(225deg)">▲</span></button>
|
||||
<button class="dpad-btn diag diag-upleft" @mousedown.prevent="$emit('ptz-move', { direction: 'upleft', speed: controSpeed })" @mouseup.prevent="$emit('ptz-stop')"><span style="display:inline-block;transform:rotate(-45deg)">▲</span></button>
|
||||
<button class="dpad-btn dpad-center" title="停止" @click="$emit('ptz-stop')">⏹</button>
|
||||
</div>
|
||||
<div class="ptz-speed-slider">
|
||||
<span class="ptz-speed-label">速度</span>
|
||||
<el-slider v-model="controSpeed" :max="8" :min="1" style="flex: 1" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ptz-right">
|
||||
<div class="ptz-func-group">
|
||||
<div class="ptz-func-row">
|
||||
<div class="ptz-func-btn" title="变倍+" @mousedown.prevent="$emit('ptz-move', { direction: 'zoomin', speed: controSpeed })" @mouseup.prevent="$emit('ptz-stop')">
|
||||
<i class="el-icon-zoom-in" /><span>变倍+</span>
|
||||
</div>
|
||||
<div class="ptz-func-btn" title="变倍-" @mousedown.prevent="$emit('ptz-move', { direction: 'zoomout', speed: controSpeed })" @mouseup.prevent="$emit('ptz-stop')">
|
||||
<i class="el-icon-zoom-out" /><span>变倍-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ptz-func-row">
|
||||
<div class="ptz-func-btn" title="聚焦+" @mousedown.prevent="$emit('focus-move', { command: 'near', speed: controSpeed })" @mouseup.prevent="$emit('focus-stop')">
|
||||
<i class="iconfont icon-bianjiao-fangda" /><span>聚焦+</span>
|
||||
</div>
|
||||
<div class="ptz-func-btn" title="聚焦-" @mousedown.prevent="$emit('focus-move', { command: 'far', speed: controSpeed })" @mouseup.prevent="$emit('focus-stop')">
|
||||
<i class="iconfont icon-bianjiao-suoxiao" /><span>聚焦-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ptz-func-row">
|
||||
<div class="ptz-func-btn" title="光圈+" @mousedown.prevent="$emit('iris-move', { command: 'in', speed: controSpeed })" @mouseup.prevent="$emit('iris-stop')">
|
||||
<i class="iconfont icon-guangquan" /><span>光圈+</span>
|
||||
</div>
|
||||
<div class="ptz-func-btn" title="光圈-" @mousedown.prevent="$emit('iris-move', { command: 'out', speed: controSpeed })" @mouseup.prevent="$emit('iris-stop')">
|
||||
<i class="iconfont icon-guangquan-" /><span>光圈-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ptzPrecise v-if="showPrecise" :device-id="deviceId" :channel-device-id="channelId" @position="$emit('precise-position', $event)" style="margin-top: 6px" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ptzPrecise from './ptzPrecise.vue'
|
||||
|
||||
export default {
|
||||
name: 'PtzControls',
|
||||
components: { ptzPrecise },
|
||||
props: {
|
||||
deviceId: { type: String, default: null },
|
||||
channelId: { type: String, default: null },
|
||||
showPrecise: { type: Boolean, default: true }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
controSpeed: 5
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('mouseup', this.onWindowMouseUp)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('mouseup', this.onWindowMouseUp)
|
||||
},
|
||||
methods: {
|
||||
onWindowMouseUp() {
|
||||
this.$emit('ptz-stop')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ptz-section-inner {
|
||||
display: flex;
|
||||
padding: 8px 4px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.ptz-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.ptz-dpad {
|
||||
position: relative;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
flex: none;
|
||||
}
|
||||
.dpad-ring {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
border-radius: 50%;
|
||||
background: #f5f7fa;
|
||||
pointer-events: none;
|
||||
}
|
||||
.dpad-btn {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
user-select: none;
|
||||
transition: all 0.15s;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.card {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
font-size: 18px;
|
||||
color: #303133;
|
||||
}
|
||||
.card:hover {
|
||||
background: #409EFF;
|
||||
color: #fff;
|
||||
box-shadow: 0 3px 10px rgba(64,158,255,0.4);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.card:active {
|
||||
background: #337ecc;
|
||||
transform: scale(0.92);
|
||||
}
|
||||
.card-up { top: 18px; left: 67px; }
|
||||
.card-right { top: 67px; left: 116px; }
|
||||
.card-down { top: 116px; left: 67px; }
|
||||
.card-left { top: 67px; left: 18px; }
|
||||
.diag {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
color: #a8abb2;
|
||||
}
|
||||
.diag:hover {
|
||||
background: #409EFF;
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 8px rgba(64,158,255,0.35);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.diag:active {
|
||||
background: #337ecc;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
.diag-upright { top: 40px; left: 110px; }
|
||||
.diag-downright { top: 110px; left: 110px; }
|
||||
.diag-downleft { top: 110px; left: 34px; }
|
||||
.diag-upleft { top: 40px; left: 34px; }
|
||||
.dpad-center {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, #eef0f4, #e0e3e8);
|
||||
font-size: 20px;
|
||||
color: #909399;
|
||||
line-height: 1;
|
||||
}
|
||||
.dpad-center:hover {
|
||||
background: #409EFF;
|
||||
color: #fff;
|
||||
box-shadow: 0 3px 10px rgba(64,158,255,0.4);
|
||||
transform: translate(-50%, -50%) scale(1.1);
|
||||
}
|
||||
.dpad-center:active {
|
||||
background: #337ecc;
|
||||
transform: translate(-50%, -50%) scale(0.92);
|
||||
}
|
||||
.ptz-speed-slider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 120px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.ptz-speed-label {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-right: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ptz-right { flex: 1; }
|
||||
.ptz-func-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
.ptz-func-row {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
.ptz-func-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 44px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
user-select: none;
|
||||
font-size: 11px;
|
||||
}
|
||||
.ptz-func-btn:hover {
|
||||
background: #409EFF;
|
||||
color: #fff;
|
||||
}
|
||||
.ptz-func-btn:active {
|
||||
background: #337ecc;
|
||||
}
|
||||
.ptz-func-btn i { font-size: 14px; margin-bottom: 2px; }
|
||||
</style>
|
||||
65
web/src/views/common/ptzPrecise.vue
Normal file
65
web/src/views/common/ptzPrecise.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div id="ptzPrecise">
|
||||
<div class="precise-title">精确控制</div>
|
||||
<div class="precise-row">
|
||||
<span class="precise-label">水平</span>
|
||||
<el-input-number size="mini" v-model="form.pan" :min="0" :max="3600" style="width: 100%" />
|
||||
</div>
|
||||
<div class="precise-row">
|
||||
<span class="precise-label">垂直</span>
|
||||
<el-input-number size="mini" v-model="form.tilt" :min="-1800" :max="1800" style="width: 100%" />
|
||||
</div>
|
||||
<div class="precise-row">
|
||||
<span class="precise-label">变倍</span>
|
||||
<el-input-number size="mini" v-model="form.zoom" :min="1" :max="128" style="width: 100%" />
|
||||
</div>
|
||||
<el-button size="mini" type="primary" style="width: 100%; margin-top: 4px" @click="emitPosition">定位</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PtzPrecise',
|
||||
props: {
|
||||
deviceId: { type: String, default: null },
|
||||
channelDeviceId: { type: String, default: null }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: { pan: 0, tilt: 0, zoom: 1 }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
emitPosition() {
|
||||
this.$emit('position', { ...this.form })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#ptzPrecise {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
background: #fafafa;
|
||||
}
|
||||
.precise-title {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.precise-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.precise-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
width: 32px;
|
||||
text-align: left;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
@ -3,46 +3,22 @@
|
||||
<el-tag
|
||||
v-for="item in presetList"
|
||||
:key="item.presetId"
|
||||
closable
|
||||
size="mini"
|
||||
style="margin-right: 1rem; cursor: pointer; margin-bottom: 0.6rem"
|
||||
@close="delPreset(item)"
|
||||
@click="gotoPreset(item)"
|
||||
>
|
||||
{{ item.presetName?item.presetName:item.presetId }}
|
||||
{{ item.presetName || item.presetId }}
|
||||
</el-tag>
|
||||
<el-input
|
||||
v-if="inputVisible"
|
||||
ref="saveTagInput"
|
||||
v-model="ptzPresetId"
|
||||
min="1"
|
||||
max="255"
|
||||
placeholder="预置位编号"
|
||||
addon-before="预置位编号"
|
||||
addon-after="(1-255)"
|
||||
style="width: 300px; vertical-align: bottom;"
|
||||
size="small"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<el-button @click="addPreset()">保存</el-button>
|
||||
<el-button @click="cancel()">取消</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button v-else size="small" @click="showInput">+ 添加</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'PtzPreset',
|
||||
components: {},
|
||||
props: ['channelDeviceId', 'deviceId'],
|
||||
data() {
|
||||
return {
|
||||
presetList: [],
|
||||
inputVisible: false,
|
||||
ptzPresetId: ''
|
||||
presetList: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@ -53,54 +29,11 @@ export default {
|
||||
this.$store.dispatch('frontEnd/queryPreset', [this.deviceId, this.channelDeviceId])
|
||||
.then(data => {
|
||||
this.presetList = data
|
||||
// 防止出现表格错位
|
||||
this.$nextTick(() => {
|
||||
this.$refs.channelListTable.doLayout()
|
||||
})
|
||||
})
|
||||
},
|
||||
showInput() {
|
||||
this.inputVisible = true
|
||||
this.$nextTick(_ => {
|
||||
this.$refs.saveTagInput.$refs.input.focus()
|
||||
})
|
||||
},
|
||||
addPreset: function() {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
fullscreen: true,
|
||||
text: '正在发送指令',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
this.$store.dispatch('frontEnd/addPreset', [this.deviceId, this.channelDeviceId, this.ptzPresetId])
|
||||
.then(data => {
|
||||
setTimeout(() => {
|
||||
this.inputVisible = false
|
||||
this.ptzPresetId = ''
|
||||
this.getPresetList()
|
||||
}, 1000)
|
||||
}).catch((error) => {
|
||||
loading.close()
|
||||
this.inputVisible = false
|
||||
this.ptzPresetId = ''
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
cancel: function() {
|
||||
this.inputVisible = false
|
||||
this.ptzPresetId = ''
|
||||
},
|
||||
gotoPreset: function(preset) {
|
||||
console.log(preset)
|
||||
this.$store.dispatch('frontEnd/callPreset', [this.deviceId, this.channelDeviceId, preset.presetId])
|
||||
.then(data => {
|
||||
.then(() => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '调用成功',
|
||||
@ -113,40 +46,7 @@ export default {
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
},
|
||||
delPreset: function(preset) {
|
||||
this.$confirm('确定删除此预置位', '提示', {
|
||||
dangerouslyUseHTMLString: true,
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
fullscreen: true,
|
||||
text: '正在发送指令',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
this.$store.dispatch('frontEnd/deletePreset', [this.deviceId, this.channelDeviceId, preset.presetId])
|
||||
.then(data => {
|
||||
setTimeout(() => {
|
||||
this.getPresetList()
|
||||
}, 1000)
|
||||
}).catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div id="channelList" style="height: calc(100vh - 124px);">
|
||||
<div v-if="!editId" style="height: 100%">
|
||||
<div v-if="!editId && !ptzConfigChannelDeviceId" style="height: 100%">
|
||||
<el-form :inline="true" size="mini">
|
||||
<el-form-item style="margin-right: 2rem">
|
||||
<el-page-header content="通道列表" @back="showDevice" />
|
||||
@ -189,6 +189,8 @@
|
||||
设备录像控制-开始</el-dropdown-item>
|
||||
<el-dropdown-item command="stopRecord" :disabled="device == null || device.online === 0">
|
||||
设备录像控制-停止</el-dropdown-item>
|
||||
<el-dropdown-item command="ptzConfig" :disabled="device == null || device.online === 0">
|
||||
云台配置</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
|
||||
</el-dropdown>
|
||||
@ -209,6 +211,7 @@
|
||||
|
||||
<devicePlayer ref="devicePlayer" />
|
||||
<channel-edit v-if="editId" :id="editId" :close-edit="closeEdit" />
|
||||
<ptzConfig v-if="ptzConfigChannelDeviceId" :device-id="ptzConfigDeviceId" :channel-device-id="ptzConfigChannelDeviceId" @close="closePtzConfig" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@ -216,12 +219,14 @@
|
||||
<script>
|
||||
import devicePlayer from '../../dialog/devicePlayer.vue'
|
||||
import Edit from './edit.vue'
|
||||
import ptzConfig from '@/views/device/common/ptzConfig.vue'
|
||||
|
||||
export default {
|
||||
name: 'ChannelList',
|
||||
components: {
|
||||
devicePlayer,
|
||||
ChannelEdit: Edit
|
||||
ChannelEdit: Edit,
|
||||
ptzConfig
|
||||
},
|
||||
props: {
|
||||
defaultPage: {
|
||||
@ -258,6 +263,8 @@ export default {
|
||||
total: 0,
|
||||
beforeUrl: '/device',
|
||||
editId: null,
|
||||
ptzConfigDeviceId: null,
|
||||
ptzConfigChannelDeviceId: null,
|
||||
loadSnap: {},
|
||||
ptzTypes: {
|
||||
0: '未知',
|
||||
@ -278,7 +285,6 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(23222)
|
||||
if (this.deviceId) {
|
||||
this.$store.dispatch('device/queryDeviceOne', this.deviceId)
|
||||
.then(data => {
|
||||
@ -372,6 +378,10 @@ export default {
|
||||
itemData.playLoading = false
|
||||
})
|
||||
},
|
||||
closePtzConfig: function() {
|
||||
this.ptzConfigDeviceId = null
|
||||
this.ptzConfigChannelDeviceId = null
|
||||
},
|
||||
moreClick: function(command, itemData) {
|
||||
if (command === 'records') {
|
||||
this.queryRecords(itemData)
|
||||
@ -381,6 +391,10 @@ export default {
|
||||
this.startRecord(itemData)
|
||||
} else if (command === 'stopRecord') {
|
||||
this.stopRecord(itemData)
|
||||
} else if (command === 'ptzConfig') {
|
||||
console.log(itemData.channelId)
|
||||
this.ptzConfigDeviceId = this.deviceId
|
||||
this.ptzConfigChannelDeviceId = itemData.deviceId
|
||||
}
|
||||
},
|
||||
queryRecords: function(itemData) {
|
||||
|
||||
124
web/src/views/device/common/playerPtzPanel.vue
Normal file
124
web/src/views/device/common/playerPtzPanel.vue
Normal file
@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div class="player-ptz-panel">
|
||||
<div class="player-section">
|
||||
<div class="player-wrapper" :style="{ height: playerHeight }">
|
||||
<playerTabs ref="playerTabs" :video-url="videoUrl" :has-audio="hasAudio" :show-button="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ptz-section">
|
||||
<ptzControls
|
||||
:device-id="deviceId"
|
||||
:channel-id="channelDeviceId"
|
||||
:show-precise="false"
|
||||
@ptz-move="onPtzMove"
|
||||
@ptz-stop="onPtzStop"
|
||||
@focus-move="onFocusMove"
|
||||
@focus-stop="onFocusStop"
|
||||
@iris-move="onIrisMove"
|
||||
@iris-stop="onIrisStop"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import playerTabs from '../../common/playerTabs.vue'
|
||||
import ptzControls from '../../common/ptzControls.vue'
|
||||
|
||||
export default {
|
||||
name: 'PlayerPtzPanel',
|
||||
components: { playerTabs, ptzControls },
|
||||
props: {
|
||||
deviceId: { type: String, default: null },
|
||||
channelDeviceId: { type: String, default: null }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
streamInfo: null,
|
||||
videoUrl: '',
|
||||
hasAudio: false,
|
||||
playerHeight: '36vh'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.startPlay()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopPlay()
|
||||
},
|
||||
methods: {
|
||||
ptzSpeed(speed) {
|
||||
return parseInt(speed * 255 / 8)
|
||||
},
|
||||
onPtzMove(e) {
|
||||
const speedVal = this.ptzSpeed(e.speed)
|
||||
this.$store.dispatch('frontEnd/ptz', [this.deviceId, this.channelDeviceId, e.direction, speedVal, speedVal, speedVal])
|
||||
},
|
||||
onPtzStop() {
|
||||
this.$store.dispatch('frontEnd/ptz', [this.deviceId, this.channelDeviceId, 'stop', 0, 0, 0])
|
||||
},
|
||||
onFocusMove(e) {
|
||||
const speedVal = this.ptzSpeed(e.speed)
|
||||
this.$store.dispatch('frontEnd/focus', [this.deviceId, this.channelDeviceId, e.command, speedVal])
|
||||
},
|
||||
onFocusStop() {
|
||||
this.$store.dispatch('frontEnd/focus', [this.deviceId, this.channelDeviceId, 'stop', 0])
|
||||
},
|
||||
onIrisMove(e) {
|
||||
const speedVal = this.ptzSpeed(e.speed)
|
||||
this.$store.dispatch('frontEnd/iris', [this.deviceId, this.channelDeviceId, e.command, speedVal])
|
||||
},
|
||||
onIrisStop() {
|
||||
this.$store.dispatch('frontEnd/iris', [this.deviceId, this.channelDeviceId, 'stop', 0])
|
||||
},
|
||||
startPlay() {
|
||||
this.$store.dispatch('play/play', [this.deviceId, this.channelDeviceId])
|
||||
.then(data => {
|
||||
this.streamInfo = data
|
||||
this.hasAudio = data.hasAudio
|
||||
this.videoUrl = this.getUrlByStreamInfo(data)
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.playerTabs) {
|
||||
this.$refs.playerTabs.play(this.videoUrl)
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message({ showClose: true, message: e || '播放失败', type: 'error' })
|
||||
})
|
||||
},
|
||||
stopPlay() {
|
||||
this.$store.dispatch('play/stop', { deviceId: this.deviceId, channelId: this.channelDeviceId })
|
||||
.catch(() => {})
|
||||
},
|
||||
getUrlByStreamInfo(streamInfo) {
|
||||
const info = streamInfo || this.streamInfo
|
||||
if (!info) return ''
|
||||
const src = info.transcodeStream || info
|
||||
if (location.protocol === 'https:') {
|
||||
return src['wss_flv']
|
||||
}
|
||||
return src['ws_flv']
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.player-ptz-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.player-section {
|
||||
flex: 1;
|
||||
}
|
||||
.ptz-section {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
}
|
||||
.player-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
112
web/src/views/device/common/ptzConfig.vue
Normal file
112
web/src/views/device/common/ptzConfig.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div id="dhPtzConfigPage">
|
||||
<el-page-header content="云台设置" @back="$emit('close')" />
|
||||
<div class="ptz-config-body">
|
||||
<div class="config-sidebar">
|
||||
<el-menu :default-active="activeTab" @select="handleMenuSelect">
|
||||
<el-menu-item index="preset">
|
||||
<i class="el-icon-map-location" style="margin-right: 6px" />
|
||||
<span>预置点</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="cruise">
|
||||
<i class="el-icon-s-order" style="margin-right: 6px" />
|
||||
<span>巡航组</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="scan">
|
||||
<i class="el-icon-s-grid" style="margin-right: 6px" />
|
||||
<span>自动扫描</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="wiper">
|
||||
<i class="el-icon-umbrella" style="margin-right: 6px" />
|
||||
<span>雨刷</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="switch">
|
||||
<i class="el-icon-s-tools" style="margin-right: 6px" />
|
||||
<span>辅助开关</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
<div class="content-wrapper">
|
||||
<div class="player-panel">
|
||||
<playerPtzPanel :device-id="deviceId" :channel-device-id="channelDeviceId" />
|
||||
</div>
|
||||
<div class="tab-panel">
|
||||
<ptzPresetConfig v-if="activeTab === 'preset'" :device-id="deviceId" :channel-device-id="channelDeviceId" />
|
||||
<ptzCruiseConfig v-if="activeTab === 'cruise'" :device-id="deviceId" :channel-device-id="channelDeviceId" />
|
||||
<ptzScanConfig v-if="activeTab === 'scan'" :device-id="deviceId" :channel-device-id="channelDeviceId" />
|
||||
<ptzWiperConfig v-if="activeTab === 'wiper'" :device-id="deviceId" :channel-device-id="channelDeviceId" />
|
||||
<ptzSwitchConfig v-if="activeTab === 'switch'" :device-id="deviceId" :channel-device-id="channelDeviceId" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import playerPtzPanel from './playerPtzPanel.vue'
|
||||
import ptzPresetConfig from './ptzPresetConfig.vue'
|
||||
import ptzCruiseConfig from './ptzCruiseConfig.vue'
|
||||
import ptzScanConfig from './ptzScanConfig.vue'
|
||||
import ptzWiperConfig from './ptzWiperConfig.vue'
|
||||
import ptzSwitchConfig from './ptzSwitchConfig.vue'
|
||||
|
||||
export default {
|
||||
name: 'PtzConfigPage',
|
||||
components: { playerPtzPanel, ptzPresetConfig, ptzCruiseConfig, ptzScanConfig, ptzWiperConfig, ptzSwitchConfig },
|
||||
props: {
|
||||
deviceId: { type: String, default: null },
|
||||
channelDeviceId: { type: String, default: null }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'preset'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleMenuSelect(index) {
|
||||
this.activeTab = index
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#dhPtzConfigPage {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.ptz-config-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
padding-top: 16px;
|
||||
}
|
||||
.config-sidebar {
|
||||
width: 140px;
|
||||
flex: none;
|
||||
border-right: 1px solid #e6e6e6;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.config-sidebar .el-menu {
|
||||
border-right: none;
|
||||
}
|
||||
.content-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
.player-panel {
|
||||
width: 600px;
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid #e6e6e6;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.tab-panel {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 0 12px;
|
||||
}
|
||||
</style>
|
||||
200
web/src/views/device/common/ptzCruiseConfig.vue
Normal file
200
web/src/views/device/common/ptzCruiseConfig.vue
Normal file
@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div style="height: 100%; display: flex; flex-direction: column;">
|
||||
<el-form size="small" inline style="margin-bottom: 12px; padding: 16px 8px; border: 1px solid #e6e6e6; border-radius: 4px;">
|
||||
<el-form-item label="巡航组号" style="margin-bottom: 0;">
|
||||
<el-input-number v-model="cruiseId" :min="1" :max="255" controls-position="right" style="width: 140px" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div v-if="presetPoints.length > 0" style="margin-bottom: 8px;">
|
||||
<el-tag
|
||||
v-for="(item, index) in presetPoints"
|
||||
:key="index"
|
||||
closable
|
||||
size="small"
|
||||
style="margin-right: 8px; margin-bottom: 4px;"
|
||||
@close="removePoint(item, index)"
|
||||
>
|
||||
{{ item.presetName || ('预置点' + item.presetId) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div v-else style="color: #909399; font-size: 12px; margin-bottom: 8px;">暂无巡航点</div>
|
||||
<el-form v-if="showAddPoint" size="mini" inline style="margin-bottom: 8px;">
|
||||
<el-form-item style="margin-bottom: 0;">
|
||||
<el-select v-model="selectedPreset" placeholder="选择预置点" style="width: 160px">
|
||||
<el-option
|
||||
v-for="p in allPresetList"
|
||||
:key="p.presetId"
|
||||
:label="p.presetName || ('预置点' + p.presetId)"
|
||||
:value="p"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-bottom: 0;">
|
||||
<el-button type="primary" @click="addPoint">确定</el-button>
|
||||
<el-button @click="showAddPoint = false">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button v-else size="small" style="margin-bottom: 8px;" @click="showAddPoint = true">添加巡航点</el-button>
|
||||
<el-form v-if="showSpeedInput" size="mini" inline style="margin-bottom: 8px;">
|
||||
<el-form-item label="速度" style="margin-bottom: 0;">
|
||||
<el-input-number v-model="cruiseSpeed" :min="1" :max="255" controls-position="right" style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-bottom: 0;">
|
||||
<el-button type="primary" @click="setSpeed">确定</el-button>
|
||||
<el-button @click="cancelSpeed">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button v-else size="small" style="margin-bottom: 8px;" @click="openSpeed">设置巡航速度</el-button>
|
||||
<el-form v-if="showTimeInput" size="mini" inline style="margin-bottom: 8px;">
|
||||
<el-form-item label="停留时间(秒)" style="margin-bottom: 0;">
|
||||
<el-input-number v-model="cruiseTime" :min="1" :max="300" controls-position="right" style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-bottom: 0;">
|
||||
<el-button type="primary" @click="setTime">确定</el-button>
|
||||
<el-button @click="cancelTime">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button v-else size="small" style="margin-bottom: 8px;" @click="openTime">设置巡航时间</el-button>
|
||||
<div style="margin-top: 8px;">
|
||||
<el-button size="small" type="primary" :loading="starting" :disabled="starting" @click="startCruise">开始巡航</el-button>
|
||||
<el-button size="small" :loading="stopping" :disabled="stopping" @click="stopCruise">停止巡航</el-button>
|
||||
<el-button size="small" type="danger" :loading="deleting" :disabled="deleting" @click="deleteCruise">删除巡航</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PtzCruiseConfig',
|
||||
props: {
|
||||
deviceId: { type: String, default: null },
|
||||
channelDeviceId: { type: String, default: null }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cruiseId: 1,
|
||||
presetPoints: [],
|
||||
allPresetList: [],
|
||||
selectedPreset: null,
|
||||
showAddPoint: false,
|
||||
showSpeedInput: false,
|
||||
showTimeInput: false,
|
||||
cruiseSpeed: 5,
|
||||
cruiseTime: 15,
|
||||
starting: false,
|
||||
stopping: false,
|
||||
deleting: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadPresets()
|
||||
},
|
||||
methods: {
|
||||
loadPresets() {
|
||||
this.$store.dispatch('frontEnd/queryPreset', [this.deviceId, this.channelDeviceId])
|
||||
.then(data => {
|
||||
this.allPresetList = data || []
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('[巡航] 加载预置点列表失败', error)
|
||||
})
|
||||
},
|
||||
addPoint() {
|
||||
if (!this.selectedPreset) {
|
||||
this.$message({ showClose: true, message: '请选择预置点', type: 'warning' })
|
||||
return
|
||||
}
|
||||
this.$store.dispatch('frontEnd/addPointForCruise', [this.deviceId, this.channelDeviceId, this.cruiseId, this.selectedPreset.presetId])
|
||||
.then(() => {
|
||||
this.presetPoints.push(this.selectedPreset)
|
||||
this.selectedPreset = null
|
||||
this.showAddPoint = false
|
||||
this.$message({ showClose: true, message: '添加成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
})
|
||||
},
|
||||
removePoint(preset, index) {
|
||||
this.$store.dispatch('frontEnd/deletePointForCruise', [this.deviceId, this.channelDeviceId, this.cruiseId, preset.presetId])
|
||||
.then(() => {
|
||||
this.presetPoints.splice(index, 1)
|
||||
this.$message({ showClose: true, message: '删除成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
})
|
||||
},
|
||||
openSpeed() {
|
||||
this.showSpeedInput = true
|
||||
},
|
||||
cancelSpeed() {
|
||||
this.showSpeedInput = false
|
||||
this.cruiseSpeed = 5
|
||||
},
|
||||
setSpeed() {
|
||||
this.$store.dispatch('frontEnd/setCruiseSpeed', [this.deviceId, this.channelDeviceId, this.cruiseId, this.cruiseSpeed])
|
||||
.then(() => {
|
||||
this.showSpeedInput = false
|
||||
this.$message({ showClose: true, message: '速度设置成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
})
|
||||
},
|
||||
openTime() {
|
||||
this.showTimeInput = true
|
||||
},
|
||||
cancelTime() {
|
||||
this.showTimeInput = false
|
||||
this.cruiseTime = 15
|
||||
},
|
||||
setTime() {
|
||||
this.$store.dispatch('frontEnd/setCruiseTime', [this.deviceId, this.channelDeviceId, this.cruiseId, this.cruiseTime])
|
||||
.then(() => {
|
||||
this.showTimeInput = false
|
||||
this.$message({ showClose: true, message: '时间设置成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
})
|
||||
},
|
||||
startCruise() {
|
||||
this.starting = true
|
||||
this.$store.dispatch('frontEnd/startCruise', [this.deviceId, this.channelDeviceId, this.cruiseId])
|
||||
.then(() => {
|
||||
this.$message({ showClose: true, message: '巡航启动成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
}).finally(() => {
|
||||
this.starting = false
|
||||
})
|
||||
},
|
||||
stopCruise() {
|
||||
this.stopping = true
|
||||
this.$store.dispatch('frontEnd/stopCruise', [this.deviceId, this.channelDeviceId, this.cruiseId])
|
||||
.then(() => {
|
||||
this.$message({ showClose: true, message: '巡航停止成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
}).finally(() => {
|
||||
this.stopping = false
|
||||
})
|
||||
},
|
||||
deleteCruise() {
|
||||
this.$confirm('确定删除此巡航组所有点?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.deleting = true
|
||||
this.$store.dispatch('frontEnd/deletePointForCruise', [this.deviceId, this.channelDeviceId, this.cruiseId, 0])
|
||||
.then(() => {
|
||||
this.presetPoints = []
|
||||
this.$message({ showClose: true, message: '删除成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
}).finally(() => {
|
||||
this.deleting = false
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
156
web/src/views/device/common/ptzPresetConfig.vue
Normal file
156
web/src/views/device/common/ptzPresetConfig.vue
Normal file
@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div style="height: 100%; display: flex; flex-direction: column;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
|
||||
<div>
|
||||
<el-button type="primary" :disabled="showAddForm" @click="openAdd">添加预置点</el-button>
|
||||
<el-button :loading="clearing" :disabled="clearing" @click="clearAll">清空</el-button>
|
||||
</div>
|
||||
<el-button icon="el-icon-refresh-right" circle @click="getPresetList" />
|
||||
</div>
|
||||
<el-form v-if="showAddForm" size="small" inline style="margin-bottom: 6px; padding: 16px 8px; border: 1px solid #e6e6e6; border-radius: 4px; display: flex; align-items: center;">
|
||||
<el-form-item label="序号" style="margin-bottom: 0; margin-right: 2rem">
|
||||
<el-input-number v-model="addPresetId" :min="1" :max="255" controls-position="right" style="width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-bottom: 0;">
|
||||
<el-button type="primary" :loading="submitting" :disabled="submitting" @click="confirmAdd">确定</el-button>
|
||||
<el-button @click="cancelAdd">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
:data="presetList"
|
||||
border
|
||||
stripe
|
||||
max-height="100%"
|
||||
style="flex: 1"
|
||||
>
|
||||
<el-table-column prop="presetId" label="序号" align="center" />
|
||||
<el-table-column label="名称">
|
||||
<template v-slot="{ row }">
|
||||
<span>{{ row.presetName || ('预置点' + row.presetId) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="140" align="center">
|
||||
<template v-slot="{ row }">
|
||||
<el-button size="mini" type="text" @click="callPreset(row)">调用</el-button>
|
||||
<el-button size="mini" type="text" style="color: #F56C6C" :loading="deletingId === row.presetId" :disabled="deletingId !== null" @click="delPreset(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PtzPresetConfig',
|
||||
props: {
|
||||
deviceId: { type: String, default: null },
|
||||
channelDeviceId: { type: String, default: null }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
presetList: [],
|
||||
showAddForm: false,
|
||||
addPresetId: 1,
|
||||
submitting: false,
|
||||
clearing: false,
|
||||
deletingId: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getPresetList()
|
||||
},
|
||||
methods: {
|
||||
getPresetList() {
|
||||
this.$store.dispatch('frontEnd/queryPreset', [this.deviceId, this.channelDeviceId])
|
||||
.then(data => {
|
||||
this.presetList = data || []
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
},
|
||||
openAdd() {
|
||||
this.addPresetId = this.getNextAvailableId()
|
||||
this.showAddForm = true
|
||||
},
|
||||
cancelAdd() {
|
||||
this.showAddForm = false
|
||||
this.addPresetId = 1
|
||||
},
|
||||
confirmAdd() {
|
||||
const exists = this.presetList.some(p => p.presetId === this.addPresetId)
|
||||
if (exists) {
|
||||
this.$message({ showClose: true, message: '序号 ' + this.addPresetId + ' 已存在', type: 'warning' })
|
||||
return
|
||||
}
|
||||
this.submitting = true
|
||||
this.$store.dispatch('frontEnd/addPreset', [this.deviceId, this.channelDeviceId, this.addPresetId])
|
||||
.then(() => {
|
||||
this.showAddForm = false
|
||||
setTimeout(() => {
|
||||
this.getPresetList()
|
||||
}, 600)
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
}).finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
},
|
||||
callPreset(preset) {
|
||||
this.$store.dispatch('frontEnd/callPreset', [this.deviceId, this.channelDeviceId, preset.presetId])
|
||||
.then(() => {
|
||||
this.$message({ showClose: true, message: '调用成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
})
|
||||
},
|
||||
delPreset(preset) {
|
||||
this.$confirm('确定删除此预置位', '提示', {
|
||||
dangerouslyUseHTMLString: true,
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.deletingId = preset.presetId
|
||||
this.$store.dispatch('frontEnd/deletePreset', [this.deviceId, this.channelDeviceId, preset.presetId])
|
||||
.then(() => {
|
||||
this.getPresetList()
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
}).finally(() => {
|
||||
this.deletingId = null
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
clearAll() {
|
||||
if (this.presetList.length === 0) return
|
||||
this.$confirm('确定清空所有预置点?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.clearing = true
|
||||
const promises = this.presetList.map(p =>
|
||||
this.$store.dispatch('frontEnd/deletePreset', [this.deviceId, this.channelDeviceId, p.presetId])
|
||||
)
|
||||
Promise.all(promises).then(() => {
|
||||
this.presetList = []
|
||||
this.$message({ showClose: true, message: '清空成功', type: 'success' })
|
||||
}).catch(() => {
|
||||
this.$message({ showClose: true, message: '清空失败', type: 'error' })
|
||||
}).finally(() => {
|
||||
this.clearing = false
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
getNextAvailableId() {
|
||||
if (!this.presetList || this.presetList.length === 0) return 1
|
||||
const used = this.presetList.map(p => p.presetId).sort((a, b) => a - b)
|
||||
for (let i = 0; i < used.length - 1; i++) {
|
||||
if (used[i + 1] - used[i] > 1) return used[i] + 1
|
||||
}
|
||||
return used[used.length - 1] + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
107
web/src/views/device/common/ptzScanConfig.vue
Normal file
107
web/src/views/device/common/ptzScanConfig.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div style="height: 100%; display: flex; flex-direction: column;">
|
||||
<el-form size="small" inline style="margin-bottom: 12px; padding: 16px 8px; border: 1px solid #e6e6e6; border-radius: 4px;">
|
||||
<el-form-item label="扫描组号" style="margin-bottom: 0;">
|
||||
<el-input-number v-model="scanId" :min="1" :max="255" controls-position="right" style="width: 140px" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div style="margin-bottom: 8px;">
|
||||
<el-button size="small" :loading="leftLoading" :disabled="leftLoading" @click="setLeft">设置左边界</el-button>
|
||||
<el-button size="small" :loading="rightLoading" :disabled="rightLoading" @click="setRight">设置右边界</el-button>
|
||||
</div>
|
||||
<el-form v-if="showSpeedInput" size="mini" inline style="margin-bottom: 8px;">
|
||||
<el-form-item label="扫描速度" style="margin-bottom: 0;">
|
||||
<el-input-number v-model="scanSpeed" :min="1" :max="255" controls-position="right" style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-bottom: 0;">
|
||||
<el-button type="primary" @click="setSpeed">确定</el-button>
|
||||
<el-button @click="cancelSpeed">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button v-else size="small" style="margin-bottom: 8px;" @click="showSpeedInput = true">设置扫描速度</el-button>
|
||||
<div style="margin-top: 8px;">
|
||||
<el-button size="small" type="primary" :loading="starting" :disabled="starting" @click="startScan">开始自动扫描</el-button>
|
||||
<el-button size="small" :loading="stopping" :disabled="stopping" @click="stopScan">停止自动扫描</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PtzScanConfig',
|
||||
props: {
|
||||
deviceId: { type: String, default: null },
|
||||
channelDeviceId: { type: String, default: null }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
scanId: 1,
|
||||
showSpeedInput: false,
|
||||
scanSpeed: 5,
|
||||
leftLoading: false,
|
||||
rightLoading: false,
|
||||
starting: false,
|
||||
stopping: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setLeft() {
|
||||
this.leftLoading = true
|
||||
this.$store.dispatch('frontEnd/setLeftForScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||
.then(() => {
|
||||
this.$message({ showClose: true, message: '左边界设置成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
}).finally(() => {
|
||||
this.leftLoading = false
|
||||
})
|
||||
},
|
||||
setRight() {
|
||||
this.rightLoading = true
|
||||
this.$store.dispatch('frontEnd/setRightForScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||
.then(() => {
|
||||
this.$message({ showClose: true, message: '右边界设置成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
}).finally(() => {
|
||||
this.rightLoading = false
|
||||
})
|
||||
},
|
||||
setSpeed() {
|
||||
this.$store.dispatch('frontEnd/setSpeedForScan', [this.deviceId, this.channelDeviceId, this.scanId, this.scanSpeed])
|
||||
.then(() => {
|
||||
this.showSpeedInput = false
|
||||
this.$message({ showClose: true, message: '速度设置成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
})
|
||||
},
|
||||
cancelSpeed() {
|
||||
this.showSpeedInput = false
|
||||
this.scanSpeed = 5
|
||||
},
|
||||
startScan() {
|
||||
this.starting = true
|
||||
this.$store.dispatch('frontEnd/startScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||
.then(() => {
|
||||
this.$message({ showClose: true, message: '扫描启动成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
}).finally(() => {
|
||||
this.starting = false
|
||||
})
|
||||
},
|
||||
stopScan() {
|
||||
this.stopping = true
|
||||
this.$store.dispatch('frontEnd/stopScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||
.then(() => {
|
||||
this.$message({ showClose: true, message: '扫描停止成功', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
}).finally(() => {
|
||||
this.stopping = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
42
web/src/views/device/common/ptzSwitchConfig.vue
Normal file
42
web/src/views/device/common/ptzSwitchConfig.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form size="mini" inline>
|
||||
<el-form-item label="开关编号" style="margin-bottom: 0;">
|
||||
<el-input-number v-model="switchId" :min="1" :max="255" controls-position="right" style="width: 140px" />
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-bottom: 0;">
|
||||
<el-button size="small" :loading="loading" :disabled="loading" @click="control('on')">开启</el-button>
|
||||
<el-button size="small" :loading="loading" :disabled="loading" @click="control('off')">关闭</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PtzSwitchConfig',
|
||||
props: {
|
||||
deviceId: { type: String, default: null },
|
||||
channelDeviceId: { type: String, default: null }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
switchId: 1,
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
control(command) {
|
||||
this.loading = true
|
||||
this.$store.dispatch('frontEnd/auxiliary', [this.deviceId, this.channelDeviceId, command, this.switchId])
|
||||
.then(() => {
|
||||
this.$message({ showClose: true, message: command === 'on' ? '开关已开启' : '开关已关闭', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
34
web/src/views/device/common/ptzWiperConfig.vue
Normal file
34
web/src/views/device/common/ptzWiperConfig.vue
Normal file
@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-button size="small" :loading="loading" :disabled="loading" @click="control('on')">开启</el-button>
|
||||
<el-button size="small" :loading="loading" :disabled="loading" @click="control('off')">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PtzWiperConfig',
|
||||
props: {
|
||||
deviceId: { type: String, default: null },
|
||||
channelDeviceId: { type: String, default: null }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
control(command) {
|
||||
this.loading = true
|
||||
this.$store.dispatch('frontEnd/wiper', [this.deviceId, this.channelDeviceId, command])
|
||||
.then(() => {
|
||||
this.$message({ showClose: true, message: command === 'on' ? '雨刷已开启' : '雨刷已关闭', type: 'success' })
|
||||
}).catch(error => {
|
||||
this.$message({ showClose: true, message: error, type: 'error' })
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user