Commit 951f8bb51b80f69141f1619f2aa84a2f4f50a566

Authored by xp.Huang
2 parents 2cc73ce7 e8c89150

Merge branch 'feat/device-new-protocol' into 'main_dev'

fix: 隐藏GBT28181的页面和逻辑

See merge request yunteng/thingskit-front!986
@@ -119,3 +119,11 @@ export const closeFlvPlay = (url: string, browserId: string) => { @@ -119,3 +119,11 @@ export const closeFlvPlay = (url: string, browserId: string) => {
119 url: `/rtsp/closeFlv?url=${encodeURIComponent(url)}&browserId=${browserId}`, 119 url: `/rtsp/closeFlv?url=${encodeURIComponent(url)}&browserId=${browserId}`,
120 }); 120 });
121 }; 121 };
  122 +
  123 +//云台控制
  124 +export const controlling = (params: any) => {
  125 + return defHttp.get({
  126 + url: `/video/controlling`,
  127 + params,
  128 + });
  129 +};
@@ -78,6 +78,8 @@ @@ -78,6 +78,8 @@
78 </PageWrapper> 78 </PageWrapper>
79 <CameraDrawer @register="registerDrawer" @success="handleSuccess" /> 79 <CameraDrawer @register="registerDrawer" @success="handleSuccess" />
80 <VideoPreviewModal @register="registerModal" /> 80 <VideoPreviewModal @register="registerModal" />
  81 +
  82 + <VideoModal @register="registerModal1" />
81 </div> 83 </div>
82 </template> 84 </template>
83 85
@@ -96,6 +98,7 @@ @@ -96,6 +98,7 @@
96 import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; 98 import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
97 import { Popconfirm } from 'ant-design-vue'; 99 import { Popconfirm } from 'ant-design-vue';
98 import { Tag } from 'ant-design-vue'; 100 import { Tag } from 'ant-design-vue';
  101 + import VideoModal from '/@/views/device/list/cpns/tabs/VideoChannel/videoModal.vue';
99 102
100 export default defineComponent({ 103 export default defineComponent({
101 components: { 104 components: {
@@ -105,6 +108,7 @@ @@ -105,6 +108,7 @@
105 TableAction, 108 TableAction,
106 CameraDrawer, 109 CameraDrawer,
107 VideoPreviewModal, 110 VideoPreviewModal,
  111 + VideoModal,
108 TableImg, 112 TableImg,
109 Authority, 113 Authority,
110 Popconfirm, 114 Popconfirm,
@@ -114,7 +118,9 @@ @@ -114,7 +118,9 @@
114 setup(_, { emit }) { 118 setup(_, { emit }) {
115 const searchInfo = reactive<Recordable>({}); 119 const searchInfo = reactive<Recordable>({});
116 const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); 120 const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
117 - const [registerModal, { openModal }] = useModal(); 121 + const [registerModal, { openModal }] = useModal(); //手动输入
  122 +
  123 + const [registerModal1, { openModal: openModal1 }] = useModal(); //流媒体获取
118 // 表格hooks 124 // 表格hooks
119 const [registerTable, { reload, setProps, clearSelectedRowKeys }] = useTable({ 125 const [registerTable, { reload, setProps, clearSelectedRowKeys }] = useTable({
120 title: '视频列表', 126 title: '视频列表',
@@ -176,7 +182,14 @@ @@ -176,7 +182,14 @@
176 handleSuccess(); 182 handleSuccess();
177 }; 183 };
178 const handleViewVideo = (record) => { 184 const handleViewVideo = (record) => {
179 - openModal(true, { 185 + if (record.accessMode === AccessMode.ManuallyEnter) {
  186 + openModal(true, {
  187 + isUpdate: true,
  188 + record,
  189 + });
  190 + return;
  191 + }
  192 + openModal1(true, {
180 isUpdate: true, 193 isUpdate: true,
181 record, 194 record,
182 }); 195 });
@@ -198,6 +211,7 @@ @@ -198,6 +211,7 @@
198 organizationIdTreeRef, 211 organizationIdTreeRef,
199 handleViewVideo, 212 handleViewVideo,
200 registerModal, 213 registerModal,
  214 + registerModal1,
201 AccessMode, 215 AccessMode,
202 handleSwitchMode, 216 handleSwitchMode,
203 CameraPermission, 217 CameraPermission,
@@ -54,6 +54,9 @@ @@ -54,6 +54,9 @@
54 <TabPane key="task" tab="任务"> 54 <TabPane key="task" tab="任务">
55 <Task :tbDeviceId="deviceDetail.tbDeviceId" /> 55 <Task :tbDeviceId="deviceDetail.tbDeviceId" />
56 </TabPane> 56 </TabPane>
  57 + <!-- <TabPane v-if="false" key="videoChannel" tab="视频通道">
  58 + <VideoChannel :deviceDetail="deviceDetail" :fromId="deviceDetail?.tbDeviceId" />
  59 + </TabPane> -->
57 </Tabs> 60 </Tabs>
58 </BasicDrawer> 61 </BasicDrawer>
59 </template> 62 </template>
@@ -74,6 +77,7 @@ @@ -74,6 +77,7 @@
74 import EventManage from '../tabs/EventManage/index.vue'; 77 import EventManage from '../tabs/EventManage/index.vue';
75 import { DeviceRecord } from '/@/api/device/model/deviceModel'; 78 import { DeviceRecord } from '/@/api/device/model/deviceModel';
76 import Task from '../tabs/Task.vue'; 79 import Task from '../tabs/Task.vue';
  80 + // import { VideoChannel } from '../tabs/VideoChannel/index';
77 81
78 export default defineComponent({ 82 export default defineComponent({
79 name: 'DeviceModal', 83 name: 'DeviceModal',
@@ -91,6 +95,7 @@ @@ -91,6 +95,7 @@
91 CommandRecord, 95 CommandRecord,
92 EventManage, 96 EventManage,
93 Task, 97 Task,
  98 + // VideoChannel,
94 }, 99 },
95 emits: ['reload', 'register', 'openTbDeviceDetail', 'openGatewayDeviceDetail'], 100 emits: ['reload', 'register', 'openTbDeviceDetail', 'openGatewayDeviceDetail'],
96 setup(_props, { emit }) { 101 setup(_props, { emit }) {
@@ -99,9 +104,13 @@ @@ -99,9 +104,13 @@
99 const deviceDetailRef = ref(); 104 const deviceDetailRef = ref();
100 const deviceDetail = ref<DeviceRecord>({} as unknown as DeviceRecord); 105 const deviceDetail = ref<DeviceRecord>({} as unknown as DeviceRecord);
101 const tbDeviceId = ref(''); 106 const tbDeviceId = ref('');
  107 +
  108 + const isTransportType = ref<Boolean>(false); //获取产品是不是GB/T 28181
102 // 详情回显 109 // 详情回显
103 const [register] = useDrawerInner(async (data) => { 110 const [register] = useDrawerInner(async (data) => {
104 - const { id } = data; 111 + const { id, transportType, deviceType } = data || {};
  112 + isTransportType.value =
  113 + transportType == 'GB/T28181' && deviceType == 'DIRECT_CONNECTION' ? true : false;
105 // 设备详情 114 // 设备详情
106 const res = await getDeviceDetail(id); 115 const res = await getDeviceDetail(id);
107 deviceDetail.value = res; 116 deviceDetail.value = res;
@@ -136,6 +145,7 @@ @@ -136,6 +145,7 @@
136 tbDeviceId, 145 tbDeviceId,
137 handleOpenTbDeviceDetail, 146 handleOpenTbDeviceDetail,
138 handleOpenGatewayDevice, 147 handleOpenGatewayDevice,
  148 + isTransportType,
139 drawerTitle, 149 drawerTitle,
140 }; 150 };
141 }, 151 },
  1 +import { h } from 'vue';
  2 +import { BasicColumn, FormSchema } from '/@/components/Table';
  3 +import { Tag } from 'ant-design-vue';
  4 +import { withInstall } from '/@/utils/index';
  5 +import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
  6 +
  7 +import VideoPlay from './video.vue';
  8 +export const Video = withInstall(VideoPlay);
  9 +
  10 +export const configColumns: BasicColumn[] = [
  11 + {
  12 + title: '通道编号',
  13 + dataIndex: 'channellNumber',
  14 + },
  15 + {
  16 + title: '设备名称',
  17 + dataIndex: 'deviceName',
  18 + },
  19 + {
  20 + title: '通道名称',
  21 + dataIndex: 'channelName',
  22 + },
  23 + {
  24 + title: '厂家',
  25 + dataIndex: 'manufacturer',
  26 + },
  27 + {
  28 + title: '开启音频',
  29 + dataIndex: 'turnOnAudio',
  30 + slots: { customRender: 'turnOnAudio' },
  31 + },
  32 + {
  33 + title: '状态',
  34 + dataIndex: 'state',
  35 + format: (text) => {
  36 + return h(
  37 + Tag,
  38 + {
  39 + color: Number(text) === 1 ? 'green' : 'blue',
  40 + },
  41 + () => (Number(text) === 1 ? '在线' : '离线')
  42 + );
  43 + },
  44 + },
  45 + {
  46 + title: '操作',
  47 + dataIndex: 'action',
  48 + slots: { customRender: 'action' },
  49 + },
  50 +];
  51 +
  52 +export const searchFormSchema: FormSchema[] | any = [
  53 + {
  54 + field: 'name',
  55 + label: '设备名称',
  56 + component: 'Input',
  57 + colProps: { span: 6 },
  58 + componentProps: {
  59 + maxLength: 255,
  60 + placeholder: '请输入设备名称',
  61 + },
  62 + },
  63 + {
  64 + field: 'deviceType',
  65 + label: '设备类型',
  66 + component: 'Select',
  67 + colProps: { span: 6 },
  68 + componentProps: {
  69 + options: [
  70 + { label: '网关设备', value: DeviceTypeEnum.GATEWAY },
  71 + { label: '直连设备', value: DeviceTypeEnum.DIRECT_CONNECTION },
  72 + { label: '网关子设备', value: DeviceTypeEnum.SENSOR },
  73 + ],
  74 + placeholder: '请选择设备类型',
  75 + },
  76 + },
  77 + // {
  78 + // field: 'channelName',
  79 + // label: '通道名称',
  80 + // component: 'Input',
  81 + // colProps: { span: 6 },
  82 + // componentProps: {
  83 + // maxLength: 255,
  84 + // placeholder: '请输入通道名称',
  85 + // },
  86 + // },
  87 + {
  88 + field: 'manufacturer',
  89 + label: '厂家',
  90 + component: 'Select',
  91 + colProps: { span: 6 },
  92 + componentProps: {
  93 + options: [{}],
  94 + placeholder: '请选择厂家',
  95 + },
  96 + },
  97 +];
  1 +import VideoChannel from './index.vue';
  2 +export { VideoChannel };
  1 +<template>
  2 + <BasicTable
  3 + class="bg-neutral-100 dark:text-gray-300 dark:bg-dark-700 p-4"
  4 + @register="registerTable"
  5 + >
  6 + <template #turnOnAudio="{ record }">
  7 + <Switch
  8 + :checked="record.status === 1"
  9 + :loading="record.pendingStatus"
  10 + checkedChildren="开启"
  11 + unCheckedChildren="关闭"
  12 + @change="(checked:boolean)=>handleTurnVideo(checked,record)"
  13 + />
  14 + </template>
  15 + <template #action="{ record }">
  16 + <TableAction
  17 + :actions="[
  18 + {
  19 + label: '播放',
  20 + auth: 'api:yt:sceneLinkage:get',
  21 + icon: 'ant-design:play-circle-outlined',
  22 + onClick: handlePlay.bind(null, record),
  23 + },
  24 + ]"
  25 + /></template>
  26 + </BasicTable>
  27 + <VideoModal @register="registerModal" />
  28 +</template>
  29 +
  30 +<script lang="ts" setup>
  31 + import { configColumns, searchFormSchema } from './config';
  32 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  33 + import { Switch } from 'ant-design-vue';
  34 + import { DeviceRecord } from '/@/api/device/model/deviceModel';
  35 + import { watch } from 'vue';
  36 + import VideoModal from './videoModal.vue';
  37 + import { useModal } from '/@/components/Modal';
  38 + import { onMounted } from 'vue';
  39 + import { useMessage } from '/@/hooks/web/useMessage';
  40 +
  41 + const props = defineProps({
  42 + fromId: {
  43 + type: String,
  44 + default: '',
  45 + },
  46 + deviceDetail: {
  47 + type: Object as PropType<DeviceRecord>,
  48 + required: true,
  49 + },
  50 + });
  51 +
  52 + watch(
  53 + () => props,
  54 + () => {
  55 + console.log(props, 'props');
  56 + }
  57 + );
  58 +
  59 + const [registerModal, { openModal }] = useModal();
  60 +
  61 + const [registerTable, { setTableData, setProps, setSelectedRowKeys, reload }] = useTable({
  62 + // api: deviceCommandRecordGetQuery,
  63 + columns: configColumns,
  64 + showTableSetting: true,
  65 + bordered: true,
  66 + showIndexColumn: false,
  67 + formConfig: {
  68 + labelWidth: 120,
  69 + schemas: searchFormSchema,
  70 + },
  71 + beforeFetch: (params) => {
  72 + console.log(params);
  73 + },
  74 + useSearchForm: true,
  75 + });
  76 +
  77 + const handleTurnVideo = async (checked: Boolean, record: Recordable) => {
  78 + console.log(checked, record, 'record');
  79 + setProps({
  80 + loading: true,
  81 + });
  82 + setSelectedRowKeys([]);
  83 + const newStatus = checked ? 1 : 0;
  84 + const { createMessage } = useMessage();
  85 + try {
  86 + if (newStatus) createMessage.success('开启成功');
  87 + else createMessage.success('关闭成功');
  88 + } finally {
  89 + setProps({
  90 + loading: false,
  91 + });
  92 + reload();
  93 + }
  94 + };
  95 +
  96 + const tableList = [
  97 + {
  98 + id: '8b66f4fa-88e0-42b2-be33-60652cd2bda1',
  99 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  100 + createTime: '2023-03-10 17:16:54',
  101 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  102 + updateTime: '2023-04-11 10:25:49',
  103 + name: 'dasd',
  104 + enabled: false,
  105 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  106 + videoUrl: 'ws://192.168.10.134:28080/rtp/62020000492000000002_34020000001320000001.live.flv',
  107 + sn: 's',
  108 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  109 + organizationName: '车车组织',
  110 + status: false,
  111 + accessMode: 0,
  112 + playProtocol: 0,
  113 + channellNumber: 1,
  114 + },
  115 + {
  116 + id: '9ff408ab-980f-470c-a55c-e4e284ddd34d',
  117 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  118 + createTime: '2023-02-22 14:50:13',
  119 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  120 + updateTime: '2023-04-11 10:36:25',
  121 + name: '2',
  122 + enabled: false,
  123 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  124 + videoUrl:
  125 + 'https://vcsplay.scjtonline.cn:8099/live/HD_d80a740b-2672-4ad1-90e4-52eb71d8c2ef.m3u8?auth_key=1681180571-0-0-dfdb334e5f83838ade5f01f61f910107',
  126 + sn: '212',
  127 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  128 + organizationName: '车车组织',
  129 + status: false,
  130 + accessMode: 0,
  131 + playProtocol: 0,
  132 + channellNumber: 1,
  133 + },
  134 + {
  135 + id: 'a6edd8fb-a91a-4a1b-8124-65959206dac4',
  136 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  137 + createTime: '2023-02-21 16:39:51',
  138 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  139 + updateTime: '2023-04-11 10:33:27',
  140 + name: 'dasda',
  141 + enabled: false,
  142 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  143 + videoUrl:
  144 + 'https://vcsplay.scjtonline.cn:8200/live/HD_1b361aa9-5230-48ba-b8be-d1e2c4e9b178.m3u8?auth_key=1681180365-0-0-e77f40e88550091053139f5562d8afa2',
  145 + brand: 'dasdad',
  146 + sn: 'adsad',
  147 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  148 + organizationName: '车车组织',
  149 + status: false,
  150 + accessMode: 0,
  151 + playProtocol: 0,
  152 + channellNumber: 1,
  153 + },
  154 + {
  155 + id: '51b4c0bc-8050-4b37-bdb3-9fe08b14cf86',
  156 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  157 + createTime: '2023-02-21 16:37:44',
  158 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  159 + updateTime: '2023-04-11 10:56:59',
  160 + name: '视频',
  161 + enabled: false,
  162 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  163 + videoUrl:
  164 + 'https://vcsplay.scjtonline.cn:8093/live/HD_c54034ca-4a6d-11eb-8edc-3cd2e55e088c.m3u8?auth_key=1681181808-0-0-d756d9b0426b71482e45b9377958e4dc',
  165 + brand: '视频厂家',
  166 + sn: 'DART2143SAD12RE',
  167 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  168 + organizationName: '车车组织',
  169 + status: false,
  170 + accessMode: 0,
  171 + playProtocol: 0,
  172 + channellNumber: 1,
  173 + },
  174 + {
  175 + id: '8c0e6ed0-3176-4e76-bd8e-92f722f61e79',
  176 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  177 + createTime: '2022-12-01 15:55:54',
  178 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  179 + updateTime: '2023-04-11 10:59:22',
  180 + name: '视频',
  181 + enabled: false,
  182 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  183 + avatar: 'https://demo.thingskit.com:9000/yunteng/itbbqOBXIUzWvjq.jpeg',
  184 + videoUrl: 'http://113.204.115.250:83/openUrl/iFzoWME/live.m3u8',
  185 + brand: '厂家',
  186 + sn: 'JDKSA11321',
  187 + organizationId: '9196fd9a-624a-4891-a8b3-ce588baedf9f',
  188 + organizationName: '丰田',
  189 + status: false,
  190 + channellNumber: 1,
  191 + accessMode: 0,
  192 + playProtocol: 0,
  193 + },
  194 + {
  195 + id: 'c30422a7-9498-49a5-96d5-e77f3a2823e3',
  196 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  197 + createTime: '2022-12-01 14:42:52',
  198 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  199 + updateTime: '2023-03-02 17:12:53',
  200 + name: '湖南卫视',
  201 + enabled: false,
  202 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  203 + videoUrl:
  204 + 'http://219.151.31.38/liveplay-kk.rtxapp.com/live/program/live/hnwshd/4000000/mnf.m3u8',
  205 + brand: 'BUZHIDAO',
  206 + sn: 'QKDK123456',
  207 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  208 + organizationName: '车车组织',
  209 + status: false,
  210 + channellNumber: 1,
  211 + accessMode: 0,
  212 + playProtocol: 0,
  213 + },
  214 + {
  215 + id: 'e3253015-b8df-4e8f-ad0c-f634d3fd927e',
  216 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  217 + createTime: '2022-11-29 11:19:00',
  218 + name: '玩具视频播放',
  219 + enabled: false,
  220 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  221 + avatar: 'http://nc.tianzow.com:29000/yunteng/tgygMQmYaaPDPdG.jpg',
  222 + videoUrl: 'https://ask.dcloud.net.cn/topic.m3u8',
  223 + brand: '成都厂家',
  224 + sn: 'TCL1528GOP',
  225 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  226 + organizationName: '车车组织',
  227 + status: false,
  228 + accessMode: 0,
  229 + channellNumber: 1,
  230 + playProtocol: 0,
  231 + },
  232 + {
  233 + id: '9edc9f73-3a9a-4e4b-9b4c-b955329b99b3',
  234 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  235 + createTime: '2022-11-29 11:13:44',
  236 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  237 + updateTime: '2023-01-11 10:03:18',
  238 + name: '视频',
  239 + enabled: false,
  240 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  241 + sn: '7aeb4061f3cf4ba384ed8e1a3027d2a8',
  242 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  243 + organizationName: '车车组织',
  244 + status: false,
  245 + accessMode: 1,
  246 + videoPlatformId: '8cc7c20e-693b-48ea-9124-994e0908c83e',
  247 + streamType: 0,
  248 + playProtocol: 0,
  249 + channellNumber: 1,
  250 + videoPlatformDTO: {
  251 + enabled: false,
  252 + host: '113.204.115.250:7120',
  253 + appKey: '28238690',
  254 + appSecret: 'F3e3Ffvo9Wyg9jkl8BUS',
  255 + ssl: 1,
  256 + },
  257 + },
  258 + {
  259 + id: '9d6675ad-07ed-45ae-bb4a-457000e164f8',
  260 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  261 + createTime: '2022-11-21 11:49:59',
  262 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  263 + updateTime: '2023-04-11 10:53:27',
  264 + name: '新视频',
  265 + enabled: false,
  266 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  267 + videoUrl: 'http://www.w3school.com.cn/example/html5/mov_bbb.mp4',
  268 + brand: '1111',
  269 + sn: 'XYZ',
  270 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  271 + organizationName: '车车组织',
  272 + status: false,
  273 + accessMode: 0,
  274 + channellNumber: 1,
  275 + playProtocol: 0,
  276 + },
  277 + ];
  278 +
  279 + onMounted(() => {
  280 + setTableData(tableList);
  281 + });
  282 +
  283 + const handlePlay = (record: Recordable) => {
  284 + console.log(record);
  285 + openModal(true, {
  286 + record,
  287 + });
  288 + };
  289 +</script>
  1 +<script lang="ts" setup>
  2 + import { isNumber } from 'lodash';
  3 + import videoJs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js';
  4 + import 'video.js/dist/video-js.css';
  5 + import { computed, CSSProperties, onMounted, onUnmounted, ref, unref } from 'vue';
  6 + import { useDesign } from '/@/hooks/web/useDesign';
  7 + import { getJwtToken, getShareJwtToken } from '/@/utils/auth';
  8 + import { isShareMode } from '/@/views/sys/share/hook';
  9 + import 'videojs-flvjs-es6';
  10 + import {
  11 + CaretUpOutlined,
  12 + CaretRightOutlined,
  13 + PauseOutlined,
  14 + CaretDownOutlined,
  15 + CaretLeftOutlined,
  16 + ZoomInOutlined,
  17 + ZoomOutOutlined,
  18 + } from '@ant-design/icons-vue';
  19 + import { Button } from 'ant-design-vue';
  20 + import { nextTick } from 'vue';
  21 + import { controlling } from '/@/api/camera/cameraManager';
  22 +
  23 + const { prefixCls } = useDesign('basic-video-play');
  24 +
  25 + const props = defineProps<{
  26 + options?: VideoJsPlayerOptions;
  27 + withToken?: boolean;
  28 + }>();
  29 +
  30 + const emit = defineEmits<{
  31 + (event: 'ready', instance?: Nullable<VideoJsPlayer>): void;
  32 + (event: 'onUnmounted'): void;
  33 + }>();
  34 +
  35 + const videoPlayEl = ref<HTMLVideoElement>();
  36 +
  37 + const videoPlayInstance = ref<Nullable<VideoJsPlayer>>();
  38 +
  39 + const getOptions = computed(() => {
  40 + const { options, withToken } = props;
  41 +
  42 + const defaultOptions: VideoJsPlayerOptions & Recordable = {
  43 + language: 'zh',
  44 + muted: true,
  45 + liveui: true,
  46 + controls: true,
  47 + techOrder: ['html5', 'flvjs'],
  48 + flvjs: {
  49 + mediaDataSource: {
  50 + isLive: true,
  51 + cors: true,
  52 + hasAudio: false,
  53 + withCredentials: false,
  54 + },
  55 + config: {
  56 + headers: {
  57 + ...(withToken
  58 + ? {
  59 + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`,
  60 + }
  61 + : {}),
  62 + },
  63 + autoCleanupSourceBuffer: true,
  64 + },
  65 + },
  66 + };
  67 + return videoJs.mergeOptions(defaultOptions, options);
  68 + });
  69 +
  70 + const getWidthHeight = computed(() => {
  71 + let { width = 300, height = 150 } = unref(getOptions);
  72 + width = isNumber(width) ? (`${width}px` as unknown as number) : width;
  73 + height = isNumber(height) ? (`${height}px` as unknown as number) : height;
  74 + return { width, height } as CSSProperties;
  75 + });
  76 +
  77 + const init = () => {
  78 + if (unref(videoPlayInstance)) unref(videoPlayInstance)?.dispose();
  79 + videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => {
  80 + emit('ready', unref(videoPlayInstance));
  81 + });
  82 + };
  83 +
  84 + //播放/暂停
  85 + const handleClick = () => {
  86 + unref(isPlay) && unref(videoPlayInstance)?.pause();
  87 + !unref(isPlay) && unref(videoPlayInstance)?.play();
  88 + };
  89 +
  90 + const getId = () => {
  91 + const { options } = props || {};
  92 + const { sources }: any = options || {};
  93 + return sources?.[0]?.id;
  94 + };
  95 +
  96 + const handleControl = (action: number, direction: string) => {
  97 + const organizationId = getId();
  98 + controlling({ cameralndexCode: organizationId, action, command: direction });
  99 + };
  100 +
  101 + const isPlay = ref<Boolean | null | undefined>(false);
  102 +
  103 + onMounted(async () => {
  104 + init();
  105 + await nextTick();
  106 + // isPlay.value = unref(videoPlayInstance)?.paused();
  107 + videoPlayInstance.value?.on('loadedmetadata', () => {});
  108 + videoPlayInstance.value?.on('waiting', () => {
  109 + isPlay.value = false;
  110 + });
  111 + videoPlayInstance.value?.on('play', () => {
  112 + isPlay.value = true;
  113 + });
  114 + videoPlayInstance.value?.on('playing', () => {
  115 + isPlay.value = true;
  116 + });
  117 + videoPlayInstance.value?.on('pause', () => {
  118 + isPlay.value = false;
  119 + });
  120 + videoPlayInstance.value?.on('ended', () => {
  121 + isPlay.value = false;
  122 + });
  123 + });
  124 +
  125 + //长按开始
  126 + const moveStart = (action) => {
  127 + handleControl(0, action);
  128 + };
  129 + // 长按结束
  130 + const moveStop = (action) => {
  131 + handleControl(1, action);
  132 + };
  133 +
  134 + onUnmounted(() => {
  135 + unref(videoPlayInstance)?.dispose();
  136 + videoPlayInstance.value = null;
  137 + emit('onUnmounted');
  138 + });
  139 +
  140 + defineExpose({
  141 + reloadPlayer: init,
  142 + getInstance: () => unref(videoPlayInstance),
  143 + });
  144 +</script>
  145 +
  146 +<template>
  147 + <div :class="prefixCls" class="!w-full h-full flex" :style="getWidthHeight">
  148 + <video
  149 + ref="videoPlayEl"
  150 + class="video-js vjs-big-play-centered vjs-show-big-play-button-on-pause !w-8/10 !h-full"
  151 + muted
  152 + >
  153 + </video>
  154 +
  155 + <div class="!w-2/10 bg-white flex items-center flex-col">
  156 + <h1>云台控制</h1>
  157 +
  158 + <div class="home mt-5">
  159 + <Button class="front-sty-center child center" shape="circle" @click="handleClick()">
  160 + <PauseOutlined v-if="isPlay" class="child-icon" style="color: #fffbfb" />
  161 + <CaretRightOutlined v-else class="child-icon" style="color: #fffbfb" />
  162 + </Button>
  163 +
  164 + <div class="box">
  165 + <div>
  166 + <Button
  167 + class="left-top in-block"
  168 + @mousedown="moveStart('up')"
  169 + @mouseup="moveStop('up')"
  170 + >
  171 + <CaretUpOutlined class="icon-rotate child-icon" />
  172 + </Button>
  173 + <Button
  174 + class="right-top in-block"
  175 + @mousedown="moveStart('RIGHT')"
  176 + @mouseup="moveStop('RIGHT')"
  177 + >
  178 + <CaretRightOutlined class="icon-rotate child-icon" />
  179 + </Button>
  180 + </div>
  181 + <div>
  182 + <Button
  183 + class="left-bottom in-block"
  184 + @mousedown="moveStart('LEFT')"
  185 + @mouseup="moveStop('LEFT')"
  186 + >
  187 + <CaretLeftOutlined class="icon-rotate child-icon" />
  188 + </Button>
  189 + <Button
  190 + class="right-bottom in-block"
  191 + @mousedown="moveStart('DOWN')"
  192 + @mouseup="moveStop('DOWN')"
  193 + >
  194 + <CaretDownOutlined class="icon-rotate child-icon" />
  195 + </Button>
  196 + </div>
  197 +
  198 + <Button class="circle" @click="handleClick" />
  199 + </div>
  200 + </div>
  201 + <div class="flex justify-center mt-8">
  202 + <Button
  203 + class="button-icon"
  204 + @mousedown="moveStart('ZOOM_IN')"
  205 + @mouseup="moveStop('ZOOM_IN')"
  206 + style="border-radius: :50%;"
  207 + >
  208 + <ZoomInOutlined style="color: #315a9c; font-size: 1.5rem" />
  209 + </Button>
  210 + <Button
  211 + class="ml-10 button-icon"
  212 + @mousedown="moveStart('ZOOM_OUT')"
  213 + @mouseup="moveStop('ZOOM_OUT')"
  214 + style="border-radius: :50%;"
  215 + >
  216 + <ZoomOutOutlined style="color: #315a9c; font-size: 1.5rem" />
  217 + </Button>
  218 + </div>
  219 + </div>
  220 + </div>
  221 +</template>
  222 +
  223 +<style lang="less" scoped>
  224 + @prefix-cls: ~'@{namespace}-basic-video-play';
  225 +
  226 + .@{prefix-cls} {
  227 + .vjs-error-display {
  228 + .vjs-modal-dialog-content::after {
  229 + content: '无法加载视频,原因可能是服务器或网络故障,也可能是格式不支持.';
  230 + }
  231 + }
  232 + }
  233 +
  234 + .child {
  235 + position: absolute;
  236 + width: 3rem;
  237 + height: 3rem;
  238 + display: flex;
  239 + justify-content: center;
  240 + background: #e2dede;
  241 + align-items: center;
  242 + border: none;
  243 + }
  244 +
  245 + .child-icon {
  246 + font-size: 1.5rem;
  247 + color: #fffbfb;
  248 + }
  249 +
  250 + .button-icon {
  251 + width: 3rem;
  252 + height: 3rem;
  253 + background: #f5f5f5;
  254 + border: none;
  255 + border-radius: 50% !important;
  256 + }
  257 +
  258 + .center {
  259 + top: 50%;
  260 + left: 50%;
  261 + width: 4rem;
  262 + height: 4rem;
  263 + transform: translate(-50%, -50%);
  264 + border-radius: 50%;
  265 + background: #5586d4;
  266 + }
  267 +
  268 + .home {
  269 + position: relative;
  270 + width: 10rem;
  271 + height: 10rem;
  272 + }
  273 +
  274 + .box {
  275 + transform: rotateZ(45deg);
  276 + width: 10rem;
  277 + height: 10rem;
  278 + }
  279 +
  280 + .icon-rotate {
  281 + transform: rotate(315deg);
  282 + }
  283 +
  284 + .front-sty-center {
  285 + position: absolute;
  286 + top: 50%;
  287 + z-index: 9999;
  288 + left: 50%;
  289 + transform: translate(-50%, -50%);
  290 + }
  291 +
  292 + .circle {
  293 + display: inline-block;
  294 + border-radius: 50%;
  295 + background-color: #5586d4;
  296 + width: 4rem;
  297 + height: 4rem;
  298 + position: absolute;
  299 + top: 50%;
  300 + left: 50%;
  301 + transform: translate(-50%, -50%);
  302 + }
  303 +
  304 + .in-block {
  305 + display: inline-block;
  306 + position: relative;
  307 + }
  308 +
  309 + .left-top {
  310 + width: 5rem;
  311 + height: 5rem;
  312 + border-radius: 5rem 0 0 0;
  313 + background-color: #e2dede;
  314 + }
  315 +
  316 + .right-top {
  317 + width: 5rem;
  318 + height: 5rem;
  319 + border-radius: 0 5rem 0 0;
  320 + background-color: #e2dede;
  321 + }
  322 +
  323 + .left-bottom {
  324 + width: 5rem;
  325 + height: 5rem;
  326 + border-radius: 0 0 0 5rem;
  327 + background-color: #e2dede;
  328 + }
  329 +
  330 + .right-bottom {
  331 + width: 5rem;
  332 + height: 5rem;
  333 + border-radius: 0 0 5rem 0;
  334 + background-color: #e2dede;
  335 + }
  336 +</style>
  1 +<template>
  2 + <div>
  3 + <BasicModal
  4 + v-bind="$attrs"
  5 + width="60rem"
  6 + destroyOnClose
  7 + :height="heightNum"
  8 + @register="register"
  9 + title="视频预览"
  10 + :showOkBtn="false"
  11 + @cancel="handleCancel"
  12 + >
  13 + <div class="flex items-center justify-center w-full h-full min-h-96 video-container">
  14 + <Video
  15 + v-if="showVideo"
  16 + :options="(options as any)"
  17 + :withToken="withToken"
  18 + @on-unmounted="handleCloseFlvPlayUrl"
  19 + />
  20 + </div>
  21 + </BasicModal>
  22 + </div>
  23 +</template>
  24 +<script setup lang="ts">
  25 + import { ref, reactive, unref } from 'vue';
  26 + import { BasicModal, useModalInner } from '/@/components/Modal';
  27 + import type { StreamingManageRecord, CameraModel } from '/@/api/camera/model/cameraModel';
  28 + import { getVideoTypeByUrl } from '/@/components/Video';
  29 + import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '/@/api/camera/cameraManager';
  30 + import { isRtspProtocol } from '/@/components/Video/src/utils';
  31 + import { VideoJsPlayerOptions } from 'video.js';
  32 + import { useFingerprint } from '/@/utils/useFingerprint';
  33 + import { GetResult } from '@fingerprintjs/fingerprintjs';
  34 + import { AccessMode } from '/@/views/camera/manage/config.data';
  35 + import { Video } from './config';
  36 +
  37 + const heightNum = ref(800);
  38 + const showVideo = ref(false);
  39 +
  40 + const playUrl = ref('');
  41 +
  42 + const withToken = ref(false);
  43 +
  44 + const fingerprintResult = ref<Nullable<GetResult>>(null);
  45 +
  46 + const options = reactive<VideoJsPlayerOptions>({
  47 + width: '100%' as unknown as number,
  48 + height: 384 as unknown as number,
  49 + autoplay: true,
  50 + });
  51 +
  52 + const setSources = (url: string, fingerprintResult: GetResult, id) => {
  53 + const flag = isRtspProtocol(url);
  54 + options.sources = [
  55 + {
  56 + src: flag ? getFlvPlayUrl(url, fingerprintResult.visitorId) : url,
  57 + type: getVideoTypeByUrl(url),
  58 + id,
  59 + },
  60 + ];
  61 + };
  62 +
  63 + const { getResult } = useFingerprint();
  64 + const [register] = useModalInner(
  65 + async (data: { record: CameraModel | StreamingManageRecord }) => {
  66 + const { record } = data;
  67 + const result = await getResult();
  68 + fingerprintResult.value = result;
  69 + if (record.accessMode === AccessMode.ManuallyEnter) {
  70 + if ((record as CameraModel).videoUrl) {
  71 + if (isRtspProtocol((record as CameraModel).videoUrl)) {
  72 + playUrl.value = (record as CameraModel).videoUrl;
  73 + closeFlvPlay(unref(playUrl), result.visitorId);
  74 + withToken.value = true;
  75 + }
  76 + setSources((record as CameraModel).videoUrl, result, record.id);
  77 + }
  78 + } else {
  79 + try {
  80 + const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!);
  81 + setSources(url, result, record.id);
  82 + } catch (error) {}
  83 + }
  84 + showVideo.value = true;
  85 + }
  86 + );
  87 +
  88 + const handleCloseFlvPlayUrl = () => {
  89 + if (isRtspProtocol(unref(playUrl))) {
  90 + closeFlvPlay(unref(playUrl)!, unref(fingerprintResult)!.visitorId!);
  91 + }
  92 + };
  93 +
  94 + const handleCancel = () => {
  95 + showVideo.value = false;
  96 + withToken.value = false;
  97 + };
  98 +</script>
  99 +
  100 +<style lang="less" scoped>
  101 + .video-container:deep(.vben-basic-video-play) {
  102 + min-height: 13rem;
  103 + }
  104 +
  105 + .video-container:deep(.video-js) {
  106 + min-height: 13rem;
  107 + }
  108 +</style>
@@ -372,10 +372,13 @@ @@ -372,10 +372,13 @@
372 } 372 }
373 373
374 function handleDetail(record: Recordable) { 374 function handleDetail(record: Recordable) {
375 - const { id, tbDeviceId } = record; 375 + const { id, tbDeviceId, deviceProfile, deviceType } = record;
  376 + const { transportType } = deviceProfile || {};
376 openDrawer(true, { 377 openDrawer(true, {
377 id, 378 id,
378 tbDeviceId, 379 tbDeviceId,
  380 + transportType,
  381 + deviceType,
379 }); 382 });
380 } 383 }
381 384
@@ -145,6 +145,7 @@ @@ -145,6 +145,7 @@
145 } 145 }
146 }; 146 };
147 const handleStepNext = (e, data) => { 147 const handleStepNext = (e, data) => {
  148 + // const { deviceType } = unref(DevConStRef)?.getFieldsValue() || {};
148 if (e) { 149 if (e) {
149 current.value++; 150 current.value++;
150 unref(isUpdate) 151 unref(isUpdate)
@@ -15,19 +15,20 @@ @@ -15,19 +15,20 @@
15 ifShowBtn: { type: Boolean, default: true }, 15 ifShowBtn: { type: Boolean, default: true },
16 }); 16 });
17 17
18 - const [register, { validate, setFieldsValue, resetFields, updateSchema }] = useForm({  
19 - labelWidth: 100,  
20 - schemas: step1Schemas,  
21 - actionColOptions: {  
22 - span: 14,  
23 - },  
24 - showResetButton: false,  
25 - showActionButtonGroup: props.ifShowBtn ? true : false,  
26 - submitButtonOptions: {  
27 - text: '下一步',  
28 - },  
29 - submitFunc: customSubmitFunc,  
30 - }); 18 + const [register, { validate, setFieldsValue, resetFields, updateSchema, getFieldsValue }] =
  19 + useForm({
  20 + labelWidth: 100,
  21 + schemas: step1Schemas,
  22 + actionColOptions: {
  23 + span: 14,
  24 + },
  25 + showResetButton: false,
  26 + showActionButtonGroup: props.ifShowBtn ? true : false,
  27 + submitButtonOptions: {
  28 + text: '下一步',
  29 + },
  30 + submitFunc: customSubmitFunc,
  31 + });
31 const editOrAddNameStatus = (nameStatus) => 32 const editOrAddNameStatus = (nameStatus) =>
32 updateSchema({ 33 updateSchema({
33 field: 'name', 34 field: 'name',
@@ -87,6 +88,7 @@ @@ -87,6 +88,7 @@
87 resetFormData, 88 resetFormData,
88 getFormData, 89 getFormData,
89 editOrAddDeviceTypeStatus, 90 editOrAddDeviceTypeStatus,
  91 + getFieldsValue,
90 setFieldsdefaultRuleChainId, 92 setFieldsdefaultRuleChainId,
91 }); 93 });
92 </script> 94 </script>
1 <template> 1 <template>
2 <div 2 <div
3 class="step2-style" 3 class="step2-style"
4 - :style="[isMqttType == 'DEFAULT' ? { minHeight: 0 + 'px' } : { minHeight: 800 + 'px' }]" 4 + :style="[
  5 + isMqttType == 'DEFAULT' || isMqttType == 'GB/T28181'
  6 + ? { minHeight: 0 + 'px' }
  7 + : { minHeight: 800 + 'px' },
  8 + ]"
5 > 9 >
6 <div 10 <div
7 :style="[ 11 :style="[
@@ -138,6 +142,7 @@ @@ -138,6 +142,7 @@
138 const getSnmpVal = await snmpRef.value?.getFormData(); 142 const getSnmpVal = await snmpRef.value?.getFormData();
139 const getTcpVal = await tcpRef.value?.getFormData(); 143 const getTcpVal = await tcpRef.value?.getFormData();
140 step2Data.transportConfiguration = { 144 step2Data.transportConfiguration = {
  145 + // type: isMqttType.value,
141 ...getMqttVal, 146 ...getMqttVal,
142 ...getCoapVal, 147 ...getCoapVal,
143 ...getLwm2mVal, 148 ...getLwm2mVal,
@@ -149,18 +154,25 @@ @@ -149,18 +154,25 @@
149 }; 154 };
150 155
151 const editOrAddTransportTypeStatus = (status: boolean) => { 156 const editOrAddTransportTypeStatus = (status: boolean) => {
  157 + const options = [
  158 + { label: '默认', value: 'DEFAULT' },
  159 + { label: 'MQTT', value: 'MQTT' },
  160 + { label: 'CoAP', value: 'COAP' },
  161 + // { label: 'LWM2M', value: 'LWM2M' },
  162 + // { label: 'SNMP', value: 'SNMP' },
  163 + { label: 'TCP/UDP', value: 'TCP' },
  164 + ];
  165 + // if (deviceType == 'DIRECT_CONNECTION') {
  166 + // options.push({ label: 'GB/T 28181', value: 'GB/T28181' });//暂时隐藏 GBT 28181写完放出来
  167 + // }
  168 + // if (deviceType != 'DIRECT_CONNECTION' && isMqttType.value == 'GB/T28181') {
  169 + // setFieldsValue({ transportType: null });
  170 + // }
152 updateSchema({ 171 updateSchema({
153 field: 'transportType', 172 field: 'transportType',
154 componentProps: { 173 componentProps: {
155 disabled: status, 174 disabled: status,
156 - options: [  
157 - { label: '默认', value: 'DEFAULT' },  
158 - { label: 'MQTT', value: 'MQTT' },  
159 - { label: 'CoAP', value: 'COAP' },  
160 - // { label: 'LWM2M', value: 'LWM2M' },  
161 - // { label: 'SNMP', value: 'SNMP' },  
162 - { label: 'TCP/UDP', value: 'TCP' },  
163 - ], 175 + options,
164 onChange(e) { 176 onChange(e) {
165 isMqttType.value = e; 177 isMqttType.value = e;
166 }, 178 },
@@ -78,6 +78,6 @@ export const getSendValues = async (option, getDesign, checked) => { @@ -78,6 +78,6 @@ export const getSendValues = async (option, getDesign, checked) => {
78 78
79 export const CommandTypeEnumLIst = { 79 export const CommandTypeEnumLIst = {
80 '0': { CUSTOM: 0, name: '自定义' }, 80 '0': { CUSTOM: 0, name: '自定义' },
81 - '1': { CUSTOM: 0, name: '服务' },  
82 - '2': { CUSTOM: 0, name: '属性' }, 81 + '1': { CUSTOM: 1, name: '服务' },
  82 + '2': { CUSTOM: 2, name: '属性' },
83 }; 83 };