feat:查询条件
No files matched your search
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<el-select style="width: 100%" v-model="state.selectId" :placeholder="props.placeholder"
|
||||
:clearable="props.clearable" remote-show-suffix filterable remote :disabled="props.disabled"
|
||||
:remote-method="remoteSearch" :loading="state.loading" @visible-change="(e: boolean) => e && getOptions()"
|
||||
@change="change">
|
||||
<el-option v-for="v in state.options" :key="v.uniqueCode" :label="`${v.name}`" :value="v.uniqueCode">
|
||||
<div class="flex items-center">
|
||||
<div class="color-primary">{{ v.name }}</div>
|
||||
<div class="flex items-center justify-center ml5">
|
||||
<dict-tag :options="state.hardType" :value="v.type" />
|
||||
</div>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="select-area-type">
|
||||
import { reactive } from "vue"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import * as AreaManageApi from "/@/api/area/areaManage"
|
||||
|
||||
const emit = defineEmits(["change"])
|
||||
|
||||
const selectId = defineModel()
|
||||
|
||||
const AREA_TTYPE = {
|
||||
car: ["NM", "BM", "DM"],
|
||||
person: ["NM", "BM", "DM", "BGL"],
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// car | person
|
||||
areaType?: string
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
clearable?: boolean
|
||||
autoFirst?: boolean
|
||||
apiParams?: any
|
||||
}>(),
|
||||
{
|
||||
// car | person
|
||||
areaType: "car",
|
||||
placeholder: "请选择",
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
autoFirst: false,
|
||||
apiParams: {},
|
||||
},
|
||||
)
|
||||
|
||||
const queryForm = reactive<any>({
|
||||
name: "",
|
||||
current: 1,
|
||||
size: 10,
|
||||
})
|
||||
|
||||
const state = reactive<any>({
|
||||
loading: true,
|
||||
selectId: selectId,
|
||||
options: [],
|
||||
hardType: [],
|
||||
})
|
||||
|
||||
watch(
|
||||
selectId,
|
||||
(newVal, oldValue) => {
|
||||
if (newVal == oldValue) return
|
||||
state.selectId = newVal
|
||||
getOptions()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
async function getOptions() {
|
||||
try {
|
||||
state.loading = true
|
||||
const _pa = {
|
||||
...queryForm,
|
||||
...props.apiParams,
|
||||
}
|
||||
if (props.areaType && props.areaType == "car") {
|
||||
_pa.uniqueCodeList = AREA_TTYPE[props.areaType]
|
||||
}
|
||||
if (props.areaType && props.areaType == "person") {
|
||||
_pa.uniqueCodeList = AREA_TTYPE[props.areaType]
|
||||
}
|
||||
const { data } = await AreaManageApi.select(_pa)
|
||||
state.options = data.records ?? []
|
||||
if (props.autoFirst && state.options.length != 0) {
|
||||
state.selectId = state.options[0].uniqueCode
|
||||
emit("change", state.selectId)
|
||||
}
|
||||
const { hard_type } = useDict("hard_type")
|
||||
state.hardType = hard_type
|
||||
} finally {
|
||||
setTimeout(() => (state.loading = false), 500)
|
||||
}
|
||||
}
|
||||
|
||||
function remoteSearch(params: string) {
|
||||
if (params != queryForm.name) {
|
||||
queryForm.name = params
|
||||
getOptions()
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
state.selectId = null
|
||||
getOptions()
|
||||
}
|
||||
|
||||
function change() {
|
||||
emit("change", selectId)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
props.autoFirst && getOptions()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<dict-select style="width: 100%" v-model="state.selectId" :options="selectOptions" :placeholder="placeholder" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="select-person-car-type">
|
||||
import { reactive } from "vue"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
|
||||
const selectId = defineModel()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// car | person
|
||||
areaType?: string
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
clearable?: boolean
|
||||
autoFirst?: boolean
|
||||
apiParams?: any
|
||||
}>(),
|
||||
{
|
||||
// car | person
|
||||
areaType: "car",
|
||||
placeholder: "请选择",
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
autoFirst: false,
|
||||
apiParams: {},
|
||||
},
|
||||
)
|
||||
|
||||
const { car_type, person_type } = useDict("car_type", "person_type")
|
||||
|
||||
const selectOptions = computed(() => {
|
||||
const list = props.areaType == "car" ? car_type.value : person_type.value
|
||||
if (!list) {
|
||||
return []
|
||||
}
|
||||
return list
|
||||
})
|
||||
|
||||
const state = reactive<any>({
|
||||
loading: true,
|
||||
selectId: selectId,
|
||||
options: [],
|
||||
hardType: [],
|
||||
})
|
||||
|
||||
watch(
|
||||
selectId,
|
||||
(newVal, oldValue) => {
|
||||
if (newVal == oldValue) return
|
||||
state.selectId = newVal
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
@@ -35,6 +35,8 @@ import SelectTreeChannel from "/@/components/Select/SelectTreeChannel.vue"
|
||||
import SelectTreeChannelRadio from "/@/components/Select/SelectTreeChannelRadio.vue"
|
||||
import SelectTreeDept from "/@/components/Select/SelectTreeDept.vue"
|
||||
import SelectDeviceTree from "/@/components/Select/SelectDeviceTree.vue"
|
||||
import SelectAreaType from "/@/components/Select/SelectAreaType.vue"
|
||||
import SelectPersonCarType from "/@/components/Select/SelectPersonCarType.vue"
|
||||
|
||||
import ColumnBtn from "/@/components/Column/ColumnBtn.vue"
|
||||
|
||||
@@ -99,6 +101,8 @@ export default {
|
||||
app.component('SelectTreeChannelRadio', SelectTreeChannelRadio);
|
||||
app.component('SelectTreeDept', SelectTreeDept);
|
||||
app.component('SelectDeviceTree', SelectDeviceTree);
|
||||
app.component('SelectAreaType', SelectAreaType);
|
||||
app.component('SelectPersonCarType', SelectPersonCarType);
|
||||
|
||||
app.component('ColumnBtn', ColumnBtn);
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
云台样式
|
||||
*/
|
||||
.ws-pan-tilt-control{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border: 1px solid #e9ebee;
|
||||
padding-top: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ws-pan-tilt-circle{
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
background-color: #f5f6f9;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ws-pan-tilt-direction-item{
|
||||
position: absolute;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
transform-origin: 100% 100%;
|
||||
transition: border 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ws-pan-tilt-direction-item:nth-child(1){
|
||||
transform: rotate(66deg) skew(45deg);
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(1) img{
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
transform: skew(-45deg) rotate(-66deg) translate(-1px, -8px);
|
||||
top: 55px;
|
||||
left: 52px;
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(2){
|
||||
transform: rotate(111deg) skew(45deg);
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(2) img{
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
transform: skew(-45deg) rotate(-111deg) translate(1px, 2px);
|
||||
top: 55px;
|
||||
left: 44px;
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(3){
|
||||
transform: rotate(156deg) skew(45deg);
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(3) img{
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
transform: skew(-45deg) rotate(-156deg) translate(0px, 3px);
|
||||
top: 55px;
|
||||
left: 44px;
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(4){
|
||||
transform: rotate(-159deg) skew(45deg);
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(4) img{
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
transform: skew(-45deg) rotate(159deg) translate(0px, 1px);
|
||||
top: 55px;
|
||||
left: 46px;
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(5){
|
||||
transform: rotate(-114deg) skew(45deg);
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(5) img{
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
transform: skew(-45deg) rotate(114deg) translate(2px, -2px);
|
||||
top: 51px;
|
||||
left: 48px;
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(6){
|
||||
transform: rotate(-69deg) skew(45deg);
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(6) img{
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
transform: skew(-45deg) rotate(69deg) translate(2px, 2px);
|
||||
top: 52px;
|
||||
left: 50px;
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(7){
|
||||
transform: rotate(-24deg) skew(45deg);
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(7) img{
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
transform: skew(-45deg) rotate(24deg) translate(2px, 2px);
|
||||
top: 50px;
|
||||
left: 48px;
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(8){
|
||||
transform: rotate(21deg) skew(45deg);
|
||||
}
|
||||
.ws-pan-tilt-direction-item:nth-child(8) img{
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
transform: skew(-45deg) rotate(-21deg) translate(0px, 2px);
|
||||
top: 52px;
|
||||
left: 49px;
|
||||
}
|
||||
.ws-pan-tilt-inner-circle{
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
top: 28%;
|
||||
left: 28%;
|
||||
}
|
||||
|
||||
.cloud-control-wrapper{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 42px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 1px solid #e9ebee;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.cloud-control-wrapper .ws-pan-tilt-control-item img{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.cloud-control-wrapper .cloud-control-separate{
|
||||
border-left: 1px solid #e9ebee;
|
||||
height: 20px;
|
||||
}
|
||||
.ws-pan-tilt-mask {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
cursor: not-allowed;
|
||||
background-color: #ccc;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.ws-pan-tilt-mask-direction {
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 42px);
|
||||
}
|
||||
.ws-pan-tilt-mask-zoom {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 42px;
|
||||
width: calc(50% + 36px);
|
||||
}
|
||||
.ws-pan-tilt-mask-aperture {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 42px;
|
||||
width: calc(50% - 36px);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
const arrowT = new URL('./svg/arrow-t.svg', import.meta.url).href
|
||||
const arrowRt = new URL('./svg/arrow-rt.svg', import.meta.url).href
|
||||
const arrowR = new URL('./svg/arrow-r.svg', import.meta.url).href
|
||||
const arrowRb = new URL('./svg/arrow-rb.svg', import.meta.url).href
|
||||
const arrowB = new URL('./svg/arrow-b.svg', import.meta.url).href
|
||||
const arrowLb = new URL('./svg/arrow-lb.svg', import.meta.url).href
|
||||
const arrowL = new URL('./svg/arrow-l.svg', import.meta.url).href
|
||||
const arrowLt = new URL('./svg/arrow-lt.svg', import.meta.url).href
|
||||
const ptzIcon1 = new URL('./svg/ptz-icon1.svg', import.meta.url).href
|
||||
const ptzIcon2 = new URL('./svg/ptz-icon2.svg', import.meta.url).href
|
||||
const ptzIcon3 = new URL('./svg/ptz-icon3.svg', import.meta.url).href
|
||||
const ptzIcon4 = new URL('./svg/ptz-icon4.svg', import.meta.url).href
|
||||
const ptzIcon5 = new URL('./svg/ptz-icon5.svg', import.meta.url).href
|
||||
const ptzIcon6 = new URL('./svg/ptz-icon6.svg', import.meta.url).href
|
||||
|
||||
class PanTilt {
|
||||
constructor(options = {}) {
|
||||
// 云台节点
|
||||
this.el = options.el;
|
||||
// 云台元素
|
||||
this.$el = document.querySelector('#' + this.el)
|
||||
if (this.$el) {
|
||||
this.__createPanTilt();
|
||||
}
|
||||
// 选中的窗口的正在播放的通道
|
||||
this.channel = null;
|
||||
// 相关的请求接口方法
|
||||
// 云台方向控制接口
|
||||
this.setPtzDirection = options.setPtzDirection;
|
||||
// 云台镜头控制接口
|
||||
this.setPtzCamera = options.setPtzCamera;
|
||||
this.clickDirectFlag = false;
|
||||
this.setPtzDirectionPromiseList = [];
|
||||
this.setPtzDirectionRun = false;
|
||||
this.setPtzCameraPromiseList = [];
|
||||
this.setPtzCameraRun = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置通道
|
||||
* @param channel
|
||||
*/
|
||||
setChannel(channel) {
|
||||
this.channel = { ...channel };
|
||||
if (!channel) {
|
||||
// 通道不存在,则禁用云台
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask`).style.display = 'block'
|
||||
return;
|
||||
}
|
||||
let capability = channel.capability;
|
||||
switch (String(channel.cameraType)) {
|
||||
case "1": // 枪机通道
|
||||
// 枪机通道能力集有电动聚焦或者云台控制时,开放变倍、变焦
|
||||
if (parseInt(capability, 2) & parseInt("100", 2)
|
||||
|| parseInt(capability, 2) & parseInt("10000000000000000", 2)
|
||||
) {
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask-zoom`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask-zoom`).style.display = 'block'
|
||||
}
|
||||
|
||||
// 枪机通道能力集有云台控制时,开放方向控制
|
||||
if (parseInt(capability, 2) & parseInt("10000000000000000", 2)) {
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask-direction`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask-direction`).style.display = 'block'
|
||||
}
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask-aperture`).style.display = 'block'
|
||||
break;
|
||||
case "2": // 球机通道
|
||||
// 球机通道,可以使用云台所有功能
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask`).style.display = 'none'
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask-zoom`).style.display = 'none'
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask-aperture`).style.display = 'none'
|
||||
break;
|
||||
default:
|
||||
// 默认禁用云台
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask`).style.display = 'block'
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask-zoom`).style.display = 'block'
|
||||
document.querySelector(`#${this.el} .ws-pan-tilt-mask-aperture`).style.display = 'block'
|
||||
}
|
||||
}
|
||||
|
||||
__createPanTilt() {
|
||||
this.$el.innerHTML = `
|
||||
<div class="ws-pan-tilt-control">
|
||||
<!--云台方向控制-->
|
||||
<div class="ws-pan-tilt-circle">
|
||||
<div class="ws-pan-tilt-direction-item"><img src="${arrowT}" title="上" direct="1"/></div>
|
||||
<div class="ws-pan-tilt-direction-item"><img src="${arrowRt}" title="右上" direct="7"/></div>
|
||||
<div class="ws-pan-tilt-direction-item"><img src="${arrowR}" title="右" direct="4"/></div>
|
||||
<div class="ws-pan-tilt-direction-item"><img src="${arrowRb}" title="右下" direct="8"/></div>
|
||||
<div class="ws-pan-tilt-direction-item"><img src="${arrowB}" title="下" direct="2"/></div>
|
||||
<div class="ws-pan-tilt-direction-item"><img src="${arrowLb}" title="左下" direct="6"/></div>
|
||||
<div class="ws-pan-tilt-direction-item"><img src="${arrowL}" title="左" direct="3"/></div>
|
||||
<div class="ws-pan-tilt-direction-item"><img src="${arrowLt}" title="左上" direct="5"/></div>
|
||||
<div class="ws-pan-tilt-inner-circle">
|
||||
<div class="three-circle">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--云台变倍、聚焦、光圈控制-->
|
||||
<div class="cloud-control-wrapper">
|
||||
<div class="ws-pan-tilt-control-item"><img src="${ptzIcon1}" title="变倍-" operateType="1" direct="2"/></div>
|
||||
<div class="ws-pan-tilt-control-item"><img src="${ptzIcon2}" title="变倍+" operateType="1" direct="1"/></div>
|
||||
<div class="cloud-control-separate"></div>
|
||||
<div class="ws-pan-tilt-control-item"><img src="${ptzIcon3}" title="聚焦-" operateType="2" direct="2"/></div>
|
||||
<div class="ws-pan-tilt-control-item"><img src="${ptzIcon4}" title="聚焦+" operateType="2" direct="1"/></div>
|
||||
<div class="cloud-control-separate"></div>
|
||||
<div class="ws-pan-tilt-control-item"><img src="${ptzIcon5}" title="光圈-" operateType="3" direct="2"/></div>
|
||||
<div class="ws-pan-tilt-control-item"><img src="${ptzIcon6}" title="光圈+" operateType="3" direct="1"/></div>
|
||||
</div>
|
||||
|
||||
<!--遮罩,当通道没有云台功能时,使用遮罩遮住云台按钮-->
|
||||
<!--方向按钮遮罩-->
|
||||
<div class="ws-pan-tilt-mask ws-pan-tilt-mask-direction"></div>
|
||||
<!--变倍、聚焦遮罩-->
|
||||
<div class="ws-pan-tilt-mask ws-pan-tilt-mask-zoom"></div>
|
||||
<!--光圈遮罩-->
|
||||
<div class="ws-pan-tilt-mask ws-pan-tilt-mask-aperture"></div>
|
||||
</div>
|
||||
`
|
||||
// 添加监听
|
||||
this.addListener()
|
||||
|
||||
}
|
||||
|
||||
addListener() {
|
||||
try {
|
||||
let domList = document.querySelectorAll(`#${this.el} .ws-pan-tilt-direction-item img`)
|
||||
domList.forEach(dom => {
|
||||
dom.addEventListener('mouseup', (e) => {
|
||||
if (this.clickDirectFlag) {
|
||||
this.clickDirectFlag = false
|
||||
this.__setPtzDirection.call(this, e.target.getAttribute("direct"), "0")
|
||||
}
|
||||
})
|
||||
dom.addEventListener('mousedown', this._throttle((e) => {
|
||||
if (!this.clickDirectFlag) {
|
||||
this.clickDirectFlag = true
|
||||
this.__setPtzDirection.call(this, e.target.getAttribute("direct"), "1")
|
||||
}
|
||||
}))
|
||||
dom.addEventListener('mouseout', this._throttle((e) => {
|
||||
if (this.clickDirectFlag) {
|
||||
this.clickDirectFlag = false
|
||||
this.__setPtzDirection.call(this, e.target.getAttribute("direct"), "0")
|
||||
}
|
||||
}))
|
||||
})
|
||||
let domList2 = document.querySelectorAll(`#${this.el} .ws-pan-tilt-control-item img`)
|
||||
domList2.forEach(dom => {
|
||||
dom.addEventListener('mouseup', (e) => {
|
||||
if (this.clickDirectFlag) {
|
||||
this.clickDirectFlag = false
|
||||
this.__setPtzCamera.call(this, e.target.getAttribute("operateType"), e.target.getAttribute("direct"), "0")
|
||||
}
|
||||
})
|
||||
dom.addEventListener('mousedown', this._throttle((e) => {
|
||||
if (!this.clickDirectFlag) {
|
||||
this.clickDirectFlag = true
|
||||
this.__setPtzCamera.call(this, e.target.getAttribute("operateType"), e.target.getAttribute("direct"), "1")
|
||||
}
|
||||
}))
|
||||
dom.addEventListener('mouseout', this._throttle((e) => {
|
||||
if (this.clickDirectFlag) {
|
||||
this.clickDirectFlag = false
|
||||
this.__setPtzCamera.call(this, e.target.getAttribute("operateType"), e.target.getAttribute("direct"), "0")
|
||||
}
|
||||
}))
|
||||
})
|
||||
} catch (err) {
|
||||
let timer = setTimeout(() => {
|
||||
this.addListener()
|
||||
clearTimeout(timer)
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
__setPtzDirection(direct, command) {
|
||||
// 方向:1=上,2=下,3=左,4=右,5=左上,6=左下,7=右上,8=右下
|
||||
const params = {
|
||||
project: 'PSDK',
|
||||
method: 'DMS.Ptz.OperateDirect',
|
||||
data: {
|
||||
direct,
|
||||
command,
|
||||
stepX: '4',
|
||||
stepY: '4',
|
||||
channelId: this.channel.id
|
||||
}
|
||||
};
|
||||
this.setPtzDirectionPromiseList.push(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.setPtzDirection && this.setPtzDirection(params).then(() => {
|
||||
resolve()
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
})
|
||||
if (!this.setPtzDirectionRun) {
|
||||
let reqQueue = () => {
|
||||
this.setPtzDirectionRun = true
|
||||
this.setPtzDirectionPromiseList[0]().then(() => {
|
||||
}).catch((err) => {
|
||||
console.error('云台方向控制err:', err);
|
||||
}).finally(() => {
|
||||
this.setPtzDirectionPromiseList.shift()
|
||||
if (!this.setPtzDirectionPromiseList.length) {
|
||||
this.setPtzDirectionRun = false
|
||||
} else {
|
||||
reqQueue()
|
||||
}
|
||||
})
|
||||
}
|
||||
reqQueue()
|
||||
}
|
||||
}
|
||||
|
||||
__setPtzCamera(operateType, direct, command) {
|
||||
// operateType 操作类型:1=变倍,2=变焦,3=光圈
|
||||
// direct 方向:1=增加,2=减小
|
||||
// command 命令:0=停止,1=开启
|
||||
const params = {
|
||||
project: 'PSDK',
|
||||
method: 'DMS.Ptz.OperateCamera',
|
||||
data: {
|
||||
operateType,
|
||||
direct,
|
||||
command,
|
||||
step: '4',
|
||||
channelId: this.channel.id
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.setPtzCameraPromiseList.push(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.setPtzCamera && this.setPtzCamera(params).then(() => {
|
||||
resolve()
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
})
|
||||
if (!this.setPtzCameraRun) {
|
||||
let reqQueue = () => {
|
||||
this.setPtzCameraRun = true
|
||||
this.setPtzCameraPromiseList[0]().then(() => {
|
||||
}).catch((err) => {
|
||||
console.error('云台方向控制err:', err);
|
||||
}).finally(() => {
|
||||
this.setPtzCameraPromiseList.shift()
|
||||
if (!this.setPtzCameraPromiseList.length) {
|
||||
this.setPtzCameraRun = false
|
||||
} else {
|
||||
reqQueue()
|
||||
}
|
||||
})
|
||||
}
|
||||
reqQueue()
|
||||
}
|
||||
}
|
||||
|
||||
_throttle(func, delay = 300) {
|
||||
let lastCall = 0;
|
||||
return function (...args) {
|
||||
const now = new Date().getTime();
|
||||
if (now - lastCall < delay) {
|
||||
return;
|
||||
}
|
||||
lastCall = now;
|
||||
return func.apply(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PanTilt
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="_5-2" class="st0" d="M10.1,15L18,7.1V5.5H2.2v1.6L10.1,15z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 478 B |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="_7-2" class="st0" d="M5.1,10l7.9,7.9h1.6V2.1H13L5.1,10z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 476 B |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="_6-2" class="st0" d="M6.9,14.7h6.2c0.9,0,1.6-0.7,1.6-1.6l0,0L6.9,5.3C6,5.3,5.3,6,5.3,6.9l0,0v6.2
|
||||
C5.3,14,6,14.7,6.9,14.7C6.9,14.7,6.9,14.7,6.9,14.7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 570 B |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="_8-2" class="st0" d="M6.9,5.5h6.2c0.9,0,1.6,0.7,1.6,1.6l0,0l-7.8,7.8c-0.9,0-1.6-0.7-1.6-1.6l0,0V7.1
|
||||
C5.3,6.2,6,5.5,6.9,5.5C6.9,5.5,6.9,5.5,6.9,5.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 569 B |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="_3-2" class="st0" d="M15.1,10l-7.9,7.9H5.6V2.1h1.6L15.1,10z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 480 B |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="_4-2" class="st0" d="M13.5,14.7H7.3c-0.9,0-1.6-0.7-1.6-1.6l0,0l7.8-7.8c0.9,0,1.6,0.7,1.6,1.6l0,0v6.2
|
||||
C15.1,14,14.4,14.7,13.5,14.7C13.5,14.7,13.5,14.7,13.5,14.7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 582 B |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="_2-2" class="st0" d="M13.5,5.5H7.3c-0.9,0-1.6,0.7-1.6,1.6l0,0l7.8,7.8c0.9,0,1.6-0.7,1.6-1.6l0,0V7.1
|
||||
C15.1,6.2,14.4,5.5,13.5,5.5C13.5,5.5,13.5,5.5,13.5,5.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 577 B |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="_1-2" class="st0" d="M10.1,5l7.9,7.9v1.6H2.2v-1.6L10.1,5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 478 B |
@@ -0,0 +1,4 @@
|
||||
<svg id="_1" data-name="1" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<rect id="矩形_552" data-name="矩形 552" width="20" height="20" fill="none" opacity="0"/>
|
||||
<path id="联合_354" data-name="联合 354" d="M-5040.636-9881.895l-2.5-2.5a6.963,6.963,0,0,1-4.192,1.4,7.009,7.009,0,0,1-7-7,7.009,7.009,0,0,1,7-7,7.01,7.01,0,0,1,7,7,6.966,6.966,0,0,1-1.4,4.192l2.5,2.5a1,1,0,0,1,0,1.413,1,1,0,0,1-.707.293A1,1,0,0,1-5040.636-9881.895Zm-11.689-8.1a5.005,5.005,0,0,0,5,5,5.006,5.006,0,0,0,5-5,5.006,5.006,0,0,0-5-5A5.005,5.005,0,0,0-5052.325-9890Zm3.73,1a1,1,0,0,1-1-1,1,1,0,0,1,1-1h3a1,1,0,0,1,1,1,1,1,0,0,1-1,1Z" transform="translate(5056.266 9898.999)" fill="#657685"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 718 B |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="联合_355" class="st0" d="M15.6,17.1l-2.5-2.5c-1.2,0.9-2.7,1.4-4.2,1.4c-3.9,0-7-3.1-7-7c0-3.9,3.1-7,7-7
|
||||
c3.9,0,7,3.1,7,7c0,1.5-0.5,3-1.4,4.2l2.5,2.5c0.4,0.4,0.4,1,0,1.4c-0.2,0.2-0.4,0.3-0.7,0.3C16.1,17.4,15.8,17.3,15.6,17.1z M3.9,9
|
||||
c0,2.8,2.2,5,5,5c2.8,0,5-2.2,5-5c0-2.8-2.2-5-5-5C6.2,4,3.9,6.2,3.9,9L3.9,9z M8.2,10.5V10H7.7c-0.6,0-1-0.4-1-1s0.4-1,1-1h0.5V7.5
|
||||
c0-0.6,0.4-1,1-1s1,0.4,1,1V8h0.5c0.6,0,1,0.4,1,1s-0.4,1-1,1h-0.5v0.5c0,0.6-0.4,1-1,1C8.6,11.5,8.2,11.1,8.2,10.5
|
||||
C8.2,10.5,8.2,10.5,8.2,10.5L8.2,10.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 929 B |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="_3" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="联合_357" class="st0" d="M15,17h-3v-2h3v-3h2v5H15z M3,17v-5h2v3h3v2H3z M9,10c0-0.6,0.4-1,1-1s1,0.4,1,1s-0.4,1-1,1
|
||||
C9.5,11,9,10.6,9,10C9,10,9,10,9,10L9,10z M15,8V5h-3V3h5v5H15z M3,8V3h5v2H5v3H3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 611 B |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="_4" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="联合_357" class="st0" d="M15,17h-3v-2h3v-3h2v5H15z M3,17v-5h2v3h3v2H3z M8,10c0-1.1,0.9-2,2-2s2,0.9,2,2s-0.9,2-2,2
|
||||
C8.9,12,8,11.1,8,10C8,10,8,10,8,10L8,10z M15,8V5h-3V3h5v5H15z M3,8V3h5v2H5v3H3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 611 B |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="联合_358" class="st0" d="M5.2,15.1l2.6-4.6l3.7,6.3C11,16.9,10.5,17,10,17C8.2,17,6.5,16.3,5.2,15.1z M9.9,12.5h6.7
|
||||
c-0.8,1.9-2.3,3.4-4.3,4.1L9.9,12.5z M3,10c0-0.6,0.1-1.3,0.3-1.9h5.1l-3.7,6.4C3.6,13.2,3,11.6,3,10L3,10z M11.7,11.8l3.6-6.3
|
||||
C16.4,6.8,17,8.3,17,10c0,0.6-0.1,1.2-0.2,1.8H11.7z M11.3,7.8l-0.2-0.3l-0.1-0.1L8.7,3.1C9.1,3,9.5,3,10,3c1.8,0,3.5,0.7,4.8,1.9
|
||||
l-2.6,4.4L11.3,7.8z M3.5,7.4c0.8-1.9,2.4-3.4,4.4-4l2.3,4H3.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 849 B |
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#657685;}
|
||||
</style>
|
||||
<path id="减去_260" class="st0" d="M10,17c-1.8,0-3.5-0.7-4.8-1.9l1.3-2.3c0.9,1.1,2.2,1.7,3.5,1.7h0.1l1.4,2.3
|
||||
C11,16.9,10.5,17,10,17z M12.2,16.6L12.2,16.6L11,14.4c1.1-0.2,2.2-0.9,2.8-1.9h2.8C15.8,14.5,14.2,16,12.2,16.6L12.2,16.6z
|
||||
M4.6,14.5L4.6,14.5C3.6,13.2,3,11.6,3,10c0-0.6,0.1-1.3,0.3-1.9h2.7C5.7,8.7,5.5,9.4,5.5,10c0,0.7,0.2,1.4,0.5,2.1L4.6,14.5
|
||||
L4.6,14.5z M16.8,11.7h-2.6c0.2-0.6,0.4-1.1,0.4-1.8c0-0.8-0.2-1.5-0.5-2.1l1.4-2.4C16.4,6.8,17,8.3,17,10
|
||||
C17,10.6,16.9,11.2,16.8,11.7L16.8,11.7z M6.4,7.4H3.5c0.8-1.9,2.4-3.4,4.4-4l1.3,2.3C8.1,5.8,7,6.4,6.4,7.4L6.4,7.4z M13.5,7.1
|
||||
L13.5,7.1c-0.9-1-2.1-1.6-3.5-1.6L8.7,3.1C9.1,3,9.5,3,10,3c1.8,0,3.5,0.7,4.8,1.9L13.5,7.1L13.5,7.1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -8,7 +8,11 @@
|
||||
<div v-if="pageTotal > 1 && showBar" :style="videoPageStyle" class="video-container-page">
|
||||
<el-pagination class="video-pager" :current-page="pageCurrent" :page-size="pageSize" :total="pageTotal"
|
||||
layout="prev, next, total,jumper" small @size-change="onSizeChange" @current-change="onCurrentChange" />
|
||||
<img v-if="currentWindowType == 0 && visibleYt" class="video-yt" src="/yt.png" alt="云台" @click="visibleYt">
|
||||
</div>
|
||||
<teleport to="body">
|
||||
<Yt ref="ytRef" :title="ytState.title" :draggable="true" :resizable="false" />
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -21,7 +25,9 @@ import * as VideoPlayer from "./lib/videoPlayer";
|
||||
|
||||
import * as settingApi from "/@/api/com/comDhSetting"
|
||||
|
||||
const emit = defineEmits(["changePage"])
|
||||
const Yt = defineAsyncComponent(() => import("./yt.vue"));
|
||||
|
||||
const emit = defineEmits(["changePage", "changeSetting"])
|
||||
|
||||
|
||||
const TAG = "|视频预览|"
|
||||
@@ -46,6 +52,13 @@ let lastRealSignature = ""
|
||||
let lastRealStartAt = 0
|
||||
let lastPlaybackSignature = ""
|
||||
let lastPlaybackStartAt = 0
|
||||
let currentSnum = null
|
||||
|
||||
const ytRef = ref()
|
||||
|
||||
const ytState = reactive({
|
||||
title: "",
|
||||
})
|
||||
|
||||
const pageCurrent = ref(1)
|
||||
const pageSize = ref(9)
|
||||
@@ -133,6 +146,7 @@ function clampByDivision(list) {
|
||||
}
|
||||
|
||||
function applyDivisionByCount(count) {
|
||||
if (!props.autoApplyDivision) return
|
||||
if (!VIDEO_PLAYER?.changeDivision) return
|
||||
const nextDivision = getDivisionByCount(count)
|
||||
const currentDivision = Number(VIDEO_PLAYER?.setting?.division)
|
||||
@@ -231,10 +245,14 @@ function flushReadyQueue() {
|
||||
* - 提供登录信息与播放器基础参数,支持父组件通过 ref 调用预览与回放方法
|
||||
*/
|
||||
const props = defineProps({
|
||||
/** 是否展示云台 */
|
||||
visibleYt: { type: Boolean, default: true },
|
||||
/** 播放器类型:0-实时预览,3-录像回放,7-录像回放(支持倒放) */
|
||||
windowType: { type: Number, default: 0 },
|
||||
/** 默认展示的窗口数量 */
|
||||
division: { type: Number, default: 1 },
|
||||
/** 是否自动应用分屏配置 */
|
||||
autoApplyDivision: { type: Boolean, default: true },
|
||||
/**
|
||||
* 分屏配置
|
||||
* 全部配置[1, 2, 4, 6, 8, 9, 13, 16, 20, 25, 36, 64]
|
||||
@@ -308,7 +326,7 @@ async function initVideoPlayer(type = props.windowType) {
|
||||
// 底部操作栏, 选传,【true - 显示, false - 隐藏】
|
||||
showBar: props.showBar,
|
||||
// 如果DOM元素被插件挡住了,把DOM元素的类名传入。
|
||||
shieldClass: ["el-select__popper", "el-popper", "video-container-page"],
|
||||
shieldClass: ["el-select__popper", "el-popper", "video-container-page", "yt_info_popup"],
|
||||
// 如果插件要在dom内滚动,需要把DOM元素的类名传入,请查看案例-遮挡
|
||||
coverShieldClass: [],
|
||||
// 有 iframe 时,top层 的 dom 元素被插件挡住了,把DOM元素的类名传入。
|
||||
@@ -415,10 +433,14 @@ async function initVideoPlayer(type = props.windowType) {
|
||||
// 关闭视频窗口回调
|
||||
closeWindowSuccess: ({ isAll, snum, channelList }) => {
|
||||
console.log(TAG, "关闭视频窗口,isAll:", isAll, "snum:", snum, "channelList:", channelList)
|
||||
emitSetting()
|
||||
},
|
||||
// 鼠标单击窗口回调
|
||||
clickWindow: (snum) => {
|
||||
console.log(TAG, "鼠标单击窗口,snum:", snum)
|
||||
currentSnum = snum
|
||||
emitSetting()
|
||||
clickWindowCallback()
|
||||
},
|
||||
// 鼠标双击窗口回调
|
||||
dbClickWindow: (snum) => {
|
||||
@@ -431,10 +453,12 @@ async function initVideoPlayer(type = props.windowType) {
|
||||
// rtsp 流下载录像成功回调
|
||||
downloadRecordSuccess: (info) => {
|
||||
console.log(TAG, "RTSP录像下载成功,info:", info)
|
||||
ElMessage.success(info.message)
|
||||
},
|
||||
// rtsp 流下载录像失败回调
|
||||
downloadRecordError: (info, err) => {
|
||||
console.log(TAG, "RTSP录像下载失败,info:", info, "err:", err)
|
||||
ElMessage.error("RTSP录像下载失败")
|
||||
}
|
||||
})
|
||||
currentWindowType.value = type
|
||||
@@ -526,11 +550,11 @@ async function startPlayback(items) {
|
||||
lastPlaybackStartAt = now
|
||||
applyDivisionByCount(Array.isArray(items) ? items.length : 1)
|
||||
console.log(TAG, "开始录像回放,items:", items)
|
||||
try {
|
||||
await closeVideo()
|
||||
} catch (e) {
|
||||
console.log(TAG, "开始回放前关闭旧视频失败,err:", e)
|
||||
}
|
||||
// try {
|
||||
// await closeVideo()
|
||||
// } catch (e) {
|
||||
// console.log(TAG, "开始回放前关闭旧视频失败,err:", e)
|
||||
// }
|
||||
VIDEO_PLAYER.startPlayback(items)
|
||||
loading.value = false
|
||||
}
|
||||
@@ -604,6 +628,39 @@ function saveSnapshot(base64Url, filename) {
|
||||
}
|
||||
}
|
||||
|
||||
function toLocalFileUrl(p) {
|
||||
const s = String(p || "")
|
||||
if (s.startsWith("file://")) return s
|
||||
if (/^[a-zA-Z]:/.test(s)) {
|
||||
const fixed = s.replace(/\\/g, "/")
|
||||
return `file:///${encodeURI(fixed)}`
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
function tryCopyText(text) {
|
||||
const t = String(text ?? "")
|
||||
if (!t) return Promise.resolve(false)
|
||||
if (navigator.clipboard?.writeText) {
|
||||
return navigator.clipboard.writeText(t).then(() => true).catch(() => false)
|
||||
}
|
||||
try {
|
||||
const input = document.createElement("input")
|
||||
input.value = t
|
||||
input.style.position = "fixed"
|
||||
input.style.left = "-9999px"
|
||||
input.style.top = "0"
|
||||
document.body.appendChild(input)
|
||||
input.focus()
|
||||
input.select()
|
||||
const ok = document.execCommand("copy")
|
||||
document.body.removeChild(input)
|
||||
return Promise.resolve(!!ok)
|
||||
} catch (e) {
|
||||
return Promise.resolve(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存视频到本地(可选)
|
||||
* @param {string} path 视频路径
|
||||
@@ -618,12 +675,20 @@ function saveVideo(path, filename) {
|
||||
const p = String(path)
|
||||
const isLocal = p.startsWith("file://") || /^[a-zA-Z]:/.test(p)
|
||||
if (isLocal) {
|
||||
console.log(TAG, "无法通过浏览器下载本地文件,路径:", p)
|
||||
if (navigator.clipboard?.writeText) {
|
||||
navigator.clipboard.writeText(p)
|
||||
.then(() => console.log(TAG, "已复制到剪贴板,请在资源管理器打开"))
|
||||
.catch((e) => console.log(TAG, "复制到剪贴板失败,err:", e))
|
||||
const fileUrl = toLocalFileUrl(p)
|
||||
let opened = false
|
||||
try {
|
||||
opened = !!window.open(fileUrl, "_blank")
|
||||
} catch (e) {
|
||||
opened = false
|
||||
}
|
||||
tryCopyText(p).then((copied) => {
|
||||
if (opened) {
|
||||
ElMessage.success(`已尝试打开本地录像:${p}${copied ? "(路径已复制)" : ""}`)
|
||||
} else {
|
||||
ElMessage.warning(`录像已下载到本地:${p}${copied ? "(路径已复制,可在资源管理器粘贴打开)" : "(请在资源管理器打开)"}`)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
const a = document.createElement("a")
|
||||
@@ -666,6 +731,41 @@ function setTotal(total) {
|
||||
}
|
||||
}
|
||||
|
||||
function emitSetting() {
|
||||
emit("changeSetting", {
|
||||
...VIDEO_PLAYER.setting,
|
||||
currentSnum,
|
||||
})
|
||||
}
|
||||
|
||||
function clickWindowCallback() {
|
||||
if (props.visibleYt && currentWindowType == 0) {
|
||||
updateYtChannel()
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************* start 云台相关 start ************************************* */
|
||||
|
||||
function visibleYt() {
|
||||
console.log("visibleYt,currentSnum:", currentSnum);
|
||||
if (currentSnum !== null && currentSnum !== undefined) {
|
||||
VIDEO_PLAYER.setting
|
||||
}
|
||||
ytRef.value.open()
|
||||
}
|
||||
|
||||
const updateYtChannel = () => {
|
||||
if (!ytRef.value?.isVisible?.()) {
|
||||
return
|
||||
}
|
||||
if (currentSnum !== null && currentSnum !== undefined) {
|
||||
ytState.title = ""
|
||||
ytRef.value.setChannel({})
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************* end 云台相关 end ************************************* */
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("visibilitychange", handleVisibilityChange)
|
||||
})
|
||||
@@ -740,13 +840,23 @@ defineExpose({
|
||||
.video-container-page {
|
||||
position: absolute;
|
||||
pointer-events: auto;
|
||||
background: #2F3236;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
:deep(.video-pager) {
|
||||
background: #2F3236;
|
||||
border-radius: 20px;
|
||||
padding: 6px 10px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
:deep(.video-yt) {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.el-pagination) {
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
<template>
|
||||
<div class="yt_info_popup" v-if="dialogVisible"
|
||||
:style="{ left: position.x + 'px', top: position.y + 'px', width: size.width + 'px', height: size.height + 'px' }">
|
||||
<div class="yt_popup_header" @pointerdown="handleHeaderMouseDown"
|
||||
:style="{ cursor: props.draggable ? (isDragging ? 'grabbing' : 'grab') : 'default' }">
|
||||
<div class="yt_popup_header_left">
|
||||
<h3>云台控制</h3>
|
||||
<h3 v-if="props.title">-{{ props.title }}</h3>
|
||||
</div>
|
||||
<button @click.stop="handleClose" class="close_button" @pointerdown.stop @mousedown.stop>×</button>
|
||||
</div>
|
||||
<div class="yt-container">
|
||||
<div id="pantilt"></div>
|
||||
</div>
|
||||
<div v-if="props.resizable" class="resize_handle resize_se" @pointerdown.stop="startResize('se', $event)"></div>
|
||||
<div v-if="props.resizable" class="resize_handle resize_sw" @pointerdown.stop="startResize('sw', $event)"></div>
|
||||
<div v-if="props.resizable" class="resize_handle resize_ne" @pointerdown.stop="startResize('ne', $event)"></div>
|
||||
<div v-if="props.resizable" class="resize_handle resize_nw" @pointerdown.stop="startResize('nw', $event)"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineExpose, onUnmounted, onMounted, onBeforeUnmount, defineProps, nextTick } from "vue";
|
||||
|
||||
import { ytApi } from "@/api/v2"
|
||||
|
||||
import "./lib/panTilt.css"
|
||||
import PanTilt from './lib/panTilt';
|
||||
|
||||
const props = defineProps({
|
||||
title: { type: String, default: "" },
|
||||
draggable: { type: Boolean, default: true },
|
||||
resizable: { type: Boolean, default: true }
|
||||
})
|
||||
|
||||
let YT = null
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const minSize = { width: 350, height: 290 }
|
||||
const sizeRatio = minSize.width / minSize.height
|
||||
const size = ref({ width: minSize.width, height: minSize.height })
|
||||
const position = ref({ x: 100, y: 100 })
|
||||
const isDragging = ref(false)
|
||||
const dragStart = ref({ x: 0, y: 0 })
|
||||
const initialPosition = ref({ x: 0, y: 0 })
|
||||
const dragPointerId = ref(null)
|
||||
const dragPointerEl = ref(null)
|
||||
const isResizing = ref(false)
|
||||
const resizeStart = ref({ x: 0, y: 0 })
|
||||
const initialSize = ref({ width: minSize.width, height: minSize.height })
|
||||
const resizeInitialPosition = ref({ x: 0, y: 0 })
|
||||
const resizeDirection = ref("se")
|
||||
const resizePointerId = ref(null)
|
||||
const resizePointerEl = ref(null)
|
||||
|
||||
function resetLayout() {
|
||||
size.value = { width: minSize.width, height: minSize.height }
|
||||
position.value = {
|
||||
x: Math.max(0, (window.innerWidth - size.value.width) / 2),
|
||||
y: Math.max(0, (window.innerHeight - size.value.height) / 2)
|
||||
}
|
||||
}
|
||||
|
||||
function startDrag(event) {
|
||||
isDragging.value = true
|
||||
dragPointerId.value = event.pointerId ?? null
|
||||
dragPointerEl.value = event.currentTarget ?? null
|
||||
dragStart.value = {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
}
|
||||
initialPosition.value = {
|
||||
x: position.value.x,
|
||||
y: position.value.y
|
||||
}
|
||||
try {
|
||||
if (dragPointerEl.value && dragPointerId.value !== null) {
|
||||
dragPointerEl.value.setPointerCapture(dragPointerId.value)
|
||||
dragPointerEl.value.addEventListener("pointermove", onDrag, { passive: false })
|
||||
dragPointerEl.value.addEventListener("pointerup", stopDrag, { passive: false })
|
||||
dragPointerEl.value.addEventListener("pointercancel", stopDrag, { passive: false })
|
||||
}
|
||||
} catch (e) { }
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
function handleHeaderMouseDown(event) {
|
||||
if (!props.draggable) return
|
||||
startDrag(event)
|
||||
}
|
||||
|
||||
function onDrag(event) {
|
||||
if (!isDragging.value) return
|
||||
const deltaX = event.clientX - dragStart.value.x
|
||||
const deltaY = event.clientY - dragStart.value.y
|
||||
const newX = initialPosition.value.x + deltaX
|
||||
const newY = initialPosition.value.y + deltaY
|
||||
const maxX = window.innerWidth - size.value.width
|
||||
const maxY = window.innerHeight - size.value.height
|
||||
position.value = {
|
||||
x: Math.max(0, Math.min(maxX, newX)),
|
||||
y: Math.max(0, Math.min(maxY, newY))
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
function stopDrag() {
|
||||
isDragging.value = false
|
||||
try {
|
||||
if (dragPointerEl.value) {
|
||||
dragPointerEl.value.removeEventListener("pointermove", onDrag)
|
||||
dragPointerEl.value.removeEventListener("pointerup", stopDrag)
|
||||
dragPointerEl.value.removeEventListener("pointercancel", stopDrag)
|
||||
if (dragPointerId.value !== null) {
|
||||
dragPointerEl.value.releasePointerCapture?.(dragPointerId.value)
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
dragPointerId.value = null
|
||||
dragPointerEl.value = null
|
||||
}
|
||||
|
||||
function startResize(direction, event) {
|
||||
if (!props.resizable) return
|
||||
isResizing.value = true
|
||||
resizeDirection.value = direction
|
||||
resizePointerId.value = event.pointerId ?? null
|
||||
resizePointerEl.value = event.currentTarget ?? null
|
||||
resizeStart.value = {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
}
|
||||
initialSize.value = {
|
||||
width: size.value.width,
|
||||
height: size.value.height
|
||||
}
|
||||
resizeInitialPosition.value = {
|
||||
x: position.value.x,
|
||||
y: position.value.y
|
||||
}
|
||||
try {
|
||||
if (resizePointerEl.value && resizePointerId.value !== null) {
|
||||
resizePointerEl.value.setPointerCapture(resizePointerId.value)
|
||||
resizePointerEl.value.addEventListener("pointermove", onResize, { passive: false })
|
||||
resizePointerEl.value.addEventListener("pointerup", stopResize, { passive: false })
|
||||
resizePointerEl.value.addEventListener("pointercancel", stopResize, { passive: false })
|
||||
}
|
||||
} catch (e) { }
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
function onResize(event) {
|
||||
if (!isResizing.value) return
|
||||
const deltaX = event.clientX - resizeStart.value.x
|
||||
const deltaY = event.clientY - resizeStart.value.y
|
||||
const hasWest = resizeDirection.value.includes('w')
|
||||
const hasNorth = resizeDirection.value.includes('n')
|
||||
const candidateWidth = hasWest ? initialSize.value.width - deltaX : initialSize.value.width + deltaX
|
||||
const candidateHeight = hasNorth ? initialSize.value.height - deltaY : initialSize.value.height + deltaY
|
||||
const maxWidthByHorizontal = hasWest ? resizeInitialPosition.value.x + initialSize.value.width : window.innerWidth - resizeInitialPosition.value.x
|
||||
const maxHeightByVertical = hasNorth ? resizeInitialPosition.value.y + initialSize.value.height : window.innerHeight - resizeInitialPosition.value.y
|
||||
const maxWidthAllowed = Math.min(maxWidthByHorizontal, maxHeightByVertical * sizeRatio)
|
||||
let targetWidth = Math.max(candidateWidth, candidateHeight * sizeRatio)
|
||||
targetWidth = Math.max(minSize.width, Math.min(maxWidthAllowed, targetWidth))
|
||||
const targetHeight = targetWidth / sizeRatio
|
||||
size.value = {
|
||||
width: targetWidth,
|
||||
height: targetHeight
|
||||
}
|
||||
position.value = {
|
||||
x: hasWest ? Math.max(0, resizeInitialPosition.value.x + (initialSize.value.width - targetWidth)) : resizeInitialPosition.value.x,
|
||||
y: hasNorth ? Math.max(0, resizeInitialPosition.value.y + (initialSize.value.height - targetHeight)) : resizeInitialPosition.value.y
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
function stopResize() {
|
||||
isResizing.value = false
|
||||
try {
|
||||
if (resizePointerEl.value) {
|
||||
resizePointerEl.value.removeEventListener("pointermove", onResize)
|
||||
resizePointerEl.value.removeEventListener("pointerup", stopResize)
|
||||
resizePointerEl.value.removeEventListener("pointercancel", stopResize)
|
||||
if (resizePointerId.value !== null) {
|
||||
resizePointerEl.value.releasePointerCapture?.(resizePointerId.value)
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
resizePointerId.value = null
|
||||
resizePointerEl.value = null
|
||||
}
|
||||
|
||||
function resetState() {
|
||||
stopDrag()
|
||||
stopResize()
|
||||
resetLayout()
|
||||
YT = null
|
||||
}
|
||||
|
||||
async function open() {
|
||||
resetLayout()
|
||||
dialogVisible.value = true
|
||||
await nextTick()
|
||||
YT = new PanTilt({
|
||||
el: 'pantilt',
|
||||
setPtzDirection: ytApi.ytOperateDirect,
|
||||
setPtzCamera: ytApi.ytOperateCamera,
|
||||
controlSitPosition: ytApi.ytSitPosition,
|
||||
})
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
dialogVisible.value = false
|
||||
resetState()
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
YT = null
|
||||
})
|
||||
|
||||
/**
|
||||
* 打开云台参数
|
||||
* @typedef {Object} ShowYtOptions
|
||||
* @property {string} id 通道 id
|
||||
* @property {string} cameraType 1-枪机,2-球机
|
||||
* @property {string} capability 摄像头能力集, 值为接口返回,枪机需要,球机支持所有能力可不传此参数。
|
||||
*/
|
||||
|
||||
/**
|
||||
* 打开云台参数
|
||||
* @param {ShowYtOptions} items 回放参数数组
|
||||
*/
|
||||
async function showYt(channel) {
|
||||
await open()
|
||||
YT.setChannel(channel)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开云台参数
|
||||
* @param {ShowYtOptions} items 回放参数数组
|
||||
*/
|
||||
async function setChannel(channel) {
|
||||
if (!dialogVisible.value) return
|
||||
await nextTick()
|
||||
if (!YT) {
|
||||
YT = new PanTilt({
|
||||
el: 'pantilt',
|
||||
setPtzDirection: ytApi.ytOperateDirect,
|
||||
setPtzCamera: ytApi.ytOperateCamera,
|
||||
controlSitPosition: ytApi.ytSitPosition,
|
||||
})
|
||||
}
|
||||
YT.setChannel(channel)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
isVisible: () => dialogVisible.value,
|
||||
setChannel,
|
||||
showYt,
|
||||
open: open,
|
||||
close: handleClose
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
resetLayout()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopDrag()
|
||||
stopResize()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.yt-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.yt-container :deep(#pantilt) {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.yt_info_popup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
background: rgba(25, 30, 45, 0.85);
|
||||
border: 1px solid #0a529a;
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
min-width: 360px;
|
||||
min-height: 360px;
|
||||
z-index: 1000;
|
||||
pointer-events: all;
|
||||
transition: none;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.yt_popup_header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #444;
|
||||
padding: 5px 0 10px 0;
|
||||
margin-bottom: 10px;
|
||||
user-select: none;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.yt_popup_header_left {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.yt_info_popup h3 {
|
||||
margin-top: 0;
|
||||
font-size: 18px;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.yt_info_popup .close_button {
|
||||
width: auto;
|
||||
padding: 0 5px;
|
||||
margin: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #aaa;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.yt_info_popup .close_button:hover {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.resize_handle {
|
||||
position: absolute;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-right: 2px solid rgba(255, 255, 255, 0.6);
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.6);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.resize_se {
|
||||
right: 4px;
|
||||
bottom: 4px;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
.resize_sw {
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
cursor: nesw-resize;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.resize_ne {
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
cursor: nesw-resize;
|
||||
transform: scaleY(-1);
|
||||
}
|
||||
|
||||
.resize_nw {
|
||||
left: 4px;
|
||||
top: 4px;
|
||||
cursor: nwse-resize;
|
||||
transform: scale(-1);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
function parseDate(input: string) {
|
||||
const normalized = input.replace(/\//g, "-").replace(" ", "T");
|
||||
const d = new Date(normalized);
|
||||
if (!isNaN(d.getTime())) return d;
|
||||
const m = input.match(
|
||||
/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?$/
|
||||
);
|
||||
if (m) {
|
||||
const y = Number(m[1]);
|
||||
const mo = Number(m[2]) - 1;
|
||||
const da = Number(m[3]);
|
||||
const h = Number(m[4]);
|
||||
const mi = Number(m[5]);
|
||||
const se = m[6] ? Number(m[6]) : 0;
|
||||
return new Date(y, mo, da, h, mi, se);
|
||||
}
|
||||
return new Date(input);
|
||||
}
|
||||
|
||||
export function timeDiff(enterTime: string, exitTime: string) {
|
||||
const enter = parseDate(enterTime);
|
||||
const exit = parseDate(exitTime);
|
||||
let diff = exit.getTime() - enter.getTime();
|
||||
if (diff < 0) diff = 0;
|
||||
const totalMinutes = Math.floor(diff / (1000 * 60));
|
||||
const totalHours = Math.floor(totalMinutes / 60);
|
||||
const diffMinutes = totalMinutes % 60;
|
||||
const diffDays = Math.floor(totalHours / 24);
|
||||
const diffHours = totalHours % 24;
|
||||
if (diffDays > 0) {
|
||||
if (diffMinutes === 0) return `${diffDays}天${diffHours}小时`;
|
||||
return `${diffDays}天${diffHours}小时${diffMinutes}分钟`;
|
||||
}
|
||||
if (diffHours === 0) return `${diffMinutes}分钟`;
|
||||
if (diffMinutes === 0) return `${diffHours}小时`;
|
||||
return `${diffHours}小时${diffMinutes}分钟`;
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
<el-form-item style="width: 250px;" label="车牌号" prop="carNum">
|
||||
<el-input v-model="state.queryForm.carNum" placeholder="请输入车牌号" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 250px;" label="是否军车" prop="isArmyCar">
|
||||
<dict-select v-model="state.queryForm.isArmyCar" :options="yes_no_type" placeholder="请选择" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 350px;" label="入场时间" prop="enterTimeList">
|
||||
<el-date-picker v-model="state.queryForm.enterTimeList" value-format="YYYY-MM-DD" type="daterange" />
|
||||
</el-form-item>
|
||||
@@ -77,6 +80,13 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="停留时间" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.enterTime && row.exitTime">{{ timeDiff(row.enterTime, row.exitTime) }}</span>
|
||||
<span v-else class="color-danger">未出场</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
@@ -91,13 +101,14 @@
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { timeDiff } from '/@/utils/dispose';
|
||||
|
||||
import * as CarAccessApi from "/@/api/car/carAccess";
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
// 定义查询字典
|
||||
const { dh_enter_Mode } = useDict('dh_enter_Mode');
|
||||
const { dh_enter_Mode, yes_no_type } = useDict('dh_enter_Mode', 'yes_no_type');
|
||||
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref()
|
||||
|
||||
@@ -56,6 +56,12 @@
|
||||
<span class="color-danger" v-else>未出场</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="停留时间" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.inTime && row.outTime">{{ timeDiff(row.inTime, row.outTime) }}</span>
|
||||
<span v-else class="color-danger">未出场</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle"
|
||||
v-bind="state.pagination" />
|
||||
@@ -70,6 +76,7 @@ import * as carParkApi from "/@/api/car/carPark";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { usePanel } from "/@/stores/panel"
|
||||
import { timeDiff } from '/@/utils/dispose';
|
||||
|
||||
const storesPanel = usePanel()
|
||||
|
||||
|
||||
@@ -60,7 +60,8 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<dict-tag v-if="row.carNumColor" :options="dh_car_num_color" :value="row.carNumColor" />
|
||||
<el-tag v-else type="danger">未知</el-tag>
|
||||
<span>{{ row.carNum }}</span>
|
||||
<span v-if="row.carNum">{{ row.carNum }}</span>
|
||||
<span v-else class="color-danger">未知</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
<el-form-item style="width: 250px;" label="开门类型" prop="openType">
|
||||
<dict-select v-model="state.queryForm.openType" :options="dh_door_open_type" placeholder="请选择进开门类型" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 250px;" label="人员类型" prop="personType">
|
||||
<select-person-car-type areaType="person" v-model="state.queryForm.personType" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 250px;" label="区域" prop="areaType">
|
||||
<select-area-type areaType="person" v-model="state.queryForm.areaType" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 450px;" label="抓拍时间" prop="swingTimeList">
|
||||
<el-date-picker v-model="state.queryForm.swingTimeList" value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="datetimerange" />
|
||||
@@ -44,25 +50,25 @@
|
||||
<el-table-column prop="personCode" min-width="100" label="人员编号">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.personCode">{{ row.personCode }}</span>
|
||||
<span v-else class="color-danger">访客</span>
|
||||
<span v-else class="color-danger">未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="personName" min-width="100" label="姓名">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.personName">{{ row.personName }}</span>
|
||||
<span v-else class="color-danger">访客</span>
|
||||
<span v-else class="color-danger">未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="paperNumber" min-width="100" label="证件号码" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.paperNumber">{{ row.paperNumber }}</span>
|
||||
<span v-else class="color-danger">访客</span>
|
||||
<span v-else class="color-danger">未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deptName" min-width="100" label="部门名称">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.deptName">{{ row.deptName }}</span>
|
||||
<span v-else class="color-danger">访客</span>
|
||||
<span v-else class="color-danger">未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
@@ -67,28 +67,28 @@
|
||||
:label="columnSetting['personCode'].label">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.personCode">{{ row.personCode }}</span>
|
||||
<span v-else class="color-danger">访客</span>
|
||||
<span v-else class="color-danger">未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="columnSetting['personName'].visible" prop="personName" min-width="100"
|
||||
:label="columnSetting['personName'].label">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.personName">{{ row.personName }}</span>
|
||||
<span v-else class="color-danger">访客</span>
|
||||
<span v-else class="color-danger">未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="columnSetting['paperNumber'].visible" prop="paperNumber" min-width="100"
|
||||
:label="columnSetting['paperNumber'].label" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.paperNumber">{{ row.paperNumber }}</span>
|
||||
<span v-else class="color-danger">访客</span>
|
||||
<span v-else class="color-danger">未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="columnSetting['deptName'].visible" prop="deptName" min-width="100"
|
||||
:label="columnSetting['deptName'].label">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.deptName">{{ row.deptName }}</span>
|
||||
<span v-else class="color-danger">访客</span>
|
||||
<span v-else class="color-danger">未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- end 人员 end -->
|
||||
|
||||