diff --git a/src/api/video/camera.js b/src/api/video/camera.js new file mode 100644 index 00000000..92c2c671 --- /dev/null +++ b/src/api/video/camera.js @@ -0,0 +1,82 @@ +import request from '@/utils/request' +import { preventRepeatFu } from "@/utils/hciot"; + +// 查询监控详情列表 +export function listInfo(query) { + return request({ + url: '/monitor/video/list', + method: 'get', + params: query + }) +} + +// 查询监控详情详细 +export function getInfo(videoId) { + return request({ + url: '/monitor/video/' + videoId, + method: 'get' + }) +} + +// 新增监控详情 +export function addInfo(data) { + preventRepeatFu(true) + return request({ + url: '/monitor/video', + method: 'post', + data: data + }) +} + +// 修改监控详情 +export function updateInfo(data) { + preventRepeatFu(true) + return request({ + url: '/monitor/video', + method: 'put', + data: data + }) +} + +// 查询监控详情 +export function queryInfoById(recordId) { + return request({ + url: '/monitor/video/' + recordId, + method: 'get' + }) +} + +// 删除监控详情 +export function delInfo(videoId) { + return request({ + url: '/monitor/video/' + videoId, + method: 'delete' + }) +} + +// 查询监控详情列表 +export function listVideos(query) { + return request({ + url: '/iot/video/list/' + query.siteId, + method: 'get' + }) +} + +// 新增监控详情 +export function screenshot(data) { + preventRepeatFu(true) + return request({ + url: '/iot/screenshot/add', + method: 'post', + data: data + }) +} + +// 查询监控详情列表 +export function screenshotList(query) { + return request({ + url: '/iot/screenshot/list', + method: 'get', + params: query + }) +} diff --git a/src/utils/gbsLive.js b/src/utils/gbsLive.js new file mode 100644 index 00000000..ff72f848 --- /dev/null +++ b/src/utils/gbsLive.js @@ -0,0 +1,199 @@ +import { serviceApi } from '@/config/dvr.config' +import { getPTZCmd, PTZ_TYPE } from './ptz-cmd.js' +import axios from 'axios' +/** + * 获取gbs 指令接口 api + * @param {*} v 使用 m7s 版本 + * @returns + */ +export const getServiceApi = (v) => { + return serviceApi[v || 'v3'] +} +/** + * 获取播放地址前缀,ws 方式 + * @param {*} protocol 协议 + * @param {*} ip ip + * @param {*} port 端口 + * @returns + */ +export const getServiceUrlWS = (protocol, ip, port) => { + if (protocol === 'https://') { + return `wss://${ip}:${port}` + } else { + return `ws://${ip}:${port}` + } +} + +/** + * 获取 协议 服务器地址 + * @param {*} protocol 协议 + * @param {*} ip ip + * @param {*} port 端口 + * @returns + */ +export const getServiceUrl = (protocol, ip, port) => { + return `${protocol}${ip}:${port}` +} + +// 日期格式化 +export function parseTime(time, pattern) { + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + var dateTime + if (typeof time === 'object') { + dateTime = time + } else { + if (typeof time === 'string' && /^[0-9]+$/.test(time)) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time.replace(new RegExp(/-/gm), '/') + } + if (typeof time === 'number' && time.toString().length === 10) { + time = time * 1000 + } + dateTime = new Date(time) + } + const formatObj = { + y: dateTime.getFullYear(), + m: dateTime.getMonth() + 1, + d: dateTime.getDate(), + h: dateTime.getHours(), + i: dateTime.getMinutes(), + s: dateTime.getSeconds(), + a: dateTime.getDay() + } + const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { + return ['日', '一', '二', '三', '四', '五', '六'][value] + } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return timeStr +} + +/** +* 发送 查询录像 +* @param {*} device 设备 +* @param {*} channel 通道 +* @param {*} startTime 开始时间 (字符串,格式:2021-7-23T12:00:00) +* @param {*} endTime 结束时间 (字符串,格式:2021-7-23T12:00:00) +*/ +export const getRecordsLists = (device, channel, startTime, endTime) => { + let url = `${getServiceUrl( + device["https"] === true ? "https://" : "http://", + device.m7sHost, + device.m7sPort + )}${getServiceApi(device['m7sVersion'])}query/records` + axiosGet(url, { + id: device.devId, + channel: channel.devChannel, + startTime: parseTime(new Date(startTime), '{y}-{m}-{d}T{h}:{i}:{s}'), + endTime: parseTime(new Date(endTime), '{y}-{m}-{d}T{h}:{i}:{s}') + }) +} + +/** + * 开启设备推流 + * @param {*} device 设备 + * @param {*} channel 通道 + * @param {*} startTime 开始时间(纯数字Unix时间戳) + * @param {*} endTime 结束时间(纯数字Unix时间戳) + */ + export const handleStartInvite = (device, channel, startTime, endTime) => { + var params = { + id: device.devId, + channel: channel.devChannel, + startTime: Math.floor(new Date(startTime).getTime() / 1000), + endTime: Math.floor(new Date(endTime).getTime() / 1000) + } + let url = `${getServiceUrl( + device["https"] === true ? "https://" : "http://", + device.m7sHost, + device.m7sPort + )}${getServiceApi(device['m7sVersion'])}invite` + axiosGet(url, params) +} +/** + * 停止推流 + * @param {*} device 设备 + * @param {*} channel 通道 + */ +export const handleStopBye = (device, channel) => { + var params = { + id: device.devId, + channel: channel.devChannel + } + let url = `${getServiceUrl( + device["https"] === true ? "https://" : "http://", + device.m7sHost, + device.m7sPort + )}${getServiceApi(device['m7sVersion'])}bye` + if (getServiceApi() === serviceApi['v3']) { + params['live'] = false + } + axiosGet(url, params) +} + +export const handleSendDirect = (reqUrl, params, type, ) => { + let url = `${reqUrl}control` + axiosGet(url, Object.assign(params, { + ptzcmd: getPTZCmd({ + type: PTZ_TYPE[type] + }) + })) +} + +/** + * 获取 url 中参数 + * @param {*} strSearch + * @returns + */ +export const jsonSearchFu = (strSearch) => { + if (strSearch.indexOf('?') > -1) { + strSearch = strSearch.substring(strSearch.indexOf('?') + 1) + } + const jsonSearch = {} + const arrSearch = strSearch.split('&') + if (arrSearch == null) { + return + } + arrSearch.forEach(element => { + if (element !== '') { + const arrParam = element.split('=') + if (arrParam.length > 1) { + jsonSearch[arrParam[0]] = arrParam[1] + } + } + }) + return jsonSearch +} + +/** + * 开启设备推流 直播 + * @param {*} device 设备 + * @param {*} channel 通道 + */ + export const handleStartInviteLive = (device, channel) => { + let url = `${getServiceUrl( + device["https"] === true ? "https://" : "http://", + device.m7sHost, + device.m7sPort + )}${getServiceApi(device['m7sVersion'])}invite` + axiosGet(url, { + id: device.devId, + channel: channel.devChannel + }) +} + +export const axiosGet = (url, params) => { + return axios.get(url, { + params: params + }) +} diff --git a/src/utils/ptz-cmd.js b/src/utils/ptz-cmd.js new file mode 100644 index 00000000..2148fdd5 --- /dev/null +++ b/src/utils/ptz-cmd.js @@ -0,0 +1,228 @@ +/** + * Date:2020/11/2 + * Desc: ptz cmd 封装 + * cmd[0] //首字节以05H开头 + * cmd[1] //组合码,高4位为版本信息v1.0,版本信息0H,低四位为校验码 + * // 校验码 = (cmd[0]的高4位+cmd[0]的低4位+cmd[1]的高4位)%16 + * cmd[2] //地址的低8位???什么地址,地址范围000h ~ FFFh(0~4095),其中000h为广播地址 + * cmd[3] //指令码 + * cmd[4] //数据1,水平控制速度、聚焦速度 + * cmd[5] //数据2,垂直控制速度、光圈速度 + * cmd[6] // 高4位为数据3=变倍控制速度,低4位为地址高4位 + */ + +// 所以对一个内存地址,也就是8位二进制,如:0000 0001,0000就是高四位,0001就是低四位, + +const PTZ_TYPE = { + stop: 'stop', + right: 'right', + left: 'left', + up: 'up', + down: 'down', + leftUp: 'leftUp', + leftDown: 'leftDown', + rightUp: 'rightUp', + rightDown: 'rightDown', + zoomFar: 'zoomFar', + zoomNear: 'zoomNear', + apertureFar: 'apertureFar', + apertureNear: 'apertureNear', + focusFar: 'focusFar', + focusNear: 'focusNear', + setPos: 'setPos', + calPos: 'calPos', + delPos: 'delPos', + wiperOpen: 'wiperOpen', + wiperClose: 'wiperClose' +} + +const PTZ_CMD_TYPE = { + stop: 0x00, + + right: 0x01, // 0000 0001 + left: 0x02, // 0000 0010 + up: 0x08, // 0000 1000 + down: 0x04, // 0000 0100 + + leftUp: 0x0A, // 0000 1010 + leftDown: 0x06, // 0000 0110 + rightUp: 0x09, // 0000 1001 + rightDown: 0x05, // 0000 0101 + + zoomFar: 0x10, // 镜头 放大 + zoomNear: 0x20, // 镜头 缩小 + + apertureFar: 0x48, // 光圈 缩小 // + apertureNear: 0x44, // 光圈 放大 + + focusFar: 0x42, // 聚焦 近 + focusNear: 0x41, // 聚焦 远 + + setPos: 0x81, // 设置预设点 + calPos: 0x82, // 调用预设点 + delPos: 0x83, // 删除预设点 + + wiperOpen: 0x8C, // 雨刷开 + wiperClose: 0x8D // 雨刷关 +} + +// 0x19 :00011001 +// 0x32 :00110010 +// 0x4b :01001011 +// 0x64 :01100100 +// 0xFA :11111010 +// 速度范围: 为00H~FFH +const SPEED_ARRAY = [0x19, 0x32, 0x4b, 0x64, 0x7d, 0x96, 0xAF, 0xC8, 0xE1, 0xFA] + +// 0x01 :000000001 +// 0x02 :000000010 +// 0x03 :000000011 +// +// 0x10 :000010000 +// 预置位范围:01H~FFH +const POSITION_ARRAY = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10] +// 0H~FH; 低四位地址是高四位0000 +// 1,3,5,7,9,10,a,c,d,f +const ZOOM_ARRAY = [0x10, 0x30, 0x50, 0x70, 0x90, 0xA0, 0xB0, 0xC0, 0xd0, 0xe0] + +// 获取 direction 方向型 +/** + * + * @param options + * type: + * speed:default 5 + * index: + * @returns {string} + */ +function getPTZCmd(options) { + const { type, speed, index } = options + const ptzSpeed = getPTZSpeed(speed) + var indexValue3, indexValue4, indexValue5, indexValue6 + // 第四个字节。 + indexValue3 = PTZ_CMD_TYPE[type] + switch (type) { + case PTZ_TYPE.up: + case PTZ_TYPE.down: + // 字节6 垂直控制速度相对值 + indexValue5 = ptzSpeed + // 字节7 地址高四位ob0000_0000 + // indexValue6 = 0x00; + break + case PTZ_TYPE.apertureFar: + case PTZ_TYPE.apertureNear: + // 字节6 光圈速度 + indexValue5 = ptzSpeed + // 字节7 地址高四位ob0000_0000 + // indexValue6 = 0x00; + break + case PTZ_TYPE.right: + case PTZ_TYPE.left: + // 字节5 水平控制速度相对值 + indexValue4 = ptzSpeed + // 字节7 地址高四位ob0000_0000 + // indexValue6 = 0x00; + break + case PTZ_TYPE.focusFar: + case PTZ_TYPE.focusNear: + // 字节5 聚焦速度 + indexValue4 = ptzSpeed + // 字节7 地址高四位ob0000_0000 + // indexValue6 = 0x00; + break + case PTZ_TYPE.leftUp: + case PTZ_TYPE.leftDown: + case PTZ_TYPE.rightUp: + case PTZ_TYPE.rightDown: + // 字节5 水平控制速度相对值 + indexValue4 = ptzSpeed + // 字节6 垂直控制速度相对值 + indexValue5 = ptzSpeed + // 字节7 地址高四位ob0000_0000 + // indexValue6 = 0x00; + break + case PTZ_TYPE.zoomFar: + case PTZ_TYPE.zoomNear: + // 字节7 镜头变倍控制速度相对值 zoom + indexValue6 = getZoomSpeed(speed) + break + case PTZ_TYPE.calPos: + case PTZ_TYPE.delPos: + case PTZ_TYPE.setPos: + // 第五个字节 00H + // indexValue4 = 0x00; + // 字节6 01H~FFH 位置。 + indexValue5 = getPTZPositionIndex(index) + break + case PTZ_TYPE.wiperClose: + case PTZ_TYPE.wiperOpen: + // 字节5为辅助开关编号,取值为“1”表示雨刷控制。 + indexValue4 = 0x01 + break + default: + break + } + return ptzCmdToString(indexValue3, indexValue4, indexValue5, indexValue6) +} + +function getPTZSpeed(speed) { + speed = speed || 5 + const speedIndex = speed - 1 + const ptzSpeed = SPEED_ARRAY[speedIndex] || SPEED_ARRAY[4] + return ptzSpeed +} + +function getZoomSpeed(speed) { + speed = speed || 5 + const speedIndex = speed - 1 + const ptzSpeed = ZOOM_ARRAY[speedIndex] || ZOOM_ARRAY[4] + return ptzSpeed +} + +function getPTZPositionIndex(index) { + return POSITION_ARRAY[index - 1] +} + +function ptzCmdToString(indexValue3, indexValue4, indexValue5, indexValue6) { + // + var cmd = Buffer.alloc(8) + // 首字节以05H开头 + cmd[0] = 0xA5 + // 组合码,高4位为版本信息v1.0,版本信息0H,低四位为校验码 + cmd[1] = 0x0F + // 校验码 = (cmd[0]的高4位+cmd[0]的低4位+cmd[1]的高4位)%16 + cmd[2] = 0x01 + // + if (indexValue3) { + cmd[3] = indexValue3 + } + if (indexValue4) { + cmd[4] = indexValue4 + } + if (indexValue5) { + cmd[5] = indexValue5 + } + if (indexValue6) { + cmd[6] = indexValue6 + } + + cmd[7] = (cmd[0] + cmd[1] + cmd[2] + cmd[3] + cmd[4] + cmd[5] + cmd[6]) % 256 + + return bytes2HexString(cmd) +} + +function bytes2HexString(byte) { + let hexs = '' + for (let i = 0; i < byte.length; i++) { + let hex = (byte[i]).toString(16) + if (hex.length === 1) { + hex = '0' + hex + } + hexs += hex.toUpperCase() + } + return hexs +} + +export { + getPTZCmd, + PTZ_TYPE +} diff --git a/src/utils/requestNoToken.js b/src/utils/requestNoToken.js new file mode 100644 index 00000000..be1fac15 --- /dev/null +++ b/src/utils/requestNoToken.js @@ -0,0 +1,182 @@ +import axios from 'axios' +import { Notification, MessageBox, Message } from 'element-ui' +import store from '@/store' +import router from '@/router' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { tansParams, preventCloseFu, jsonSearchFu } from "@/utils/hciot"; + +axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' +// 创建axios实例 +const service = axios.create({ + // axios中请求配置有baseURL选项,表示请求URL公共部分 + baseURL: process.env.VUE_APP_BASE_API, + // 超时 + timeout: 30000 +}) + +// request拦截器 +service.interceptors.request.use(config => { + // const isToken = (config.headers || {}).isToken === false + // if (getToken() && !isToken) { + // config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + // } + return config +}, error => { + console.log(error) + Promise.reject(error) +}) + +// 响应拦截器 +service.interceptors.response.use(res => { + preventCloseFu(); + const code = res.data.code || 200; + const message = errorCode[code] || res.data.msg || errorCode['default'] + var urlObj = jsonSearchFu(window.location.search); + if (code === 401) { + if (process.env.VUE_APP_SSO == 'true') { + store.dispatch('FedLogOut').then(() => { + location.reload() // 为了重新实例化vue-router对象 避免bug + }) + } else { + MessageBox.confirm( + '登录状态已过期,您可以继续留在该页面,或者重新登录', + '系统提示', + { + confirmButtonText: '重新登录', + cancelButtonText: '取消', + type: 'warning' + } + ).then(() => { + store.dispatch('FedLogOut').then(() => { + // location.reload() // 为了重新实例化vue-router对象 避免bug + console.log('FedLogOut', urlObj['redirect']) + if (urlObj['redirect']) { + window.location.replace(`/login?redirect=${urlObj['redirect']}`) + } else { + window.location.replace('/login?redirect=%2Findex') + } + }) + }) + } + } else if (code === 500) { + Message({ + message: message, + type: 'error' + }) + return Promise.reject(new Error(message)) + } else if (code !== 200 && code !== 1000 && code !== 100) { + Notification.error({ + title: '请求接口错误' + }) + return Promise.reject('error') + } else { + return res.data + } + }, + error => { + var urlObj = jsonSearchFu(window.location.search); + if (error.message.indexOf('401') >= 0) { + if (process.env.VUE_APP_SSO == 'true') { + store.dispatch('FedLogOut').then(() => { + location.reload() // 为了重新实例化vue-router对象 避免bug + }) + } else { + MessageBox.confirm( + '登录状态已过期,您可以继续留在该页面,或者重新登录', + '系统提示', + { + confirmButtonText: '重新登录', + cancelButtonText: '取消', + type: 'warning' + } + ).then(() => { + store.dispatch('FedLogOut').then(() => { + // location.reload() // 为了重新实例化vue-router对象 避免bug + // window.location.replace('/login?redirect=%2Findex') + if (urlObj['redirect']) { + window.location.replace(`/login?redirect=${urlObj['redirect']}`) + } else { + window.location.replace('/login?redirect=%2Findex') + } + }) + }) + } + } else if (error.message.indexOf('404') >= 0) { + Message({ + message: '404找不到路径,请联系管理员解决!', + type: 'error', + duration: 5 * 1000 + }) + } else { + Message({ + message: error.message, + type: 'error', + duration: 5 * 1000 + }) + } + return Promise.reject(error) + } +) + +// 通用下载方法 +export function download(url, params, filename) { + return service.post(url, params, { + transformRequest: [(params) => { + return tansParams(params) + }], + responseType: 'blob' + }).then((data) => { + const content = data + const blob = new Blob([content]) + if ('download' in document.createElement('a')) { + const elink = document.createElement('a') + elink.download = filename + elink.style.display = 'none' + elink.href = URL.createObjectURL(blob) + document.body.appendChild(elink) + elink.click() + URL.revokeObjectURL(elink.href) + document.body.removeChild(elink) + } else { + navigator.msSaveBlob(blob, filename) + } + }).catch((r) => { + console.error(r) + }) +} + +export function downloadCustom(url, params, filename) { + const queryStr = Object.keys(params) + .map(function (key) { + return encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); + }) + .join("&"); + return service.post(`${url}?${queryStr}`,params, { + transformRequest: [(params) => { + return tansParams(params) + }], + responseType: 'blob' + }).then((data) => { + const content = data + const blob = new Blob([content]) + if ('download' in document.createElement('a')) { + const elink = document.createElement('a') + elink.download = filename + elink.style.display = 'none' + elink.href = URL.createObjectURL(blob) + document.body.appendChild(elink) + elink.click() + URL.revokeObjectURL(elink.href) + document.body.removeChild(elink) + } else { + navigator.msSaveBlob(blob, filename) + } + }).catch((r) => { + console.error(r) + }) +} + + + +export default service diff --git a/src/views/iot/video/camera/index.vue b/src/views/iot/video/camera/index.vue new file mode 100644 index 00000000..4e9bf352 --- /dev/null +++ b/src/views/iot/video/camera/index.vue @@ -0,0 +1,1280 @@ + + + + diff --git a/src/views/iot/video/profile/SelectInput/index.vue b/src/views/iot/video/profile/SelectInput/index.vue new file mode 100644 index 00000000..5f334baa --- /dev/null +++ b/src/views/iot/video/profile/SelectInput/index.vue @@ -0,0 +1,317 @@ + + +