增加新版本web页面
This commit is contained in:
364
web/src/views/common/CommonChannelEdit.vue
Normal file
364
web/src/views/common/CommonChannelEdit.vue
Normal file
@@ -0,0 +1,364 @@
|
||||
<template>
|
||||
<div id="CommonChannelEdit" v-loading="loading" style="width: 100%">
|
||||
<el-form ref="passwordForm" status-icon label-width="160px" class="channel-form">
|
||||
<div class="form-box">
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="form.gbName" placeholder="请输入通道名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="编码">
|
||||
<el-input v-model="form.gbDeviceId" placeholder="请输入通道编码">
|
||||
<template v-slot:append>
|
||||
<el-button @click="buildDeviceIdCode(form.gbDeviceId)">生成</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备厂商">
|
||||
<el-input v-model="form.gbManufacturer" placeholder="请输入设备厂商" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备型号">
|
||||
<el-input v-model="form.gbModel" placeholder="请输入设备型号" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="行政区域">
|
||||
<el-input v-model="form.gbCivilCode" placeholder="请输入行政区域">
|
||||
<template v-slot:append>
|
||||
<el-button @click="chooseCivilCode()">选择</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="安装地址">
|
||||
<el-input v-model="form.gbAddress" placeholder="请输入安装地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="子设备">
|
||||
<el-select v-model="form.gbParental" style="width: 100%" placeholder="请选择是否有子设备">
|
||||
<el-option label="有" :value="1" />
|
||||
<el-option label="无" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="父节点编码">
|
||||
<el-input v-model="form.gbParentId" placeholder="请输入父节点编码或选择所属虚拟组织">
|
||||
<template v-slot:append>
|
||||
<el-button @click="chooseGroup()">选择</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备状态">
|
||||
<el-select v-model="form.gbStatus" style="width: 100%" placeholder="请选择设备状态">
|
||||
<el-option label="在线" value="ON" />
|
||||
<el-option label="离线" value="OFF" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="经度">
|
||||
<el-input v-model="form.gbLongitude" placeholder="请输入经度" />
|
||||
</el-form-item>
|
||||
<el-form-item label="纬度">
|
||||
<el-input v-model="form.gbLatitude" placeholder="请输入纬度" />
|
||||
</el-form-item>
|
||||
<el-form-item label="云台类型">
|
||||
<el-select v-model="form.gbPtzType" style="width: 100%" placeholder="请选择云台类型">
|
||||
<el-option label="球机" :value="1" />
|
||||
<el-option label="半球" :value="2" />
|
||||
<el-option label="固定枪机" :value="3" />
|
||||
<el-option label="遥控枪机" :value="4" />
|
||||
<el-option label="遥控半球" :value="5" />
|
||||
<el-option label="多目设备的全景/拼接通道" :value="6" />
|
||||
<el-option label="多目设备的分割通道" :value="7" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div>
|
||||
<el-form-item label="警区">
|
||||
<el-input v-model="form.gbBlock" placeholder="请输入警区" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备归属">
|
||||
<el-input v-model="form.gbOwner" placeholder="请输入设备归属" />
|
||||
</el-form-item>
|
||||
<el-form-item label="信令安全模式">
|
||||
<el-select v-model="form.gbSafetyWay" style="width: 100%" placeholder="请选择信令安全模式">
|
||||
<el-option label="不采用" :value="0" />
|
||||
<el-option label="S/MIME签名" :value="2" />
|
||||
<el-option label="S/MIME加密签名同时采用" :value="3" />
|
||||
<el-option label="数字摘要" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="注册方式">
|
||||
<el-select v-model="form.gbRegisterWay" style="width: 100%" placeholder="请选择注册方式">
|
||||
<el-option label="IETFRFC3261标准" :value="1" />
|
||||
<el-option label="基于口令的双向认证" :value="2" />
|
||||
<el-option label="基于数字证书的双向认证注册" :value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="证书序列号">
|
||||
<el-input v-model="form.gbCertNum" type="number" placeholder="请输入证书序列号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="证书有效标识">
|
||||
<el-select v-model="form.gbCertifiable" style="width: 100%" placeholder="请选择证书有效标识">
|
||||
<el-option label="有效" :value="1" />
|
||||
<el-option label="无效" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="无效原因码">
|
||||
<el-input v-model="form.gbCertNum" type="errCode" placeholder="请输入无效原因码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="证书终止有效期">
|
||||
<el-date-picker
|
||||
v-model="form.gbEndTime"
|
||||
type="datetime"
|
||||
placeholder="选择日期时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="保密属性">
|
||||
<el-select v-model="form.gbSecrecy" style="width: 100%" placeholder="请选择保密属性">
|
||||
<el-option label="不涉密" :value="0" />
|
||||
<el-option label="涉密" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="IP地址">
|
||||
<el-input v-model="form.gbIpAddress" placeholder="请输入IP地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="端口">
|
||||
<el-input v-model="form.gbPort" type="number" placeholder="请输入端口" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备口令">
|
||||
<el-input v-model="form.gbPassword" placeholder="请输入设备口令" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div>
|
||||
<el-form-item label="业务分组编号">
|
||||
<el-input v-model="form.gbBusinessGroupId" placeholder="请输入业务分组编号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="位置类型">
|
||||
<el-select v-model="form.gbPositionType" style="width: 100%" placeholder="请选择位置类型">
|
||||
<el-option label="省际检查站" :value="1" />
|
||||
<el-option label="党政机关" :value="2" />
|
||||
<el-option label="车站码头" :value="3" />
|
||||
<el-option label="中心广场" :value="4" />
|
||||
<el-option label="体育场馆" :value="5" />
|
||||
<el-option label="商业中心" :value="6" />
|
||||
<el-option label="宗教场所" :value="7" />
|
||||
<el-option label="校园周边" :value="8" />
|
||||
<el-option label="治安复杂区域" :value="9" />
|
||||
<el-option label="交通干线" :value="10" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="室外/室内">
|
||||
<el-select v-model="form.gbRoomType" style="width: 100%" placeholder="请选择位置类型">
|
||||
<el-option label="室外" :value="1" />
|
||||
<el-option label="室内" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用途">
|
||||
<el-select v-model="form.gbUseType" style="width: 100%" placeholder="请选择位置类型">
|
||||
<el-option label="治安" :value="1" />
|
||||
<el-option label="交通" :value="2" />
|
||||
<el-option label="重点" :value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="补光">
|
||||
<el-select v-model="form.gbSupplyLightType" style="width: 100%" placeholder="请选择位置类型">
|
||||
<el-option label="无补光" :value="1" />
|
||||
<el-option label="红外补光" :value="2" />
|
||||
<el-option label="白光补光" :value="3" />
|
||||
<el-option label="激光补光" :value="4" />
|
||||
<el-option label="其他" :value="9" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="监视方位">
|
||||
<el-select v-model="form.gbDirectionType" style="width: 100%" placeholder="请选择位置类型">
|
||||
<el-option label="东(西向东)" :value="1" />
|
||||
<el-option label="西(东向西)" :value="2" />
|
||||
<el-option label="南(北向南)" :value="3" />
|
||||
<el-option label="北(南向北)" :value="4" />
|
||||
<el-option label="东南(西北到东南)" :value="5" />
|
||||
<el-option label="东北(西南到东北)" :value="6" />
|
||||
<el-option label="西南(东北到西南)" :value="7" />
|
||||
<el-option label="西北(东南到西北)" :value="8" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分辨率">
|
||||
<el-input v-model="form.gbResolution" placeholder="请输入分辨率" />
|
||||
</el-form-item>
|
||||
<el-form-item label="下载倍速">
|
||||
<el-select v-model="form.gbDownloadSpeedArray" multiple style="width: 100%" placeholder="请选择位置类型">
|
||||
<el-option label="1倍速" value="1" />
|
||||
<el-option label="2倍速" value="2" />
|
||||
<el-option label="4倍速" value="4" />
|
||||
<el-option label="8倍速" value="8" />
|
||||
<el-option label="16倍速" value="16" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="空域编码能力">
|
||||
<el-select v-model="form.gbSvcSpaceSupportMod" style="width: 100%" placeholder="请选择空域编码能力">
|
||||
<el-option label="1级增强" value="1" />
|
||||
<el-option label="2级增强" value="2" />
|
||||
<el-option label="3级增强" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="时域编码能力">
|
||||
<el-select v-model="form.gbSvcTimeSupportMode" style="width: 100%" placeholder="请选择空域编码能力">
|
||||
<el-option label="1级增强" value="1" />
|
||||
<el-option label="2级增强" value="2" />
|
||||
<el-option label="3级增强" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div style="float: right;">
|
||||
<el-button type="primary" @click="onSubmit">保存</el-button>
|
||||
<el-button v-if="cancel" @click="cancelSubmit">取消</el-button>
|
||||
<el-button v-if="form.dataType === 1" @click="reset">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</el-form>
|
||||
<channelCode ref="channelCode" />
|
||||
<chooseCivilCode ref="chooseCivilCode" />
|
||||
<chooseGroup ref="chooseGroup" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import channelCode from './../dialog/channelCode'
|
||||
import ChooseCivilCode from '../dialog/chooseCivilCode.vue'
|
||||
import ChooseGroup from '../dialog/chooseGroup.vue'
|
||||
|
||||
export default {
|
||||
name: 'CommonChannelEdit',
|
||||
components: {
|
||||
ChooseCivilCode,
|
||||
ChooseGroup,
|
||||
channelCode
|
||||
},
|
||||
props: ['id', 'dataForm', 'saveSuccess', 'cancel'],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
form: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 获取完整信息
|
||||
if (this.id) {
|
||||
this.getCommonChannel()
|
||||
} else {
|
||||
if (!this.dataForm.gbDeviceId) {
|
||||
this.dataForm.gbDeviceId = ''
|
||||
}
|
||||
console.log(this.dataForm)
|
||||
this.form = this.dataForm
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit: function() {
|
||||
this.loading = true
|
||||
if (this.form.gbDownloadSpeedArray) {
|
||||
this.form.gbDownloadSpeed = this.form.gbDownloadSpeedArray.join('/')
|
||||
}
|
||||
if (this.form.gbId) {
|
||||
this.$store.dispatch('commonChanel/update', this.form)
|
||||
.then(data => {
|
||||
this.$message.success({
|
||||
showClose: true,
|
||||
message: '保存成功'
|
||||
})
|
||||
if (this.saveSuccess) {
|
||||
this.saveSuccess()
|
||||
}
|
||||
}).finally(() => [
|
||||
this.loading = false
|
||||
])
|
||||
} else {
|
||||
this.$store.dispatch('commonChanel/add', this.form)
|
||||
.then(data => {
|
||||
this.$message.success({
|
||||
showClose: true,
|
||||
message: '保存成功'
|
||||
})
|
||||
if (this.saveSuccess) {
|
||||
this.saveSuccess()
|
||||
}
|
||||
}).finally(() => [
|
||||
this.loading = false
|
||||
])
|
||||
}
|
||||
},
|
||||
reset: function() {
|
||||
this.$confirm('确定重置为默认内容?', '提示', {
|
||||
dangerouslyUseHTMLString: true,
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.loading = true
|
||||
this.$axios({
|
||||
method: 'post',
|
||||
url: '/api/common/channel/reset',
|
||||
params: {
|
||||
id: this.form.gbId
|
||||
}
|
||||
}).then((res) => {
|
||||
if (res.data.code === 0) {
|
||||
this.$message.success({
|
||||
showClose: true,
|
||||
message: '重置成功 已保存'
|
||||
})
|
||||
this.getCommonChannel()
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error(error)
|
||||
}).finally(() => [
|
||||
this.loading = false
|
||||
])
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
},
|
||||
getCommonChannel: function() {
|
||||
this.loading = true
|
||||
this.$store.dispatch('commonChanel/queryOne', this.id)
|
||||
.then(data => {
|
||||
if (data.gbDownloadSpeed) {
|
||||
data.gbDownloadSpeedArray = data.gbDownloadSpeed.split('/')
|
||||
}
|
||||
this.form = data
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
buildDeviceIdCode: function(deviceId) {
|
||||
this.$refs.channelCode.openDialog(code => {
|
||||
console.log(this.form)
|
||||
console.log('code===> ' + code)
|
||||
this.form.gbDeviceId = code
|
||||
console.log('code22===> ' + code)
|
||||
}, deviceId)
|
||||
},
|
||||
chooseCivilCode: function() {
|
||||
this.$refs.chooseCivilCode.openDialog(code => {
|
||||
this.form.gbCivilCode = code
|
||||
})
|
||||
},
|
||||
chooseGroup: function() {
|
||||
this.$refs.chooseGroup.openDialog((deviceId, businessGroupId) => {
|
||||
this.form.gbBusinessGroupId = businessGroupId
|
||||
this.form.gbParentId = deviceId
|
||||
})
|
||||
},
|
||||
cancelSubmit: function() {
|
||||
if (this.cancel) {
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.channel-form {
|
||||
display: grid;
|
||||
background-color: #FFFFFF;
|
||||
padding: 1rem 2rem 0 2rem;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
74
web/src/views/common/DeviceTree.vue
Executable file
74
web/src/views/common/DeviceTree.vue
Executable file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div id="DeviceTree" style="width: 100%;height: 100%; background-color: #FFFFFF; overflow: auto; padding: 30px">
|
||||
<div style="height: 30px; display: grid; grid-template-columns: auto auto">
|
||||
<div>通道列表</div>
|
||||
<div>
|
||||
<el-switch
|
||||
v-model="showRegion"
|
||||
active-color="#13ce66"
|
||||
inactive-color="rgb(64, 158, 255)"
|
||||
active-text="行政区划"
|
||||
inactive-text="业务分组"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<RegionTree v-if="showRegion" ref="regionTree" :edit="false" :show-header="false" :has-channel="true" :click-event="treeNodeClickEvent" />
|
||||
<GroupTree v-if="!showRegion" ref="groupTree" :edit="false" :show-header="false" :has-channel="true" :click-event="treeNodeClickEvent" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RegionTree from './RegionTree.vue'
|
||||
import GroupTree from './GroupTree.vue'
|
||||
|
||||
export default {
|
||||
name: 'DeviceTree',
|
||||
components: { GroupTree, RegionTree },
|
||||
props: ['device', 'onlyCatalog', 'clickEvent', 'contextMenuEvent'],
|
||||
data() {
|
||||
return {
|
||||
showRegion: true,
|
||||
defaultProps: {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
isLeaf: 'isLeaf'
|
||||
}
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
// if (this.jessibuca) {
|
||||
// this.jessibuca.destroy();
|
||||
// }
|
||||
// this.playing = false;
|
||||
// this.loaded = false;
|
||||
// this.performance = "";
|
||||
},
|
||||
methods: {
|
||||
handleClick: function(tab, event) {
|
||||
},
|
||||
treeNodeClickEvent: function(data) {
|
||||
if (data.leaf) {
|
||||
console.log(23111)
|
||||
console.log(data)
|
||||
if (this.clickEvent) {
|
||||
this.clickEvent(data.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.device-tree-main-box{
|
||||
text-align: left;
|
||||
}
|
||||
.device-online{
|
||||
color: #252525;
|
||||
}
|
||||
.device-offline{
|
||||
color: #727272;
|
||||
}
|
||||
</style>
|
||||
384
web/src/views/common/GroupTree.vue
Executable file
384
web/src/views/common/GroupTree.vue
Executable file
@@ -0,0 +1,384 @@
|
||||
<template>
|
||||
<div id="DeviceTree" style="border-right: 1px solid #EBEEF5; padding: 0 20px">
|
||||
<div v-if="showHeader" class="page-header">
|
||||
<el-form :inline="true" size="mini">
|
||||
<el-form-item style="visibility: hidden">
|
||||
<el-input
|
||||
v-model="searchSrt"
|
||||
style="margin-right: 1rem; width: 12rem;"
|
||||
size="mini"
|
||||
placeholder="关键字"
|
||||
prefix-icon="el-icon-search"
|
||||
clearable
|
||||
@input="search"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="显示编号">
|
||||
<el-checkbox v-model="showCode" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div>
|
||||
<el-alert
|
||||
v-if="showAlert && edit"
|
||||
title="操作提示"
|
||||
description="你可以使用右键菜单管理节点"
|
||||
type="info"
|
||||
style="text-align: left"
|
||||
/>
|
||||
<vue-easy-tree
|
||||
ref="veTree"
|
||||
class="flow-tree"
|
||||
node-key="treeId"
|
||||
:height="treeHeight?treeHeight:'78vh'"
|
||||
lazy
|
||||
:load="loadNode"
|
||||
:data="treeData"
|
||||
:props="props"
|
||||
:default-expanded-keys="['']"
|
||||
@node-contextmenu="contextmenuEventHandler"
|
||||
@node-click="nodeClickHandler"
|
||||
>
|
||||
<template v-slot:default="{ node, data }">
|
||||
<span class="custom-tree-node">
|
||||
<span
|
||||
v-if="node.data.type === 0 && chooseId !== node.data.deviceId"
|
||||
style="color: #409EFF"
|
||||
class="iconfont icon-bianzubeifen3"
|
||||
/>
|
||||
<span
|
||||
v-if="node.data.type === 0 && chooseId === node.data.deviceId"
|
||||
style="color: #c60135;"
|
||||
class="iconfont icon-bianzubeifen3"
|
||||
/>
|
||||
<span
|
||||
v-if="node.data.type === 1 && node.data.status === 'ON'"
|
||||
style="color: #409EFF"
|
||||
class="iconfont icon-shexiangtou2"
|
||||
/>
|
||||
<span
|
||||
v-if="node.data.type === 1 && node.data.status !== 'ON'"
|
||||
style="color: #808181"
|
||||
class="iconfont icon-shexiangtou2"
|
||||
/>
|
||||
<span
|
||||
v-if="node.data.deviceId !=='' && showCode"
|
||||
style=" padding-left: 1px"
|
||||
:title="node.data.deviceId"
|
||||
>{{ node.label }}(编号:{{ node.data.deviceId }})</span>
|
||||
<span
|
||||
v-if="node.data.deviceId ==='' || !showCode"
|
||||
style=" padding-left: 1px"
|
||||
:title="node.data.deviceId"
|
||||
>{{ node.label }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</vue-easy-tree>
|
||||
</div>
|
||||
<groupEdit ref="groupEdit" />
|
||||
<gbDeviceSelect ref="gbDeviceSelect" />
|
||||
<gbChannelSelect ref="gbChannelSelect" data-type="group" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueEasyTree from '@wchbrad/vue-easy-tree'
|
||||
import groupEdit from './../dialog/groupEdit'
|
||||
import gbDeviceSelect from './../dialog/GbDeviceSelect'
|
||||
import GbChannelSelect from '../dialog/GbChannelSelect.vue'
|
||||
|
||||
export default {
|
||||
name: 'DeviceTree',
|
||||
components: {
|
||||
GbChannelSelect,
|
||||
VueEasyTree, groupEdit, gbDeviceSelect
|
||||
},
|
||||
props: ['edit', 'enableAddChannel', 'clickEvent', 'onChannelChange', 'showHeader', 'hasChannel', 'addChannelToGroup', 'treeHeight'],
|
||||
data() {
|
||||
return {
|
||||
props: {
|
||||
label: 'name',
|
||||
id: 'treeId'
|
||||
},
|
||||
showCode: false,
|
||||
showAlert: true,
|
||||
searchSrt: '',
|
||||
chooseId: '',
|
||||
treeData: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
destroyed() {
|
||||
// if (this.jessibuca) {
|
||||
// this.jessibuca.destroy();
|
||||
// }
|
||||
// this.playing = false;
|
||||
// this.loaded = false;
|
||||
// this.performance = "";
|
||||
},
|
||||
methods: {
|
||||
search() {
|
||||
|
||||
},
|
||||
loadNode: function(node, resolve) {
|
||||
if (node.level === 0) {
|
||||
resolve([{
|
||||
treeId: '',
|
||||
deviceId: '',
|
||||
name: '根资源组',
|
||||
isLeaf: false,
|
||||
type: 0
|
||||
}])
|
||||
} else {
|
||||
if (node.data.leaf) {
|
||||
resolve([])
|
||||
return
|
||||
}
|
||||
this.$store.dispatch('group/getTreeList', {
|
||||
query: this.searchSrt,
|
||||
parent: node.data.id,
|
||||
hasChannel: this.hasChannel
|
||||
}).then(data => {
|
||||
if (data.length > 0) {
|
||||
this.showAlert = false
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
}
|
||||
},
|
||||
reset: function() {
|
||||
this.$forceUpdate()
|
||||
},
|
||||
contextmenuEventHandler: function(event, data, node, element) {
|
||||
if (!this.edit) {
|
||||
return
|
||||
}
|
||||
if (node.data.type === 0) {
|
||||
const menuItem = [
|
||||
{
|
||||
label: '刷新节点',
|
||||
icon: 'el-icon-refresh',
|
||||
disabled: false,
|
||||
onClick: () => {
|
||||
this.refreshNode(node)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '新建节点',
|
||||
icon: 'el-icon-plus',
|
||||
disabled: false,
|
||||
onClick: () => {
|
||||
this.addGroup(data.id, node)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '编辑节点',
|
||||
icon: 'el-icon-edit',
|
||||
disabled: node.level === 1,
|
||||
onClick: () => {
|
||||
this.editGroup(data, node)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '删除节点',
|
||||
icon: 'el-icon-delete',
|
||||
disabled: node.level === 1,
|
||||
divided: true,
|
||||
onClick: () => {
|
||||
this.$confirm('确定删除?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.removeGroup(data.id, node)
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
if (this.enableAddChannel) {
|
||||
menuItem.push(
|
||||
{
|
||||
label: '添加设备',
|
||||
icon: 'el-icon-plus',
|
||||
disabled: node.level <= 2,
|
||||
onClick: () => {
|
||||
this.addChannelFormDevice(data.id, node)
|
||||
}
|
||||
}
|
||||
)
|
||||
menuItem.push(
|
||||
{
|
||||
label: '移除设备',
|
||||
icon: 'el-icon-delete',
|
||||
disabled: node.level <= 2,
|
||||
divided: true,
|
||||
onClick: () => {
|
||||
this.removeChannelFormDevice(data.id, node)
|
||||
}
|
||||
}
|
||||
)
|
||||
menuItem.push(
|
||||
{
|
||||
label: '添加通道',
|
||||
icon: 'el-icon-plus',
|
||||
disabled: node.level <= 2,
|
||||
onClick: () => {
|
||||
this.addChannel(data.id, node)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
this.$contextmenu({
|
||||
items: menuItem,
|
||||
event, // 鼠标事件信息
|
||||
customClass: 'custom-class', // 自定义菜单 class
|
||||
zIndex: 3000 // 菜单样式 z-index
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
removeGroup: function(id, node) {
|
||||
this.$store.dispatch('group/deleteGroup', node.data.id)
|
||||
.then(data => {
|
||||
node.parent.loaded = false
|
||||
node.parent.expand()
|
||||
if (this.onChannelChange) {
|
||||
this.onChannelChange(node.data.deviceId)
|
||||
}
|
||||
})
|
||||
},
|
||||
addChannelFormDevice: function(id, node) {
|
||||
this.$refs.gbDeviceSelect.openDialog((rows) => {
|
||||
const deviceIds = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
deviceIds.push(rows[i].id)
|
||||
}
|
||||
this.$store.dispatch('group/add', {
|
||||
parentId: node.data.deviceId,
|
||||
businessGroup: node.data.businessGroup,
|
||||
deviceIds: deviceIds
|
||||
})
|
||||
.then(data => {
|
||||
this.$message.success({
|
||||
showClose: true,
|
||||
message: '保存成功'
|
||||
})
|
||||
if (this.onChannelChange) {
|
||||
this.onChannelChange()
|
||||
}
|
||||
console.log(node)
|
||||
node.loaded = false
|
||||
node.expand()
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
removeChannelFormDevice: function(id, node) {
|
||||
this.$refs.gbDeviceSelect.openDialog((rows) => {
|
||||
const deviceIds = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
deviceIds.push(rows[i].id)
|
||||
}
|
||||
this.$store.dispatch('commonChanel/deleteDeviceFromGroup', deviceIds)
|
||||
.then(data => {
|
||||
this.$message.success({
|
||||
showClose: true,
|
||||
message: '保存成功'
|
||||
})
|
||||
if (this.onChannelChange) {
|
||||
this.onChannelChange()
|
||||
}
|
||||
node.loaded = false
|
||||
node.expand()
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
addChannel: function(id, node) {
|
||||
this.$refs.gbChannelSelect.openDialog((data) => {
|
||||
console.log('选择的数据')
|
||||
console.log(data)
|
||||
this.addChannelToGroup(node.data.deviceId, node.data.businessGroup, data)
|
||||
})
|
||||
},
|
||||
refreshNode: function(node) {
|
||||
console.log(node)
|
||||
node.loaded = false
|
||||
node.expand()
|
||||
},
|
||||
refresh: function(id) {
|
||||
console.log('刷新节点: ' + id)
|
||||
// 查询node
|
||||
const node = this.$refs.veTree.getNode(id)
|
||||
if (node) {
|
||||
node.loaded = false
|
||||
node.expand()
|
||||
}
|
||||
},
|
||||
addGroup: function(id, node) {
|
||||
this.$refs.groupEdit.openDialog({
|
||||
id: 0,
|
||||
name: '',
|
||||
deviceId: '',
|
||||
civilCode: '',
|
||||
parentDeviceId: node.level > 2 ? node.data.deviceId : '',
|
||||
parentId: node.data.id,
|
||||
businessGroup: node.level > 2 ? node.data.businessGroup : node.data.deviceId
|
||||
}, form => {
|
||||
console.log(node)
|
||||
node.loaded = false
|
||||
node.expand()
|
||||
}, id)
|
||||
},
|
||||
editGroup: function(id, node) {
|
||||
console.log(node)
|
||||
this.$refs.groupEdit.openDialog(node.data, form => {
|
||||
console.log(node)
|
||||
node.parent.loaded = false
|
||||
node.parent.expand()
|
||||
}, id)
|
||||
},
|
||||
nodeClickHandler: function(data, node, tree) {
|
||||
this.chooseId = data.deviceId
|
||||
if (this.clickEvent) {
|
||||
this.clickEvent(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.device-tree-main-box {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.device-online {
|
||||
color: #252525;
|
||||
}
|
||||
|
||||
.device-offline {
|
||||
color: #727272;
|
||||
}
|
||||
|
||||
.custom-tree-node .el-radio__label {
|
||||
padding-left: 4px !important;
|
||||
}
|
||||
|
||||
.flow-tree {
|
||||
overflow: auto;
|
||||
margin: 10px;
|
||||
}
|
||||
.flow-tree .vue-recycle-scroller__item-wrapper{
|
||||
height: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
255
web/src/views/common/MapComponent.vue
Executable file
255
web/src/views/common/MapComponent.vue
Executable file
@@ -0,0 +1,255 @@
|
||||
<template>
|
||||
<div id="mapContainer" ref="mapContainer" style="width: 100%;height: 100%;" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'ol/ol.css'
|
||||
import Map from 'ol/Map'
|
||||
import OSM from 'ol/source/OSM'
|
||||
import XYZ from 'ol/source/XYZ'
|
||||
import VectorSource from 'ol/source/Vector'
|
||||
import Tile from 'ol/layer/Tile'
|
||||
import VectorLayer from 'ol/layer/Vector'
|
||||
import Style from 'ol/style/Style'
|
||||
import Stroke from 'ol/style/Stroke'
|
||||
import Icon from 'ol/style/Icon'
|
||||
import View from 'ol/View'
|
||||
import Feature from 'ol/Feature'
|
||||
import Overlay from 'ol/Overlay'
|
||||
import { Point, LineString } from 'ol/geom'
|
||||
import { get as getProj, fromLonLat } from 'ol/proj'
|
||||
import { ZoomSlider, Zoom } from 'ol/control'
|
||||
import { containsCoordinate } from 'ol/extent'
|
||||
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
let olMap = null
|
||||
|
||||
export default {
|
||||
name: 'MapComponent',
|
||||
props: [],
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.init()
|
||||
}, 100)
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
destroyed() {
|
||||
// if (this.jessibuca) {
|
||||
// this.jessibuca.destroy();
|
||||
// }
|
||||
// this.playing = false;
|
||||
// this.loaded = false;
|
||||
// this.performance = "";
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
let center = fromLonLat([116.41020, 39.915119])
|
||||
if (mapParam.center) {
|
||||
center = fromLonLat(mapParam.center)
|
||||
}
|
||||
const view = new View({
|
||||
center: center,
|
||||
zoom: mapParam.zoom || 10,
|
||||
projection: this.projection,
|
||||
maxZoom: mapParam.maxZoom || 19,
|
||||
minZoom: mapParam.minZoom || 1
|
||||
})
|
||||
let tileLayer = null
|
||||
if (mapParam.tilesUrl) {
|
||||
tileLayer = new Tile({
|
||||
source: new XYZ({
|
||||
projection: getProj('EPSG:3857'),
|
||||
wrapX: false,
|
||||
tileSize: 256 || mapParam.tileSize,
|
||||
url: mapParam.tilesUrl
|
||||
})
|
||||
})
|
||||
} else {
|
||||
tileLayer = new Tile({
|
||||
preload: 4,
|
||||
source: new OSM()
|
||||
})
|
||||
}
|
||||
olMap = new Map({
|
||||
target: this.$refs.mapContainer, // 容器ID
|
||||
layers: [tileLayer], // 默认图层
|
||||
view: view, // 视图
|
||||
controls: [ // 控件
|
||||
// new ZoomSlider(),
|
||||
new Zoom()
|
||||
]
|
||||
})
|
||||
console.log(3222)
|
||||
},
|
||||
setCenter(point) {
|
||||
|
||||
},
|
||||
zoomIn(zoom) {
|
||||
|
||||
},
|
||||
zoomOut(zoom) {
|
||||
|
||||
},
|
||||
centerAndZoom(point, zoom, callback) {
|
||||
var zoom_ = olMap.getView().getZoom()
|
||||
zoom = zoom || zoom_
|
||||
var duration = 600
|
||||
olMap.getView().setCenter(fromLonLat(point))
|
||||
olMap.getView().animate({
|
||||
zoom: zoom,
|
||||
duration: duration
|
||||
})
|
||||
},
|
||||
panTo(point, zoom) {
|
||||
const duration = 800
|
||||
|
||||
olMap.getView().cancelAnimations()
|
||||
olMap.getView().animate({
|
||||
center: fromLonLat(point),
|
||||
duration: duration
|
||||
})
|
||||
if (!containsCoordinate(olMap.getView().calculateExtent(), fromLonLat(point))) {
|
||||
olMap.getView().animate({
|
||||
zoom: olMap.getView().getZoom() - 1,
|
||||
duration: duration / 2
|
||||
}, {
|
||||
zoom: zoom || olMap.getView().getZoom(),
|
||||
duration: duration / 2
|
||||
})
|
||||
}
|
||||
},
|
||||
fit(layer) {
|
||||
const extent = layer.getSource().getExtent()
|
||||
if (extent) {
|
||||
olMap.getView().fit(extent, {
|
||||
duration: 600,
|
||||
padding: [100, 100, 100, 100]
|
||||
})
|
||||
}
|
||||
},
|
||||
openInfoBox(position, content, offset) {
|
||||
const id = v4()
|
||||
// let infoBox = document.createElement("div");
|
||||
// infoBox.innerHTML = content ;
|
||||
// infoBox.setAttribute("infoBoxId", id)
|
||||
const overlay = new Overlay({
|
||||
id: id,
|
||||
autoPan: true,
|
||||
autoPanAnimation: {
|
||||
duration: 250
|
||||
},
|
||||
element: content,
|
||||
positioning: 'bottom-center',
|
||||
offset: offset
|
||||
// className:overlayStyle.className
|
||||
})
|
||||
olMap.addOverlay(overlay)
|
||||
overlay.setPosition(fromLonLat(position))
|
||||
return id
|
||||
},
|
||||
closeInfoBox(id) {
|
||||
olMap.getOverlayById(id).setPosition(undefined)
|
||||
// olMap.removeOverlay(olMap.getOverlayById(id))
|
||||
},
|
||||
/**
|
||||
* 添加图层
|
||||
* @param data
|
||||
* [
|
||||
* {
|
||||
*
|
||||
* position: [119.1212,45,122],
|
||||
* image: {
|
||||
* src:"/images/123.png",
|
||||
* anchor: [0.5, 0.5]
|
||||
*
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ]
|
||||
*/
|
||||
addLayer(data, clickEvent) {
|
||||
const style = new Style()
|
||||
if (data.length > 0) {
|
||||
const features = []
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const feature = new Feature(new Point(fromLonLat(data[i].position)))
|
||||
feature.customData = data[i].data
|
||||
const cloneStyle = style.clone()
|
||||
cloneStyle.setImage(new Icon({
|
||||
anchor: data[i].image.anchor,
|
||||
crossOrigin: 'Anonymous',
|
||||
src: data[i].image.src
|
||||
}))
|
||||
feature.setStyle(cloneStyle)
|
||||
features.push(feature)
|
||||
}
|
||||
const source = new VectorSource()
|
||||
source.addFeatures(features)
|
||||
const vectorLayer = new VectorLayer({
|
||||
source: source,
|
||||
style: style,
|
||||
renderMode: 'image',
|
||||
declutter: false
|
||||
})
|
||||
olMap.addLayer(vectorLayer)
|
||||
if (typeof clickEvent === 'function') {
|
||||
olMap.on('click', (event) => {
|
||||
vectorLayer.getFeatures(event.pixel).then((features) => {
|
||||
if (features.length > 0) {
|
||||
const items = []
|
||||
for (let i = 0; i < features.length; i++) {
|
||||
items.push(features[i].customData)
|
||||
}
|
||||
clickEvent(items)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return vectorLayer
|
||||
}
|
||||
},
|
||||
removeLayer(layer) {
|
||||
olMap.removeLayer(layer)
|
||||
},
|
||||
|
||||
addLineLayer(positions) {
|
||||
if (positions.length > 0) {
|
||||
const points = []
|
||||
for (let i = 0; i < positions.length; i++) {
|
||||
points.push(fromLonLat(positions[i]))
|
||||
}
|
||||
const line = new LineString(points)
|
||||
const lineFeature = new Feature(line)
|
||||
lineFeature.setStyle(new Style({
|
||||
stroke: new Stroke({
|
||||
width: 4,
|
||||
color: '#0c6d6a'
|
||||
})
|
||||
}))
|
||||
const source = new VectorSource()
|
||||
source.addFeature(lineFeature)
|
||||
const vectorLayer = new VectorLayer({
|
||||
source: source
|
||||
})
|
||||
olMap.addLayer(vectorLayer)
|
||||
return vectorLayer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
392
web/src/views/common/RegionTree.vue
Executable file
392
web/src/views/common/RegionTree.vue
Executable file
@@ -0,0 +1,392 @@
|
||||
<template>
|
||||
<div id="DeviceTree" style="border-right: 1px solid #EBEEF5; padding: 0 20px">
|
||||
<div v-if="showHeader" class="page-header">
|
||||
<el-form :inline="true" size="mini">
|
||||
<el-form-item style="visibility: hidden">
|
||||
<el-input
|
||||
v-model="searchSrt"
|
||||
style="margin-right: 1rem; width: 12rem;"
|
||||
size="mini"
|
||||
placeholder="关键字"
|
||||
prefix-icon="el-icon-search"
|
||||
clearable
|
||||
@input="search"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="显示编号">
|
||||
<el-checkbox v-model="showCode" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div>
|
||||
<el-alert
|
||||
v-if="showAlert && edit"
|
||||
title="操作提示"
|
||||
description="你可以使用右键菜单管理节点"
|
||||
type="info"
|
||||
style="text-align: left"
|
||||
/>
|
||||
<vue-easy-tree
|
||||
ref="veTree"
|
||||
class="flow-tree"
|
||||
node-key="treeId"
|
||||
:height="treeHeight?treeHeight:'78vh'"
|
||||
lazy
|
||||
:load="loadNode"
|
||||
:data="treeData"
|
||||
:props="props"
|
||||
:default-expanded-keys="['']"
|
||||
@node-contextmenu="contextmenuEventHandler"
|
||||
@node-click="nodeClickHandler"
|
||||
>
|
||||
<template v-slot:default="{ node, data }" class="custom-tree-node">
|
||||
<span class="custom-tree-node">
|
||||
<span
|
||||
v-if="node.data.type === 0 && chooseId !== node.data.deviceId"
|
||||
style="color: #409EFF"
|
||||
class="iconfont icon-bianzubeifen3"
|
||||
/>
|
||||
<span
|
||||
v-if="node.data.type === 0 && chooseId === node.data.deviceId"
|
||||
style="color: #c60135;"
|
||||
class="iconfont icon-bianzubeifen3"
|
||||
/>
|
||||
<span
|
||||
v-if="node.data.type === 1 && node.data.status === 'ON'"
|
||||
style="color: #409EFF"
|
||||
class="iconfont icon-shexiangtou2"
|
||||
/>
|
||||
<span
|
||||
v-if="node.data.type === 1 && node.data.status !== 'ON'"
|
||||
style="color: #808181"
|
||||
class="iconfont icon-shexiangtou2"
|
||||
/>
|
||||
<span
|
||||
v-if="node.data.deviceId !=='' && showCode"
|
||||
style=" padding-left: 1px"
|
||||
:title="node.data.deviceId"
|
||||
>{{ node.label }}(编号:{{ node.data.deviceId }})</span>
|
||||
<span
|
||||
v-if="node.data.deviceId ==='' || !showCode"
|
||||
style=" padding-left: 1px"
|
||||
:title="node.data.deviceId"
|
||||
>{{ node.label }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</vue-easy-tree>
|
||||
</div>
|
||||
<regionEdit ref="regionEdit" />
|
||||
<gbDeviceSelect ref="gbDeviceSelect" />
|
||||
<GbChannelSelect ref="gbChannelSelect" data-type="civilCode" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueEasyTree from '@wchbrad/vue-easy-tree'
|
||||
import regionEdit from './../dialog/regionEdit'
|
||||
import gbDeviceSelect from './../dialog/GbDeviceSelect'
|
||||
import GbChannelSelect from '../dialog/GbChannelSelect.vue'
|
||||
|
||||
export default {
|
||||
name: 'DeviceTree',
|
||||
components: {
|
||||
GbChannelSelect,
|
||||
VueEasyTree, regionEdit, gbDeviceSelect
|
||||
},
|
||||
props: ['edit', 'enableAddChannel', 'clickEvent', 'onChannelChange', 'showHeader', 'hasChannel', 'addChannelToCivilCode', 'treeHeight'],
|
||||
data() {
|
||||
return {
|
||||
props: {
|
||||
label: 'name'
|
||||
},
|
||||
showCode: false,
|
||||
showAlert: true,
|
||||
searchSrt: '',
|
||||
chooseId: '',
|
||||
treeData: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
destroyed() {
|
||||
// if (this.jessibuca) {
|
||||
// this.jessibuca.destroy();
|
||||
// }
|
||||
// this.playing = false;
|
||||
// this.loaded = false;
|
||||
// this.performance = "";
|
||||
},
|
||||
methods: {
|
||||
search() {
|
||||
|
||||
},
|
||||
loadNode: function(node, resolve) {
|
||||
if (node.level === 0) {
|
||||
resolve([{
|
||||
treeId: '',
|
||||
deviceId: '',
|
||||
name: '根资源组',
|
||||
isLeaf: false,
|
||||
type: 0
|
||||
}])
|
||||
} else if (node.data.deviceId.length <= 8) {
|
||||
if (node.data.leaf) {
|
||||
resolve([])
|
||||
return
|
||||
}
|
||||
this.$store.dispatch('region/getTreeList', {
|
||||
query: this.searchSrt,
|
||||
parent: node.data.id,
|
||||
hasChannel: this.hasChannel
|
||||
})
|
||||
.then(data => {
|
||||
if (data.length > 0) {
|
||||
this.showAlert = false
|
||||
}
|
||||
resolve(data)
|
||||
}).finally(() => {
|
||||
this.locading = false
|
||||
})
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
},
|
||||
reset: function() {
|
||||
this.$forceUpdate()
|
||||
},
|
||||
contextmenuEventHandler: function(event, data, node, element) {
|
||||
if (!this.edit) {
|
||||
return
|
||||
}
|
||||
console.log(node.level)
|
||||
if (node.data.type === 0) {
|
||||
const menuItem = [
|
||||
{
|
||||
label: '刷新节点',
|
||||
icon: 'el-icon-refresh',
|
||||
disabled: false,
|
||||
onClick: () => {
|
||||
this.refreshNode(node)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '新建节点',
|
||||
icon: 'el-icon-plus',
|
||||
disabled: false,
|
||||
onClick: () => {
|
||||
this.addRegion(data.id, node)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '编辑节点',
|
||||
icon: 'el-icon-edit',
|
||||
disabled: node.level === 1,
|
||||
onClick: () => {
|
||||
this.editCatalog(data, node)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '删除节点',
|
||||
icon: 'el-icon-delete',
|
||||
disabled: node.level === 1,
|
||||
divided: true,
|
||||
onClick: () => {
|
||||
this.$confirm('确定删除?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.removeRegion(data.id, node)
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
if (this.enableAddChannel) {
|
||||
menuItem.push(
|
||||
{
|
||||
label: '添加设备',
|
||||
icon: 'el-icon-plus',
|
||||
disabled: node.level === 1,
|
||||
onClick: () => {
|
||||
this.addChannelFormDevice(data.id, node)
|
||||
}
|
||||
}
|
||||
)
|
||||
menuItem.push(
|
||||
{
|
||||
label: '移除设备',
|
||||
icon: 'el-icon-delete',
|
||||
disabled: node.level === 1,
|
||||
divided: true,
|
||||
onClick: () => {
|
||||
this.removeChannelFormDevice(data.id, node)
|
||||
}
|
||||
}
|
||||
)
|
||||
menuItem.push(
|
||||
{
|
||||
label: '添加通道',
|
||||
icon: 'el-icon-plus',
|
||||
disabled: node.level === 1,
|
||||
onClick: () => {
|
||||
this.addChannel(data.id, node)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
this.$contextmenu({
|
||||
items: menuItem,
|
||||
event, // 鼠标事件信息
|
||||
customClass: 'custom-class', // 自定义菜单 class
|
||||
zIndex: 3000 // 菜单样式 z-index
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
removeRegion: function(id, node) {
|
||||
this.$store.dispatch('region/deleteRegion', node.data.id)
|
||||
.then((data) => {
|
||||
console.log('移除成功')
|
||||
node.parent.loaded = false
|
||||
node.parent.expand()
|
||||
}).catch(function(error) {
|
||||
console.log(error)
|
||||
})
|
||||
},
|
||||
addChannelFormDevice: function(id, node) {
|
||||
this.$refs.gbDeviceSelect.openDialog((rows) => {
|
||||
const deviceIds = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
deviceIds.push(rows[i].id)
|
||||
}
|
||||
this.$store.dispatch('commonChanel/addDeviceToRegion', {
|
||||
civilCode: node.data.deviceId,
|
||||
deviceIds: deviceIds
|
||||
}).then((data) => {
|
||||
this.$message.success({
|
||||
showClose: true,
|
||||
message: '保存成功'
|
||||
})
|
||||
if (this.onChannelChange) {
|
||||
this.onChannelChange()
|
||||
}
|
||||
node.loaded = false
|
||||
node.expand()
|
||||
}).catch(function(error) {
|
||||
console.log(error)
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
removeChannelFormDevice: function(id, node) {
|
||||
this.$refs.gbDeviceSelect.openDialog((rows) => {
|
||||
const deviceIds = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
deviceIds.push(rows[i].id)
|
||||
}
|
||||
this.$store.dispatch('commonChanel/deleteDeviceFromRegion', deviceIds)
|
||||
.then((data) => {
|
||||
this.$message.success({
|
||||
showClose: true,
|
||||
message: '保存成功'
|
||||
})
|
||||
if (this.onChannelChange) {
|
||||
this.onChannelChange(node.data.deviceId)
|
||||
}
|
||||
node.loaded = false
|
||||
node.expand()
|
||||
}).catch(function(error) {
|
||||
console.log(error)
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
addChannel: function(id, node) {
|
||||
this.$refs.gbChannelSelect.openDialog((data) => {
|
||||
console.log('选择的数据')
|
||||
console.log(data)
|
||||
this.addChannelToCivilCode(node.data.deviceId, data)
|
||||
})
|
||||
},
|
||||
refreshNode: function(node) {
|
||||
node.loaded = false
|
||||
node.expand()
|
||||
},
|
||||
refresh: function(id) {
|
||||
console.log(id)
|
||||
// 查询node
|
||||
const node = this.$refs.veTree.getNode(id)
|
||||
if (node) {
|
||||
node.loaded = false
|
||||
node.expand()
|
||||
}
|
||||
},
|
||||
addRegion: function(id, node) {
|
||||
console.log(node)
|
||||
|
||||
this.$refs.regionEdit.openDialog(form => {
|
||||
node.loaded = false
|
||||
node.expand()
|
||||
}, {
|
||||
deviceId: '',
|
||||
name: '',
|
||||
parentId: node.data.id,
|
||||
parentDeviceId: node.data.deviceId
|
||||
})
|
||||
},
|
||||
editCatalog: function(data, node) {
|
||||
// 打开添加弹窗
|
||||
this.$refs.regionEdit.openDialog(form => {
|
||||
node.loaded = false
|
||||
node.expand()
|
||||
}, node.data)
|
||||
},
|
||||
nodeClickHandler: function(data, node, tree) {
|
||||
this.chooseId = data.deviceId
|
||||
if (this.clickEvent) {
|
||||
this.clickEvent(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.device-tree-main-box {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.device-online {
|
||||
color: #252525;
|
||||
}
|
||||
|
||||
.device-offline {
|
||||
color: #727272;
|
||||
}
|
||||
|
||||
.custom-tree-node .el-radio__label {
|
||||
padding-left: 4px !important;
|
||||
}
|
||||
|
||||
.tree-scroll{
|
||||
width: 200px;
|
||||
border: 1px solid #E7E7E7;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.flow-tree {
|
||||
overflow: auto;
|
||||
margin: 10px;
|
||||
}
|
||||
.flow-tree .vue-recycle-scroller__item-wrapper{
|
||||
height: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
195
web/src/views/common/VideoTimeLine/WindowListItem.vue
Normal file
195
web/src/views/common/VideoTimeLine/WindowListItem.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div ref="windowListItem" class="windowListItem" :class="{active: active}" @click="onClick">
|
||||
<span class="order">{{ index + 1 }}</span>
|
||||
<canvas ref="canvas" class="windowListItemCanvas" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'WindowListItem',
|
||||
props: {
|
||||
index: {
|
||||
type: Number
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
totalMS: {
|
||||
type: Number
|
||||
},
|
||||
startTimestamp: {
|
||||
type: Number
|
||||
},
|
||||
width: {
|
||||
type: Number
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
height: 0,
|
||||
ctx: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
this.drawTimeSegments()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2020-04-14 09:20:22
|
||||
* @Desc: 初始化
|
||||
*/
|
||||
init() {
|
||||
const { height } = this.$refs.windowListItem.getBoundingClientRect()
|
||||
this.height = height - 1
|
||||
this.$refs.canvas.width = this.width
|
||||
this.$refs.canvas.height = this.height
|
||||
this.ctx = this.$refs.canvas.getContext('2d')
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2020-04-14 15:42:49
|
||||
* @Desc: 绘制时间段
|
||||
*/
|
||||
drawTimeSegments(callback, path) {
|
||||
if (!this.data.timeSegments || this.data.timeSegments.length <= 0) {
|
||||
return
|
||||
}
|
||||
const PX_PER_MS = this.width / this.totalMS // px/ms,每毫秒占的像素
|
||||
this.data.timeSegments.forEach((item) => {
|
||||
if (
|
||||
item.beginTime <= this.startTimestamp + this.totalMS &&
|
||||
item.endTime >= this.startTimestamp
|
||||
) {
|
||||
this.ctx.beginPath()
|
||||
let x = (item.beginTime - this.startTimestamp) * PX_PER_MS
|
||||
let w
|
||||
if (x < 0) {
|
||||
x = 0
|
||||
w = (item.endTime - this.startTimestamp) * PX_PER_MS
|
||||
} else {
|
||||
w = (item.endTime - item.beginTime) * PX_PER_MS
|
||||
}
|
||||
const heightStartRatio = item.startRatio === undefined ? 0.6 : item.startRatio
|
||||
const heightEndRatio = item.endRatio === undefined ? 0.9 : item.endRatio
|
||||
if (path) {
|
||||
this.ctx.rect(
|
||||
x,
|
||||
this.height * heightStartRatio,
|
||||
w,
|
||||
this.height * (heightEndRatio - heightStartRatio)
|
||||
)
|
||||
} else {
|
||||
this.ctx.fillStyle = item.color
|
||||
this.ctx.fillRect(
|
||||
x,
|
||||
this.height * heightStartRatio,
|
||||
w,
|
||||
this.height * (heightEndRatio - heightStartRatio)
|
||||
)
|
||||
}
|
||||
callback && callback(item)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2020-04-14 14:25:43
|
||||
* @Desc: 清除画布
|
||||
*/
|
||||
clearCanvas() {
|
||||
this.ctx.clearRect(0, 0, this.width, this.height)
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-01-20 19:07:31
|
||||
* @Desc: 绘制
|
||||
*/
|
||||
draw() {
|
||||
this.$nextTick(() => {
|
||||
this.clearCanvas()
|
||||
this.drawTimeSegments()
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-01-20 19:26:46
|
||||
* @Desc: 点击事件
|
||||
*/
|
||||
onClick(e) {
|
||||
this.$emit('click', e)
|
||||
const { left, top } = this.$refs.windowListItem.getBoundingClientRect()
|
||||
const x = e.clientX - left
|
||||
const y = e.clientY - top
|
||||
const timeSegments = this.getClickTimeSegments(x, y)
|
||||
if (timeSegments.length > 0) {
|
||||
this.$emit('click_window_timeSegments', timeSegments, this.index, this.data)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-01-20 16:24:54
|
||||
* @Desc: 检测当前是否点击了某个时间段
|
||||
*/
|
||||
getClickTimeSegments(x, y) {
|
||||
if (!this.data.timeSegments || this.data.timeSegments.length <= 0) {
|
||||
return []
|
||||
}
|
||||
const inItems = []
|
||||
this.drawTimeSegments((item) => {
|
||||
if (this.ctx.isPointInPath(x, y)) {
|
||||
inItems.push(item)
|
||||
}
|
||||
}, true)
|
||||
return inItems
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-01-21 11:25:26
|
||||
* @Desc: 获取位置信息
|
||||
*/
|
||||
getRect() {
|
||||
return this.$refs.windowListItem ? this.$refs.windowListItem.getBoundingClientRect() : null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.windowListItem {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #999999;
|
||||
user-select: none;
|
||||
}
|
||||
.windowListItem.active {
|
||||
background-color: #000;
|
||||
}
|
||||
.windowListItem .order {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
border-right: 1px solid #999999;
|
||||
}
|
||||
|
||||
</style>
|
||||
89
web/src/views/common/VideoTimeLine/constant.js
Normal file
89
web/src/views/common/VideoTimeLine/constant.js
Normal file
@@ -0,0 +1,89 @@
|
||||
// 一小时的毫秒数
|
||||
export const ONE_HOUR_STAMP = 60 * 60 * 1000
|
||||
// 时间分辨率,即整个时间轴表示的时间范围
|
||||
export const ZOOM = [0.5, 1, 2, 6, 12, 24, 72, 360, 720, 8760, 87600]// 半小时、1小时、2小时、6小时、12小时、1天、3天、15天、30天、365天、365*10天
|
||||
// 时间分辨率对应的每格小时数,即最小格代表多少小时
|
||||
export const ZOOM_HOUR_GRID = [1 / 60, 1 / 60, 2 / 60, 1 / 6, 0.25, 0.5, 1, 4, 4, 720, 7200]
|
||||
export const MOBILE_ZOOM_HOUR_GRID = [
|
||||
1 / 20,
|
||||
1 / 30,
|
||||
1 / 20,
|
||||
1 / 3,
|
||||
0.5,
|
||||
2,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
720, 7200
|
||||
]
|
||||
// 时间分辨率对应的时间显示判断条件
|
||||
export const ZOOM_DATE_SHOW_RULE = [
|
||||
() => { // 全部显示
|
||||
return true
|
||||
},
|
||||
date => { // 每五分钟显示
|
||||
return date.getMinutes() % 5 === 0
|
||||
},
|
||||
date => { // 每十分钟显示
|
||||
return date.getMinutes() % 10 === 0
|
||||
},
|
||||
date => { // 整点和半点显示
|
||||
return date.getMinutes() === 0 || date.getMinutes() === 30
|
||||
},
|
||||
date => { // 整点显示
|
||||
return date.getMinutes() === 0
|
||||
},
|
||||
date => { // 偶数整点的小时
|
||||
return date.getHours() % 2 === 0 && date.getMinutes() === 0
|
||||
},
|
||||
date => { // 每三小时小时
|
||||
return date.getHours() % 3 === 0 && date.getMinutes() === 0
|
||||
},
|
||||
date => { // 每12小时
|
||||
return date.getHours() % 12 === 0 && date.getMinutes() === 0
|
||||
},
|
||||
date => { // 全不显示
|
||||
return false
|
||||
},
|
||||
date => {
|
||||
return true
|
||||
},
|
||||
date => {
|
||||
return true
|
||||
}
|
||||
]
|
||||
export const MOBILE_ZOOM_DATE_SHOW_RULE = [
|
||||
() => { // 全部显示
|
||||
return true
|
||||
},
|
||||
date => { // 每五分钟显示
|
||||
return date.getMinutes() % 5 === 0
|
||||
},
|
||||
date => { // 每十分钟显示
|
||||
return date.getMinutes() % 10 === 0
|
||||
},
|
||||
date => { // 整点和半点显示
|
||||
return date.getMinutes() === 0 || date.getMinutes() === 30
|
||||
},
|
||||
date => { // 偶数整点的小时
|
||||
return date.getHours() % 2 === 0 && date.getMinutes() === 0
|
||||
},
|
||||
date => { // 偶数整点的小时
|
||||
return date.getHours() % 4 === 0 && date.getMinutes() === 0
|
||||
},
|
||||
date => { // 每三小时小时
|
||||
return date.getHours() % 3 === 0 && date.getMinutes() === 0
|
||||
},
|
||||
date => { // 每12小时
|
||||
return date.getHours() % 12 === 0 && date.getMinutes() === 0
|
||||
},
|
||||
date => { // 全不显示
|
||||
return false
|
||||
},
|
||||
date => {
|
||||
return true
|
||||
},
|
||||
date => {
|
||||
return true
|
||||
}
|
||||
]
|
||||
1048
web/src/views/common/VideoTimeLine/index.vue
Normal file
1048
web/src/views/common/VideoTimeLine/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
315
web/src/views/common/h265web.vue
Normal file
315
web/src/views/common/h265web.vue
Normal file
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<div id="h265Player" ref="container" style="background-color: #000000; " @dblclick="fullscreenSwich">
|
||||
<div id="glplayer" ref="playerBox" style="width: 100%; height: 100%; margin: 0 auto;" />
|
||||
<div v-if="playerLoading" class="player-loading">
|
||||
<i class="el-icon-loading" />
|
||||
<span>视频加载中</span>
|
||||
</div>
|
||||
<div v-if="showButton" id="buttonsBox" class="buttons-box">
|
||||
<div class="buttons-box-left">
|
||||
<i v-if="!playing" class="iconfont icon-play h265web-btn" @click="unPause" />
|
||||
<i v-if="playing" class="iconfont icon-pause h265web-btn" @click="pause" />
|
||||
<i class="iconfont icon-stop h265web-btn" @click="destroy" />
|
||||
<i v-if="isNotMute" class="iconfont icon-audio-high h265web-btn" @click="mute()" />
|
||||
<i v-if="!isNotMute" class="iconfont icon-audio-mute h265web-btn" @click="cancelMute()" />
|
||||
</div>
|
||||
<div class="buttons-box-right">
|
||||
<!-- <i class="iconfont icon-file-record1 h265web-btn"></i>-->
|
||||
<!-- <i class="iconfont icon-xiangqing2 h265web-btn" ></i>-->
|
||||
<i
|
||||
class="iconfont icon-camera1196054easyiconnet h265web-btn"
|
||||
style="font-size: 1rem !important"
|
||||
@click="screenshot"
|
||||
/>
|
||||
<i class="iconfont icon-shuaxin11 h265web-btn" @click="playBtnClick" />
|
||||
<i v-if="!fullscreen" class="iconfont icon-weibiaoti10 h265web-btn" @click="fullscreenSwich" />
|
||||
<i v-if="fullscreen" class="iconfont icon-weibiaoti11 h265web-btn" @click="fullscreenSwich" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const h265webPlayer = {}
|
||||
/**
|
||||
* 从github上复制的
|
||||
* @see https://github.com/numberwolf/h265web.js/blob/master/example_normal/index.js
|
||||
*/
|
||||
const token = 'base64:QXV0aG9yOmNoYW5neWFubG9uZ3xudW1iZXJ3b2xmLEdpdGh1YjpodHRwczovL2dpdGh1Yi5jb20vbnVtYmVyd29sZixFbWFpbDpwb3JzY2hlZ3QyM0Bmb3htYWlsLmNvbSxRUTo1MzEzNjU4NzIsSG9tZVBhZ2U6aHR0cDovL3h2aWRlby52aWRlbyxEaXNjb3JkOm51bWJlcndvbGYjODY5NCx3ZWNoYXI6bnVtYmVyd29sZjExLEJlaWppbmcsV29ya0luOkJhaWR1'
|
||||
export default {
|
||||
name: 'H265web',
|
||||
props: ['videoUrl', 'error', 'hasAudio', 'height', 'showButton'],
|
||||
data() {
|
||||
return {
|
||||
playing: false,
|
||||
isNotMute: false,
|
||||
quieting: false,
|
||||
fullscreen: false,
|
||||
loaded: false, // mute
|
||||
speed: 0,
|
||||
kBps: 0,
|
||||
btnDom: null,
|
||||
videoInfo: null,
|
||||
volume: 1,
|
||||
rotate: 0,
|
||||
vod: true, // 点播
|
||||
forceNoOffscreen: false,
|
||||
playerWidth: 0,
|
||||
playerHeight: 0,
|
||||
inited: false,
|
||||
playerLoading: false,
|
||||
mediaInfo: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
videoUrl(newData, oldData) {
|
||||
this.play(newData)
|
||||
},
|
||||
playing(newData, oldData) {
|
||||
this.$emit('playStatusChange', newData)
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
mounted() {
|
||||
const paramUrl = decodeURIComponent(this.$route.params.url)
|
||||
window.onresize = () => {
|
||||
this.updatePlayerDomSize()
|
||||
}
|
||||
this.btnDom = document.getElementById('buttonsBox')
|
||||
console.log('初始化时的地址为: ' + paramUrl)
|
||||
if (paramUrl) {
|
||||
this.play(this.videoUrl)
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
if (h265webPlayer[this._uid]) {
|
||||
h265webPlayer[this._uid].destroy()
|
||||
}
|
||||
this.playing = false
|
||||
this.loaded = false
|
||||
this.playerLoading = false
|
||||
},
|
||||
methods: {
|
||||
updatePlayerDomSize() {
|
||||
const dom = this.$refs.container
|
||||
if (!this.parentNodeResizeObserver) {
|
||||
this.parentNodeResizeObserver = new ResizeObserver(entries => {
|
||||
this.updatePlayerDomSize()
|
||||
})
|
||||
this.parentNodeResizeObserver.observe(dom.parentNode)
|
||||
}
|
||||
const boxWidth = dom.parentNode.clientWidth
|
||||
const boxHeight = dom.parentNode.clientHeight
|
||||
let width = boxWidth
|
||||
let height = (9 / 16) * width
|
||||
if (boxHeight > 0 && boxWidth > boxHeight / 9 * 16) {
|
||||
height = boxHeight
|
||||
width = boxHeight / 9 * 16
|
||||
}
|
||||
|
||||
const clientHeight = Math.min(document.body.clientHeight, document.documentElement.clientHeight)
|
||||
if (height > clientHeight) {
|
||||
height = clientHeight
|
||||
width = (16 / 9) * height
|
||||
}
|
||||
|
||||
this.$refs.playerBox.style.width = width + 'px'
|
||||
this.$refs.playerBox.style.height = height + 'px'
|
||||
this.playerWidth = width
|
||||
this.playerHeight = height
|
||||
if (this.playing) {
|
||||
h265webPlayer[this._uid].resize(this.playerWidth, this.playerHeight)
|
||||
}
|
||||
},
|
||||
resize(width, height) {
|
||||
this.playerWidth = width
|
||||
this.playerHeight = height
|
||||
this.$refs.playerBox.style.width = width + 'px'
|
||||
this.$refs.playerBox.style.height = height + 'px'
|
||||
if (this.playing) {
|
||||
h265webPlayer[this._uid].resize(this.playerWidth, this.playerHeight)
|
||||
}
|
||||
},
|
||||
create(url) {
|
||||
this.playerLoading = true
|
||||
const options = {}
|
||||
h265webPlayer[this._uid] = new window.new265webjs(url, Object.assign(
|
||||
{
|
||||
player: 'glplayer', // 播放器容器id
|
||||
width: this.playerWidth,
|
||||
height: this.playerHeight,
|
||||
token: token,
|
||||
extInfo: {
|
||||
coreProbePart: 0.4,
|
||||
probeSize: 8192,
|
||||
ignoreAudio: this.hasAudio == null ? 0 : (this.hasAudio ? 0 : 1)
|
||||
}
|
||||
},
|
||||
options
|
||||
))
|
||||
const h265web = h265webPlayer[this._uid]
|
||||
h265web.onOpenFullScreen = () => {
|
||||
this.fullscreen = true
|
||||
}
|
||||
h265web.onCloseFullScreen = () => {
|
||||
this.fullscreen = false
|
||||
}
|
||||
h265web.onReadyShowDone = () => {
|
||||
// 准备好显示了,尝试自动播放
|
||||
const result = h265web.play()
|
||||
this.playing = result
|
||||
this.playerLoading = false
|
||||
}
|
||||
h265web.onLoadFinish = () => {
|
||||
this.loaded = true
|
||||
// 可以获取mediaInfo
|
||||
// @see https://github.com/numberwolf/h265web.js/blob/8b26a31ffa419bd0a0f99fbd5111590e144e36a8/example_normal/index.js#L252C9-L263C11
|
||||
this.mediaInfo = h265web.mediaInfo()
|
||||
}
|
||||
h265web.onPlayTime = (videoPTS) => {
|
||||
this.$emit('playTimeChange', videoPTS)
|
||||
}
|
||||
h265web.do()
|
||||
},
|
||||
screenshot: function() {
|
||||
if (h265webPlayer[this._uid]) {
|
||||
const canvas = document.createElement('canvas')
|
||||
console.log(this.mediaInfo)
|
||||
canvas.width = this.mediaInfo.meta.size.width
|
||||
canvas.height = this.mediaInfo.meta.size.height
|
||||
h265webPlayer[this._uid].snapshot(canvas) // snapshot to canvas
|
||||
|
||||
// 下载截图
|
||||
const link = document.createElement('a')
|
||||
link.download = 'screenshot.png'
|
||||
link.href = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream')
|
||||
link.click()
|
||||
}
|
||||
},
|
||||
playBtnClick: function(event) {
|
||||
this.play(this.videoUrl)
|
||||
},
|
||||
play: function(url) {
|
||||
if (h265webPlayer[this._uid]) {
|
||||
this.destroy()
|
||||
}
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
if (this.playerWidth === 0 || this.playerHeight === 0) {
|
||||
this.updatePlayerDomSize()
|
||||
setTimeout(() => {
|
||||
this.play(url)
|
||||
}, 300)
|
||||
return
|
||||
}
|
||||
this.create(url)
|
||||
},
|
||||
unPause: function() {
|
||||
if (h265webPlayer[this._uid]) {
|
||||
h265webPlayer[this._uid].play()
|
||||
}
|
||||
this.playing = h265webPlayer[this._uid].isPlaying()
|
||||
this.err = ''
|
||||
},
|
||||
pause: function() {
|
||||
if (h265webPlayer[this._uid]) {
|
||||
h265webPlayer[this._uid].pause()
|
||||
}
|
||||
this.playing = h265webPlayer[this._uid].isPlaying()
|
||||
this.err = ''
|
||||
},
|
||||
mute: function() {
|
||||
if (h265webPlayer[this._uid]) {
|
||||
h265webPlayer[this._uid].setVoice(0.0)
|
||||
this.isNotMute = false
|
||||
}
|
||||
},
|
||||
cancelMute: function() {
|
||||
if (h265webPlayer[this._uid]) {
|
||||
h265webPlayer[this._uid].setVoice(1.0)
|
||||
this.isNotMute = true
|
||||
}
|
||||
},
|
||||
destroy: function() {
|
||||
if (h265webPlayer[this._uid]) {
|
||||
h265webPlayer[this._uid].release()
|
||||
}
|
||||
h265webPlayer[this._uid] = null
|
||||
this.playing = false
|
||||
this.err = ''
|
||||
},
|
||||
fullscreenSwich: function() {
|
||||
const isFull = this.isFullscreen()
|
||||
if (isFull) {
|
||||
h265webPlayer[this._uid].closeFullScreen()
|
||||
} else {
|
||||
h265webPlayer[this._uid].fullScreen()
|
||||
}
|
||||
this.fullscreen = !isFull
|
||||
},
|
||||
isFullscreen: function() {
|
||||
return document.fullscreenElement ||
|
||||
document.msFullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.webkitFullscreenElement || false
|
||||
},
|
||||
setPlaybackRate: function(speed) {
|
||||
h265webPlayer[this._uid].setPlaybackRate(speed)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.buttons-box {
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
background-color: rgba(43, 51, 63, 0.7);
|
||||
position: absolute;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
user-select: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.h265web-btn {
|
||||
width: 20px;
|
||||
color: rgb(255, 255, 255);
|
||||
line-height: 27px;
|
||||
margin: 0px 10px;
|
||||
padding: 0px 2px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
|
||||
.buttons-box-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
.player-loading {
|
||||
width: fit-content;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
left: calc(50% - 52px);
|
||||
top: calc(50% - 52px);
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
}
|
||||
.player-loading i{
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
.player-loading span{
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
</style>
|
||||
325
web/src/views/common/jessibuca.vue
Executable file
325
web/src/views/common/jessibuca.vue
Executable file
@@ -0,0 +1,325 @@
|
||||
<template>
|
||||
<div
|
||||
ref="container"
|
||||
style="width:100%; height: 100%; background-color: #000000;margin:0 auto;position: relative;"
|
||||
@dblclick="fullscreenSwich"
|
||||
>
|
||||
<div style="width:100%; padding-top: 56.25%; position: relative;" />
|
||||
<div id="buttonsBox" class="buttons-box">
|
||||
<div class="buttons-box-left">
|
||||
<i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick" />
|
||||
<i v-if="playing" class="iconfont icon-pause jessibuca-btn" @click="pause" />
|
||||
<i class="iconfont icon-stop jessibuca-btn" @click="destroy" />
|
||||
<i v-if="isNotMute" class="iconfont icon-audio-high jessibuca-btn" @click="mute()" />
|
||||
<i v-if="!isNotMute" class="iconfont icon-audio-mute jessibuca-btn" @click="cancelMute()" />
|
||||
</div>
|
||||
<div class="buttons-box-right">
|
||||
<span class="jessibuca-btn">{{ kBps }} kb/s</span>
|
||||
<!-- <i class="iconfont icon-file-record1 jessibuca-btn"></i>-->
|
||||
<!-- <i class="iconfont icon-xiangqing2 jessibuca-btn" ></i>-->
|
||||
<i
|
||||
class="iconfont icon-camera1196054easyiconnet jessibuca-btn"
|
||||
style="font-size: 1rem !important"
|
||||
@click="screenshot"
|
||||
/>
|
||||
<i class="iconfont icon-shuaxin11 jessibuca-btn" @click="playBtnClick" />
|
||||
<i v-if="!fullscreen" class="iconfont icon-weibiaoti10 jessibuca-btn" @click="fullscreenSwich" />
|
||||
<i v-if="fullscreen" class="iconfont icon-weibiaoti11 jessibuca-btn" @click="fullscreenSwich" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const jessibucaPlayer = {}
|
||||
export default {
|
||||
name: 'Jessibuca',
|
||||
props: ['videoUrl', 'error', 'hasAudio', 'height'],
|
||||
data() {
|
||||
return {
|
||||
playing: false,
|
||||
isNotMute: false,
|
||||
quieting: false,
|
||||
fullscreen: false,
|
||||
loaded: false, // mute
|
||||
speed: 0,
|
||||
performance: '', // 工作情况
|
||||
kBps: 0,
|
||||
btnDom: null,
|
||||
videoInfo: null,
|
||||
volume: 1,
|
||||
rotate: 0,
|
||||
vod: true, // 点播
|
||||
forceNoOffscreen: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
videoUrl: {
|
||||
handler(val, _) {
|
||||
this.$nextTick(() => {
|
||||
this.play(val)
|
||||
})
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const paramUrl = decodeURIComponent(this.$route.params.url)
|
||||
this.$nextTick(() => {
|
||||
this.updatePlayerDomSize()
|
||||
window.onresize = this.updatePlayerDomSize
|
||||
if (typeof (this.videoUrl) === 'undefined') {
|
||||
this.videoUrl = paramUrl
|
||||
}
|
||||
this.btnDom = document.getElementById('buttonsBox')
|
||||
})
|
||||
},
|
||||
// mounted() {
|
||||
// const ro = new ResizeObserver(entries => {
|
||||
// entries.forEach(entry => {
|
||||
// this.updatePlayerDomSize()
|
||||
// });
|
||||
// });
|
||||
// ro.observe(this.$refs.container);
|
||||
// },
|
||||
mounted() {
|
||||
this.updatePlayerDomSize()
|
||||
},
|
||||
destroyed() {
|
||||
if (jessibucaPlayer[this._uid]) {
|
||||
jessibucaPlayer[this._uid].destroy()
|
||||
}
|
||||
this.playing = false
|
||||
this.loaded = false
|
||||
this.performance = ''
|
||||
},
|
||||
methods: {
|
||||
updatePlayerDomSize() {
|
||||
const dom = this.$refs.container
|
||||
if (!this.parentNodeResizeObserver) {
|
||||
this.parentNodeResizeObserver = new ResizeObserver(entries => {
|
||||
this.updatePlayerDomSize()
|
||||
})
|
||||
this.parentNodeResizeObserver.observe(dom.parentNode)
|
||||
}
|
||||
const boxWidth = dom.parentNode.clientWidth
|
||||
const boxHeight = dom.parentNode.clientHeight
|
||||
let width = boxWidth
|
||||
let height = (9 / 16) * width
|
||||
if (boxHeight > 0 && boxWidth > boxHeight / 9 * 16) {
|
||||
height = boxHeight
|
||||
width = boxHeight / 9 * 16
|
||||
}
|
||||
|
||||
const clientHeight = Math.min(document.body.clientHeight, document.documentElement.clientHeight)
|
||||
if (height > clientHeight) {
|
||||
height = clientHeight
|
||||
width = (16 / 9) * height
|
||||
}
|
||||
|
||||
this.$refs.playerBox.style.width = width + 'px'
|
||||
this.$refs.playerBox.style.height = height + 'px'
|
||||
this.playerWidth = width
|
||||
this.playerHeight = height
|
||||
if (this.playing) {
|
||||
jessibucaPlayer[this._uid].resize(this.playerWidth, this.playerHeight)
|
||||
}
|
||||
},
|
||||
create() {
|
||||
const options = {
|
||||
container: this.$refs.container,
|
||||
autoWasm: true,
|
||||
background: '',
|
||||
controlAutoHide: false,
|
||||
debug: false,
|
||||
decoder: 'static/js/jessibuca/decoder.js',
|
||||
forceNoOffscreen: false,
|
||||
hasAudio: typeof (this.hasAudio) === 'undefined' ? true : this.hasAudio,
|
||||
heartTimeout: 5,
|
||||
heartTimeoutReplay: true,
|
||||
heartTimeoutReplayTimes: 3,
|
||||
hiddenAutoPause: false,
|
||||
hotKey: true,
|
||||
isFlv: false,
|
||||
isFullResize: false,
|
||||
isNotMute: this.isNotMute,
|
||||
isResize: false,
|
||||
keepScreenOn: true,
|
||||
loadingText: '请稍等, 视频加载中......',
|
||||
loadingTimeout: 10,
|
||||
loadingTimeoutReplay: true,
|
||||
loadingTimeoutReplayTimes: 3,
|
||||
openWebglAlignment: false,
|
||||
operateBtns: {
|
||||
fullscreen: false,
|
||||
screenshot: false,
|
||||
play: false,
|
||||
audio: false,
|
||||
record: false
|
||||
},
|
||||
recordType: 'mp4',
|
||||
rotate: 0,
|
||||
showBandwidth: false,
|
||||
supportDblclickFullscreen: false,
|
||||
timeout: 10,
|
||||
useMSE: true,
|
||||
useWCS: false,
|
||||
useWebFullScreen: true,
|
||||
videoBuffer: 0.1,
|
||||
wasmDecodeErrorReplay: true,
|
||||
wcsUseVideoRender: true
|
||||
}
|
||||
console.log('Jessibuca -> options: ', options)
|
||||
jessibucaPlayer[this._uid] = new window.Jessibuca({ ...options })
|
||||
|
||||
const jessibuca = jessibucaPlayer[this._uid]
|
||||
const _this = this
|
||||
jessibuca.on('pause', function() {
|
||||
_this.playing = false
|
||||
})
|
||||
jessibuca.on('play', function() {
|
||||
_this.playing = true
|
||||
})
|
||||
jessibuca.on('fullscreen', function(msg) {
|
||||
_this.fullscreen = msg
|
||||
})
|
||||
jessibuca.on('mute', function(msg) {
|
||||
_this.isNotMute = !msg
|
||||
})
|
||||
jessibuca.on('performance', function(performance) {
|
||||
let show = '卡顿'
|
||||
if (performance === 2) {
|
||||
show = '非常流畅'
|
||||
} else if (performance === 1) {
|
||||
show = '流畅'
|
||||
}
|
||||
_this.performance = show
|
||||
})
|
||||
jessibuca.on('kBps', function(kBps) {
|
||||
_this.kBps = Math.round(kBps)
|
||||
})
|
||||
jessibuca.on('videoInfo', function(msg) {
|
||||
console.log('Jessibuca -> videoInfo: ', msg)
|
||||
})
|
||||
jessibuca.on('audioInfo', function(msg) {
|
||||
console.log('Jessibuca -> audioInfo: ', msg)
|
||||
})
|
||||
jessibuca.on('error', function(msg) {
|
||||
console.log('Jessibuca -> error: ', msg)
|
||||
})
|
||||
jessibuca.on('timeout', function(msg) {
|
||||
console.log('Jessibuca -> timeout: ', msg)
|
||||
})
|
||||
jessibuca.on('loadingTimeout', function(msg) {
|
||||
console.log('Jessibuca -> timeout: ', msg)
|
||||
})
|
||||
jessibuca.on('delayTimeout', function(msg) {
|
||||
console.log('Jessibuca -> timeout: ', msg)
|
||||
})
|
||||
jessibuca.on('playToRenderTimes', function(msg) {
|
||||
console.log('Jessibuca -> playToRenderTimes: ', msg)
|
||||
})
|
||||
},
|
||||
playBtnClick: function(event) {
|
||||
this.play(this.videoUrl)
|
||||
},
|
||||
play: function(url) {
|
||||
console.log('Jessibuca -> url: ', url)
|
||||
if (jessibucaPlayer[this._uid]) {
|
||||
this.destroy()
|
||||
}
|
||||
this.create()
|
||||
jessibucaPlayer[this._uid].on('play', () => {
|
||||
this.playing = true
|
||||
this.loaded = true
|
||||
this.quieting = jessibuca.quieting
|
||||
})
|
||||
if (jessibucaPlayer[this._uid].hasLoaded()) {
|
||||
jessibucaPlayer[this._uid].play(url)
|
||||
} else {
|
||||
jessibucaPlayer[this._uid].on('load', () => {
|
||||
jessibucaPlayer[this._uid].play(url)
|
||||
})
|
||||
}
|
||||
},
|
||||
pause: function() {
|
||||
if (jessibucaPlayer[this._uid]) {
|
||||
jessibucaPlayer[this._uid].pause()
|
||||
}
|
||||
this.playing = false
|
||||
this.err = ''
|
||||
this.performance = ''
|
||||
},
|
||||
screenshot: function() {
|
||||
if (jessibucaPlayer[this._uid]) {
|
||||
jessibucaPlayer[this._uid].screenshot()
|
||||
}
|
||||
},
|
||||
mute: function() {
|
||||
if (jessibucaPlayer[this._uid]) {
|
||||
jessibucaPlayer[this._uid].mute()
|
||||
}
|
||||
},
|
||||
cancelMute: function() {
|
||||
if (jessibucaPlayer[this._uid]) {
|
||||
jessibucaPlayer[this._uid].cancelMute()
|
||||
}
|
||||
},
|
||||
destroy: function() {
|
||||
if (jessibucaPlayer[this._uid]) {
|
||||
jessibucaPlayer[this._uid].destroy()
|
||||
}
|
||||
if (document.getElementById('buttonsBox') == null) {
|
||||
this.$refs.container.appendChild(this.btnDom)
|
||||
}
|
||||
jessibucaPlayer[this._uid] = null
|
||||
this.playing = false
|
||||
this.err = ''
|
||||
this.performance = ''
|
||||
},
|
||||
fullscreenSwich: function() {
|
||||
const isFull = this.isFullscreen()
|
||||
jessibucaPlayer[this._uid].setFullscreen(!isFull)
|
||||
this.fullscreen = !isFull
|
||||
},
|
||||
isFullscreen: function() {
|
||||
return document.fullscreenElement ||
|
||||
document.msFullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.webkitFullscreenElement || false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.buttons-box {
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
background-color: rgba(43, 51, 63, 0.7);
|
||||
position: absolute;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
user-select: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.jessibuca-btn {
|
||||
width: 20px;
|
||||
color: rgb(255, 255, 255);
|
||||
line-height: 27px;
|
||||
margin: 0px 10px;
|
||||
padding: 0px 2px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
|
||||
.buttons-box-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
||||
97
web/src/views/common/mediaInfo.vue
Normal file
97
web/src/views/common/mediaInfo.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div id="mediaInfo">
|
||||
<el-button style="position: absolute; right: 1rem;" icon="el-icon-refresh-right" circle size="mini" @click="getMediaInfo" />
|
||||
<el-descriptions size="mini" :column="3" title="概况">
|
||||
<el-descriptions-item label="观看人数">{{ info.readerCount }}</el-descriptions-item>
|
||||
<el-descriptions-item label="网络">{{ formatByteSpeed() }}</el-descriptions-item>
|
||||
<el-descriptions-item label="持续时间">{{ info.aliveSecond }}秒</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr">
|
||||
<el-descriptions v-if="info.videoCodec" size="mini" :column="2" title="视频信息">
|
||||
<el-descriptions-item label="编码">{{ info.videoCodec }}</el-descriptions-item>
|
||||
<el-descriptions-item
|
||||
label="分辨率"
|
||||
>{{ info.width }}x{{ info.height }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="FPS">{{ info.fps }}</el-descriptions-item>
|
||||
<el-descriptions-item label="丢包率">{{ info.loss }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-descriptions v-if="info.audioCodec" size="mini" :column="2" title="音频信息">
|
||||
<el-descriptions-item label="编码">
|
||||
{{ info.audioCodec }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="采样率">{{ info.audioSampleRate }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'MediaInfo',
|
||||
components: {},
|
||||
props: ['app', 'stream', 'mediaServerId'],
|
||||
data() {
|
||||
return {
|
||||
info: {},
|
||||
task: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getMediaInfo()
|
||||
},
|
||||
methods: {
|
||||
getMediaInfo: function() {
|
||||
this.$store.dispatch('server/getMediaInfo', {
|
||||
app: this.app,
|
||||
stream: this.stream,
|
||||
mediaServerId: this.mediaServerId
|
||||
})
|
||||
.then(data => {
|
||||
this.info = data
|
||||
})
|
||||
},
|
||||
startTask: function() {
|
||||
this.task = setInterval(this.getMediaInfo, 1000)
|
||||
},
|
||||
stopTask: function() {
|
||||
if (this.task) {
|
||||
window.clearInterval(this.task)
|
||||
this.task = null
|
||||
}
|
||||
},
|
||||
formatByteSpeed: function() {
|
||||
const bytesSpeed = this.info.bytesSpeed
|
||||
const num = 1024.0 // byte
|
||||
if (bytesSpeed < num) return bytesSpeed + ' B/S'
|
||||
if (bytesSpeed < Math.pow(num, 2)) return (bytesSpeed / num).toFixed(2) + ' KB/S' // kb
|
||||
if (bytesSpeed < Math.pow(num, 3)) { return (bytesSpeed / Math.pow(num, 2)).toFixed(2) + ' MB/S' } // M
|
||||
if (bytesSpeed < Math.pow(num, 4)) { return (bytesSpeed / Math.pow(num, 3)).toFixed(2) + ' G/S' } // G
|
||||
return (bytesSpeed / Math.pow(num, 4)).toFixed(2) + ' T/S' // T
|
||||
},
|
||||
formatAliveSecond: function() {
|
||||
const aliveSecond = this.info.aliveSecond
|
||||
const h = parseInt(aliveSecond.value / 3600)
|
||||
const minute = parseInt((aliveSecond.value / 60) % 60)
|
||||
const second = Math.ceil(aliveSecond.value % 60)
|
||||
|
||||
const hours = h < 10 ? '0' + h : h
|
||||
const formatSecond = second > 59 ? 59 : second
|
||||
return `${hours > 0 ? `${hours}小时` : ''}${minute < 10 ? '0' + minute : minute}分${
|
||||
formatSecond < 10 ? '0' + formatSecond : formatSecond
|
||||
}秒`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.channel-form {
|
||||
display: grid;
|
||||
background-color: #FFFFFF;
|
||||
padding: 1rem 2rem 0 2rem;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
328
web/src/views/common/ptzCruising.vue
Normal file
328
web/src/views/common/ptzCruising.vue
Normal file
@@ -0,0 +1,328 @@
|
||||
<template>
|
||||
<div id="ptzCruising">
|
||||
<div style="display: grid; grid-template-columns: 80px auto; line-height: 28px">
|
||||
<span>巡航组号: </span>
|
||||
<el-input
|
||||
v-model="cruiseId"
|
||||
min="1"
|
||||
max="255"
|
||||
placeholder="巡航组号"
|
||||
addon-before="巡航组号"
|
||||
addon-after="(1-255)"
|
||||
size="mini"
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
<el-tag
|
||||
v-for="(item, index) in presetList"
|
||||
:key="item.presetId"
|
||||
closable
|
||||
style="margin-right: 1rem; cursor: pointer"
|
||||
@close="delPreset(item, index)"
|
||||
>
|
||||
{{ item.presetName ? item.presetName : item.presetId }}
|
||||
</el-tag>
|
||||
</p>
|
||||
|
||||
<el-form v-if="selectPresetVisible" size="mini" :inline="true">
|
||||
<el-form-item>
|
||||
<el-select v-model="selectPreset" placeholder="请选择预置点">
|
||||
<el-option
|
||||
v-for="item in allPresetList"
|
||||
:key="item.presetId"
|
||||
:label="item.presetName"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="addCruisePoint">保存</el-button>
|
||||
<el-button type="primary" @click="cancelAddCruisePoint">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button v-else size="mini" @click="selectPresetVisible=true">添加巡航点</el-button>
|
||||
|
||||
<el-form v-if="setSpeedVisible" size="mini" :inline="true">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-if="setSpeedVisible"
|
||||
v-model="cruiseSpeed"
|
||||
min="1"
|
||||
max="4095"
|
||||
placeholder="巡航速度"
|
||||
addon-before="巡航速度"
|
||||
addon-after="(1-4095)"
|
||||
size="mini"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="setCruiseSpeed">保存</el-button>
|
||||
<el-button @click="cancelSetCruiseSpeed">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button v-else size="mini" @click="setSpeedVisible = true">设置巡航速度</el-button>
|
||||
<el-form v-if="setTimeVisible" size="mini" :inline="true">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="cruiseTime"
|
||||
min="1"
|
||||
max="4095"
|
||||
placeholder="巡航停留时间(秒)"
|
||||
addon-before="巡航停留时间(秒)"
|
||||
addon-after="(1-4095)"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="setCruiseTime">保存</el-button>
|
||||
<el-button @click="cancelSetCruiseTime">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button v-else size="mini" @click="setTimeVisible = true">设置巡航时间</el-button>
|
||||
<el-button size="mini" @click="startCruise">开始巡航</el-button>
|
||||
<el-button size="mini" @click="stopCruise">停止巡航</el-button>
|
||||
<el-button size="mini" type="danger" @click="deleteCruise">删除巡航</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'PtzCruising',
|
||||
components: {},
|
||||
props: ['channelDeviceId', 'deviceId'],
|
||||
data() {
|
||||
return {
|
||||
cruiseId: 1,
|
||||
presetList: [],
|
||||
allPresetList: [],
|
||||
selectPreset: '',
|
||||
inputVisible: false,
|
||||
selectPresetVisible: false,
|
||||
setSpeedVisible: false,
|
||||
setTimeVisible: false,
|
||||
cruiseSpeed: '',
|
||||
cruiseTime: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getPresetList()
|
||||
},
|
||||
methods: {
|
||||
getPresetList: function() {
|
||||
this.$store.dispatch('frontEnd/queryPreset', [this.deviceId, this.channelDeviceId])
|
||||
.then((data) => {
|
||||
this.allPresetList = data
|
||||
})
|
||||
},
|
||||
addCruisePoint: 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/addPointForCruise',
|
||||
[this.deviceId, this.channelDeviceId, this.cruiseId, this.selectPreset.presetId])
|
||||
.then((data) => {
|
||||
this.presetList.push(this.selectPreset)
|
||||
}).catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
this.selectPreset = ''
|
||||
this.selectPresetVisible = false
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
cancelAddCruisePoint: function() {
|
||||
this.selectPreset = ''
|
||||
this.selectPresetVisible = false
|
||||
},
|
||||
delPreset: function(preset, index) {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
fullscreen: true,
|
||||
text: '正在发送指令',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
this.$store.dispatch('frontEnd/deletePointForCruise',
|
||||
[this.deviceId, this.channelDeviceId, this.cruiseId, preset.presetId])
|
||||
.then((data) => {
|
||||
this.presetList.splice(index, 1)
|
||||
}).catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
deleteCruise: function(preset, index) {
|
||||
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/deletePointForCruise',
|
||||
[this.deviceId, this.channelDeviceId, this.cruiseId, 0])
|
||||
.then((data) => {
|
||||
this.presetList = []
|
||||
}).catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
})
|
||||
},
|
||||
setCruiseSpeed: 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/setCruiseSpeed',
|
||||
[this.deviceId, this.channelDeviceId, this.cruiseId, this.cruiseSpeed])
|
||||
.then((data) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '保存成功',
|
||||
type: 'success'
|
||||
})
|
||||
}).catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
this.cruiseSpeed = ''
|
||||
this.setSpeedVisible = false
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
cancelSetCruiseSpeed: function() {
|
||||
this.cruiseSpeed = ''
|
||||
this.setSpeedVisible = false
|
||||
},
|
||||
setCruiseTime: 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/setCruiseTime',
|
||||
[this.deviceId, this.channelDeviceId, this.cruiseId, this.cruiseTime])
|
||||
.then((data) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '保存成功',
|
||||
type: 'success'
|
||||
})
|
||||
}).catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
this.setTimeVisible = false
|
||||
this.cruiseTime = ''
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
cancelSetCruiseTime: function() {
|
||||
this.setTimeVisible = false
|
||||
this.cruiseTime = ''
|
||||
},
|
||||
startCruise: 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/startCruise',
|
||||
[this.deviceId, this.channelDeviceId, this.cruiseId])
|
||||
.then((data) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '发送成功',
|
||||
type: 'success'
|
||||
})
|
||||
}).catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
this.setTimeVisible = false
|
||||
this.cruiseTime = ''
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
stopCruise: 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/stopCruise',
|
||||
[this.deviceId, this.channelDeviceId, this.cruiseId])
|
||||
.then((data) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '发送成功',
|
||||
type: 'success'
|
||||
})
|
||||
}).catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
this.setTimeVisible = false
|
||||
this.cruiseTime = ''
|
||||
loading.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.channel-form {
|
||||
display: grid;
|
||||
background-color: #FFFFFF;
|
||||
padding: 1rem 2rem 0 2rem;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
152
web/src/views/common/ptzPreset.vue
Normal file
152
web/src/views/common/ptzPreset.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div id="ptzPreset" style="width: 100%">
|
||||
<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 }}
|
||||
</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: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getPresetList()
|
||||
},
|
||||
methods: {
|
||||
getPresetList: function() {
|
||||
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, this.ptzPresetId])
|
||||
.then(data => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '调用成功',
|
||||
type: 'success'
|
||||
})
|
||||
}).catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
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, this.ptzPresetId])
|
||||
.then(data => {
|
||||
setTimeout(() => {
|
||||
this.getPresetList()
|
||||
}, 1000)
|
||||
}).catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
212
web/src/views/common/ptzScan.vue
Normal file
212
web/src/views/common/ptzScan.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<div id="ptzScan">
|
||||
<div style="display: grid; grid-template-columns: 80px auto; line-height: 28px">
|
||||
<span>扫描组号: </span>
|
||||
<el-input
|
||||
v-model="scanId"
|
||||
min="1"
|
||||
max="255"
|
||||
placeholder="扫描组号"
|
||||
addon-before="扫描组号"
|
||||
addon-after="(1-255)"
|
||||
size="mini"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-button size="mini" @click="setScanLeft">设置左边界</el-button>
|
||||
<el-button size="mini" @click="setScanRight">设置右边界</el-button>
|
||||
|
||||
<el-form v-if="setSpeedVisible" size="mini" :inline="true">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-if="setSpeedVisible"
|
||||
v-model="speed"
|
||||
min="1"
|
||||
max="4095"
|
||||
placeholder="巡航速度"
|
||||
addon-before="巡航速度"
|
||||
addon-after="(1-4095)"
|
||||
size="mini"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="setSpeed">保存</el-button>
|
||||
<el-button @click="cancelSetSpeed">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button v-else size="mini" @click="setSpeedVisible = true">设置扫描速度</el-button>
|
||||
|
||||
<el-button size="mini" @click="startScan">开始自动扫描</el-button>
|
||||
<el-button size="mini" @click="stopScan">停止自动扫描</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'PtzScan',
|
||||
components: {},
|
||||
props: ['channelDeviceId', 'deviceId'],
|
||||
data() {
|
||||
return {
|
||||
scanId: 1,
|
||||
setSpeedVisible: false,
|
||||
speed: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
setSpeed: 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/setSpeedForScan', [this.deviceId, this.channelDeviceId, this.scanId, this.speed])
|
||||
.then(data => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '保存成功',
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
this.speed = ''
|
||||
this.setSpeedVisible = false
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
cancelSetSpeed: function() {
|
||||
this.speed = ''
|
||||
this.setSpeedVisible = false
|
||||
},
|
||||
setScanLeft: 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/setLeftForScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||
.then(data => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '保存成功',
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
this.speed = ''
|
||||
this.setSpeedVisible = false
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
setScanRight: 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/setRightForScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||
.then(data => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '保存成功',
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
this.speed = ''
|
||||
this.setSpeedVisible = false
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
startScan: 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/startScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||
.then(data => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '发送成功',
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
stopScan: 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/stopScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||
.then(data => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '发送成功',
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.channel-form {
|
||||
display: grid;
|
||||
background-color: #FFFFFF;
|
||||
padding: 1rem 2rem 0 2rem;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
76
web/src/views/common/ptzSwitch.vue
Normal file
76
web/src/views/common/ptzSwitch.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div id="ptzScan">
|
||||
<el-form size="mini" :inline="true">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="switchId"
|
||||
min="1"
|
||||
max="4095"
|
||||
placeholder="开关编号"
|
||||
addon-before="开关编号"
|
||||
addon-after="(2-255)"
|
||||
size="mini"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="mini" @click="open('on')">开启</el-button>
|
||||
<el-button size="mini" @click="open('off')">关闭</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'PtzScan',
|
||||
components: {},
|
||||
props: ['channelDeviceId', 'deviceId'],
|
||||
data() {
|
||||
return {
|
||||
switchId: 1
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
open: function(command) {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
fullscreen: true,
|
||||
text: '正在发送指令',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
this.$store.dispatch('frontEnd/auxiliary', [this.deviceId, this.channelDeviceId, command, this.switchId])
|
||||
.then(data => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '保存成功',
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.channel-form {
|
||||
display: grid;
|
||||
background-color: #FFFFFF;
|
||||
padding: 1rem 2rem 0 2rem;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
58
web/src/views/common/ptzWiper.vue
Normal file
58
web/src/views/common/ptzWiper.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div id="ptzWiper">
|
||||
<el-button size="mini" @click="open('on')">开启</el-button>
|
||||
<el-button size="mini" @click="open('off')">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'PtzWiper',
|
||||
components: {},
|
||||
props: ['channelDeviceId', 'deviceId'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
open: function(command) {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
fullscreen: true,
|
||||
text: '正在发送指令',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
this.$store.dispatch('frontEnd/wiper', [this.deviceId, this.channelDeviceId, command])
|
||||
.then(data => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '保存成功',
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.channel-form {
|
||||
display: grid;
|
||||
background-color: #FFFFFF;
|
||||
padding: 1rem 2rem 0 2rem;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
481
web/src/views/common/weekTimePicker.vue
Normal file
481
web/src/views/common/weekTimePicker.vue
Normal file
@@ -0,0 +1,481 @@
|
||||
<template>
|
||||
<div id="weekTimePicker" class="week-time-picker">
|
||||
<el-row style="margin-left: 0">
|
||||
<el-col>
|
||||
<div>
|
||||
<el-row style="margin-left: 0">
|
||||
<el-col :span="24">
|
||||
<div class="time-select-header">
|
||||
<el-button @click="selectAll()">全选</el-button>
|
||||
<el-button @click="clearTrack()">清空</el-button>
|
||||
<el-button @click="removeSelectedTrack()">删除</el-button>
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="20" :offset="2">
|
||||
<div class="time-plan-ruler" style="width: 100%">
|
||||
<div v-for="index in 24*4" :key="index" :class="rulerClass(index - 1)" style="width: 1.04167%;">
|
||||
<span v-if="index === 0 || (index - 1) % 4 === 0 " class="ruler-text">{{
|
||||
(index - 1) / 4
|
||||
}}</span>
|
||||
<span v-if="index === 24*4" class="ruler-text">24</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-for="(week, index) in weekData" :key="index" class="time-select-main-container">
|
||||
<el-col :span="2" class="label">{{ week.name }}</el-col>
|
||||
<el-col :span="20">
|
||||
<div class="day-plan" @mousedown="dayPlanMousedown($event, index)">
|
||||
<div v-for="(track, trackIndex) in week.data" :key="trackIndex" class="track" :style="getTrackStyle(track)" @click.stop="selectTrack(trackIndex, index)" @mousedown.stop="">
|
||||
<el-tooltip v-show="checkSelected(trackIndex, index)" :ref="'startPointToolTip-' + index + '-' + trackIndex" :content="getTooltip(track.start)" :placement="(track.end - track.start) < 100 ? 'bottom': 'top'" :manual="true" :value="checkSelected(trackIndex, index)" effect="light" transition="el-zoom-in-top">
|
||||
<div ref="startPoint" class="hand" style="left: 0%" @mousedown.stop="startPointMousedown($event, index, trackIndex)" />
|
||||
</el-tooltip>
|
||||
<el-tooltip v-show="checkSelected(trackIndex, index)" :ref="'endPointToolTip-' + index + '-' + trackIndex" :content="getTooltip(track.end)" placement="top" :manual="true" :value="checkSelected(trackIndex, index)" effect="light" transition="el-zoom-in-top">
|
||||
<div class="hand" style="left: 100%;" @mousedown.stop="endPointMousedown($event, index, trackIndex)" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div v-if="tempTrack.index === index" class="track" :style="getTrackStyle(tempTrack)" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" class="operate">
|
||||
<el-popover
|
||||
:ref="'copyBox' + index"
|
||||
placement="right"
|
||||
width="400"
|
||||
trigger="click"
|
||||
>
|
||||
<div>
|
||||
<el-form size="mini" :inline="true">
|
||||
<el-form-item v-for="(data, indexForCopy) in weekDataForCopy(index)" :key="indexForCopy" :label="data.weekData.name">
|
||||
<el-checkbox v-model="weekDataCheckBox[data.index]" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div style="float: right;">
|
||||
<el-button @click="weekDataCheckBoxForAll(index)">全选</el-button>
|
||||
<el-button type="primary" @click="onSubmitCopy(index)">确认</el-button>
|
||||
<el-button @click="closeCopyBox(index)">取消</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<el-button slot="reference" type="text" size="medium">复制</el-button>
|
||||
</el-popover>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'WeekTimePicker',
|
||||
props: ['planArray'],
|
||||
emits: ['update:planArray'],
|
||||
data() {
|
||||
return {
|
||||
weekData: [
|
||||
{
|
||||
name: '星期一',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '星期二',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '星期三',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '星期四',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '星期五',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '星期六',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '星期天',
|
||||
data: []
|
||||
}
|
||||
|
||||
],
|
||||
weekDataCheckBox: [false, false, false, false, false, false, false],
|
||||
selectedTrack: {
|
||||
trackIndex: null,
|
||||
index: null
|
||||
},
|
||||
tempTrack: {
|
||||
index: null,
|
||||
start: null,
|
||||
end: null,
|
||||
x: null,
|
||||
clientWidth: null
|
||||
},
|
||||
startPointTrack: {
|
||||
index: null,
|
||||
trackIndex: null,
|
||||
x: null,
|
||||
clientWidth: null,
|
||||
target: null
|
||||
},
|
||||
endPointTrack: {
|
||||
index: null,
|
||||
trackIndex: null,
|
||||
x: null,
|
||||
clientWidth: null,
|
||||
target: null
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
value: {
|
||||
get() {
|
||||
return this.modelValue
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:modelValue', value)
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
planArray: function(array) {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
this.weekData[i].data = array[i].data
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
document.addEventListener('click', () => {
|
||||
this.selectedTrack.trackIndex = null
|
||||
this.selectedTrack.index = null
|
||||
})
|
||||
document.addEventListener('mousemove', this.dayPlanMousemove)
|
||||
document.addEventListener('mouseup', this.dayPlanMouseup)
|
||||
},
|
||||
methods: {
|
||||
rulerClass(index) {
|
||||
if (index === 0 || index % 4 === 0) {
|
||||
return 'hour ruler-section'
|
||||
} else {
|
||||
return 'ruler-section'
|
||||
}
|
||||
},
|
||||
checkSelected(trackIndex, index) {
|
||||
return index === this.selectedTrack.index && trackIndex === this.selectedTrack.trackIndex
|
||||
},
|
||||
selectTrack(trackIndex, index) {
|
||||
console.log(index)
|
||||
if (this.selectedTrack === index * 1000 + trackIndex) {
|
||||
return
|
||||
}
|
||||
this.selectedTrack.index = index
|
||||
this.selectedTrack.trackIndex = trackIndex
|
||||
},
|
||||
getTrackStyle(track) {
|
||||
const width = (100 / 24 / 60) * (track.end - track.start)
|
||||
const left = (100 / 24 / 60) * track.start
|
||||
return `left: ${left}%; width: ${width}%;`
|
||||
},
|
||||
getTooltip(time) {
|
||||
const hour = Math.floor(time / 60)
|
||||
const hourStr = hour < 10 ? '0' + hour : hour
|
||||
const minuteStr = (time - hour * 60) < 10 ? '0' + Math.floor((time - hour * 60)) : Math.floor(time - hour * 60)
|
||||
return hourStr + ':' + minuteStr
|
||||
},
|
||||
dayPlanMousedown(event, index) {
|
||||
this.tempTrack.index = index
|
||||
this.tempTrack.start = event.offsetX / event.target.clientWidth * 24 * 60
|
||||
this.tempTrack.x = event.screenX
|
||||
this.tempTrack.clientWidth = event.target.clientWidth
|
||||
this.selectedTrack.index = null
|
||||
this.selectedTrack.trackIndex = null
|
||||
},
|
||||
startPointMousedown(event, index, trackIndex) {
|
||||
this.startPointTrack.index = index
|
||||
this.startPointTrack.trackIndex = trackIndex
|
||||
this.startPointTrack.x = event.screenX
|
||||
this.startPointTrack.clientWidth = event.target.parentNode.parentNode.clientWidth
|
||||
this.startPointTrack.target = event.target
|
||||
},
|
||||
endPointMousedown(event, index, trackIndex) {
|
||||
this.endPointTrack.index = index
|
||||
this.endPointTrack.trackIndex = trackIndex
|
||||
this.endPointTrack.x = event.screenX
|
||||
this.endPointTrack.clientWidth = event.target.parentNode.parentNode.clientWidth
|
||||
this.endPointTrack.target = event.target
|
||||
},
|
||||
dayPlanMousemove(event) {
|
||||
if (this.tempTrack.index !== null) {
|
||||
if (event.screenX - this.tempTrack.x === 0) {
|
||||
return
|
||||
}
|
||||
let end = (event.screenX - this.tempTrack.x) / this.tempTrack.clientWidth * 24 * 60 + this.tempTrack.start
|
||||
if (end > 24 * 60) {
|
||||
end = 24 * 60
|
||||
}
|
||||
this.tempTrack.end = end
|
||||
} else if (this.startPointTrack.trackIndex !== null) {
|
||||
if (event.screenX - this.startPointTrack.x === 0) {
|
||||
return
|
||||
}
|
||||
let start = (event.screenX - this.startPointTrack.x) / this.startPointTrack.clientWidth * 24 * 60 +
|
||||
this.weekData[this.startPointTrack.index].data[this.startPointTrack.trackIndex].start
|
||||
|
||||
if (start < 0) {
|
||||
start = 0
|
||||
}
|
||||
this.weekData[this.startPointTrack.index].data[this.startPointTrack.trackIndex].start = start
|
||||
this.startPointTrack.x = event.screenX
|
||||
// 设置提示框位置
|
||||
this.$refs[`startPointToolTip-${this.startPointTrack.index}-${this.startPointTrack.trackIndex}`][0].popperElm.style.left = this.startPointTrack.target.getBoundingClientRect().left - 20 + 'px'
|
||||
this.updateValue()
|
||||
} else if (this.endPointTrack.trackIndex !== null) {
|
||||
if (event.screenX - this.endPointTrack.x === 0) {
|
||||
return
|
||||
}
|
||||
let end = (event.screenX - this.endPointTrack.x) / this.endPointTrack.clientWidth * 24 * 60 +
|
||||
this.weekData[this.endPointTrack.index].data[this.endPointTrack.trackIndex].end
|
||||
if (end > 24 * 60) {
|
||||
end = 24 * 60
|
||||
}
|
||||
this.weekData[this.endPointTrack.index].data[this.endPointTrack.trackIndex].end = end
|
||||
this.endPointTrack.x = event.screenX
|
||||
// 设置提示框位置
|
||||
this.$refs[`endPointToolTip-${this.endPointTrack.index}-${this.endPointTrack.trackIndex}`][0].popperElm.style.left = this.endPointTrack.target.getBoundingClientRect().left - 20 + 'px'
|
||||
this.updateValue()
|
||||
}
|
||||
},
|
||||
dayPlanMouseup(event) {
|
||||
if (this.startPointTrack.index !== null) {
|
||||
const track = this.weekData[this.startPointTrack.index].data[this.startPointTrack.trackIndex]
|
||||
this.trackHandler(this.startPointTrack.index, track.start, track.end)
|
||||
this.startPointTrack.index = null
|
||||
this.startPointTrack.trackIndex = null
|
||||
this.startPointTrack.x = null
|
||||
this.startPointTrack.clientWidth = null
|
||||
return
|
||||
}
|
||||
|
||||
if (this.endPointTrack.index !== null) {
|
||||
const track = this.weekData[this.endPointTrack.index].data[this.endPointTrack.trackIndex]
|
||||
this.trackHandler(this.endPointTrack.index, track.start, track.end)
|
||||
this.endPointTrack.index = null
|
||||
this.endPointTrack.trackIndex = null
|
||||
this.endPointTrack.x = null
|
||||
this.endPointTrack.clientWidth = null
|
||||
return
|
||||
}
|
||||
if (this.tempTrack.index === null) {
|
||||
return
|
||||
}
|
||||
if (this.tempTrack.end - this.tempTrack.start < 10) {
|
||||
this.tempTrack.index = null
|
||||
this.tempTrack.start = null
|
||||
this.tempTrack.end = null
|
||||
return
|
||||
}
|
||||
const index = this.tempTrack.index
|
||||
this.weekData[index].data.push({
|
||||
start: this.tempTrack.start,
|
||||
end: this.tempTrack.end
|
||||
})
|
||||
this.trackHandler(index, this.tempTrack.start, this.tempTrack.end)
|
||||
this.tempTrack.index = null
|
||||
this.tempTrack.start = null
|
||||
this.tempTrack.end = null
|
||||
this.updateValue()
|
||||
},
|
||||
trackHandler: function(index, start, end) {
|
||||
// 检查时间段是否重合 重合则合并
|
||||
this.weekData[index].data = this.checkTrack(this.weekData[index].data)
|
||||
this.selectedTrack.trackIndex = null
|
||||
setTimeout(() => {
|
||||
this.selectedTrack.index = index
|
||||
for (let i = 0; i < this.weekData[index].data.length; i++) {
|
||||
const current = this.weekData[index].data[i]
|
||||
if (current.start <= start && current.end >= end) {
|
||||
this.selectedTrack.trackIndex = i
|
||||
return
|
||||
}
|
||||
}
|
||||
}, 100)
|
||||
},
|
||||
removeSelectedTrack: function() {
|
||||
this.weekData[this.selectedTrack.index].data.splice(this.selectedTrack.trackIndex, 1)
|
||||
this.updateValue()
|
||||
},
|
||||
clearTrack: function() {
|
||||
for (let i = 0; i < this.weekData.length; i++) {
|
||||
const week = this.weekData[i]
|
||||
week.data.splice(0, week.data.length)
|
||||
}
|
||||
this.updateValue()
|
||||
},
|
||||
selectAll: function() {
|
||||
this.clearTrack()
|
||||
for (let i = 0; i < this.weekData.length; i++) {
|
||||
const week = this.weekData[i]
|
||||
week.data.push({
|
||||
start: 0,
|
||||
end: 24 * 60
|
||||
})
|
||||
}
|
||||
this.updateValue()
|
||||
},
|
||||
checkTrack: function(intervals) {
|
||||
if (intervals.length === 0) return []
|
||||
|
||||
// 按起始时间排序
|
||||
intervals.sort((a, b) => a.start - b.start)
|
||||
|
||||
const merged = [intervals[0]]
|
||||
|
||||
for (let i = 1; i < intervals.length; i++) {
|
||||
const current = intervals[i]
|
||||
const last = merged[merged.length - 1]
|
||||
|
||||
if (current.start <= last.end) {
|
||||
// 合并时间段
|
||||
last.end = Math.max(last.end, current.end)
|
||||
} else {
|
||||
merged.push(current)
|
||||
}
|
||||
}
|
||||
return merged
|
||||
},
|
||||
updateValue: function() {
|
||||
this.$emit('update:planArray', this.weekData)
|
||||
},
|
||||
weekDataForCopy(index) {
|
||||
const result = []
|
||||
for (let i = 0; i < this.weekData.length; i++) {
|
||||
if (i !== index) {
|
||||
result.push({
|
||||
weekData: this.weekData[i],
|
||||
index: i
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
},
|
||||
weekDataCheckBoxForAll: function(index) {
|
||||
for (let i = 0; i < this.weekDataCheckBox.length; i++) {
|
||||
if (i !== index) {
|
||||
this.$set(this.weekDataCheckBox, i, true)
|
||||
}
|
||||
}
|
||||
},
|
||||
onSubmitCopy: function(index) {
|
||||
const dataValue = this.weekData[index].data
|
||||
for (let i = 0; i < this.weekDataCheckBox.length; i++) {
|
||||
if (this.weekDataCheckBox[i]) {
|
||||
this.$set(this.weekData[i], 'data', JSON.parse(JSON.stringify(dataValue)))
|
||||
}
|
||||
}
|
||||
|
||||
this.closeCopyBox(index)
|
||||
},
|
||||
closeCopyBox: function(index) {
|
||||
this.weekDataCheckBox = [false, false, false, false, false, false, false]
|
||||
this.$refs['copyBox' + index][0].doClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.time-select-header {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
margin-bottom: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
.time-plan-ruler {
|
||||
height: 14px;
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
line-height: 23px;
|
||||
}
|
||||
|
||||
.time-plan-ruler .hour {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.time-plan-ruler div {
|
||||
display: inline-block;
|
||||
height: 5px;
|
||||
border-left: 1px solid #555;
|
||||
}
|
||||
|
||||
.time-plan-ruler div:last-child {
|
||||
border-right: 1px solid #555;
|
||||
border-left: 1px solid #555;
|
||||
}
|
||||
|
||||
.time-plan-ruler .ruler-text {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
margin-left: -5px;
|
||||
font: 11px / 1 sans-serif;
|
||||
}
|
||||
.time-plan-ruler div:last-child .ruler-text {
|
||||
margin-left: 0;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.time-select-main-container {
|
||||
border: 1px solid #e8e8e8;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 9px 0 4px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.time-select-main-container .label {
|
||||
line-height: 40px;
|
||||
float: left;
|
||||
height: 100%;
|
||||
padding-left: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.time-select-main-container .day-plan {
|
||||
position: relative;
|
||||
top: 15px;
|
||||
height: 12px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid #c5c5c5;
|
||||
background-color: #e8eaeb;
|
||||
cursor: pointer;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.time-select-main-container .day-plan .track{
|
||||
background: #52c41a;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
}
|
||||
.time-select-main-container .day-plan .track .hand{
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: -3px;
|
||||
margin-left: -6px;
|
||||
background-color: #fff;
|
||||
border: solid 2px #91d5ff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.time-select-main-container .operate {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user