Commit 338f226b25d5c5cfc4696c57cf1ce1635117746f

Authored by xp.Huang
2 parents 66a595da 628d883c

Merge branch 'fix/video-GBT-cancel' into 'main_dev'

fix: 修复国标播放和新增接口调用问题

See merge request yunteng/thingskit-front!1211
... ... @@ -8,10 +8,10 @@ VITE_GLOB_PUBLIC_PATH = /
8 8 # Please note that no line breaks
9 9
10 10 # 本地
11   -VITE_PROXY = [["/api","http://localhost:8080/api"],["/thingskit-scada","http://localhost:5173/thingskit-scada"],["/large-designer", "http://localhost:5555/large-designer/"]]
  11 +VITE_PROXY = [["/api","http://222.180.200.114:30480/api"],["/thingskit-scada","http://localhost:5173/thingskit-scada"],["/large-designer", "http://localhost:5555/large-designer/"]]
12 12
13 13 # 实时数据的ws地址
14   -VITE_GLOB_WEB_SOCKET = ws://192.168.1.8:8080/api/ws/plugins/telemetry?token=
  14 +VITE_GLOB_WEB_SOCKET = ws://222.180.200.114:30480/api/ws/plugins/telemetry?token=
15 15
16 16 # Delete console
17 17 VITE_GLOB_DROP_CONSOLE = true
... ...
... ... @@ -20,6 +20,7 @@ enum CameraManagerApi {
20 20 STREAMING_DELETE_URL = '/video/platform',
21 21 STREAMING_PLAY_GET_URL = '/video/url',
22 22 VIDEO_CONTROL_STOP = '/video/control/stop/',
  23 + CAMERA_ADDGPT_API_URL = '/video/addGpt',
23 24 }
24 25
25 26 export const cameraPage = (params: CameraQueryParam) => {
... ... @@ -135,3 +136,11 @@ export const stopOnDemandVideoApiGet = (deviceId: string, channelId: string) =>
135 136 url: `${CameraManagerApi.VIDEO_CONTROL_STOP}${deviceId}/${channelId}`,
136 137 });
137 138 };
  139 +
  140 +// GPT28181
  141 +export const createGPTPostApi = (data: Recordable) => {
  142 + return defHttp.post({
  143 + url: CameraManagerApi.CAMERA_ADDGPT_API_URL,
  144 + data,
  145 + });
  146 +};
... ...
1 1 import { RpcCommandType } from '../../device/model/deviceConfigModel';
2 2 import { DataType, ExtensionDesc } from '../../device/model/modelOfMatterModel';
3 3 import { DataTypeEnum, ObjectModelAccessModeEnum } from '/@/enums/objectModelEnum';
  4 +import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
4 5 import { DataSource } from '/@/views/visual/palette/types';
5 6
6 7 export interface AddDataBoardParams {
... ... @@ -170,4 +171,5 @@ export interface GetMeetTheConditionsDeviceParams {
170 171 deviceType?: string;
171 172 organizationId?: string;
172 173 deviceProfileId?: string;
  174 + transportType: TransportTypeEnum;
173 175 }
... ...
... ... @@ -39,6 +39,7 @@ export interface VideoChannelItemType {
39 39 channelType: number;
40 40 ptztype: number;
41 41 ifShowGBT?: boolean;
  42 + accessMode?: number;
42 43 }
43 44
44 45 export interface VideoChannelPlayAddressType {
... ...
  1 +export function useDecToIEE754() {
  2 + function numberToArr32(number: number) {
  3 + const arr = new Uint8Array(4);
  4 + const view = new DataView(arr.buffer);
  5 + view.setFloat32(0, +number);
  6 + return arr;
  7 + }
  8 +
  9 + function numberToArr64(number: number) {
  10 + const arr = new Uint8Array(8);
  11 + const view = new DataView(arr.buffer);
  12 + view.setFloat64(0, +number);
  13 + return arr;
  14 + }
  15 +
  16 + function arrToBase(toBase: number, arr: Uint8Array) {
  17 + let result = '';
  18 + for (let i = 0; i < arr.length; i++) {
  19 + result += (256 + arr[i]).toString(toBase).substring(1).toUpperCase();
  20 + }
  21 +
  22 + return result;
  23 + }
  24 +
  25 + function coverNumberByBase(toBase, number: number) {
  26 + if (toBase === 'bin32') {
  27 + return arrToBase(2, numberToArr32(number));
  28 + } else if (toBase === 'bin64') {
  29 + return arrToBase(2, numberToArr64(number));
  30 + } else if (toBase === 'hex32') {
  31 + return arrToBase(16, numberToArr32(number));
  32 + } else if (toBase === 'hex64') {
  33 + return arrToBase(16, numberToArr64(number));
  34 + }
  35 + }
  36 +
  37 + return { coverNumberByBase };
  38 +}
... ...
... ... @@ -193,6 +193,21 @@ export const CameraVideoUrl: Rule[] = [
193 193 },
194 194 ];
195 195
  196 +// 验证设备通道号正则
  197 +export const CameraChannelNoRule: Rule[] = [
  198 + {
  199 + required: true,
  200 + validator: (_, value: string) => {
  201 + const ChannelNoRegexp = /^\d{1,20}$/;
  202 + if (!ChannelNoRegexp.test(value)) {
  203 + return Promise.reject('输入内容只能是数字,且不能超过20位');
  204 + }
  205 + return Promise.resolve();
  206 + },
  207 + validateTrigger: 'blur',
  208 + },
  209 +];
  210 +
196 211 export const CameraVideoStreamUrl: Rule[] = [
197 212 {
198 213 required: true,
... ...
... ... @@ -110,7 +110,6 @@
110 110 await nextTick();
111 111 const { record } = data || {};
112 112 editId.value = data.record.id;
113   -
114 113 if (data.record.avatar) {
115 114 setFieldsValue({
116 115 avatar: [{ uid: buildUUID(), name: 'name', url: data.record.avatar } as FileItem],
... ... @@ -127,6 +126,13 @@
127 126 }
128 127 const { avatar, ...params } = data.record;
129 128 avatarStr.value = avatar;
  129 + //回显GBT28181
  130 + if (record?.accessMode === AccessMode.GBT28181) {
  131 + setFieldsValue({
  132 + deviceId: record?.params?.deviceId,
  133 + channelNo: record?.params?.channelNo,
  134 + });
  135 + }
130 136 // 不能把avatar字段回显进去,页面会显示大量警告
131 137 await setFieldsValue({
132 138 videoType: record.videoPlatformDTO?.type,
... ... @@ -167,6 +173,14 @@
167 173 values.params = values.channelNo ? { channelNo: values.channelNo } : null;
168 174 }
169 175
  176 + //新增GBT28181
  177 + if (values?.accessMode === AccessMode.GBT28181) {
  178 + values.params = {
  179 + channelNo: values.channelNo,
  180 + deviceId: values.deviceId,
  181 + };
  182 + }
  183 +
170 184 await createOrEditCameraManage(values);
171 185 closeDrawer();
172 186 emit('success');
... ...
... ... @@ -10,10 +10,13 @@
10 10 class="w-3/4 xl:w-4/5"
11 11 >
12 12 <template #toolbar>
13   - <a-button type="primary" @click="handleSwitchMode">分屏模式</a-button>
  13 + <Authority :value="CameraPermission.CREATE">
  14 + <a-button type="primary" @click="handleOpenGBTDrawer"> 批量新增GBT28181 </a-button>
  15 + </Authority>
14 16 <Authority :value="CameraPermission.CREATE">
15 17 <a-button type="primary" @click="handleCreateOrEdit(null)"> 新增视频 </a-button>
16 18 </Authority>
  19 + <a-button type="primary" @click="handleSwitchMode">分屏模式</a-button>
17 20 <Authority :value="CameraPermission.DELETE">
18 21 <Popconfirm
19 22 title="您确定要批量删除数据"
... ... @@ -78,8 +81,8 @@
78 81 </PageWrapper>
79 82 <CameraDrawer @register="registerDrawer" @success="handleSuccess" />
80 83 <VideoPreviewModal @register="registerModal" />
81   -
82 84 <VideoModal @register="registerModal1" />
  85 + <GBTDrawer @register="registerGBTDrawer" @success="handleGBTSuccess" />
83 86 </div>
84 87 </template>
85 88
... ... @@ -89,6 +92,7 @@
89 92 import { PageWrapper } from '/@/components/Page';
90 93 import { useDrawer } from '/@/components/Drawer';
91 94 import CameraDrawer from './CameraDrawer.vue';
  95 + import GBTDrawer from './components/GBTDrawer.vue';
92 96 import { useResetOrganizationTree, OrganizationIdTree } from '/@/views/common/organizationIdTree';
93 97 import { cameraPage, deleteCameraManage } from '/@/api/camera/cameraManager';
94 98 import {
... ... @@ -120,6 +124,7 @@
120 124 Authority,
121 125 Popconfirm,
122 126 Tag,
  127 + GBTDrawer,
123 128 },
124 129 emits: ['switchMode'],
125 130 setup(_, { emit }) {
... ... @@ -155,6 +160,9 @@
155 160 // 弹框
156 161 const [registerDrawer, { openDrawer }] = useDrawer();
157 162
  163 + //GBT28181 Drawer
  164 + const [registerGBTDrawer, { openDrawer: openGBTDrawer }] = useDrawer();
  165 +
158 166 // 刷新
159 167 const handleSuccess = () => {
160 168 reload();
... ... @@ -195,15 +203,34 @@
195 203 isUpdate: true,
196 204 record,
197 205 };
198   - if (record.accessMode === AccessMode.Streaming && type === VideoPlatformEnum.ISC)
199   - openModal1(true, commonRecord);
200   - else openModal(true, commonRecord);
  206 + // 新增一个判断,GBT28181也弹出云台控制界面
  207 + // if (record.accessMode === AccessMode.Streaming && type === VideoPlatformEnum.ISC) {
  208 + // openModal1(true, commonRecord);
  209 + // } else if (record.accessMode === AccessMode.GBT28181) {
  210 + // openModal1(true, { ...commonRecord, ifShowGBT: true });
  211 + // } else openModal(true, commonRecord);
  212 +
  213 + if (
  214 + record.accessMode === AccessMode.ManuallyEnter ||
  215 + (record.accessMode === AccessMode.Streaming && type === VideoPlatformEnum.FLUORITE)
  216 + ) {
  217 + openModal(true, commonRecord);
  218 + } else if (record.accessMode === AccessMode.GBT28181) {
  219 + openModal1(true, { ...commonRecord, ifShowGBT: true });
  220 + } else openModal1(true, commonRecord);
201 221 };
202 222
203 223 const handleSwitchMode = () => {
204 224 emit('switchMode', PageMode.SPLIT_SCREEN_MODE);
205 225 };
206 226
  227 + // 批量新增GBT28181国标
  228 + const handleOpenGBTDrawer = () => {
  229 + openGBTDrawer(true);
  230 + };
  231 +
  232 + const handleGBTSuccess = () => reload();
  233 +
207 234 return {
208 235 searchInfo,
209 236 hasBatchDelete,
... ... @@ -220,6 +247,9 @@
220 247 AccessMode,
221 248 handleSwitchMode,
222 249 CameraPermission,
  250 + handleOpenGBTDrawer,
  251 + registerGBTDrawer,
  252 + handleGBTSuccess,
223 253 };
224 254 },
225 255 });
... ...
... ... @@ -7,6 +7,7 @@
7 7 import { CameraRecord } from '/@/api/camera/model/cameraModel';
8 8 import { useFullscreen } from '@vueuse/core';
9 9 import CameraDrawer from './CameraDrawer.vue';
  10 + import GBTDrawer from './components/GBTDrawer.vue';
10 11 import { useDrawer } from '/@/components/Drawer';
11 12 import { AccessMode, CameraPermission, PageMode } from './config.data';
12 13 import SvgIcon from '/@/components/Icon/src/SvgIcon.vue';
... ... @@ -180,12 +181,20 @@
180 181
181 182 const [registerDrawer, { openDrawer }] = useDrawer();
182 183
  184 + //GBT28181 Drawer
  185 + const [registerGBTDrawer, { openDrawer: openGBTDrawer }] = useDrawer();
  186 +
183 187 const handleAddCamera = () => {
184 188 openDrawer(true, {
185 189 isUpdate: false,
186 190 });
187 191 };
188 192
  193 + // 批量新增GBT28181国标
  194 + const handleOpenGBTDrawer = () => {
  195 + openGBTDrawer(true);
  196 + };
  197 +
189 198 const handleCloseFlvPlayUrl = async (record: CameraRecordItem) => {
190 199 if (isRtspProtocol(record.videoUrl)) {
191 200 closeFlvPlay(record.videoUrl, unref(fingerprintResult)!.visitorId!);
... ... @@ -254,6 +263,10 @@
254 263 <div class="flex items-center gap-4">
255 264 <div class="flex">
256 265 <Authority :value="CameraPermission.CREATE">
  266 + <Button type="primary" @click="handleOpenGBTDrawer">批量新增GBT28181</Button>
  267 + </Authority>
  268 + <div style="width: 17px"></div>
  269 + <Authority :value="CameraPermission.CREATE">
257 270 <Button type="primary" @click="handleAddCamera">新增视频</Button>
258 271 </Authority>
259 272 </div>
... ... @@ -320,6 +333,7 @@
320 333 </section>
321 334 </PageWrapper>
322 335 <CameraDrawer @register="registerDrawer" @success="getCameraList" />
  336 + <GBTDrawer @register="registerGBTDrawer" @success="getCameraList" />
323 337 </div>
324 338 </template>
325 339
... ...
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + showFooter
  6 + title="新增GBT28181"
  7 + width="30%"
  8 + @ok="handleSubmit"
  9 + wrapClassName="camera-configration-drawer"
  10 + >
  11 + <BasicForm @register="registerForm">
  12 + <!-- 表单设备通道插槽 -->
  13 + <template #deviceChannelsSlot="{ model }">
  14 + <span class="hidden">{{ handleSelectOrg(model['organizationId']) }}</span>
  15 + <SelectDevice ref="selectDeviceRef" :selectOptions="selectDeviceOptions" />
  16 + </template>
  17 + </BasicForm>
  18 + </BasicDrawer>
  19 +</template>
  20 +
  21 +<script lang="ts" setup name="GBTDrawer">
  22 + import { ref, nextTick, onMounted, watch } from 'vue';
  23 + import { BasicForm, useForm } from '/@/components/Form';
  24 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  25 + import { useMessage } from '/@/hooks/web/useMessage';
  26 + import { getStreamingMediaList, createGPTPostApi } from '/@/api/camera/cameraManager';
  27 + import SelectDevice from './SelectDevice.vue';
  28 + import { formGBTSchema, StreamInterface } from '../config.data';
  29 + import { getMeetTheConditionsDevice } from '/@/api/dataBoard';
  30 + import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
  31 +
  32 + const emits = defineEmits(['success', 'register']);
  33 +
  34 + const streamConfigOptions = ref<StreamInterface[]>([]);
  35 +
  36 + const selectDeviceOptions = ref<StreamInterface[]>([]);
  37 +
  38 + const orgId = ref('');
  39 +
  40 + const selectDeviceRef = ref<InstanceType<typeof SelectDevice>>();
  41 +
  42 + const { createMessage } = useMessage();
  43 +
  44 + const [registerForm, { validate, resetFields }] = useForm({
  45 + labelWidth: 120,
  46 + schemas: formGBTSchema,
  47 + showActionButtonGroup: false,
  48 + });
  49 +
  50 + const getStreamingMediaLists = async () => {
  51 + const rest = await getStreamingMediaList({});
  52 + streamConfigOptions.value = rest?.map(({ host: label, id: value, type }) => ({
  53 + label,
  54 + value,
  55 + type,
  56 + }));
  57 + };
  58 +
  59 + onMounted(() => getStreamingMediaLists());
  60 +
  61 + const handleSelectOrg = (e) => (orgId.value = e);
  62 +
  63 + const getDeviceListByOrg = async (organizationId) => {
  64 + const values = await getMeetTheConditionsDevice({
  65 + organizationId,
  66 + transportType: TransportTypeEnum.GBT28181,
  67 + });
  68 + selectDeviceOptions.value = values
  69 + ? values.map((item) => ({
  70 + label: item.alias || item.name,
  71 + value: item.tbDeviceId,
  72 + id: item.id,
  73 + deviceProfileId: item.deviceProfileId,
  74 + }))
  75 + : [];
  76 + };
  77 +
  78 + watch(
  79 + () => orgId.value,
  80 + (newValue) => {
  81 + if (newValue) {
  82 + //获取设备
  83 + selectDeviceRef.value?.resetValue();
  84 + getDeviceListByOrg(newValue);
  85 + }
  86 + },
  87 + {
  88 + immediate: true,
  89 + }
  90 + );
  91 +
  92 + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
  93 + await resetFields();
  94 + await nextTick();
  95 + selectDeviceRef.value?.resetValue();
  96 + const { record } = data || {};
  97 + // 批量新增不需要回显
  98 + return record;
  99 + });
  100 +
  101 + const handleSubmit = async () => {
  102 + try {
  103 + setDrawerProps({ confirmLoading: true });
  104 + const values = await validate();
  105 + if (!values) return;
  106 + const gptDeviceDTOS = selectDeviceRef.value?.getDeviceChannels();
  107 + if (Array.isArray(gptDeviceDTOS) && gptDeviceDTOS.length == 0)
  108 + return createMessage.error('请填写设备通道号');
  109 + const mergeValues = {
  110 + ...values,
  111 + accessMode: 2,
  112 + gptDeviceDTOS,
  113 + };
  114 + await createGPTPostApi(mergeValues);
  115 + createMessage.success('批量新增成功');
  116 + closeDrawer();
  117 + emits('success');
  118 + } finally {
  119 + setDrawerProps({ confirmLoading: false });
  120 + }
  121 + };
  122 +</script>
  123 +
  124 +<style lang="less" scoped>
  125 + .camera-configration-drawer {
  126 + .ant-input-number {
  127 + width: 100% !important;
  128 + }
  129 + }
  130 +</style>
... ...
  1 +<template>
  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" />
  4 + <a-input v-model:value="param.channelNos" class="w-1/2 flex-1" placeholder="请输入通道号" />
  5 + </div>
  6 +</template>
  7 +<script lang="ts">
  8 + export default {
  9 + inheritAttrs: false,
  10 + };
  11 +</script>
  12 +<script lang="ts" setup name="SelectAttributes">
  13 + import { reactive, UnwrapRef, watchEffect } from 'vue';
  14 + import { propTypes } from '/@/utils/propTypes';
  15 + import { DeviceChannelInterface } from '../config.data';
  16 +
  17 + const props = defineProps({
  18 + value: propTypes.object.def({}),
  19 + disabled: {
  20 + type: Boolean,
  21 + required: false,
  22 + },
  23 + });
  24 +
  25 + //动态数据
  26 + const dynamicInput: UnwrapRef<{ params: DeviceChannelInterface[] }> = reactive({ params: [] });
  27 +
  28 + const initVal = async () => {
  29 + if (props.value) {
  30 + dynamicInput.params.push({
  31 + deviceName: props.value.label,
  32 + deviceID: props.value.value,
  33 + channelNos: props.value.channelNos,
  34 + });
  35 + }
  36 + };
  37 +
  38 + //数值改变
  39 + const valEffect = watchEffect(() => {
  40 + initVal();
  41 + });
  42 +
  43 + valEffect();
  44 +
  45 + //chang改变
  46 + const emitChange = () => {
  47 + return { ...dynamicInput.params[0], channelNos: [dynamicInput.params[0].channelNos] };
  48 + };
  49 +
  50 + defineExpose({
  51 + emitChange,
  52 + });
  53 +</script>
  54 +<style scoped lang="css">
  55 + .dynamic-delete-button {
  56 + cursor: pointer;
  57 + position: relative;
  58 + top: 4px;
  59 + font-size: 24px;
  60 + color: #999;
  61 + transition: all 0.3s;
  62 + }
  63 +
  64 + .dynamic-delete-button:hover {
  65 + color: #777;
  66 + }
  67 +
  68 + .dynamic-delete-button[disabled] {
  69 + cursor: not-allowed;
  70 + opacity: 0.5;
  71 + }
  72 +</style>
... ...
  1 +<template>
  2 + <Select
  3 + placeholder="请选择设备"
  4 + v-model:value="selectValue"
  5 + style="width: 100%"
  6 + :options="selectOptions"
  7 + v-bind="createPickerSearch()"
  8 + @change="handleDeviceChange"
  9 + :disabled="disabled"
  10 + mode="multiple"
  11 + labelInValue
  12 + />
  13 + <template v-for="(item, index) in deviceList" :key="index">
  14 + <InputChannel
  15 + :ref="bindDeviceRef.deviceAttrRef"
  16 + :value="item"
  17 + :index="index"
  18 + :disabled="disabled"
  19 + />
  20 + </template>
  21 +</template>
  22 +<script lang="ts" setup name="SelectDevice">
  23 + import { ref, Ref, PropType, unref } from 'vue';
  24 + import { Select } from 'ant-design-vue';
  25 + import { createPickerSearch } from '/@/utils/pickerSearch';
  26 + import { StreamInterface } from '../config.data';
  27 + import InputChannel from './InputChannel.vue';
  28 +
  29 + defineProps({
  30 + selectOptions: {
  31 + type: Array as PropType<StreamInterface[]>,
  32 + required: true,
  33 + },
  34 + disabled: {
  35 + type: Boolean,
  36 + required: false,
  37 + },
  38 + });
  39 +
  40 + const selectValue = ref([]);
  41 +
  42 + const bindDeviceRef = {
  43 + deviceAttrRef: ref([]),
  44 + };
  45 +
  46 + const deviceList: Ref<StreamInterface[]> = ref([]);
  47 +
  48 + const getDeviceChannels = () => {
  49 + return unref(bindDeviceRef.deviceAttrRef)?.map((item: any) => item.emitChange());
  50 + };
  51 +
  52 + const handleDeviceChange = (_, options) => {
  53 + deviceList.value = options;
  54 + };
  55 +
  56 + const resetValue = () => {
  57 + selectValue.value = [];
  58 + deviceList.value = [];
  59 + };
  60 + defineExpose({
  61 + resetValue,
  62 + getDeviceChannels,
  63 + });
  64 +</script>
  65 +<style scoped lang="css"></style>
... ...
1 1 import { BasicColumn, FormSchema } from '/@/components/Table';
2 2 import { FormSchema as QFormSchema, useComponentRegister } from '/@/components/Form/index';
3 3
4   -import { CameraVideoUrl, CameraMaxLength } from '/@/utils/rules';
  4 +import { CameraVideoUrl, CameraMaxLength, CameraChannelNoRule } from '/@/utils/rules';
5 5 import { h } from 'vue';
6 6 import SnHelpMessage from './SnHelpMessage.vue';
7 7 import SnHelpMessage1 from './SnHelpMessage1.vue';
... ... @@ -11,9 +11,24 @@ import { createImgPreview } from '/@/components/Preview';
11 11 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
12 12 import { findDictItemByCode } from '/@/api/system/dict';
13 13 import { DictEnum } from '/@/enums/dictEnum';
  14 +import { DataSourceField } from '../../visual/packages/config/common.config';
  15 +import { getMeetTheConditionsDevice } from '/@/api/dataBoard';
  16 +import { useMessage } from '/@/hooks/web/useMessage';
  17 +import { TransportTypeEnum } from '../../device/profiles/components/TransportDescript/const';
14 18
15 19 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
16 20
  21 +export interface StreamInterface {
  22 + label: string;
  23 + value: string;
  24 + type?: string;
  25 +}
  26 +
  27 +//设备通道接口
  28 +export interface DeviceChannelInterface {
  29 + [x: string]: string | string[];
  30 +}
  31 +
17 32 export enum CameraPermission {
18 33 PREVIEW = 'api:yt:video:get',
19 34 CREATE = 'api:yt:video:post',
... ... @@ -24,6 +39,7 @@ export enum CameraPermission {
24 39 export enum AccessMode {
25 40 ManuallyEnter = 0,
26 41 Streaming = 1,
  42 + GBT28181 = 2,
27 43 }
28 44
29 45 export enum PlayProtocol {
... ... @@ -186,6 +202,7 @@ export const formSchema: QFormSchema[] = [
186 202 options: [
187 203 { label: '手动输入', value: AccessMode.ManuallyEnter },
188 204 { label: '流媒体获取', value: AccessMode.Streaming },
  205 + { label: 'GBT28181', value: AccessMode.GBT28181 },
189 206 ],
190 207 onChange() {
191 208 formActionType.setFieldsValue({
... ... @@ -199,6 +216,63 @@ export const formSchema: QFormSchema[] = [
199 216 },
200 217 },
201 218 {
  219 + field: 'deviceId',
  220 + label: '设备',
  221 + required: true,
  222 + component: 'ApiSelect',
  223 + componentProps({ formModel, formActionType }) {
  224 + const { setFieldsValue } = formActionType;
  225 + const organizationId = formModel[DataSourceField.ORIGINATION_ID];
  226 + return {
  227 + api: async () => {
  228 + if (organizationId) {
  229 + try {
  230 + const data = await getMeetTheConditionsDevice({
  231 + organizationId,
  232 + transportType: TransportTypeEnum.GBT28181,
  233 + });
  234 + if (data)
  235 + return data.map((item) => ({
  236 + ...item,
  237 + label: item.alias || item.name,
  238 + value: item.tbDeviceId,
  239 + deviceType: item.deviceType,
  240 + }));
  241 + } catch (error) {}
  242 + }
  243 + return [];
  244 + },
  245 + placeholder: '请选择设备',
  246 + onChange() {
  247 + setFieldsValue({
  248 + channelNo: '',
  249 + });
  250 + },
  251 + onFocus() {
  252 + const { createMessage } = useMessage();
  253 + if (!organizationId) return createMessage.error('请先选择组织');
  254 + },
  255 + };
  256 + },
  257 + ifShow({ values }) {
  258 + return values.accessMode === AccessMode.GBT28181;
  259 + },
  260 + },
  261 + {
  262 + field: 'channelNo',
  263 + label: '通道号',
  264 + required: true,
  265 + component: 'Input',
  266 + ifShow({ values }) {
  267 + return values.accessMode === AccessMode.GBT28181;
  268 + },
  269 + componentProps: {
  270 + maxLength: 20,
  271 + placeholder: '请输入通道号',
  272 + },
  273 + rules: [{ required: true, message: '通道号是必填项' }, ...CameraChannelNoRule],
  274 + },
  275 + {
202 276 field: 'brand',
203 277 label: '视频厂家',
204 278 component: 'Input',
... ... @@ -410,3 +484,43 @@ export const formSchema: QFormSchema[] = [
410 484 },
411 485 },
412 486 ];
  487 +
  488 +// GBT28181表单配置项
  489 +export const formGBTSchema: QFormSchema[] = [
  490 + {
  491 + label: '视频流获取方式',
  492 + field: 'accessMode',
  493 + component: 'RadioGroup',
  494 + rules: [{ required: true, message: '视频流获取方式为必选项', type: 'number' }],
  495 + defaultValue: AccessMode.GBT28181,
  496 + componentProps({ formActionType }) {
  497 + return {
  498 + defaultValue: AccessMode.ManuallyEnter,
  499 + placeholder: '请选择视频流获取方式',
  500 + options: [{ label: 'GBT28181', value: AccessMode.GBT28181 }],
  501 + onChange() {
  502 + formActionType.setFieldsValue({
  503 + brand: null,
  504 + sn: null,
  505 + videoUrl: null,
  506 + videoPlatformId: null,
  507 + });
  508 + },
  509 + };
  510 + },
  511 + },
  512 + {
  513 + field: 'organizationId',
  514 + label: '所属组织',
  515 + required: true,
  516 + component: 'OrgTreeSelect',
  517 + ifShow: ({ values }) => values.accessMode === AccessMode.GBT28181,
  518 + },
  519 + {
  520 + field: 'deviceChannels',
  521 + label: '设备和通道号',
  522 + component: 'Input',
  523 + slot: 'deviceChannelsSlot',
  524 + ifShow: ({ values }) => values.accessMode === AccessMode.GBT28181,
  525 + },
  526 +];
... ...
... ... @@ -55,6 +55,11 @@ export function buildTableDataSourceByObjectModel(
55 55 );
56 56 }
57 57
  58 + if (isModbusDevice) {
  59 + result.boolClose = '关';
  60 + result.boolOpen = '开';
  61 + }
  62 +
58 63 return result;
59 64 }
60 65
... ...
... ... @@ -166,6 +166,7 @@
166 166 handleControl(1, action);
167 167 };
168 168
  169 + //页面卸载
169 170 onUnmounted(() => {
170 171 unref(videoPlayInstance)?.dispose();
171 172 videoPlayInstance.value = null;
... ... @@ -175,6 +176,7 @@
175 176
176 177 // 停止点播视频
177 178 const stopOnDemandVideo = async () => {
  179 + if (!unref(getGBT)) return;
178 180 const { tbDeviceId, channelId } = props.GBTOption;
179 181 await stopOnDemandVideoApiGet(tbDeviceId, channelId);
180 182 };
... ...
... ... @@ -33,6 +33,8 @@
33 33 import { getVideoControlStart } from '/@/api/device/videoChannel';
34 34 import { VideoChannelItemType } from '/@/api/device/model/videoChannelModel';
35 35 import { getVideoTypeByUrl } from '/@/components/Video';
  36 + import { AccessMode } from '/@/views/camera/manage/config.data';
  37 + import { getStreamingPlayUrl } from '/@/api/camera/cameraManager';
36 38
37 39 const heightNum = ref(800);
38 40 const showVideo = ref(false);
... ... @@ -57,18 +59,27 @@
57 59 const [register, { setModalProps }] = useModalInner(
58 60 async (data: { record: VideoChannelItemType }) => {
59 61 const { record, ifShowGBT = false } = data;
  62 + const { params } = record as Recordable;
60 63 videoId.value = record.id || '';
61 64 isGBT.value = ifShowGBT;
62   - GBTOption.value.tbDeviceId = record.deviceId;
63   - GBTOption.value.channelId = record.channelId;
  65 + GBTOption.value.tbDeviceId = params?.deviceId ? params?.deviceId : record?.deviceId;
  66 + GBTOption.value.channelId = params?.channelNo ? params?.channelNo : record?.channelId;
64 67 try {
65 68 setModalProps({ loading: true, loadingTip: '视频加载中...' });
66   -
67   - const result = await getVideoControlStart({
68   - deviceId: record.deviceId,
69   - channelId: record.channelId,
70   - });
71   - options.sources = [{ src: result.data.flv, type: getVideoTypeByUrl(result.data.flv) }];
  69 + // getStreamingPlayUrl
  70 + let result: any = null;
  71 + if (record.accessMode === AccessMode.GBT28181) {
  72 + result = await getVideoControlStart({
  73 + deviceId: params?.deviceId ? params?.deviceId : record?.deviceId,
  74 + channelId: params?.channelNo ? params?.channelNo : record?.channelId,
  75 + });
  76 + options.sources = [{ src: result.data.flv, type: getVideoTypeByUrl(result.data.flv) }];
  77 + } else {
  78 + result = await getStreamingPlayUrl(record.id);
  79 + options.sources = [
  80 + { src: result.data.url, type: getVideoTypeByUrl(result.data.url), id: record.id },
  81 + ];
  82 + }
72 83 showVideo.value = true;
73 84 } catch (error) {
74 85 } finally {
... ...
... ... @@ -56,7 +56,6 @@
56 56 if (record) {
57 57 await nextTick();
58 58 }
59   -
60 59 const title = `${DataActionModeNameEnum[mode]}物模型`;
61 60 setModalProps({
62 61 showOkBtn: mode !== DataActionModeEnum.READ,
... ...