tplink摄像头物联管理

This commit is contained in:
曹磊 2025-03-14 17:32:08 +08:00
parent e275543bad
commit bcb34ab8c8
44 changed files with 13914 additions and 8931 deletions

View File

@ -166,6 +166,7 @@
</div> </div>
</div> </div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
<script src="/static/tums-player/tums-player.umd.min.js"></script>
<!-- 百度统计 --> <!-- 百度统计 -->
<script> <script>
var _hmt = _hmt || []; var _hmt = _hmt || [];

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,129 @@
/*
* Author: Liu Ninggang
* File Created: Monday, 27th September 2021 10:28:13 am
* Last Modified: Wednesday, 29th September 2021 5:36:21 pm
* Modified By: Liu Ninggang
* Copyright (c) 2021 TP-LINK
*/
/* eslint-disable */
let inputBuffer;
let outputBuffer;
let has_init = 0;
let width, height;
let dataSeq;
let decoderType;
let videoDecoder;
let dataParams = {};
var Module = {};
postMessage({ hasInstalled: true });
const initVideoDecoder = () => {
videoDecoder = new VideoDecoder({
output: async (frame) => {
let { timestamp, format, codedWidth, codedHeight } = frame;
let param = dataParams[timestamp] || {};
postMessage({ data: frame, width: codedWidth, height: codedHeight, timestamp, seq: param.seq, format }, [frame]);
frame.close();
delete dataParams[timestamp];
},
error: (err) => {
console.log('video decoder error: ', err);
postMessage({ decodeError: err, state: videoDecoder.state });
}
});
has_init = 1;
};
const initWasmDecoder = (scripts = [], wasmBinary) => {
Module.wasmBinary = wasmBinary;
self.importScripts(...scripts);
Module.onRuntimeInitialized = function () {
Module._hevc_decoder_init();
this.bufferLen = 2304 * 1296 * 8;
inputBuffer = Module._malloc(this.bufferLen);
outputBuffer = Module._malloc(this.bufferLen);
width = Module._malloc(4);
height = Module._malloc(4);
has_init = 1;
postMessage({ hasInit: true });
};
};
this.onmessage = async function (event) {
let { cmd } = event.data;
if (cmd === 'init') {
decoderType = event.data.decoderType;
let { scripts, wasmBinary } = event.data;
if (decoderType === 'wasm') {
initWasmDecoder(scripts, wasmBinary);
} else if (decoderType === 'webcodecs') {
postMessage({ hasInit: true });
} else {
initVideoDecoder();
initWasmDecoder(scripts, wasmBinary);
}
} else if (cmd === 'webcodecs_config') {
initVideoDecoder();
let { data } = event;
let config = {
codec: data.codec,
codeWidth: data.width,
codeHeight: data.height
};
videoDecoder.configure(config);
width = data.width;
height = data.height;
postMessage({ hasWebcodecsConfig: true });
} else if (cmd === 'decode') {
if (!has_init) {
return;
}
let chunk = new Uint8Array(event.data.data);
let { code_type, pts, timestamp, seq, type } = event.data;
if (decoderType === 'webcodecs') {
if (videoDecoder.state === 'unconfigured' || videoDecoder.state === 'closed') return;
let encodedVideoChunk = new EncodedVideoChunk({
type,
timestamp,
data: chunk
});
videoDecoder.decode(encodedVideoChunk);
dataParams[timestamp] = { timestamp, seq, type };
} else {
Module.HEAP8.set(chunk, inputBuffer);
if (seq) {
dataSeq = seq;
}
let len = Module._hevc_to_yuv(code_type, inputBuffer, chunk.length, outputBuffer, width, height);
if (len > 0) {
let data1 = Module.HEAPU8.subarray(width, width + 4);
let result_width = data1[0] + data1[1] * 256;
let data2 = Module.HEAPU8.subarray(height, height + 4);
let result_height = data2[0] + data2[1] * 256;
let outArray = Module.HEAPU8.subarray(outputBuffer, outputBuffer + len);
let outputData = new Uint8Array(outArray);
if (dataSeq) {
seq = dataSeq;
dataSeq = 0;
}
postMessage({ data: outputData, pts: pts, width: result_width, height: result_height, timestamp, seq }, [outputData.buffer]);
}
}
} else if (cmd === 'close') {
if (videoDecoder && videoDecoder.state === 'configured') {
videoDecoder.close();
}
close();
}
};

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,92 @@
/*
* Author: Liu Ninggang
* File Created: Monday, 27th September 2021 10:28:13 am
* Last Modified: Wednesday, 29th September 2021 5:36:21 pm
* Modified By: Liu Ninggang
* Copyright (c) 2021 TP-LINK
*/
/* eslint-disable */
let inputBuffer;
let outputBuffer;
let finalInputBuffer;
let finalOutputBuffer;
let has_init = 0;
let isG726 = true;
var Module = {};
postMessage({ hasInstalled: true });
this.onmessage = function (event) {
let { cmd } = event.data;
if (cmd === 'init') {
let { scripts, wasmBinary } = event.data;
Module.wasmBinary = wasmBinary;
self.importScripts(...scripts);
return;
}
if (!Module['asm']) return;
if (!has_init) {
Module._tp_ns2_init(event.data.samplerate || 8000);
has_init = 1;
inputBuffer = Module._malloc(1024 * 2);
outputBuffer = Module._malloc(1024 * 2);
finalOutputBuffer = Module._malloc(1024 * 2);
postMessage({ hasInit: true });
return;
}
let { dts, pts, timestamp, seq, type, samplerate, bitCount, decoderType } = event.data;
let param = {
pts: pts,
dts: dts,
timestamp: timestamp,
seq: seq,
type: type,
samplerate: samplerate,
bitCount: bitCount
};
const chunk = event.data.data;
Module.HEAPU8.set(chunk, inputBuffer);
let chunk_num;
if (decoderType === '_decodeG726') {
if (isG726) {
Module._initG726State(0, bitCount);
isG726 = false;
}
chunk_num = Module._decodeG726(0, inputBuffer, chunk.length, outputBuffer);
if (chunk_num === 1) {
let getLength = ((chunk.length << 4) / bitCount) >>> 1;
Module._tp_ns2_process(outputBuffer, getLength, finalOutputBuffer);
let finalOutArray = Module.HEAP16.subarray(finalOutputBuffer >> 1, (finalOutputBuffer + getLength * 2) >> 1);
finalOutArray = new Int16Array(finalOutArray);
postMessage({
data: finalOutArray.buffer,
...param
});
}
} else if (decoderType === '_decodeAAC') {
var pcmLen = Module._decodeAAC(outputBuffer, inputBuffer, chunk.length);
if (pcmLen >= 0) {
Module._tp_ns2_process(outputBuffer, pcmLen, finalOutputBuffer);
let finalOutArray = Module.HEAP16.subarray(finalOutputBuffer >> 1, (finalOutputBuffer + pcmLen * 2) >> 1);
finalOutArray = new Int16Array(finalOutArray);
postMessage({
data: finalOutArray.buffer,
...param
});
}
} else {
chunk_num = Module[decoderType](outputBuffer, inputBuffer, chunk.length);
if (chunk_num === 1) {
Module._tp_ns2_process(outputBuffer, chunk.length, finalOutputBuffer);
let finalOutArray = Module.HEAP16.subarray(finalOutputBuffer >> 1, (finalOutputBuffer + chunk.length * 2) >> 1);
finalOutArray = new Int16Array(finalOutArray);
postMessage({
data: finalOutArray.buffer,
...param
});
}
}
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,42 @@
postMessage({ hasInstalled: true });
let offscreenCanvas;
let webgl;
let decoderType;
this.onmessage = (event) => {
const { data } = event;
const { cmd } = data;
if (cmd === 'init') {
const { canvas, options, scripts } = data;
self.importScripts(...scripts);
const WebGL = self.webgl.default;
offscreenCanvas = canvas;
webgl = new WebGL(canvas, options);
decoderType = options.decoderType;
return;
}
if (cmd === 'render') {
const { frame, width, height, yLength, uvLength } = data;
offscreenCanvas.width = width;
offscreenCanvas.height = height;
if (decoderType === 'webcodecs') {
webgl && webgl.renderFrame(frame, width, height);
if (!webgl) frame.close();
} else {
webgl && webgl.renderFrame(frame, width, height, yLength, uvLength);
}
} else if (cmd === 'display') {
const { options } = data;
webgl && webgl.setDisplayInfo(options);
} else if (cmd === 'close') {
webgl && webgl.dispose();
this.close();
}
};

View File

@ -0,0 +1,23 @@
import type { AppRouteRecordRaw } from '/@/router/types';
import { LAYOUT } from '/@/router/constant';
export const tplink: AppRouteRecordRaw = {
path: '/tplink',
name: 'ai-parent',
component: LAYOUT,
meta: {
title: 'tplink',
},
children: [
{
path: 'cameraConfig',
name: 'cameraConfig',
component: () => import('/@/views/iot/tplink/camera/components/CameraPictureConfig.vue'),
meta: {
title: '画面配置',
},
},
],
};
export const tplinkList = [tplink];

View File

@ -23,6 +23,7 @@ import { getBackMenuAndPerms } from '/@/api/sys/menu';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { PageEnum } from '/@/enums/pageEnum'; import { PageEnum } from '/@/enums/pageEnum';
import { tplinkList } from "@/router/routes/tplink";
// 系统权限 // 系统权限
interface AuthItem { interface AuthItem {
@ -279,7 +280,7 @@ export const usePermissionStore = defineStore({
routeList = flatMultiLevelRoutes(routeList); routeList = flatMultiLevelRoutes(routeList);
// update-begin--author:liaozhiyang---date:20240529---for【TV360X-522】ai助手路由写死在前端 // update-begin--author:liaozhiyang---date:20240529---for【TV360X-522】ai助手路由写死在前端
routes = [PAGE_NOT_FOUND_ROUTE, ...routeList, ...staticRoutesList]; routes = [PAGE_NOT_FOUND_ROUTE, ...routeList, ...staticRoutesList, ...tplinkList];
// update-end--author:liaozhiyang---date:20240529---for【TV360X-522】ai助手路由写死在前端 // update-end--author:liaozhiyang---date:20240529---for【TV360X-522】ai助手路由写死在前端
break; break;
} }

View File

@ -0,0 +1,268 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
queryProjectTreeSync = '/iot/projectInfo/queryRegionTreeSync',
queryRegionTreeSync = '/iot/regionInfo/queryRegionTreeSync',
syncProject = '/iot/projectInfo/sync',
syncRegion = '/iot/regionInfo/sync',
list = '/iot/cameraInfo/list',
ipcCapability = '/iot/cameraInfo/getIpcCapability',
nuList = '/iot/cameraInfo/nuList',
edit = '/iot/cameraInfo/edit',
rebootDevice = '/iot/cameraInfo/rebootDevice',
previewUrl = '/iot/cameraInfo/getPreviewUrl',
getImageCommon = '/iot/cameraInfo/getImageCommon',
setImageCommon = '/iot/cameraInfo/setImageCommon',
getOsdCapability = '/iot/cameraInfo/getOsdCapability',
getOsd = '/iot/cameraInfo/getOsd',
setOsd = '/iot/cameraInfo/setOsd',
getTamperDet = '/iot/cameraInfo/getTamperDet',
setTamperDet = '/iot/cameraInfo/setTamperDet',
getTamperNotif = '/iot/cameraInfo/getTamperNotif',
setTamperNotif = '/iot/cameraInfo/setTamperNotif',
testAudio = '/iot/cameraInfo/testAudio',
getAlarmInfo = '/iot/cameraInfo/getAlarmInfo',
setAlarmInfo = '/iot/cameraInfo/setAlarmInfo',
getAlarmPlan = '/iot/cameraInfo/getAlarmPlan',
setAlarmPlan = '/iot/cameraInfo/setAlarmPlan',
getVideoParams = '/iot/cameraInfo/getVideoParams',
setVideoParams = '/iot/cameraInfo/setVideoParams',
configRecovery = '/iot/cameraInfo/configRecovery',
searchVideo = '/iot/cameraInfo/searchVideo',
getPlaybackUrlList = '/iot/cameraInfo/getPlaybackUrlList',
deletePlaybackChn = '/iot/cameraInfo/deletePlaybackChn',
getMultitransUrl = '/iot/cameraInfo/getMultitransUrl',
getRecordCfgs = '/iot/cameraInfo/getRecordCfgs',
setRecordCfgs = '/iot/cameraInfo/setRecordCfgs',
getBatchProgress = '/iot/cameraInfo/getBatchProgress',
uploadToServer = '/iot/cameraInfo/uploadToServer',
stopUploadToServer = '/iot/cameraInfo/stopUploadToServer',
getUploadToServerProcess = '/iot/cameraInfo/getUploadToServerProcess',
}
/**
*
* @param params
*/
export const queryProjectTreeSync = (params?) => defHttp.get({ url: Api.queryProjectTreeSync, params });
/**
*
* @param params
*/
export const queryRegionTreeSync = (params?) => defHttp.get({ url: Api.queryRegionTreeSync, params });
/**
*
* @param params
*/
export const syncProject = (params?) => defHttp.get({ url: Api.syncProject, params });
/**
*
* @param params
*/
export const syncRegion = (params?) => defHttp.get({ url: Api.syncRegion, params });
/**
*
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* IPC能力集
* @param params
*/
export const getIpcCapability = (params) => defHttp.get({ url: Api.ipcCapability, params });
/**
* url
* @param params
*/
export const getPreviewUrl = (params) => defHttp.get({ url: Api.previewUrl, params });
/**
*
* @param params
*/
export const nuList = (params) => defHttp.get({ url: Api.nuList, params });
/**
*
* @param params
*/
export const update = (params) => {
return defHttp.post({ url: Api.edit, params });
}
/**
*
* @param params
*/
export const rebootDevice = (params) => {
return defHttp.post({ url: Api.rebootDevice, params });
}
/**
*
* @param params
*/
export const getImageCommon = (params) => defHttp.post({ url: Api.getImageCommon, params });
/**
*
* @param params
*/
export const setImageCommon = (params) => defHttp.post({ url: Api.setImageCommon, params });
/**
* OSD能力集参数
* @param params
*/
export const getOsdCapability = (params) => defHttp.post({ url: Api.getOsdCapability, params });
/**
* OSD参数
* @param params
*/
export const getOsd = (params) => defHttp.post({ url: Api.getOsd, params });
/**
* OSD参数
* @param params
*/
export const setOsd = (params) => defHttp.post({ url: Api.setOsd, params });
/**
*
* @param params
*/
export const getTamperDet = (params) => defHttp.post({ url: Api.getTamperDet, params });
/**
*
* @param params
*/
export const setTamperDet = (params) => defHttp.post({ url: Api.setTamperDet, params });
/**
*
* @param params
*/
export const getTamperNotif = (params) => defHttp.post({ url: Api.getTamperNotif, params });
/**
*
* @param params
*/
export const setTamperNotif = (params) => defHttp.post({ url: Api.setTamperNotif, params });
/**
*
* @param params
*/
export const testAudio = (params) => defHttp.post({ url: Api.testAudio, params });
/**
* /
* @param params
*/
export const getAlarmInfo = (params) => defHttp.post({ url: Api.getAlarmInfo, params });
/**
* /
* @param params
*/
export const setAlarmInfo = (params) => defHttp.post({ url: Api.setAlarmInfo, params });
/**
* /
* @param params
*/
export const getAlarmPlan = (params) => defHttp.post({ url: Api.getAlarmPlan, params });
/**
* /
* @param params
*/
export const setAlarmPlan = (params) => defHttp.post({ url: Api.setAlarmPlan, params });
/**
*
* @param params
*/
export const getVideoParams = (params) => defHttp.post({ url: Api.getVideoParams, params });
/**
*
* @param params
*/
export const setVideoParams = (params) => defHttp.post({ url: Api.setVideoParams, params });
/**
*
* @param params
*/
export const configRecovery = (params) => defHttp.post({ url: Api.configRecovery, params });
/**
*
* @param params
*/
export const searchVideo = (params) => defHttp.get({ url: Api.searchVideo, params });
/**
* url
* @param params
*/
export const getPlaybackUrlList = (params) => defHttp.get({ url: Api.getPlaybackUrlList, params });
/**
* url
* @param params
*/
export const deletePlaybackChn = (params) => defHttp.get({ url: Api.deletePlaybackChn, params });
/**
* --Multitrans能力
* @param params
*/
export const getMultitransUrl = (params) => defHttp.get({ url: Api.getMultitransUrl, params });
/**
* --
* @param params
*/
export const getRecordCfgs = (params) => defHttp.get({ url: Api.getRecordCfgs, params });
/**
* --
* @param params
*/
export const setRecordCfgs = (params) => defHttp.get({ url: Api.setRecordCfgs, params });
/**
* --
* @param params
*/
export const getBatchProgress = (params) => defHttp.get({ url: Api.getBatchProgress, params });
/**
*
* @param params
*/
export const uploadToServer = (params) => defHttp.get({ url: Api.uploadToServer, params });
/**
*
* @param params
*/
export const stopUploadToServer = (params) => defHttp.get({ url: Api.stopUploadToServer, params });
/**
*
* @param params
*/
export const getUploadToServerProcess = (params) => defHttp.get({ url: Api.getUploadToServerProcess, params });

View File

@ -0,0 +1,266 @@
import {BasicColumn} from '/@/components/Table';
import {FormSchema} from '/@/components/Table';
import dayjs from 'dayjs';
//列表数据
export const columns: BasicColumn[] = [
{
title: '设备序号',
align: "center",
dataIndex: 'deviceIndex'
},
{
title: '设备名称',
align: "center",
dataIndex: 'deviceName'
},
{
title: '设备状态',
align: "center",
dataIndex: 'deviceStatus_dictText'
},
{
title: '设备型号',
align: "center",
dataIndex: 'deviceModel'
},
{
title: 'IP地址',
align: "center",
dataIndex: 'ip'
},
{
title: 'MAC地址',
align: "center",
dataIndex: 'mac'
},
{
title: '区域名称',
align: "center",
dataIndex: 'regionName'
},
{
title: '父设备名称',
align: "center",
dataIndex: 'parentDeviceName'
},
{
title: '项目名称',
align: "center",
dataIndex: 'projectName'
},
{
title: '位置名称',
align: "center",
dataIndex: 'locationName'
},
{
title: '护理单元',
align: "center",
dataIndex: 'nuId_dictText',
},
// {
// title: '护理单元',
// align: "center",
// dataIndex: 'nuName'
// },
];
export const formSchema: FormSchema[] = [
{
label: '',
field: 'id',
component: 'Input',
show: false,
},
{
label: '设备序号',
field: 'deviceIndex',
component: 'Input',
// dynamicDisabled: ({value})=>{
// return true;
// }
dynamicDisabled: true
},
{
label: '设备名称',
field: 'deviceName',
component: 'Input',
},
{
label: '设备状态',
field: 'deviceStatus',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'tplink_status',
placeholder: '请选择设备状态',
},
dynamicDisabled: true
},
{
label: '设备型号',
field: 'deviceModel',
component: 'Input',
dynamicDisabled: true
},
{
label: 'IP地址',
field: 'ip',
component: 'Input',
dynamicDisabled: true
},
{
label: 'MAC地址',
field: 'mac',
component: 'Input',
dynamicDisabled: true
},
{
label: '区域名称',
field: 'regionName',
component: 'Input',
dynamicDisabled: true
},
{
label: '父设备名称',
field: 'parentDeviceName',
component: 'Input',
dynamicDisabled: true
},
{
label: '项目ID',
field: 'projectId',
component: 'Input',
show: false,
},
{
label: '项目名称',
field: 'projectName',
component: 'Input',
dynamicDisabled: true
},
{
label: '位置名称',
field: 'locationName',
component: 'Input',
dynamicDisabled: true
},
{
label: '护理单元',
field: 'nuId',
component: 'JSelectNu',
componentProps: {
rowKey: 'nuId',
labelKey: 'nuName',
selectType: true,
},
},
{
label: '设备重启',
field: 'id',
component: 'Input',
slot: 'customInput',
}
];
export const searchFormSchema: FormSchema[] = [
{
label: '设备状态',
field: 'deviceStatus',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'tplink_status',
placeholder: '请选择状态',
stringToNumber: true,
},
//colProps: { span: 6 },
},
];
//列表数据
export const recordingColumns: BasicColumn[] = [
{
title: '开始时间',
align: "center",
dataIndex: 'startTimeFt'
},
{
title: '结束时间',
align: "center",
dataIndex: 'endTimeFt'
},
{
title: '时长',
align: "center",
dataIndex: 'duration'
},
{
title: '大小(MB)',
align: "center",
dataIndex: 'size'
},
{
title: '回放类型',
align: "center",
dataIndex: 'videoType',
customRender:({record})=>{
return record.videoType?(record.videoType=='1'?'定时录像':'移动侦测'):'';
},
},
{
title: '错误状态',
align: "center",
dataIndex: 'errorMsg'
},
{
title: '设备序号',
align: "center",
dataIndex: 'videoDevId'
},
{
title: '存储设备ID',
align: "center",
dataIndex: 'storageDevId'
},
{
title: '双摄IPC通道ID',
align: "center",
dataIndex: 'channelId'
},
{
title: '所属NVS的ID',
align: "center",
dataIndex: 'nvsIdInPoolList'
},
];
export const searchRecording: FormSchema[] = [
{
field: 'dataDate',
label: '录像日期',
component: 'DatePicker',
defaultValue: dayjs(new Date()).format('YYYY-MM-DD'),
componentProps: {
//日期格式化,页面上显示的值
format: 'YYYY-MM-DD',
//返回值格式化(绑定值的格式)
valueFormat: 'YYYY-MM-DD',
//是否显示今天按钮
showToday: true,
},
colProps: { span: 6 },
},
// {
// field: 'videoType',
// label: '回放类型',
// component: 'JDictSelectTag',
// componentProps: {
// placeholder: '请选择类型',
// options: [
// { label: '定时录像', value: '1' },
// { label: '移动侦测', value: '2' },
// ],
// },
// colProps: { span: 6 },
// },
];

View File

@ -0,0 +1,300 @@
<template>
<a-spin :spinning="confirmLoading">
<JFormContainer :disabled="disabled">
<template #detail>
<a-row>
<a-col :span="12">
<a-row>
<a-col :span="20">
<a-divider orientation="left">主码流</a-divider>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
分辨率
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.main_resolution" :options="formData.mainResolutionArr"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
视频帧率
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.main_frame_rate" :options="formData.mainFrameRatesArr"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
码率类型
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.main_bitrate_type" :options="formData.mainBitrateTypeArr"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
图像质量
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.main_quality" :options="formData.mainQualityArr"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 10px">
<a-col :span="6" class="labelText">
码率上限
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.main_bitrate" :options="formData.mainBitrateArr"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 10px">
<a-col :span="6" class="labelText">
视频编码
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.main_encode_type" :options="formData.mainEncodeTypeArr"/>
</a-col>
</a-row>
</a-col>
</a-row>
<a-row style="margin-top: 14px">
<a-col :span="1"></a-col>
<a-col :span="20">
<a-button type="primary" preIcon="ant-design:save-outlined" @click="setParams">保存</a-button>
</a-col>
</a-row>
</a-col>
<a-col :span="12">
<a-row>
<a-col :span="20">
<a-divider orientation="left">子码流</a-divider>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
分辨率
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.minor_resolution" :options="formData.minorResolutionArr"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
视频帧率
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.minor_frame_rate" :options="formData.minorFrameRatesArr"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
码率类型
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.minor_bitrate_type" :options="formData.minorBitrateTypeArr"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
图像质量
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.minor_quality" :options="formData.minorQualityArr"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 10px">
<a-col :span="6" class="labelText">
码率上限
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.minor_bitrate" :options="formData.minorBitrateArr"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 10px">
<a-col :span="6" class="labelText">
视频编码
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.minor_encode_type" :options="formData.minorEncodeTypeArr"/>
</a-col>
</a-row>
</a-col>
</a-row>
</a-col>
</a-row>
</template>
</JFormContainer>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineProps, onMounted, watch,} from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import { getValueType } from '/@/utils';
import { Form } from 'ant-design-vue';
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
import { getVideoParams,setVideoParams } from "@/views/iot/tplink/camera/camera.api";
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
const formData = reactive<Record<string, any>>({
deviceIndex: '', //
main_quality: '', //
mainQualityArr: [],
main_bitrate: '', //
mainBitrateArr: [],
main_frame_rate: '', //
mainFrameRatesArr: [],
main_encode_type: '', //
mainEncodeTypeArr: [],
main_resolution: '', //
mainResolutionArr: [],
main_bitrate_type: '', //
mainBitrateTypeArr: [],
minor_quality: '', //
minorQualityArr: [],
minor_bitrate: '', //
minorBitrateArr: [],
minor_frame_rate: '', //
minorFrameRatesArr: [],
minor_encode_type: '', //
minorEncodeTypeArr: [],
minor_resolution: '', //
minorResolutionArr: [],
minor_bitrate_type: '', //
minorBitrateTypeArr: [],
});
const confirmLoading = ref<boolean>(false);
/**
* 获取画面通用信息
*/
function getParams(deviceIndex){
if(deviceIndex==null){
return
}
getVideoParams({
"deviceIndex": deviceIndex
}).then(res=>{
let mainData = res.mainData;
let minorData = res.minorData;
formData.main_quality = mainData.quality;
formData.main_bitrate = mainData.bitrate;
formData.main_frame_rate = mainData.frame_rate;
formData.main_encode_type = mainData.encode_type;
formData.main_resolution = mainData.resolution;
formData.main_bitrate_type = mainData.bitrate_type;
formData.minor_quality = minorData.quality;
formData.minor_bitrate = minorData.bitrate;
formData.minor_frame_rate = minorData.frame_rate;
formData.minor_encode_type = minorData.encode_type;
formData.minor_resolution = minorData.resolution;
formData.minor_bitrate_type = minorData.bitrate_type;
formData.mainQualityArr = res.mainQualityArr;
formData.mainBitrateArr = res.mainBitrateArr;
formData.mainFrameRatesArr = res.mainFrameRatesArr;
formData.mainEncodeTypeArr = res.mainEncodeTypeArr;
formData.mainResolutionArr = res.mainResolutionArr;
formData.mainBitrateTypeArr = res.mainBitrateTypeArr;
formData.minorQualityArr = res.minorQualityArr;
formData.minorBitrateArr = res.minorBitrateArr;
formData.minorFrameRatesArr = res.minorFrameRatesArr;
formData.minorEncodeTypeArr = res.minorEncodeTypeArr;
formData.minorResolutionArr = res.minorResolutionArr;
formData.minorBitrateTypeArr = res.minorBitrateTypeArr;
});
}
/**
* 设置画面通用信息
*/
function setParams(){
confirmLoading.value = true;
let params = {
"deviceIndex": formData.deviceIndex,
"main":{
"quality": formData.main_quality,
"bitrate": formData.main_bitrate,
"frame_rate": formData.main_frame_rate,
"encode_type": formData.main_encode_type,
"resolution": formData.main_resolution,
"bitrate_type": formData.main_bitrate_type,
},
"minor":{
"quality": formData.minor_quality,
"bitrate": formData.minor_bitrate,
"frame_rate": formData.minor_frame_rate,
"encode_type": formData.minor_encode_type,
"resolution": formData.minor_resolution,
"bitrate_type": formData.minor_bitrate_type,
},
};
setVideoParams(params).then(res=>{
confirmLoading.value = false;
});
}
onMounted(() => {
watch(
() => props.data,
async () => {
formData.deviceIndex = props.data.deviceIndex;
getParams(formData.deviceIndex);
},
{ deep: true, immediate: true }
);
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px;
}
.labelText {
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 8px;
}
.ant-select {
display: flex;
}
</style>

View File

@ -0,0 +1,305 @@
<template>
<a-spin :spinning="confirmLoading">
<JFormContainer :disabled="disabled">
<template #detail>
<a-row>
<a-col :span="24">
<a-row>
<a-col :span="20">
<a-divider orientation="left">当镜头被其它物体遮挡时发生报警</a-divider>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
镜头遮挡
</a-col>
<a-col :span="16">
<a-switch v-model:checked="formData.enabled" checked-children="已开启" un-checked-children="已关闭" />
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
灵敏度
</a-col>
<a-col :span="6">
<a-slider v-model:value="formData.digitalSensitivity" :disabled="!formData.enabled" :min="1" :max="100" @change="(value) => changeSensitivity(value)"/>
</a-col>
<a-col :span="4">
<a-input-number v-model:value="formData.digitalSensitivity" :disabled="!formData.enabled" :min="1" :max="100" style="margin-left: 12px" @change="(value) => changeSensitivity(value)"/>
</a-col>
</a-row>
</a-col>
</a-row>
</a-col>
<a-col :span="24">
<a-row>
<a-col :span="20">
<a-divider orientation="left">处理方式</a-divider>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="5" class="labelText">
</a-col>
<a-col :span="16">
<a-checkbox v-model:checked="formData.msgPushEnabled" :disabled="!formData.enabled">消息提醒触发事件后设备会上传云端信息</a-checkbox>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="5" class="labelText">
</a-col>
<a-col :span="16">
<a-checkbox v-model:checked="formData.lightAlarmEnabled" :disabled="!formData.enabled">白光告警触发事件后设备会发出白光告警</a-checkbox>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="5" class="labelText">
</a-col>
<a-col :span="16">
<a-checkbox v-model:checked="formData.soundAlarmEnabled" :disabled="!formData.enabled">声音告警触发事件后设备会发出声音告警</a-checkbox>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="7" class="labelText">
报警声音
</a-col>
<a-col :span="5">
<a-select v-model:value="formData.soundAlarmType" :options="formData.soundAlarmTypeArr" :disabled="!formData.enabled"/>
</a-col>
<a-col :span="5" style="padding-left: 10px">
<a-button preIcon="ant-design:customer-service-outlined" @click="soundAlarm" :disabled="!formData.enabled">试听</a-button>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 10px">
<a-col :span="7" class="labelText">
播放次数
</a-col>
<a-col :span="5">
<a-select v-model:value="formData.soundAlarmTimes" :options="formData.soundAlarmTimesArr" :disabled="!formData.enabled"/>
</a-col>
</a-row>
</a-col>
</a-row>
</a-col>
<a-col :span="24">
<a-row style="margin-top: 14px">
<a-col :span="1"></a-col>
<a-col :span="20">
<a-button type="primary" preIcon="ant-design:save-outlined" @click="setTamper">保存</a-button>
</a-col>
</a-row>
</a-col>
</a-row>
</template>
</JFormContainer>
</a-spin>
<a-modal v-model:visible="visible" title="设备上试听报警声音" :footer="null" width="400px">
<ul>
<li class="li" v-for="item in formData.soundAlarmTypeArr">
<div style="display: flex;justify-content: space-between;">
<div>{{item.label}}</div>
<div><a-button preIcon="ant-design:customer-service-outlined" @click="playAudio(item.value)">试听</a-button></div>
</div>
</li>
</ul>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, defineProps, onMounted, watch,} from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import { getValueType } from '/@/utils';
import { Form } from 'ant-design-vue';
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
import {
getTamperDet,
setTamperDet,
getTamperNotif,
setTamperNotif,
testAudio
} from "@/views/iot/tplink/camera/camera.api";
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
const formData = reactive<Record<string, any>>({
deviceIndex: '', //
enabled: false, //
digitalSensitivity: '50', //
msgPushEnabled: true, //
lightAlarmEnabled: false, //
soundAlarmEnabled: false, //
soundAlarmType: '', //
soundAlarmTypeArr: [
{value:'0',label:'报警音'},
{value:'1',label:'提示音'},
{value:'2',label:'警戒区域,尽快离开'},
{value:'3',label:'危险区域,请勿靠近'},
{value:'4',label:'此区域禁止停车'},
{value:'5',label:'你已进入实时监控区域'},
{value:'6',label:'您好,欢迎光临'},
{value:'7',label:'贵重物品,请勿触摸'},
{value:'8',label:'私人领域, 禁止入内'},
{value:'9',label:'水深危险,注意安全'},
{value:'10',label:'高处危险,请勿攀爬'},
{value:'11',label:'垃圾请分类投放'},
], //
soundAlarmTimes: '', //
soundAlarmTimesArr:[{value:'1',label:'1次'},{value:'2',label:'2次'},{value:'3',label:'3次'},{value:'4',label:'4次'},{value:'5',label:'5次'}],
});
const confirmLoading = ref<boolean>(false);
const visible = ref<boolean>(false);
/**
* 获取镜头遮挡信息
*/
function getTamper(deviceIndex){
if(deviceIndex==null){
return
}
//
getTamperDet({
"deviceIndex": deviceIndex
}).then(res=>{
if(res.enabled == "on"){
formData.enabled = true;
}else{
formData.enabled = false;
}
formData.digitalSensitivity = res.digital_sensitivity;
});
//
getTamperNotif({
"deviceIndex": deviceIndex
}).then(res=>{
let tamper_notif_list = res.tamper_notif_list;
let sound_alarm_info = res.sound_alarm_info;
if(tamper_notif_list.msg_push_enabled == "on"){
formData.msgPushEnabled = true;
}else{
formData.msgPushEnabled = false;
}
if(tamper_notif_list.light_alarm_enabled == "on"){
formData.lightAlarmEnabled = true;
}else{
formData.lightAlarmEnabled = false;
}
if(tamper_notif_list.sound_alarm_enabled == "on"){
formData.soundAlarmEnabled = true;
}else{
formData.soundAlarmEnabled = false;
}
formData.soundAlarmType = sound_alarm_info.sound_alarm_type;
formData.soundAlarmTimes = sound_alarm_info.sound_alarm_times;
});
}
/**
* 设置镜头遮挡信息
*/
function setTamper(){
confirmLoading.value = true;
let params = {
"deviceIndex": formData.deviceIndex,
};
if(formData.enabled){
params["enabled"] = "on";
}else{
params["enabled"] = "off";
}
if(formData.enabled){
params["digitalSensitivity"] = formData.digitalSensitivity;
}
setTamperDet(params).then(res=>{
confirmLoading.value = false;
});
if(formData.enabled){
if(formData.msgPushEnabled){
params["msgPushEnabled"] = "on";
}else{
params["msgPushEnabled"] = "off";
}
if(formData.lightAlarmEnabled){
params["lightAlarmEnabled"] = "on";
}else{
params["lightAlarmEnabled"] = "off";
}
if(formData.soundAlarmEnabled){
params["soundAlarmEnabled"] = "on";
}else{
params["soundAlarmEnabled"] = "off";
}
params["soundAlarmType"] = formData.soundAlarmType;
params["soundAlarmTimes"] = formData.soundAlarmTimes;
setTamperNotif(params).then(res=>{
confirmLoading.value = false;
});
}
}
function soundAlarm(){
visible.value = true;
}
/**
* 播放告警声音
* @param id
*/
function playAudio(id){
let params = {
"deviceIndex": formData.deviceIndex,
"force": 1,
"id": id
};
testAudio(params);
}
onMounted(() => {
watch(
() => props.data,
async () => {
formData.deviceIndex = props.data.deviceIndex;
getTamper(formData.deviceIndex);
},
{ deep: true, immediate: true }
);
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px;
}
.labelText {
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 8px;
}
.ant-select {
display: flex;
}
.li {
padding: 12px 20px;
}
</style>

View File

@ -0,0 +1,455 @@
<template>
<a-spin :spinning="confirmLoading">
<JFormContainer :disabled="disabled">
<template #detail>
<a-row>
<a-col :span="12">
<a-row>
<a-col :span="20">
<a-divider orientation="left">设置视频呈现的效果</a-divider>
</a-col>
</a-row>
<a-row>
<a-col :span="2"></a-col>
<a-col :span="20">
<div id="video-container" class="video-container"></div>
</a-col>
</a-row>
<a-row style="margin-top: 14px">
<a-col :span="4" class="labelText">
画面镜像
</a-col>
<a-col :span="8">
<a-select v-model:value="formData.flip_type" @change="(value) => changeSwitch('flip_type', value)">
<a-select-option value="off">关闭</a-select-option>
<a-select-option value="left_and_right">左右</a-select-option>
<a-select-option value="up_and_down">上下</a-select-option>
<a-select-option value="center">中心</a-select-option>
</a-select>
</a-col>
<a-col :span="4" class="labelText">
画面翻转
</a-col>
<a-col :span="8">
<a-select v-model:value="formData.rotate_type" @change="(value) => changeSwitch('rotate_type', value)">
<a-select-option value="off">关闭</a-select-option>
<a-select-option value="anticlockwise_180">上下翻转</a-select-option>
<a-select-option value="anticlockwise_90">左翻转90°</a-select-option>
<a-select-option value="clockwise_90">右翻转90°</a-select-option>
</a-select>
</a-col>
</a-row>
<a-row style="margin-top: 14px">
<a-col :span="1"></a-col>
<a-col :span="20">
<a-button preIcon="ant-design:rollback-outlined" @click="restoreDefault">恢复默认</a-button>
</a-col>
</a-row>
</a-col>
<a-col :span="12">
<a-row>
<a-col :span="20">
<a-divider orientation="left">画面调节</a-divider>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
亮度
</a-col>
<a-col :span="12">
<a-slider v-model:value="formData.luma" :min="1" :max="100" @change="(value) => changeCommon('luma', value)"/>
</a-col>
<a-col :span="4">
<a-input-number v-model:value="formData.luma" :min="1" :max="100" style="margin-left: 12px" @change="(value) => changeCommon('luma', value)"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
对比度
</a-col>
<a-col :span="12">
<a-slider v-model:value="formData.contrast" :min="1" :max="100" @change="(value) => changeCommon('contrast', value)"/>
</a-col>
<a-col :span="4">
<a-input-number v-model:value="formData.contrast" :min="1" :max="100" style="margin-left: 12px" @change="(value) => changeCommon('contrast', value)"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
饱和度
</a-col>
<a-col :span="12">
<a-slider v-model:value="formData.saturation" :min="1" :max="100" @change="(value) => changeCommon('saturation', value)"/>
</a-col>
<a-col :span="4">
<a-input-number v-model:value="formData.saturation" :min="1" :max="100" style="margin-left: 12px" @change="(value) => changeCommon('saturation', value)"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
锐度
</a-col>
<a-col :span="12">
<a-slider v-model:value="formData.sharpness" :min="1" :max="100" @change="(value) => changeCommon('sharpness', value)"/>
</a-col>
<a-col :span="4">
<a-input-number v-model:value="formData.sharpness" :min="1" :max="100" style="margin-left: 12px" @change="(value) => changeCommon('sharpness', value)"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 10px">
<a-col :span="6" class="labelText">
宽动态
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.wide_dynamic" @change="(value) => changeCommon('wide_dynamic', value)">
<a-select-option value="off">关闭</a-select-option>
<a-select-option value="on">开启</a-select-option>
</a-select>
</a-col>
</a-row>
</a-col>
<a-col :span="20" v-show="formData.wide_dynamic=='on'">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText"></a-col>
<a-col :span="12">
<a-slider v-model:value="formData.wd_gain" :min="1" :max="100" @change="(value) => changeCommon('wd_gain', value)"/>
</a-col>
<a-col :span="4">
<a-input-number v-model:value="formData.wd_gain" :min="1" :max="100" style="margin-left: 12px" @change="(value) => changeCommon('wd_gain', value)"/>
</a-col>
</a-row>
</a-col>
</a-row>
<a-row>
<a-col :span="20">
<a-divider orientation="left">照明设置</a-divider>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
照明模式
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.night_vision_mode" @change="(value) => changeSwitch('night_vision_mode', value)">
<a-select-option value="wtl_night_vision">白光照明</a-select-option>
<a-select-option value="inf_night_vision">红外照明</a-select-option>
<a-select-option value="md_night_vision">移动侦测全彩</a-select-option>
</a-select>
</a-col>
</a-row>
</a-col>
<a-col :span="20" v-show="formData.night_vision_mode=='md_night_vision'">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
防红外过曝
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.smartir" @change="(value) => changeCommon('smartir', value)">
<a-select-option value="auto_ir_ae">自动-增强模式</a-select-option>
<a-select-option value="auto_ir">自动-标准模式</a-select-option>
<a-select-option value="manual">手动</a-select-option>
</a-select>
</a-col>
</a-row>
</a-col>
<a-col :span="20" v-show="formData.night_vision_mode=='md_night_vision'&&formData.smartir=='manual'">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
防过曝等级
</a-col>
<a-col :span="12">
<a-slider v-model:value="formData.smartir_level" :min="1" :max="100" @change="(value) => changeCommon('smartir_level', value)"/>
</a-col>
<a-col :span="4">
<a-input-number v-model:value="formData.smartir_level" :min="1" :max="100" style="margin-left: 12px" @change="(value) => changeCommon('smartir_level', value)"/>
</a-col>
</a-row>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
白光强度
</a-col>
<a-col :span="16">
<a-select v-model:value="formData.smartwtl" @change="(value) => changeCommon('smartwtl', value)">
<a-select-option value="auto_wtl_ae">智能白光-柔和</a-select-option>
<a-select-option value="auto_wtl">智能白光-标准</a-select-option>
<a-select-option value="manual">手动</a-select-option>
</a-select>
</a-col>
</a-row>
</a-col>
<a-col :span="20" v-show="formData.smartwtl=='manual'">
<a-row style="margin-bottom: 14px">
<a-col :span="6" class="labelText">
白光等级
</a-col>
<a-col :span="12">
<a-slider v-model:value="formData.smartwtl_digital_level" :min="1" :max="100" @change="(value) => changeCommon('smartwtl_digital_level', value)"/>
</a-col>
<a-col :span="4">
<a-input-number v-model:value="formData.smartwtl_digital_level" :min="1" :max="100" style="margin-left: 12px" @change="(value) => changeCommon('smartwtl_digital_level', value)"/>
</a-col>
</a-row>
</a-col>
</a-row>
</a-col>
</a-row>
</template>
</JFormContainer>
</a-spin>
</template>
<script lang="ts" setup>
import {
ref,
reactive,
defineProps,
onMounted,
watch,
} from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import { getValueType } from '/@/utils';
import { Form } from 'ant-design-vue';
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
import {
getImageCommon,
setImageCommon,
configRecovery,
getPreviewUrl,
} from "@/views/iot/tplink/camera/camera.api";
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
const formRef = ref();
const useForm = Form.useForm;
const formData = reactive<Record<string, any>>({
deviceIndex: '',//
streamType: 0,// 0 1
//
url: '',//URL
backupUrl: '',//URLIPnull
wsUrl: '',//ws
wssUrl: '',//wss
chroma: "0", //
luma: "0", //
sharpness: "0", //
saturation: "0", //
contrast: "0", //
wd_gain: "0", //
smartir_level: "0",//
wide_dynamic: "off", // "off"// "on"//
smartir: "auto_ir",//"auto_ir_ae" //- "auto_ir" //- "manual"//
smartwtl: "auto_wtl",//"auto_wtl_ae" //- "auto_wtl" //- "manual" //
smartwtl_digital_level: "0",//
flip_type: "center",// "off"// "left_and_right"// "up_and_down"// "center"//
night_vision_mode: "md_night_vision",//"wtl_night_vision"// "inf_night_vision"// "md_night_vision"//
rotate_type: "off",// "off"// "anticlockwise_180"//180 "clockwise_90"//90 "anticlockwise_90"//90
});
const player = ref();
const { createMessage } = useMessage();
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 8 } });
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
const confirmLoading = ref<boolean>(false);
/**
* 恢复默认
function restoreDefault(){
if(formData.deviceIndex==''){
return
}
confirmLoading.value=true;
configRecovery({"deviceIndex":formData.deviceIndex}).then(res=>{
confirmLoading.value=false;
getCommon(formData.deviceIndex);
getSwitch(formData.deviceIndex);
});
}
/**
* 播放
*/
async function preview(deviceIndex,streamType,url) {
if(deviceIndex==null){
return
}
if (url!=''){
return
}
await getPreviewUrl({"deviceIndex":deviceIndex,"streamType":streamType}).then(res=>{
formData.url = res.url;
formData.backupUrl = res.backupUrl;
formData.wsUrl = res.wsUrl;
formData.wssUrl = res.wssUrl;
confirmLoading.value=false;
});
const TumsPlayer = window['tums-player'].default;
player.value = new TumsPlayer('video-container', {
type: "rtsp", // rtsp
url: formData.url, // , getPreviewUrl
// url: formData.backupUrl, // , getPreviewUrl
socket: formData.wssUrl, // websocket, getPreviewUrl
pluginPath: '/static', // sdkpluginPath
});
let isPlaying = player.value.isPlaying();
if (!isPlaying) {
if (player.value.isInit) {
player.value.start();
} else {
player.value.play();
}
}
}
/**
* 销毁
*/
function destroy(player){
if (player){
player.value.destroy().then(() => {
}); //
}
}
/**
* 获取画面通用信息
*/
function getCommon(deviceIndex){
if(deviceIndex==null){
return
}
getImageCommon({
"deviceIndex": deviceIndex,
"type": "common"
}).then(res=>{
formData.chroma = res.chroma; //
formData.luma = res.luma; //
formData.sharpness = res.sharpness; //
formData.saturation = res.saturation; //
formData.contrast = res.contrast; //
formData.wd_gain = res.wd_gain; //
formData.smartir_level = res.smartir_level; //
formData.wide_dynamic = res.wide_dynamic; // "off"// "on"//
formData.smartir = res.smartir; //"auto_ir_ae" //- "auto_ir" //- "manual"//
formData.smartwtl = res.smartwtl; //"auto_wtl_ae" //- "auto_wtl" //- "manual" //
formData.smartwtl_digital_level = res.smartwtl_digital_level; //
});
}
/**
* 获取照明设置信息
*/
function getSwitch(deviceIndex){
if(deviceIndex==null){
return
}
getImageCommon({
"deviceIndex": deviceIndex,
"type": "switch"
}).then(res=>{
// console.log(res);
formData.flip_type = res.flip_type; //
formData.night_vision_mode = res.night_vision_mode; //
formData.rotate_type = res.rotate_type; // 使
});
}
/**
* 设置画面信息
* @param paramKey
* @param paramValue
*/
function setCommon(paramKey, paramValue){
setImageCommon({
"deviceIndex": formData.deviceIndex,
"type": formData.deviceIndex,
paramKey: paramValue
}).then(res=>{ });
}
/**
* 设置照明设置信息
* @param paramObj
* @param type
*/
function setCommonSwitch(paramObj,type){
setImageCommon({
"deviceIndex": formData.deviceIndex,
"type": type,
"param": paramObj
}).then(res=>{ });
}
/**
* 更改画面信息
* @param attr
* @param value
*/
function changeCommon(attr,value){
let param = {};
param[attr] = value;
setCommonSwitch(param,"common");
}
/**
* 更改照明设置信息
* @param attr
* @param value
*/
function changeSwitch(attr,value){
let param = {};
param[attr] = value;
setCommonSwitch(param,"switch");
}
onMounted(() => {
watch(
() => props.data,
async () => {
formData.deviceIndex = props.data.deviceIndex;
getCommon(formData.deviceIndex);
getSwitch(formData.deviceIndex);
preview(formData.deviceIndex,formData.streamType,formData.url);
},
{ deep: true, immediate: true }
);
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px;
}
.labelText {
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 8px;
}
.ant-select {
display: flex;
}
.video-container {
height: 500px;
width: 500px;
background-color: #010917;
}
</style>

View File

@ -0,0 +1,124 @@
<template>
<BasicDrawer
v-bind="$attrs"
@register="registerDrawer"
:title="getTitle"
:width="adaptiveWidth"
@ok="handleSubmit"
:showFooter="showFooter"
destroyOnClose
>
<BasicForm @register="registerForm" >
<template #customInput="{ model, field }">
<a-button preIcon="ant-design:poweroff-outlined" @click="restartNow(model)">立即重启</a-button>
</template>
</BasicForm>
</BasicDrawer>
</template>
<script lang="ts" setup>
import {defineComponent, ref, computed, unref, useAttrs, createVNode, h} from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from "@/views/iot/tplink/camera/camera.data";
import { update,rebootDevice } from '@/views/iot/tplink/camera/camera.api';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
import { getTenantId } from "/@/utils/auth";
import {Modal} from "ant-design-vue";
import {ExclamationCircleOutlined} from "@ant-design/icons-vue";
// Emits
const emit = defineEmits(['success', 'register']);
const attrs = useAttrs();
const isUpdate = ref(true);
const rowId = ref('');
const departOptions = ref([]);
let isFormDepartUser = false;
//
const [registerForm, { setProps, resetFields, setFieldsValue, validate, updateSchema }] = useForm({
labelWidth: 90,
schemas: formSchema,
showActionButtonGroup: false,
});
const showFooter = ref(true);
//
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
await resetFields();
showFooter.value = data?.showFooter ?? true;
setDrawerProps({ confirmLoading: false, showFooter: showFooter.value });
isUpdate.value = !!data?.isUpdate;
//
if (typeof data.record === 'object') {
setFieldsValue({
...data.record,
});
}
//
setProps({ disabled: !showFooter.value });
});
//
const getTitle = computed(() => {
if (!unref(isUpdate)) {
return '新增摄像头';
} else {
return unref(showFooter) ? '编辑摄像头' : '摄像头详情';
}
});
const { adaptiveWidth } = useDrawerAdaptiveWidth();
/**
* 提交事件
*/
async function handleSubmit() {
try {
let values = await validate();
setDrawerProps({ confirmLoading: true });
let params = values;
await update(params);
//
closeDrawer();
//
emit('success');
} finally {
setDrawerProps({ confirmLoading: false });
}
}
/**
* 设备重启
* @param record
*/
function restartNow(record){
Modal.confirm({
title: '设备重启',
width: '500px',
icon: createVNode(ExclamationCircleOutlined),
content: createVNode('div', { style: 'color:red;' }, '重启设备过程中,请勿插拔电源,以免损坏设备。确定要重启吗?'),
okText: '重启',
onOk() {
let params = {
deviceIndex: record.deviceIndex,
projectId: record.projectId,
};
rebootDevice(params);
Modal.success({
title: '重启IPC',
okText: '确定',
content: h('div', {}, [
h('p', '正在重启IPC重启过程大约需要60秒钟'),
h('p', '重启后,请刷新表格以获取最新设备状态'),
]),
});
},
onCancel() {
// console.log('Cancel');
},
class: 'test',
});
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,207 @@
<template>
<div>
<!--引用表格-->
<BasicTable @register="registerTable">
<!--插槽:table标题-->
<template #tableTitle>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)"/>
</template>
<template v-slot:bodyCell="{ column, record, index, text }">
</template>
</BasicTable>
<!-- 表单区域 -->
<CameraPreviewModal ref="previewModal"></CameraPreviewModal>
<CameraInfoDrawer @register="registerDrawer" @success="handleSuccess" />
</div>
</template>
<script lang="ts" name="iot-nuIotCameraInfo" setup>
import {ref, reactive, createVNode, h, onMounted, watch, unref} from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns, searchFormSchema } from '@/views/iot/tplink/camera/camera.data';
import { list } from '@/views/iot/tplink/camera/camera.api';
import { useUserStore } from '/@/store/modules/user';
import { useDrawer } from "@/components/Drawer";
import { useRouter } from 'vue-router';
import CameraPreviewModal from './CameraPreviewModal.vue'
import CameraInfoDrawer from './CameraInfoDrawer.vue'
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
//drawer
const [registerDrawer, { openDrawer }] = useDrawer();
let router = useRouter();
const formRef = ref();
const queryParam = reactive<any>({});
const registerModal = ref();
const previewModal = ref();
const deviceModal = ref();
const userStore = useUserStore();
//table
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '护理单元-物联管理-摄像头信息',
api: list,
columns,
canResize: false,
formConfig: {
// labelWidth: 200,
schemas: searchFormSchema,
},
actionColumn: {
width: 180,
fixed: 'right',
},
beforeFetch: async (params) => {
return Object.assign(params, queryParam);
},
},
});
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] = tableContext;
const labelCol = reactive({
xs:24,
sm:4,
xl:6,
xxl:4
});
const wrapperCol = reactive({
xs: 24,
sm: 20,
});
onMounted(() => {
watch(
() => props.data,
async () => {
console.log(props.data);
queryParam.projectId = props.data.projectId;
queryParam.regionId = props.data.regionId;
console.log(queryParam);
let record = unref(props.data);
if (typeof record !== 'object') {
record = {};
}
reload();
},
{ deep: true, immediate: true }
);
});
/**
* 编辑
*/
function handleEdit(record: Recordable) {
openDrawer(true, {
record,
isUpdate: true,
showFooter: true,
tenantSaas: false,
});
}
/**
* 预览
*/
function handlePreview(record: Recordable) {
previewModal.value.disableSubmit = true;
previewModal.value.edit(record);
}
/**
* 成功回调
*/
function handleSuccess() {
reload();
}
/**
* 画面配置
*/
function handlePicConfig(record) {
router.push({
path:'/tplink/cameraConfig',
query: {
deviceIndex: record.deviceIndex,
parentId: record.parentId,
multitrans: record.multitrans,
projectId: record.projectId,
regionId: record.regionId,
ip: record.ip
}
})
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '画面配置',
onClick: handlePicConfig.bind(null, record),
},
{
label: '编辑',
onClick: handleEdit.bind(null, record),
},
{
label: '预览',
onClick: handlePreview.bind(null, record),
},
];
}
/**
* 左侧树选择后触发
*/
function onTreeSelect(data) {
console.log('onTreeSelect: ', data);
// departData.value = data;
}
/**
* 左侧树rootTreeData触发
*/
function onRootTreeData(data) {
console.log('onRootTreeData: ', data);
// rootTreeData.value = data;
}
</script>
<style lang="less" scoped>
.jeecg-basic-table-form-container {
padding: 0;
.table-page-search-submitButtons {
display: block;
margin-bottom: 24px;
white-space: nowrap;
}
.query-group-cust{
min-width: 100px !important;
}
.query-group-split-cust{
width: 30px;
display: inline-block;
text-align: center
}
.ant-form-item:not(.ant-form-item-with-help){
margin-bottom: 16px;
height: 32px;
}
:deep(.ant-picker),:deep(.ant-input-number){
width: 100%;
}
}
.p-2{
height: calc(100vh - 120px);
}
</style>

View File

@ -0,0 +1,205 @@
<template>
<a-card :bordered="false" style="height: 100%">
<div class="j-table-operator" style="width: 100%">
<!-- <a-button preIcon="ant-design:sync-outlined" @click="loadRootTreeData">刷新</a-button>-->
<a-button type="primary" preIcon="ant-design:sync-outlined" @click="syncProjectInfo">同步项目</a-button>
<template v-if="currentRegion !=null">
<a-button preIcon="ant-design:sync-outlined" @click="syncRegionInfo">同步区域</a-button>
</template>
</div>
<a-spin :spinning="loading">
<!--区域树-->
<template v-if="treeData.length > 0">
<a-tree
v-if="!treeReloading"
:clickRowToExpand="false"
:treeData="treeData"
:selectedKeys="selectedKeys"
:checkStrictly="checkStrictly"
:load-data="loadChildrenTreeData"
:checkedKeys="checkedKeys"
v-model:expandedKeys="expandedKeys"
@select="onSelect"
>
</a-tree>
</template>
<a-empty v-else description="暂无数据" />
</a-spin>
</a-card>
</template>
<script lang="ts" setup>
import { nextTick, ref } from 'vue';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useMethods } from '/@/hooks/system/useMethods';
const { createMessage } = useMessage();
import { queryProjectTreeSync, queryRegionTreeSync, syncProject, syncRegion } from '@/views/iot/tplink/camera/camera.api';
const emit = defineEmits(['select', 'rootTreeData']);
const loading = ref<boolean>(false);
//
const treeData = ref<any[]>([]);
//
const checkedKeys = ref<any[]>([]);
//
const expandedKeys = ref<any[]>([]);
//
const selectedKeys = ref<any[]>([]);
//
const treeReloading = ref<boolean>(false);
//
const checkStrictly = ref<boolean>(true);
//
const currentRegion = ref<any>(null);
//
async function loadRootTreeData() {
try {
loading.value = true;
treeData.value = [];
const result = await queryProjectTreeSync();
if (Array.isArray(result)) {
treeData.value = result;
}
if (expandedKeys.value.length === 0) {
autoExpandParentNode();
} else {
if (selectedKeys.value.length === 0) {
let item = treeData.value[0];
if (item) {
//
setSelectedKey(item.id, item);
}
} else {
emit('select', currentRegion.value);
}
}
emit('rootTreeData', treeData.value);
} finally {
loading.value = false;
}
}
loadRootTreeData();
/**
* 加载子级区域信息
*/
async function loadChildrenTreeData(treeNode) {
try {
const result = await queryRegionTreeSync({
parentId: treeNode.dataRef.id,
projectId: treeNode.dataRef.projectId,
});
if (result.length == 0) {
treeNode.dataRef.isLeaf = true;
} else {
treeNode.dataRef.children = result;
if (expandedKeys.value.length > 0) {
//
let subKeys: any[] = [];
for (let key of expandedKeys.value) {
if (result.findIndex((item) => item.id === key) !== -1) {
subKeys.push(key);
}
}
if (subKeys.length > 0) {
expandedKeys.value = [...expandedKeys.value];
}
}
}
treeData.value = [...treeData.value];
emit('rootTreeData', treeData.value);
} catch (e) {
console.error(e);
}
return Promise.resolve();
}
/**
* 自动展开父节点只展开一级
*/
function autoExpandParentNode() {
let item = treeData.value[0];
if (item) {
if (!item.isLeaf) {
expandedKeys.value = [item.key];
}
//
setSelectedKey(item.id, item);
reloadTree();
} else {
emit('select', null);
}
}
/**
* 重新加载树组件防止无法默认展开数据
*/
async function reloadTree() {
await nextTick();
treeReloading.value = true;
await nextTick();
treeReloading.value = false;
}
/**
* 设置当前选中的行
*/
function setSelectedKey(key: string, data?: object) {
console.log('setSelectedKey: ', key);
selectedKeys.value = [key];
if (data) {
currentRegion.value = data;
emit('select', data);
}
}
/**
* 树选择事件
*/
function onSelect(selKeys, event) {
console.log('onSelect: ', selKeys, event);
if (selKeys.length > 0 && selectedKeys.value[0] !== selKeys[0]) {
setSelectedKey(selKeys[0], event.selectedNodes[0]);
} else {
//
setSelectedKey(selectedKeys.value[0]);
}
}
/**
* 同步项目
*/
async function syncProjectInfo(){
await syncProject();
await loadRootTreeData();
}
/**
* 同步区域
*/
async function syncRegionInfo(){
let data = currentRegion.value;
if (data == null) {
createMessage.warning('请先选择一个区域');
return;
}
const record = { projectId: data.projectId };
await syncRegion(record);
await loadRootTreeData();
}
defineExpose({
loadRootTreeData,
});
</script>
<style lang="less" scoped>
:deep(.ant-card-body){
padding: 24px 0px 0px 24px;
}
</style>

View File

@ -0,0 +1,713 @@
<template>
<a-spin :spinning="confirmLoading">
<JFormContainer :disabled="disabled">
<template #detail>
<a-row>
<a-col :span="24">
<a-row>
<a-col :span="20">
<a-divider orientation="left">白光报警</a-divider>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="4" class="labelText">
白光报警
</a-col>
<a-col :span="16">
<a-switch v-model:checked="formData.enabled" checked-children="已开启" un-checked-children="已关闭" />
</a-col>
</a-row>
</a-col>
</a-row>
</a-col>
<a-col :span="24">
<a-row>
<a-col :span="20">
<a-divider orientation="left">布防时间设置</a-divider>
</a-col>
<a-col style="width: 800px;">
<a-row style="margin-bottom: 14px">
<a-col :span="2" class="labelText">
</a-col>
<a-col :span="22">
<div class="cleanTitle">
<div>仅在以下时间段进行白光报警</div>
<div>
<a-button preIcon="ant-design:delete-outlined" @click="cleanPlan" :disabled="!formData.enabled">清空计划</a-button>
</div>
</div>
</a-col>
</a-row>
</a-col>
<a-col style="width: 800px;">
<a-row style="margin-bottom: 14px">
<a-col :span="2" class="labelText">
</a-col>
<a-col :span="22">
<div class="armingSchedule" id="armingSchedule" @mousedown="handleMouseDown">
<div id="recPlanGrid" class="test" style="overflow: hidden;">
<div class="cruiseDiv"><i class="iCruise displayNone" style="left: 373px;"></i></div>
<div class="psHourList">
<div class="psHourLi" v-for="(idx,index) in 25" :key="index">
<span>{{ index }}</span>
</div>
</div>
<ul class="psWeekList">
<li>星期一</li>
<li>星期二</li>
<li>星期三</li>
<li>星期四</li>
<li>星期五</li>
<li>星期六</li>
<li>星期日</li>
</ul>
<div class="tableDiv">
<div class="trDiv" v-for="weekDay in 7">
<div :class="getClass(index)" v-for="(idx,index) in 25" :key="index">
<div class="blank" style="width: 24px;" :id="getId(weekDay,index)" @click="divSelect($event,weekDay,index)" @mouseover="overDivSelect($event,weekDay,index)"></div>
</div>
</div>
<div id="editDiv"></div>
</div>
</div>
</div>
</a-col>
</a-row>
</a-col>
</a-row>
</a-col>
<a-col :span="24">
<a-row style="margin-top: 14px">
<a-col :span="1"></a-col>
<a-col :span="20">
<a-button type="primary" preIcon="ant-design:save-outlined" @click="saveAlarm">保存</a-button>
</a-col>
</a-row>
</a-col>
</a-row>
</template>
</JFormContainer>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineProps, onMounted, watch,} from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import { getValueType } from '/@/utils';
import { Form } from 'ant-design-vue';
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
import {
getAlarmInfo, setAlarmInfo,
getAlarmPlan, setAlarmPlan,
} from "@/views/iot/tplink/camera/camera.api";
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
const formData = reactive<Record<string, any>>({
deviceIndex: '', //
enabled: false, //
});
const confirmLoading = ref<boolean>(false);
const planVisible = ref<boolean>(false);
const mondayArr = ref<string>([]);
const mondayMergeArr = ref<string>([]);
const tuesdayArr = ref<string>([]);
const tuesdayMergeArr = ref<string>([]);
const wednesdayArr = ref<string>([]);
const wednesdayMergeArr = ref<string>([]);
const thursdayArr = ref<string>([]);
const thursdayMergeArr = ref<string>([]);
const fridayArr = ref<string>([]);
const fridayMergeArr = ref<string>([]);
const saturdayArr = ref<string>([]);
const saturdayMergeArr = ref<string>([]);
const sundayArr = ref<string>([]);
const sundayMergeArr = ref<string>([]);
/**
* 设置单元格样式
*/
function getClass(index){
if(index == 0) {
return 'tdDiv firTd';
}else if(index == 23){
return 'tdDiv lastTd';
}else if(index > 23){
return 'editBtn';
}else{
return 'tdDiv';
}
}
/**
* 设置单元格ID
*/
function getId(weekDay,index){
let id = weekDay+'-'+index;
return id
}
/**
* 格式化各星期数据
*/
function initPlan(armingScheduleLight){
let monday = decodeURIComponent(armingScheduleLight.monday);
initValue(1,monday);
mergeNumbers(1,1,-1);
let tuesday = decodeURIComponent(armingScheduleLight.tuesday);
initValue(2,tuesday);
mergeNumbers(1,2,-1);
let wednesday = decodeURIComponent(armingScheduleLight.wednesday);
initValue(3,wednesday);
mergeNumbers(1,3,-1);
let thursday = decodeURIComponent(armingScheduleLight.thursday);
initValue(4,thursday);
mergeNumbers(1,4,-1);
let friday = decodeURIComponent(armingScheduleLight.friday);
initValue(5,friday);
mergeNumbers(1,5,-1);
let saturday = decodeURIComponent(armingScheduleLight.saturday);
initValue(6,saturday);
mergeNumbers(1,6,-1);
let sunday = decodeURIComponent(armingScheduleLight.sunday);
initValue(7,sunday);
mergeNumbers(1,7,-1);
}
/**
* 初始化各星期数据
*/
function initValue(weekDay,valueArr){
valueArr = valueArr.replaceAll('[','').replaceAll(']','').replaceAll('"','');
const arr = valueArr.split(",");
arr.forEach(item =>{
const hours = item.split("-");
const start = parseInt(hours[0].substring(0, 2));
const end = parseInt(hours[1].substring(0, 2));
if(start<end){
for(let i=start;i<end;i++){
const id = weekDay+'-'+i;
const element = document.getElementById(id);
element.className = 'timing';
switch (weekDay) {
case 1:
mondayArr.value.push(i);
break;
case 2:
tuesdayArr.value.push(i);
break;
case 3:
wednesdayArr.value.push(i);
break;
case 4:
thursdayArr.value.push(i);
break;
case 5:
fridayArr.value.push(i);
break;
case 6:
saturdayArr.value.push(i);
break;
case 7:
sundayArr.value.push(i);
break;
default:
break;
}
}
}
})
}
/**
* 清除计划
*/
function cleanPlan(){
const elements = document.querySelectorAll('.timing');
elements.forEach(item =>{
item.className = 'blank';
})
mondayArr.value = [];
mondayMergeArr.value = [];
tuesdayArr.value = [];
tuesdayMergeArr.value = [];
wednesdayArr.value = [];
wednesdayMergeArr.value = [];
thursdayArr.value = [];
thursdayMergeArr.value = [];
fridayArr.value = [];
fridayMergeArr.value = [];
saturdayArr.value = [];
saturdayMergeArr.value = [];
sundayArr.value = [];
sundayMergeArr.value = [];
}
/**
* 合并各星期计划数组
*/
function mergeNumbers(type,weekDay,hourValue) {
let numbers = [];
switch (weekDay) {
case 1:
Object.assign(numbers, mondayArr.value);
break;
case 2:
Object.assign(numbers, tuesdayArr.value);
break;
case 3:
Object.assign(numbers, wednesdayArr.value);
break;
case 4:
Object.assign(numbers, thursdayArr.value);
break;
case 5:
Object.assign(numbers, fridayArr.value);
break;
case 6:
Object.assign(numbers, saturdayArr.value);
break;
case 7:
Object.assign(numbers, sundayArr.value);
break;
default:
break;
}
if(hourValue!=-1){
if(type == 1) {
numbers.push(hourValue);
numbers.sort(function(a, b) {
return a - b;
});
}else {
let idx = numbers.indexOf(hourValue);
numbers.splice(idx,1);
}
}
let start = numbers[0];
let end = null;
let mergedNumbers = [];
for (let i = 0; i < numbers.length; i++) {
if (i === 0) {
start = numbers[i];
end = numbers[i];
} else if (numbers[i] === numbers[i - 1] + 1) {
end = numbers[i];
} else {
if (start === end) {
mergedNumbers.push(start.toString());
} else {
mergedNumbers.push(`${start}-${end}`);
}
start = numbers[i];
end = numbers[i];
}
}
if (start === end) {
mergedNumbers.push(start.toString());
} else {
mergedNumbers.push(`${start}-${end}`);
}
if(mergedNumbers.length>6){
return false;
}
switch (weekDay) {
case 1:
mondayArr.value = [];
mondayMergeArr.value = [];
Object.assign(mondayMergeArr.value, mergedNumbers);
Object.assign(mondayArr.value, numbers);
break;
case 2:
tuesdayArr.value = [];
tuesdayMergeArr.value = [];
Object.assign(tuesdayMergeArr.value, mergedNumbers);
Object.assign(tuesdayArr.value, numbers);
break;
case 3:
wednesdayArr.value = [];
wednesdayMergeArr.value = [];
Object.assign(wednesdayMergeArr.value, mergedNumbers);
Object.assign(wednesdayArr.value, numbers);
break;
case 4:
thursdayArr.value = [];
thursdayMergeArr.value = [];
Object.assign(thursdayMergeArr.value, mergedNumbers);
Object.assign(thursdayArr.value, numbers);
break;
case 5:
fridayArr.value = [];
fridayMergeArr.value = [];
Object.assign(fridayMergeArr.value, mergedNumbers);
Object.assign(fridayArr.value, numbers);
break;
case 6:
saturdayArr.value = [];
saturdayMergeArr.value = [];
Object.assign(saturdayMergeArr.value, mergedNumbers);
Object.assign(saturdayArr.value, numbers);
break;
case 7:
sundayArr.value = [];
sundayMergeArr.value = [];
Object.assign(sundayMergeArr.value, mergedNumbers);
Object.assign(sundayArr.value, numbers);
break;
default:
break;
}
// console.log('', mondayArr.value);
// console.log('', tuesdayArr.value);
// console.log('', wednesdayArr.value);
// console.log('', thursdayArr.value);
// console.log('', fridayArr.value);
// console.log('', saturdayArr.value);
// console.log('', sundayArr.value);
// console.log('', mondayMergeArr.value);
// console.log('', tuesdayMergeArr.value);
// console.log('', wednesdayMergeArr.value);
// console.log('', thursdayMergeArr.value);
// console.log('', fridayMergeArr.value);
// console.log('', saturdayMergeArr.value);
// console.log('', sundayMergeArr.value);
return true;
}
/**
* 点击选择单元格
*/
function divSelect(event,weekDay,hourValue){
if(formData.enabled){
if(event.target.className == "blank"){
let visiable = mergeNumbers(1,weekDay,hourValue);
if(visiable){
event.target.className = "timing";
}
}else{
let visiable = mergeNumbers(2,weekDay,hourValue);
if(visiable){
event.target.className = "blank";
}
}
}
}
/**
* 悬浮选择单元格
*/
function overDivSelect(event,weekDay,hourValue){
if(formData.enabled&&planVisible.value){
if(event.target.className == "blank"){
let visiable = mergeNumbers(1,weekDay,hourValue);
if(visiable){
event.target.className = "timing";
}
}else{
let visiable = mergeNumbers(2,weekDay,hourValue);
if(visiable){
event.target.className = "blank";
}
}
}
}
/**
* 获取报警
*/
function getAlarm(deviceIndex){
if(deviceIndex==null){
return
}
//
getAlarmInfo({
"deviceIndex": deviceIndex
}).then(res=>{
if(res.light_alarm_enabled == "on"){
formData.enabled = true;
}else{
formData.enabled = false;
}
});
//
getAlarmPlan({
"deviceIndex": deviceIndex,
"type": "light"
}).then(res=>{
let armingScheduleLight = res.arming_schedule_light;
initPlan(armingScheduleLight);
});
}
/**
* 保存白光报警设置信息
*/
function saveAlarm(){
confirmLoading.value = true;
let params = {
"deviceIndex": formData.deviceIndex,
"type": "light"
};
if(formData.enabled){
params["enabled"] = "on";
}else{
params["enabled"] = "off";
}
setAlarmInfo(params).then(res=>{
confirmLoading.value = false;
});
if(formData.enabled){
params["alarmPlan"] = setAlarmPlanData();
setAlarmPlan(params).then(res=>{
confirmLoading.value = false;
});
}
}
/**
* 获取报警计划数据
*/
function setAlarmPlanData(){
const monday = getHoursEncode(mondayMergeArr.value);
const tuesday = getHoursEncode(tuesdayMergeArr.value);
const wednesday = getHoursEncode(wednesdayMergeArr.value);
const thursday = getHoursEncode(thursdayMergeArr.value);
const friday = getHoursEncode(fridayMergeArr.value);
const saturday = getHoursEncode(saturdayMergeArr.value);
const sunday = getHoursEncode(sundayMergeArr.value);
let alarmPlan = {
"monday": monday,
"tuesday": tuesday,
"wednesday": wednesday,
"thursday": thursday,
"friday": friday,
"saturday": saturday,
"sunday": sunday
}
return alarmPlan;
}
/**
* 格式化设置计划数据
* @param weekArr
*/
function getHoursEncode(weekArr){
let hoursEncode = "[";
if(weekArr.length>0){
let hourArrStr = "";
weekArr.forEach(item =>{
let hours = 0;
let end = 0;
const arr = item.split("-");
hours = parseInt(arr[0]);
if(arr.length>1){
end = parseInt(arr[1]) + 1;
}else{
end = hours + 1;
}
if(hours<10){
hours = "0" + hours;
}
hours = hours + "00-"
if(end<10){
hours = hours + "0" + end;
}else{
hours = hours + end;
}
hours = hours + "00";
hours = '"' + hours + '"';
hourArrStr = hourArrStr + hours + ",";
})
hoursEncode = "[" + hourArrStr.substring(0,hourArrStr.length-1) + "]";
}else{
hoursEncode = '["0000-0000"]';
}
return encodeURIComponent(hoursEncode);
}
/**
* 鼠标按下
*/
function handleMouseDown(){
// console.log('', event);
planVisible.value = true;
}
/**
* 鼠标抬起
*/
function handleMouseUp(){
// console.log('', event);
planVisible.value = false;
}
onMounted(() => {
watch(
() => props.data,
async () => {
formData.deviceIndex = props.data.deviceIndex;
getAlarm(formData.deviceIndex);
},
{ deep: true, immediate: true }
);
// window.addEventListener('mousedown', handleMouseDown);
window.addEventListener('mouseup', handleMouseUp);
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px;
}
.labelText {
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 8px;
}
.cleanTitle {
display: flex;
justify-content: space-between;
align-items: center;
}
div.armingSchedule {
*position: relative;
z-index: 0;
background: #ebeef0;
border: 1px solid #ced7e0;
height: 260px;
border-radius: 2px;
}
div.cruiseDiv {
width: 100%;
height: 8px;
}
div.cruiseDiv i.iCruise {
width: 8px;
height: 8px;
position: relative;
//background: url(../../web-static/images/pointDown.png) no-repeat;
display: inline-block;
*display: inline;
*zoom: 1;
}
div.psHourList {
color: #666;
margin-left: 63px;
}
div.psHourList div.psHourLi {
width: 25px;
display: inline-block;
text-align: center;
*display: inline;
*zoom: 1;
}
ul.psWeekList {
float: left;
color: #333;
font-size: 13px;
list-style: none;
margin-right: 16px;
margin-left: 20px;
_width: 42px;
_margin-left: 8px;
}
ul.psWeekList li {
height: 32px;
line-height: 32px;
_height: 34px;
}
div.tableDiv {
z-index: 0;
*position: relative;
float: left;
}
div.tableDiv div.trDiv {
width: 100%;
height: 24px;
margin-top: 4px;
margin-bottom: 8px;
}
div.tableDiv div.trDiv div.firTd {
border-left: solid 1px #ced7e0;
border-bottom-left-radius: 2px!important;
border-top-left-radius: 2px!important;
}
div.tableDiv div.trDiv div.tdDiv {
height: 24px;
overflow: hidden;
vertical-align: middle;
border-right: solid 1px #ced7e0;
border-top: solid 1px #ced7e0;
border-bottom: solid 1px #ced7e0;
display: inline-block;
*display: inline;
*zoom: 1;
}
div.tableDiv div.trDiv div.editBtn {
//background-image: url(@/assets/loginmini/icon/jeecg_bg.png);
//background-image: url(@/assets/images/planEdit.png);
/* background-image: url(@/views/iot/tplink/camera/icons/planEdit.png);*/
background: url(../icons/plan_set.png) no-repeat;
background-size: cover;
//background: url(@/assets/images/planEdit.png) no-repeat;
//background: new URL('@/assets/images/planEdit.png', import.meta.url).href;
width: 20px;
vertical-align: middle;
height: 24px;
cursor: pointer;
margin-left: 8px;
margin-right: 8px;
display: none;
zoom: 1;
}
div.tableDiv div.trDiv:hover div.editBtn{
display: inline-block;
}
div.tableDiv div.trDiv div.lastTd {
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
}
div.tableDiv div.trDiv div.tdDiv div.timing {
background-color: #5a92ff;
width: 24px;
height: 24px;
}
div.tableDiv div.trDiv div.tdDiv div.blank {
background-color: #ffffff;
width: 24px;
height: 24px;
}
.displayNone {
display: none!important;
}
</style>

View File

@ -0,0 +1,195 @@
<template>
<a-spin :spinning="confirmLoading">
<a-row>
<a-col :span="24">
<div id="video-container-multitrans" v-if="initContainerMultitrans"></div>
</a-col>
<a-col :span="24" style="padding: 5px;">
<span style="margin-left: 15px;">回放倍速
<a-select
ref="select"
v-model:value="formData.scale"
style="width: 120px"
@focus="focus"
@change="changeScale"
>
<a-select-option value="0.0625">0.0625</a-select-option>
<a-select-option value="0.125">0.125</a-select-option>
<a-select-option value="0.25">0.25</a-select-option>
<a-select-option value="0.5">0.5</a-select-option>
<a-select-option value="1">1</a-select-option>
<a-select-option value="2">2</a-select-option>
<a-select-option value="4">4</a-select-option>
<a-select-option value="8">8</a-select-option>
<a-select-option value="16">16</a-select-option>
</a-select>
</span>
</a-col>
</a-row>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineExpose, nextTick, defineProps, computed, onMounted } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import { getValueType } from '/@/utils';
import { Form } from 'ant-design-vue';
import { getMultitransUrl } from "@/views/iot/tplink/camera/camera.api";
const TumsPlayer = window['tums-player'].default;
const props = defineProps({
formDisabled: { type: Boolean, default: false },
formData: { type: Object, default: () => ({})},
formBpm: { type: Boolean, default: true }
});
const formRef = ref();
const player = ref();
const initContainerMultitrans = ref(true);
const useForm = Form.useForm;
const emit = defineEmits(['register', 'ok']);
const formData = reactive<Record<string, any>>({
//
videoDevId: '',//
multitrans: '',//IPCmultitrans
startTime: '',//
endTime: '',//
scale: 1,//
});
const { createMessage } = useMessage();
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
const confirmLoading = ref<boolean>(false);
//
const validatorRules = reactive({
});
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: false });
//
const disabled = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return false;
}else{
return true;
}
}
return props.formDisabled;
});
/**
* 详情
*/
async function edit(record) {
await nextTick(() => {
confirmLoading.value=true;
resetFields();
const tmpData = {};
Object.keys(formData).forEach((key) => {
if(record.hasOwnProperty(key)){
tmpData[key] = record[key]
}
})
//
Object.assign(formData, tmpData);
});
//
await getMultitransUrl({
"deviceIndex":formData.videoDevId,
"startTime":formData.startTime,
"endTime":formData.endTime,
"scale":formData.scale
}).then(res=>{
confirmLoading.value=false;
if(res.success == false){
createMessage.error(res.message)
}else{
const list = res;
list.forEach(item => {
let startTime = item.startTime*1000;
let endTime = item.endTime*1000;
let duration = endTime - startTime;
// console.log(duration);
// console.log("url:"+item.url);
// console.log("socket:"+item.wssUrl);
// console.log("queryAddress:"+item.queryAddress);
// console.log("videoDevId:"+item.videoDevId);
// console.log("storageDevId:"+item.storageDevId);
// console.log("startTime:"+startTime);
// console.log("endTime:"+endTime);
// console.log("scale:"+item.scale);
player.value = new TumsPlayer('video-container-multitrans', {
"autoplay": true,
"resolution": "HD",
"streamType": "sdvod", //
"url": item.url, // url
"socket": item.wssUrl,
"type": "rtsp",
"decoderType": "wasm", //
"queryAddress": item.queryAddress,
"videoSessionId": "",
"videoDevId": item.videoDevId, // id
"useMultitrans": true,
"storageDevId": item.storageDevId,
"startTime": startTime, // 13
"endTime": endTime, // 13
"eventType": [1, 2], //
"scale": item.scale //
});
setTimeout(()=>{stop(player)}, duration);
})
}
});
}
/**
* 切换回放倍
*/
function changeScale(){
createMessage.info('切换回放倍速至'+formData.scale+'倍');
let scale = parseInt(formData.scale) ;
player.value.setPlaybackConfig({"scale":scale});
}
/**
* 暂停播放
*/
function stop(player){
if (player){
player.value.stop();
}
}
/**
* 销毁
*/
function destroy(){
console.log("destroy");
if (player.value){
player.value.destroy().then(() => { }); //
initContainerMultitrans.value = false
setTimeout(()=>{
initContainerMultitrans.value = true
},1000)
}
}
defineExpose({
edit,
destroy
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px;
}
#video-container-multitrans {
padding: 0px 25px;
width: 600px;
height: 550px;
background: #1a1a1a;
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<j-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
<CameraMultitransForm ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></CameraMultitransForm>
</j-modal>
</template>
<script lang="ts" setup>
import { ref, nextTick, defineExpose } from 'vue';
import CameraMultitransForm from './CameraMultitransForm.vue'
import JModal from '/@/components/Modal/src/JModal/JModal.vue';
const title = ref<string>('');
const width = ref<number>(600);
const visible = ref<boolean>(false);
const disableSubmit = ref<boolean>(false);
const registerForm = ref();
const emit = defineEmits(['register', 'success']);
// const player = ref();
/**
* 编辑
* @param record
*/
function edit(record) {
title.value = "录像回放";
visible.value = true;
nextTick(() => {
registerForm.value.edit(record);
});
}
/**
* 确定按钮点击事件
*/
function handleOk() {
registerForm.value.submitForm();
}
/**
* form保存回调事件
*/
function submitCallback() {
handleCancel();
}
/**
* 取消按钮回调事件
*/
function handleCancel() {
registerForm.value.destroy();
visible.value = false;
}
defineExpose({
edit,
disableSubmit,
});
</script>
<style lang="less">
/**隐藏样式-modal确定按钮 */
.jee-hidden {
display: none !important;
}
</style>
<style lang="less" scoped></style>

View File

@ -0,0 +1,305 @@
<template>
<a-spin :spinning="confirmLoading">
<JFormContainer :disabled="disabled">
<template #detail>
<a-row>
<a-col :span="24">
<a-row>
<a-col :span="1"></a-col>
<a-col :span="22">
<a-divider orientation="left">设置视频呈现的效果</a-divider>
</a-col>
</a-row>
<a-row>
<a-col :span="1"></a-col>
<a-col :span="20">
<div id="video-container" class="video-container">
<div class="osd-text-top">
<span v-show="formData.dateEnabled">{{formData.dateData}}&nbsp;</span>
<span v-show="formData.weekEnabled">{{formData.weekData}}&nbsp;</span>
<span v-show="formData.dateEnabled">{{formData.timeData}}</span>
</div>
<div class="osd-text-bottom" v-show="formData.labelEnabled">
{{formData.labelData}}
</div>
</div>
</a-col>
</a-row>
<a-row>
<a-col :span="1"></a-col>
<a-col :span="20">
<a-row style="margin: 14px 0px">
<a-col :span="3" class="label-text">
<a-checkbox v-model:checked="formData.dateEnabled">显示日期</a-checkbox>
</a-col>
<a-col :span="8">
{{formData.dateData}} {{formData.timeData}}
</a-col>
</a-row>
</a-col>
</a-row>
<a-row>
<a-col :span="1"></a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="3" class="label-text">
<a-checkbox v-model:checked="formData.weekEnabled">显示星期</a-checkbox>
</a-col>
<a-col :span="8">
{{formData.weekData}}
</a-col>
</a-row>
</a-col>
</a-row>
<a-row>
<a-col :span="1"></a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="3" class="label-text">
<a-checkbox v-model:checked="formData.labelEnabled">通道名称</a-checkbox>
</a-col>
<a-col :span="8">
<a-input v-model:value="formData.labelData" :disabled="!formData.labelEnabled"/>
</a-col>
</a-row>
</a-col>
</a-row>
<a-row style="margin-top: 14px">
<a-col :span="1"></a-col>
<a-col :span="20">
<a-button type="primary" preIcon="ant-design:save-outlined" @click="setOsdData">保存</a-button>
</a-col>
</a-row>
</a-col>
</a-row>
</template>
</JFormContainer>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineProps, onMounted, watch,} from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import { getValueType } from '/@/utils';
import { Form } from 'ant-design-vue';
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
import {
getOsdCapability,
getOsd,
setOsd,
getPreviewUrl
} from "@/views/iot/tplink/camera/camera.api";
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
const formData = reactive<Record<string, any>>({
deviceIndex: '', //
weekData:'',
dateData:'',
timeData:'',
labelData:'',
dateEnabled: true,
weekEnabled: true,
labelEnabled: true,
streamType: 0,// 0 1
//
url: '',//URL
backupUrl: '',//URLIPnull
wsUrl: '',//ws
wssUrl: '',//wss
});
const player = ref();
const confirmLoading = ref<boolean>(false);
/**
* 获取当前时间和星期
*/
function getCurrentTime(){
let days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
let now = new Date();
formData.dateData = now.getFullYear()+"-"+(('0' + (now.getMonth() + 1)).slice(-2))+"-"+(('0' + (now.getDate() + 1)).slice(-2));
formData.timeData = (('0' + (now.getHours() + 1)).slice(-2))+":"+(('0' + (now.getMinutes() + 1)).slice(-2))+":"+(('0' + (now.getSeconds() + 1)).slice(-2))
formData.weekData = days[now.getDay()];
}
/**
* 获取OSD能力集
*/
// function getCapability(deviceIndex){
// if(deviceIndex==null){
// return
// }
// getOsdCapability({
// "deviceIndex": deviceIndex
// }).then(res=>{
//
// });
// }
/**
* 获取OSD参数
*/
function getOsdData(deviceIndex){
if(deviceIndex==null){
return
}
getOsd({
"deviceIndex": deviceIndex
}).then(res=>{
if(res.date.enabled == "on"){
formData.dateEnabled = true;
}else{
formData.dateEnabled = false;
}
if(res.week.enabled == "on"){
formData.weekEnabled = true;
}else{
formData.weekEnabled = false;
}
if(res.label_info[0].label_info_1.enabled == "on"){
formData.labelEnabled = true;
}else{
formData.labelEnabled = false;
}
formData.labelData = res.label_info[0].label_info_1.text;
formData.labelData = formData.labelData.replaceAll("%20", " ")
});
}
/**
* 设置OSD参数
*/
function setOsdData(){
confirmLoading.value = true;
let params = {
"deviceIndex": formData.deviceIndex,
"mainFontPixel": [256,256,256,256,256] //
};
if(formData.dateEnabled){
params["dateEnabled"] = "on";
}else{
params["dateEnabled"] = "off";
}
if(formData.weekEnabled){
params["weekEnabled"] = "on";
}else{
params["weekEnabled"] = "off";
}
if(formData.labelEnabled){
params["labelEnabled"] = "on";
}else{
params["labelEnabled"] = "off";
}
setOsd(params).then(res=>{
confirmLoading.value = false;
});
}
/**
* 播放
*/
async function preview(deviceIndex,streamType,url) {
if(deviceIndex==null){
return
}
if (url!=''){
return
}
await getPreviewUrl({"deviceIndex":deviceIndex,"streamType":streamType}).then(res=>{
formData.url = res.url;
formData.backupUrl = res.backupUrl;
formData.wsUrl = res.wsUrl;
formData.wssUrl = res.wssUrl;
confirmLoading.value=false;
});
const TumsPlayer = window['tums-player'].default;
player.value = new TumsPlayer('video-container', {
type: "rtsp", // rtsp
url: formData.url, // , getPreviewUrl
// url: formData.backupUrl, // , getPreviewUrl
socket: formData.wssUrl, // websocket, getPreviewUrl
pluginPath: '/static', // sdkpluginPath
});
let isPlaying = player.value.isPlaying();
if (!isPlaying) {
if (player.value.isInit) {
player.value.start();
} else {
player.value.play();
}
}
}
/**
* 销毁
*/
function destroy(player){
if (player){
player.value.destroy().then(() => {
}); //
}
}
onMounted(() => {
watch(
() => props.data,
async () => {
formData.deviceIndex = props.data.deviceIndex;
// getCapability(formData.deviceIndex);
getCurrentTime();
getOsdData(formData.deviceIndex);
preview(formData.deviceIndex,formData.streamType,formData.url);
},
{ deep: true, immediate: true }
);
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px;
}
.label-text {
display: flex;
align-items: center;
}
.ant-select {
display: flex;
}
.video-container {
height: 500px;
width: 500px;
background-color: #010917;
position: relative;
}
.osd-text-top {
color: #40a9ff;
padding-left: 10px;
padding-top: 5px;
position: absolute;
top: 0px;
z-index: 999;
}
.osd-text-bottom {
color: #40a9ff;
padding-left: 10px;
padding-bottom: 5px;
position: absolute;
bottom: 0px;
z-index: 999;
}
</style>

View File

@ -0,0 +1,228 @@
<template>
<div>
<a-tabs
v-model:activeKey="activeKey"
tab-position="left"
:style="{ height: '100%' }"
@tabScroll="callback"
>
<a-tab-pane :key="1" tab="画面显示">
<a-tabs
v-model:activeKey="oneChildActiveKey"
tab-position="top"
:style="{ height: '100%' }"
@tabScroll="callback"
type="card"
>
<a-tab-pane :key="1" tab="视频显示">
<div class="scrollable" v-if="oneChildActiveKey==1">
<CameraCommonForm :data="cameraData"></CameraCommonForm>
</div>
</a-tab-pane>
<a-tab-pane :key="2" tab="OSD">
<div class="scrollable" v-if="oneChildActiveKey==2">
<CameraOsdForm :data="cameraData"></CameraOsdForm>
</div>
</a-tab-pane>
<a-tab-pane :key="3" tab="码流参数">
<div class="scrollable" v-if="oneChildActiveKey==3">
<CameraBitrateForm :data="cameraData"></CameraBitrateForm>
</div>
</a-tab-pane>
</a-tabs>
</a-tab-pane>
<a-tab-pane :key="2" tab="事件侦测">
<a-tabs
v-model:activeKey="twoChildActiveKey"
tab-position="top"
:style="{ height: '100%' }"
@tabScroll="callback"
type="card"
>
<a-tab-pane :key="1" tab="镜头遮挡">
<div class="scrollable">
<CameraBlockForm :data="cameraData"></CameraBlockForm>
</div>
</a-tab-pane>
<!-- <a-tab-pane :key="2" tab="移动侦测">-->
<!-- <div class="scrollable">-->
<!-- 21-->
<!-- </div>-->
<!-- </a-tab-pane>-->
<!-- <a-tab-pane :key="3" tab="区域覆盖">-->
<!-- <div class="scrollable">-->
<!-- 23-->
<!-- </div>-->
<!-- </a-tab-pane>-->
<!-- <a-tab-pane :key="4" tab="越界侦测">-->
<!-- <div class="scrollable">-->
<!-- 24-->
<!-- </div>-->
<!-- </a-tab-pane>-->
<!-- <a-tab-pane :key="5" tab="区域入侵">-->
<!-- <div class="scrollable">-->
<!-- 25-->
<!-- </div>-->
<!-- </a-tab-pane>-->
</a-tabs>
</a-tab-pane>
<a-tab-pane :key="3" tab="报警设备">
<a-tabs
v-model:activeKey="threeChildActiveKey"
tab-position="top"
:style="{ height: '100%' }"
@tabScroll="callback"
type="card"
>
<a-tab-pane :key="1" tab="白光报警">
<div class="scrollable" v-if="threeChildActiveKey==1">
<CameraLightAlarmForm :data="cameraData"></CameraLightAlarmForm>
</div>
</a-tab-pane>
<a-tab-pane :key="2" tab="声音报警">
<div class="scrollable" v-if="threeChildActiveKey==2">
<CameraSoundAlarmForm :data="cameraData"></CameraSoundAlarmForm>
</div>
</a-tab-pane>
</a-tabs>
</a-tab-pane>
<a-tab-pane :key="4" tab="录像管理">
<a-tabs
v-model:activeKey="fourChildActiveKey"
tab-position="top"
:style="{ height: '100%' }"
@tabScroll="callback"
type="card"
>
<a-tab-pane :key="1" tab="MP4转发FTP">
<div class="scrollable">
<CameraUploadForm :data="cameraData"></CameraUploadForm>
</div>
</a-tab-pane>
<a-tab-pane :key="2" tab="当天录像">
<div class="scrollable">
<CameraRecordList :data="cameraData" />
</div>
</a-tab-pane>
<!-- <a-tab-pane :key="3" tab="录像设置">
<div class="scrollable">
<a-button type="primary" preIcon="ant-design:search-outlined" @click="recordingConfig">获取录像配置</a-button>
<a-button type="primary" preIcon="ant-design:search-outlined" @click="setRecordingPlan">设置录像计划</a-button>
<a-button type="primary" preIcon="ant-design:search-outlined" @click="batchProgress">录像计划进度</a-button>
</div>
</a-tab-pane>-->
</a-tabs>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script lang="ts" name="iot-nuIotCameraInfo" setup>
import { ref, onMounted, reactive } from "vue";
import { useRouter } from 'vue-router';
import type { TabsProps } from 'ant-design-vue';
import { useListPage } from "@/hooks/system/useListPage";
import { getImageCommon,setImageCommon } from "@/views/iot/tplink/camera/camera.api";
import { recordingColumns,searchRecording } from "@/views/iot/tplink/camera/camera.data";
import CameraCommonForm from './CameraCommonForm.vue';//
import CameraOsdForm from './CameraOsdForm.vue';//OSD
import CameraBitrateForm from './CameraBitrateForm.vue';//
import CameraBlockForm from './CameraBlockForm.vue';//
import CameraLightAlarmForm from './CameraLightAlarmForm.vue';//
import CameraSoundAlarmForm from './CameraSoundAlarmForm.vue';//
import CameraRecordList from './CameraRecordList.vue';//
import CameraUploadForm from './CameraUploadForm.vue';//FTP
import { useMessage } from "@/hooks/web/useMessage";
const { createMessage } = useMessage();
let router = useRouter();
const formData = reactive<Record<string, any>>({
//
deviceIndex: '',//
parentId: '',//(nvr,nvs)ID
multitrans: '',//multitrans
scale: 1 ,//
projectId: '' ,//ID
regionId: '' ,//ID
ip: '' ,//IP
});
const cameraData = ref({});
const activeKey = ref(1);
const oneChildActiveKey = ref(1);
const twoChildActiveKey = ref(1);
const threeChildActiveKey = ref(1);
const imageData = reactive<Record<string, any>>({
//
luma: "50", //
contrast: '50', //
saturation: '50', //
sharpness: '50', //
wide_dynamic: 'off', //
wd_gain: '50', //
});
// function recordingConfig(){
// getRecordCfgs({
// "deviceIndex": formData.deviceIndex,
// "parentId": formData.parentId,
// "projectId": formData.projectId,
// "regionId": formData.regionId,
// "ip": formData.ip
// }).then(res=>{
//
// });
// }
//
// function setRecordingPlan(){
// setRecordCfgs({
// "deviceIndex": formData.deviceIndex,
// "parentId": formData.parentId,
// "recordSwitch": 0,
// "streamType": 0
// }).then(res=>{
//
// });
// }
//
// function batchProgress(){
// getBatchProgress({}).then(res=>{});
// }
onMounted(()=>{
formData.deviceIndex = router.currentRoute.value.query.deviceIndex;
formData.parentId = router.currentRoute.value.query.parentId;
formData.multitrans = router.currentRoute.value.query.multitrans;
formData.projectId = router.currentRoute.value.query.projectId;
formData.regionId = router.currentRoute.value.query.regionId;
formData.ip = router.currentRoute.value.query.ip;
cameraData.value.deviceIndex = formData.deviceIndex;
cameraData.value.projectId = formData.projectId;
cameraData.value.regionId = formData.regionId;
cameraData.value.multitrans = formData.multitrans;
cameraData.value.scale = formData.scale;
})
</script>
<style lang="less" scoped>
.scrollable {
width: 99.6%;
height: calc(100vh - 160px);
overflow: auto; /* 或者使用 scroll, hidden, visible */
//border: 1px solid #000;
background-color: white;
}
:deep(.ant-tabs-nav){
margin-bottom: 3px;
margin-left: -1px;
}
:deep(.ant-tabs-left>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane){
padding-left: 3px;
}
</style>

View File

@ -0,0 +1,197 @@
<template>
<a-spin :spinning="confirmLoading">
<a-row>
<a-col :span="24">
<div id="video-container-playback" v-if="initContainerPlayback"></div>
</a-col>
<a-col :span="24" style="padding: 5px;">
<span style="margin-left: 15px;">回放倍速
<a-select
ref="select"
v-model:value="formData.scale"
style="width: 120px"
@focus="focus"
@change="changeScale"
>
<a-select-option value="0.0625">0.0625</a-select-option>
<a-select-option value="0.125">0.125</a-select-option>
<a-select-option value="0.25">0.25</a-select-option>
<a-select-option value="0.5">0.5</a-select-option>
<a-select-option value="1">1</a-select-option>
<a-select-option value="2">2</a-select-option>
<a-select-option value="4">4</a-select-option>
<a-select-option value="8">8</a-select-option>
<a-select-option value="16">16</a-select-option>
</a-select>
</span>
</a-col>
</a-row>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineExpose, nextTick, defineProps, computed, onMounted } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import { getValueType } from '/@/utils';
import { Form } from 'ant-design-vue';
import {
getPlaybackUrlList,
deletePlaybackChn
} from "@/views/iot/tplink/camera/camera.api";
const TumsPlayer = window['tums-player'].default;
const props = defineProps({
formDisabled: { type: Boolean, default: false },
formData: { type: Object, default: () => ({})},
formBpm: { type: Boolean, default: true }
});
const formRef = ref();
const player = ref();
const initContainerPlayback = ref(true);
const useForm = Form.useForm;
const emit = defineEmits(['register', 'ok']);
const formData = reactive<Record<string, any>>({
//
videoDevId: '',//
startTime: '',//
endTime: '',//
videoType: '',//
scale: 1,//
sessionId: ''//sessionId
});
const { createMessage } = useMessage();
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
const confirmLoading = ref<boolean>(false);
//
const validatorRules = reactive({
});
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: false });
//
const disabled = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return false;
}else{
return true;
}
}
return props.formDisabled;
});
/**
* 详情
*/
async function edit(record) {
await nextTick(() => {
confirmLoading.value=true;
resetFields();
const tmpData = {};
Object.keys(formData).forEach((key) => {
if(record.hasOwnProperty(key)){
tmpData[key] = record[key]
}
})
//
Object.assign(formData, tmpData);
});
//
await getPlaybackUrlList({
"deviceIndex":formData.videoDevId,
"startTime":formData.startTime,
"endTime":formData.endTime,
"videoType":formData.videoType,
"scale":formData.scale
}).then(res=>{
confirmLoading.value=false;
// console.log(res);
const error = res.error;
if(error==null||error==''){
formData.sessionId = '';
const list = res.data;
list.forEach(item => {
formData.sessionId += item.sessionId+","
let startTime = item.startTime*1000;
let endTime = item.endTime*1000;
// console.log("url:"+item.url);
// console.log("socket:"+item.wssUrl);
// console.log("queryAddress:"+item.queryAddress);
// console.log("videoSessionId:"+item.sessionId);
// console.log("videoDevId:"+item.videoDevId);
// console.log("storageDevId:"+item.storageDevId);
// console.log("startTime:"+startTime);
// console.log("endTime:"+endTime);
// console.log("scale:"+item.scale);
player.value = new TumsPlayer('video-container-playback', {
"autoplay": true,
"resolution": "HD",
"streamType": "sdvod", //
"url": item.url, // url
"socket": item.wssUrl,
"type": "rtsp",
"decoderType": "wasm", //
"queryAddress": item.queryAddress,
"videoSessionId": item.sessionId,
"videoDevId": item.videoDevId, // id
// "useMultitrans": true,
"storageDevId": item.storageDevId,
"startTime": startTime, // 13
"endTime": endTime, // 13
"eventType": [1, 2], //
"scale": item.scale //
});
});
}
});
}
/**
* 切换回放倍
*/
function changeScale(){
createMessage.info('切换回放倍速至'+formData.scale+'倍');
let scale = parseInt(formData.scale) ;
player.value.setPlaybackConfig({"scale":scale});
}
/**
* 销毁
*/
async function destroy(){
console.log("destroy");
if (player.value){
deletePlaybackChn({
"deviceIndex":formData.videoDevId,
"sessionId":formData.sessionId
}).then(res=>{
console.log(res)
});
// player.value.destroy().then(() => { }); //
initContainerPlayback.value = false
setTimeout(()=>{
initContainerPlayback.value = true
},1000)
}
}
defineExpose({
edit,
destroy
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px;
}
#video-container-playback {
padding: 0px 25px;
width: 600px;
height: 550px;
background: #1a1a1a;
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<j-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
<CameraPlaybackForm ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></CameraPlaybackForm>
</j-modal>
</template>
<script lang="ts" setup>
import { ref, nextTick, defineExpose } from 'vue';
import CameraPlaybackForm from './CameraPlaybackForm.vue'
import JModal from '/@/components/Modal/src/JModal/JModal.vue';
const title = ref<string>('');
const width = ref<number>(600);
const visible = ref<boolean>(false);
const disableSubmit = ref<boolean>(false);
const registerForm = ref();
const emit = defineEmits(['register', 'success']);
// const player = ref();
/**
* 编辑
* @param record
*/
function edit(record) {
title.value = "录像回放";;
visible.value = true;
nextTick(() => {
registerForm.value.edit(record);
});
}
/**
* 确定按钮点击事件
*/
function handleOk() {
registerForm.value.submitForm();
}
/**
* form保存回调事件
*/
function submitCallback() {
handleCancel();
}
/**
* 取消按钮回调事件
*/
function handleCancel() {
registerForm.value.destroy();
visible.value = false;
}
defineExpose({
edit,
disableSubmit,
});
</script>
<style lang="less">
/**隐藏样式-modal确定按钮 */
.jee-hidden {
display: none !important;
}
</style>
<style lang="less" scoped></style>

View File

@ -0,0 +1,318 @@
<template>
<a-spin :spinning="confirmLoading">
<a-row>
<a-col :span="24">
<div id="video-container"></div>
</a-col>
<a-col :span="24" style="padding: 5px;">
<span style="margin-left: 5px;" v-show="!izPlaying">
<a-button preIcon="ant-design:play-circle-outlined" @click="play">播放</a-button>
</span>
<span style="margin-left: 5px;" v-show="izPlaying">
<a-button preIcon="ant-design:pause-circle-outlined" @click="pause">暂停</a-button>
</span>
<span style="margin-left: 5px;">
<a-button preIcon="ant-design:swap-outlined" @click="switchResolution">{{ resolution }}</a-button>
</span>
<span style="margin-left: 5px;">
<a-button preIcon="ant-design:phone-outlined" @click="screenshot">巡航*</a-button>
</span>
<span style="margin-left: 5px;">
<a-button preIcon="ant-design:phone-outlined" @click="screenshot">电话*</a-button>
</span>
<span style="margin-left: 15px;">分屏
<a-select
ref="select"
v-model:value="fishEyeDisplayMode"
style="width: 120px"
@focus="focus"
@change="setFishEyeDisplayMode"
>
<a-select-option value="ORIGIN">原图</a-select-option>
<a-select-option value="FISHEYE_360D">360全景</a-select-option>
<a-select-option value="FISHEYE_180D">180全景</a-select-option>
<a-select-option value="FISHEYE_WIN_PLANE_TOP_QUAD">四分屏</a-select-option>
<a-select-option value="FISHEYE_LONGITUDE">全景拉伸</a-select-option>
</a-select>
</span>
</a-col>
<a-col :span="24" style="padding: 5px;">
<span style="margin-left: 5px;">
<a-button preIcon="ant-design:picture-outlined" @click="screenshot">截图</a-button>
</span>
<span style="margin-left: 5px;" v-show="!izRecording">
<a-button preIcon="ant-design:video-camera-outlined" @click="recordingStart">录制</a-button>
</span>
<span style="margin-left: 5px;" v-show="izRecording">
<a-button type="primary" danger ghost preIcon="ant-design:video-camera-outlined" @click="recordingEnd">录制</a-button>
</span>
<!-- <span style="margin-left: 15px;">画面翻转
<a-select
ref="select"
v-model:value="rotateType"
style="width: 120px"
@focus="focus"
@change="saveImageSwitch"
>
<a-select-option value="off">不翻转</a-select-option>
<a-select-option value="anticlockwise_180">上下翻转</a-select-option>
<a-select-option value="clockwise_90">右翻转90°</a-select-option>
<a-select-option value="anticlockwise_90">左翻转90°</a-select-option>
</a-select>
</span>-->
<span style="margin-left: 5px;">
<a-button preIcon="ant-design:alert-outlined" @click="manualAlarm">报警</a-button>
</span>
</a-col>
</a-row>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineExpose, nextTick, defineProps, computed, onMounted } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import { getValueType } from '/@/utils';
import { Form } from 'ant-design-vue';
import { getPreviewUrl, testAudio } from "@/views/iot/tplink/camera/camera.api";
const props = defineProps({
formDisabled: { type: Boolean, default: false },
formData: { type: Object, default: () => ({})},
formBpm: { type: Boolean, default: true }
});
const formRef = ref();
const player = ref();
const resolution = ref<string>('超清');
const izPlaying = ref<boolean>(true);
const izRecording = ref<boolean>(false);
const fishEyeDisplayMode = ref<string>('ORIGIN');
const rotateType = ref<string>('off');
const useForm = Form.useForm;
const emit = defineEmits(['register', 'ok']);
const formData = reactive<Record<string, any>>({
//
deviceIndex: '',//
streamType: 0,// 0 1
//
url: '',//URL
backupUrl: '',//URLIPnull
wsUrl: '',//ws
wssUrl: '',//wss
});
const { createMessage } = useMessage();
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
const confirmLoading = ref<boolean>(false);
//
const validatorRules = reactive({
});
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: false });
//
const disabled = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return false;
}else{
return true;
}
}
return props.formDisabled;
});
/**
* 详情
*/
async function edit(record) {
await nextTick(() => {
confirmLoading.value=true;
resetFields();
const tmpData = {};
Object.keys(formData).forEach((key) => {
if(record.hasOwnProperty(key)){
tmpData[key] = record[key]
}
})
//
Object.assign(formData, tmpData);
});
createPreview();
/* await getIpcCapability({"deviceIndex":formData.deviceIndex}).then(res=>{
console.log(res);
});*/
}
/**
* 创建预览
*/
async function createPreview(){
await getPreviewUrl({"deviceIndex":formData.deviceIndex,"streamType":formData.streamType}).then(res=>{
formData.url = res.url;
formData.backupUrl = res.backupUrl;
formData.wsUrl = res.wsUrl;
formData.wssUrl = res.wssUrl;
confirmLoading.value=false;
});
if (player.value){
player.value.destroy().then(() => {
}); //
}
const TumsPlayer = window['tums-player'].default;
player.value = new TumsPlayer('video-container', {
type: "rtsp", // rtsp
url: formData.url, // , getPreviewUrl
// url: formData.backupUrl, // , getPreviewUrl
socket: formData.wssUrl, // websocket, getPreviewUrl
pluginPath: '/static', // sdkpluginPath
});
let isPlaying = player.value.isPlaying();
if (!isPlaying) {
if (player.value.isInit) {
player.value.start();
} else {
player.value.play();
}
izPlaying.value = true;
}
}
/**
* 切换超清/流程
*/
function switchResolution(){
if(formData.streamType == 0){
resolution.value = '流畅';
formData.streamType = 1;
}else{
resolution.value = '超清';
formData.streamType = 0;
}
createMessage.info('正在切换至'+resolution.value);
createPreview();
}
/**
* 播放
*/
function play(){
izPlaying.value = true;
player.value.play();
}
/**
* 暂停
*/
function pause(){
izPlaying.value = false;
player.value.pause();
}
/**
* 截屏
*/
function screenshot(){
player.value.screenshot();
}
/**
* 鱼眼画面显示模式
*/
function setFishEyeDisplayMode(){
player.value.setFishEyeDisplayMode(fishEyeDisplayMode.value);
}
/**
* 画面翻转
*/
function saveImageSwitch(){
player.value.saveImageSwitch({rotate_type:1}).then(() => {
// 0.5-2s
createMessage.success('设置成功实际画面需要0.5-2s进行响应');
}).catch((errData) => {
//
// errData.error_code
console.log(errData);
createMessage.error('设置错误,'+errData.msg);
});
}
/**
* 手动报警
*/
function manualAlarm(){
let params = {
"deviceIndex": formData.deviceIndex,
"force": 1,
"id": '0'
};
testAudio(params);
}
/**
* 开始录制
*/
function recordingStart(){
izRecording.value = true;
player.value.startRecording({micStream:true}).then((res) => {
// resolve
console.log(res);
}).catch((errData) => {
//
// errData.error_code
createMessage.error('录制错误,'+errData.msg);
});
}
/**
* 结束录制
*/
function recordingEnd(){
izRecording.value = false;
let fileName = formData.deviceIndex+'-'+(new Date().getTime());
player.value.stopRecording(fileName, true).then((res) => {
// resolve
console.log(res);
}).catch((errData) => {
//
// errData.error_code
createMessage.error('录制错误,'+errData.msg);
});
}
/**
* 销毁
*/
function destroy(){
console.log("destroy");
if (player){
player.value.destroy().then(() => {
}); //
}
}
defineExpose({
edit,
destroy
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px;
}
#video-container {
padding: 0px 25px;
width: 600px;
height: 500px;
background: #1a1a1a;
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<j-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
<CameraPreviewForm ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></CameraPreviewForm>
</j-modal>
</template>
<script lang="ts" setup>
import { ref, nextTick, defineExpose } from 'vue';
import CameraPreviewForm from './CameraPreviewForm.vue'
import JModal from '/@/components/Modal/src/JModal/JModal.vue';
const title = ref<string>('');
const width = ref<number>(600);
const visible = ref<boolean>(false);
const disableSubmit = ref<boolean>(false);
const registerForm = ref();
const emit = defineEmits(['register', 'success']);
// const player = ref();
/**
* 编辑
* @param record
*/
function edit(record) {
title.value = record.deviceName;
visible.value = true;
nextTick(() => {
registerForm.value.edit(record);
});
}
/**
* 确定按钮点击事件
*/
function handleOk() {
registerForm.value.submitForm();
}
/**
* form保存回调事件
*/
function submitCallback() {
handleCancel();
}
/**
* 取消按钮回调事件
*/
function handleCancel() {
registerForm.value.destroy();
visible.value = false;
}
defineExpose({
edit,
disableSubmit,
});
</script>
<style lang="less">
/**隐藏样式-modal确定按钮 */
.jee-hidden {
display: none !important;
}
</style>
<style lang="less" scoped></style>

View File

@ -0,0 +1,133 @@
<template>
<div>
<!--引用表格-->
<BasicTable @register="registerTable">
<!--插槽:table标题-->
<template #tableTitle>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)"/>
</template>
<template v-slot:bodyCell="{ column, record, index, text }">
</template>
</BasicTable>
</div>
<CameraPlaybackModal ref="playbackModal"></CameraPlaybackModal>
<CameraMultitransModal ref="multitransModal"></CameraMultitransModal>
</template>
<script lang="ts" name="iot-nuIotCameraInfo" setup>
import {ref, reactive, createVNode, h, onMounted, watch, unref} from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { recordingColumns,searchRecording } from '@/views/iot/tplink/camera/camera.data';
import { searchVideo } from '@/views/iot/tplink/camera/camera.api';
import { useUserStore } from '/@/store/modules/user';
import { useDrawer } from "@/components/Drawer";
import { useRouter } from 'vue-router';
import CameraPlaybackModal from './CameraPlaybackModal.vue';//
import CameraMultitransModal from './CameraMultitransModal.vue';//Multitrans
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
//drawer
const [registerDrawer, { openDrawer }] = useDrawer();
let router = useRouter();
const formRef = ref();
const queryParam = reactive<any>({});
const registerModal = ref();
const userStore = useUserStore();
const playbackModal = ref();
const multitransModal = ref();
//table
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '护理单元-物联管理-录像信息',
api: searchVideo,
columns: recordingColumns,
canResize: false,
showIndexColumn: true,
actionColumn: {
width: 180,
fixed: 'right',
},
formConfig: {
schemas: searchRecording,
},
beforeFetch: async (params) => {
return Object.assign(params, queryParam);
},
},
});
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] = tableContext;
const labelCol = reactive({
xs:24,
sm:4,
xl:6,
xxl:4
});
const wrapperCol = reactive({
xs: 24,
sm: 20,
});
onMounted(() => {
watch(
() => props.data,
async () => {
queryParam.deviceIndex = props.data.deviceIndex;
queryParam.projectId = props.data.projectId;
queryParam.regionId = props.data.regionId;
queryParam.multitrans = props.data.multitrans;
queryParam.scale = props.data.scale;
// let record = unref(props.data);
// if (typeof record !== 'object') {
// record = {};
// }
reload();
},
{ deep: true, immediate: true }
);
});
/**
* 播放
*/
function handlePlayback(record: Recordable) {
record.scale = props.data.scale;
record.multitrans = props.data.multitrans;
if(record.multitrans=="1"){
multitransModal.value.disableSubmit = true;
multitransModal.value.edit(record);
}else{
playbackModal.value.disableSubmit = true;
playbackModal.value.edit(record);
}
}
/**
* 成功回调
*/
function handleSuccess() {
reload();
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '播放',
onClick: handlePlayback.bind(null, record),
}
];
}
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,713 @@
<template>
<a-spin :spinning="confirmLoading">
<JFormContainer :disabled="disabled">
<template #detail>
<a-row>
<a-col :span="24">
<a-row>
<a-col :span="20">
<a-divider orientation="left">声音报警</a-divider>
</a-col>
<a-col :span="20">
<a-row style="margin-bottom: 14px">
<a-col :span="4" class="labelText">
声音报警
</a-col>
<a-col :span="16">
<a-switch v-model:checked="formData.enabled" checked-children="已开启" un-checked-children="已关闭" />
</a-col>
</a-row>
</a-col>
</a-row>
</a-col>
<a-col :span="24">
<a-row>
<a-col :span="20">
<a-divider orientation="left">布防时间设置</a-divider>
</a-col>
<a-col style="width: 800px;">
<a-row style="margin-bottom: 14px">
<a-col :span="2" class="labelText">
</a-col>
<a-col :span="22">
<div class="cleanTitle">
<div>仅在以下时间段进行声音报警</div>
<div>
<a-button preIcon="ant-design:delete-outlined" @click="cleanPlan" :disabled="!formData.enabled">清空计划</a-button>
</div>
</div>
</a-col>
</a-row>
</a-col>
<a-col style="width: 800px;">
<a-row style="margin-bottom: 14px">
<a-col :span="2" class="labelText">
</a-col>
<a-col :span="22">
<div class="armingSchedule" id="armingSchedule" @mousedown="handleMouseDown">
<div id="recPlanGrid" class="test" style="overflow: hidden;">
<div class="cruiseDiv"><i class="iCruise displayNone" style="left: 373px;"></i></div>
<div class="psHourList">
<div class="psHourLi" v-for="(idx,index) in 25" :key="index">
<span>{{ index }}</span>
</div>
</div>
<ul class="psWeekList">
<li>星期一</li>
<li>星期二</li>
<li>星期三</li>
<li>星期四</li>
<li>星期五</li>
<li>星期六</li>
<li>星期日</li>
</ul>
<div class="tableDiv">
<div class="trDiv" v-for="weekDay in 7">
<div :class="getClass(index)" v-for="(idx,index) in 25" :key="index">
<div class="blank" style="width: 24px;" :id="getId(weekDay,index)" @click="divSelect($event,weekDay,index)" @mouseover="overDivSelect($event,weekDay,index)"></div>
</div>
</div>
<div id="editDiv"></div>
</div>
</div>
</div>
</a-col>
</a-row>
</a-col>
</a-row>
</a-col>
<a-col :span="24">
<a-row style="margin-top: 14px">
<a-col :span="1"></a-col>
<a-col :span="20">
<a-button type="primary" preIcon="ant-design:save-outlined" @click="saveAlarm">保存</a-button>
</a-col>
</a-row>
</a-col>
</a-row>
</template>
</JFormContainer>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineProps, onMounted, watch,} from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import { getValueType } from '/@/utils';
import { Form } from 'ant-design-vue';
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
import {
getAlarmInfo, setAlarmInfo,
getAlarmPlan, setAlarmPlan,
} from "@/views/iot/tplink/camera/camera.api";
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
const formData = reactive<Record<string, any>>({
deviceIndex: '', //
enabled: false, //
});
const confirmLoading = ref<boolean>(false);
const planVisible = ref<boolean>(false);
const mondayArr = ref<string>([]);
const mondayMergeArr = ref<string>([]);
const tuesdayArr = ref<string>([]);
const tuesdayMergeArr = ref<string>([]);
const wednesdayArr = ref<string>([]);
const wednesdayMergeArr = ref<string>([]);
const thursdayArr = ref<string>([]);
const thursdayMergeArr = ref<string>([]);
const fridayArr = ref<string>([]);
const fridayMergeArr = ref<string>([]);
const saturdayArr = ref<string>([]);
const saturdayMergeArr = ref<string>([]);
const sundayArr = ref<string>([]);
const sundayMergeArr = ref<string>([]);
/**
* 设置单元格样式
*/
function getClass(index){
if(index == 0) {
return 'tdDiv firTd';
}else if(index == 23){
return 'tdDiv lastTd';
}else if(index > 23){
return 'editBtn';
}else{
return 'tdDiv';
}
}
/**
* 设置单元格ID
*/
function getId(weekDay,index){
let id = weekDay+'-'+index;
return id
}
/**
* 格式化各星期数据
*/
function initPlan(armingScheduleSound){
let monday = decodeURIComponent(armingScheduleSound.monday);
initValue(1,monday);
mergeNumbers(1,1,-1);
let tuesday = decodeURIComponent(armingScheduleSound.tuesday);
initValue(2,tuesday);
mergeNumbers(1,2,-1);
let wednesday = decodeURIComponent(armingScheduleSound.wednesday);
initValue(3,wednesday);
mergeNumbers(1,3,-1);
let thursday = decodeURIComponent(armingScheduleSound.thursday);
initValue(4,thursday);
mergeNumbers(1,4,-1);
let friday = decodeURIComponent(armingScheduleSound.friday);
initValue(5,friday);
mergeNumbers(1,5,-1);
let saturday = decodeURIComponent(armingScheduleSound.saturday);
initValue(6,saturday);
mergeNumbers(1,6,-1);
let sunday = decodeURIComponent(armingScheduleSound.sunday);
initValue(7,sunday);
mergeNumbers(1,7,-1);
}
/**
* 初始化各星期数据
*/
function initValue(weekDay,valueArr){
valueArr = valueArr.replaceAll('[','').replaceAll(']','').replaceAll('"','');
const arr = valueArr.split(",");
arr.forEach(item =>{
const hours = item.split("-");
const start = parseInt(hours[0].substring(0, 2));
const end = parseInt(hours[1].substring(0, 2));
if(start<end){
for(let i=start;i<end;i++){
const id = weekDay+'-'+i;
const element = document.getElementById(id);
element.className = 'timing';
switch (weekDay) {
case 1:
mondayArr.value.push(i);
break;
case 2:
tuesdayArr.value.push(i);
break;
case 3:
wednesdayArr.value.push(i);
break;
case 4:
thursdayArr.value.push(i);
break;
case 5:
fridayArr.value.push(i);
break;
case 6:
saturdayArr.value.push(i);
break;
case 7:
sundayArr.value.push(i);
break;
default:
break;
}
}
}
})
}
/**
* 清除计划
*/
function cleanPlan(){
const elements = document.querySelectorAll('.timing');
elements.forEach(item =>{
item.className = 'blank';
})
mondayArr.value = [];
mondayMergeArr.value = [];
tuesdayArr.value = [];
tuesdayMergeArr.value = [];
wednesdayArr.value = [];
wednesdayMergeArr.value = [];
thursdayArr.value = [];
thursdayMergeArr.value = [];
fridayArr.value = [];
fridayMergeArr.value = [];
saturdayArr.value = [];
saturdayMergeArr.value = [];
sundayArr.value = [];
sundayMergeArr.value = [];
}
/**
* 合并各星期计划数组
*/
function mergeNumbers(type,weekDay,hourValue) {
let numbers = [];
switch (weekDay) {
case 1:
Object.assign(numbers, mondayArr.value);
break;
case 2:
Object.assign(numbers, tuesdayArr.value);
break;
case 3:
Object.assign(numbers, wednesdayArr.value);
break;
case 4:
Object.assign(numbers, thursdayArr.value);
break;
case 5:
Object.assign(numbers, fridayArr.value);
break;
case 6:
Object.assign(numbers, saturdayArr.value);
break;
case 7:
Object.assign(numbers, sundayArr.value);
break;
default:
break;
}
if(hourValue!=-1){
if(type == 1) {
numbers.push(hourValue);
numbers.sort(function(a, b) {
return a - b;
});
}else {
let idx = numbers.indexOf(hourValue);
numbers.splice(idx,1);
}
}
let start = numbers[0];
let end = null;
let mergedNumbers = [];
for (let i = 0; i < numbers.length; i++) {
if (i === 0) {
start = numbers[i];
end = numbers[i];
} else if (numbers[i] === numbers[i - 1] + 1) {
end = numbers[i];
} else {
if (start === end) {
mergedNumbers.push(start.toString());
} else {
mergedNumbers.push(`${start}-${end}`);
}
start = numbers[i];
end = numbers[i];
}
}
if (start === end) {
mergedNumbers.push(start.toString());
} else {
mergedNumbers.push(`${start}-${end}`);
}
if(mergedNumbers.length>6){
return false;
}
switch (weekDay) {
case 1:
mondayArr.value = [];
mondayMergeArr.value = [];
Object.assign(mondayMergeArr.value, mergedNumbers);
Object.assign(mondayArr.value, numbers);
break;
case 2:
tuesdayArr.value = [];
tuesdayMergeArr.value = [];
Object.assign(tuesdayMergeArr.value, mergedNumbers);
Object.assign(tuesdayArr.value, numbers);
break;
case 3:
wednesdayArr.value = [];
wednesdayMergeArr.value = [];
Object.assign(wednesdayMergeArr.value, mergedNumbers);
Object.assign(wednesdayArr.value, numbers);
break;
case 4:
thursdayArr.value = [];
thursdayMergeArr.value = [];
Object.assign(thursdayMergeArr.value, mergedNumbers);
Object.assign(thursdayArr.value, numbers);
break;
case 5:
fridayArr.value = [];
fridayMergeArr.value = [];
Object.assign(fridayMergeArr.value, mergedNumbers);
Object.assign(fridayArr.value, numbers);
break;
case 6:
saturdayArr.value = [];
saturdayMergeArr.value = [];
Object.assign(saturdayMergeArr.value, mergedNumbers);
Object.assign(saturdayArr.value, numbers);
break;
case 7:
sundayArr.value = [];
sundayMergeArr.value = [];
Object.assign(sundayMergeArr.value, mergedNumbers);
Object.assign(sundayArr.value, numbers);
break;
default:
break;
}
// console.log('', mondayArr.value);
// console.log('', tuesdayArr.value);
// console.log('', wednesdayArr.value);
// console.log('', thursdayArr.value);
// console.log('', fridayArr.value);
// console.log('', saturdayArr.value);
// console.log('', sundayArr.value);
// console.log('', mondayMergeArr.value);
// console.log('', tuesdayMergeArr.value);
// console.log('', wednesdayMergeArr.value);
// console.log('', thursdayMergeArr.value);
// console.log('', fridayMergeArr.value);
// console.log('', saturdayMergeArr.value);
// console.log('', sundayMergeArr.value);
return true;
}
/**
* 点击选择单元格
*/
function divSelect(event,weekDay,hourValue){
if(formData.enabled){
if(event.target.className == "blank"){
let visiable = mergeNumbers(1,weekDay,hourValue);
if(visiable){
event.target.className = "timing";
}
}else{
let visiable = mergeNumbers(2,weekDay,hourValue);
if(visiable){
event.target.className = "blank";
}
}
}
}
/**
* 悬浮选择单元格
*/
function overDivSelect(event,weekDay,hourValue){
if(formData.enabled&&planVisible.value){
if(event.target.className == "blank"){
let visiable = mergeNumbers(1,weekDay,hourValue);
if(visiable){
event.target.className = "timing";
}
}else{
let visiable = mergeNumbers(2,weekDay,hourValue);
if(visiable){
event.target.className = "blank";
}
}
}
}
/**
* 获取报警
*/
function getAlarm(deviceIndex){
if(deviceIndex==null){
return
}
//
getAlarmInfo({
"deviceIndex": deviceIndex
}).then(res=>{
if(res.sound_alarm_enabled == "on"){
formData.enabled = true;
}else{
formData.enabled = false;
}
});
//
getAlarmPlan({
"deviceIndex": deviceIndex,
"type": "sound"
}).then(res=>{
let armingScheduleSound = res.arming_schedule_sound;
initPlan(armingScheduleSound);
});
}
/**
* 保存声音报警设置信息
*/
function saveAlarm(){
confirmLoading.value = true;
let params = {
"deviceIndex": formData.deviceIndex,
"type": "sound"
};
if(formData.enabled){
params["enabled"] = "on";
}else{
params["enabled"] = "off";
}
setAlarmInfo(params).then(res=>{
confirmLoading.value = false;
});
if(formData.enabled){
params["alarmPlan"] = setAlarmPlanData();
setAlarmPlan(params).then(res=>{
confirmLoading.value = false;
});
}
}
/**
* 获取报警计划数据
*/
function setAlarmPlanData(){
const monday = getHoursEncode(mondayMergeArr.value);
const tuesday = getHoursEncode(tuesdayMergeArr.value);
const wednesday = getHoursEncode(wednesdayMergeArr.value);
const thursday = getHoursEncode(thursdayMergeArr.value);
const friday = getHoursEncode(fridayMergeArr.value);
const saturday = getHoursEncode(saturdayMergeArr.value);
const sunday = getHoursEncode(sundayMergeArr.value);
let alarmPlan = {
"monday": monday,
"tuesday": tuesday,
"wednesday": wednesday,
"thursday": thursday,
"friday": friday,
"saturday": saturday,
"sunday": sunday
}
return alarmPlan;
}
/**
* 格式化设置计划数据
* @param weekArr
*/
function getHoursEncode(weekArr){
let hoursEncode = "[";
if(weekArr.length>0){
let hourArrStr = "";
weekArr.forEach(item =>{
let hours = 0;
let end = 0;
const arr = item.split("-");
hours = parseInt(arr[0]);
if(arr.length>1){
end = parseInt(arr[1]) + 1;
}else{
end = hours + 1;
}
if(hours<10){
hours = "0" + hours;
}
hours = hours + "00-"
if(end<10){
hours = hours + "0" + end;
}else{
hours = hours + end;
}
hours = hours + "00";
hours = '"' + hours + '"';
hourArrStr = hourArrStr + hours + ",";
})
hoursEncode = "[" + hourArrStr.substring(0,hourArrStr.length-1) + "]";
}else{
hoursEncode = '["0000-0000"]';
}
return encodeURIComponent(hoursEncode);
}
/**
* 鼠标按下
*/
function handleMouseDown(){
// console.log('', event);
planVisible.value = true;
}
/**
* 鼠标抬起
*/
function handleMouseUp(){
// console.log('', event);
planVisible.value = false;
}
onMounted(() => {
watch(
() => props.data,
async () => {
formData.deviceIndex = props.data.deviceIndex;
getAlarm(formData.deviceIndex);
},
{ deep: true, immediate: true }
);
// window.addEventListener('mousedown', handleMouseDown);
window.addEventListener('mouseup', handleMouseUp);
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px;
}
.labelText {
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 8px;
}
.cleanTitle {
display: flex;
justify-content: space-between;
align-items: center;
}
div.armingSchedule {
*position: relative;
z-index: 0;
background: #ebeef0;
border: 1px solid #ced7e0;
height: 260px;
border-radius: 2px;
}
div.cruiseDiv {
width: 100%;
height: 8px;
}
div.cruiseDiv i.iCruise {
width: 8px;
height: 8px;
position: relative;
//background: url(../../web-static/images/pointDown.png) no-repeat;
display: inline-block;
*display: inline;
*zoom: 1;
}
div.psHourList {
color: #666;
margin-left: 63px;
}
div.psHourList div.psHourLi {
width: 25px;
display: inline-block;
text-align: center;
*display: inline;
*zoom: 1;
}
ul.psWeekList {
float: left;
color: #333;
font-size: 13px;
list-style: none;
margin-right: 16px;
margin-left: 20px;
_width: 42px;
_margin-left: 8px;
}
ul.psWeekList li {
height: 32px;
line-height: 32px;
_height: 34px;
}
div.tableDiv {
z-index: 0;
*position: relative;
float: left;
}
div.tableDiv div.trDiv {
width: 100%;
height: 24px;
margin-top: 4px;
margin-bottom: 8px;
}
div.tableDiv div.trDiv div.firTd {
border-left: solid 1px #ced7e0;
border-bottom-left-radius: 2px!important;
border-top-left-radius: 2px!important;
}
div.tableDiv div.trDiv div.tdDiv {
height: 24px;
overflow: hidden;
vertical-align: middle;
border-right: solid 1px #ced7e0;
border-top: solid 1px #ced7e0;
border-bottom: solid 1px #ced7e0;
display: inline-block;
*display: inline;
*zoom: 1;
}
div.tableDiv div.trDiv div.editBtn {
//background-image: url(@/assets/loginmini/icon/jeecg_bg.png);
//background-image: url(@/assets/images/planEdit.png);
/* background-image: url(@/views/iot/tplink/camera/icons/planEdit.png);*/
background: url(../icons/plan_set.png) no-repeat;
background-size: cover;
//background: url(@/assets/images/planEdit.png) no-repeat;
//background: new URL('@/assets/images/planEdit.png', import.meta.url).href;
width: 20px;
vertical-align: middle;
height: 24px;
cursor: pointer;
margin-left: 8px;
margin-right: 8px;
display: none;
zoom: 1;
}
div.tableDiv div.trDiv:hover div.editBtn{
display: inline-block;
}
div.tableDiv div.trDiv div.lastTd {
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
}
div.tableDiv div.trDiv div.tdDiv div.timing {
background-color: #5a92ff;
width: 24px;
height: 24px;
}
div.tableDiv div.trDiv div.tdDiv div.blank {
background-color: #ffffff;
width: 24px;
height: 24px;
}
.displayNone {
display: none!important;
}
</style>

View File

@ -0,0 +1,210 @@
<template>
<a-spin :spinning="confirmLoading">
<JFormContainer :disabled="disabled">
<template #detail>
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol" name="NuIotCameraUploadForm">
<a-row>
<a-col :span="24">
<a-form-item label="录像类型" v-bind="validateInfos.videoType" id="NuIotCameraUploadForm-videoType" name="videoType">
<a-select v-model:value="formData.videoType" placeholder="请选择设备状态">
<a-select-option value="1">定时录像</a-select-option>
<a-select-option value="2">移动侦测录像</a-select-option>
<a-select-option value="3">全部</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="开始时间" v-bind="validateInfos.startTime" id="NuIotCameraUploadForm-startTime" name="startTime">
<a-date-picker show-time placeholder="请选择开始时间" v-model:value="formData.startTime"/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="结束时间" v-bind="validateInfos.endTime" id="NuIotCameraUploadForm-endTime" name="endTime">
<a-date-picker show-time placeholder="请选择结束时间" v-model:value="formData.endTime"/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="文件名称" id="NuIotCameraUploadForm-fileName" name="fileName">
<a-input v-model:value="formData.fileName" placeholder="请输入文件名称" allow-clear ></a-input>
</a-form-item>
</a-col>
<a-col :span="24" style="text-align: center">
<a-button type="primary" preIcon="ant-design:cloud-upload-outlined" @click="uploadSummit" v-show="canUpload">回放上传</a-button>
<a-button type="primary" preIcon="ant-design:disconnect-outlined" @click="uploadStop" v-show="!canUpload">停止上传</a-button>
<!-- <a-button type="primary" preIcon="ant-design:search-outlined" @click="getProgress">查询进度</a-button>-->
</a-col>
</a-row>
</a-form>
<a-row style="margin-top: 50px">
<a-col :span="2">
</a-col>
<a-col :span="20">
<a-progress :percent="percentRate" v-if="showPercent"/>
</a-col>
</a-row>
</template>
</JFormContainer>
</a-spin>
</template>
<script lang="ts" setup>
import {
ref,
reactive,
defineExpose,
nextTick,
defineProps,
computed,
onMounted,
watch,
unref
} from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import { getValueType } from '/@/utils';
import { Form } from 'ant-design-vue';
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
import {uploadToServer,stopUploadToServer,getUploadToServerProcess} from "@/views/iot/tplink/camera/camera.api";
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
const formRef = ref();
const useForm = Form.useForm;
const emit = defineEmits(['register', 'ok']);
const formData = reactive<Record<string, any>>({
deviceIndex: '',
parentId: '',
videoType: '',
storageType: '0',
fileName: '',
startTime: '',
endTime: '',
taskId: '',
});
const percentRate = ref(0);
const showPercent = ref(false);
const timerRef = ref();
const canUpload = ref(true);
const { createMessage } = useMessage();
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
const confirmLoading = ref<boolean>(false);
//
const validatorRules = {
videoType: [{ required: true, message: '请选择录像类型!' }],
startTime: [{ required: true, message: '请选择开始时间!' }],
endTime: [{ required: true, message: '请选择结束时间!' }],
};
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: false });
/**
* 上传提交
*/
async function uploadSummit(){
await validate();
let startTime = new Date(formData.startTime).getTime();
startTime = Math.floor(startTime/1000);
let endTime = new Date(formData.endTime).getTime();
endTime = Math.floor(endTime/1000);
let params = {
videoType : formData.videoType,
fileName : formData.fileName,
startTime: startTime,
endTime: endTime,
deviceIndex: formData.deviceIndex,
parentId: formData.parentId,
storageType: formData.storageType
}
confirmLoading.value=true;
await uploadToServer(params).then(res=>{
confirmLoading.value=false;
canUpload.value = false;
const taskId = res.taskId;
formData.taskId = taskId;
percentRate.value = 0;
showPercent.value = true;
timerRef.value = setInterval(()=>{
uploadProgress()
},3000 );
}).catch(res=>{
console.log(res);
});
}
/**
* 上传暂定
*/
function uploadStop(){
let params = {
taskId : formData.taskId
}
stopUploadToServer(params).then(res=>{
clearInterval(timerRef.value);
canUpload.value = true;
}).catch(res=>{
console.log(res);
});
}
// function getProgress(){
// let params = {
// taskId : formData.fileName
// }
// getUploadToServerProcess(params).then(res=>{
// const process = res.process;
// percentRate.value = Math.round(process/10);
// }).catch(res=>{
// console.log(res);
// });
// }
/**
* 上传进度
*/
function uploadProgress(){
if(showPercent.value){
let params = {
taskId : formData.taskId
}
getUploadToServerProcess(params).then(res=>{
const process = res.process;
percentRate.value = Math.round(process / 10);
if (percentRate.value >= 100) {
clearInterval(timerRef.value);
canUpload.value = true;
}
}).catch(res=>{
console.log(res);
});
}
}
/**
* 销毁
*/
function destroy(){
clearInterval(timerRef.value);
}
onMounted(() => {
watch(
() => props.data,
async () => {
formData.deviceIndex = props.data.deviceIndex;
formData.projectId = props.data.projectId;
formData.regionId = props.data.regionId;
formData.multitrans = props.data.multitrans;
formData.scale = props.data.scale;
console.log(formData);
},
{ deep: true, immediate: true }
);
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,47 @@
<template>
<a-row class="p-2" type="flex" :gutter="10">
<a-col :xl="4" :lg="24" :md="24" style="margin-bottom: 10px">
<CameraLeftTree ref="leftTree" @select="onTreeSelect" @rootTreeData="onRootTreeData" />
</a-col>
<a-col :xl="20" :lg="24" :md="24" style="margin-bottom: 10px">
<div v-show="cameraData != null">
<CameraInfoList :data="cameraData" />
</div>
<div v-show="cameraData == null" style="padding-top: 40px">
<a-empty description="请选择区域" />
</div>
</a-col>
</a-row>
</template>
<script lang="ts" name="iot-nuIotCameraInfo" setup>
import { ref } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import CameraLeftTree from './components/CameraLeftTree.vue'
import CameraInfoList from "@/views/iot/tplink/camera/components/CameraInfoList.vue";
// ref
const leftTree = ref();
//
const cameraData = ref({});
const rootTreeData = ref<any[]>([]);
//
function onTreeSelect(data) {
console.log('onTreeSelect: ', data);
cameraData.value = data;
}
// rootTreeData
function onRootTreeData(data) {
rootTreeData.value = data;
}
</script>
<style lang="less" scoped>
.p-2{
height: calc(100vh - 120px);
}
</style>

View File

@ -0,0 +1,18 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/iot/projectInfo/list',
sync = '/iot/projectInfo/sync',
}
/**
*
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
*
* @param params
*/
export const sync = (params) => defHttp.get({ url: Api.sync, params });

View File

@ -0,0 +1,130 @@
import {BasicColumn} from '/@/components/Table';
import {FormSchema} from '/@/components/Table';
//列表数据
export const columns: BasicColumn[] = [
{
title: '项目序号',
align: "center",
dataIndex: 'projectId'
},
{
title: '项目名称',
align: "center",
dataIndex: 'projectName'
},
{
title: '创建时间',
align: "center",
dataIndex: 'createTimeStr'
},
{
title: '设备数量',
align: "center",
dataIndex: 'deviceNum'
},
{
title: '离线设备数',
align: "center",
dataIndex: 'offlineNum'
},
{
title: '异常设备数',
align: "center",
dataIndex: 'abnormalNum'
},
{
title: '运行天数',
align: "center",
dataIndex: 'runningTimeStr'
},
{
title: '状态',
align: "center",
dataIndex: 'status',
customRender:({record})=>{
return record.status?(record.status=='1'?'正常':'冻结'):'';
},
}
];
export const searchFormSchema: FormSchema[] = [
{
label: '名称',
field: 'projectName',
component: 'Input',
//colProps: { span: 6 },
},
{
label: '状态',
field: 'status',
component: 'JDictSelectTag',
componentProps: {
placeholder: '请选择状态',
options: [
{ label: '正常', value: '1' },
{ label: '冻结', value: '2' },
],
},
//colProps: { span: 6 },
}
];
export const formSchema: FormSchema[] = [
{
label: '',
field: 'id',
component: 'Input',
show: false,
},
{
label: '项目序号',
field: 'projectId',
component: 'Input',
},
{
label: '项目名称',
field: 'projectName',
component: 'Input',
},
{
label: '创建时间',
field: 'createTimeStr',
component: 'Input',
},
{
label: '设备数量',
field: 'deviceNum',
component: 'Input',
},
{
label: '离线设备数',
field: 'offlineNum',
component: 'Input',
},
{
label: '异常设备数',
field: 'abnormalNum',
component: 'Input',
},
{
label: '运行天数',
field: 'runningTimeStr',
component: 'Input',
},
{
label: '状态',
field: 'status',
component: 'JDictSelectTag',
defaultValue: 1,
componentProps: ({}) => {
return {
options: [
{ label: '正常', value: 1, key: '1' },
{ label: '冻结', value: 2, key: '2' },
]
};
},
},
];

View File

@ -0,0 +1,153 @@
<template>
<a-spin :spinning="syncoading">
<div class="p-2">
<!--引用表格-->
<BasicTable @register="registerTable">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:sync-outlined" @click="handleSync"> 同步</a-button>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)"/>
</template>
<template v-slot:bodyCell="{ column, record, index, text }">
</template>
</BasicTable>
<!-- 表单区域 -->
<ProjectInfoDrawer @register="registerDrawer" @success="handleSuccess" />
</div>
</a-spin>
</template>
<script lang="ts" name="iot-nuIotCameraInfo" setup>
import {ref, reactive, createVNode, h} from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns,searchFormSchema } from './ProjectInfo.data';
import { list, sync } from './ProjectInfo.api';
import { useUserStore } from '/@/store/modules/user';
import { useRouter } from 'vue-router';
import { useDrawer } from "@/components/Drawer";
import ProjectInfoDrawer from './components/ProjectInfoDrawer.vue';
//drawer
const [registerDrawer, { openDrawer }] = useDrawer();
let router = useRouter();
const formRef = ref();
const syncoading = ref<boolean>(false);
const queryParam = reactive<any>({});
const userStore = useUserStore();
//table
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '护理单元-物联管理-项目信息',
api: list,
columns,
canResize:false,
formConfig: {
// labelWidth: 200,
schemas: searchFormSchema,
},
actionColumn: {
width: 120,
fixed: 'right',
},
beforeFetch: async (params) => {
return Object.assign(params, queryParam);
},
},
});
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] = tableContext;
const labelCol = reactive({
xs:24,
sm:4,
xl:6,
xxl:4
});
const wrapperCol = reactive({
xs: 24,
sm: 20,
});
/**
* 详情
*/
function handleDetail(record: Recordable) {
openDrawer(true, {
record,
isUpdate: true,
showFooter: false,
tenantSaas: false,
});
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
},
];
}
/**
* 查询
*/
function searchQuery() {
reload();
}
/**
* 重置
*/
function searchReset() {
formRef.value.resetFields();
selectedRowKeys.value = [];
//
reload();
}
/**
* 信息同步
*/
function handleSync(){
syncoading.value = true;
sync({}).then(res=>{
syncoading.value = false;
//
reload();
}).catch(res=>{
syncoading.value = false;
});
}
</script>
<style lang="less" scoped>
.jeecg-basic-table-form-container {
padding: 0;
.table-page-search-submitButtons {
display: block;
margin-bottom: 24px;
white-space: nowrap;
}
.query-group-cust{
min-width: 100px !important;
}
.query-group-split-cust{
width: 30px;
display: inline-block;
text-align: center
}
.ant-form-item:not(.ant-form-item-with-help){
margin-bottom: 16px;
height: 32px;
}
:deep(.ant-picker),:deep(.ant-input-number){
width: 100%;
}
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<BasicDrawer
v-bind="$attrs"
@register="registerDrawer"
:title="getTitle"
:width="adaptiveWidth"
@ok="handleSubmit"
:showFooter="showFooter"
destroyOnClose
>
<BasicForm @register="registerForm" />
</BasicDrawer>
</template>
<script lang="ts" setup>
import { defineComponent, ref, computed, unref, useAttrs } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from "@/views/iot/tplink/project/ProjectInfo.data";
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
import { getTenantId } from "/@/utils/auth";
// Emits
const emit = defineEmits(['success', 'register']);
const attrs = useAttrs();
const isUpdate = ref(true);
const rowId = ref('');
const departOptions = ref([]);
let isFormDepartUser = false;
//
const [registerForm, { setProps, resetFields, setFieldsValue, validate, updateSchema }] = useForm({
labelWidth: 90,
schemas: formSchema,
showActionButtonGroup: false,
});
const showFooter = ref(true);
//
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
await resetFields();
showFooter.value = data?.showFooter ?? true;
setDrawerProps({ confirmLoading: false, showFooter: showFooter.value });
isUpdate.value = !!data?.isUpdate;
//
if (typeof data.record === 'object') {
setFieldsValue({
...data.record,
});
}
//
setProps({ disabled: !showFooter.value });
});
//
const getTitle = computed(() => {
if (!unref(isUpdate)) {
return '新增项目';
} else {
return unref(showFooter) ? '编辑项目' : '项目详情';
}
});
const { adaptiveWidth } = useDrawerAdaptiveWidth();
//
async function handleSubmit() {
try {
//
closeDrawer();
//
emit('success');
} finally {
setDrawerProps({ confirmLoading: false });
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,18 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/iot/regionInfo/list',
sync = '/iot/regionInfo/sync',
}
/**
*
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
*
* @param params
*/
export const sync = (params) => defHttp.get({ url: Api.sync, params });

View File

@ -0,0 +1,108 @@
import {BasicColumn} from '/@/components/Table';
import {FormSchema} from '/@/components/Table';
//列表数据
export const columns: BasicColumn[] = [
{
title: '区域序号',
align: "center",
dataIndex: 'regionId'
},
{
title: '区域名称',
align: "center",
dataIndex: 'regionName'
},
{
title: '区域层级',
align: "center",
dataIndex: 'regionLevel'
},
{
title: '项目名称',
align: "center",
dataIndex: 'projectName'
},
{
title: '更新时间',
align: "center",
dataIndex: 'updateTime'
},
{
title: '区域次序',
align: "center",
dataIndex: 'sort'
},
];
export const searchFormSchema: FormSchema[] = [
{
label: '项目',
field: 'projectId',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'nu_iot_tplink_project,project_name,project_id',
placeholder: '请选择项目',
},
//colProps: { span: 6 },
},
];
export const formSchema: FormSchema[] = [
{
label: '',
field: 'id',
component: 'Input',
show: false,
},
{
label: '项目序号',
field: 'projectId',
component: 'Input'
},
{
label: '项目名称',
field: 'projectName',
component: 'Input',
},
{
label: '创建时间',
field: 'createTimeStr',
component: 'Input',
},
{
label: '设备数量',
field: 'deviceNum',
component: 'Input',
},
{
label: '离线设备数',
field: 'offlineNum',
component: 'Input',
},
{
label: '异常设备数',
field: 'abnormalNum',
component: 'Input',
},
{
label: '运行天数',
field: 'runningTimeStr',
component: 'Input',
},
{
label: '状态',
field: 'status',
component: 'JDictSelectTag',
defaultValue: 1,
componentProps: ({}) => {
return {
options: [
{ label: '正常', value: 1, key: '1' },
{ label: '冻结', value: 2, key: '2' },
]
};
},
},
];

View File

@ -0,0 +1,161 @@
<template>
<a-spin :spinning="syncoading">
<div class="p-2">
<!--引用表格-->
<BasicTable @register="registerTable">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:sync-outlined" @click="handleSync"> 同步</a-button>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)"/>
</template>
<template v-slot:bodyCell="{ column, record, index, text }">
</template>
</BasicTable>
<!-- 表单区域 -->
</div>
</a-spin>
</template>
<script lang="ts" name="iot-nuIotCameraInfo" setup>
import {ref, reactive, createVNode, h} from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns,searchFormSchema } from './RegionInfo.data';
import { list, sync } from './RegionInfo.api';
import { useUserStore } from '/@/store/modules/user';
import { useRouter } from 'vue-router';
import { useDrawer } from "@/components/Drawer";
import { useMessage } from "@/hooks/web/useMessage";
const { createMessage } = useMessage();
//drawer
const [registerDrawer, { openDrawer }] = useDrawer();
let router = useRouter();
const formRef = ref();
const syncoading = ref<boolean>(false);
const queryParam = reactive<any>({});
const userStore = useUserStore();
//table
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '护理单元-物联管理-区域信息',
api: list,
columns,
canResize:false,
//
showActionColumn: false,
formConfig: {
// labelWidth: 200,
schemas: searchFormSchema,
},
actionColumn: {
width: 120,
fixed: 'right',
},
beforeFetch: async (params) => {
return Object.assign(params, queryParam);
},
},
});
const [registerTable, { reload, getForm, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] = tableContext;
const labelCol = reactive({
xs:24,
sm:4,
xl:6,
xxl:4
});
const wrapperCol = reactive({
xs: 24,
sm: 20,
});
/**
* 详情
*/
function handleDetail(record: Recordable) {
openDrawer(true, {
record,
isUpdate: true,
showFooter: false,
tenantSaas: false,
});
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
},
];
}
/**
* 查询
*/
function searchQuery() {
reload();
}
/**
* 重置
*/
function searchReset() {
formRef.value.resetFields();
selectedRowKeys.value = [];
//
reload();
}
/**
* 信息同步
*/
function handleSync(){
let { getFieldsValue } = getForm();
let params = getFieldsValue();
if(params.projectId==null||params.projectId==''){
createMessage.error("请先择一个项目");
return;
}
syncoading.value = true;
sync(params).then(res=>{
syncoading.value = false;
//
reload();
}).catch(res=>{
syncoading.value = false;
});
}
</script>
<style lang="less" scoped>
.jeecg-basic-table-form-container {
padding: 0;
.table-page-search-submitButtons {
display: block;
margin-bottom: 24px;
white-space: nowrap;
}
.query-group-cust{
min-width: 100px !important;
}
.query-group-split-cust{
width: 30px;
display: inline-block;
text-align: center
}
.ant-form-item:not(.ant-form-item-with-help){
margin-bottom: 16px;
height: 32px;
}
:deep(.ant-picker),:deep(.ant-input-number){
width: 100%;
}
}
</style>