feat:视频回放

This commit is contained in:
dzc committed 2025-07-21 12:05:53 +08:00
1 parent e0f2147882
commit f18796aef3
14 files changed
+487 -8

No files matched your search

+1 -1
View File
@@ -1 +1 @@
var __defProp=Object.defineProperty,__defNormalProp=(e,t,s)=>t in e?__defProp(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s,__publicField=(e,t,s)=>(__defNormalProp(e,"symbol"!=typeof t?t+"":t,s),s);!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).WSPlayer={})}(this,(function(e){"use strict";const t={websocketPorts:{realmonitor:"realmonitor-websocket",playback:"playback-websocket",realmonitor_ws:"9100",playback_ws:"9320",realmonitor_wss:"9102",playback_wss:"9322"},errorVideoInfo:{100:"浏览器资源不足",101:"当前浏览器不支持硬解码",102:"硬解码播放失败",103:"硬解码播放延时超过8秒",104:"硬解码失败",201:"当前音频无法播放",202:"websocket连接错误",203:"文件播放完成",401:"该用户无操作权限",404:"请求超时或未找到",405:"播放超时",406:"视频流类型解析失败,请检查通道配置",407:"请求超时",408:"请求超时或码流不支持",409:"服务未推送码流",410:"视频流异常断开",411:"录像播放完成",457:"时间设置错误",503:"WS连接地址错误,非当前连接的ICC服务器返回",504:"对讲失败",701:"Chrome版本低,请升级到最新的Chrome版本",702:"Firefox版本低,请升级到最新的Firefox版本",703:"Edge版本低,请升级到最新的Edge版本",defaultErrorMsg:"播放失败,请检查配置"},errorInfo:{101:"所选通道离线,无法播放",102:"登录权限过期,查询实时预览rtsp失败",103:"获取实时预览rtsp失败",104:"操作失败,请稍后重试",105:"通道休眠,正在唤醒中,请稍后再试",106:"请传入rtsp地址和websocket地址",107:"当前未传records录像文件, 将无法拖动进度条",108:"请求超时,请稍后重试",201:"所选通道未查询到录像文件",202:"查询录像文件列表失败",203:"查询录像rtsp失败",204:"{0}倍速无法播放音频",205:"通道休眠,正在唤醒中,请稍后再试",206:"当前倍速不支持",301:"正在对讲,无法关闭音频",302:"其他设备对讲中,无法开启音频",303:"其他设备对讲中,无法开启对讲",304:"查询对讲rtsp失败",305:"http协议不支持对讲",306:"设备对讲失败",307:"不支持PCM音频格式对讲",308:"不支持ARM音频格式对讲",309:"不支持G711u音频格式对讲",310:"不支持G726音频格式对讲",311:"不支持AAC音频格式对讲",312:"不支持G722音频格式对讲",402:"该用户无播放权限",501:"解码库未初始化完成,请稍后播放!",502:"解码库未初始化完成,请稍后对讲!",503:"请检查创建播放器时,播放器容器是否存在",601:"所操作播放器不存在",602:"所选播放器正在本地录像中,不可重复本地录像",603:"所选播放器未播放录像,不可本地录像",604:"所选播放器未开始本地录像,不可操作关闭本地录像",605:"时间跳转播放传参错误",606:"设置自适应拉伸传参错误",607:"实时预览不支持倍速播放",608:"需传入正确的{0}方法:{1}",609:"超出当前设置最大窗口布局数量",610:"操作自定义窗口布局失败:{0}",611:"实时预览不支持播放方法",612:"实时预览不支持暂停方法",613:"实时预览不支持跳转播放",614:"当前窗口未播放实时预览画面, 无法进行对讲",701:"云台被用户{0}锁定,无法操作",702:"控制云台三维定位失败{0}",703:"控制云台{0}{1}失败{2}",704:"控制云台方向失败{0}",30000001:"SDP初始化失败",30000002:"接入服务内部错误(信令建立过程)",30000101:"请求失败",30000102:"拉流过程异常中断",30000103:"接入服务内部异常(码流传输过程)",30000104:"老设备回放时登入设备失败",30000105:"设备离线",30000106:"通道离线",30000107:"设备被删除",30000108:"通道被删除",30001001:"创建子链接失败",30001002:"设备不支持",30001003:"设备资源不足",30001004:"查询不到录像",30001005:"设备返回超时",30001006:"超过最大连接数",30002001:"拒绝对讲",30002002:"对讲被其他客户端打开,请稍后重试",30002003:"资源冲突",30002004:"不支持的语音编码格式",30002005:"无权限",30002006:"请求对讲失败",30002007:"请求喊话失败",30003001:"码率超限"},windowDefaultCustomDivision:{2:[{lStep:0,tStep:0,wStep:50,hStep:100},{lStep:50,tStep:0,wStep:50,hStep:100}],3:[{wStep:50,hStep:100,tStep:0,lStep:0},{wStep:50,hStep:50,tStep:0,lStep:50},{wStep:50,hStep:50,tStep:50,lStep:50}],6:[{wStep:66,hStep:66,tStep:0,lStep:0,selectIndex:0},{wStep:34,hStep:33,tStep:0,lStep:66,selectIndex:1},{wStep:34,hStep:33,tStep:33,lStep:66,selectIndex:2},{wStep:33,hStep:34,tStep:66,lStep:0,selectIndex:3},{wStep:33,hStep:34,tStep:66,lStep:33,selectIndex:4},{wStep:34,hStep:34,tStep:66,lStep:66,selectIndex:5}],8:[{wStep:75,hStep:75,tStep:0,lStep:0,selectIndex:0},{wStep:25,hStep:25,tStep:Line truncated
var __defProp = Object.defineProperty, __defNormalProp = (e, t, s) => t in e ? __defProp(e, t, { enumerable: !0, configurable: !0, writable: !0, value: s }) : e[t] = s, __publicField = (e, t, s) => (__defNormalProp(e, "symbol" != typeof t ? t + "" : t, s), s); !function (e, t) { "object" == typeof exports && "undefined" != typeof module ? t(exports) : "function" == typeof define && define.amd ? define(["exports"], t) : t((e = "undefined" != typeof globalThis ? globalThis : e || self).WSPlayer = {}) }(this, (function (e) { "use strict"; const t = { websocketPorts: { realmonitor: "realmonitor-websocket", playback: "playback-websocket", realmonitor_ws: "9100", playback_ws: "9320", realmonitor_wss: "9102", playback_wss: "9322" }, errorVideoInfo: { 100: "浏览器资源不足", 101: "当前浏览器不支持硬解码", 102: "硬解码播放失败", 103: "硬解码播放延时超过8秒", 104: "硬解码失败", 201: "当前音频无法播放", 202: "websocket连接错误", 203: "文件播放完成", 401: "该用户无操作权限", 404: "请求超时或未找到", 405: "播放超时", 406: "视频流类型解析失败,请检查通道配置", 407: "请求超时", 408: "请求超时或码流不支持", 409: "服务未推送码流", 410: "视频流异常断开", 411: "录像播放完成", 457: "时间设置错误", 503: "WS连接地址错误,非当前连接的ICC服务器返回", 504: "对讲失败", 701: "Chrome版本低,请升级到最新的Chrome版本", 702: "Firefox版本低,请升级到最新的Firefox版本", 703: "Edge版本低,请升级到最新的Edge版本", defaultErrorMsg: "播放失败,请检查配置" }, errorInfo: { 101: "所选通道离线,无法播放", 102: "登录权限过期,查询实时预览rtsp失败", 103: "获取实时预览rtsp失败", 104: "操作失败,请稍后重试", 105: "通道休眠,正在唤醒中,请稍后再试", 106: "请传入rtsp地址和websocket地址", 107: "当前未传records录像文件, 将无法拖动进度条", 108: "请求超时,请稍后重试", 201: "所选通道未查询到录像文件", 202: "查询录像文件列表失败", 203: "查询录像rtsp失败", 204: "{0}倍速无法播放音频", 205: "通道休眠,正在唤醒中,请稍后再试", 206: "当前倍速不支持", 301: "正在对讲,无法关闭音频", 302: "其他设备对讲中,无法开启音频", 303: "其他设备对讲中,无法开启对讲", 304: "查询对讲rtsp失败", 305: "http协议不支持对讲", 306: "设备对讲失败", 307: "不支持PCM音频格式对讲", 308: "不支持ARM音频格式对讲", 309: "不支持G711u音频格式对讲", 310: "不支持G726音频格式对讲", 311: "不支持AAC音频格式对讲", 312: "不支持G722音频格式对讲", 402: "该用户无播放权限", 501: "解码库未初始化完成,请稍后播放!", 502: "解码库未初始化完成,请稍后对讲!", 503: "请检查创建播放器时,播放器容器是否存在", 601: "所操作播放器不存在", 602: "所选播放器正在本地录像中,不可重复本地录像", 603: "所选播放器未播放录像,不可本地录像", 604: "所选播放器未开始本地录像,不可操作关闭本地录像", 605: "时间跳转播放传参错误", 606: "设置自适应拉伸传参错误", 607: "实时预览不支持倍速播放", 608: "需传入正确的{0}方法:{1}", 609: "超出当前设置最大窗口布局数量", 610: "操作自定义窗口布局失败:{0}", 611: "实时预览不支持播放方法", 612: "实时预览不支持暂停方法", 613: "实时预览不支持跳转播放", 614: "当前窗口未播放实时预览画面, 无法进行对讲", 701: "云台被用户{0}锁定,无法操作", 702: "控制云台三维定位失败{0}", 703: "控制云台{0}{1}失败{2}", 704: "控制云台方向失败{0}", 30000001: "SDP初始化失败", 30000002: "接入服务内部错误(信令建立过程)", 30000101: "请求失败", 30000102: "拉流过程异常中断", 30000103: "接入服务内部异常(码流传输过程)", 30000104: "老设备回放时登入设备失败", 30000105: "设备离线", 30000106: "通道离线", 30000107: "设备被删除", 30000108: "通道被删除", 30001001: "创建子链接失败", 30001002: "设备不支持", 30001003: "设备资源不足", 30001004: "查询不到录像", 30001005: "设备返回超时", 30001006: "超过最大连接数", 30002001: "拒绝对讲", 30002002: "对讲被其他客户端打开,请稍后重试", 30002003: "资源冲突", 30002004: "不支持的语音编码格式", 30002005: "无权限", 30002006: "请求对讲失败", 30002007: "请求喊话失败", 30003001: "码率超限" }, windowDefaultCustomDivision: { 2: [{ lStep: 0, tStep: 0, wStep: 50, hStep: 100 }, { lStep: 50, tStep: 0, wStep: 50, hStep: 100 }], 3: [{ wStep: 50, hStep: 100, tStep: 0, lStep: 0 }, { wStep: 50, hStep: 50, tStep: 0, lStep: 50 }, { wStep: 50, hStep: 50, tStep: 50, lStep: 50 }], 6: [{ wStep: 66, hStep: 66, tStep:Line truncated
+8
View File
@@ -8,6 +8,14 @@ export function list(query: any) {
})
}
export function getReplayList(data: any) {
return request({
url: '/admin/deviceChannel/getReplayList',
method: 'post',
data,
})
}
export function fetchList(query?: Object) {
return request({
url: '/admin/deviceChannel/page',
+34
View File
@@ -0,0 +1,34 @@
import request from "/@/utils/request"
/**
* 视频回放列表
*/
export function defaultReplayVideoList(data: any) {
return request({
url: '/admin/third/camera/defaultReplayVideoList',
method: 'post',
data: data
})
}
/**
* 报警录像信息列表
*/
export function alarmReplayVideoList(data: any) {
return request({
url: '/admin/third/camera/alarmReplayVideoList',
method: 'post',
data: data
})
}
/**
* 报警录像信息列表
*/
export function getRtspReplayVideo(data: any) {
return request({
url: '/admin/third/camera/getRtspReplayVideo',
method: 'post',
data: data
})
}
+6 -1
View File
@@ -79,6 +79,7 @@
<script setup lang="ts" name="DraggableOrgTree">
import { computed } from "vue"
import { useMessage } from "/@/hooks/message"
import { getTree } from "/@/api/sys/sysOrg"
@@ -239,7 +240,11 @@ defineExpose({
<style>
.splitpanes {
height: initial !important;
height: 100%;
}
.splitpanes>.splitpanes__pane {
height: 100%;
}
.splitpanes--vertical>.splitpanes__splitter {
+2 -2
View File
@@ -202,13 +202,13 @@ export function useTable(options?: BasicTableProps) {
* 刷新后不跳转第一页,则入参 getDataList(false)
* @param refresh 是否刷新当前页码
*/
const getDataList = (refresh?: any) => {
const getDataList = async (refresh?: any) => {
// 如果需要刷新,则将state.pagination.current重置为1
if (refresh !== false) {
state.pagination!.current = 1;
}
// 再次发起查询操作
query();
await query();
};
/**
+66
View File
@@ -182,6 +182,72 @@ export function parseTime(time, pattern) {
return time_str;
}
/**
* 将日期数组转换为包含具体时间的格式
* @param dateList 日期数组,格式如 ["2025-07-19", "2025-07-21"]
* @param format 输出格式,默认为 'YYYY-MM-DD HH:mm:ss'
* @returns 返回包含开始时间和结束时间的对象
* @description 开始日期设置为 00:00:00,结束日期设置为 23:59:59
*/
export function formatDateRange(dateList: string[], format: string = 'YYYY-MM-DD HH:mm:ss'): {
startTime: string;
endTime: string;
startTimestamp: number;
endTimestamp: number;
} {
if (!dateList || dateList.length !== 2) {
throw new Error('dateList must be an array with exactly 2 elements');
}
const [startDate, endDate] = dateList;
// 创建开始时间 (00:00:00)
const startDateTime = new Date(`${startDate} 00:00:00`);
// 创建结束时间 (23:59:59)
const endDateTime = new Date(`${endDate} 23:59:59`);
// 格式化时间字符串
const startTime = formatDate(startDateTime, format);
const endTime = formatDate(endDateTime, format);
// 获取时间戳(秒)
const startTimestamp = Math.floor(startDateTime.getTime() / 1000);
const endTimestamp = Math.floor(endDateTime.getTime() / 1000);
return {
startTime,
endTime,
startTimestamp,
endTimestamp
};
}
/**
* 快速转换日期数组为时间戳范围
* @param dateList 日期数组,格式如 ["2025-07-19", "2025-07-21"]
* @returns 返回开始和结束的时间戳(秒)
*/
export function getDateRangeTimestamps(dateList: string[]): {
startTime: number;
endTime: number;
} {
if (!dateList || dateList.length !== 2) {
throw new Error('dateList must be an array with exactly 2 elements');
}
const [startDate, endDate] = dateList;
// 创建开始时间 (00:00:00)
const startDateTime = new Date(`${startDate} 00:00:00`);
// 创建结束时间 (23:59:59)
const endDateTime = new Date(`${endDate} 23:59:59`);
return {
startTime: Math.floor(startDateTime.getTime() / 1000),
endTime: Math.floor(endDateTime.getTime() / 1000)
};
}
// 日期格式化
export function parseDate(time, pattern) {
if (arguments.length === 0 || !time) {
+3
View File
@@ -9,6 +9,9 @@
<el-form-item style="width: 250px;" label="车主姓名" prop="personName">
<el-input v-model="state.queryForm.personName" placeholder="请输入车主姓名" />
</el-form-item>
<el-form-item style="width: 250px;" label="车辆类型" prop="carType">
<dict-select v-model="state.queryForm.carType" :options="dh_car_type" placeholder="请选择车辆类型" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
@@ -34,7 +34,7 @@ import * as DeviceChannelApi from "/@/api/device/deviceChannel"
import * as DeviceCameraApi from "/@/api/device/deviceCamera"
// @ts-ignore
import PlayerManager from "./lib/icc/PlayerManager";
import PlayerManager from "../lib/icc/PlayerManager";
let realPlayer: any = null;
@@ -0,0 +1,178 @@
<template>
<div class="w-full h-full bg-emerald-50">
<div id="ws-record-player" class="w-full h-full" />
</div>
</template>
<script setup lang="ts" name="ReplayVideo">
import * as AlarmReplayVideoListApi from "/@/api/third/deviceCameraReplay"
import { formatDateRange, getDateRangeTimestamps } from "/@/utils/formatTime"
const emit = defineEmits(['mounted'])
// @ts-ignore
import PlayerManager from "../lib/icc/PlayerManager";
let replayVideo: any = null
const state = reactive<any>({
startTimestamp: null,
endTimestamp: null,
queryForm: {},
dialogVisible: false,
channelList: [],
loading: false,
treeLoading: false,
currentNum: 4,
})
onMounted(() => {
emit("mounted")
})
onUnmounted(() => {
destroy()
})
// 初始化播放器
const init = (dataList: any, queryForm: any) => {
state.queryForm = queryForm
if (dataList && dataList.length) {
state.channelList = dataList
}
// 处理日期范围
if (queryForm.videoDateList && queryForm.videoDateList.length == 2) {
const dateRange = formatDateRange(queryForm.videoDateList)
state.startTimestamp = dateRange.startTimestamp
state.endTimestamp = dateRange.endTimestamp
}
replayVideo = new PlayerManager({
el: "ws-record-player", /** 实时预览容器id,创建多个播放器,传入不同的容器id即可 **/
type: 'record', /** real - 实时预览 record - 录像回放 **/
maxNum: 16, /** 一个播放器上限能播放的路数,可根据实际情况设置,支持 1 4 9 16 25 **/
num: state.currentNum, /** 初始化,页面显示的路数 **/
showControl: true, /** 是否显示工具栏,默认显示 **/
prefixUrl: 'lib', /** 解码库所在位置的前缀 **/
isRTPStream: true, /** true-标准码流拉流 false-私有码流拉流 **/
receiveMessageFromWSPlayer: (methods: any, data: any, err: any) => { /* 回调函数,可以在以下回调函数里面做监听 */
switch (methods) {
case 'initializationCompleted':
// 初始化完成,可以调用拉流方法,实现播放器加载后立即显示画面
playRecord()
break;
case "recordSuccess":
// 录像回放成功
console.log("录像回放成功")
break;
case "recordError":
// 录像回放失败
console.log("录像回放失败", err)
break;
case "recordFinish":
// 录像回放完成
console.log("录像回放完成");
break;
// 拖拽时间戳的回调
case 'switchStartTime':
console.log("拖拽时间戳的回调:", data);
// data.timeStamp 时间戳
// data.channelData 通道信息
// {timeStamp, channelData: player.options.channelData, hasVideoRecord: true}
// palyerManager.recordByUrl(recordPlayer.recordByUrl({
// wsURL: '', // ws地址
// rtspURL: '', // 重新请求后的rtsp
// channelId , // 必传, 通道id
// records: records, // 录像列表
// selectIndex: 0, // 必传
// playRecordByTime: false // 必传false
// }))
break;
case 'selectWindowChanged':
// 选中的窗口发生改变
console.log(data, "返回窗口信息")
break;
case 'windowNumChanged':
// 播放器显示的路数发生改变
console.log(data, "返回显示的窗口数量")
if (state.currentNum == data) return
state.currentNum = data
if (replayVideo) {
for (let index = 0; index < state.currentNum; index++) {
replayVideo.close(index)
}
}
playRecord()
break;
case 'closeVideo': // 视频关闭回调
// 点击关闭按钮引发的视频关闭进行提示
// 切换视频引发的视频关闭不进行提示
if (!data.changeVideoFlag) {
console.log(`窗口${data.selectIndex}的视频已关闭`)
}
break;
case 'statusChanged':
// 视频状态发生改变
break;
case 'errorInfo':
// 错误信息汇总
console.log(data, "可打印查看错误消息");
}
}
})
}
// 流地址方式播放录像回放
const playRecord = async () => {
// 根据maxNum获取
if (!state.channelList || !state.channelList.length) return
const playList = state.channelList.slice(0, state.currentNum)
for (let index = 0; index < playList.length; index++) {
const element: any = playList[index];
const _data = {
...state.queryForm,
channelId: element.channelCode
}
// 获取回放列表
const recordRes = await AlarmReplayVideoListApi.defaultReplayVideoList(_data)
if (recordRes.code != 0) continue
const videoRes = await AlarmReplayVideoListApi.getRtspReplayVideo(_data)
if (videoRes.code != 0) continue
console.log("data:", videoRes.data);
replayVideo.recordByUrl({
wsURL: videoRes.data.wsURL,
rtspURL: videoRes.data.fullUrl,
channelId: videoRes.data.channelId, // 必传, 通道id
records: recordRes.data || [], // 录像列表
selectIndex: index, // 必传
playRecordByTime: true, // 默认传true,拖拽异常时请传入false
startTime: state.startTimestamp, // 开始时间戳(秒)
endTime: state.endTimestamp, // 结束时间戳(秒)
// playerAdapter: opt.playerAdapter, // 选传, , selfAdaption 自适应 | stretching 拉伸
channelData: {
records: recordRes.data || []
}, // 选传, 通道信息
})
}
}
// 销毁
function destroy() {
if (!replayVideo) return
if (replayVideo) {
for (let index = 0; index < state.currentNum; index++) {
replayVideo.close(index)
}
}
replayVideo = null;
}
// 暴露变量
defineExpose({
init
});
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,166 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<draggable-org-Tree ref="draggableDepTreeRef" placeholder="请输入部门名称" :show-expand="true"
@getSelectNode="handleGetSelectNode" switch-inactive-text="包含" switch-active-text="不包含" :defaultTreeWidth="20"
:nodeClickable="false">
<template #default>
<div class="flex flex-col h-full">
<el-row shadow="hover" v-show="showSearch" class="ml10">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item style="width: 350px;" label="录像时间段" prop="videoDateList">
<el-date-picker v-model="state.queryForm.videoDateList" value-format="YYYY-MM-DD" type="daterange"
:disabled-date="disabledDate" placeholder="请选择日期范围" start-placeholder="开始日期" end-placeholder="结束日期"
@change="handleDateChange" />
</el-form-item>
<el-form-item style="width: 250px;" label="码流" prop="streamType">
<dict-select v-model="state.queryForm.streamType" :options="dh_video_stream_type"
placeholder="请选择码流" />
</el-form-item>
<el-form-item style="width: 250px;" label="录像来源" prop="recordSource">
<dict-select v-model="state.queryForm.recordSource" :options="dh_replay_source"
placeholder="请选择录像来源" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="replayVideoMounted">
{{ $t('common.queryBtn') }}
</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<replay-video class="flex-1 overflow-hidden" ref="replayVideoRef" @mounted="replayVideoMounted" />
</div>
</template>
</draggable-org-Tree>
</div>
</div>
</template>
<script setup lang="ts" name="systemDeviceCameraReplay">
import { BasicTableProps, useTable } from "/@/hooks/table";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useDict } from '/@/hooks/dict';
import * as DeviceCameraReplayApi from "/@/api/third/deviceCameraReplay";
import * as DeviceChannelApi from "/@/api/device/deviceChannel"
// 引入组件
const ReplayVideo = defineAsyncComponent(() => import('../components/replay-video/index.vue'));
// 定义查询字典
const { dh_replay_source, dh_video_stream_type } = useDict("dh_replay_source", "dh_video_stream_type")
// 定义变量内容
const draggableDepTreeRef = ref()
const replayVideoRef = ref()
// 搜索变量
const queryRef = ref()
const showSearch = ref(true)
// 多选变量
const selectObjs = ref([]) as any
const multiple = ref(true)
const state = reactive<any>({
queryForm: {
ownerCode: null,
orgCodes: null,
isInclude: null,
streamType: "1",
recordSource: "1",
videoDateList: getDefaultDateRange()
},
dataList: []
})
const getDataList = async () => {
const { data } = await DeviceChannelApi.getReplayList(state.queryForm)
if (data && data.length) state.dataList = data
}
onMounted(() => {
getDataList()
})
const replayVideoMounted = async () => {
await getDataList()
if (replayVideoRef.value) {
replayVideoRef.value.init(state.dataList, state.queryForm)
}
}
// 获取默认日期范围(前三天)
function getDefaultDateRange() {
const today = new Date()
const threeDaysAgo = new Date(today.getTime() - 2 * 24 * 60 * 60 * 1000) // 前三天(包含今天)
const formatDate = (date: Date) => {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
return [formatDate(threeDaysAgo), formatDate(today)]
}
// 禁用日期的逻辑
const disabledDate = (time: Date) => {
const today = new Date()
today.setHours(23, 59, 59, 999) // 设置为今天的最后一刻
// 禁用未来日期
if (time.getTime() > today.getTime()) {
return true
}
// 如果已经选择了开始日期,限制结束日期最多3天
const selectedDates = state.queryForm.videoDateList
if (selectedDates && selectedDates.length === 1) {
const startDate = new Date(selectedDates[0])
const maxEndDate = new Date(startDate.getTime() + 2 * 24 * 60 * 60 * 1000) // 最多3天
const minEndDate = new Date(startDate.getTime() - 2 * 24 * 60 * 60 * 1000) // 最少3天前
return time.getTime() > maxEndDate.getTime() || time.getTime() < minEndDate.getTime()
}
return false
}
// 处理日期变化
const handleDateChange = (dates: string[]) => {
if (dates && dates.length === 2) {
const startDate = new Date(dates[0])
const endDate = new Date(dates[1])
const diffTime = Math.abs(endDate.getTime() - startDate.getTime())
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1 // 包含起始日期
if (diffDays > 3) {
useMessage().warning('最多只能选择3天的时间范围')
// 重置为默认值
state.queryForm.videoDateList = getDefaultDateRange()
return
}
}
}
// 清空搜索条件
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields()
// 重置日期为默认前三天
state.queryForm.videoDateList = getDefaultDateRange()
// 重置码流为默认值
state.queryForm.streamType = "1"
// 重置录像来源为默认值
state.queryForm.recordSource = "1"
// 清空多选
selectObjs.value = []
draggableDepTreeRef.value.resetData()
getDataList()
}
// 树节点选择时
const handleGetSelectNode = (curClickedNode: any, checkedTreeNodes: any, isDeepTree: boolean) => {
state.queryForm.ownerCode = curClickedNode.id
state.queryForm.orgCodes = checkedTreeNodes.map((item: any) => item.id)
state.queryForm.isInclude = !isDeepTree
getDataList()
}
</script>
+3 -3
View File
@@ -10,12 +10,12 @@
<el-form-item style="width: 250px;" label="设备名" prop="deviceName">
<el-input v-model="state.queryForm.deviceName" placeholder="请输入设备名" />
</el-form-item>
<el-form-item style="width: 250px;" label="设备类型" prop="deviceCategory">
<dict-select v-model="state.queryForm.deviceCategory" :options="device_category" />
</el-form-item>
<el-form-item style="width: 250px;" label="状态" prop="isOnline">
<dict-select v-model="state.queryForm.isOnline" :options="is_nline" />
</el-form-item>
<el-form-item style="width: 250px;" label="同步状态" prop="isSync">
<dict-select v-model="state.queryForm.isSync" :options="is_sync" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
+19
View File
@@ -1,6 +1,25 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row shadow="hover" v-show="showSearch" class="ml10">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item style="width: 250px;" label="姓名" prop="name">
<el-input v-model="state.queryForm.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item style="width: 250px;" label="手机号" prop="phone">
<el-input v-model="state.queryForm.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item style="width: 250px;" label="证件号码" prop="paperNumber">
<el-input v-model="state.queryForm.paperNumber" placeholder="请输入证件号码" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()"