Showing
23 changed files
with
333 additions
and
275 deletions
1 | { | 1 | { |
2 | - "i18n-ally.localesPaths": [ | ||
3 | - "src/locales", | ||
4 | - "src/locales/lang", | ||
5 | - "public/resource/tinymce/langs" | ||
6 | - ], | ||
7 | - "commentTranslate.targetLanguage": "en", | ||
8 | - "cSpell.words": [ | ||
9 | - "Cmds", | ||
10 | - "unref" | ||
11 | - ] | 2 | + "i18n-ally.localesPaths": ["src/locales", "src/locales/lang", "public/resource/tinymce/langs"] |
12 | } | 3 | } |
build/vite/plugin/dropConsoleInVue3VideoPlay.ts
deleted
100644 → 0
1 | -import { Plugin } from 'vite'; | ||
2 | - | ||
3 | -export function dropConsoleInVue3VideoPlayPlugin(params?: { enabled: boolean }) { | ||
4 | - const { enabled = true } = params || {}; | ||
5 | - return { | ||
6 | - name: 'drop-console-in-vue3-video-play-plugin', | ||
7 | - transform(src, id) { | ||
8 | - if (enabled) { | ||
9 | - if (id.includes('vue3-video-play')) { | ||
10 | - return { | ||
11 | - code: src.replaceAll('console.log', ''), | ||
12 | - }; | ||
13 | - } | ||
14 | - } | ||
15 | - }, | ||
16 | - } as Plugin; | ||
17 | -} |
@@ -16,7 +16,6 @@ import { configThemePlugin } from './theme'; | @@ -16,7 +16,6 @@ import { configThemePlugin } from './theme'; | ||
16 | import { configImageminPlugin } from './imagemin'; | 16 | import { configImageminPlugin } from './imagemin'; |
17 | import { configSvgIconsPlugin } from './svgSprite'; | 17 | import { configSvgIconsPlugin } from './svgSprite'; |
18 | import { configHmrPlugin } from './hmr'; | 18 | import { configHmrPlugin } from './hmr'; |
19 | -import { dropConsoleInVue3VideoPlayPlugin } from './dropConsoleInVue3VideoPlay'; | ||
20 | 19 | ||
21 | export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { | 20 | export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { |
22 | const { | 21 | const { |
@@ -64,8 +63,6 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { | @@ -64,8 +63,6 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { | ||
64 | //vite-plugin-theme | 63 | //vite-plugin-theme |
65 | vitePlugins.push(configThemePlugin(isBuild)); | 64 | vitePlugins.push(configThemePlugin(isBuild)); |
66 | 65 | ||
67 | - vitePlugins.push(dropConsoleInVue3VideoPlayPlugin()); | ||
68 | - | ||
69 | // The following plugins only work in the production environment | 66 | // The following plugins only work in the production environment |
70 | if (isBuild) { | 67 | if (isBuild) { |
71 | //vite-plugin-imagemin | 68 | //vite-plugin-imagemin |
@@ -63,13 +63,13 @@ | @@ -63,13 +63,13 @@ | ||
63 | "sortablejs": "^1.14.0", | 63 | "sortablejs": "^1.14.0", |
64 | "tinymce": "^5.8.2", | 64 | "tinymce": "^5.8.2", |
65 | "vditor": "^3.8.6", | 65 | "vditor": "^3.8.6", |
66 | + "video.js": "^7.20.3", | ||
66 | "vue": "^3.2.31", | 67 | "vue": "^3.2.31", |
67 | "vue-i18n": "9.1.7", | 68 | "vue-i18n": "9.1.7", |
68 | "vue-json-pretty": "^2.0.4", | 69 | "vue-json-pretty": "^2.0.4", |
69 | "vue-router": "^4.0.11", | 70 | "vue-router": "^4.0.11", |
70 | "vue-types": "^4.0.3", | 71 | "vue-types": "^4.0.3", |
71 | "vue3-grid-layout": "^1.0.0", | 72 | "vue3-grid-layout": "^1.0.0", |
72 | - "vue3-video-play": "^1.3.1-beta.4", | ||
73 | "xlsx": "^0.17.0" | 73 | "xlsx": "^0.17.0" |
74 | }, | 74 | }, |
75 | "devDependencies": { | 75 | "devDependencies": { |
@@ -90,6 +90,7 @@ | @@ -90,6 +90,7 @@ | ||
90 | "@types/qrcode": "^1.4.1", | 90 | "@types/qrcode": "^1.4.1", |
91 | "@types/qs": "^6.9.7", | 91 | "@types/qs": "^6.9.7", |
92 | "@types/sortablejs": "^1.10.7", | 92 | "@types/sortablejs": "^1.10.7", |
93 | + "@types/video.js": "^7.3.49", | ||
93 | "@typescript-eslint/eslint-plugin": "^4.29.1", | 94 | "@typescript-eslint/eslint-plugin": "^4.29.1", |
94 | "@typescript-eslint/parser": "^4.29.1", | 95 | "@typescript-eslint/parser": "^4.29.1", |
95 | "@vitejs/plugin-legacy": "^1.5.1", | 96 | "@vitejs/plugin-legacy": "^1.5.1", |
src/components/Video/index.ts
0 → 100644
src/components/Video/src/VideoPlay.vue
0 → 100644
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 | + | ||
8 | + const { prefixCls } = useDesign('basic-video-play'); | ||
9 | + | ||
10 | + const props = defineProps<{ | ||
11 | + options?: VideoJsPlayerOptions; | ||
12 | + }>(); | ||
13 | + | ||
14 | + const emit = defineEmits<{ | ||
15 | + (event: 'ready', instance?: Nullable<VideoJsPlayer>): void; | ||
16 | + }>(); | ||
17 | + | ||
18 | + const videoPlayEl = ref<HTMLVideoElement>(); | ||
19 | + | ||
20 | + const videoPlayInstance = ref<Nullable<VideoJsPlayer>>(); | ||
21 | + | ||
22 | + const getOptions = computed(() => { | ||
23 | + const { options } = props; | ||
24 | + const defaultOptions: VideoJsPlayerOptions = { | ||
25 | + language: 'zh', | ||
26 | + muted: true, | ||
27 | + liveui: true, | ||
28 | + controls: true, | ||
29 | + }; | ||
30 | + return { ...defaultOptions, ...options }; | ||
31 | + }); | ||
32 | + | ||
33 | + const getWidthHeight = computed(() => { | ||
34 | + let { width = 300, height = 150 } = unref(getOptions); | ||
35 | + width = isNumber(width) ? (`${width}px` as unknown as number) : width; | ||
36 | + height = isNumber(height) ? (`${height}px` as unknown as number) : height; | ||
37 | + return { width, height } as CSSProperties; | ||
38 | + }); | ||
39 | + | ||
40 | + const init = () => { | ||
41 | + videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => { | ||
42 | + emit('ready', unref(videoPlayInstance)); | ||
43 | + }); | ||
44 | + }; | ||
45 | + | ||
46 | + onMounted(() => { | ||
47 | + init(); | ||
48 | + }); | ||
49 | + | ||
50 | + onUnmounted(() => { | ||
51 | + unref(videoPlayInstance)?.dispose(); | ||
52 | + videoPlayInstance.value = null; | ||
53 | + }); | ||
54 | +</script> | ||
55 | + | ||
56 | +<template> | ||
57 | + <div :class="prefixCls" class="w-full h-full" :style="getWidthHeight"> | ||
58 | + <video | ||
59 | + ref="videoPlayEl" | ||
60 | + class="video-js vjs-big-play-centered vjs-show-big-play-button-on-pause !w-full !h-full" | ||
61 | + ></video> | ||
62 | + </div> | ||
63 | +</template> | ||
64 | + | ||
65 | +<style lang="less"> | ||
66 | + @prefix-cls: ~'@{namespace}-basic-video-play'; | ||
67 | + | ||
68 | + .@{prefix-cls} { | ||
69 | + .vjs-error-display { | ||
70 | + .vjs-modal-dialog-content::after { | ||
71 | + content: '无法加载视频,原因可能是服务器或网络故障,也可能是格式不支持.'; | ||
72 | + } | ||
73 | + } | ||
74 | + } | ||
75 | +</style> |
src/components/Video/src/utils.ts
0 → 100644
1 | +export enum VideoPlayerType { | ||
2 | + m3u8 = 'application/x-mpegURL', | ||
3 | + mp4 = 'video/mp4', | ||
4 | + webm = 'video/webm', | ||
5 | +} | ||
6 | + | ||
7 | +export const getVideoTypeByUrl = (url: string) => { | ||
8 | + const splitExtReg = /(?:.*)(?<=\.)/; | ||
9 | + | ||
10 | + const type = url.replace(splitExtReg, ''); | ||
11 | + | ||
12 | + if (VideoPlayerType[type]) return VideoPlayerType[type]; | ||
13 | + | ||
14 | + return VideoPlayerType.mp4; | ||
15 | +}; |
@@ -383,7 +383,7 @@ export const MediaTypeValidate: Rule[] = [ | @@ -383,7 +383,7 @@ export const MediaTypeValidate: Rule[] = [ | ||
383 | required: true, | 383 | required: true, |
384 | validator: (_, value: string) => { | 384 | validator: (_, value: string) => { |
385 | const reg = /(?:.*)(?<=\.)/; | 385 | const reg = /(?:.*)(?<=\.)/; |
386 | - const type = value.replace(reg, ''); | 386 | + const type = (value || '').replace(reg, ''); |
387 | if (type !== MediaType.M3U8) { | 387 | if (type !== MediaType.M3U8) { |
388 | return Promise.reject('视频流只支持m3u8格式'); | 388 | return Promise.reject('视频流只支持m3u8格式'); |
389 | } | 389 | } |
@@ -3,117 +3,63 @@ | @@ -3,117 +3,63 @@ | ||
3 | <BasicModal | 3 | <BasicModal |
4 | v-bind="$attrs" | 4 | v-bind="$attrs" |
5 | width="55rem" | 5 | width="55rem" |
6 | + destroyOnClose | ||
6 | :height="heightNum" | 7 | :height="heightNum" |
7 | @register="register" | 8 | @register="register" |
8 | title="视频预览" | 9 | title="视频预览" |
9 | - @cancel="handleCancel" | ||
10 | :showOkBtn="false" | 10 | :showOkBtn="false" |
11 | + @cancel="handleCancel" | ||
11 | > | 12 | > |
12 | - <div class="video-sty"> | ||
13 | - <div> | ||
14 | - <videoPlay | ||
15 | - v-if="showVideo" | ||
16 | - ref="video" | ||
17 | - style="display: inline-block; width: 100%" | ||
18 | - v-bind="options" | ||
19 | - /> | ||
20 | - </div> | 13 | + <div class="flex items-center justify-center bg-dark-900 w-full h-full min-h-52"> |
14 | + <BasicVideoPlay v-if="showVideo" :options="options" /> | ||
21 | </div> | 15 | </div> |
22 | - <!-- <div | ||
23 | - class="bg-black h-80 text-light-50 w-full h-full flex justify-center items-center" | ||
24 | - v-if="!showVideo" | ||
25 | - > | ||
26 | - 视频播放出错啦! | ||
27 | - </div> --> | ||
28 | </BasicModal> | 16 | </BasicModal> |
29 | </div> | 17 | </div> |
30 | </template> | 18 | </template> |
31 | <script setup lang="ts"> | 19 | <script setup lang="ts"> |
32 | - import { ref, nextTick, reactive } from 'vue'; | 20 | + import { ref, reactive } from 'vue'; |
33 | import { BasicModal, useModalInner } from '/@/components/Modal'; | 21 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
34 | import type { StreamingManageRecord, CameraModel } from '/@/api/camera/model/cameraModel'; | 22 | import type { StreamingManageRecord, CameraModel } from '/@/api/camera/model/cameraModel'; |
35 | - import { videoPlay } from 'vue3-video-play'; // 引入组件 | ||
36 | - import 'vue3-video-play/dist/style.css'; // 引入css | ||
37 | - import { AccessMode, MediaType } from './config.data'; | 23 | + import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video'; |
24 | + import { AccessMode } from './config.data'; | ||
38 | import { getStreamingPlayUrl } from '/@/api/camera/cameraManager'; | 25 | import { getStreamingPlayUrl } from '/@/api/camera/cameraManager'; |
26 | + import { VideoJsPlayerOptions } from 'video.js'; | ||
39 | 27 | ||
40 | const heightNum = ref(800); | 28 | const heightNum = ref(800); |
41 | const showVideo = ref(false); | 29 | const showVideo = ref(false); |
42 | - const options = reactive({ | ||
43 | - width: '800px', | ||
44 | - height: '450px', | ||
45 | - color: '#409eff', | ||
46 | - muted: false, //静音 | ||
47 | - webFullScreen: false, | ||
48 | - autoPlay: true, //自动播放 | ||
49 | - currentTime: 0, | ||
50 | - loop: false, //循环播放 | ||
51 | - mirror: false, //镜像画面 | ||
52 | - ligthOff: false, //关灯模式 | ||
53 | - volume: 0.3, //默认音量大小 | ||
54 | - control: true, //是否显示控制器 | ||
55 | - title: '', //视频名称 | ||
56 | - type: 'm3u8', | ||
57 | - src: '', //视频源 | ||
58 | - controlBtns: [ | ||
59 | - 'audioTrack', | ||
60 | - 'quality', | ||
61 | - 'speedRate', | ||
62 | - 'volume', | ||
63 | - 'setting', | ||
64 | - 'pip', | ||
65 | - 'pageFullScreen', | ||
66 | - 'fullScreen', | ||
67 | - ], | 30 | + const options = reactive<VideoJsPlayerOptions>({ |
31 | + width: '100%' as unknown as number, | ||
32 | + height: '100%' as unknown as number, | ||
33 | + autoplay: true, | ||
68 | }); | 34 | }); |
69 | - const video: any = ref(null); | ||
70 | 35 | ||
71 | - nextTick(() => { | ||
72 | - console.log(video.value); | ||
73 | - }); | ||
74 | - | ||
75 | - const getMediaType = (suffix: string) => { | ||
76 | - return suffix === MediaType.M3U8 ? suffix : `video/${suffix}`; | 36 | + const setSources = (url: string) => { |
37 | + options.sources = [ | ||
38 | + { | ||
39 | + src: url, | ||
40 | + type: getVideoTypeByUrl(url), | ||
41 | + }, | ||
42 | + ]; | ||
77 | }; | 43 | }; |
78 | 44 | ||
79 | const [register] = useModalInner( | 45 | const [register] = useModalInner( |
80 | async (data: { record: CameraModel | StreamingManageRecord }) => { | 46 | async (data: { record: CameraModel | StreamingManageRecord }) => { |
81 | - let reg = /(?:.*)(?<=\.)/; | ||
82 | const { record } = data; | 47 | const { record } = data; |
83 | if (record.accessMode === AccessMode.ManuallyEnter) { | 48 | if (record.accessMode === AccessMode.ManuallyEnter) { |
84 | if ((record as CameraModel).videoUrl) { | 49 | if ((record as CameraModel).videoUrl) { |
85 | - const type = (record as CameraModel).videoUrl.replace(reg, ''); | ||
86 | - showVideo.value = true; | ||
87 | - options.type = getMediaType(type); | ||
88 | - options.src = (record as CameraModel).videoUrl; | ||
89 | - options.autoPlay = true; | 50 | + setSources((record as CameraModel).videoUrl); |
90 | } | 51 | } |
91 | } else { | 52 | } else { |
92 | try { | 53 | try { |
93 | const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!); | 54 | const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!); |
94 | - showVideo.value = true; | ||
95 | - options.src = url; | ||
96 | - const type = (url as CameraModel).videoUrl.replace(reg, ''); | ||
97 | - options.type = getMediaType(type); | ||
98 | - } catch (error) { | ||
99 | - } finally { | ||
100 | - showVideo.value = true; | ||
101 | - } | 55 | + setSources(url); |
56 | + } catch (error) {} | ||
102 | } | 57 | } |
58 | + showVideo.value = true; | ||
103 | } | 59 | } |
104 | ); | 60 | ); |
105 | 61 | ||
106 | const handleCancel = () => { | 62 | const handleCancel = () => { |
107 | - //关闭暂停播放视频 | ||
108 | - options.src = ''; | ||
109 | - video.value.pause(); | 63 | + showVideo.value = false; |
110 | }; | 64 | }; |
111 | </script> | 65 | </script> |
112 | -<style> | ||
113 | - .video-sty { | ||
114 | - width: 100%; | ||
115 | - display: flex; | ||
116 | - align-items: center; | ||
117 | - justify-content: center; | ||
118 | - } | ||
119 | -</style> |
@@ -5,21 +5,27 @@ | @@ -5,21 +5,27 @@ | ||
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 } from '/@/api/camera/cameraManager'; |
7 | import { CameraRecord } from '/@/api/camera/model/cameraModel'; | 7 | import { CameraRecord } from '/@/api/camera/model/cameraModel'; |
8 | - import { videoPlay as VideoPlay } from 'vue3-video-play'; | ||
9 | - import 'vue3-video-play/dist/style.css'; | ||
10 | import { useFullscreen } from '@vueuse/core'; | 8 | import { useFullscreen } from '@vueuse/core'; |
11 | import CameraDrawer from './CameraDrawer.vue'; | 9 | import CameraDrawer from './CameraDrawer.vue'; |
12 | import { useDrawer } from '/@/components/Drawer'; | 10 | import { useDrawer } from '/@/components/Drawer'; |
13 | - import { AccessMode, MediaType, PageMode } from './config.data'; | 11 | + import { AccessMode, PageMode } from './config.data'; |
14 | import SvgIcon from '/@/components/Icon/src/SvgIcon.vue'; | 12 | import SvgIcon from '/@/components/Icon/src/SvgIcon.vue'; |
15 | - import { isDef } from '/@/utils/is'; | ||
16 | import { getStreamingPlayUrl } from '/@/api/camera/cameraManager'; | 13 | import { getStreamingPlayUrl } from '/@/api/camera/cameraManager'; |
17 | import { buildUUID } from '/@/utils/uuid'; | 14 | import { buildUUID } from '/@/utils/uuid'; |
15 | + import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video'; | ||
16 | + import { VideoJsPlayerOptions } from 'video.js'; | ||
17 | + import { getBoundingClientRect } from '/@/utils/domUtils'; | ||
18 | 18 | ||
19 | type CameraRecordItem = CameraRecord & { | 19 | type CameraRecordItem = CameraRecord & { |
20 | canPlay?: boolean; | 20 | canPlay?: boolean; |
21 | - type?: string; | ||
22 | isTransform?: boolean; | 21 | isTransform?: boolean; |
22 | + videoPlayerOptions?: VideoJsPlayerOptions; | ||
23 | + }; | ||
24 | + | ||
25 | + const basicVideoPlayOptions: VideoJsPlayerOptions = { | ||
26 | + width: '100%' as unknown as number, | ||
27 | + height: '100%' as unknown as number, | ||
28 | + autoplay: true, | ||
23 | }; | 29 | }; |
24 | 30 | ||
25 | const emit = defineEmits(['switchMode']); | 31 | const emit = defineEmits(['switchMode']); |
@@ -36,32 +42,6 @@ | @@ -36,32 +42,6 @@ | ||
36 | total: 0, | 42 | total: 0, |
37 | }); | 43 | }); |
38 | 44 | ||
39 | - const options = reactive({ | ||
40 | - width: '200px', | ||
41 | - height: '200px', | ||
42 | - color: '#409eff', | ||
43 | - muted: true, //静音 | ||
44 | - webFullScreen: false, | ||
45 | - autoPlay: true, //自动播放 | ||
46 | - currentTime: 0, | ||
47 | - loop: false, //循环播放 | ||
48 | - mirror: false, //镜像画面 | ||
49 | - ligthOff: false, //关灯模式 | ||
50 | - volume: 0.3, //默认音量大小 | ||
51 | - control: true, //是否显示控制器 | ||
52 | - type: 'm3u8', | ||
53 | - controlBtns: [ | ||
54 | - 'audioTrack', | ||
55 | - 'quality', | ||
56 | - 'speedRate', | ||
57 | - 'volume', | ||
58 | - 'setting', | ||
59 | - 'pip', | ||
60 | - 'pageFullScreen', | ||
61 | - 'fullScreen', | ||
62 | - ], | ||
63 | - }); | ||
64 | - | ||
65 | // 树形选择器 | 45 | // 树形选择器 |
66 | const handleSelect = (orgId: string) => { | 46 | const handleSelect = (orgId: string) => { |
67 | organizationId.value = orgId; | 47 | organizationId.value = orgId; |
@@ -80,7 +60,6 @@ | @@ -80,7 +60,6 @@ | ||
80 | pagination.total = total; | 60 | pagination.total = total; |
81 | 61 | ||
82 | for (const item of items) { | 62 | for (const item of items) { |
83 | - // await beforeVideoPlay(item); | ||
84 | (item as CameraRecordItem).isTransform = false; | 63 | (item as CameraRecordItem).isTransform = false; |
85 | beforeVideoPlay(item); | 64 | beforeVideoPlay(item); |
86 | } | 65 | } |
@@ -97,30 +76,39 @@ | @@ -97,30 +76,39 @@ | ||
97 | loading.value = false; | 76 | loading.value = false; |
98 | } | 77 | } |
99 | }; | 78 | }; |
100 | - const getMediaType = (suffix: string) => { | ||
101 | - return suffix === MediaType.M3U8 ? suffix : `video/${suffix}`; | ||
102 | - }; | ||
103 | 79 | ||
104 | const beforeVideoPlay = async (record: CameraRecordItem) => { | 80 | const beforeVideoPlay = async (record: CameraRecordItem) => { |
105 | - let reg = /(?:.*)(?<=\.)/; | ||
106 | if (record.accessMode === AccessMode.ManuallyEnter) { | 81 | if (record.accessMode === AccessMode.ManuallyEnter) { |
107 | if (record.videoUrl) { | 82 | if (record.videoUrl) { |
108 | - const type = record.videoUrl.replace(reg, ''); | ||
109 | - record.type = getMediaType(type); | 83 | + (record as CameraRecordItem).videoPlayerOptions = { |
84 | + ...basicVideoPlayOptions, | ||
85 | + sources: [ | ||
86 | + { | ||
87 | + src: record.videoUrl, | ||
88 | + type: getVideoTypeByUrl(record.videoUrl), | ||
89 | + }, | ||
90 | + ], | ||
91 | + }; | ||
110 | record.isTransform = true; | 92 | record.isTransform = true; |
111 | } | 93 | } |
112 | } | 94 | } |
113 | if (record.accessMode === AccessMode.Streaming) { | 95 | if (record.accessMode === AccessMode.Streaming) { |
114 | try { | 96 | try { |
115 | const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!); | 97 | const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!); |
116 | - const type = url.replace(reg, ''); | ||
117 | const index = unref(cameraList).findIndex((item) => item.id === record.id); | 98 | const index = unref(cameraList).findIndex((item) => item.id === record.id); |
118 | if (~index) { | 99 | if (~index) { |
119 | const oldRecord = unref(cameraList).at(index)!; | 100 | const oldRecord = unref(cameraList).at(index)!; |
120 | unref(cameraList)[index] = { | 101 | unref(cameraList)[index] = { |
121 | ...oldRecord, | 102 | ...oldRecord, |
122 | - videoUrl: url, | ||
123 | - type: getMediaType(type), | 103 | + videoPlayerOptions: { |
104 | + ...basicVideoPlayOptions, | ||
105 | + sources: [ | ||
106 | + { | ||
107 | + src: url, | ||
108 | + type: getVideoTypeByUrl(url), | ||
109 | + }, | ||
110 | + ], | ||
111 | + }, | ||
124 | isTransform: true, | 112 | isTransform: true, |
125 | }; | 113 | }; |
126 | } | 114 | } |
@@ -160,20 +148,6 @@ | @@ -160,20 +148,6 @@ | ||
160 | } | 148 | } |
161 | }; | 149 | }; |
162 | 150 | ||
163 | - const handleLoadStart = (record: CameraRecordItem) => { | ||
164 | - const index = unref(cameraList).findIndex((item) => item.id === record.id); | ||
165 | - setTimeout(() => { | ||
166 | - ~index && | ||
167 | - !unref(cameraList).at(index)!.canPlay && | ||
168 | - (unref(cameraList).at(index)!.canPlay = false); | ||
169 | - }, 30000); | ||
170 | - }; | ||
171 | - | ||
172 | - const handleLoadData = (record: CameraRecordItem) => { | ||
173 | - const index = unref(cameraList).findIndex((item) => item.id === record.id); | ||
174 | - ~index && (unref(cameraList).at(index)!.canPlay = true); | ||
175 | - }; | ||
176 | - | ||
177 | const [registerDrawer, { openDrawer }] = useDrawer(); | 151 | const [registerDrawer, { openDrawer }] = useDrawer(); |
178 | 152 | ||
179 | const handleAddCamera = () => { | 153 | const handleAddCamera = () => { |
@@ -185,13 +159,26 @@ | @@ -185,13 +159,26 @@ | ||
185 | onMounted(() => { | 159 | onMounted(() => { |
186 | getCameraList(); | 160 | getCameraList(); |
187 | }); | 161 | }); |
162 | + | ||
163 | + const listEl = ref(); | ||
164 | + onMounted(() => { | ||
165 | + const clientHeight = document.documentElement.clientHeight; | ||
166 | + const rect = getBoundingClientRect(unref(listEl)!.$el!) as DOMRect; | ||
167 | + // list pading top 8 maring-top 8 extra slot 56 | ||
168 | + const listContainerMarginBottom = 16; | ||
169 | + const listContainerHeight = clientHeight - rect.top - listContainerMarginBottom; | ||
170 | + const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector( | ||
171 | + '.ant-spin-container' | ||
172 | + ) as HTMLElement; | ||
173 | + listContainerEl && (listContainerEl.style.height = listContainerHeight + 'px'); | ||
174 | + }); | ||
188 | </script> | 175 | </script> |
189 | 176 | ||
190 | <template> | 177 | <template> |
191 | <div> | 178 | <div> |
192 | <PageWrapper dense contentFullHeight contentClass="flex"> | 179 | <PageWrapper dense contentFullHeight contentClass="flex"> |
193 | <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" /> | 180 | <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" /> |
194 | - <section class="p-4 pl-9 split-screen-mode flex flex-col flex-auto w-3/4 xl:w-4/5"> | 181 | + <section class="p-4 split-screen-mode flex flex-col flex-auto w-3/4 xl:w-4/5"> |
195 | <div class="p-3 bg-light-50 flex justify-between mb-4 dark:bg-dark-900"> | 182 | <div class="p-3 bg-light-50 flex justify-between mb-4 dark:bg-dark-900"> |
196 | <div class="flex gap-4 cursor-pointer items-center"> | 183 | <div class="flex gap-4 cursor-pointer items-center"> |
197 | <div | 184 | <div |
@@ -247,6 +234,7 @@ | @@ -247,6 +234,7 @@ | ||
247 | </div> | 234 | </div> |
248 | <section ref="videoContainer" class="flex-auto"> | 235 | <section ref="videoContainer" class="flex-auto"> |
249 | <List | 236 | <List |
237 | + ref="listEl" | ||
250 | :loading="loading" | 238 | :loading="loading" |
251 | :data-source="cameraList" | 239 | :data-source="cameraList" |
252 | class="bg-light-50 w-full h-full dark:bg-dark-900 split-mode-list" | 240 | class="bg-light-50 w-full h-full dark:bg-dark-900 split-mode-list" |
@@ -264,24 +252,12 @@ | @@ -264,24 +252,12 @@ | ||
264 | v-if="!item.placeholder" | 252 | v-if="!item.placeholder" |
265 | class="bg-black w-full h-full overflow-hidden relative video-container" | 253 | class="bg-black w-full h-full overflow-hidden relative video-container" |
266 | > | 254 | > |
267 | - <Spin v-show="!item.isTransform" :spinning="!item.isTransform"> | ||
268 | - <div class="bg-black text-light-50"> </div> | ||
269 | - </Spin> | ||
270 | - <VideoPlay | ||
271 | - v-show="item.isTransform" | ||
272 | - @loadstart="handleLoadStart(item)" | ||
273 | - @loadeddata="handleLoadData(item)" | ||
274 | - v-bind="options" | ||
275 | - :src="item.videoUrl" | ||
276 | - :title="item.name" | ||
277 | - :type="item.type" | 255 | + <Spin |
256 | + class="!absolute top-1/2 left-1/2 transform -translate-1/2" | ||
257 | + v-show="!item.isTransform" | ||
258 | + :spinning="!item.isTransform" | ||
278 | /> | 259 | /> |
279 | - <div | ||
280 | - v-if="item.isTransform && isDef(item.canPlay) && !item.canPlay" | ||
281 | - class="video-container-error-msk absolute top-0 left-0 text-lg w-full h-full text-light-50 flex justify-center items-center z-50 bg-black" | ||
282 | - > | ||
283 | - 视频加载出错了! | ||
284 | - </div> | 260 | + <BasicVideoPlay v-if="item.isTransform" :options="item.videoPlayerOptions" /> |
285 | <div | 261 | <div |
286 | class="video-container-mask absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center items-center" | 262 | class="video-container-mask absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center items-center" |
287 | style="height: 100%; background-color: rgba(0, 0, 0, 0.5)" | 263 | style="height: 100%; background-color: rgba(0, 0, 0, 0.5)" |
@@ -313,24 +289,6 @@ | @@ -313,24 +289,6 @@ | ||
313 | height: 100%; | 289 | height: 100%; |
314 | } | 290 | } |
315 | 291 | ||
316 | - .split-screen-mode:deep(.d-player-wrap) { | ||
317 | - width: 100%; | ||
318 | - height: 100%; | ||
319 | - } | ||
320 | - | ||
321 | - .split-screen-mode:deep(.ant-tabs-tab-active) { | ||
322 | - border-bottom: 1px solid #eee; | ||
323 | - } | ||
324 | - | ||
325 | - .split-screen-mode:deep(video) { | ||
326 | - position: absolute; | ||
327 | - height: calc(100%) !important; | ||
328 | - } | ||
329 | - | ||
330 | - .split-screen-mode:deep(.d-player-control) { | ||
331 | - z-index: 99; | ||
332 | - } | ||
333 | - | ||
334 | .video-container { | 292 | .video-container { |
335 | .video-container-mask { | 293 | .video-container-mask { |
336 | opacity: 0; | 294 | opacity: 0; |
@@ -344,8 +302,6 @@ | @@ -344,8 +302,6 @@ | ||
344 | } | 302 | } |
345 | 303 | ||
346 | .video-container-error-msk { | 304 | .video-container-error-msk { |
347 | - // opacity: 0; | ||
348 | - // visibility: hidden; | ||
349 | color: #000; | 305 | color: #000; |
350 | } | 306 | } |
351 | } | 307 | } |
@@ -363,7 +319,6 @@ | @@ -363,7 +319,6 @@ | ||
363 | 319 | ||
364 | .split-mode-list:deep(.ant-col) { | 320 | .split-mode-list:deep(.ant-col) { |
365 | width: 100%; | 321 | width: 100%; |
366 | - // height: var(--height); | ||
367 | height: 100%; | 322 | height: 100%; |
368 | } | 323 | } |
369 | </style> | 324 | </style> |
@@ -173,8 +173,8 @@ export const formSchema: QFormSchema[] = [ | @@ -173,8 +173,8 @@ export const formSchema: QFormSchema[] = [ | ||
173 | { | 173 | { |
174 | field: 'videoUrl', | 174 | field: 'videoUrl', |
175 | label: '视频流', | 175 | label: '视频流', |
176 | - required: true, | ||
177 | component: 'Input', | 176 | component: 'Input', |
177 | + required: true, | ||
178 | ifShow({ values }) { | 178 | ifShow({ values }) { |
179 | return values.accessMode === AccessMode.ManuallyEnter; | 179 | return values.accessMode === AccessMode.ManuallyEnter; |
180 | }, | 180 | }, |
@@ -182,7 +182,7 @@ export const formSchema: QFormSchema[] = [ | @@ -182,7 +182,7 @@ export const formSchema: QFormSchema[] = [ | ||
182 | placeholder: '请输入视频流', | 182 | placeholder: '请输入视频流', |
183 | maxLength: 255, | 183 | maxLength: 255, |
184 | }, | 184 | }, |
185 | - rules: [...CameraVideoUrl, ...MediaTypeValidate, { required: true, message: '视频流是必填项' }], | 185 | + rules: [{ required: true, message: '视频流是必填项' }, ...CameraVideoUrl, ...MediaTypeValidate], |
186 | }, | 186 | }, |
187 | 187 | ||
188 | { | 188 | { |
1 | <template> | 1 | <template> |
2 | - <div class="organization-tree flex relative"> | ||
3 | - <div class="cursor-pointer flex py-4 fold-icon" :class="foldFlag ? 'absolute' : ''"> | 2 | + <div class="organization-tree flex relative items-center py-4" :class="foldFlag ? '' : 'pl-4'"> |
3 | + <div | ||
4 | + class="cursor-pointer flex py-4 fold-icon absolute rounded svg:fill-gray-400 hover:bg-gray-200" | ||
5 | + :class="foldFlag ? '' : '-right-4'" | ||
6 | + > | ||
4 | <div @click="handleFold"> | 7 | <div @click="handleFold"> |
5 | - <CaretRightOutlined :class="[foldFlag ? '' : 'rotate-180']" class="text-xl transform" /> | 8 | + <CaretRightOutlined |
9 | + :class="[foldFlag ? '' : 'rotate-180']" | ||
10 | + class="transform fill-gray-100" | ||
11 | + /> | ||
6 | </div> | 12 | </div> |
7 | </div> | 13 | </div> |
8 | - <div | ||
9 | - :style="{ width: foldFlag ? '0px' : '100%' }" | ||
10 | - :class="[foldFlag ? '' : 'my-4 ml-2']" | ||
11 | - class="bg-white mr-0 overflow-hidden" | ||
12 | - > | 14 | + <div :style="{ width: foldFlag ? '0px' : '100%' }" class="bg-white mr-0 overflow-hidden h-full"> |
13 | <BasicTree | 15 | <BasicTree |
14 | title="组织列表" | 16 | title="组织列表" |
15 | toolbar | 17 | toolbar |
@@ -161,7 +161,7 @@ | @@ -161,7 +161,7 @@ | ||
161 | <template> | 161 | <template> |
162 | <PageWrapper dense contentFullHeight contentClass="flex"> | 162 | <PageWrapper dense contentFullHeight contentClass="flex"> |
163 | <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" /> | 163 | <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" /> |
164 | - <section class="flex-auto pl-9 p-4 configuration-list"> | 164 | + <section class="flex-auto p-4 configuration-list"> |
165 | <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4"> | 165 | <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4"> |
166 | <BasicForm @register="registerForm" /> | 166 | <BasicForm @register="registerForm" /> |
167 | </div> | 167 | </div> |
@@ -4,6 +4,7 @@ import { deviceProfile, getGATEWAYdevice } from '/@/api/device/deviceManager'; | @@ -4,6 +4,7 @@ import { deviceProfile, getGATEWAYdevice } from '/@/api/device/deviceManager'; | ||
4 | 4 | ||
5 | export enum TypeEnum { | 5 | export enum TypeEnum { |
6 | IS_GATEWAY = 'GATEWAY', | 6 | IS_GATEWAY = 'GATEWAY', |
7 | + SENSOR = 'SENSOR', | ||
7 | } | 8 | } |
8 | export const isGateWay = (type: string) => { | 9 | export const isGateWay = (type: string) => { |
9 | return type === TypeEnum.IS_GATEWAY; | 10 | return type === TypeEnum.IS_GATEWAY; |
@@ -688,9 +689,39 @@ export const CommandSchemas: FormSchema[] = [ | @@ -688,9 +689,39 @@ export const CommandSchemas: FormSchema[] = [ | ||
688 | }, | 689 | }, |
689 | }, | 690 | }, |
690 | { | 691 | { |
692 | + field: 'valueType', | ||
693 | + label: '命令类型', | ||
694 | + component: 'RadioGroup', | ||
695 | + defaultValue: 'json', | ||
696 | + componentProps: () => { | ||
697 | + return { | ||
698 | + options: [ | ||
699 | + { label: 'JSON', value: 'json' }, | ||
700 | + { label: '字符串', value: 'string' }, | ||
701 | + ], | ||
702 | + }; | ||
703 | + }, | ||
704 | + }, | ||
705 | + { | ||
706 | + field: 'commandText', | ||
707 | + label: '请输入命令内容', | ||
708 | + ifShow: ({ model }) => { | ||
709 | + return model['valueType'] === 'string'; | ||
710 | + }, | ||
711 | + component: 'InputTextArea', | ||
712 | + componentProps: { | ||
713 | + autosize: { | ||
714 | + minRows: 6, | ||
715 | + }, | ||
716 | + }, | ||
717 | + }, | ||
718 | + { | ||
691 | field: 'commandValue', | 719 | field: 'commandValue', |
692 | label: '请输入命令内容', | 720 | label: '请输入命令内容', |
693 | slot: 'commandSlot', | 721 | slot: 'commandSlot', |
694 | component: 'InputTextArea', | 722 | component: 'InputTextArea', |
723 | + show: ({ model }) => { | ||
724 | + return model['valueType'] === 'json'; | ||
725 | + }, | ||
695 | }, | 726 | }, |
696 | ]; | 727 | ]; |
@@ -5,46 +5,60 @@ import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; | @@ -5,46 +5,60 @@ import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; | ||
5 | import { getCustomerList } from '/@/api/device/deviceManager'; | 5 | import { getCustomerList } from '/@/api/device/deviceManager'; |
6 | import { DescItem } from '/@/components/Description/index'; | 6 | import { DescItem } from '/@/components/Description/index'; |
7 | import moment from 'moment'; | 7 | import moment from 'moment'; |
8 | +import { h } from 'vue'; | ||
9 | +import { Button } from 'ant-design-vue'; | ||
10 | +import { TypeEnum } from './data'; | ||
11 | + | ||
8 | // 设备详情的描述 | 12 | // 设备详情的描述 |
9 | -export const descSchema: DescItem[] = [ | ||
10 | - { | ||
11 | - field: 'createTime', | ||
12 | - label: '创建时间', | ||
13 | - }, | ||
14 | - { | ||
15 | - field: 'name', | ||
16 | - label: '设备名称', | ||
17 | - }, | ||
18 | - { | ||
19 | - field: 'label', | ||
20 | - label: '设备标签', | ||
21 | - }, | ||
22 | - { | ||
23 | - field: 'deviceProfile.name', | ||
24 | - label: '产品', | ||
25 | - }, | ||
26 | - { | ||
27 | - field: 'gatewayName', | ||
28 | - label: '所属网关', | ||
29 | - show: (data) => !!data.gatewayName, | ||
30 | - }, | ||
31 | - { | ||
32 | - field: 'deviceType', | ||
33 | - label: '设备类型', | ||
34 | - render: (text) => { | ||
35 | - return text === DeviceTypeEnum.GATEWAY | ||
36 | - ? '网关设备' | ||
37 | - : text == DeviceTypeEnum.DIRECT_CONNECTION | ||
38 | - ? '直连设备' | ||
39 | - : '网关子设备'; | 13 | +export const descSchema = (emit: EmitType): DescItem[] => { |
14 | + return [ | ||
15 | + { | ||
16 | + field: 'createTime', | ||
17 | + label: '创建时间', | ||
40 | }, | 18 | }, |
41 | - }, | ||
42 | - { | ||
43 | - field: 'description', | ||
44 | - label: '描述', | ||
45 | - span: 2, | ||
46 | - }, | ||
47 | -]; | 19 | + { |
20 | + field: 'name', | ||
21 | + label: '设备名称', | ||
22 | + }, | ||
23 | + { | ||
24 | + field: 'label', | ||
25 | + label: '设备标签', | ||
26 | + }, | ||
27 | + { | ||
28 | + field: 'deviceProfile.name', | ||
29 | + label: '产品', | ||
30 | + render(val, data) { | ||
31 | + if (TypeEnum.SENSOR !== data.deviceType) return val; | ||
32 | + return h( | ||
33 | + Button, | ||
34 | + { type: 'link', style: { padding: 0 }, onClick: () => emit('open-gateway-device', data) }, | ||
35 | + { default: () => val } | ||
36 | + ); | ||
37 | + }, | ||
38 | + }, | ||
39 | + { | ||
40 | + field: 'gatewayName', | ||
41 | + label: '所属网关', | ||
42 | + show: (data) => !!data.gatewayName, | ||
43 | + }, | ||
44 | + { | ||
45 | + field: 'deviceType', | ||
46 | + label: '设备类型', | ||
47 | + render: (text) => { | ||
48 | + return text === DeviceTypeEnum.GATEWAY | ||
49 | + ? '网关设备' | ||
50 | + : text == DeviceTypeEnum.DIRECT_CONNECTION | ||
51 | + ? '直连设备' | ||
52 | + : '网关子设备'; | ||
53 | + }, | ||
54 | + }, | ||
55 | + { | ||
56 | + field: 'description', | ||
57 | + label: '描述', | ||
58 | + // span: 2, | ||
59 | + }, | ||
60 | + ]; | ||
61 | +}; | ||
48 | 62 | ||
49 | // 实时数据表格 | 63 | // 实时数据表格 |
50 | export const realTimeDataColumns: BasicColumn[] = [ | 64 | export const realTimeDataColumns: BasicColumn[] = [ |
@@ -10,7 +10,11 @@ | @@ -10,7 +10,11 @@ | ||
10 | > | 10 | > |
11 | <Tabs v-model:activeKey="activeKey" :size="size"> | 11 | <Tabs v-model:activeKey="activeKey" :size="size"> |
12 | <TabPane key="1" tab="详情"> | 12 | <TabPane key="1" tab="详情"> |
13 | - <Detail ref="deviceDetailRef" :deviceDetail="deviceDetail" /> | 13 | + <Detail |
14 | + ref="deviceDetailRef" | ||
15 | + :deviceDetail="deviceDetail" | ||
16 | + @open-gateway-device="handleOpenGatewayDevice" | ||
17 | + /> | ||
14 | </TabPane> | 18 | </TabPane> |
15 | <TabPane key="2" tab="实时数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> | 19 | <TabPane key="2" tab="实时数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> |
16 | <RealTimeData :deviceDetail="deviceDetail" /> | 20 | <RealTimeData :deviceDetail="deviceDetail" /> |
@@ -67,7 +71,7 @@ | @@ -67,7 +71,7 @@ | ||
67 | TBoxDetail, | 71 | TBoxDetail, |
68 | HistoryData, | 72 | HistoryData, |
69 | }, | 73 | }, |
70 | - emits: ['reload', 'register', 'openTbDeviceDetail'], | 74 | + emits: ['reload', 'register', 'openTbDeviceDetail', 'openGatewayDeviceDetail'], |
71 | setup(_props, { emit }) { | 75 | setup(_props, { emit }) { |
72 | const activeKey = ref('1'); | 76 | const activeKey = ref('1'); |
73 | const size = ref('small'); | 77 | const size = ref('small'); |
@@ -92,6 +96,10 @@ | @@ -92,6 +96,10 @@ | ||
92 | const handleOpenTbDeviceDetail = (data: { id: string; tbDeviceId: string }) => { | 96 | const handleOpenTbDeviceDetail = (data: { id: string; tbDeviceId: string }) => { |
93 | emit('openTbDeviceDetail', data); | 97 | emit('openTbDeviceDetail', data); |
94 | }; | 98 | }; |
99 | + | ||
100 | + const handleOpenGatewayDevice = (data: { gatewayId: string; tbDeviceId: string }) => { | ||
101 | + emit('openGatewayDeviceDetail', { id: data.gatewayId }); | ||
102 | + }; | ||
95 | return { | 103 | return { |
96 | size, | 104 | size, |
97 | activeKey, | 105 | activeKey, |
@@ -101,6 +109,7 @@ | @@ -101,6 +109,7 @@ | ||
101 | deviceDetailRef, | 109 | deviceDetailRef, |
102 | tbDeviceId, | 110 | tbDeviceId, |
103 | handleOpenTbDeviceDetail, | 111 | handleOpenTbDeviceDetail, |
112 | + handleOpenGatewayDevice, | ||
104 | }; | 113 | }; |
105 | }, | 114 | }, |
106 | }); | 115 | }); |
@@ -30,6 +30,14 @@ | @@ -30,6 +30,14 @@ | ||
30 | import { QuestionCircleOutlined } from '@ant-design/icons-vue'; | 30 | import { QuestionCircleOutlined } from '@ant-design/icons-vue'; |
31 | import { Tooltip } from 'ant-design-vue'; | 31 | import { Tooltip } from 'ant-design-vue'; |
32 | 32 | ||
33 | + interface CommandParams { | ||
34 | + additionalInfo: Recordable; | ||
35 | + cmdType: string; | ||
36 | + method: string; | ||
37 | + params: string | Recordable; | ||
38 | + persistent: boolean; | ||
39 | + } | ||
40 | + | ||
33 | export default defineComponent({ | 41 | export default defineComponent({ |
34 | components: { BasicForm, Button, QuestionCircleOutlined, Tooltip }, | 42 | components: { BasicForm, Button, QuestionCircleOutlined, Tooltip }, |
35 | props: { | 43 | props: { |
@@ -41,7 +49,7 @@ | @@ -41,7 +49,7 @@ | ||
41 | emits: ['register'], | 49 | emits: ['register'], |
42 | setup(props) { | 50 | setup(props) { |
43 | const { createMessage } = useMessage(); | 51 | const { createMessage } = useMessage(); |
44 | - const jsonData: any = ref({}); | 52 | + const jsonData = ref<CommandParams>({} as unknown as CommandParams); |
45 | const disable = ref(false); | 53 | const disable = ref(false); |
46 | const [registerForm, { getFieldsValue, validate, resetFields }] = useForm({ | 54 | const [registerForm, { getFieldsValue, validate, resetFields }] = useForm({ |
47 | labelWidth: 100, | 55 | labelWidth: 100, |
@@ -82,13 +90,17 @@ | @@ -82,13 +90,17 @@ | ||
82 | if (!valid) return; | 90 | if (!valid) return; |
83 | // 收集表单数据 | 91 | // 收集表单数据 |
84 | const field = getFieldsValue(); | 92 | const field = getFieldsValue(); |
85 | - const getJson = unref(jsonInstance).get(); | 93 | + if (field.valueType === 'json') { |
94 | + const getJson = unref(jsonInstance).get(); | ||
95 | + jsonData.value.params = getJson; | ||
96 | + } else { | ||
97 | + jsonData.value.params = field.commandText; | ||
98 | + } | ||
86 | jsonData.value.persistent = true; | 99 | jsonData.value.persistent = true; |
87 | jsonData.value.additionalInfo = { | 100 | jsonData.value.additionalInfo = { |
88 | cmdType: 'API', | 101 | cmdType: 'API', |
89 | }; | 102 | }; |
90 | jsonData.value.method = 'methodThingskit'; | 103 | jsonData.value.method = 'methodThingskit'; |
91 | - jsonData.value.params = { ...getJson }; | ||
92 | commandIssuanceApi(field.commandType, props.deviceDetail.tbDeviceId, jsonData.value) | 104 | commandIssuanceApi(field.commandType, props.deviceDetail.tbDeviceId, jsonData.value) |
93 | .then((res) => { | 105 | .then((res) => { |
94 | if (!res) return; | 106 | if (!res) return; |
@@ -92,10 +92,11 @@ | @@ -92,10 +92,11 @@ | ||
92 | required: true, | 92 | required: true, |
93 | }, | 93 | }, |
94 | }, | 94 | }, |
95 | - setup(props) { | 95 | + emits: ['open-gateway-device'], |
96 | + setup(props, { emit }) { | ||
96 | const [register] = useDescription({ | 97 | const [register] = useDescription({ |
97 | layout: 'vertical', | 98 | layout: 'vertical', |
98 | - schema: descSchema, | 99 | + schema: descSchema(emit), |
99 | column: 2, | 100 | column: 2, |
100 | }); | 101 | }); |
101 | 102 |
@@ -152,9 +152,12 @@ | @@ -152,9 +152,12 @@ | ||
152 | <DeviceDetailDrawer | 152 | <DeviceDetailDrawer |
153 | @register="registerDetailDrawer" | 153 | @register="registerDetailDrawer" |
154 | @open-tb-device-detail="handleOpenTbDeviceDetail" | 154 | @open-tb-device-detail="handleOpenTbDeviceDetail" |
155 | + @open-gateway-device-detail="handleOpenGatewayDetail" | ||
155 | /> | 156 | /> |
156 | <DeviceDetailDrawer @register="registerTbDetailDrawer" /> | 157 | <DeviceDetailDrawer @register="registerTbDetailDrawer" /> |
157 | 158 | ||
159 | + <DeviceDetailDrawer @register="registerGatewayDetailDrawer" /> | ||
160 | + | ||
158 | <DeviceModal @register="registerModal" @success="handleSuccess" @reload="handleSuccess" /> | 161 | <DeviceModal @register="registerModal" @success="handleSuccess" @reload="handleSuccess" /> |
159 | <CustomerModal @register="registerCustomerModal" @reload="handleReload" /> | 162 | <CustomerModal @register="registerCustomerModal" @reload="handleReload" /> |
160 | </PageWrapper> | 163 | </PageWrapper> |
@@ -218,6 +221,7 @@ | @@ -218,6 +221,7 @@ | ||
218 | const [registerCustomerModal, { openModal: openCustomerModal }] = useModal(); | 221 | const [registerCustomerModal, { openModal: openCustomerModal }] = useModal(); |
219 | const [registerDetailDrawer, { openDrawer }] = useDrawer(); | 222 | const [registerDetailDrawer, { openDrawer }] = useDrawer(); |
220 | const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer(); | 223 | const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer(); |
224 | + const [registerGatewayDetailDrawer, { openDrawer: openGatewayDetailDrawer }] = useDrawer(); | ||
221 | 225 | ||
222 | const [registerTable, { reload, setSelectedRowKeys, setProps }] = useTable({ | 226 | const [registerTable, { reload, setSelectedRowKeys, setProps }] = useTable({ |
223 | title: '设备列表', | 227 | title: '设备列表', |
@@ -329,6 +333,10 @@ | @@ -329,6 +333,10 @@ | ||
329 | openTbDeviceDrawer(true, data); | 333 | openTbDeviceDrawer(true, data); |
330 | }; | 334 | }; |
331 | 335 | ||
336 | + const handleOpenGatewayDetail = (data: { id: string; tbDeviceId: string }) => { | ||
337 | + openGatewayDetailDrawer(true, data); | ||
338 | + }; | ||
339 | + | ||
332 | return { | 340 | return { |
333 | registerTable, | 341 | registerTable, |
334 | handleCreate, | 342 | handleCreate, |
@@ -354,6 +362,8 @@ | @@ -354,6 +362,8 @@ | ||
354 | handleReload, | 362 | handleReload, |
355 | registerTbDetailDrawer, | 363 | registerTbDetailDrawer, |
356 | handleOpenTbDeviceDetail, | 364 | handleOpenTbDeviceDetail, |
365 | + handleOpenGatewayDetail, | ||
366 | + registerGatewayDetailDrawer, | ||
357 | }; | 367 | }; |
358 | }, | 368 | }, |
359 | }); | 369 | }); |
1 | <template> | 1 | <template> |
2 | <BasicDrawer v-bind="$attrs" title="产品详情" @register="register" width="50%"> | 2 | <BasicDrawer v-bind="$attrs" title="产品详情" @register="register" width="50%"> |
3 | - <Tabs :animated="true" v-model:activeKey="activeKey"> | ||
4 | - <TabPane forceRender key="1" tab="产品"> | 3 | + <Tabs :animated="true" v-model:activeKey="activeKey" @change="handlePanelChange"> |
4 | + <TabPane forceRender key="product" tab="产品"> | ||
5 | <div class="relative"> | 5 | <div class="relative"> |
6 | <DeviceConfigurationStep :ifShowBtn="false" ref="DevConStRef" /> | 6 | <DeviceConfigurationStep :ifShowBtn="false" ref="DevConStRef" /> |
7 | <div class="absolute w-full h-full top-0 cursor-not-allowed"></div> | 7 | <div class="absolute w-full h-full top-0 cursor-not-allowed"></div> |
8 | </div> | 8 | </div> |
9 | </TabPane> | 9 | </TabPane> |
10 | - <TabPane forceRender key="2" tab="传输配置"> | 10 | + <TabPane forceRender key="transport" tab="传输配置"> |
11 | <div class="relative"> | 11 | <div class="relative"> |
12 | <TransportConfigurationStep :ifShowBtn="false" ref="TransConStRef" /> | 12 | <TransportConfigurationStep :ifShowBtn="false" ref="TransConStRef" /> |
13 | <div class="absolute w-full h-full top-0 cursor-not-allowed"></div> | 13 | <div class="absolute w-full h-full top-0 cursor-not-allowed"></div> |
14 | </div> | 14 | </div> |
15 | </TabPane> | 15 | </TabPane> |
16 | - <TabPane forceRender key="3" tab="物模型管理"> | 16 | + <TabPane forceRender key="modelOfMatter" tab="物模型管理"> |
17 | <PhysicalModelManagementStep /> | 17 | <PhysicalModelManagementStep /> |
18 | </TabPane> | 18 | </TabPane> |
19 | </Tabs> | 19 | </Tabs> |
@@ -30,7 +30,11 @@ | @@ -30,7 +30,11 @@ | ||
30 | 30 | ||
31 | defineEmits(['register']); | 31 | defineEmits(['register']); |
32 | 32 | ||
33 | - const activeKey = ref('1'); | 33 | + type ActiveKey = 'product' | 'transport' | 'modelOfMatter'; |
34 | + | ||
35 | + const activeKey = ref<ActiveKey>('product'); | ||
36 | + | ||
37 | + const record = ref<Recordable>({}); | ||
34 | 38 | ||
35 | const DevConStRef = ref<InstanceType<typeof DeviceConfigurationStep>>(); | 39 | const DevConStRef = ref<InstanceType<typeof DeviceConfigurationStep>>(); |
36 | const TransConStRef = ref<InstanceType<typeof TransportConfigurationStep>>(); | 40 | const TransConStRef = ref<InstanceType<typeof TransportConfigurationStep>>(); |
@@ -44,11 +48,14 @@ | @@ -44,11 +48,14 @@ | ||
44 | }; | 48 | }; |
45 | 49 | ||
46 | const [register, {}] = useDrawerInner(async (data: Recordable) => { | 50 | const [register, {}] = useDrawerInner(async (data: Recordable) => { |
47 | - activeKey.value = '1'; | ||
48 | - const res = await deviceConfigGetDetail(data.record.id); | ||
49 | - setDeviceConfFormData(res); | ||
50 | - setTransConfFormData(res); | 51 | + activeKey.value = 'product'; |
52 | + record.value = await deviceConfigGetDetail(data.record.id); | ||
53 | + setDeviceConfFormData(unref(record)); | ||
51 | }); | 54 | }); |
55 | + | ||
56 | + const handlePanelChange = (activeKey: ActiveKey) => { | ||
57 | + if (activeKey === 'transport') setTransConfFormData(unref(record)); | ||
58 | + }; | ||
52 | </script> | 59 | </script> |
53 | 60 | ||
54 | <style lang="less" scope></style> | 61 | <style lang="less" scope></style> |
@@ -4,12 +4,12 @@ | @@ -4,12 +4,12 @@ | ||
4 | ref="formRef" | 4 | ref="formRef" |
5 | :model="scriptForm" | 5 | :model="scriptForm" |
6 | name="basic" | 6 | name="basic" |
7 | - :label-col="{ span: 3 }" | ||
8 | - :wrapper-col="{ span: 17 }" | 7 | + :label-col="{ span: 4 }" |
8 | + :wrapper-col="{ span: 16 }" | ||
9 | autocomplete="off" | 9 | autocomplete="off" |
10 | > | 10 | > |
11 | <a-form-item | 11 | <a-form-item |
12 | - :label="ifAdd ? '名称' : '输入参数'" | 12 | + :label="ifAdd ? '名称' : '输入参数(params)'" |
13 | :name="ifAdd ? 'name' : 'params'" | 13 | :name="ifAdd ? 'name' : 'params'" |
14 | :rules="[{ required: true, message: ifAdd ? '请输入脚本名称' : '请输入参数' }]" | 14 | :rules="[{ required: true, message: ifAdd ? '请输入脚本名称' : '请输入参数' }]" |
15 | > | 15 | > |
@@ -42,7 +42,10 @@ | @@ -42,7 +42,10 @@ | ||
42 | copy | 42 | copy |
43 | </Button> | 43 | </Button> |
44 | </a-form-item> | 44 | </a-form-item> |
45 | - <a-form-item :label="ifAdd ? '备注' : '输出参数'" :name="ifAdd ? 'description' : 'output'"> | 45 | + <a-form-item |
46 | + :label="ifAdd ? '备注' : '输出参数(output)'" | ||
47 | + :name="ifAdd ? 'description' : 'output'" | ||
48 | + > | ||
46 | <a-textarea | 49 | <a-textarea |
47 | :rows="3" | 50 | :rows="3" |
48 | v-if="ifAdd" | 51 | v-if="ifAdd" |
@@ -98,7 +98,7 @@ | @@ -98,7 +98,7 @@ | ||
98 | }, 10); | 98 | }, 10); |
99 | } else { | 99 | } else { |
100 | if (res) { | 100 | if (res) { |
101 | - converScriptRef.value?.setScriptOutputData(res?.output); | 101 | + converScriptRef.value?.setScriptOutputData(res?.output || res?.error); |
102 | } | 102 | } |
103 | } | 103 | } |
104 | emits('success', res); | 104 | emits('success', res); |