Commit 551858c395b7e3051d5125d92b27c102031735cb
Merge branch 'feat/video-component-support-rtsp-protocol' into 'main_dev'
feat: 实现视频组件rtsp协议播放 See merge request yunteng/thingskit-front!649
Showing
10 changed files
with
1809 additions
and
34 deletions
@@ -9,15 +9,21 @@ | @@ -9,15 +9,21 @@ | ||
9 | "COAP", | 9 | "COAP", |
10 | "echarts", | 10 | "echarts", |
11 | "edrx", | 11 | "edrx", |
12 | - "EFENTO", | 12 | + "EFENTO", |
13 | + "fingerprintjs", | ||
14 | + "flvjs", | ||
15 | + "flvjs", | ||
13 | "inited", | 16 | "inited", |
17 | + "liveui", | ||
14 | "MQTT", | 18 | "MQTT", |
15 | "notif", | 19 | "notif", |
16 | "PROTOBUF", | 20 | "PROTOBUF", |
21 | + "rtsp", | ||
17 | "SCADA", | 22 | "SCADA", |
18 | "SNMP", | 23 | "SNMP", |
19 | "unref", | 24 | "unref", |
20 | "vben", | 25 | "vben", |
26 | + "videojs", | ||
21 | "VITE", | 27 | "VITE", |
22 | "vnode", | 28 | "vnode", |
23 | "vueuse", | 29 | "vueuse", |
@@ -35,6 +35,7 @@ | @@ -35,6 +35,7 @@ | ||
35 | "gen:iconfont": "esno ./build/generate/iconfont/index.ts" | 35 | "gen:iconfont": "esno ./build/generate/iconfont/index.ts" |
36 | }, | 36 | }, |
37 | "dependencies": { | 37 | "dependencies": { |
38 | + "@fingerprintjs/fingerprintjs": "^3.4.1", | ||
38 | "@iconify/iconify": "^2.0.3", | 39 | "@iconify/iconify": "^2.0.3", |
39 | "@logicflow/core": "^0.6.9", | 40 | "@logicflow/core": "^0.6.9", |
40 | "@logicflow/extension": "^0.6.9", | 41 | "@logicflow/extension": "^0.6.9", |
@@ -49,6 +50,7 @@ | @@ -49,6 +50,7 @@ | ||
49 | "cropperjs": "^1.5.12", | 50 | "cropperjs": "^1.5.12", |
50 | "crypto-js": "^4.1.1", | 51 | "crypto-js": "^4.1.1", |
51 | "echarts": "^5.1.2", | 52 | "echarts": "^5.1.2", |
53 | + "flv.js": "^1.6.2", | ||
52 | "hls.js": "^1.0.10", | 54 | "hls.js": "^1.0.10", |
53 | "intro.js": "^4.1.0", | 55 | "intro.js": "^4.1.0", |
54 | "jsoneditor": "^9.7.2", | 56 | "jsoneditor": "^9.7.2", |
@@ -65,6 +67,7 @@ | @@ -65,6 +67,7 @@ | ||
65 | "tinymce": "^5.8.2", | 67 | "tinymce": "^5.8.2", |
66 | "vditor": "^3.8.6", | 68 | "vditor": "^3.8.6", |
67 | "video.js": "^7.20.3", | 69 | "video.js": "^7.20.3", |
70 | + "videojs-flvjs-es6": "^1.0.1", | ||
68 | "vue": "3.2.31", | 71 | "vue": "3.2.31", |
69 | "vue-i18n": "9.1.7", | 72 | "vue-i18n": "9.1.7", |
70 | "vue-json-pretty": "^2.0.4", | 73 | "vue-json-pretty": "^2.0.4", |
@@ -109,3 +109,13 @@ export const getStreamingPlayUrl = (entityId: string) => { | @@ -109,3 +109,13 @@ export const getStreamingPlayUrl = (entityId: string) => { | ||
109 | url: `${CameraManagerApi.STREAMING_PLAY_GET_URL}/${entityId}`, | 109 | url: `${CameraManagerApi.STREAMING_PLAY_GET_URL}/${entityId}`, |
110 | }); | 110 | }); |
111 | }; | 111 | }; |
112 | + | ||
113 | +export const getFlvPlayUrl = (url: string, browserId: string) => { | ||
114 | + return `/api/yt/rtsp/openFlv?url=${encodeURIComponent(url)}&browserId=${browserId}`; | ||
115 | +}; | ||
116 | + | ||
117 | +export const closeFlvPlay = (url: string, browserId: string) => { | ||
118 | + return defHttp.get({ | ||
119 | + url: `/rtsp/closeFlv?url=${encodeURIComponent(url)}&browserId=${browserId}`, | ||
120 | + }); | ||
121 | +}; |
@@ -4,15 +4,19 @@ | @@ -4,15 +4,19 @@ | ||
4 | import 'video.js/dist/video-js.css'; | 4 | import 'video.js/dist/video-js.css'; |
5 | import { computed, CSSProperties, onMounted, onUnmounted, ref, unref } from 'vue'; | 5 | import { computed, CSSProperties, onMounted, onUnmounted, ref, unref } from 'vue'; |
6 | import { useDesign } from '/@/hooks/web/useDesign'; | 6 | import { useDesign } from '/@/hooks/web/useDesign'; |
7 | - | 7 | + import { getJwtToken, getShareJwtToken } from '/@/utils/auth'; |
8 | + import { isShareMode } from '/@/views/sys/share/hook'; | ||
9 | + import 'videojs-flvjs-es6'; | ||
8 | const { prefixCls } = useDesign('basic-video-play'); | 10 | const { prefixCls } = useDesign('basic-video-play'); |
9 | 11 | ||
10 | const props = defineProps<{ | 12 | const props = defineProps<{ |
11 | options?: VideoJsPlayerOptions; | 13 | options?: VideoJsPlayerOptions; |
14 | + withToken?: boolean; | ||
12 | }>(); | 15 | }>(); |
13 | 16 | ||
14 | const emit = defineEmits<{ | 17 | const emit = defineEmits<{ |
15 | (event: 'ready', instance?: Nullable<VideoJsPlayer>): void; | 18 | (event: 'ready', instance?: Nullable<VideoJsPlayer>): void; |
19 | + (event: 'onUnmounted'): void; | ||
16 | }>(); | 20 | }>(); |
17 | 21 | ||
18 | const videoPlayEl = ref<HTMLVideoElement>(); | 22 | const videoPlayEl = ref<HTMLVideoElement>(); |
@@ -20,13 +24,34 @@ | @@ -20,13 +24,34 @@ | ||
20 | const videoPlayInstance = ref<Nullable<VideoJsPlayer>>(); | 24 | const videoPlayInstance = ref<Nullable<VideoJsPlayer>>(); |
21 | 25 | ||
22 | const getOptions = computed(() => { | 26 | const getOptions = computed(() => { |
23 | - const { options } = props; | ||
24 | - const defaultOptions: VideoJsPlayerOptions = { | 27 | + const { options, withToken } = props; |
28 | + const defaultOptions: VideoJsPlayerOptions & Recordable = { | ||
25 | language: 'zh', | 29 | language: 'zh', |
26 | muted: true, | 30 | muted: true, |
27 | liveui: true, | 31 | liveui: true, |
28 | controls: true, | 32 | controls: true, |
33 | + techOrder: ['html5', 'flvjs'], | ||
34 | + flvjs: { | ||
35 | + mediaDataSource: { | ||
36 | + isLive: true, | ||
37 | + cors: true, | ||
38 | + hasAudio: false, | ||
39 | + withCredentials: false, | ||
40 | + autoCleanupSourceBuffer: true, | ||
41 | + autoCleanupMaxBackwardDuration: 60, | ||
42 | + }, | ||
43 | + config: { | ||
44 | + headers: { | ||
45 | + ...(withToken | ||
46 | + ? { | ||
47 | + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | ||
48 | + } | ||
49 | + : {}), | ||
50 | + }, | ||
51 | + }, | ||
52 | + }, | ||
29 | }; | 53 | }; |
54 | + | ||
30 | return { ...defaultOptions, ...options }; | 55 | return { ...defaultOptions, ...options }; |
31 | }); | 56 | }); |
32 | 57 | ||
@@ -50,6 +75,7 @@ | @@ -50,6 +75,7 @@ | ||
50 | onUnmounted(() => { | 75 | onUnmounted(() => { |
51 | unref(videoPlayInstance)?.dispose(); | 76 | unref(videoPlayInstance)?.dispose(); |
52 | videoPlayInstance.value = null; | 77 | videoPlayInstance.value = null; |
78 | + emit('onUnmounted'); | ||
53 | }); | 79 | }); |
54 | </script> | 80 | </script> |
55 | 81 | ||
@@ -58,7 +84,9 @@ | @@ -58,7 +84,9 @@ | ||
58 | <video | 84 | <video |
59 | ref="videoPlayEl" | 85 | ref="videoPlayEl" |
60 | class="video-js vjs-big-play-centered vjs-show-big-play-button-on-pause !w-full !h-full" | 86 | class="video-js vjs-big-play-centered vjs-show-big-play-button-on-pause !w-full !h-full" |
61 | - ></video> | 87 | + muted |
88 | + > | ||
89 | + </video> | ||
62 | </div> | 90 | </div> |
63 | </template> | 91 | </template> |
64 | 92 |
@@ -2,17 +2,26 @@ export enum VideoPlayerType { | @@ -2,17 +2,26 @@ export enum VideoPlayerType { | ||
2 | m3u8 = 'application/x-mpegURL', | 2 | m3u8 = 'application/x-mpegURL', |
3 | mp4 = 'video/mp4', | 3 | mp4 = 'video/mp4', |
4 | webm = 'video/webm', | 4 | webm = 'video/webm', |
5 | + flv = 'video/x-flv', | ||
5 | } | 6 | } |
6 | 7 | ||
8 | +export const isRtspProtocol = (url: string) => { | ||
9 | + const reg = /^rtsp:\/\//g; | ||
10 | + return reg.test(url); | ||
11 | +}; | ||
12 | + | ||
7 | export const getVideoTypeByUrl = (url: string) => { | 13 | export const getVideoTypeByUrl = (url: string) => { |
8 | - const splitExtReg = /(?:.*)(?<=\.)/; | ||
9 | - const type = url.replace(splitExtReg, ''); | ||
10 | - /** | ||
11 | - * https://vcsplay.scjtonline.cn:8200/live/HD_1569b634-4789-11eb-ab67-3cd2e55e0b20.m3u8?auth_key=1681179278-0-0-5c54a376f2ca32d05c4a152ee96336e9 | ||
12 | - * 如果是这种格式的m3u8,则截取的是这一部分.m3u8?auth_key=1681179278-0-0-5c54a376f2ca32d05c4a152ee96336e9 | ||
13 | - */ | ||
14 | - if (type.startsWith('m3u8')) return VideoPlayerType.m3u8; | ||
15 | - if (type.startsWith('mp4')) return VideoPlayerType.mp4; | ||
16 | - if (type.startsWith('webm')) return VideoPlayerType.webm; | ||
17 | - return VideoPlayerType.webm; | 14 | + try { |
15 | + const { protocol, pathname } = new URL(url); | ||
16 | + if (protocol.startsWith('rtsp:')) return VideoPlayerType.flv; | ||
17 | + | ||
18 | + const reg = /[^.]\w*$/; | ||
19 | + const mathValue = pathname.match(reg) || []; | ||
20 | + const ext = (mathValue[0] as keyof typeof VideoPlayerType) || 'webm'; | ||
21 | + const type = VideoPlayerType[ext]; | ||
22 | + return type ? type : VideoPlayerType.webm; | ||
23 | + } catch (error) { | ||
24 | + console.error(error); | ||
25 | + return VideoPlayerType.webm; | ||
26 | + } | ||
18 | }; | 27 | }; |
src/utils/useFingerprint.ts
0 → 100644
@@ -11,58 +11,89 @@ | @@ -11,58 +11,89 @@ | ||
11 | @cancel="handleCancel" | 11 | @cancel="handleCancel" |
12 | > | 12 | > |
13 | <div | 13 | <div |
14 | - class="flex items-center justify-center bg-dark-900 w-full h-full min-h-52 video-container" | 14 | + class="flex items-center justify-center bg-dark-900 w-full h-full min-h-96 video-container" |
15 | > | 15 | > |
16 | - <BasicVideoPlay v-if="showVideo" :options="options" /> | 16 | + <BasicVideoPlay |
17 | + v-if="showVideo" | ||
18 | + :options="(options as any)" | ||
19 | + :withToken="withToken" | ||
20 | + @on-unmounted="handleCloseFlvPlayUrl" | ||
21 | + /> | ||
17 | </div> | 22 | </div> |
18 | </BasicModal> | 23 | </BasicModal> |
19 | </div> | 24 | </div> |
20 | </template> | 25 | </template> |
21 | <script setup lang="ts"> | 26 | <script setup lang="ts"> |
22 | - import { ref, reactive } from 'vue'; | 27 | + import { ref, reactive, unref } from 'vue'; |
23 | import { BasicModal, useModalInner } from '/@/components/Modal'; | 28 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
24 | import type { StreamingManageRecord, CameraModel } from '/@/api/camera/model/cameraModel'; | 29 | import type { StreamingManageRecord, CameraModel } from '/@/api/camera/model/cameraModel'; |
25 | import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video'; | 30 | import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video'; |
26 | import { AccessMode } from './config.data'; | 31 | import { AccessMode } from './config.data'; |
27 | - import { getStreamingPlayUrl } from '/@/api/camera/cameraManager'; | 32 | + import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '/@/api/camera/cameraManager'; |
33 | + import { isRtspProtocol } from '/@/components/Video/src/utils'; | ||
28 | import { VideoJsPlayerOptions } from 'video.js'; | 34 | import { VideoJsPlayerOptions } from 'video.js'; |
35 | + import { useFingerprint } from '/@/utils/useFingerprint'; | ||
36 | + import { GetResult } from '@fingerprintjs/fingerprintjs'; | ||
29 | 37 | ||
30 | const heightNum = ref(800); | 38 | const heightNum = ref(800); |
31 | const showVideo = ref(false); | 39 | const showVideo = ref(false); |
40 | + | ||
41 | + const playUrl = ref(''); | ||
42 | + | ||
43 | + const withToken = ref(false); | ||
44 | + | ||
45 | + const fingerprintResult = ref<Nullable<GetResult>>(null); | ||
46 | + | ||
32 | const options = reactive<VideoJsPlayerOptions>({ | 47 | const options = reactive<VideoJsPlayerOptions>({ |
33 | width: '100%' as unknown as number, | 48 | width: '100%' as unknown as number, |
34 | - height: '100%' as unknown as number, | 49 | + height: 384 as unknown as number, |
35 | autoplay: true, | 50 | autoplay: true, |
36 | }); | 51 | }); |
37 | 52 | ||
38 | - const setSources = (url: string) => { | 53 | + const setSources = (url: string, fingerprintResult: GetResult) => { |
54 | + const flag = isRtspProtocol(url); | ||
39 | options.sources = [ | 55 | options.sources = [ |
40 | { | 56 | { |
41 | - src: url, | 57 | + src: flag ? getFlvPlayUrl(url, fingerprintResult.visitorId) : url, |
42 | type: getVideoTypeByUrl(url), | 58 | type: getVideoTypeByUrl(url), |
43 | }, | 59 | }, |
44 | ]; | 60 | ]; |
45 | }; | 61 | }; |
46 | 62 | ||
63 | + const { getResult } = useFingerprint(); | ||
47 | const [register] = useModalInner( | 64 | const [register] = useModalInner( |
48 | async (data: { record: CameraModel | StreamingManageRecord }) => { | 65 | async (data: { record: CameraModel | StreamingManageRecord }) => { |
49 | const { record } = data; | 66 | const { record } = data; |
67 | + const result = await getResult(); | ||
68 | + fingerprintResult.value = result; | ||
50 | if (record.accessMode === AccessMode.ManuallyEnter) { | 69 | if (record.accessMode === AccessMode.ManuallyEnter) { |
51 | if ((record as CameraModel).videoUrl) { | 70 | if ((record as CameraModel).videoUrl) { |
52 | - setSources((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); | ||
53 | } | 77 | } |
54 | } else { | 78 | } else { |
55 | try { | 79 | try { |
56 | const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!); | 80 | const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!); |
57 | - setSources(url); | 81 | + setSources(url, result); |
58 | } catch (error) {} | 82 | } catch (error) {} |
59 | } | 83 | } |
60 | showVideo.value = true; | 84 | showVideo.value = true; |
61 | } | 85 | } |
62 | ); | 86 | ); |
63 | 87 | ||
88 | + const handleCloseFlvPlayUrl = () => { | ||
89 | + if (isRtspProtocol(unref(playUrl))) { | ||
90 | + closeFlvPlay(unref(playUrl)!, unref(fingerprintResult)!.visitorId!); | ||
91 | + } | ||
92 | + }; | ||
93 | + | ||
64 | const handleCancel = () => { | 94 | const handleCancel = () => { |
65 | showVideo.value = false; | 95 | showVideo.value = false; |
96 | + withToken.value = false; | ||
66 | }; | 97 | }; |
67 | </script> | 98 | </script> |
68 | 99 |
1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
2 | import { PageWrapper } from '/@/components/Page'; | 2 | import { PageWrapper } from '/@/components/Page'; |
3 | import OrganizationIdTree from '../../common/organizationIdTree/src/OrganizationIdTree.vue'; | 3 | import OrganizationIdTree from '../../common/organizationIdTree/src/OrganizationIdTree.vue'; |
4 | - import { onMounted, reactive, ref, unref, watch } from 'vue'; | 4 | + import { onMounted, reactive, Ref, ref, unref, watch } from 'vue'; |
5 | import { Spin, Button, Pagination, Space, List } from 'ant-design-vue'; | 5 | import { Spin, Button, Pagination, Space, List } from 'ant-design-vue'; |
6 | - import { cameraPage } from '/@/api/camera/cameraManager'; | 6 | + import { cameraPage, closeFlvPlay, getFlvPlayUrl } from '/@/api/camera/cameraManager'; |
7 | import { CameraRecord } from '/@/api/camera/model/cameraModel'; | 7 | import { CameraRecord } from '/@/api/camera/model/cameraModel'; |
8 | import { useFullscreen } from '@vueuse/core'; | 8 | import { useFullscreen } from '@vueuse/core'; |
9 | import CameraDrawer from './CameraDrawer.vue'; | 9 | import CameraDrawer from './CameraDrawer.vue'; |
@@ -16,11 +16,16 @@ | @@ -16,11 +16,16 @@ | ||
16 | import { VideoJsPlayerOptions } from 'video.js'; | 16 | import { VideoJsPlayerOptions } from 'video.js'; |
17 | import { getBoundingClientRect } from '/@/utils/domUtils'; | 17 | import { getBoundingClientRect } from '/@/utils/domUtils'; |
18 | import { Authority } from '/@/components/Authority'; | 18 | import { Authority } from '/@/components/Authority'; |
19 | + import { isRtspProtocol } from '/@/components/Video/src/utils'; | ||
20 | + import { useFingerprint } from '/@/utils/useFingerprint'; | ||
21 | + import { GetResult } from '@fingerprintjs/fingerprintjs'; | ||
19 | 22 | ||
20 | type CameraRecordItem = CameraRecord & { | 23 | type CameraRecordItem = CameraRecord & { |
21 | canPlay?: boolean; | 24 | canPlay?: boolean; |
22 | isTransform?: boolean; | 25 | isTransform?: boolean; |
26 | + withToken?: boolean; | ||
23 | videoPlayerOptions?: VideoJsPlayerOptions; | 27 | videoPlayerOptions?: VideoJsPlayerOptions; |
28 | + playSourceUrl?: string; | ||
24 | }; | 29 | }; |
25 | 30 | ||
26 | const basicVideoPlayOptions: VideoJsPlayerOptions = { | 31 | const basicVideoPlayOptions: VideoJsPlayerOptions = { |
@@ -43,6 +48,8 @@ | @@ -43,6 +48,8 @@ | ||
43 | total: 0, | 48 | total: 0, |
44 | }); | 49 | }); |
45 | 50 | ||
51 | + const fingerprintResult = ref<Nullable<GetResult>>(null); | ||
52 | + | ||
46 | // 树形选择器 | 53 | // 树形选择器 |
47 | const handleSelect = (orgId: string) => { | 54 | const handleSelect = (orgId: string) => { |
48 | organizationId.value = orgId; | 55 | organizationId.value = orgId; |
@@ -60,12 +67,14 @@ | @@ -60,12 +67,14 @@ | ||
60 | }); | 67 | }); |
61 | pagination.total = total; | 68 | pagination.total = total; |
62 | 69 | ||
70 | + const result = await getResult(); | ||
71 | + fingerprintResult.value = result; | ||
63 | for (const item of items) { | 72 | for (const item of items) { |
64 | (item as CameraRecordItem).isTransform = false; | 73 | (item as CameraRecordItem).isTransform = false; |
65 | (item as CameraRecordItem).videoPlayerOptions = { | 74 | (item as CameraRecordItem).videoPlayerOptions = { |
66 | ...basicVideoPlayOptions, | 75 | ...basicVideoPlayOptions, |
67 | }; | 76 | }; |
68 | - beforeVideoPlay(item); | 77 | + beforeVideoPlay(item, result); |
69 | } | 78 | } |
70 | if (items.length < pagination.pageSize) { | 79 | if (items.length < pagination.pageSize) { |
71 | const fillArr: any = Array.from({ length: pagination.pageSize - items.length }).map(() => ({ | 80 | const fillArr: any = Array.from({ length: pagination.pageSize - items.length }).map(() => ({ |
@@ -81,15 +90,25 @@ | @@ -81,15 +90,25 @@ | ||
81 | } | 90 | } |
82 | }; | 91 | }; |
83 | 92 | ||
84 | - const beforeVideoPlay = async (record: CameraRecordItem) => { | 93 | + const { getResult } = useFingerprint(); |
94 | + const beforeVideoPlay = async (record: CameraRecordItem, fingerprintResult: GetResult) => { | ||
85 | if (record.accessMode === AccessMode.ManuallyEnter) { | 95 | if (record.accessMode === AccessMode.ManuallyEnter) { |
86 | if (record.videoUrl) { | 96 | if (record.videoUrl) { |
97 | + const isFlvPlay = isRtspProtocol(record.videoUrl); | ||
98 | + const type = getVideoTypeByUrl(record.videoUrl); | ||
99 | + record.playSourceUrl = record.videoUrl; | ||
100 | + if (isFlvPlay) { | ||
101 | + // handleFlvPlayerUnload(record, fingerprintResult!.visitorId); | ||
102 | + record.playSourceUrl = getFlvPlayUrl(record.videoUrl, fingerprintResult.visitorId); | ||
103 | + record.withToken = true; | ||
104 | + } | ||
105 | + | ||
87 | (record as CameraRecordItem).videoPlayerOptions = { | 106 | (record as CameraRecordItem).videoPlayerOptions = { |
88 | ...basicVideoPlayOptions, | 107 | ...basicVideoPlayOptions, |
89 | sources: [ | 108 | sources: [ |
90 | { | 109 | { |
91 | - src: record.videoUrl, | ||
92 | - type: getVideoTypeByUrl(record.videoUrl), | 110 | + src: record.playSourceUrl, |
111 | + type, | ||
93 | }, | 112 | }, |
94 | ], | 113 | ], |
95 | }; | 114 | }; |
@@ -104,6 +123,7 @@ | @@ -104,6 +123,7 @@ | ||
104 | const oldRecord = unref(cameraList).at(index)!; | 123 | const oldRecord = unref(cameraList).at(index)!; |
105 | unref(cameraList)[index] = { | 124 | unref(cameraList)[index] = { |
106 | ...oldRecord, | 125 | ...oldRecord, |
126 | + | ||
107 | videoPlayerOptions: { | 127 | videoPlayerOptions: { |
108 | ...basicVideoPlayOptions, | 128 | ...basicVideoPlayOptions, |
109 | sources: [ | 129 | sources: [ |
@@ -112,7 +132,7 @@ | @@ -112,7 +132,7 @@ | ||
112 | type: getVideoTypeByUrl(url), | 132 | type: getVideoTypeByUrl(url), |
113 | }, | 133 | }, |
114 | ], | 134 | ], |
115 | - }, | 135 | + } as any, |
116 | isTransform: true, | 136 | isTransform: true, |
117 | }; | 137 | }; |
118 | } | 138 | } |
@@ -134,7 +154,7 @@ | @@ -134,7 +154,7 @@ | ||
134 | getCameraList(); | 154 | getCameraList(); |
135 | }; | 155 | }; |
136 | 156 | ||
137 | - const { enter, isFullscreen } = useFullscreen(videoContainer); | 157 | + const { enter, isFullscreen } = useFullscreen(videoContainer as Ref<HTMLDivElement>); |
138 | 158 | ||
139 | const handleFullScreen = () => { | 159 | const handleFullScreen = () => { |
140 | enter(); | 160 | enter(); |
@@ -160,6 +180,12 @@ | @@ -160,6 +180,12 @@ | ||
160 | }); | 180 | }); |
161 | }; | 181 | }; |
162 | 182 | ||
183 | + const handleCloseFlvPlayUrl = async (record: CameraRecordItem) => { | ||
184 | + if (isRtspProtocol(record.videoUrl)) { | ||
185 | + closeFlvPlay(record.videoUrl, unref(fingerprintResult)!.visitorId!); | ||
186 | + } | ||
187 | + }; | ||
188 | + | ||
163 | onMounted(() => { | 189 | onMounted(() => { |
164 | getCameraList(); | 190 | getCameraList(); |
165 | }); | 191 | }); |
@@ -244,7 +270,7 @@ | @@ -244,7 +270,7 @@ | ||
244 | :loading="loading" | 270 | :loading="loading" |
245 | :data-source="cameraList" | 271 | :data-source="cameraList" |
246 | class="bg-light-50 w-full h-full dark:bg-dark-900 split-mode-list" | 272 | class="bg-light-50 w-full h-full dark:bg-dark-900 split-mode-list" |
247 | - :grid="gridLayout" | 273 | + :grid="(gridLayout as any)" |
248 | :style="{ '--height': `${100 / pagination.colNumber}%` }" | 274 | :style="{ '--height': `${100 / pagination.colNumber}%` }" |
249 | > | 275 | > |
250 | <template #renderItem="{ item }"> | 276 | <template #renderItem="{ item }"> |
@@ -263,7 +289,12 @@ | @@ -263,7 +289,12 @@ | ||
263 | v-show="!item.isTransform" | 289 | v-show="!item.isTransform" |
264 | :spinning="!item.isTransform" | 290 | :spinning="!item.isTransform" |
265 | /> | 291 | /> |
266 | - <BasicVideoPlay v-if="item.isTransform" :options="item.videoPlayerOptions" /> | 292 | + <BasicVideoPlay |
293 | + v-if="item.isTransform" | ||
294 | + :options="item.videoPlayerOptions" | ||
295 | + :with-token="item.withToken" | ||
296 | + @on-unmounted="handleCloseFlvPlayUrl(item)" | ||
297 | + /> | ||
267 | <div | 298 | <div |
268 | class="video-container-mask absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center items-center" | 299 | class="video-container-mask absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center items-center" |
269 | style="height: 100%; background-color: rgba(0, 0, 0, 0.5)" | 300 | style="height: 100%; background-color: rgba(0, 0, 0, 0.5)" |
@@ -316,6 +347,10 @@ | @@ -316,6 +347,10 @@ | ||
316 | .split-mode-list:deep(.ant-row) { | 347 | .split-mode-list:deep(.ant-row) { |
317 | width: 100%; | 348 | width: 100%; |
318 | height: 100%; | 349 | height: 100%; |
350 | + | ||
351 | + > div { | ||
352 | + height: var(--height); | ||
353 | + } | ||
319 | } | 354 | } |
320 | 355 | ||
321 | .split-mode-list:deep(.ant-list-item) { | 356 | .split-mode-list:deep(.ant-list-item) { |
src/views/rule/dataFlow/cpns/config.ts
0 → 100644
1 | +import { FormSchema } from '/@/components/Form'; | ||
2 | +import { findDictItemByCode } from '/@/api/system/dict'; | ||
3 | +import { h, ref, unref } from 'vue'; | ||
4 | +import { isExistDataManagerNameApi } from '/@/api/datamanager/dataManagerApi'; | ||
5 | +import { getDeviceProfile } from '/@/api/alarm/position'; | ||
6 | +import { BasicColumn, BasicTableProps } from '/@/components/Table'; | ||
7 | +import { devicePage } from '/@/api/device/deviceManager'; | ||
8 | +import { Tag } from 'ant-design-vue'; | ||
9 | +import { DeviceRecord } from '/@/api/device/model/deviceModel'; | ||
10 | +import { FETCH_SETTING } from '/@/components/Table/src/const'; | ||
11 | +import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'; | ||
12 | +import { useMessage } from '/@/hooks/web/useMessage'; | ||
13 | + | ||
14 | +const typeValue = ref(''); | ||
15 | +export enum CredentialsEnum { | ||
16 | + IS_ANONYMOUS = 'anonymous', | ||
17 | + IS_BASIC = 'basic', | ||
18 | + IS_PEM = 'pem', | ||
19 | +} | ||
20 | +export const isBasic = (type: string) => { | ||
21 | + return type === CredentialsEnum.IS_BASIC; | ||
22 | +}; | ||
23 | +export const isPem = (type: string) => { | ||
24 | + return type === CredentialsEnum.IS_PEM; | ||
25 | +}; | ||
26 | + | ||
27 | +export enum DataSourceType { | ||
28 | + ALL = 'ALL', | ||
29 | + PRODUCT = 'PRODUCTS', | ||
30 | + DEVICE = 'DEVICES', | ||
31 | +} | ||
32 | + | ||
33 | +export enum BasicInfoFormField { | ||
34 | + DATA_SOURCE_TYPE = 'datasourceType', | ||
35 | + DATA_SOURCE_PRODUCT = 'datasourceProduct', | ||
36 | + DATA_SOURCE_DEVICE = 'datasourceDevice', | ||
37 | + CONVERT_CONFIG_ID = 'convertConfigId', | ||
38 | +} | ||
39 | + | ||
40 | +export enum DeviceStatusEnum { | ||
41 | + OFFLINE = 'OFFLINE', | ||
42 | + ONLINE = 'ONLINE', | ||
43 | + INACTIVE = 'INACTIVE', | ||
44 | +} | ||
45 | + | ||
46 | +export enum DeviceStatusNameEnum { | ||
47 | + OFFLINE = '离线', | ||
48 | + ONLINE = '在线', | ||
49 | + INACTIVE = '待激活', | ||
50 | +} | ||
51 | + | ||
52 | +export enum DeviceTypeEnum { | ||
53 | + SENSOR = 'SENSOR', | ||
54 | + DIRECT_CONNECTION = 'DIRECT_CONNECTION', | ||
55 | + GATEWAY = 'GATEWAY', | ||
56 | +} | ||
57 | + | ||
58 | +export enum DeviceTypeNameEnum { | ||
59 | + SENSOR = '网关子设备', | ||
60 | + DIRECT_CONNECTION = '直连设备', | ||
61 | + GATEWAY = '网关设备', | ||
62 | +} | ||
63 | + | ||
64 | +const handleGroupDevice = (options: DeviceRecord[]) => { | ||
65 | + const map = new Map<string, string[]>(); | ||
66 | + options.forEach((item) => { | ||
67 | + if (map.has(item.profileId)) { | ||
68 | + const deviceList = map.get(item.profileId)!; | ||
69 | + deviceList.push(item.tbDeviceId); | ||
70 | + } else { | ||
71 | + map.set(item.profileId, [item.tbDeviceId]); | ||
72 | + } | ||
73 | + }); | ||
74 | + const value = Array.from(map.entries()).map(([product, devices]) => ({ product, devices })); | ||
75 | + | ||
76 | + return value; | ||
77 | +}; | ||
78 | + | ||
79 | +const deviceTableFormSchema: FormSchema[] = [ | ||
80 | + { | ||
81 | + field: 'name', | ||
82 | + label: '设备名称', | ||
83 | + component: 'Input', | ||
84 | + colProps: { span: 9 }, | ||
85 | + componentProps: { | ||
86 | + placeholder: '请输入设备名称', | ||
87 | + }, | ||
88 | + }, | ||
89 | + { | ||
90 | + field: 'deviceType', | ||
91 | + label: '设备类型', | ||
92 | + component: 'ApiSelect', | ||
93 | + colProps: { span: 9 }, | ||
94 | + componentProps: { | ||
95 | + placeholder: '请选择设备类型', | ||
96 | + api: findDictItemByCode, | ||
97 | + params: { | ||
98 | + dictCode: 'device_type', | ||
99 | + }, | ||
100 | + labelField: 'itemText', | ||
101 | + valueField: 'itemValue', | ||
102 | + }, | ||
103 | + }, | ||
104 | +]; | ||
105 | +const { clipboardRef, isSuccessRef } = useCopyToClipboard(); | ||
106 | +const { createMessage } = useMessage(); | ||
107 | +const deviceTableColumn: BasicColumn[] = [ | ||
108 | + { | ||
109 | + title: '状态', | ||
110 | + dataIndex: 'deviceState', | ||
111 | + customRender: ({ text }) => { | ||
112 | + return h( | ||
113 | + Tag, | ||
114 | + { | ||
115 | + color: | ||
116 | + text === DeviceStatusEnum.INACTIVE | ||
117 | + ? 'warning' | ||
118 | + : text === DeviceStatusEnum.OFFLINE | ||
119 | + ? 'error' | ||
120 | + : 'success', | ||
121 | + }, | ||
122 | + () => DeviceStatusNameEnum[text] | ||
123 | + ); | ||
124 | + }, | ||
125 | + }, | ||
126 | + { | ||
127 | + title: '别名/设备名称', | ||
128 | + dataIndex: 'name', | ||
129 | + customRender: ({ record }) => { | ||
130 | + return h('div', [ | ||
131 | + h( | ||
132 | + 'div', | ||
133 | + { | ||
134 | + class: 'cursor-pointer', | ||
135 | + onClick: () => { | ||
136 | + clipboardRef.value = record.name; | ||
137 | + if (unref(isSuccessRef)) createMessage.success('复制成功~'); | ||
138 | + }, | ||
139 | + }, | ||
140 | + [ | ||
141 | + record.alias && h('div', { class: 'truncate' }, record.alias), | ||
142 | + h('div', { class: 'text-blue-400 truncate' }, record.name), | ||
143 | + ] | ||
144 | + ), | ||
145 | + ]); | ||
146 | + }, | ||
147 | + }, | ||
148 | + { | ||
149 | + title: '设备类型', | ||
150 | + dataIndex: 'deviceType', | ||
151 | + customRender: ({ text }) => { | ||
152 | + return h(Tag, { color: 'success' }, () => DeviceTypeNameEnum[text]); | ||
153 | + }, | ||
154 | + }, | ||
155 | + { | ||
156 | + title: '所属产品', | ||
157 | + dataIndex: 'deviceProfile.name', | ||
158 | + }, | ||
159 | + { | ||
160 | + title: '所属组织', | ||
161 | + dataIndex: 'organizationDTO.name', | ||
162 | + }, | ||
163 | +]; | ||
164 | + | ||
165 | +const TransferTableProps: BasicTableProps = { | ||
166 | + formConfig: { | ||
167 | + layout: 'inline', | ||
168 | + labelWidth: 80, | ||
169 | + schemas: deviceTableFormSchema, | ||
170 | + actionColOptions: { span: 6 }, | ||
171 | + }, | ||
172 | + size: 'small', | ||
173 | + maxHeight: 240, | ||
174 | + useSearchForm: true, | ||
175 | + columns: deviceTableColumn, | ||
176 | + showIndexColumn: false, | ||
177 | + fetchSetting: FETCH_SETTING, | ||
178 | +} as BasicTableProps; | ||
179 | + | ||
180 | +export const modeForm = (submitFn?: Function): FormSchema[] => { | ||
181 | + return [ | ||
182 | + { | ||
183 | + field: BasicInfoFormField.CONVERT_CONFIG_ID, | ||
184 | + label: '', | ||
185 | + component: 'Input', | ||
186 | + show: false, | ||
187 | + }, | ||
188 | + { | ||
189 | + field: BasicInfoFormField.DATA_SOURCE_TYPE, | ||
190 | + label: '数据源', | ||
191 | + component: 'RadioGroup', | ||
192 | + defaultValue: DataSourceType.ALL, | ||
193 | + componentProps: { | ||
194 | + options: [ | ||
195 | + { label: '全部', value: DataSourceType.ALL }, | ||
196 | + { label: '产品', value: DataSourceType.PRODUCT }, | ||
197 | + { label: '设备', value: DataSourceType.DEVICE }, | ||
198 | + ], | ||
199 | + }, | ||
200 | + }, | ||
201 | + { | ||
202 | + field: BasicInfoFormField.DATA_SOURCE_PRODUCT, | ||
203 | + label: '数据源产品', | ||
204 | + component: 'TransferModal', | ||
205 | + ifShow: ({ model }) => { | ||
206 | + return model[BasicInfoFormField.DATA_SOURCE_TYPE] !== DataSourceType.ALL; | ||
207 | + }, | ||
208 | + valueField: 'value', | ||
209 | + changeEvent: 'update:value', | ||
210 | + componentProps: ({ formActionType }) => { | ||
211 | + const { setFieldsValue } = formActionType; | ||
212 | + return { | ||
213 | + api: getDeviceProfile, | ||
214 | + labelField: 'name', | ||
215 | + valueField: 'tbProfileId', | ||
216 | + transferProps: { | ||
217 | + listStyle: { height: '400px' }, | ||
218 | + showSearch: true, | ||
219 | + filterOption: (inputValue: string, option: Recordable) => { | ||
220 | + const upperCaseInputValue = inputValue.toUpperCase(); | ||
221 | + const upperCaseOptionValue = option.name.toUpperCase(); | ||
222 | + return upperCaseOptionValue.includes(upperCaseInputValue); | ||
223 | + }, | ||
224 | + }, | ||
225 | + onChange: () => { | ||
226 | + setFieldsValue({ [BasicInfoFormField.DATA_SOURCE_DEVICE]: [] }); | ||
227 | + }, | ||
228 | + }; | ||
229 | + }, | ||
230 | + }, | ||
231 | + { | ||
232 | + field: BasicInfoFormField.DATA_SOURCE_DEVICE, | ||
233 | + label: '数据源设备', | ||
234 | + component: 'TransferTableModal', | ||
235 | + ifShow: ({ model }) => { | ||
236 | + return model[BasicInfoFormField.DATA_SOURCE_TYPE] === DataSourceType.DEVICE; | ||
237 | + }, | ||
238 | + valueField: 'value', | ||
239 | + changeEvent: 'update:value', | ||
240 | + componentProps: ({ formActionType }) => { | ||
241 | + const { getFieldsValue } = formActionType; | ||
242 | + const values = getFieldsValue(); | ||
243 | + const convertConfigId = Reflect.get(values, BasicInfoFormField.CONVERT_CONFIG_ID); | ||
244 | + const devices = Reflect.get(values, BasicInfoFormField.DATA_SOURCE_DEVICE); | ||
245 | + | ||
246 | + return { | ||
247 | + labelField: 'name', | ||
248 | + valueField: 'tbDeviceId', | ||
249 | + primaryKey: 'tbDeviceId', | ||
250 | + pendingTableProps: { | ||
251 | + ...TransferTableProps, | ||
252 | + api: devicePage, | ||
253 | + beforeFetch: (params) => { | ||
254 | + const values = getFieldsValue(); | ||
255 | + const deviceProfileIds = Reflect.get(values, BasicInfoFormField.DATA_SOURCE_PRODUCT); | ||
256 | + const convertConfigId = Reflect.get(values, BasicInfoFormField.CONVERT_CONFIG_ID); | ||
257 | + if (convertConfigId) { | ||
258 | + Object.assign(params, { convertConfigId, selected: false }); | ||
259 | + } | ||
260 | + return { ...params, deviceProfileIds }; | ||
261 | + }, | ||
262 | + } as BasicTableProps, | ||
263 | + selectedTableProps: { | ||
264 | + ...TransferTableProps, | ||
265 | + // api | ||
266 | + api: !!(convertConfigId && devices) ? devicePage : undefined, | ||
267 | + beforeFetch: (params) => { | ||
268 | + const values = getFieldsValue(); | ||
269 | + const deviceProfileIds = Reflect.get(values, BasicInfoFormField.DATA_SOURCE_PRODUCT); | ||
270 | + const convertConfigId = Reflect.get(values, BasicInfoFormField.CONVERT_CONFIG_ID); | ||
271 | + if (convertConfigId) { | ||
272 | + Object.assign(params, { convertConfigId, selected: true }); | ||
273 | + } | ||
274 | + return { ...params, deviceProfileIds }; | ||
275 | + }, | ||
276 | + } as BasicTableProps, | ||
277 | + initSelectedOptions: async ({ setSelectedTotal }) => { | ||
278 | + const values = getFieldsValue(); | ||
279 | + const convertConfigId = Reflect.get(values, BasicInfoFormField.CONVERT_CONFIG_ID); | ||
280 | + const deviceProfileIds = Reflect.get(values, BasicInfoFormField.DATA_SOURCE_PRODUCT); | ||
281 | + const devices = Reflect.get(values, BasicInfoFormField.DATA_SOURCE_DEVICE); | ||
282 | + if (convertConfigId && devices) { | ||
283 | + const { items, total } = await devicePage({ | ||
284 | + page: 1, | ||
285 | + pageSize: 10, | ||
286 | + convertConfigId: values[BasicInfoFormField.CONVERT_CONFIG_ID], | ||
287 | + deviceProfileIds, | ||
288 | + selected: true, | ||
289 | + }); | ||
290 | + setSelectedTotal(total); | ||
291 | + return items; | ||
292 | + } | ||
293 | + return []; | ||
294 | + }, | ||
295 | + onSelectedAfter: async () => { | ||
296 | + submitFn && (await submitFn(false)); | ||
297 | + }, | ||
298 | + onRemoveAfter: async ({ reloadSelected }) => { | ||
299 | + submitFn && (await submitFn(false)); | ||
300 | + reloadSelected(); | ||
301 | + }, | ||
302 | + transformValue: (_selectedRowKeys: string[], selectedRows: DeviceRecord[]) => { | ||
303 | + return handleGroupDevice(selectedRows); | ||
304 | + }, | ||
305 | + }; | ||
306 | + }, | ||
307 | + }, | ||
308 | + { | ||
309 | + field: 'type', | ||
310 | + label: '转换方式', | ||
311 | + component: 'ApiSelect', | ||
312 | + required: true, | ||
313 | + colProps: { | ||
314 | + span: 24, | ||
315 | + }, | ||
316 | + componentProps({}) { | ||
317 | + return { | ||
318 | + api: findDictItemByCode, | ||
319 | + params: { | ||
320 | + dictCode: 'convert_data_to', | ||
321 | + }, | ||
322 | + labelField: 'itemText', | ||
323 | + valueField: 'itemValue', | ||
324 | + onChange(value) { | ||
325 | + typeValue.value = value; | ||
326 | + }, | ||
327 | + }; | ||
328 | + }, | ||
329 | + }, | ||
330 | + { | ||
331 | + field: 'remark', | ||
332 | + label: '描述', | ||
333 | + colProps: { span: 24 }, | ||
334 | + component: 'Input', | ||
335 | + componentProps: { | ||
336 | + maxLength: 255, | ||
337 | + placeholder: '请输入描述', | ||
338 | + }, | ||
339 | + }, | ||
340 | + ]; | ||
341 | +}; | ||
342 | + | ||
343 | +export const modeKafkaForm: FormSchema[] = [ | ||
344 | + { | ||
345 | + field: 'name', | ||
346 | + label: '名称', | ||
347 | + colProps: { span: 12 }, | ||
348 | + required: true, | ||
349 | + component: 'Input', | ||
350 | + componentProps: { | ||
351 | + maxLength: 255, | ||
352 | + placeholder: '请输入名称', | ||
353 | + }, | ||
354 | + dynamicRules: () => { | ||
355 | + return [ | ||
356 | + { | ||
357 | + required: true, | ||
358 | + validator(_, value) { | ||
359 | + return new Promise((resolve, reject) => { | ||
360 | + if (value == '') { | ||
361 | + reject('请输入名称'); | ||
362 | + } else { | ||
363 | + resolve(); | ||
364 | + } | ||
365 | + }); | ||
366 | + }, | ||
367 | + }, | ||
368 | + ]; | ||
369 | + }, | ||
370 | + }, | ||
371 | + { | ||
372 | + field: 'topicPattern', | ||
373 | + label: '消息主题', | ||
374 | + colProps: { span: 12 }, | ||
375 | + required: true, | ||
376 | + component: 'Input', | ||
377 | + defaultValue: 'my-topic', | ||
378 | + componentProps: { | ||
379 | + maxLength: 255, | ||
380 | + placeholder: '请输入消息主题', | ||
381 | + }, | ||
382 | + }, | ||
383 | + { | ||
384 | + field: 'bootstrapServers', | ||
385 | + label: '服务器', | ||
386 | + colProps: { span: 12 }, | ||
387 | + component: 'Input', | ||
388 | + defaultValue: 'localhost:9092', | ||
389 | + required: true, | ||
390 | + componentProps: { | ||
391 | + maxLength: 255, | ||
392 | + placeholder: 'localhost:9092', | ||
393 | + }, | ||
394 | + }, | ||
395 | + { | ||
396 | + field: 'retries', | ||
397 | + label: '重连次数', | ||
398 | + colProps: { span: 12 }, | ||
399 | + component: 'InputNumber', | ||
400 | + defaultValue: 0, | ||
401 | + componentProps: { | ||
402 | + maxLength: 255, | ||
403 | + }, | ||
404 | + }, | ||
405 | + { | ||
406 | + field: 'batchSize', | ||
407 | + label: '生产者并发', | ||
408 | + colProps: { span: 12 }, | ||
409 | + component: 'InputNumber', | ||
410 | + defaultValue: 16384, | ||
411 | + componentProps: { | ||
412 | + maxLength: 255, | ||
413 | + }, | ||
414 | + }, | ||
415 | + { | ||
416 | + field: 'linger', | ||
417 | + label: '缓存时间', | ||
418 | + colProps: { span: 12 }, | ||
419 | + component: 'InputNumber', | ||
420 | + defaultValue: 0, | ||
421 | + componentProps: { | ||
422 | + maxLength: 255, | ||
423 | + }, | ||
424 | + }, | ||
425 | + { | ||
426 | + field: 'bufferMemory', | ||
427 | + label: '最大缓存', | ||
428 | + colProps: { span: 12 }, | ||
429 | + component: 'InputNumber', | ||
430 | + defaultValue: 33554432, | ||
431 | + componentProps: { | ||
432 | + maxLength: 255, | ||
433 | + }, | ||
434 | + }, | ||
435 | + { | ||
436 | + field: 'acks', | ||
437 | + component: 'Select', | ||
438 | + label: '响应码', | ||
439 | + colProps: { span: 12 }, | ||
440 | + defaultValue: '-1', | ||
441 | + componentProps: { | ||
442 | + placeholder: '请选择响应码', | ||
443 | + options: [ | ||
444 | + { label: 'all', value: 'all' }, | ||
445 | + { label: '-1', value: '-1' }, | ||
446 | + { label: '0', value: '0' }, | ||
447 | + { label: '1', value: '1' }, | ||
448 | + ], | ||
449 | + }, | ||
450 | + }, | ||
451 | + { | ||
452 | + field: 'keySerializer', | ||
453 | + label: '键序列化', | ||
454 | + colProps: { span: 24 }, | ||
455 | + required: true, | ||
456 | + component: 'Input', | ||
457 | + defaultValue: 'org.apache.kafka.common.serialization.StringSerializer', | ||
458 | + componentProps: { | ||
459 | + maxLength: 255, | ||
460 | + placeholder: 'org.apache.kafka.common.serialization.StringSerializer', | ||
461 | + }, | ||
462 | + }, | ||
463 | + { | ||
464 | + field: 'valueSerializer', | ||
465 | + label: '值序列化', | ||
466 | + colProps: { span: 24 }, | ||
467 | + required: true, | ||
468 | + component: 'Input', | ||
469 | + defaultValue: 'org.apache.kafka.common.serialization.StringSerializer', | ||
470 | + componentProps: { | ||
471 | + maxLength: 255, | ||
472 | + placeholder: 'org.apache.kafka.common.serialization.StringSerializer', | ||
473 | + }, | ||
474 | + }, | ||
475 | + { | ||
476 | + field: 'otherProperties', | ||
477 | + label: '其他属性', | ||
478 | + colProps: { span: 24 }, | ||
479 | + component: 'JAddInput', | ||
480 | + subLabel: '不可重复', | ||
481 | + }, | ||
482 | + { | ||
483 | + field: 'addMetadataKeyValuesAsKafkaHeaders', | ||
484 | + label: '是否启用', | ||
485 | + colProps: { span: 12 }, | ||
486 | + component: 'Checkbox', | ||
487 | + renderComponentContent: '将消息的元数据以键值对的方式添加到Kafka消息头中', | ||
488 | + }, | ||
489 | + { | ||
490 | + field: 'kafkaHeadersCharset', | ||
491 | + component: 'Select', | ||
492 | + label: '字符集', | ||
493 | + required: true, | ||
494 | + colProps: { span: 12 }, | ||
495 | + defaultValue: 'UTF-8', | ||
496 | + componentProps: { | ||
497 | + placeholder: '请选择字符集编码', | ||
498 | + options: [ | ||
499 | + { label: 'US-ASCII', value: 'US' }, | ||
500 | + { label: 'ISO-8859-1', value: 'ISO-8859-1' }, | ||
501 | + { label: 'UTF-8', value: 'UTF-8' }, | ||
502 | + { label: 'UTF-16BE', value: 'UTF-16BE' }, | ||
503 | + { label: 'UTF-16LE', value: 'UTF-16LE' }, | ||
504 | + { label: 'UTF-16', value: 'UTF-16' }, | ||
505 | + ], | ||
506 | + }, | ||
507 | + ifShow: ({ values }) => { | ||
508 | + return !!values.addMetadataKeyValuesAsKafkaHeaders; | ||
509 | + }, | ||
510 | + }, | ||
511 | + { | ||
512 | + field: 'description', | ||
513 | + label: '说明', | ||
514 | + colProps: { span: 24 }, | ||
515 | + component: 'InputTextArea', | ||
516 | + componentProps: { | ||
517 | + maxLength: 255, | ||
518 | + rows: 4, | ||
519 | + placeholder: '请输入说明', | ||
520 | + }, | ||
521 | + }, | ||
522 | +]; | ||
523 | + | ||
524 | +export const modeMqttForm: FormSchema[] = [ | ||
525 | + { | ||
526 | + field: 'name', | ||
527 | + label: '名称', | ||
528 | + colProps: { span: 12 }, | ||
529 | + component: 'Input', | ||
530 | + componentProps: { | ||
531 | + maxLength: 255, | ||
532 | + placeholder: '请输入名称', | ||
533 | + }, | ||
534 | + }, | ||
535 | + { | ||
536 | + field: 'topicPattern', | ||
537 | + label: '主题模式', | ||
538 | + colProps: { span: 12 }, | ||
539 | + required: true, | ||
540 | + component: 'Input', | ||
541 | + defaultValue: 'my-topic', | ||
542 | + componentProps: { | ||
543 | + maxLength: 255, | ||
544 | + placeholder: '请输入Topic pattern', | ||
545 | + }, | ||
546 | + }, | ||
547 | + { | ||
548 | + field: 'host', | ||
549 | + label: '主机', | ||
550 | + colProps: { span: 12 }, | ||
551 | + component: 'Input', | ||
552 | + componentProps: { | ||
553 | + maxLength: 255, | ||
554 | + placeholder: '请输入Host', | ||
555 | + }, | ||
556 | + }, | ||
557 | + { | ||
558 | + field: 'port', | ||
559 | + label: '端口', | ||
560 | + colProps: { span: 12 }, | ||
561 | + component: 'InputNumber', | ||
562 | + defaultValue: 1883, | ||
563 | + required: true, | ||
564 | + componentProps: { | ||
565 | + maxLength: 255, | ||
566 | + placeholder: '请输入Port', | ||
567 | + }, | ||
568 | + }, | ||
569 | + { | ||
570 | + field: 'connectTimeoutSec', | ||
571 | + label: '连接超时(秒)', | ||
572 | + colProps: { span: 12 }, | ||
573 | + component: 'InputNumber', | ||
574 | + defaultValue: 10, | ||
575 | + required: true, | ||
576 | + componentProps: { | ||
577 | + maxLength: 255, | ||
578 | + placeholder: '请输入Connection timeout (sec)', | ||
579 | + }, | ||
580 | + }, | ||
581 | + { | ||
582 | + field: 'clientId', | ||
583 | + label: '客户端ID', | ||
584 | + colProps: { span: 12 }, | ||
585 | + component: 'Input', | ||
586 | + componentProps: ({ formActionType }) => { | ||
587 | + const { updateSchema } = formActionType; | ||
588 | + return { | ||
589 | + onChange(e) { | ||
590 | + if (!e.data) { | ||
591 | + updateSchema({ | ||
592 | + field: 'appendClientIdSuffix', | ||
593 | + show: false, | ||
594 | + }); | ||
595 | + } else { | ||
596 | + updateSchema({ | ||
597 | + field: 'appendClientIdSuffix', | ||
598 | + show: true, | ||
599 | + }); | ||
600 | + } | ||
601 | + }, | ||
602 | + maxLength: 255, | ||
603 | + placeholder: '请输入Client ID', | ||
604 | + }; | ||
605 | + }, | ||
606 | + }, | ||
607 | + { | ||
608 | + field: 'appendClientIdSuffix', | ||
609 | + label: '', | ||
610 | + colProps: { span: 12 }, | ||
611 | + defaultValue: false, | ||
612 | + component: 'Checkbox', | ||
613 | + renderComponentContent: '将服务ID作为后缀添加到客户端ID', | ||
614 | + show: false, | ||
615 | + }, | ||
616 | + { | ||
617 | + field: 'cleanSession', | ||
618 | + label: '是否启用', | ||
619 | + colProps: { span: 12 }, | ||
620 | + defaultValue: true, | ||
621 | + component: 'Checkbox', | ||
622 | + renderComponentContent: '清除会话', | ||
623 | + }, | ||
624 | + { | ||
625 | + field: 'ssl', | ||
626 | + label: '是否启用', | ||
627 | + colProps: { span: 12 }, | ||
628 | + defaultValue: false, | ||
629 | + component: 'Checkbox', | ||
630 | + renderComponentContent: '启用SSL', | ||
631 | + }, | ||
632 | + { | ||
633 | + field: 'type', | ||
634 | + component: 'Select', | ||
635 | + label: '凭据类型', | ||
636 | + colProps: { span: 12 }, | ||
637 | + defaultValue: 'anonymous', | ||
638 | + componentProps: { | ||
639 | + placeholder: '请选择Credentials', | ||
640 | + options: [ | ||
641 | + { label: 'Anonymous', value: 'anonymous' }, | ||
642 | + { label: 'Basic', value: 'basic' }, | ||
643 | + { label: 'PEM', value: 'pem' }, | ||
644 | + ], | ||
645 | + }, | ||
646 | + }, | ||
647 | + { | ||
648 | + field: 'username', | ||
649 | + label: '用户名', | ||
650 | + colProps: { span: 12 }, | ||
651 | + component: 'Input', | ||
652 | + required: true, | ||
653 | + componentProps: { | ||
654 | + maxLength: 255, | ||
655 | + placeholder: '请输入用户名', | ||
656 | + }, | ||
657 | + ifShow: ({ values }) => isBasic(Reflect.get(values, 'type')), | ||
658 | + }, | ||
659 | + { | ||
660 | + field: 'password', | ||
661 | + label: '密码', | ||
662 | + colProps: { span: 12 }, | ||
663 | + component: 'InputPassword', | ||
664 | + componentProps: { | ||
665 | + maxLength: 255, | ||
666 | + placeholder: '请输入密码', | ||
667 | + }, | ||
668 | + ifShow: ({ values }) => isBasic(Reflect.get(values, 'type')), | ||
669 | + }, | ||
670 | + { | ||
671 | + field: '4', | ||
672 | + label: '', | ||
673 | + colProps: { span: 24 }, | ||
674 | + component: 'Input', | ||
675 | + slot: 'uploadAdd1', | ||
676 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
677 | + }, | ||
678 | + { | ||
679 | + field: '11', | ||
680 | + label: '', | ||
681 | + colProps: { span: 24 }, | ||
682 | + component: 'Input', | ||
683 | + slot: 'showImg1', | ||
684 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
685 | + }, | ||
686 | + { | ||
687 | + field: '5', | ||
688 | + label: '', | ||
689 | + colProps: { span: 24 }, | ||
690 | + component: 'Input', | ||
691 | + slot: 'uploadAdd2', | ||
692 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
693 | + }, | ||
694 | + { | ||
695 | + field: '1111', | ||
696 | + label: '', | ||
697 | + colProps: { span: 24 }, | ||
698 | + component: 'Input', | ||
699 | + slot: 'showImg2', | ||
700 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
701 | + }, | ||
702 | + { | ||
703 | + field: '6', | ||
704 | + label: '', | ||
705 | + colProps: { span: 24 }, | ||
706 | + component: 'Input', | ||
707 | + slot: 'uploadAdd3', | ||
708 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
709 | + }, | ||
710 | + { | ||
711 | + field: '111111', | ||
712 | + label: '', | ||
713 | + colProps: { span: 24 }, | ||
714 | + component: 'Input', | ||
715 | + slot: 'showImg3', | ||
716 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
717 | + }, | ||
718 | + { | ||
719 | + field: 'password', | ||
720 | + label: '密码', | ||
721 | + colProps: { span: 12 }, | ||
722 | + component: 'InputPassword', | ||
723 | + componentProps: { | ||
724 | + maxLength: 255, | ||
725 | + placeholder: '请输入密码', | ||
726 | + }, | ||
727 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
728 | + }, | ||
729 | + { | ||
730 | + field: 'description', | ||
731 | + label: '说明', | ||
732 | + colProps: { span: 24 }, | ||
733 | + component: 'InputTextArea', | ||
734 | + componentProps: { | ||
735 | + maxLength: 255, | ||
736 | + rows: 4, | ||
737 | + placeholder: '请输入说明', | ||
738 | + }, | ||
739 | + }, | ||
740 | +]; | ||
741 | + | ||
742 | +export const modeRabbitMqForm: FormSchema[] = [ | ||
743 | + { | ||
744 | + field: 'name', | ||
745 | + label: '名称', | ||
746 | + colProps: { span: 12 }, | ||
747 | + required: true, | ||
748 | + component: 'Input', | ||
749 | + componentProps: { | ||
750 | + maxLength: 255, | ||
751 | + placeholder: '请输入名称', | ||
752 | + }, | ||
753 | + dynamicRules: () => { | ||
754 | + return [ | ||
755 | + { | ||
756 | + required: true, | ||
757 | + validator(_, value) { | ||
758 | + return new Promise((resolve, reject) => { | ||
759 | + if (value == '') { | ||
760 | + reject('请输入名称'); | ||
761 | + } else { | ||
762 | + resolve(); | ||
763 | + } | ||
764 | + }); | ||
765 | + }, | ||
766 | + }, | ||
767 | + ]; | ||
768 | + }, | ||
769 | + }, | ||
770 | + { | ||
771 | + field: 'exchangeNamePattern', | ||
772 | + label: '交换名称模式', | ||
773 | + colProps: { span: 12 }, | ||
774 | + component: 'Input', | ||
775 | + componentProps: { | ||
776 | + maxLength: 255, | ||
777 | + placeholder: '请输入模式', | ||
778 | + }, | ||
779 | + }, | ||
780 | + { | ||
781 | + field: 'routingKeyPattern', | ||
782 | + label: '路由密钥模式', | ||
783 | + colProps: { span: 12 }, | ||
784 | + component: 'Input', | ||
785 | + componentProps: { | ||
786 | + maxLength: 255, | ||
787 | + placeholder: '请输入模式', | ||
788 | + }, | ||
789 | + }, | ||
790 | + { | ||
791 | + field: 'messageProperties', | ||
792 | + component: 'Select', | ||
793 | + label: '消息属性', | ||
794 | + colProps: { span: 12 }, | ||
795 | + componentProps: { | ||
796 | + placeholder: '请选择消息属性', | ||
797 | + options: [ | ||
798 | + { label: 'BASIC', value: 'BASIC' }, | ||
799 | + { label: 'TEXT_PLAIN', value: 'TEXT_PLAIN' }, | ||
800 | + { label: 'MINIMAL_BASIC', value: 'MINIMAL_BASIC' }, | ||
801 | + { label: 'MINIMAL_PERSISTENT_BASIC', value: 'MINIMAL_PERSISTENT_BASIC' }, | ||
802 | + { label: 'PERSISTENT_BASIC', value: 'PERSISTENT_BASIC' }, | ||
803 | + { label: 'PERSISTENT_TEXT_PLAIN', value: 'PERSISTENT_TEXT_PLAIN' }, | ||
804 | + ], | ||
805 | + }, | ||
806 | + }, | ||
807 | + { | ||
808 | + field: 'host', | ||
809 | + label: '主机', | ||
810 | + colProps: { span: 12 }, | ||
811 | + component: 'Input', | ||
812 | + required: true, | ||
813 | + defaultValue: 'localhost', | ||
814 | + componentProps: { | ||
815 | + maxLength: 255, | ||
816 | + placeholder: 'localhost', | ||
817 | + }, | ||
818 | + }, | ||
819 | + { | ||
820 | + field: 'port', | ||
821 | + label: '端口', | ||
822 | + colProps: { span: 12 }, | ||
823 | + component: 'InputNumber', | ||
824 | + defaultValue: 5672, | ||
825 | + required: true, | ||
826 | + componentProps: { | ||
827 | + maxLength: 255, | ||
828 | + placeholder: '请输入Port', | ||
829 | + }, | ||
830 | + }, | ||
831 | + { | ||
832 | + field: 'virtualHost', | ||
833 | + label: '虚拟端口(以/开头)', | ||
834 | + colProps: { span: 12 }, | ||
835 | + component: 'Input', | ||
836 | + defaultValue: '/', | ||
837 | + componentProps: { | ||
838 | + maxLength: 255, | ||
839 | + placeholder: '/', | ||
840 | + }, | ||
841 | + }, | ||
842 | + { | ||
843 | + field: 'username', | ||
844 | + label: '用户名', | ||
845 | + colProps: { span: 12 }, | ||
846 | + component: 'Input', | ||
847 | + defaultValue: 'guest', | ||
848 | + componentProps: { | ||
849 | + maxLength: 255, | ||
850 | + placeholder: '请输入用户名', | ||
851 | + }, | ||
852 | + }, | ||
853 | + { | ||
854 | + field: 'password', | ||
855 | + label: '密码', | ||
856 | + colProps: { span: 12 }, | ||
857 | + component: 'InputPassword', | ||
858 | + defaultValue: 'guest', | ||
859 | + componentProps: { | ||
860 | + maxLength: 255, | ||
861 | + placeholder: '请输入密码', | ||
862 | + }, | ||
863 | + }, | ||
864 | + { | ||
865 | + field: 'automaticRecoveryEnabled', | ||
866 | + label: '是否启用', | ||
867 | + colProps: { span: 12 }, | ||
868 | + component: 'Checkbox', | ||
869 | + renderComponentContent: '自动恢复', | ||
870 | + }, | ||
871 | + { | ||
872 | + field: 'connectionTimeout', | ||
873 | + label: '连接超时(毫秒)', | ||
874 | + colProps: { span: 12 }, | ||
875 | + component: 'InputNumber', | ||
876 | + defaultValue: 60000, | ||
877 | + componentProps: { | ||
878 | + maxLength: 255, | ||
879 | + placeholder: '请输入Connection timeout (ms)', | ||
880 | + }, | ||
881 | + }, | ||
882 | + { | ||
883 | + field: 'handshakeTimeout', | ||
884 | + label: '握手超时(毫秒)', | ||
885 | + colProps: { span: 12 }, | ||
886 | + component: 'InputNumber', | ||
887 | + defaultValue: 10000, | ||
888 | + componentProps: { | ||
889 | + maxLength: 255, | ||
890 | + placeholder: '请输入Handshake timeout (ms)', | ||
891 | + }, | ||
892 | + }, | ||
893 | + { | ||
894 | + field: 'clientProperties', | ||
895 | + label: '客户端属性', | ||
896 | + colProps: { span: 24 }, | ||
897 | + component: 'JAddInput', | ||
898 | + subLabel: '不可重复', | ||
899 | + }, | ||
900 | + { | ||
901 | + field: 'description', | ||
902 | + label: '说明', | ||
903 | + colProps: { span: 24 }, | ||
904 | + component: 'InputTextArea', | ||
905 | + componentProps: { | ||
906 | + maxLength: 255, | ||
907 | + rows: 4, | ||
908 | + placeholder: '请输入说明', | ||
909 | + }, | ||
910 | + }, | ||
911 | +]; | ||
912 | + | ||
913 | +export const modeApiForm: FormSchema[] = [ | ||
914 | + { | ||
915 | + field: 'name', | ||
916 | + label: '名称', | ||
917 | + colProps: { span: 12 }, | ||
918 | + required: true, | ||
919 | + component: 'Input', | ||
920 | + componentProps: { | ||
921 | + maxLength: 255, | ||
922 | + placeholder: '请输入名称', | ||
923 | + }, | ||
924 | + dynamicRules: ({ values }) => { | ||
925 | + return [ | ||
926 | + { | ||
927 | + required: true, | ||
928 | + validator(_, value) { | ||
929 | + return new Promise((resolve, reject) => { | ||
930 | + if (value == '') { | ||
931 | + reject('请输入名称'); | ||
932 | + } else { | ||
933 | + if (values.name) { | ||
934 | + isExistDataManagerNameApi({ | ||
935 | + name: value, | ||
936 | + type: | ||
937 | + typeValue.value == '' | ||
938 | + ? 'org.thingsboard.rule.engine.rest.TbRestApiCallNode' | ||
939 | + : typeValue.value, | ||
940 | + }).then((data) => { | ||
941 | + if (data == true) { | ||
942 | + // createMessage.error('名称已存在'); | ||
943 | + resolve(); | ||
944 | + } else { | ||
945 | + resolve(); | ||
946 | + } | ||
947 | + }); | ||
948 | + } else { | ||
949 | + resolve(); | ||
950 | + } | ||
951 | + } | ||
952 | + }); | ||
953 | + }, | ||
954 | + }, | ||
955 | + ]; | ||
956 | + }, | ||
957 | + }, | ||
958 | + { | ||
959 | + field: 'restEndpointUrlPattern', | ||
960 | + label: '端点URL模式', | ||
961 | + colProps: { span: 12 }, | ||
962 | + required: true, | ||
963 | + defaultValue: 'http://localhost/api', | ||
964 | + component: 'Input', | ||
965 | + componentProps: { | ||
966 | + maxLength: 255, | ||
967 | + placeholder: '请输入Endpoint URL pattern', | ||
968 | + }, | ||
969 | + }, | ||
970 | + { | ||
971 | + field: 'requestMethod', | ||
972 | + component: 'Select', | ||
973 | + label: '请求方式', | ||
974 | + colProps: { span: 12 }, | ||
975 | + defaultValue: 'POST', | ||
976 | + componentProps: { | ||
977 | + placeholder: '请选择Request method', | ||
978 | + options: [ | ||
979 | + { label: 'GET', value: 'GET' }, | ||
980 | + { label: 'POST', value: 'POST' }, | ||
981 | + { label: 'PUT', value: 'PUT' }, | ||
982 | + { label: 'DELETE', value: 'DELETE' }, | ||
983 | + ], | ||
984 | + }, | ||
985 | + }, | ||
986 | + { | ||
987 | + field: 'enableProxy', | ||
988 | + label: '是否启用', | ||
989 | + colProps: { span: 12 }, | ||
990 | + component: 'Checkbox', | ||
991 | + renderComponentContent: '启用代理', | ||
992 | + }, | ||
993 | + | ||
994 | + { | ||
995 | + field: 'proxyHost', | ||
996 | + label: '代理主机', | ||
997 | + colProps: { span: 12 }, | ||
998 | + required: true, | ||
999 | + component: 'Input', | ||
1000 | + componentProps: { | ||
1001 | + maxLength: 255, | ||
1002 | + placeholder: 'http或者https开头', | ||
1003 | + }, | ||
1004 | + ifShow: ({ values }) => { | ||
1005 | + return !!values.enableProxy; | ||
1006 | + }, | ||
1007 | + }, | ||
1008 | + { | ||
1009 | + field: 'proxyPort', | ||
1010 | + label: '代理端口', | ||
1011 | + colProps: { span: 12 }, | ||
1012 | + required: true, | ||
1013 | + component: 'InputNumber', | ||
1014 | + defaultValue: 0, | ||
1015 | + componentProps: { | ||
1016 | + maxLength: 255, | ||
1017 | + placeholder: 'http或者https开头', | ||
1018 | + }, | ||
1019 | + ifShow: ({ values }) => { | ||
1020 | + return !!values.enableProxy; | ||
1021 | + }, | ||
1022 | + }, | ||
1023 | + { | ||
1024 | + field: 'proxyUser', | ||
1025 | + label: '代理用户', | ||
1026 | + colProps: { span: 12 }, | ||
1027 | + required: true, | ||
1028 | + component: 'Input', | ||
1029 | + componentProps: { | ||
1030 | + maxLength: 255, | ||
1031 | + placeholder: '请输入代理用户', | ||
1032 | + }, | ||
1033 | + ifShow: ({ values }) => { | ||
1034 | + return !!values.enableProxy; | ||
1035 | + }, | ||
1036 | + }, | ||
1037 | + { | ||
1038 | + field: 'proxyPassword', | ||
1039 | + label: '代理密码', | ||
1040 | + colProps: { span: 12 }, | ||
1041 | + required: true, | ||
1042 | + component: 'InputPassword', | ||
1043 | + componentProps: { | ||
1044 | + maxLength: 255, | ||
1045 | + placeholder: '请输入代理密码', | ||
1046 | + }, | ||
1047 | + ifShow: ({ values }) => { | ||
1048 | + return !!values.enableProxy; | ||
1049 | + }, | ||
1050 | + }, | ||
1051 | + | ||
1052 | + { | ||
1053 | + field: 'useSystemProxyProperties', | ||
1054 | + label: '是否启用', | ||
1055 | + colProps: { span: 12 }, | ||
1056 | + component: 'Checkbox', | ||
1057 | + renderComponentContent: '使用系统代理属性', | ||
1058 | + }, | ||
1059 | + { | ||
1060 | + field: 'maxParallelRequestsCount', | ||
1061 | + label: '最大并行请求数', | ||
1062 | + colProps: { span: 12 }, | ||
1063 | + required: true, | ||
1064 | + component: 'InputNumber', | ||
1065 | + defaultValue: 0, | ||
1066 | + componentProps: { | ||
1067 | + maxLength: 255, | ||
1068 | + }, | ||
1069 | + ifShow: ({ values }) => { | ||
1070 | + return !!values.useSystemProxyProperties; | ||
1071 | + }, | ||
1072 | + }, | ||
1073 | + { | ||
1074 | + field: 'ignoreRequestBody', | ||
1075 | + label: '是否启用', | ||
1076 | + colProps: { span: 12 }, | ||
1077 | + component: 'Checkbox', | ||
1078 | + renderComponentContent: '无请求正文', | ||
1079 | + }, | ||
1080 | + { | ||
1081 | + field: 'readTimeoutMs', | ||
1082 | + label: '读取超时(毫秒)', | ||
1083 | + colProps: { span: 12 }, | ||
1084 | + required: true, | ||
1085 | + component: 'InputNumber', | ||
1086 | + defaultValue: 0, | ||
1087 | + componentProps: { | ||
1088 | + maxLength: 255, | ||
1089 | + }, | ||
1090 | + ifShow: ({ values }) => { | ||
1091 | + return !values.useSystemProxyProperties; | ||
1092 | + }, | ||
1093 | + }, | ||
1094 | + { | ||
1095 | + field: 'maxParallelRequestsCount', | ||
1096 | + label: '最大并行请求数', | ||
1097 | + colProps: { span: 12 }, | ||
1098 | + required: true, | ||
1099 | + component: 'InputNumber', | ||
1100 | + defaultValue: 0, | ||
1101 | + componentProps: { | ||
1102 | + maxLength: 255, | ||
1103 | + }, | ||
1104 | + ifShow: ({ values }) => { | ||
1105 | + return !values.useSystemProxyProperties; | ||
1106 | + }, | ||
1107 | + }, | ||
1108 | + { | ||
1109 | + field: 'headers', | ||
1110 | + label: 'Headers', | ||
1111 | + colProps: { span: 24 }, | ||
1112 | + defaultValue: { 'Content-Type': 'application/json' }, | ||
1113 | + component: 'JAddInput', | ||
1114 | + subLabel: '不可重复', | ||
1115 | + }, | ||
1116 | + | ||
1117 | + { | ||
1118 | + field: 'useRedisQueueForMsgPersistence', | ||
1119 | + label: '是否启用', | ||
1120 | + colProps: { span: 12 }, | ||
1121 | + component: 'Checkbox', | ||
1122 | + renderComponentContent: '使用redis队列进行消息持久性', | ||
1123 | + }, | ||
1124 | + { | ||
1125 | + field: 'trimQueue', | ||
1126 | + label: '是否启用', | ||
1127 | + colProps: { span: 12 }, | ||
1128 | + component: 'Checkbox', | ||
1129 | + renderComponentContent: '修剪redis队列', | ||
1130 | + ifShow: ({ values }) => { | ||
1131 | + return !!values.useRedisQueueForMsgPersistence; | ||
1132 | + }, | ||
1133 | + }, | ||
1134 | + { | ||
1135 | + field: 'maxQueueSize', | ||
1136 | + label: 'Redis队列最大数', | ||
1137 | + colProps: { span: 12 }, | ||
1138 | + required: true, | ||
1139 | + component: 'InputNumber', | ||
1140 | + defaultValue: 0, | ||
1141 | + componentProps: { | ||
1142 | + maxLength: 255, | ||
1143 | + }, | ||
1144 | + ifShow: ({ values }) => { | ||
1145 | + return !!values.useRedisQueueForMsgPersistence; | ||
1146 | + }, | ||
1147 | + }, | ||
1148 | + | ||
1149 | + { | ||
1150 | + field: 'type', | ||
1151 | + component: 'Select', | ||
1152 | + label: '凭据类型', | ||
1153 | + colProps: { span: 12 }, | ||
1154 | + defaultValue: 'anonymous', | ||
1155 | + componentProps: { | ||
1156 | + placeholder: '请选择凭据类型', | ||
1157 | + options: [ | ||
1158 | + { label: 'Anonymous', value: 'anonymous' }, | ||
1159 | + { label: 'Basic', value: 'basic' }, | ||
1160 | + { label: 'PEM', value: 'pem' }, | ||
1161 | + ], | ||
1162 | + }, | ||
1163 | + }, | ||
1164 | + { | ||
1165 | + field: 'username', | ||
1166 | + label: '用户名', | ||
1167 | + colProps: { span: 12 }, | ||
1168 | + component: 'Input', | ||
1169 | + required: true, | ||
1170 | + componentProps: { | ||
1171 | + maxLength: 255, | ||
1172 | + placeholder: '请输入用户名', | ||
1173 | + }, | ||
1174 | + ifShow: ({ values }) => isBasic(Reflect.get(values, 'type')), | ||
1175 | + }, | ||
1176 | + { | ||
1177 | + field: 'password', | ||
1178 | + label: '密码', | ||
1179 | + colProps: { span: 12 }, | ||
1180 | + component: 'InputPassword', | ||
1181 | + required: true, | ||
1182 | + componentProps: { | ||
1183 | + maxLength: 255, | ||
1184 | + placeholder: '请输入密码', | ||
1185 | + }, | ||
1186 | + ifShow: ({ values }) => isBasic(Reflect.get(values, 'type')), | ||
1187 | + }, | ||
1188 | + { | ||
1189 | + field: '1', | ||
1190 | + label: '', | ||
1191 | + colProps: { span: 24 }, | ||
1192 | + component: 'Input', | ||
1193 | + slot: 'uploadAdd1', | ||
1194 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
1195 | + }, | ||
1196 | + { | ||
1197 | + field: '11', | ||
1198 | + label: '', | ||
1199 | + colProps: { span: 24 }, | ||
1200 | + component: 'Input', | ||
1201 | + slot: 'showImg1', | ||
1202 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
1203 | + }, | ||
1204 | + { | ||
1205 | + field: '1', | ||
1206 | + label: '', | ||
1207 | + colProps: { span: 24 }, | ||
1208 | + component: 'Input', | ||
1209 | + slot: 'uploadAdd2', | ||
1210 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
1211 | + }, | ||
1212 | + { | ||
1213 | + field: '1111', | ||
1214 | + label: '', | ||
1215 | + colProps: { span: 24 }, | ||
1216 | + component: 'Input', | ||
1217 | + slot: 'showImg2', | ||
1218 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
1219 | + }, | ||
1220 | + { | ||
1221 | + field: '1', | ||
1222 | + label: '', | ||
1223 | + colProps: { span: 24 }, | ||
1224 | + component: 'Input', | ||
1225 | + slot: 'uploadAdd3', | ||
1226 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
1227 | + }, | ||
1228 | + { | ||
1229 | + field: '111111', | ||
1230 | + label: '', | ||
1231 | + colProps: { span: 24 }, | ||
1232 | + component: 'Input', | ||
1233 | + slot: 'showImg3', | ||
1234 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
1235 | + }, | ||
1236 | + { | ||
1237 | + field: 'password', | ||
1238 | + label: '密码', | ||
1239 | + colProps: { span: 12 }, | ||
1240 | + component: 'InputPassword', | ||
1241 | + componentProps: { | ||
1242 | + maxLength: 255, | ||
1243 | + placeholder: '请输入密码', | ||
1244 | + }, | ||
1245 | + ifShow: ({ values }) => isPem(Reflect.get(values, 'type')), | ||
1246 | + }, | ||
1247 | + | ||
1248 | + { | ||
1249 | + field: 'description', | ||
1250 | + label: '说明', | ||
1251 | + colProps: { span: 24 }, | ||
1252 | + component: 'InputTextArea', | ||
1253 | + componentProps: { | ||
1254 | + maxLength: 255, | ||
1255 | + rows: 4, | ||
1256 | + placeholder: '请输入说明', | ||
1257 | + }, | ||
1258 | + }, | ||
1259 | +]; |
1 | +<template> | ||
2 | + <div class="transfer-config-mode"> | ||
3 | + <BasicForm :showSubmitButton="false" @register="register"> | ||
4 | + <template #uploadAdd1="{ field }"> | ||
5 | + <span style="display: none">{{ field }}</span> | ||
6 | + <a-upload-dragger | ||
7 | + v-model:fileList="fileList1" | ||
8 | + name="file" | ||
9 | + :key="1" | ||
10 | + :multiple="false" | ||
11 | + @change="handleChange('T', $event)" | ||
12 | + :before-upload="() => false" | ||
13 | + > | ||
14 | + <p class="ant-upload-drag-icon"> | ||
15 | + <InboxOutlined /> | ||
16 | + </p> | ||
17 | + <p class="ant-upload-text">点击或将文件拖拽到这里上传</p> | ||
18 | + <p class="ant-upload-hint"> | ||
19 | + 支持扩展名:.jpeg .png .jpg ... | ||
20 | + <br /> | ||
21 | + 文件大小:最大支持5M | ||
22 | + </p> | ||
23 | + </a-upload-dragger> | ||
24 | + </template> | ||
25 | + <template #showImg1="{ field }"> | ||
26 | + <span style="display: none">{{ field }}</span> | ||
27 | + <img | ||
28 | + v-if="showImg1" | ||
29 | + :src="showImg1Pic" | ||
30 | + alt="avatar" | ||
31 | + style="width: 6.25rem; height: 6.25rem" | ||
32 | + /> | ||
33 | + </template> | ||
34 | + <div style="margin-top: 50px"></div> | ||
35 | + <template #uploadAdd2="{ field }"> | ||
36 | + <span style="display: none">{{ field }}</span> | ||
37 | + <a-upload-dragger | ||
38 | + v-model:fileList="fileList2" | ||
39 | + name="file" | ||
40 | + :key="2" | ||
41 | + :multiple="false" | ||
42 | + @change="handleChange('F', $event)" | ||
43 | + :before-upload="() => false" | ||
44 | + > | ||
45 | + <p class="ant-upload-drag-icon"> | ||
46 | + <InboxOutlined /> | ||
47 | + </p> | ||
48 | + <p class="ant-upload-text">点击或将文件拖拽到这里上传</p> | ||
49 | + <p class="ant-upload-hint"> | ||
50 | + 支持扩展名:.jpeg .png .jpg ... | ||
51 | + <br /> | ||
52 | + 文件大小:最大支持5M | ||
53 | + </p> | ||
54 | + </a-upload-dragger> | ||
55 | + </template> | ||
56 | + <template #showImg2="{ field }"> | ||
57 | + <span style="display: none">{{ field }}</span> | ||
58 | + <img | ||
59 | + v-if="showImg2" | ||
60 | + :src="showImg2Pic" | ||
61 | + alt="avatar" | ||
62 | + style="width: 6.25rem; height: 6.25rem" | ||
63 | + /> | ||
64 | + </template> | ||
65 | + <div style="margin-top: 50px"></div> | ||
66 | + <template #uploadAdd3="{ field }"> | ||
67 | + <span style="display: none">{{ field }}</span> | ||
68 | + <a-upload-dragger | ||
69 | + v-model:fileList="fileList3" | ||
70 | + name="file" | ||
71 | + :key="3" | ||
72 | + :multiple="false" | ||
73 | + @change="handleChange('C', $event)" | ||
74 | + :before-upload="() => false" | ||
75 | + > | ||
76 | + <p class="ant-upload-drag-icon"> | ||
77 | + <InboxOutlined /> | ||
78 | + </p> | ||
79 | + <p class="ant-upload-text">点击或将文件拖拽到这里上传</p> | ||
80 | + <p class="ant-upload-hint"> | ||
81 | + 支持扩展名:.jpeg .png .jpg ... | ||
82 | + <br /> | ||
83 | + 文件大小:最大支持5M | ||
84 | + </p> | ||
85 | + </a-upload-dragger> | ||
86 | + </template> | ||
87 | + <template #showImg3="{ field }"> | ||
88 | + <span style="display: none">{{ field }}</span> | ||
89 | + <img | ||
90 | + v-if="showImg3" | ||
91 | + :src="showImg3Pic" | ||
92 | + alt="avatar" | ||
93 | + style="width: 6.25rem; height: 6.25rem" | ||
94 | + /> | ||
95 | + </template> | ||
96 | + </BasicForm> | ||
97 | + </div> | ||
98 | +</template> | ||
99 | +<script lang="ts"> | ||
100 | + import { defineComponent, ref, reactive, nextTick } from 'vue'; | ||
101 | + import { BasicForm, useForm } from '/@/components/Form'; | ||
102 | + import { CredentialsEnum, modeMqttForm } from '../config'; | ||
103 | + import { InboxOutlined } from '@ant-design/icons-vue'; | ||
104 | + import { Alert, Divider, Descriptions, Upload } from 'ant-design-vue'; | ||
105 | + import { uploadApi } from '/@/api/personal/index'; | ||
106 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
107 | + | ||
108 | + export default defineComponent({ | ||
109 | + components: { | ||
110 | + BasicForm, | ||
111 | + [Alert.name]: Alert, | ||
112 | + [Divider.name]: Divider, | ||
113 | + [Descriptions.name]: Descriptions, | ||
114 | + [Descriptions.Item.name]: Descriptions.Item, | ||
115 | + InboxOutlined, | ||
116 | + [Upload.Dragger.name]: Upload.Dragger, | ||
117 | + }, | ||
118 | + emits: ['next', 'prev', 'register'], | ||
119 | + setup(_, { emit }) { | ||
120 | + const showImg1 = ref(false); | ||
121 | + const showImg1Pic = ref(''); | ||
122 | + const showImg2 = ref(false); | ||
123 | + const showImg2Pic = ref(''); | ||
124 | + const showImg3 = ref(false); | ||
125 | + const showImg3Pic = ref(''); | ||
126 | + const { createMessage } = useMessage(); | ||
127 | + let caCertFileName = ref(''); | ||
128 | + let privateKeyFileName = ref(''); | ||
129 | + let certFileName = ref(''); | ||
130 | + let fileList1: any = ref<[]>([]); | ||
131 | + let fileList2: any = ref<[]>([]); | ||
132 | + let fileList3: any = ref<[]>([]); | ||
133 | + const credentialsV: any = reactive({ | ||
134 | + credentials: { | ||
135 | + type: '', | ||
136 | + }, | ||
137 | + }); | ||
138 | + const sonValues: any = reactive({ | ||
139 | + configuration: {}, | ||
140 | + }); | ||
141 | + const [register, { validate, setFieldsValue, resetFields: defineClearFunc }] = useForm({ | ||
142 | + labelWidth: 120, | ||
143 | + schemas: modeMqttForm, | ||
144 | + actionColOptions: { | ||
145 | + span: 14, | ||
146 | + }, | ||
147 | + resetButtonOptions: { | ||
148 | + text: '上一步', | ||
149 | + }, | ||
150 | + resetFunc: customResetFunc, | ||
151 | + submitFunc: customSubmitFunc, | ||
152 | + }); | ||
153 | + | ||
154 | + /** | ||
155 | + * 上传图片 | ||
156 | + */ | ||
157 | + const handleChange = async (e, { file }) => { | ||
158 | + if (file.status === 'removed') { | ||
159 | + if (e == 'T') { | ||
160 | + fileList1.value = []; | ||
161 | + showImg1.value = false; | ||
162 | + showImg1Pic.value = ''; | ||
163 | + caCertFileName.value = ''; | ||
164 | + } else if (e == 'F') { | ||
165 | + fileList2.value = []; | ||
166 | + showImg2.value = false; | ||
167 | + showImg2Pic.value = ''; | ||
168 | + certFileName.value = ''; | ||
169 | + } else { | ||
170 | + fileList3.value = []; | ||
171 | + showImg3.value = false; | ||
172 | + showImg3Pic.value = ''; | ||
173 | + privateKeyFileName.value = ''; | ||
174 | + } | ||
175 | + } else { | ||
176 | + const isLt5M = file.size / 1024 / 1024 < 5; | ||
177 | + if (!isLt5M) { | ||
178 | + createMessage.error('图片大小不能超过5MB!'); | ||
179 | + } else { | ||
180 | + e == 'T' | ||
181 | + ? (fileList1.value = [file]) | ||
182 | + : e == 'F' | ||
183 | + ? (fileList2.value = [file]) | ||
184 | + : (fileList3.value = [file]); | ||
185 | + const formData = new FormData(); | ||
186 | + formData.append('file', file); | ||
187 | + const response = await uploadApi(formData); | ||
188 | + if (response.fileStaticUri) { | ||
189 | + if (e == 'T') { | ||
190 | + caCertFileName.value = response.fileStaticUri; | ||
191 | + const iscaCertFileNamePic = caCertFileName.value.split('.').pop(); | ||
192 | + if ( | ||
193 | + iscaCertFileNamePic == 'jpg' || | ||
194 | + iscaCertFileNamePic == 'png' || | ||
195 | + iscaCertFileNamePic == 'jpeg' || | ||
196 | + iscaCertFileNamePic == 'gif' | ||
197 | + ) { | ||
198 | + showImg1.value = true; | ||
199 | + showImg1Pic.value = response.fileStaticUri; | ||
200 | + } else { | ||
201 | + showImg1.value = false; | ||
202 | + } | ||
203 | + } else if (e == 'F') { | ||
204 | + certFileName.value = response.fileStaticUri; | ||
205 | + const iscertFileNamePic = certFileName.value.split('.').pop(); | ||
206 | + if ( | ||
207 | + iscertFileNamePic == 'jpg' || | ||
208 | + iscertFileNamePic == 'png' || | ||
209 | + iscertFileNamePic == 'jpeg' || | ||
210 | + iscertFileNamePic == 'gif' | ||
211 | + ) { | ||
212 | + showImg2.value = true; | ||
213 | + showImg2Pic.value = response.fileStaticUri; | ||
214 | + } else { | ||
215 | + showImg2.value = false; | ||
216 | + } | ||
217 | + } else { | ||
218 | + privateKeyFileName.value = response.fileStaticUri; | ||
219 | + const isprivateKeyFileNamePic = privateKeyFileName.value.split('.').pop(); | ||
220 | + if ( | ||
221 | + isprivateKeyFileNamePic == 'jpg' || | ||
222 | + isprivateKeyFileNamePic == 'png' || | ||
223 | + isprivateKeyFileNamePic == 'jpeg' || | ||
224 | + isprivateKeyFileNamePic == 'gif' | ||
225 | + ) { | ||
226 | + showImg3.value = true; | ||
227 | + showImg3Pic.value = response.fileStaticUri; | ||
228 | + } else { | ||
229 | + showImg3.value = false; | ||
230 | + } | ||
231 | + } | ||
232 | + } | ||
233 | + } | ||
234 | + } | ||
235 | + }; | ||
236 | + const setStepTwoFieldsValueFunc = (v, v1, v2) => { | ||
237 | + setFieldsValue(v); | ||
238 | + setFieldsValue({ | ||
239 | + name: v1, | ||
240 | + description: v2, | ||
241 | + }); | ||
242 | + setFieldsValue({ | ||
243 | + password: v.credentials?.password, | ||
244 | + username: v.credentials?.username, | ||
245 | + type: v.credentials?.type, | ||
246 | + }); | ||
247 | + fileList1.value = [ | ||
248 | + { | ||
249 | + name: v.credentials?.caCertFileName.slice(39), | ||
250 | + uid: '1', | ||
251 | + }, | ||
252 | + ]; | ||
253 | + fileList2.value = [ | ||
254 | + { | ||
255 | + name: v.credentials?.certFileName.slice(39), | ||
256 | + uid: '2', | ||
257 | + }, | ||
258 | + ]; | ||
259 | + fileList3.value = [ | ||
260 | + { | ||
261 | + name: v.credentials?.privateKeyFileName.slice(39), | ||
262 | + uid: '3', | ||
263 | + }, | ||
264 | + ]; | ||
265 | + caCertFileName.value = v.credentials?.caCertFileName; | ||
266 | + certFileName.value = v.credentials?.certFileName; | ||
267 | + privateKeyFileName.value = v.credentials?.privateKeyFileName; | ||
268 | + const iscaCertFileNamePic = v.credentials?.caCertFileName.split('.').pop(); | ||
269 | + const iscertFileNamePic = v.credentials?.certFileName.split('.').pop(); | ||
270 | + const isprivateKeyFileNamePic = v.credentials?.privateKeyFileName.split('.').pop(); | ||
271 | + if ( | ||
272 | + iscaCertFileNamePic == 'jpg' || | ||
273 | + iscaCertFileNamePic == 'png' || | ||
274 | + iscaCertFileNamePic == 'jpeg' || | ||
275 | + iscaCertFileNamePic == 'gif' | ||
276 | + ) { | ||
277 | + showImg1.value = true; | ||
278 | + showImg1Pic.value = v.credentials?.caCertFileName; | ||
279 | + } else { | ||
280 | + showImg1.value = false; | ||
281 | + } | ||
282 | + if ( | ||
283 | + iscertFileNamePic == 'jpg' || | ||
284 | + iscertFileNamePic == 'png' || | ||
285 | + iscertFileNamePic == 'jpeg' || | ||
286 | + iscertFileNamePic == 'gif' | ||
287 | + ) { | ||
288 | + showImg2.value = true; | ||
289 | + showImg2Pic.value = v.credentials?.certFileName; | ||
290 | + } else { | ||
291 | + showImg2.value = false; | ||
292 | + } | ||
293 | + if ( | ||
294 | + isprivateKeyFileNamePic == 'jpg' || | ||
295 | + isprivateKeyFileNamePic == 'png' || | ||
296 | + isprivateKeyFileNamePic == 'jpeg' || | ||
297 | + isprivateKeyFileNamePic == 'gif' | ||
298 | + ) { | ||
299 | + showImg3.value = true; | ||
300 | + showImg3Pic.value = v.credentials?.privateKeyFileName; | ||
301 | + } else { | ||
302 | + showImg3.value = false; | ||
303 | + } | ||
304 | + }; | ||
305 | + const customClearStepTwoValueFunc = async () => { | ||
306 | + nextTick(() => { | ||
307 | + defineClearFunc(); | ||
308 | + fileList1.value = []; | ||
309 | + fileList2.value = []; | ||
310 | + fileList3.value = []; | ||
311 | + caCertFileName.value = ''; | ||
312 | + privateKeyFileName.value = ''; | ||
313 | + certFileName.value = ''; | ||
314 | + showImg1.value = false; | ||
315 | + showImg1Pic.value = ''; | ||
316 | + showImg2.value = false; | ||
317 | + showImg2Pic.value = ''; | ||
318 | + showImg3.value = false; | ||
319 | + showImg3Pic.value = ''; | ||
320 | + }); | ||
321 | + }; | ||
322 | + async function customResetFunc() { | ||
323 | + emit('prev'); | ||
324 | + } | ||
325 | + async function customSubmitFunc() { | ||
326 | + try { | ||
327 | + const values = await validate(); | ||
328 | + emit('next', values); | ||
329 | + } catch (error) { | ||
330 | + } finally { | ||
331 | + } | ||
332 | + } | ||
333 | + const getSonValueFunc = async () => { | ||
334 | + sonValues.configuration = await validate(); | ||
335 | + credentialsV.credentials.type = sonValues.configuration.type; | ||
336 | + if (credentialsV.credentials.type == CredentialsEnum.IS_BASIC) { | ||
337 | + credentialsV.credentials.username = sonValues.configuration.username; | ||
338 | + credentialsV.credentials.password = sonValues.configuration.password; | ||
339 | + sonValues.configuration.username = undefined; | ||
340 | + sonValues.configuration.password = undefined; | ||
341 | + } else if (credentialsV.credentials.type == CredentialsEnum.IS_PEM) { | ||
342 | + credentialsV.credentials.caCertFileName = caCertFileName.value; | ||
343 | + credentialsV.credentials.certFileName = certFileName.value; | ||
344 | + credentialsV.credentials.privateKeyFileName = privateKeyFileName.value; | ||
345 | + } | ||
346 | + if (!sonValues.configuration.clientId) { | ||
347 | + sonValues.configuration.clientId = null; | ||
348 | + } | ||
349 | + Object.assign(sonValues.configuration, credentialsV); | ||
350 | + return sonValues; | ||
351 | + }; | ||
352 | + return { | ||
353 | + getSonValueFunc, | ||
354 | + register, | ||
355 | + setStepTwoFieldsValueFunc, | ||
356 | + customClearStepTwoValueFunc, | ||
357 | + fileList1, | ||
358 | + fileList2, | ||
359 | + fileList3, | ||
360 | + handleChange, | ||
361 | + caCertFileName, | ||
362 | + privateKeyFileName, | ||
363 | + certFileName, | ||
364 | + showImg1, | ||
365 | + showImg1Pic, | ||
366 | + showImg2, | ||
367 | + showImg2Pic, | ||
368 | + showImg3, | ||
369 | + showImg3Pic, | ||
370 | + }; | ||
371 | + }, | ||
372 | + }); | ||
373 | +</script> | ||
374 | + | ||
375 | +<style lang="less" scoped> | ||
376 | + :deep(.ant-col-24) { | ||
377 | + margin-bottom: 20px !important; | ||
378 | + } | ||
379 | + | ||
380 | + :deep(.ant-btn-default) { | ||
381 | + color: white; | ||
382 | + background: #377dff; | ||
383 | + } | ||
384 | +</style> |