diff --git a/public/yt.png b/public/yt.png
new file mode 100644
index 0000000..b0c00a8
Binary files /dev/null and b/public/yt.png differ
diff --git a/src/components/Select/SelectAreaType.vue b/src/components/Select/SelectAreaType.vue
new file mode 100644
index 0000000..aa1f42a
--- /dev/null
+++ b/src/components/Select/SelectAreaType.vue
@@ -0,0 +1,120 @@
+
+ e && getOptions()"
+ @change="change">
+
+
+
+
+
+
+
diff --git a/src/components/Select/SelectPersonCarType.vue b/src/components/Select/SelectPersonCarType.vue
new file mode 100644
index 0000000..b594065
--- /dev/null
+++ b/src/components/Select/SelectPersonCarType.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
diff --git a/src/components/index.ts b/src/components/index.ts
index 6c80ed4..b308f46 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -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);
diff --git a/src/components/rtsp-plugin-video/lib/panTilt.css b/src/components/rtsp-plugin-video/lib/panTilt.css
new file mode 100755
index 0000000..872f05f
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/panTilt.css
@@ -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);
+ }
+
\ No newline at end of file
diff --git a/src/components/rtsp-plugin-video/lib/panTilt.js b/src/components/rtsp-plugin-video/lib/panTilt.js
new file mode 100755
index 0000000..5bb9ea8
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/panTilt.js
@@ -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 = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+ // 添加监听
+ 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
diff --git a/src/components/rtsp-plugin-video/lib/svg/arrow-b.svg b/src/components/rtsp-plugin-video/lib/svg/arrow-b.svg
new file mode 100755
index 0000000..7105c60
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/arrow-b.svg
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/arrow-l.svg b/src/components/rtsp-plugin-video/lib/svg/arrow-l.svg
new file mode 100755
index 0000000..daa5902
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/arrow-l.svg
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/arrow-lb.svg b/src/components/rtsp-plugin-video/lib/svg/arrow-lb.svg
new file mode 100755
index 0000000..facc6b9
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/arrow-lb.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/arrow-lt.svg b/src/components/rtsp-plugin-video/lib/svg/arrow-lt.svg
new file mode 100755
index 0000000..b19eed1
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/arrow-lt.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/arrow-r.svg b/src/components/rtsp-plugin-video/lib/svg/arrow-r.svg
new file mode 100755
index 0000000..42ffd1a
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/arrow-r.svg
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/arrow-rb.svg b/src/components/rtsp-plugin-video/lib/svg/arrow-rb.svg
new file mode 100755
index 0000000..50adf01
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/arrow-rb.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/arrow-rt.svg b/src/components/rtsp-plugin-video/lib/svg/arrow-rt.svg
new file mode 100755
index 0000000..21d8048
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/arrow-rt.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/arrow-t.svg b/src/components/rtsp-plugin-video/lib/svg/arrow-t.svg
new file mode 100755
index 0000000..c20ce8c
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/arrow-t.svg
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/ptz-icon1.svg b/src/components/rtsp-plugin-video/lib/svg/ptz-icon1.svg
new file mode 100755
index 0000000..709449b
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/ptz-icon1.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/ptz-icon2.svg b/src/components/rtsp-plugin-video/lib/svg/ptz-icon2.svg
new file mode 100755
index 0000000..fb1e914
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/ptz-icon2.svg
@@ -0,0 +1,13 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/ptz-icon3.svg b/src/components/rtsp-plugin-video/lib/svg/ptz-icon3.svg
new file mode 100755
index 0000000..fed521e
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/ptz-icon3.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/ptz-icon4.svg b/src/components/rtsp-plugin-video/lib/svg/ptz-icon4.svg
new file mode 100755
index 0000000..c0ff7b8
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/ptz-icon4.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/ptz-icon5.svg b/src/components/rtsp-plugin-video/lib/svg/ptz-icon5.svg
new file mode 100755
index 0000000..e36190a
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/ptz-icon5.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/lib/svg/ptz-icon6.svg b/src/components/rtsp-plugin-video/lib/svg/ptz-icon6.svg
new file mode 100755
index 0000000..8541a5d
--- /dev/null
+++ b/src/components/rtsp-plugin-video/lib/svg/ptz-icon6.svg
@@ -0,0 +1,14 @@
+
+
+
diff --git a/src/components/rtsp-plugin-video/video-plugin.vue b/src/components/rtsp-plugin-video/video-plugin.vue
index 00fa542..faea30c 100644
--- a/src/components/rtsp-plugin-video/video-plugin.vue
+++ b/src/components/rtsp-plugin-video/video-plugin.vue
@@ -8,7 +8,11 @@
+
+
+
+
@@ -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) {
diff --git a/src/components/rtsp-plugin-video/yt.vue b/src/components/rtsp-plugin-video/yt.vue
new file mode 100644
index 0000000..8a3bc1b
--- /dev/null
+++ b/src/components/rtsp-plugin-video/yt.vue
@@ -0,0 +1,389 @@
+
+
+
+
+
+
+
diff --git a/src/utils/dispose.ts b/src/utils/dispose.ts
new file mode 100644
index 0000000..926f7ee
--- /dev/null
+++ b/src/utils/dispose.ts
@@ -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}分钟`;
+}
diff --git a/src/views/car/carAccess/index.vue b/src/views/car/carAccess/index.vue
index 394b18a..f6053ed 100644
--- a/src/views/car/carAccess/index.vue
+++ b/src/views/car/carAccess/index.vue
@@ -7,6 +7,9 @@
+
+
+
@@ -77,6 +80,13 @@
+
+
+
+ {{ timeDiff(row.enterTime, row.exitTime) }}
+ 未出场
+
+
@@ -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()
diff --git a/src/views/car/carPark/index.vue b/src/views/car/carPark/index.vue
index cc3d6c3..8a7ad79 100644
--- a/src/views/car/carPark/index.vue
+++ b/src/views/car/carPark/index.vue
@@ -56,6 +56,12 @@
未出场
+
+
+ {{ timeDiff(row.inTime, row.outTime) }}
+ 未出场
+
+
@@ -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()
diff --git a/src/views/car/carRecord/index.vue b/src/views/car/carRecord/index.vue
index 43c063a..3d69287 100644
--- a/src/views/car/carRecord/index.vue
+++ b/src/views/car/carRecord/index.vue
@@ -60,7 +60,8 @@
未知
- {{ row.carNum }}
+ {{ row.carNum }}
+ 未知
diff --git a/src/views/door/doorRecord/index.vue b/src/views/door/doorRecord/index.vue
index a8334de..bc04d93 100644
--- a/src/views/door/doorRecord/index.vue
+++ b/src/views/door/doorRecord/index.vue
@@ -12,6 +12,12 @@
+
+
+
+
+
+
@@ -44,25 +50,25 @@
{{ row.personCode }}
- 访客
+ 未知
{{ row.personName }}
- 访客
+ 未知
{{ row.paperNumber }}
- 访客
+ 未知
{{ row.deptName }}
- 访客
+ 未知
diff --git a/src/views/report/door/index.vue b/src/views/report/door/index.vue
index dd88240..8b89b91 100644
--- a/src/views/report/door/index.vue
+++ b/src/views/report/door/index.vue
@@ -67,28 +67,28 @@
:label="columnSetting['personCode'].label">
{{ row.personCode }}
- 访客
+ 未知
{{ row.personName }}
- 访客
+ 未知
{{ row.paperNumber }}
- 访客
+ 未知
{{ row.deptName }}
- 访客
+ 未知