Commit 37b07bcff0578b32cb23ca06286768cd61a1fb13

Authored by xp.Huang
2 parents 087e8308 2475deea

Merge branch 'perf_video_channel' into 'main_dev'

perf: 调整视频管理GBT28181,联调通道号接口,和修改为用户选择

See merge request yunteng/thingskit-front!1222
@@ -22,6 +22,7 @@ enum CameraManagerApi { @@ -22,6 +22,7 @@ enum CameraManagerApi {
22 VIDEO_CONTROL_STOP = '/video/control/stop/', 22 VIDEO_CONTROL_STOP = '/video/control/stop/',
23 CAMERA_ADDGPT_API_URL = '/video/add/gbt28181', 23 CAMERA_ADDGPT_API_URL = '/video/add/gbt28181',
24 VIDEO_CONTROL_SYNC = '/video/control/sync/', 24 VIDEO_CONTROL_SYNC = '/video/control/sync/',
  25 + VIDEO_CHANNEL_LIST = '/video/channel/list',
25 } 26 }
26 27
27 export const cameraPage = (params: CameraQueryParam) => { 28 export const cameraPage = (params: CameraQueryParam) => {
@@ -152,3 +153,10 @@ export const syncVideoApiGet = (deviceId: string) => { @@ -152,3 +153,10 @@ export const syncVideoApiGet = (deviceId: string) => {
152 url: `${CameraManagerApi.VIDEO_CONTROL_SYNC}${deviceId}`, 153 url: `${CameraManagerApi.VIDEO_CONTROL_SYNC}${deviceId}`,
153 }); 154 });
154 }; 155 };
  156 +
  157 +//获取设备通道列表
  158 +export const getDeviceChannelList = (deviceId: string) => {
  159 + return defHttp.get({
  160 + url: `${CameraManagerApi.VIDEO_CHANNEL_LIST}?deviceId=${deviceId}`,
  161 + });
  162 +};
@@ -26,9 +26,10 @@ @@ -26,9 +26,10 @@
26 import { useMessage } from '/@/hooks/web/useMessage'; 26 import { useMessage } from '/@/hooks/web/useMessage';
27 import { getStreamingMediaList, createGPTPostApi } from '/@/api/camera/cameraManager'; 27 import { getStreamingMediaList, createGPTPostApi } from '/@/api/camera/cameraManager';
28 import SelectDevice from './SelectDevice.vue'; 28 import SelectDevice from './SelectDevice.vue';
29 - import { formGBTSchema, StreamInterface } from '../config.data'; 29 + import { formGBTSchema, GBT28181DeviceSelectMethod, StreamInterface } from '../config.data';
30 import { getMeetTheConditionsDevice } from '/@/api/dataBoard'; 30 import { getMeetTheConditionsDevice } from '/@/api/dataBoard';
31 import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const'; 31 import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
  32 + import { isArray } from '/@/utils/is';
32 33
33 const emits = defineEmits(['success', 'register']); 34 const emits = defineEmits(['success', 'register']);
34 35
@@ -99,14 +100,20 @@ @@ -99,14 +100,20 @@
99 return record; 100 return record;
100 }); 101 });
101 102
  103 + const errorMessage = (errorText) => {
  104 + createMessage.error(errorText);
  105 + throw Error(errorText);
  106 + };
  107 +
102 //验证视频通道号 108 //验证视频通道号
103 const validateChannelNos = (gptDeviceDTOS) => { 109 const validateChannelNos = (gptDeviceDTOS) => {
  110 + if (isArray(gptDeviceDTOS) && gptDeviceDTOS.length == 0) {
  111 + errorMessage('请选择设备通道号');
  112 + }
104 gptDeviceDTOS.some((deviceDTO) => { 113 gptDeviceDTOS.some((deviceDTO) => {
105 const channelNo = deviceDTO['channelNos']; 114 const channelNo = deviceDTO['channelNos'];
106 - const ChannelNoRegexp = /^\d{1,20}$/;  
107 - if (!ChannelNoRegexp.test(channelNo)) {  
108 - createMessage.error('输入内容只能是数字,且不能超过20位');  
109 - throw Error('输入内容只能是数字,且不能超过20位'); 115 + if (!channelNo || (isArray(channelNo) && channelNo.length == 0)) {
  116 + errorMessage('请选择通道号');
110 } 117 }
111 }); 118 });
112 }; 119 };
@@ -116,10 +123,11 @@ @@ -116,10 +123,11 @@
116 setDrawerProps({ confirmLoading: true }); 123 setDrawerProps({ confirmLoading: true });
117 const values = await validate(); 124 const values = await validate();
118 if (!values) return; 125 if (!values) return;
119 - const gptDeviceDTOS = selectDeviceRef.value?.getDeviceChannels();  
120 - if (Array.isArray(gptDeviceDTOS) && gptDeviceDTOS.length == 0)  
121 - return createMessage.error('请填写设备通道号');  
122 - validateChannelNos(gptDeviceDTOS); 126 + let gptDeviceDTOS: Recordable[] | null = null;
  127 + if (values.allDevice === GBT28181DeviceSelectMethod.PARTIAL) {
  128 + gptDeviceDTOS = selectDeviceRef.value?.getDeviceChannels() as any as Recordable[];
  129 + validateChannelNos(gptDeviceDTOS);
  130 + }
123 const mergeValues = { 131 const mergeValues = {
124 ...values, 132 ...values,
125 accessMode: 2, 133 accessMode: 2,
1 <template> 1 <template>
2 <div v-for="(param, index) in dynamicInput.params" :key="index" class="mt-4 flex gap-2"> 2 <div v-for="(param, index) in dynamicInput.params" :key="index" class="mt-4 flex gap-2">
3 <a-input disabled v-model:value="param.deviceName" class="w-1/2 flex-1" /> 3 <a-input disabled v-model:value="param.deviceName" class="w-1/2 flex-1" />
4 - <a-input v-model:value="param.channelNos" class="w-1/2 flex-1" placeholder="请输入通道号" /> 4 + <Select
  5 + placeholder="请选择通道号"
  6 + v-model:value="param.channelNos"
  7 + class="!w-1/2"
  8 + :options="selectOptions"
  9 + v-bind="createPickerSearch()"
  10 + :disabled="disabled"
  11 + @change="handleChange"
  12 + mode="multiple"
  13 + allowClear
  14 + />
5 </div> 15 </div>
6 </template> 16 </template>
7 <script lang="ts"> 17 <script lang="ts">
@@ -10,9 +20,14 @@ @@ -10,9 +20,14 @@
10 }; 20 };
11 </script> 21 </script>
12 <script lang="ts" setup name="SelectAttributes"> 22 <script lang="ts" setup name="SelectAttributes">
13 - import { reactive, UnwrapRef, watchEffect } from 'vue'; 23 + import { reactive, UnwrapRef, watchEffect, ref } from 'vue';
14 import { propTypes } from '/@/utils/propTypes'; 24 import { propTypes } from '/@/utils/propTypes';
15 import { DeviceChannelInterface } from '../config.data'; 25 import { DeviceChannelInterface } from '../config.data';
  26 + import { createPickerSearch } from '/@/utils/pickerSearch';
  27 + import { Select } from 'ant-design-vue';
  28 + import { getDeviceChannelList } from '/@/api/camera/cameraManager';
  29 + import { isArray } from '/@/utils/is';
  30 + import cloneDeep from 'lodash-es/cloneDeep';
16 31
17 const props = defineProps({ 32 const props = defineProps({
18 value: propTypes.object.def({}), 33 value: propTypes.object.def({}),
@@ -22,10 +37,25 @@ @@ -22,10 +37,25 @@
22 }, 37 },
23 }); 38 });
24 39
  40 + const selectOptions = ref<LabelValueOptions>([]);
  41 +
25 //动态数据 42 //动态数据
26 const dynamicInput: UnwrapRef<{ params: DeviceChannelInterface[] }> = reactive({ params: [] }); 43 const dynamicInput: UnwrapRef<{ params: DeviceChannelInterface[] }> = reactive({ params: [] });
27 44
28 const initVal = async () => { 45 const initVal = async () => {
  46 + if (props.value?.value) {
  47 + const res = await getDeviceChannelList(props.value?.value);
  48 + if (isArray(res) && res.length !== 0) {
  49 + selectOptions.value = res.map((channelItem) => ({
  50 + label: channelItem.name,
  51 + value: channelItem.channelId,
  52 + }));
  53 + selectOptions.value.unshift({
  54 + label: '全选',
  55 + value: 'all',
  56 + });
  57 + }
  58 + }
29 if (props.value) { 59 if (props.value) {
30 dynamicInput.params.push({ 60 dynamicInput.params.push({
31 deviceName: props.value.label, 61 deviceName: props.value.label,
@@ -42,9 +72,40 @@ @@ -42,9 +72,40 @@
42 72
43 valEffect(); 73 valEffect();
44 74
  75 + const selectValues = ref<string[]>([]); // 保存选择的值
  76 +
  77 + const saveSelectValues = ref<Recordable>({});
  78 +
45 //chang改变 79 //chang改变
46 const emitChange = () => { 80 const emitChange = () => {
47 - return { ...dynamicInput.params[0], channelNos: [dynamicInput.params[0].channelNos] }; 81 + return saveSelectValues.value;
  82 + };
  83 +
  84 + const excludeKeyAllValue = (value: LabelValueOptions) => {
  85 + return value?.filter((selectItem) => selectItem.value !== 'all');
  86 + };
  87 +
  88 + const handleChange = (_, options) => {
  89 + // 全选
  90 + const findSelectAll = options?.find((optionItem) => optionItem.value === 'all')?.value;
  91 + if (findSelectAll) {
  92 + dynamicInput.params[0].channelNos = ['all']; //全选
  93 + excludeKeyAllValue(selectOptions.value)?.forEach(
  94 + (disabledItem) => (disabledItem.disabled = true)
  95 + );
  96 + selectValues.value = cloneDeep(selectOptions.value)
  97 + ?.filter((selectItem) => selectItem.value !== 'all')
  98 + ?.map((mapItem) => mapItem.value);
  99 + } else {
  100 + excludeKeyAllValue(selectOptions.value)?.forEach(
  101 + (disabledItem) => (disabledItem.disabled = false)
  102 + );
  103 + }
  104 + const data = cloneDeep(options)?.map((mapItem) => mapItem.value);
  105 + saveSelectValues.value = {
  106 + ...dynamicInput.params[0],
  107 + channelNos: findSelectAll ? selectValues.value : data,
  108 + };
48 }; 109 };
49 110
50 defineExpose({ 111 defineExpose({
@@ -8,11 +8,12 @@ @@ -8,11 +8,12 @@
8 @change="handleDeviceChange" 8 @change="handleDeviceChange"
9 :disabled="disabled" 9 :disabled="disabled"
10 mode="multiple" 10 mode="multiple"
  11 + :max-tag-count="selectChannelMaxCount"
11 labelInValue 12 labelInValue
12 /> 13 />
13 - <template v-for="(item, index) in deviceList" :key="index"> 14 + <template v-for="(item, index) in deviceList" :key="item.value">
14 <InputChannel 15 <InputChannel
15 - :ref="bindDeviceRef.deviceAttrRef" 16 + :ref="bindDeviceRef.deviceChannelRef"
16 :value="item" 17 :value="item"
17 :index="index" 18 :index="index"
18 :disabled="disabled" 19 :disabled="disabled"
@@ -25,6 +26,7 @@ @@ -25,6 +26,7 @@
25 import { createPickerSearch } from '/@/utils/pickerSearch'; 26 import { createPickerSearch } from '/@/utils/pickerSearch';
26 import { StreamInterface } from '../config.data'; 27 import { StreamInterface } from '../config.data';
27 import InputChannel from './InputChannel.vue'; 28 import InputChannel from './InputChannel.vue';
  29 + import { useMessage } from '/@/hooks/web/useMessage';
28 30
29 defineProps({ 31 defineProps({
30 selectOptions: { 32 selectOptions: {
@@ -37,19 +39,25 @@ @@ -37,19 +39,25 @@
37 }, 39 },
38 }); 40 });
39 41
  42 + const { createMessage } = useMessage();
  43 +
40 const selectValue = ref([]); 44 const selectValue = ref([]);
41 45
42 const bindDeviceRef = { 46 const bindDeviceRef = {
43 - deviceAttrRef: ref([]), 47 + deviceChannelRef: ref([]),
44 }; 48 };
45 49
46 const deviceList: Ref<StreamInterface[]> = ref([]); 50 const deviceList: Ref<StreamInterface[]> = ref([]);
47 51
  52 + const selectChannelMaxCount = ref(50); // 限制通道数为50
  53 +
48 const getDeviceChannels = () => { 54 const getDeviceChannels = () => {
49 - return unref(bindDeviceRef.deviceAttrRef)?.map((item: any) => item.emitChange()); 55 + return unref(bindDeviceRef.deviceChannelRef)?.map((item: any) => item.emitChange());
50 }; 56 };
51 57
52 const handleDeviceChange = (_, options) => { 58 const handleDeviceChange = (_, options) => {
  59 + if (options.length > selectChannelMaxCount.value)
  60 + return createMessage.warn('通道数需要小于50个');
53 deviceList.value = options; 61 deviceList.value = options;
54 }; 62 };
55 63
1 import { BasicColumn, FormSchema } from '/@/components/Table'; 1 import { BasicColumn, FormSchema } from '/@/components/Table';
2 import { FormSchema as QFormSchema, useComponentRegister } from '/@/components/Form/index'; 2 import { FormSchema as QFormSchema, useComponentRegister } from '/@/components/Form/index';
3 3
4 -import { CameraVideoUrl, CameraMaxLength, CameraChannelNoRule } from '/@/utils/rules'; 4 +import { CameraVideoUrl, CameraMaxLength } from '/@/utils/rules';
5 import { h } from 'vue'; 5 import { h } from 'vue';
6 import SnHelpMessage from './SnHelpMessage.vue'; 6 import SnHelpMessage from './SnHelpMessage.vue';
7 import SnHelpMessage1 from './SnHelpMessage1.vue'; 7 import SnHelpMessage1 from './SnHelpMessage1.vue';
@@ -15,6 +15,7 @@ import { DataSourceField } from '../../visual/packages/config/common.config'; @@ -15,6 +15,7 @@ import { DataSourceField } from '../../visual/packages/config/common.config';
15 import { getMeetTheConditionsDevice } from '/@/api/dataBoard'; 15 import { getMeetTheConditionsDevice } from '/@/api/dataBoard';
16 import { useMessage } from '/@/hooks/web/useMessage'; 16 import { useMessage } from '/@/hooks/web/useMessage';
17 import { TransportTypeEnum } from '../../device/profiles/components/TransportDescript/const'; 17 import { TransportTypeEnum } from '../../device/profiles/components/TransportDescript/const';
  18 +import { getDeviceChannelList } from '/@/api/camera/cameraManager';
18 19
19 useComponentRegister('OrgTreeSelect', OrgTreeSelect); 20 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
20 21
@@ -36,6 +37,12 @@ export enum CameraPermission { @@ -36,6 +37,12 @@ export enum CameraPermission {
36 DELETE = 'api:yt:video:delete', 37 DELETE = 'api:yt:video:delete',
37 } 38 }
38 39
  40 +// GBT28181设备选择全选或者部分
  41 +export enum GBT28181DeviceSelectMethod {
  42 + ALL = 1, //全部
  43 + PARTIAL = 0, //部分
  44 +}
  45 +
39 export enum AccessMode { 46 export enum AccessMode {
40 ManuallyEnter = 0, 47 ManuallyEnter = 0,
41 Streaming = 1, 48 Streaming = 1,
@@ -265,7 +272,7 @@ export const formSchema: QFormSchema[] = [ @@ -265,7 +272,7 @@ export const formSchema: QFormSchema[] = [
265 placeholder: '请选择设备', 272 placeholder: '请选择设备',
266 onChange() { 273 onChange() {
267 setFieldsValue({ 274 setFieldsValue({
268 - channelNo: '', 275 + channelNo: [],
269 }); 276 });
270 }, 277 },
271 onFocus() { 278 onFocus() {
@@ -282,15 +289,30 @@ export const formSchema: QFormSchema[] = [ @@ -282,15 +289,30 @@ export const formSchema: QFormSchema[] = [
282 field: 'channelNo', 289 field: 'channelNo',
283 label: '通道号', 290 label: '通道号',
284 required: true, 291 required: true,
285 - component: 'Input', 292 + component: 'ApiSelect',
286 ifShow({ values }) { 293 ifShow({ values }) {
287 return values.accessMode === AccessMode.GBT28181; 294 return values.accessMode === AccessMode.GBT28181;
288 }, 295 },
289 - componentProps: {  
290 - maxLength: 20,  
291 - placeholder: '请输入通道号', 296 + componentProps({ formModel }) {
  297 + const deviceId = formModel[DataSourceField.DEVICE_ID];
  298 + return {
  299 + api: async () => {
  300 + if (deviceId) {
  301 + try {
  302 + const data = await getDeviceChannelList(deviceId);
  303 + if (data)
  304 + return data.map((item) => ({
  305 + ...item,
  306 + label: item.name,
  307 + value: item.channelId,
  308 + }));
  309 + } catch (error) {}
  310 + }
  311 + return [];
  312 + },
  313 + placeholder: '请选择通道号',
  314 + };
292 }, 315 },
293 - rules: [{ required: true, message: '通道号是必填项' }, ...CameraChannelNoRule],  
294 }, 316 },
295 { 317 {
296 field: 'brand', 318 field: 'brand',
@@ -537,10 +559,30 @@ export const formGBTSchema: QFormSchema[] = [ @@ -537,10 +559,30 @@ export const formGBTSchema: QFormSchema[] = [
537 ifShow: ({ values }) => values.accessMode === AccessMode.GBT28181, 559 ifShow: ({ values }) => values.accessMode === AccessMode.GBT28181,
538 }, 560 },
539 { 561 {
  562 + label: '设备选择方式',
  563 + field: 'allDevice',
  564 + component: 'RadioGroup',
  565 + rules: [{ required: true, message: '设备选择方式为必选项', type: 'number' }],
  566 + defaultValue: GBT28181DeviceSelectMethod.ALL,
  567 + componentProps() {
  568 + return {
  569 + defaultValue: GBT28181DeviceSelectMethod.ALL,
  570 + placeholder: '请选择设备选择方式',
  571 + options: [
  572 + { label: '全部', value: GBT28181DeviceSelectMethod.ALL },
  573 + { label: '部分', value: GBT28181DeviceSelectMethod.PARTIAL },
  574 + ],
  575 + };
  576 + },
  577 + ifShow: ({ values }) => !!values.organizationId,
  578 + },
  579 + {
540 field: 'deviceChannels', 580 field: 'deviceChannels',
541 label: '设备和通道号', 581 label: '设备和通道号',
542 component: 'Input', 582 component: 'Input',
543 slot: 'deviceChannelsSlot', 583 slot: 'deviceChannelsSlot',
544 - ifShow: ({ values }) => values.accessMode === AccessMode.GBT28181, 584 + ifShow: ({ values }) =>
  585 + values.accessMode === AccessMode.GBT28181 &&
  586 + values.allDevice === GBT28181DeviceSelectMethod.PARTIAL,
545 }, 587 },
546 ]; 588 ];
@@ -88,7 +88,7 @@ export const descSchema = (emit: EmitType): DescItem[] => { @@ -88,7 +88,7 @@ export const descSchema = (emit: EmitType): DescItem[] => {
88 label: '设备编号', 88 label: '设备编号',
89 }, 89 },
90 { 90 {
91 - field: 'deviceInfo.sip.localIp', 91 + field: 'deviceInfo.sip.hostAddress',
92 label: '地址', 92 label: '地址',
93 }, 93 },
94 { 94 {