Commit bddcb179d4ce25bcb948fc110d535d7d90a9957d
Merge branch 'main_dev' into 'main'
Main dev See merge request yunteng/thingskit-front!1291
Showing
33 changed files
with
648 additions
and
809 deletions
| @@ -72,14 +72,16 @@ | @@ -72,14 +72,16 @@ | ||
| 72 | "sortablejs": "^1.14.0", | 72 | "sortablejs": "^1.14.0", |
| 73 | "tinymce": "^5.8.2", | 73 | "tinymce": "^5.8.2", |
| 74 | "vditor": "^3.8.6", | 74 | "vditor": "^3.8.6", |
| 75 | - "video.js": "^7.20.3", | ||
| 76 | - "videojs-flvjs-es6": "^1.0.1", | ||
| 77 | "vue": "3.3.4", | 75 | "vue": "3.3.4", |
| 78 | "vue-i18n": "9.1.7", | 76 | "vue-i18n": "9.1.7", |
| 79 | "vue-json-pretty": "^2.0.4", | 77 | "vue-json-pretty": "^2.0.4", |
| 80 | "vue-router": "^4.0.11", | 78 | "vue-router": "^4.0.11", |
| 81 | "vue-types": "^4.0.3", | 79 | "vue-types": "^4.0.3", |
| 82 | "vue3-grid-layout": "^1.0.0", | 80 | "vue3-grid-layout": "^1.0.0", |
| 81 | + "xgplayer": "^3.0.14", | ||
| 82 | + "xgplayer-flv": "^3.0.14", | ||
| 83 | + "xgplayer-hls": "^3.0.14", | ||
| 84 | + "xgplayer-mp4": "^3.0.14", | ||
| 83 | "xlsx": "^0.17.0" | 85 | "xlsx": "^0.17.0" |
| 84 | }, | 86 | }, |
| 85 | "devDependencies": { | 87 | "devDependencies": { |
| @@ -101,7 +103,6 @@ | @@ -101,7 +103,6 @@ | ||
| 101 | "@types/qrcode": "^1.4.1", | 103 | "@types/qrcode": "^1.4.1", |
| 102 | "@types/qs": "^6.9.7", | 104 | "@types/qs": "^6.9.7", |
| 103 | "@types/sortablejs": "^1.10.7", | 105 | "@types/sortablejs": "^1.10.7", |
| 104 | - "@types/video.js": "^7.3.49", | ||
| 105 | "@typescript-eslint/eslint-plugin": "^4.29.1", | 106 | "@typescript-eslint/eslint-plugin": "^4.29.1", |
| 106 | "@typescript-eslint/parser": "^4.29.1", | 107 | "@typescript-eslint/parser": "^4.29.1", |
| 107 | "@vitejs/plugin-legacy": "^1.5.1", | 108 | "@vitejs/plugin-legacy": "^1.5.1", |
| @@ -109,7 +109,7 @@ export const deleteStreamingMediaRecord = (params: StreamingMediaDeleteParam) => | @@ -109,7 +109,7 @@ export const deleteStreamingMediaRecord = (params: StreamingMediaDeleteParam) => | ||
| 109 | * @returns | 109 | * @returns |
| 110 | */ | 110 | */ |
| 111 | export const getStreamingPlayUrl = (entityId: string) => { | 111 | export const getStreamingPlayUrl = (entityId: string) => { |
| 112 | - return defHttp.get( | 112 | + return defHttp.get<{ data: { url: string } }>( |
| 113 | { | 113 | { |
| 114 | url: `${CameraManagerApi.STREAMING_PLAY_GET_URL}/${entityId}`, | 114 | url: `${CameraManagerApi.STREAMING_PLAY_GET_URL}/${entityId}`, |
| 115 | }, | 115 | }, |
| @@ -35,6 +35,7 @@ export interface CameraModel { | @@ -35,6 +35,7 @@ export interface CameraModel { | ||
| 35 | deviceId: string; | 35 | deviceId: string; |
| 36 | deviceName: string; | 36 | deviceName: string; |
| 37 | }; | 37 | }; |
| 38 | + playProtocol?: number; | ||
| 38 | } | 39 | } |
| 39 | 40 | ||
| 40 | export interface StreamingManageRecord { | 41 | export interface StreamingManageRecord { |
| @@ -106,4 +107,14 @@ export interface CameraRecord { | @@ -106,4 +107,14 @@ export interface CameraRecord { | ||
| 106 | channelNo: string; | 107 | channelNo: string; |
| 107 | deviceId: string; | 108 | deviceId: string; |
| 108 | }; | 109 | }; |
| 110 | + videoPlatformDTO?: VideoPlatformDTO; | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +interface VideoPlatformDTO { | ||
| 114 | + enabled: boolean; | ||
| 115 | + type: number; | ||
| 116 | + host: string; | ||
| 117 | + appKey: string; | ||
| 118 | + appSecret: string; | ||
| 119 | + ssl: number; | ||
| 109 | } | 120 | } |
| 1 | -import { RuleChainPaginationItemType } from './model/type'; | 1 | +import { ClearEventsParam, RuleChainPaginationItemType } from './model/type'; |
| 2 | import { TBPaginationResult } from '/#/axios'; | 2 | import { TBPaginationResult } from '/#/axios'; |
| 3 | import { defHttp } from '/@/utils/http/axios'; | 3 | import { defHttp } from '/@/utils/http/axios'; |
| 4 | import { RuleChainDetail, RuleChainType } from '/@/views/rule/designer/types/ruleNode'; | 4 | import { RuleChainDetail, RuleChainType } from '/@/views/rule/designer/types/ruleNode'; |
| @@ -75,3 +75,26 @@ export const getRuleNodeEventList = ( | @@ -75,3 +75,26 @@ export const getRuleNodeEventList = ( | ||
| 75 | { joinPrefix: false } | 75 | { joinPrefix: false } |
| 76 | ); | 76 | ); |
| 77 | }; | 77 | }; |
| 78 | + | ||
| 79 | +export const doClearEvents = ({ | ||
| 80 | + tenantId, | ||
| 81 | + startTime, | ||
| 82 | + endTime, | ||
| 83 | + eventType, | ||
| 84 | + ruleId, | ||
| 85 | +}: ClearEventsParam) => { | ||
| 86 | + return defHttp.post( | ||
| 87 | + { | ||
| 88 | + url: `${Api.GET_RULE_NODE_EVENTS}/${ruleId}/clear`, | ||
| 89 | + params: { | ||
| 90 | + startTime, | ||
| 91 | + endTime, | ||
| 92 | + tenantId, | ||
| 93 | + }, | ||
| 94 | + data: { | ||
| 95 | + eventType, | ||
| 96 | + }, | ||
| 97 | + }, | ||
| 98 | + { joinPrefix: false } | ||
| 99 | + ); | ||
| 100 | +}; |
| 1 | +import { EventTypeEnum } from '/@/views/rule/designer/src/components/UpdateNodeDrawer/EventSelect/config'; | ||
| 2 | + | ||
| 1 | export interface RuleChainPaginationItemType { | 3 | export interface RuleChainPaginationItemType { |
| 2 | id: Id; | 4 | id: Id; |
| 3 | createdTime: number; | 5 | createdTime: number; |
| @@ -19,3 +21,11 @@ export interface Id { | @@ -19,3 +21,11 @@ export interface Id { | ||
| 19 | export interface AdditionalInfo { | 21 | export interface AdditionalInfo { |
| 20 | description: string; | 22 | description: string; |
| 21 | } | 23 | } |
| 24 | + | ||
| 25 | +export interface ClearEventsParam { | ||
| 26 | + ruleId: string; | ||
| 27 | + tenantId: string; | ||
| 28 | + startTime: string; | ||
| 29 | + endTime: string; | ||
| 30 | + eventType: EventTypeEnum; | ||
| 31 | +} |
| 1 | <template> | 1 | <template> |
| 2 | - <a-tree-select v-bind="getAttrs" @change="handleChange"> | 2 | + <a-tree-select |
| 3 | + v-bind="getAttrs" | ||
| 4 | + @change="handleChange" | ||
| 5 | + :showSearch="true" | ||
| 6 | + :filterTreeNode="filterTreeNode" | ||
| 7 | + > | ||
| 3 | <template #[item]="data" v-for="item in Object.keys($slots)"> | 8 | <template #[item]="data" v-for="item in Object.keys($slots)"> |
| 4 | <slot :name="item" v-bind="data || {}"></slot> | 9 | <slot :name="item" v-bind="data || {}"></slot> |
| 5 | </template> | 10 | </template> |
| @@ -60,6 +65,18 @@ | @@ -60,6 +65,18 @@ | ||
| 60 | props.immediate && fetch(); | 65 | props.immediate && fetch(); |
| 61 | }); | 66 | }); |
| 62 | 67 | ||
| 68 | + function filterTreeNode(value, options) { | ||
| 69 | + const { props } = options || {}; | ||
| 70 | + | ||
| 71 | + if (!value) { | ||
| 72 | + return true; | ||
| 73 | + } | ||
| 74 | + let { name } = props || {}; | ||
| 75 | + value = value.toLowerCase(); | ||
| 76 | + name = name.toLowerCase(); | ||
| 77 | + return name.includes(value); | ||
| 78 | + } | ||
| 79 | + | ||
| 63 | async function fetch() { | 80 | async function fetch() { |
| 64 | const { api } = props; | 81 | const { api } = props; |
| 65 | if (!api || !isFunction(api)) return; | 82 | if (!api || !isFunction(api)) return; |
| @@ -80,7 +97,7 @@ | @@ -80,7 +97,7 @@ | ||
| 80 | isFirstLoaded.value = true; | 97 | isFirstLoaded.value = true; |
| 81 | emit('options-change', treeData.value); | 98 | emit('options-change', treeData.value); |
| 82 | } | 99 | } |
| 83 | - return { getAttrs, loading, handleChange }; | 100 | + return { getAttrs, loading, handleChange, filterTreeNode }; |
| 84 | }, | 101 | }, |
| 85 | }); | 102 | }); |
| 86 | </script> | 103 | </script> |
| 1 | -import { withInstall } from '/@/utils/index'; | ||
| 2 | -import VideoPlay from './src/VideoPlay.vue'; | 1 | +export { default as XGPlayer } from './src/XGPlayer.vue'; |
| 3 | 2 | ||
| 4 | export { getVideoTypeByUrl } from './src/utils'; | 3 | export { getVideoTypeByUrl } from './src/utils'; |
| 5 | - | ||
| 6 | -export const BasicVideoPlay = withInstall(VideoPlay); |
src/components/Video/src/VideoPlay.vue
deleted
100644 → 0
| 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, toRaw, unref } from 'vue'; | ||
| 6 | - import { useDesign } from '/@/hooks/web/useDesign'; | ||
| 7 | - import { getJwtToken, getShareJwtToken } from '/@/utils/auth'; | ||
| 8 | - import { isShareMode } from '/@/views/sys/share/hook'; | ||
| 9 | - import 'videojs-flvjs-es6'; | ||
| 10 | - const { prefixCls } = useDesign('basic-video-play'); | ||
| 11 | - | ||
| 12 | - const props = withDefaults( | ||
| 13 | - defineProps<{ | ||
| 14 | - options?: VideoJsPlayerOptions; | ||
| 15 | - withToken?: boolean; | ||
| 16 | - immediateInitOnMounted?: boolean; | ||
| 17 | - }>(), | ||
| 18 | - { | ||
| 19 | - immediateInitOnMounted: true, | ||
| 20 | - } | ||
| 21 | - ); | ||
| 22 | - | ||
| 23 | - const emit = defineEmits<{ | ||
| 24 | - (event: 'ready', instance?: Nullable<VideoJsPlayer>): void; | ||
| 25 | - (event: 'onUnmounted'): void; | ||
| 26 | - }>(); | ||
| 27 | - | ||
| 28 | - const videoPlayEl = ref<HTMLVideoElement>(); | ||
| 29 | - | ||
| 30 | - const videoPlayInstance = ref<Nullable<VideoJsPlayer>>(); | ||
| 31 | - | ||
| 32 | - const getOptions = computed(() => { | ||
| 33 | - const { options, withToken } = props; | ||
| 34 | - | ||
| 35 | - const defaultOptions: VideoJsPlayerOptions & Recordable = { | ||
| 36 | - language: 'zh', | ||
| 37 | - muted: true, | ||
| 38 | - liveui: true, | ||
| 39 | - controls: true, | ||
| 40 | - techOrder: ['html5', 'flvjs'], | ||
| 41 | - flvjs: { | ||
| 42 | - mediaDataSource: { | ||
| 43 | - isLive: true, | ||
| 44 | - cors: true, | ||
| 45 | - hasAudio: false, | ||
| 46 | - withCredentials: false, | ||
| 47 | - }, | ||
| 48 | - config: { | ||
| 49 | - headers: { | ||
| 50 | - ...(withToken | ||
| 51 | - ? { | ||
| 52 | - 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | ||
| 53 | - } | ||
| 54 | - : {}), | ||
| 55 | - }, | ||
| 56 | - autoCleanupSourceBuffer: true, | ||
| 57 | - }, | ||
| 58 | - }, | ||
| 59 | - }; | ||
| 60 | - return videoJs.mergeOptions(defaultOptions, options); | ||
| 61 | - }); | ||
| 62 | - | ||
| 63 | - const getWidthHeight = computed(() => { | ||
| 64 | - let { width = 300, height = 150 } = unref(getOptions); | ||
| 65 | - width = isNumber(width) ? (`${width}px` as unknown as number) : width; | ||
| 66 | - height = isNumber(height) ? (`${height}px` as unknown as number) : height; | ||
| 67 | - return { width, height } as CSSProperties; | ||
| 68 | - }); | ||
| 69 | - | ||
| 70 | - const init = () => { | ||
| 71 | - if (unref(videoPlayInstance)) unref(videoPlayInstance)?.dispose(); | ||
| 72 | - videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => { | ||
| 73 | - emit('ready', unref(videoPlayInstance)); | ||
| 74 | - }); | ||
| 75 | - //fix 修复videojs解决直播延时的问题 | ||
| 76 | - videoPlayInstance.value?.on('timeupdate', function () { | ||
| 77 | - // 计算表最新推流的时间和现在播放器播放推流的时间 | ||
| 78 | - let differTime = | ||
| 79 | - (videoPlayInstance.value as any as VideoJsPlayer)?.buffered()?.end(0) - | ||
| 80 | - (videoPlayInstance.value as any as VideoJsPlayer)?.currentTime(); | ||
| 81 | - // 差值小于1.5s时根据1倍速进行播放 | ||
| 82 | - if (differTime < 1.5) { | ||
| 83 | - videoPlayInstance.value?.playbackRate(1); | ||
| 84 | - } | ||
| 85 | - // 差值大于1.5s小于10s根据1.2倍速进行播放 | ||
| 86 | - if (differTime < 10 && differTime > 1.5) { | ||
| 87 | - videoPlayInstance.value?.playbackRate(1.2); | ||
| 88 | - } | ||
| 89 | - // 差值大于10s时进行重新加载直播流 | ||
| 90 | - if (differTime > 10) { | ||
| 91 | - init(); | ||
| 92 | - } | ||
| 93 | - }); | ||
| 94 | - // | ||
| 95 | - }; | ||
| 96 | - | ||
| 97 | - const customInit = (getOptionsFn: (optios: VideoJsPlayerOptions) => VideoJsPlayerOptions) => { | ||
| 98 | - return (videoPlayInstance.value = videoJs( | ||
| 99 | - unref(videoPlayEl)!, | ||
| 100 | - getOptionsFn(toRaw(unref(getOptions))), | ||
| 101 | - () => { | ||
| 102 | - emit('ready', unref(videoPlayInstance)); | ||
| 103 | - } | ||
| 104 | - )); | ||
| 105 | - }; | ||
| 106 | - | ||
| 107 | - onMounted(() => { | ||
| 108 | - props.immediateInitOnMounted && init(); | ||
| 109 | - }); | ||
| 110 | - | ||
| 111 | - onUnmounted(() => { | ||
| 112 | - unref(videoPlayInstance)?.dispose(); | ||
| 113 | - videoPlayInstance.value = null; | ||
| 114 | - emit('onUnmounted'); | ||
| 115 | - }); | ||
| 116 | - | ||
| 117 | - defineExpose({ | ||
| 118 | - customInit, | ||
| 119 | - reloadPlayer: init, | ||
| 120 | - getInstance: () => unref(videoPlayInstance), | ||
| 121 | - }); | ||
| 122 | -</script> | ||
| 123 | - | ||
| 124 | -<template> | ||
| 125 | - <div :class="prefixCls" class="!w-full h-full" :style="getWidthHeight" style="min-height: 100%"> | ||
| 126 | - <video | ||
| 127 | - ref="videoPlayEl" | ||
| 128 | - class="video-js vjs-big-play-centered vjs-show-big-play-button-on-pause !w-full !h-full" | ||
| 129 | - muted | ||
| 130 | - > | ||
| 131 | - </video> | ||
| 132 | - </div> | ||
| 133 | -</template> | ||
| 134 | - | ||
| 135 | -<style lang="less"> | ||
| 136 | - @prefix-cls: ~'@{namespace}-basic-video-play'; | ||
| 137 | - | ||
| 138 | - .@{prefix-cls} { | ||
| 139 | - .vjs-error-display { | ||
| 140 | - .vjs-modal-dialog-content::after { | ||
| 141 | - content: '无法加载视频,原因可能是服务器或网络故障,也可能是格式不支持.'; | ||
| 142 | - } | ||
| 143 | - } | ||
| 144 | - } | ||
| 145 | -</style> |
src/components/Video/src/XGPlayer.vue
0 → 100644
| 1 | +<script setup lang="ts"> | ||
| 2 | + import Player, { Events, IError } from 'xgplayer'; | ||
| 3 | + import { FlvPlugin } from 'xgplayer-flv'; | ||
| 4 | + import Mp4Plugin from 'xgplayer-mp4'; | ||
| 5 | + import { HlsPlugin } from 'xgplayer-hls'; | ||
| 6 | + import { onMounted, shallowRef, computed, unref, toRaw, onUnmounted, ref, watch } from 'vue'; | ||
| 7 | + import PresetPlayer from 'xgplayer'; | ||
| 8 | + import { IPlayerOptions } from 'xgplayer/es/player'; | ||
| 9 | + import 'xgplayer/dist/index.min.css'; | ||
| 10 | + import { isShareMode } from '/@/views/sys/share/hook'; | ||
| 11 | + import { getJwtToken, getShareJwtToken } from '/@/utils/auth'; | ||
| 12 | + import { XGPlayerProps } from '/@/components/Video/src/types'; | ||
| 13 | + import { StreamType } from './types'; | ||
| 14 | + | ||
| 15 | + const props = withDefaults(defineProps<XGPlayerProps>(), { | ||
| 16 | + streamType: 'auto', | ||
| 17 | + autoPlay: true, | ||
| 18 | + config: () => ({}), | ||
| 19 | + }); | ||
| 20 | + | ||
| 21 | + const emits = defineEmits<{ | ||
| 22 | + (eventName: 'ready', player: PresetPlayer): void; | ||
| 23 | + (eventName: 'play', player: PresetPlayer): void; | ||
| 24 | + (eventName: 'pause', player: PresetPlayer): void; | ||
| 25 | + (eventName: 'ended', player: PresetPlayer): void; | ||
| 26 | + (eventName: 'onUnmounted', player: PresetPlayer): void; | ||
| 27 | + }>(); | ||
| 28 | + | ||
| 29 | + function getStreamTypeByUrl(url = ''): StreamType | undefined { | ||
| 30 | + url = url || ''; | ||
| 31 | + if (url.endsWith('.m3u8')) return 'hls'; | ||
| 32 | + else if (url.endsWith('.mp4')) return 'mp4'; | ||
| 33 | + else if (url.endsWith('.flv')) { | ||
| 34 | + return 'flv'; | ||
| 35 | + } else return; | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + const getPluginByStreamType = (): IPlayerOptions => { | ||
| 39 | + let { url, withToken } = props; | ||
| 40 | + let { streamType } = props; | ||
| 41 | + streamType = streamType === 'auto' ? getStreamTypeByUrl(url)! : streamType; | ||
| 42 | + | ||
| 43 | + const liveConfig = { | ||
| 44 | + targetLatency: 10, | ||
| 45 | + maxLatency: 20, | ||
| 46 | + disconnectTime: 0, | ||
| 47 | + fetchOptions: withToken | ||
| 48 | + ? { | ||
| 49 | + headers: { | ||
| 50 | + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | ||
| 51 | + }, | ||
| 52 | + } | ||
| 53 | + : {}, | ||
| 54 | + }; | ||
| 55 | + const config: IPlayerOptions = { | ||
| 56 | + flv: liveConfig, | ||
| 57 | + hls: liveConfig, | ||
| 58 | + }; | ||
| 59 | + switch (streamType) { | ||
| 60 | + case 'hls': | ||
| 61 | + config.plugins = [HlsPlugin]; | ||
| 62 | + break; | ||
| 63 | + case 'mp4': | ||
| 64 | + config.plugins = [Mp4Plugin]; | ||
| 65 | + break; | ||
| 66 | + case 'flv': | ||
| 67 | + config.plugins = [FlvPlugin]; | ||
| 68 | + break; | ||
| 69 | + } | ||
| 70 | + return config; | ||
| 71 | + }; | ||
| 72 | + | ||
| 73 | + const videoElRef = shallowRef<Nullable<HTMLDivElement>>(); | ||
| 74 | + | ||
| 75 | + const playerRef = shallowRef<Nullable<PresetPlayer>>(); | ||
| 76 | + | ||
| 77 | + const propsRef = ref<XGPlayerProps>({}); | ||
| 78 | + | ||
| 79 | + const getPlayerConfig = computed<IPlayerOptions>(() => { | ||
| 80 | + const { url, autoPlay, config } = props; | ||
| 81 | + | ||
| 82 | + const basicConfig: IPlayerOptions = { | ||
| 83 | + ...config, | ||
| 84 | + ...propsRef, | ||
| 85 | + url, | ||
| 86 | + lang: 'zh', | ||
| 87 | + isLive: true, | ||
| 88 | + autoplay: autoPlay, | ||
| 89 | + autoplayMuted: autoPlay, | ||
| 90 | + ...getPluginByStreamType(), | ||
| 91 | + }; | ||
| 92 | + return basicConfig; | ||
| 93 | + }); | ||
| 94 | + | ||
| 95 | + function onDecodeError() { | ||
| 96 | + console.warn('player happend decode error'); | ||
| 97 | + // playerRef.value?.destroy?.(); | ||
| 98 | + // initializePlayer(); | ||
| 99 | + playerRef.value?.switchURL(props.url!); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + function initializePlayer() { | ||
| 103 | + if (unref(playerRef)) { | ||
| 104 | + playerRef.value?.destroy?.(); | ||
| 105 | + playerRef.value = null; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + const config = toRaw(unref(getPlayerConfig)); | ||
| 109 | + | ||
| 110 | + if (!unref(videoElRef)) return; | ||
| 111 | + | ||
| 112 | + const player = (playerRef.value = new Player(Object.assign(config, { el: unref(videoElRef) }))); | ||
| 113 | + | ||
| 114 | + player.on(Events.READY, () => { | ||
| 115 | + emits('ready', player); | ||
| 116 | + }); | ||
| 117 | + | ||
| 118 | + player.setEventsMiddleware({ | ||
| 119 | + error: (event, callback) => { | ||
| 120 | + const code = ( | ||
| 121 | + event as unknown as { | ||
| 122 | + error: MediaError; | ||
| 123 | + } | ||
| 124 | + ).error.code; | ||
| 125 | + if (code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED) { | ||
| 126 | + if (!props.url) { | ||
| 127 | + return; | ||
| 128 | + } | ||
| 129 | + callback(); | ||
| 130 | + return; | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + if (code === MediaError.MEDIA_ERR_DECODE) { | ||
| 134 | + // 视频流可以播放 中途解码失败重载 | ||
| 135 | + if (playerRef.value?.isPlaying) { | ||
| 136 | + onDecodeError(); | ||
| 137 | + } | ||
| 138 | + return; | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + callback(); | ||
| 142 | + }, | ||
| 143 | + }); | ||
| 144 | + | ||
| 145 | + player.on(Events.PAUSE, () => { | ||
| 146 | + emits('pause', player); | ||
| 147 | + }); | ||
| 148 | + | ||
| 149 | + player.on(Events.ENDED, () => { | ||
| 150 | + emits('ended', player); | ||
| 151 | + }); | ||
| 152 | + | ||
| 153 | + player.on(Events.PLAY, () => { | ||
| 154 | + emits('play', player); | ||
| 155 | + }); | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + onMounted(() => { | ||
| 159 | + initializePlayer(); | ||
| 160 | + }); | ||
| 161 | + | ||
| 162 | + onUnmounted(() => { | ||
| 163 | + emits('onUnmounted', unref(playerRef)!); | ||
| 164 | + playerRef.value?.destroy?.(); | ||
| 165 | + }); | ||
| 166 | + | ||
| 167 | + watch( | ||
| 168 | + () => props.url, | ||
| 169 | + () => { | ||
| 170 | + initializePlayer(); | ||
| 171 | + } | ||
| 172 | + ); | ||
| 173 | + | ||
| 174 | + defineExpose({ | ||
| 175 | + getPlayerInstance: () => unref(playerRef), | ||
| 176 | + }); | ||
| 177 | +</script> | ||
| 178 | + | ||
| 179 | +<template> | ||
| 180 | + <div ref="videoElRef"></div> | ||
| 181 | +</template> |
src/components/Video/src/types.ts
0 → 100644
src/utils/organizationSearch.ts
0 → 100644
| 1 | +export const createOrganizationSearch = () => { | ||
| 2 | + return { | ||
| 3 | + showSearch: true, | ||
| 4 | + filterTreeNode: (value, options) => { | ||
| 5 | + const { props } = options || {}; | ||
| 6 | + | ||
| 7 | + if (!value) { | ||
| 8 | + return true; | ||
| 9 | + } | ||
| 10 | + let { name } = props || {}; | ||
| 11 | + value = value?.toLowerCase(); | ||
| 12 | + name = name?.toLowerCase(); | ||
| 13 | + return name.includes(value); | ||
| 14 | + }, | ||
| 15 | + }; | ||
| 16 | +}; |
src/views/camera/manage/DialogPreviewVideo.vue
deleted
100644 → 0
| 1 | -<template> | ||
| 2 | - <div> | ||
| 3 | - <BasicModal | ||
| 4 | - v-bind="$attrs" | ||
| 5 | - width="55rem" | ||
| 6 | - destroyOnClose | ||
| 7 | - :height="heightNum" | ||
| 8 | - @register="register" | ||
| 9 | - title="视频预览" | ||
| 10 | - :showOkBtn="false" | ||
| 11 | - @cancel="handleCancel" | ||
| 12 | - > | ||
| 13 | - <div | ||
| 14 | - class="flex items-center justify-center bg-dark-900 w-full h-full min-h-96 video-container" | ||
| 15 | - > | ||
| 16 | - <BasicVideoPlay | ||
| 17 | - v-if="showVideo" | ||
| 18 | - :options="(options as any)" | ||
| 19 | - :withToken="withToken" | ||
| 20 | - @on-unmounted="handleCloseFlvPlayUrl" | ||
| 21 | - /> | ||
| 22 | - </div> | ||
| 23 | - </BasicModal> | ||
| 24 | - </div> | ||
| 25 | -</template> | ||
| 26 | -<script setup lang="ts"> | ||
| 27 | - import { ref, reactive, unref } from 'vue'; | ||
| 28 | - import { BasicModal, useModalInner } from '/@/components/Modal'; | ||
| 29 | - import type { StreamingManageRecord, CameraModel } from '/@/api/camera/model/cameraModel'; | ||
| 30 | - import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video'; | ||
| 31 | - import { AccessMode } from './config.data'; | ||
| 32 | - import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '/@/api/camera/cameraManager'; | ||
| 33 | - import { isRtspProtocol } from '/@/components/Video/src/utils'; | ||
| 34 | - import { VideoJsPlayerOptions } from 'video.js'; | ||
| 35 | - import { useFingerprint } from '/@/utils/useFingerprint'; | ||
| 36 | - import { GetResult } from '@fingerprintjs/fingerprintjs'; | ||
| 37 | - | ||
| 38 | - const heightNum = ref(800); | ||
| 39 | - const showVideo = ref(false); | ||
| 40 | - | ||
| 41 | - const playUrl = ref(''); | ||
| 42 | - | ||
| 43 | - const withToken = ref(false); | ||
| 44 | - | ||
| 45 | - const videoId = ref<string>(); | ||
| 46 | - | ||
| 47 | - const fingerprintResult = ref<Nullable<GetResult>>(null); | ||
| 48 | - | ||
| 49 | - const options = reactive<VideoJsPlayerOptions>({ | ||
| 50 | - width: '100%' as unknown as number, | ||
| 51 | - height: 384 as unknown as number, | ||
| 52 | - autoplay: true, | ||
| 53 | - }); | ||
| 54 | - | ||
| 55 | - const setSources = (url: string, fingerprintResult: GetResult) => { | ||
| 56 | - const flag = isRtspProtocol(url); | ||
| 57 | - options.sources = [ | ||
| 58 | - { | ||
| 59 | - src: flag ? getFlvPlayUrl(url, fingerprintResult.visitorId) : url, | ||
| 60 | - type: getVideoTypeByUrl(url), | ||
| 61 | - }, | ||
| 62 | - ]; | ||
| 63 | - }; | ||
| 64 | - | ||
| 65 | - const { getResult } = useFingerprint(); | ||
| 66 | - const [register] = useModalInner( | ||
| 67 | - async (data: { record: CameraModel | StreamingManageRecord }) => { | ||
| 68 | - const { record } = data; | ||
| 69 | - videoId.value = record.id || ''; | ||
| 70 | - const result = await getResult(); | ||
| 71 | - fingerprintResult.value = result; | ||
| 72 | - if (record.accessMode === AccessMode.ManuallyEnter) { | ||
| 73 | - if ((record as CameraModel).videoUrl) { | ||
| 74 | - if (isRtspProtocol((record as CameraModel).videoUrl)) { | ||
| 75 | - playUrl.value = (record as CameraModel).videoUrl; | ||
| 76 | - closeFlvPlay(unref(playUrl), result.visitorId); | ||
| 77 | - withToken.value = true; | ||
| 78 | - } | ||
| 79 | - setSources((record as CameraModel).videoUrl, result); | ||
| 80 | - } | ||
| 81 | - } else { | ||
| 82 | - try { | ||
| 83 | - const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!); | ||
| 84 | - setSources(url, result); | ||
| 85 | - } catch (error) {} | ||
| 86 | - } | ||
| 87 | - showVideo.value = true; | ||
| 88 | - } | ||
| 89 | - ); | ||
| 90 | - | ||
| 91 | - const handleCloseFlvPlayUrl = () => { | ||
| 92 | - if (isRtspProtocol(unref(playUrl))) { | ||
| 93 | - closeFlvPlay(unref(playUrl)!, unref(fingerprintResult)!.visitorId!); | ||
| 94 | - } | ||
| 95 | - }; | ||
| 96 | - | ||
| 97 | - const handleCancel = () => { | ||
| 98 | - showVideo.value = false; | ||
| 99 | - withToken.value = false; | ||
| 100 | - }; | ||
| 101 | -</script> | ||
| 102 | - | ||
| 103 | -<style lang="less" scoped> | ||
| 104 | - .video-container:deep(.vben-basic-video-play) { | ||
| 105 | - min-height: 13rem; | ||
| 106 | - } | ||
| 107 | - | ||
| 108 | - .video-container:deep(.video-js) { | ||
| 109 | - min-height: 13rem; | ||
| 110 | - } | ||
| 111 | -</style> |
| @@ -80,8 +80,7 @@ | @@ -80,8 +80,7 @@ | ||
| 80 | </BasicTable> | 80 | </BasicTable> |
| 81 | </PageWrapper> | 81 | </PageWrapper> |
| 82 | <CameraDrawer @register="registerDrawer" @success="handleSuccess" /> | 82 | <CameraDrawer @register="registerDrawer" @success="handleSuccess" /> |
| 83 | - <VideoPreviewModal @register="registerModal" /> | ||
| 84 | - <VideoModal @register="registerModal1" /> | 83 | + <VideoModal @register="registerModal" /> |
| 85 | <GBTDrawer @register="registerGBTDrawer" @success="handleGBTSuccess" /> | 84 | <GBTDrawer @register="registerGBTDrawer" @success="handleGBTSuccess" /> |
| 86 | </div> | 85 | </div> |
| 87 | </template> | 86 | </template> |
| @@ -103,14 +102,16 @@ | @@ -103,14 +102,16 @@ | ||
| 103 | CameraPermission, | 102 | CameraPermission, |
| 104 | VideoPlatformEnum, | 103 | VideoPlatformEnum, |
| 105 | accessModeConfig, | 104 | accessModeConfig, |
| 105 | + getPlayUrl, | ||
| 106 | } from './config.data'; | 106 | } from './config.data'; |
| 107 | - import VideoPreviewModal from './DialogPreviewVideo.vue'; | ||
| 108 | import { useModal } from '/@/components/Modal'; | 107 | import { useModal } from '/@/components/Modal'; |
| 109 | import { Authority } from '/@/components/Authority'; | 108 | import { Authority } from '/@/components/Authority'; |
| 110 | import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; | 109 | import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; |
| 111 | import { Popconfirm } from 'ant-design-vue'; | 110 | import { Popconfirm } from 'ant-design-vue'; |
| 112 | import { Tag } from 'ant-design-vue'; | 111 | import { Tag } from 'ant-design-vue'; |
| 113 | import VideoModal from '/@/views/device/list/cpns/tabs/VideoChannel/videoModal.vue'; | 112 | import VideoModal from '/@/views/device/list/cpns/tabs/VideoChannel/videoModal.vue'; |
| 113 | + import { VideoCancelModalParamsType } from '/@/views/device/list/cpns/tabs/VideoChannel/config'; | ||
| 114 | + import { CameraRecord } from '/@/api/camera/model/cameraModel'; | ||
| 114 | 115 | ||
| 115 | export default defineComponent({ | 116 | export default defineComponent({ |
| 116 | components: { | 117 | components: { |
| @@ -119,7 +120,6 @@ | @@ -119,7 +120,6 @@ | ||
| 119 | BasicTable, | 120 | BasicTable, |
| 120 | TableAction, | 121 | TableAction, |
| 121 | CameraDrawer, | 122 | CameraDrawer, |
| 122 | - VideoPreviewModal, | ||
| 123 | VideoModal, | 123 | VideoModal, |
| 124 | TableImg, | 124 | TableImg, |
| 125 | Authority, | 125 | Authority, |
| @@ -131,9 +131,8 @@ | @@ -131,9 +131,8 @@ | ||
| 131 | setup(_, { emit }) { | 131 | setup(_, { emit }) { |
| 132 | const searchInfo = reactive<Recordable>({}); | 132 | const searchInfo = reactive<Recordable>({}); |
| 133 | const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); | 133 | const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); |
| 134 | - const [registerModal, { openModal }] = useModal(); //手动输入 | ||
| 135 | 134 | ||
| 136 | - const [registerModal1, { openModal: openModal1 }] = useModal(); //流媒体获取 | 135 | + const [registerModal, { openModal }] = useModal(); //流媒体获取 |
| 137 | // 表格hooks | 136 | // 表格hooks |
| 138 | const [registerTable, { reload, setProps, clearSelectedRowKeys }] = useTable({ | 137 | const [registerTable, { reload, setProps, clearSelectedRowKeys }] = useTable({ |
| 139 | title: '视频列表', | 138 | title: '视频列表', |
| @@ -197,28 +196,25 @@ | @@ -197,28 +196,25 @@ | ||
| 197 | searchInfo.organizationId = organizationId; | 196 | searchInfo.organizationId = organizationId; |
| 198 | handleSuccess(); | 197 | handleSuccess(); |
| 199 | }; | 198 | }; |
| 200 | - const handleViewVideo = (record) => { | ||
| 201 | - const { videoPlatformDTO } = record; | 199 | + const handleViewVideo = (record: CameraRecord) => { |
| 200 | + const { videoPlatformDTO, params } = record; | ||
| 202 | const { type } = videoPlatformDTO || {}; | 201 | const { type } = videoPlatformDTO || {}; |
| 203 | - const commonRecord = { | ||
| 204 | - isUpdate: true, | ||
| 205 | - record, | ||
| 206 | - }; | ||
| 207 | - // 新增一个判断,GBT28181也弹出云台控制界面 | ||
| 208 | - // if (record.accessMode === AccessMode.Streaming && type === VideoPlatformEnum.ISC) { | ||
| 209 | - // openModal1(true, commonRecord); | ||
| 210 | - // } else if (record.accessMode === AccessMode.GBT28181) { | ||
| 211 | - // openModal1(true, { ...commonRecord, ifShowGBT: true }); | ||
| 212 | - // } else openModal(true, commonRecord); | ||
| 213 | 202 | ||
| 214 | - if ( | ||
| 215 | - record.accessMode === AccessMode.ManuallyEnter || | ||
| 216 | - (record.accessMode === AccessMode.Streaming && type === VideoPlatformEnum.FLUORITE) | ||
| 217 | - ) { | ||
| 218 | - openModal(true, commonRecord); | ||
| 219 | - } else if (record.accessMode === AccessMode.GBT28181) { | ||
| 220 | - openModal1(true, { ...commonRecord, ifShowGBT: true }); | ||
| 221 | - } else openModal1(true, commonRecord); | 203 | + openModal(true, { |
| 204 | + record: { | ||
| 205 | + id: record.id, | ||
| 206 | + canControl: | ||
| 207 | + [AccessMode.Streaming, AccessMode.GBT28181].includes(record.accessMode) && | ||
| 208 | + type !== VideoPlatformEnum.FLUORITE, | ||
| 209 | + isGBT: record.accessMode === AccessMode.GBT28181, | ||
| 210 | + channelId: params?.channelNo, | ||
| 211 | + tbDeviceId: params?.deviceId, | ||
| 212 | + getPlayUrl: async () => { | ||
| 213 | + const result = await getPlayUrl(record); | ||
| 214 | + return result; | ||
| 215 | + }, | ||
| 216 | + } as VideoCancelModalParamsType, | ||
| 217 | + }); | ||
| 222 | }; | 218 | }; |
| 223 | 219 | ||
| 224 | const handleSwitchMode = () => { | 220 | const handleSwitchMode = () => { |
| @@ -252,7 +248,6 @@ | @@ -252,7 +248,6 @@ | ||
| 252 | organizationIdTreeRef, | 248 | organizationIdTreeRef, |
| 253 | handleViewVideo, | 249 | handleViewVideo, |
| 254 | registerModal, | 250 | registerModal, |
| 255 | - registerModal1, | ||
| 256 | AccessMode, | 251 | AccessMode, |
| 257 | handleSwitchMode, | 252 | handleSwitchMode, |
| 258 | CameraPermission, | 253 | CameraPermission, |
| @@ -3,24 +3,21 @@ | @@ -3,24 +3,21 @@ | ||
| 3 | import OrganizationIdTree from '../../common/organizationIdTree/src/OrganizationIdTree.vue'; | 3 | import OrganizationIdTree from '../../common/organizationIdTree/src/OrganizationIdTree.vue'; |
| 4 | import { onMounted, reactive, Ref, 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, closeFlvPlay, getFlvPlayUrl } from '/@/api/camera/cameraManager'; | 6 | + import { cameraPage, closeFlvPlay } 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'; |
| 10 | import GBTDrawer from './components/GBTDrawer.vue'; | 10 | import GBTDrawer from './components/GBTDrawer.vue'; |
| 11 | import { useDrawer } from '/@/components/Drawer'; | 11 | import { useDrawer } from '/@/components/Drawer'; |
| 12 | - import { AccessMode, CameraPermission, PageMode } from './config.data'; | 12 | + import { CameraPermission, getPlayUrl, PageMode } from './config.data'; |
| 13 | import SvgIcon from '/@/components/Icon/src/SvgIcon.vue'; | 13 | import SvgIcon from '/@/components/Icon/src/SvgIcon.vue'; |
| 14 | - import { getStreamingPlayUrl } from '/@/api/camera/cameraManager'; | ||
| 15 | import { buildUUID } from '/@/utils/uuid'; | 14 | import { buildUUID } from '/@/utils/uuid'; |
| 16 | - import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video'; | ||
| 17 | - import { VideoJsPlayerOptions } from 'video.js'; | ||
| 18 | import { getBoundingClientRect } from '/@/utils/domUtils'; | 15 | import { getBoundingClientRect } from '/@/utils/domUtils'; |
| 19 | import { Authority } from '/@/components/Authority'; | 16 | import { Authority } from '/@/components/Authority'; |
| 20 | import { isRtspProtocol } from '/@/components/Video/src/utils'; | 17 | import { isRtspProtocol } from '/@/components/Video/src/utils'; |
| 21 | import { useFingerprint } from '/@/utils/useFingerprint'; | 18 | import { useFingerprint } from '/@/utils/useFingerprint'; |
| 22 | import { GetResult } from '@fingerprintjs/fingerprintjs'; | 19 | import { GetResult } from '@fingerprintjs/fingerprintjs'; |
| 23 | - import { getVideoControlStart } from '/@/api/device/videoChannel'; | 20 | + import XGPlayer from '/@/components/Video/src/XGPlayer.vue'; |
| 24 | 21 | ||
| 25 | const props = defineProps({ | 22 | const props = defineProps({ |
| 26 | mode: { | 23 | mode: { |
| @@ -29,17 +26,12 @@ | @@ -29,17 +26,12 @@ | ||
| 29 | }, | 26 | }, |
| 30 | }); | 27 | }); |
| 31 | type CameraRecordItem = CameraRecord & { | 28 | type CameraRecordItem = CameraRecord & { |
| 29 | + placeholder?: boolean; | ||
| 32 | canPlay?: boolean; | 30 | canPlay?: boolean; |
| 33 | isTransform?: boolean; | 31 | isTransform?: boolean; |
| 34 | withToken?: boolean; | 32 | withToken?: boolean; |
| 35 | - videoPlayerOptions?: VideoJsPlayerOptions; | ||
| 36 | playSourceUrl?: string; | 33 | playSourceUrl?: string; |
| 37 | - }; | ||
| 38 | - | ||
| 39 | - const basicVideoPlayOptions: VideoJsPlayerOptions = { | ||
| 40 | - width: '100%' as unknown as number, | ||
| 41 | - height: '100%' as unknown as number, | ||
| 42 | - autoplay: true, | 34 | + streamType?: string; |
| 43 | }; | 35 | }; |
| 44 | 36 | ||
| 45 | const emit = defineEmits(['switchMode']); | 37 | const emit = defineEmits(['switchMode']); |
| @@ -88,10 +80,7 @@ | @@ -88,10 +80,7 @@ | ||
| 88 | 80 | ||
| 89 | for (const item of unref(cameraList)) { | 81 | for (const item of unref(cameraList)) { |
| 90 | (item as CameraRecordItem).isTransform = false; | 82 | (item as CameraRecordItem).isTransform = false; |
| 91 | - (item as CameraRecordItem).videoPlayerOptions = { | ||
| 92 | - ...basicVideoPlayOptions, | ||
| 93 | - }; | ||
| 94 | - beforeVideoPlay(item as CameraRecordItem, result); | 83 | + beforeVideoPlay(item as CameraRecordItem); |
| 95 | } | 84 | } |
| 96 | } catch (error) { | 85 | } catch (error) { |
| 97 | } finally { | 86 | } finally { |
| @@ -100,75 +89,12 @@ | @@ -100,75 +89,12 @@ | ||
| 100 | }; | 89 | }; |
| 101 | 90 | ||
| 102 | const { getResult } = useFingerprint(); | 91 | const { getResult } = useFingerprint(); |
| 103 | - const beforeVideoPlay = async (record: CameraRecordItem, fingerprintResult: GetResult) => { | ||
| 104 | - if (record.accessMode === AccessMode.ManuallyEnter) { | ||
| 105 | - if (record.videoUrl) { | ||
| 106 | - const isFlvPlay = isRtspProtocol(record.videoUrl); | ||
| 107 | - const type = getVideoTypeByUrl(record.videoUrl); | ||
| 108 | - record.playSourceUrl = record.videoUrl; | ||
| 109 | - if (isFlvPlay) { | ||
| 110 | - // handleFlvPlayerUnload(record, fingerprintResult!.visitorId); | ||
| 111 | - record.playSourceUrl = getFlvPlayUrl(record.videoUrl, fingerprintResult.visitorId); | ||
| 112 | - record.withToken = true; | ||
| 113 | - } | ||
| 114 | - | ||
| 115 | - (record as CameraRecordItem).videoPlayerOptions = { | ||
| 116 | - ...basicVideoPlayOptions, | ||
| 117 | - sources: [ | ||
| 118 | - { | ||
| 119 | - src: record.playSourceUrl, | ||
| 120 | - type, | ||
| 121 | - }, | ||
| 122 | - ], | ||
| 123 | - }; | ||
| 124 | - record.isTransform = true; | ||
| 125 | - } | ||
| 126 | - } | ||
| 127 | - if (record.accessMode === AccessMode.GBT28181) { | ||
| 128 | - try { | ||
| 129 | - const { params } = record; | ||
| 130 | - if (params?.channelNo && params.deviceId) { | ||
| 131 | - const result = await getVideoControlStart({ | ||
| 132 | - deviceId: params.deviceId, | ||
| 133 | - channelId: params.channelNo, | ||
| 134 | - }); | ||
| 135 | - | ||
| 136 | - (record as CameraRecordItem).videoPlayerOptions = { | ||
| 137 | - ...basicVideoPlayOptions, | ||
| 138 | - sources: [{ src: result.data.flv, type: getVideoTypeByUrl(result.data.flv) }], | ||
| 139 | - }; | ||
| 140 | - } | ||
| 141 | - } finally { | ||
| 142 | - record.isTransform = true; | ||
| 143 | - } | ||
| 144 | - } | ||
| 145 | - if (record.accessMode === AccessMode.Streaming) { | ||
| 146 | - try { | ||
| 147 | - const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!); | ||
| 148 | - const index = unref(cameraList).findIndex((item) => item.id === record.id); | ||
| 149 | - if (~index) { | ||
| 150 | - const oldRecord = unref(cameraList).at(index)!; | ||
| 151 | - unref(cameraList)[index] = { | ||
| 152 | - ...oldRecord, | ||
| 153 | - | ||
| 154 | - videoPlayerOptions: { | ||
| 155 | - ...basicVideoPlayOptions, | ||
| 156 | - sources: [ | ||
| 157 | - { | ||
| 158 | - src: url, | ||
| 159 | - type: getVideoTypeByUrl(url), | ||
| 160 | - }, | ||
| 161 | - ], | ||
| 162 | - } as any, | ||
| 163 | - isTransform: true, | ||
| 164 | - }; | ||
| 165 | - } | ||
| 166 | - } catch (error) { | ||
| 167 | - } finally { | ||
| 168 | - const index = unref(cameraList).findIndex((item) => item.id === record.id); | ||
| 169 | - if (~index) unref(cameraList)[index].isTransform = true; | ||
| 170 | - } | ||
| 171 | - } | 92 | + const beforeVideoPlay = async (record: CameraRecordItem) => { |
| 93 | + if (!record.accessMode) return; | ||
| 94 | + const { url, type } = await getPlayUrl(record); | ||
| 95 | + record.playSourceUrl = url; | ||
| 96 | + record.streamType = type; | ||
| 97 | + record.isTransform = true; | ||
| 172 | }; | 98 | }; |
| 173 | 99 | ||
| 174 | const gridLayout = ref({ gutter: 1, column: 2 }); | 100 | const gridLayout = ref({ gutter: 1, column: 2 }); |
| @@ -312,11 +238,11 @@ | @@ -312,11 +238,11 @@ | ||
| 312 | ref="listEl" | 238 | ref="listEl" |
| 313 | :loading="loading" | 239 | :loading="loading" |
| 314 | :data-source="cameraList" | 240 | :data-source="cameraList" |
| 315 | - class="bg-light-50 w-full !h-full dark:bg-dark-900 split-mode-list" | 241 | + class="bg-light-50 w-full !h-full dark:bg-dark-900 split-mode-list full" |
| 316 | :grid="(gridLayout as any)" | 242 | :grid="(gridLayout as any)" |
| 317 | :style="{ '--height': `${100 / pagination.colNumber}%` }" | 243 | :style="{ '--height': `${100 / pagination.colNumber}%` }" |
| 318 | > | 244 | > |
| 319 | - <template #renderItem="{ item }"> | 245 | + <template #renderItem="{ item }: { item: CameraRecordItem }"> |
| 320 | <List.Item> | 246 | <List.Item> |
| 321 | <div class="box-border w-full !h-full p-1px"> | 247 | <div class="box-border w-full !h-full p-1px"> |
| 322 | <div | 248 | <div |
| @@ -332,17 +258,19 @@ | @@ -332,17 +258,19 @@ | ||
| 332 | v-show="!item.isTransform" | 258 | v-show="!item.isTransform" |
| 333 | :spinning="!item.isTransform" | 259 | :spinning="!item.isTransform" |
| 334 | /> | 260 | /> |
| 335 | - <BasicVideoPlay | 261 | + <XGPlayer |
| 336 | v-if="item.isTransform" | 262 | v-if="item.isTransform" |
| 337 | - :options="item.videoPlayerOptions" | 263 | + :url="item.playSourceUrl" |
| 264 | + :stream-type="item.streamType" | ||
| 338 | :with-token="item.withToken" | 265 | :with-token="item.withToken" |
| 266 | + :config="{ width: '100%', height: '100%' }" | ||
| 339 | @on-unmounted="handleCloseFlvPlayUrl(item)" | 267 | @on-unmounted="handleCloseFlvPlayUrl(item)" |
| 340 | /> | 268 | /> |
| 341 | <div | 269 | <div |
| 342 | class="video-container-mask absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center" | 270 | class="video-container-mask absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center" |
| 343 | style="height: 100%; background-color: rgba(0, 0, 0, 0.5)" | 271 | style="height: 100%; background-color: rgba(0, 0, 0, 0.5)" |
| 344 | > | 272 | > |
| 345 | - <span>{{ item.name }}</span> | 273 | + <span>{{ item.name }}{{ pagination.colNumber }}</span> |
| 346 | </div> | 274 | </div> |
| 347 | </div> | 275 | </div> |
| 348 | </div> | 276 | </div> |
| @@ -366,8 +294,10 @@ | @@ -366,8 +294,10 @@ | ||
| 366 | height: 100%; | 294 | height: 100%; |
| 367 | } | 295 | } |
| 368 | 296 | ||
| 369 | - .split-screen-mode:deep(.ant-spin-container) { | ||
| 370 | - height: 100% !important; | 297 | + .full { |
| 298 | + :deep(.ant-spin-container) { | ||
| 299 | + height: 100% !important; | ||
| 300 | + } | ||
| 371 | } | 301 | } |
| 372 | 302 | ||
| 373 | .video-container { | 303 | .video-container { |
| @@ -6,7 +6,6 @@ import { h } from 'vue'; | @@ -6,7 +6,6 @@ import { h } from 'vue'; | ||
| 6 | import SnHelpMessage from './SnHelpMessage.vue'; | 6 | import SnHelpMessage from './SnHelpMessage.vue'; |
| 7 | import SnHelpMessage1 from './SnHelpMessage1.vue'; | 7 | import SnHelpMessage1 from './SnHelpMessage1.vue'; |
| 8 | import { OrgTreeSelect } from '../../common/OrgTreeSelect'; | 8 | import { OrgTreeSelect } from '../../common/OrgTreeSelect'; |
| 9 | -import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; | ||
| 10 | import { createImgPreview } from '/@/components/Preview'; | 9 | import { createImgPreview } from '/@/components/Preview'; |
| 11 | import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter'; | 10 | import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter'; |
| 12 | import { findDictItemByCode } from '/@/api/system/dict'; | 11 | import { findDictItemByCode } from '/@/api/system/dict'; |
| @@ -15,7 +14,24 @@ import { DataSourceField } from '../../visual/packages/config/common.config'; | @@ -15,7 +14,24 @@ import { DataSourceField } from '../../visual/packages/config/common.config'; | ||
| 15 | import { getMeetTheConditionsDevice } from '/@/api/dataBoard'; | 14 | import { getMeetTheConditionsDevice } from '/@/api/dataBoard'; |
| 16 | import { useMessage } from '/@/hooks/web/useMessage'; | 15 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 17 | import { TransportTypeEnum } from '../../device/profiles/components/TransportDescript/const'; | 16 | import { TransportTypeEnum } from '../../device/profiles/components/TransportDescript/const'; |
| 18 | -import { getDeviceChannelList } from '/@/api/camera/cameraManager'; | 17 | +import { |
| 18 | + getDeviceChannelList, | ||
| 19 | + getFlvPlayUrl, | ||
| 20 | + getStreamingPlayUrl, | ||
| 21 | +} from '/@/api/camera/cameraManager'; | ||
| 22 | +import { CameraRecord } from '/@/api/camera/model/cameraModel'; | ||
| 23 | +import { isRtspProtocol } from '/@/components/Video/src/utils'; | ||
| 24 | +import { useFingerprint } from '/@/utils/useFingerprint'; | ||
| 25 | +import { getVideoControlStart } from '/@/api/device/videoChannel'; | ||
| 26 | +import { StreamType as PlayerStreamType } from '/@/components/Video/src/types'; | ||
| 27 | + | ||
| 28 | +interface FileItem { | ||
| 29 | + uid: string; | ||
| 30 | + name: string; | ||
| 31 | + status?: string; | ||
| 32 | + response?: string; | ||
| 33 | + url?: string; | ||
| 34 | +} | ||
| 19 | 35 | ||
| 20 | useComponentRegister('OrgTreeSelect', OrgTreeSelect); | 36 | useComponentRegister('OrgTreeSelect', OrgTreeSelect); |
| 21 | 37 | ||
| @@ -321,7 +337,7 @@ export const formSchema: QFormSchema[] = [ | @@ -321,7 +337,7 @@ export const formSchema: QFormSchema[] = [ | ||
| 321 | if (data) | 337 | if (data) |
| 322 | return data.map((item) => ({ | 338 | return data.map((item) => ({ |
| 323 | ...item, | 339 | ...item, |
| 324 | - label: item.name, | 340 | + label: item.name || item.channelId, |
| 325 | value: item.channelId, | 341 | value: item.channelId, |
| 326 | })); | 342 | })); |
| 327 | } catch (error) {} | 343 | } catch (error) {} |
| @@ -604,3 +620,37 @@ export const formGBTSchema: QFormSchema[] = [ | @@ -604,3 +620,37 @@ export const formGBTSchema: QFormSchema[] = [ | ||
| 604 | values.allDevice === GBT28181DeviceSelectMethod.PARTIAL, | 620 | values.allDevice === GBT28181DeviceSelectMethod.PARTIAL, |
| 605 | }, | 621 | }, |
| 606 | ]; | 622 | ]; |
| 623 | + | ||
| 624 | +export async function getPlayUrl( | ||
| 625 | + params: CameraRecord | ||
| 626 | +): Promise<{ url: string; type: PlayerStreamType }> { | ||
| 627 | + const { accessMode } = params; | ||
| 628 | + if (accessMode === AccessMode.ManuallyEnter) { | ||
| 629 | + const { videoUrl } = params; | ||
| 630 | + if (params.videoUrl) { | ||
| 631 | + const isRTSPPlay = isRtspProtocol(videoUrl); | ||
| 632 | + | ||
| 633 | + if (isRTSPPlay) { | ||
| 634 | + const { getResult } = useFingerprint(); | ||
| 635 | + const fingerprint = await getResult(); | ||
| 636 | + return { url: getFlvPlayUrl(videoUrl, fingerprint.visitorId), type: 'flv' }; | ||
| 637 | + } else { | ||
| 638 | + return { url: videoUrl, type: 'auto' }; | ||
| 639 | + } | ||
| 640 | + } | ||
| 641 | + } else if (accessMode === AccessMode.GBT28181) { | ||
| 642 | + const { deviceId, channelNo } = params?.params || {}; | ||
| 643 | + const result = await getVideoControlStart({ channelId: channelNo!, deviceId: deviceId! }); | ||
| 644 | + return { url: result.data.flv, type: 'flv' }; | ||
| 645 | + } else { | ||
| 646 | + const { id, playProtocol } = params; | ||
| 647 | + const result = await getStreamingPlayUrl(id); | ||
| 648 | + const type: PlayerStreamType = | ||
| 649 | + playProtocol === FluoriteMideaProtocolEnum.FLV | ||
| 650 | + ? 'flv' | ||
| 651 | + : playProtocol === FluoriteMideaProtocolEnum.HLS | ||
| 652 | + ? 'hls' | ||
| 653 | + : 'auto'; | ||
| 654 | + return { url: result.data.url, type }; | ||
| 655 | + } | ||
| 656 | +} |
| @@ -165,6 +165,14 @@ | @@ -165,6 +165,14 @@ | ||
| 165 | values.icon = file.url || null; | 165 | values.icon = file.url || null; |
| 166 | } | 166 | } |
| 167 | values = { ...positionState, ...values }; | 167 | values = { ...positionState, ...values }; |
| 168 | + | ||
| 169 | + Object.assign(values, { | ||
| 170 | + code: | ||
| 171 | + values?.tcpDeviceProtocol === TCPProtocolTypeEnum.CUSTOM | ||
| 172 | + ? values?.code | ||
| 173 | + : values?.addressCode, | ||
| 174 | + }); | ||
| 175 | + | ||
| 168 | delete values.deviceAddress; | 176 | delete values.deviceAddress; |
| 169 | emit('next', values); | 177 | emit('next', values); |
| 170 | // 获取输入的数据 | 178 | // 获取输入的数据 |
| @@ -419,6 +427,7 @@ | @@ -419,6 +427,7 @@ | ||
| 419 | const file = (value.icon || []).at(0) || {}; | 427 | const file = (value.icon || []).at(0) || {}; |
| 420 | value.icon = file.url || null; | 428 | value.icon = file.url || null; |
| 421 | } | 429 | } |
| 430 | + | ||
| 422 | return { | 431 | return { |
| 423 | ...value, | 432 | ...value, |
| 424 | ...(value?.code || value?.addressCode | 433 | ...(value?.code || value?.addressCode |
| 1 | import { h } from 'vue'; | 1 | import { h } from 'vue'; |
| 2 | import { BasicColumn, FormSchema } from '/@/components/Table'; | 2 | import { BasicColumn, FormSchema } from '/@/components/Table'; |
| 3 | import { Tag } from 'ant-design-vue'; | 3 | import { Tag } from 'ant-design-vue'; |
| 4 | -import { withInstall } from '/@/utils/index'; | ||
| 5 | - | ||
| 6 | -import VideoPlay from './video.vue'; | ||
| 7 | -export const Video = withInstall(VideoPlay); | 4 | +export type VideoCancelModalParamsType = { |
| 5 | + canControl?: boolean; | ||
| 6 | + isGBT?: boolean; | ||
| 7 | + tbDeviceId?: string; | ||
| 8 | + channelId?: string; | ||
| 9 | + id?: string; | ||
| 10 | + playerProps?: Recordable; | ||
| 11 | + getPlayUrl: () => Promise<Record<'url' | 'type', string>>; | ||
| 12 | +}; | ||
| 8 | 13 | ||
| 9 | //视频通道权限标识枚举 | 14 | //视频通道权限标识枚举 |
| 10 | export enum GBT28181_DEVICE_PERMISSION_ENUM { | 15 | export enum GBT28181_DEVICE_PERMISSION_ENUM { |
| @@ -50,7 +55,7 @@ export const configColumns: BasicColumn[] = [ | @@ -50,7 +55,7 @@ export const configColumns: BasicColumn[] = [ | ||
| 50 | return h( | 55 | return h( |
| 51 | Tag, | 56 | Tag, |
| 52 | { | 57 | { |
| 53 | - color: text === ChannelStatusEnum.ONLINE ? 'green' : 'blue', | 58 | + color: text === ChannelStatusEnum.ONLINE ? 'green' : 'red', |
| 54 | }, | 59 | }, |
| 55 | () => (text === ChannelStatusEnum.ONLINE ? '在线' : '离线') | 60 | () => (text === ChannelStatusEnum.ONLINE ? '在线' : '离线') |
| 56 | ); | 61 | ); |
| @@ -8,7 +8,7 @@ | @@ -8,7 +8,7 @@ | ||
| 8 | <a-button type="primary" @click="handleSyncPlay">通道同步</a-button> | 8 | <a-button type="primary" @click="handleSyncPlay">通道同步</a-button> |
| 9 | </Authority> | 9 | </Authority> |
| 10 | </template> | 10 | </template> |
| 11 | - <template #hasAudio="{ record }"> | 11 | + <template #hasAudio="{ record }: { record: VideoChannelItemType }"> |
| 12 | <Switch | 12 | <Switch |
| 13 | :checked="record.status === 1" | 13 | :checked="record.status === 1" |
| 14 | :loading="record.pendingStatus" | 14 | :loading="record.pendingStatus" |
| @@ -25,6 +25,7 @@ | @@ -25,6 +25,7 @@ | ||
| 25 | auth: GBT28181_DEVICE_PERMISSION_ENUM.PLAY_SYNC, | 25 | auth: GBT28181_DEVICE_PERMISSION_ENUM.PLAY_SYNC, |
| 26 | icon: 'ant-design:play-circle-outlined', | 26 | icon: 'ant-design:play-circle-outlined', |
| 27 | onClick: handlePlay.bind(null, record), | 27 | onClick: handlePlay.bind(null, record), |
| 28 | + ifShow: record?.status === 'ONLINE', //在线则显示播放按钮 | ||
| 28 | }, | 29 | }, |
| 29 | { | 30 | { |
| 30 | label: '停止', | 31 | label: '停止', |
| @@ -41,16 +42,22 @@ | @@ -41,16 +42,22 @@ | ||
| 41 | </template> | 42 | </template> |
| 42 | 43 | ||
| 43 | <script lang="ts" setup> | 44 | <script lang="ts" setup> |
| 44 | - import { configColumns, searchFormSchema, GBT28181_DEVICE_PERMISSION_ENUM } from './config'; | 45 | + import { |
| 46 | + configColumns, | ||
| 47 | + searchFormSchema, | ||
| 48 | + GBT28181_DEVICE_PERMISSION_ENUM, | ||
| 49 | + VideoCancelModalParamsType, | ||
| 50 | + } from './config'; | ||
| 45 | import { BasicTable, useTable, TableAction } from '/@/components/Table'; | 51 | import { BasicTable, useTable, TableAction } from '/@/components/Table'; |
| 46 | import { Switch } from 'ant-design-vue'; | 52 | import { Switch } from 'ant-design-vue'; |
| 47 | import { DeviceRecord } from '/@/api/device/model/deviceModel'; | 53 | import { DeviceRecord } from '/@/api/device/model/deviceModel'; |
| 48 | import VideoModal from './videoModal.vue'; | 54 | import VideoModal from './videoModal.vue'; |
| 49 | import { useModal } from '/@/components/Modal'; | 55 | import { useModal } from '/@/components/Modal'; |
| 50 | import { useMessage } from '/@/hooks/web/useMessage'; | 56 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 51 | - import { getVideoChannelList } from '/@/api/device/videoChannel'; | 57 | + import { getVideoChannelList, getVideoControlStart } from '/@/api/device/videoChannel'; |
| 52 | import { stopOnDemandVideoApiGet, syncVideoApiGet } from '/@/api/camera/cameraManager'; | 58 | import { stopOnDemandVideoApiGet, syncVideoApiGet } from '/@/api/camera/cameraManager'; |
| 53 | import { Authority } from '/@/components/Authority'; | 59 | import { Authority } from '/@/components/Authority'; |
| 60 | + import { VideoChannelItemType } from '/@/api/device/model/videoChannelModel'; | ||
| 54 | 61 | ||
| 55 | const props = defineProps<{ deviceDetail: DeviceRecord }>(); | 62 | const props = defineProps<{ deviceDetail: DeviceRecord }>(); |
| 56 | 63 | ||
| @@ -91,10 +98,20 @@ | @@ -91,10 +98,20 @@ | ||
| 91 | } | 98 | } |
| 92 | }; | 99 | }; |
| 93 | 100 | ||
| 94 | - const handlePlay = (record: Recordable) => { | 101 | + const handlePlay = (record: VideoChannelItemType) => { |
| 102 | + const { channelId } = record; | ||
| 95 | openModal(true, { | 103 | openModal(true, { |
| 96 | - record, | ||
| 97 | - ifShowGBT: true, | 104 | + record: { |
| 105 | + canControl: true, | ||
| 106 | + isGBT: true, | ||
| 107 | + channelId, | ||
| 108 | + tbDeviceId: props.deviceDetail.tbDeviceId, | ||
| 109 | + getPlayUrl: async () => { | ||
| 110 | + const { deviceId, channelId } = record; | ||
| 111 | + const result = await getVideoControlStart({ deviceId, channelId }); | ||
| 112 | + return { type: 'flv', url: result.data.flv }; | ||
| 113 | + }, | ||
| 114 | + } as VideoCancelModalParamsType, | ||
| 98 | }); | 115 | }); |
| 99 | }; | 116 | }; |
| 100 | 117 |
| 1 | <script lang="ts" setup> | 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 { Tooltip } from 'ant-design-vue'; | 2 | import { Tooltip } from 'ant-design-vue'; |
| 6 | - import { computed, CSSProperties, onMounted, onUnmounted, ref, unref } from 'vue'; | ||
| 7 | - import { useDesign } from '/@/hooks/web/useDesign'; | ||
| 8 | - import { getJwtToken, getShareJwtToken } from '/@/utils/auth'; | ||
| 9 | - import { isShareMode } from '/@/views/sys/share/hook'; | ||
| 10 | - import 'videojs-flvjs-es6'; | 3 | + import { ref, unref } from 'vue'; |
| 11 | import { QuestionCircleOutlined } from '@ant-design/icons-vue'; | 4 | import { QuestionCircleOutlined } from '@ant-design/icons-vue'; |
| 12 | import { | 5 | import { |
| 13 | CaretUpOutlined, | 6 | CaretUpOutlined, |
| @@ -19,106 +12,50 @@ | @@ -19,106 +12,50 @@ | ||
| 19 | ZoomOutOutlined, | 12 | ZoomOutOutlined, |
| 20 | } from '@ant-design/icons-vue'; | 13 | } from '@ant-design/icons-vue'; |
| 21 | import { Button, Slider } from 'ant-design-vue'; | 14 | import { Button, Slider } from 'ant-design-vue'; |
| 22 | - import { nextTick } from 'vue'; | ||
| 23 | import { controlling } from '/@/api/camera/cameraManager'; | 15 | import { controlling } from '/@/api/camera/cameraManager'; |
| 24 | import { setVideoControl } from '/@/api/device/videoChannel'; | 16 | import { setVideoControl } from '/@/api/device/videoChannel'; |
| 25 | - | ||
| 26 | - const { prefixCls } = useDesign('basic-video-play'); | ||
| 27 | - interface IGbtOption { | ||
| 28 | - tbDeviceId: string; | ||
| 29 | - channelId: string; | ||
| 30 | - } | 17 | + import XGPlayer from '/@/components/Video/src/XGPlayer.vue'; |
| 18 | + import PresetPlayer from 'xgplayer'; | ||
| 31 | 19 | ||
| 32 | const props = defineProps<{ | 20 | const props = defineProps<{ |
| 33 | - options?: VideoJsPlayerOptions; | ||
| 34 | - withToken?: boolean; | ||
| 35 | - isGBT?: boolean; | ||
| 36 | - GBTOption: IGbtOption; | ||
| 37 | - }>(); | ||
| 38 | - | ||
| 39 | - const emit = defineEmits<{ | ||
| 40 | - (event: 'ready', instance?: Nullable<VideoJsPlayer>): void; | ||
| 41 | - (event: 'onUnmounted'): void; | ||
| 42 | - }>(); | ||
| 43 | - | ||
| 44 | - const videoPlayEl = ref<HTMLVideoElement>(); | ||
| 45 | - | ||
| 46 | - const videoPlayInstance = ref<Nullable<VideoJsPlayer>>(); | ||
| 47 | - | ||
| 48 | - const getGBT = computed(() => { | ||
| 49 | - const { isGBT } = props; | ||
| 50 | - return isGBT; | ||
| 51 | - }); | ||
| 52 | - | ||
| 53 | - const getOptions = computed(() => { | ||
| 54 | - const { options, withToken } = props; | ||
| 55 | - | ||
| 56 | - const defaultOptions: VideoJsPlayerOptions & Recordable = { | ||
| 57 | - language: 'zh', | ||
| 58 | - muted: true, | ||
| 59 | - liveui: true, | ||
| 60 | - controls: true, | ||
| 61 | - techOrder: ['html5', 'flvjs'], | ||
| 62 | - flvjs: { | ||
| 63 | - mediaDataSource: { | ||
| 64 | - isLive: true, | ||
| 65 | - cors: true, | ||
| 66 | - hasAudio: false, | ||
| 67 | - withCredentials: false, | ||
| 68 | - }, | ||
| 69 | - config: { | ||
| 70 | - headers: { | ||
| 71 | - ...(withToken | ||
| 72 | - ? { | ||
| 73 | - 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | ||
| 74 | - } | ||
| 75 | - : {}), | ||
| 76 | - }, | ||
| 77 | - autoCleanupSourceBuffer: true, | ||
| 78 | - }, | ||
| 79 | - }, | 21 | + playUrl?: string; |
| 22 | + options?: { | ||
| 23 | + canControl?: boolean; | ||
| 24 | + isGBT?: boolean; | ||
| 25 | + tbDeviceId?: string; | ||
| 26 | + channelId?: string; | ||
| 27 | + id?: string; | ||
| 28 | + playerProps?: Recordable; | ||
| 80 | }; | 29 | }; |
| 81 | - return videoJs.mergeOptions(defaultOptions, options); | ||
| 82 | - }); | 30 | + }>(); |
| 83 | 31 | ||
| 84 | - const getWidthHeight = computed(() => { | ||
| 85 | - let { width = 300, height = 150 } = unref(getOptions); | ||
| 86 | - width = isNumber(width) ? (`${width}px` as unknown as number) : width; | ||
| 87 | - height = isNumber(height) ? (`${height}px` as unknown as number) : height; | ||
| 88 | - return { width, height } as CSSProperties; | ||
| 89 | - }); | 32 | + const playerRef = ref<InstanceType<typeof XGPlayer>>(); |
| 90 | 33 | ||
| 91 | // 控制云台控制速度 | 34 | // 控制云台控制速度 |
| 92 | const sliderValue = ref<number>(123); | 35 | const sliderValue = ref<number>(123); |
| 93 | 36 | ||
| 94 | - const init = () => { | ||
| 95 | - if (unref(videoPlayInstance)) unref(videoPlayInstance)?.dispose(); | ||
| 96 | - videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => { | ||
| 97 | - emit('ready', unref(videoPlayInstance)); | ||
| 98 | - }); | ||
| 99 | - }; | 37 | + const isPlay = ref(false); |
| 100 | 38 | ||
| 101 | //播放/暂停 | 39 | //播放/暂停 |
| 102 | const handleClick = () => { | 40 | const handleClick = () => { |
| 103 | - // unref(isPlay) ((true,停止),(false,开启)) | ||
| 104 | - if (unref(isPlay)) unref(videoPlayInstance)?.pause(); | ||
| 105 | - else unref(videoPlayInstance)?.play(); | ||
| 106 | - }; | ||
| 107 | - | ||
| 108 | - const getId = () => { | ||
| 109 | - const { options } = props || {}; | ||
| 110 | - const { sources }: any = options || {}; | ||
| 111 | - return sources?.[0]?.id; | 41 | + const playerInstance = unref(playerRef)?.getPlayerInstance(); |
| 42 | + if (playerInstance && playerInstance.paused) { | ||
| 43 | + unref(playerRef)?.getPlayerInstance()?.play(); | ||
| 44 | + isPlay.value = true; | ||
| 45 | + } else { | ||
| 46 | + unref(playerRef)?.getPlayerInstance()?.pause(); | ||
| 47 | + isPlay.value = false; | ||
| 48 | + } | ||
| 112 | }; | 49 | }; |
| 113 | 50 | ||
| 114 | const handleControl = (action: number, direction: string) => { | 51 | const handleControl = (action: number, direction: string) => { |
| 115 | - const organizationId = getId(); | ||
| 116 | - controlling({ cameraIndexCode: organizationId, action, command: direction }); | 52 | + const { options } = props; |
| 53 | + controlling({ cameraIndexCode: options?.id, action, command: direction }); | ||
| 117 | }; | 54 | }; |
| 118 | 55 | ||
| 119 | // 国标控制 | 56 | // 国标控制 |
| 120 | const handleGBTControl = (command: string, action?: number | string) => { | 57 | const handleGBTControl = (command: string, action?: number | string) => { |
| 121 | - const { tbDeviceId, channelId } = props.GBTOption; | 58 | + const { tbDeviceId, channelId } = props.options || {}; |
| 122 | setVideoControl(tbDeviceId, channelId, { | 59 | setVideoControl(tbDeviceId, channelId, { |
| 123 | command, | 60 | command, |
| 124 | horizonSpeed: action, | 61 | horizonSpeed: action, |
| @@ -127,33 +64,9 @@ | @@ -127,33 +64,9 @@ | ||
| 127 | }); | 64 | }); |
| 128 | }; | 65 | }; |
| 129 | 66 | ||
| 130 | - const isPlay = ref<Boolean | null | undefined>(false); | ||
| 131 | - | ||
| 132 | - onMounted(async () => { | ||
| 133 | - init(); | ||
| 134 | - await nextTick(); | ||
| 135 | - // isPlay.value = unref(videoPlayInstance)?.paused(); | ||
| 136 | - videoPlayInstance.value?.on('loadedmetadata', () => {}); | ||
| 137 | - videoPlayInstance.value?.on('waiting', () => { | ||
| 138 | - isPlay.value = false; | ||
| 139 | - }); | ||
| 140 | - videoPlayInstance.value?.on('play', () => { | ||
| 141 | - isPlay.value = true; | ||
| 142 | - }); | ||
| 143 | - videoPlayInstance.value?.on('playing', () => { | ||
| 144 | - isPlay.value = true; | ||
| 145 | - }); | ||
| 146 | - videoPlayInstance.value?.on('pause', () => { | ||
| 147 | - isPlay.value = false; | ||
| 148 | - }); | ||
| 149 | - videoPlayInstance.value?.on('ended', () => { | ||
| 150 | - isPlay.value = false; | ||
| 151 | - }); | ||
| 152 | - }); | ||
| 153 | - | ||
| 154 | //长按开始 | 67 | //长按开始 |
| 155 | - const moveStart = (action) => { | ||
| 156 | - if (unref(getGBT)) { | 68 | + const moveStart = (action: string) => { |
| 69 | + if (unref(props.options?.isGBT)) { | ||
| 157 | handleGBTControl(action, unref(sliderValue)); | 70 | handleGBTControl(action, unref(sliderValue)); |
| 158 | return; | 71 | return; |
| 159 | } | 72 | } |
| @@ -161,46 +74,40 @@ | @@ -161,46 +74,40 @@ | ||
| 161 | }; | 74 | }; |
| 162 | 75 | ||
| 163 | // 长按结束 | 76 | // 长按结束 |
| 164 | - const moveStop = (action) => { | ||
| 165 | - if (unref(getGBT)) { | 77 | + const moveStop = (action: string) => { |
| 78 | + if (unref(props.options?.isGBT)) { | ||
| 166 | handleGBTControl('STOP', unref(sliderValue)); | 79 | handleGBTControl('STOP', unref(sliderValue)); |
| 167 | return; | 80 | return; |
| 168 | } | 81 | } |
| 169 | handleControl(1, action); | 82 | handleControl(1, action); |
| 170 | }; | 83 | }; |
| 171 | 84 | ||
| 172 | - //页面卸载 | ||
| 173 | - onUnmounted(() => { | ||
| 174 | - unref(videoPlayInstance)?.dispose(); | ||
| 175 | - videoPlayInstance.value = null; | ||
| 176 | - emit('onUnmounted'); | ||
| 177 | - }); | ||
| 178 | - | ||
| 179 | - defineExpose({ | ||
| 180 | - reloadPlayer: init, | ||
| 181 | - getInstance: () => unref(videoPlayInstance), | ||
| 182 | - }); | 85 | + const handleVideoStatus = (flag: boolean) => { |
| 86 | + isPlay.value = flag; | ||
| 87 | + }; | ||
| 183 | </script> | 88 | </script> |
| 184 | 89 | ||
| 185 | <template> | 90 | <template> |
| 186 | - <div | ||
| 187 | - :class="prefixCls" | ||
| 188 | - class="!w-full h-full flex" | ||
| 189 | - :style="getWidthHeight" | ||
| 190 | - style="min-height: 100%" | ||
| 191 | - > | ||
| 192 | - <video | ||
| 193 | - ref="videoPlayEl" | ||
| 194 | - class="video-js vjs-big-play-centered vjs-show-big-play-button-on-pause !w-8/10 !h-full" | ||
| 195 | - muted | 91 | + <div class="!w-full h-full flex" style="min-height: 100%"> |
| 92 | + <XGPlayer | ||
| 93 | + ref="playerRef" | ||
| 94 | + v-bind="options?.playerProps" | ||
| 95 | + :url="playUrl" | ||
| 96 | + auto-play | ||
| 97 | + :config="{ fluid: true }" | ||
| 98 | + @play="handleVideoStatus(true)" | ||
| 99 | + @pause="handleVideoStatus(false)" | ||
| 100 | + @ended="handleVideoStatus(false)" | ||
| 101 | + /> | ||
| 102 | + | ||
| 103 | + <div | ||
| 104 | + v-if="options?.canControl" | ||
| 105 | + class="!w-2/10 px-4 bg-white flex justify-center items-center flex-col" | ||
| 196 | > | 106 | > |
| 197 | - </video> | ||
| 198 | - | ||
| 199 | - <div class="!w-2/10 bg-white flex items-center flex-col"> | ||
| 200 | <Tooltip> | 107 | <Tooltip> |
| 201 | - <template #title | ||
| 202 | - >长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。</template | ||
| 203 | - > | 108 | + <template #title> |
| 109 | + 长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。 | ||
| 110 | + </template> | ||
| 204 | <label class="validate-dot">云台控制</label> | 111 | <label class="validate-dot">云台控制</label> |
| 205 | <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" /> | 112 | <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" /> |
| 206 | </Tooltip> | 113 | </Tooltip> |
| @@ -248,7 +155,7 @@ | @@ -248,7 +155,7 @@ | ||
| 248 | <Button class="circle" @click="handleClick" /> | 155 | <Button class="circle" @click="handleClick" /> |
| 249 | </div> | 156 | </div> |
| 250 | </div> | 157 | </div> |
| 251 | - <div class="mt-5" v-if="getGBT"> | 158 | + <div class="mt-5" v-if="options.isGBT"> |
| 252 | <Slider v-model:value="sliderValue" :min="0" :max="255" style="width: 130px" /> | 159 | <Slider v-model:value="sliderValue" :min="0" :max="255" style="width: 130px" /> |
| 253 | </div> | 160 | </div> |
| 254 | <div class="flex justify-center mt-8"> | 161 | <div class="flex justify-center mt-8"> |
| @@ -274,16 +181,6 @@ | @@ -274,16 +181,6 @@ | ||
| 274 | </template> | 181 | </template> |
| 275 | 182 | ||
| 276 | <style lang="less" scoped> | 183 | <style lang="less" scoped> |
| 277 | - @prefix-cls: ~'@{namespace}-basic-video-play'; | ||
| 278 | - | ||
| 279 | - .@{prefix-cls} { | ||
| 280 | - .vjs-error-display { | ||
| 281 | - .vjs-modal-dialog-content::after { | ||
| 282 | - content: '无法加载视频,原因可能是服务器或网络故障,也可能是格式不支持.'; | ||
| 283 | - } | ||
| 284 | - } | ||
| 285 | - } | ||
| 286 | - | ||
| 287 | .child { | 184 | .child { |
| 288 | position: absolute; | 185 | position: absolute; |
| 289 | width: 3rem; | 186 | width: 3rem; |
| 1 | <template> | 1 | <template> |
| 2 | - <div> | ||
| 3 | - <BasicModal | ||
| 4 | - v-bind="$attrs" | ||
| 5 | - width="60rem" | ||
| 6 | - destroyOnClose | ||
| 7 | - :height="heightNum" | ||
| 8 | - @register="register" | ||
| 9 | - title="视频预览" | ||
| 10 | - :showOkBtn="false" | ||
| 11 | - @cancel="handleCancel" | ||
| 12 | - > | ||
| 13 | - <div | ||
| 14 | - class="flex items-center justify-center w-full h-full min-h-96 video-container bg-dark-50" | ||
| 15 | - > | ||
| 16 | - <VideoPlayer | ||
| 17 | - ref="videoInstance" | ||
| 18 | - v-if="showVideo" | ||
| 19 | - :options="(options as VideoJsPlayerOptions)" | ||
| 20 | - :withToken="withToken" | ||
| 21 | - :isGBT="isGBT" | ||
| 22 | - :GBTOption="GBTOption" | ||
| 23 | - /> | ||
| 24 | - </div> | ||
| 25 | - </BasicModal> | ||
| 26 | - </div> | 2 | + <BasicModal |
| 3 | + v-bind="$attrs" | ||
| 4 | + width="60rem" | ||
| 5 | + destroyOnClose | ||
| 6 | + :height="800" | ||
| 7 | + @register="register" | ||
| 8 | + title="视频预览" | ||
| 9 | + :showOkBtn="false" | ||
| 10 | + @cancel="handleCancel" | ||
| 11 | + > | ||
| 12 | + <div class="flex items-center justify-center w-full h-full min-h-96 video-container bg-dark-50"> | ||
| 13 | + <VideoPlayer :play-url="playUrl" :options="options" /> | ||
| 14 | + </div> | ||
| 15 | + </BasicModal> | ||
| 27 | </template> | 16 | </template> |
| 28 | <script setup lang="ts"> | 17 | <script setup lang="ts"> |
| 29 | - import { ref, reactive } from 'vue'; | 18 | + import { ref } from 'vue'; |
| 30 | import { BasicModal, useModalInner } from '/@/components/Modal'; | 19 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
| 31 | - import { VideoJsPlayerOptions } from 'video.js'; | ||
| 32 | import VideoPlayer from './video.vue'; | 20 | import VideoPlayer from './video.vue'; |
| 33 | - import { getVideoControlStart } from '/@/api/device/videoChannel'; | ||
| 34 | - import { VideoChannelItemType } from '/@/api/device/model/videoChannelModel'; | ||
| 35 | - import { getVideoTypeByUrl } from '/@/components/Video'; | ||
| 36 | - import { AccessMode } from '/@/views/camera/manage/config.data'; | ||
| 37 | - import { getStreamingPlayUrl } from '/@/api/camera/cameraManager'; | ||
| 38 | - | 21 | + import { VideoCancelModalParamsType } from '/@/views/device/list/cpns/tabs/VideoChannel/config'; |
| 39 | const emit = defineEmits(['reloadTable', 'register']); | 22 | const emit = defineEmits(['reloadTable', 'register']); |
| 40 | 23 | ||
| 41 | - const heightNum = ref(800); | ||
| 42 | - const showVideo = ref(false); | ||
| 43 | - | ||
| 44 | - const videoInstance = ref<InstanceType<typeof VideoPlayer>>(); | ||
| 45 | - | ||
| 46 | - const videoId = ref<string>(); | ||
| 47 | - | ||
| 48 | - const withToken = ref(false); | ||
| 49 | - | ||
| 50 | - const options = reactive<VideoJsPlayerOptions>({ | ||
| 51 | - width: '100%' as unknown as number, | ||
| 52 | - height: 384 as unknown as number, | ||
| 53 | - autoplay: true, | ||
| 54 | - }); | ||
| 55 | - const GBTOption = ref({ | ||
| 56 | - tbDeviceId: '', | ||
| 57 | - channelId: '', | ||
| 58 | - }); | ||
| 59 | - const isGBT = ref<boolean>(false); | ||
| 60 | - | 24 | + const playUrl = ref<Nullable<string>>(); |
| 25 | + const options = ref<Nullable<VideoCancelModalParamsType>>(); | ||
| 61 | const [register, { setModalProps }] = useModalInner( | 26 | const [register, { setModalProps }] = useModalInner( |
| 62 | - async (data: { record: VideoChannelItemType }) => { | ||
| 63 | - const { record, ifShowGBT = false } = data; | ||
| 64 | - const { params } = record as Recordable; | ||
| 65 | - videoId.value = record.id || ''; | ||
| 66 | - isGBT.value = ifShowGBT; | ||
| 67 | - GBTOption.value.tbDeviceId = params?.deviceId ? params?.deviceId : record?.deviceId; | ||
| 68 | - GBTOption.value.channelId = params?.channelNo ? params?.channelNo : record?.channelId; | 27 | + async (data: ModalParamsType<VideoCancelModalParamsType>) => { |
| 28 | + const { record } = data; | ||
| 69 | try { | 29 | try { |
| 30 | + playUrl.value = null; | ||
| 31 | + options.value = null; | ||
| 70 | setModalProps({ loading: true, loadingTip: '视频加载中...' }); | 32 | setModalProps({ loading: true, loadingTip: '视频加载中...' }); |
| 71 | - // getStreamingPlayUrl | ||
| 72 | - let result: any = null; | ||
| 73 | - if (record.accessMode === AccessMode.GBT28181 || ifShowGBT) { | ||
| 74 | - result = await getVideoControlStart({ | ||
| 75 | - deviceId: params?.deviceId ? params?.deviceId : record?.deviceId, | ||
| 76 | - channelId: params?.channelNo ? params?.channelNo : record?.channelId, | ||
| 77 | - }); | ||
| 78 | - options.sources = [{ src: result.data.flv, type: getVideoTypeByUrl(result.data.flv) }]; | ||
| 79 | - } else { | ||
| 80 | - result = await getStreamingPlayUrl(record.id); | ||
| 81 | - options.sources = [ | ||
| 82 | - { src: result.data.url, type: getVideoTypeByUrl(result.data.url), id: record.id }, | ||
| 83 | - ]; | ||
| 84 | - } | ||
| 85 | - showVideo.value = true; | 33 | + const { url, type } = await record.getPlayUrl(); |
| 34 | + | ||
| 35 | + playUrl.value = url; | ||
| 36 | + options.value = record; | ||
| 37 | + options.value.playerProps = { | ||
| 38 | + ...(options.value?.playerProps || {}), | ||
| 39 | + streamType: type, | ||
| 40 | + }; | ||
| 86 | } catch (error) { | 41 | } catch (error) { |
| 87 | } finally { | 42 | } finally { |
| 88 | setModalProps({ loading: false }); | 43 | setModalProps({ loading: false }); |
| @@ -91,18 +46,6 @@ | @@ -91,18 +46,6 @@ | ||
| 91 | ); | 46 | ); |
| 92 | 47 | ||
| 93 | const handleCancel = () => { | 48 | const handleCancel = () => { |
| 94 | - showVideo.value = false; | ||
| 95 | - withToken.value = false; | ||
| 96 | emit('reloadTable'); | 49 | emit('reloadTable'); |
| 97 | }; | 50 | }; |
| 98 | </script> | 51 | </script> |
| 99 | - | ||
| 100 | -<style lang="less" scoped> | ||
| 101 | - .video-container:deep(.vben-basic-video-play) { | ||
| 102 | - min-height: 13rem; | ||
| 103 | - } | ||
| 104 | - | ||
| 105 | - .video-container:deep(.video-js) { | ||
| 106 | - min-height: 13rem; | ||
| 107 | - } | ||
| 108 | -</style> |
| @@ -140,6 +140,7 @@ | @@ -140,6 +140,7 @@ | ||
| 140 | EnumTableChartMode, | 140 | EnumTableChartMode, |
| 141 | ModeSwitchButton, | 141 | ModeSwitchButton, |
| 142 | } from '/@/components/Widget'; | 142 | } from '/@/components/Widget'; |
| 143 | + import { TransportTypeEnum } from '/@/enums/deviceEnum'; | ||
| 143 | 144 | ||
| 144 | interface DeviceInfo { | 145 | interface DeviceInfo { |
| 145 | alarmStatus: 0 | 1; | 146 | alarmStatus: 0 | 1; |
| @@ -152,6 +153,7 @@ | @@ -152,6 +153,7 @@ | ||
| 152 | deviceType?: string; | 153 | deviceType?: string; |
| 153 | alias?: string; | 154 | alias?: string; |
| 154 | deviceProfileId: string; | 155 | deviceProfileId: string; |
| 156 | + transportType?: string; | ||
| 155 | } | 157 | } |
| 156 | type MarkerList = DeviceInfo & { marker: any; label: any }; | 158 | type MarkerList = DeviceInfo & { marker: any; label: any }; |
| 157 | 159 | ||
| @@ -377,7 +379,15 @@ | @@ -377,7 +379,15 @@ | ||
| 377 | width: 330, // 信息窗口宽度 | 379 | width: 330, // 信息窗口宽度 |
| 378 | height: 0, // 信息窗口高度 | 380 | height: 0, // 信息窗口高度 |
| 379 | }; | 381 | }; |
| 380 | - const { name, alias, organizationDTO, deviceState, deviceProfile, deviceType } = record; | 382 | + const { |
| 383 | + name, | ||
| 384 | + alias, | ||
| 385 | + organizationDTO, | ||
| 386 | + deviceState, | ||
| 387 | + deviceProfile, | ||
| 388 | + deviceType, | ||
| 389 | + transportType, | ||
| 390 | + } = record; | ||
| 381 | const { address, longitude, latitude } = record.deviceInfo; | 391 | const { address, longitude, latitude } = record.deviceInfo; |
| 382 | 392 | ||
| 383 | // 创建信息窗口对象 | 393 | // 创建信息窗口对象 |
| @@ -411,7 +421,9 @@ | @@ -411,7 +421,9 @@ | ||
| 411 | <div style="display:flex;justify-content:end; margin-top:10px"> | 421 | <div style="display:flex;justify-content:end; margin-top:10px"> |
| 412 | <button onclick="openDeviceInfoDrawer()" style="margin-right:10px;color:#fff;background-color:#409eff;padding:4px; border-radius:4px;">设备信息</button> | 422 | <button onclick="openDeviceInfoDrawer()" style="margin-right:10px;color:#fff;background-color:#409eff;padding:4px; border-radius:4px;">设备信息</button> |
| 413 | <button onclick="openHistoryModal()" style="display:${ | 423 | <button onclick="openHistoryModal()" style="display:${ |
| 414 | - deviceType !== 'GATEWAY' ? 'block' : 'none' | 424 | + deviceType !== 'GATEWAY' && transportType !== TransportTypeEnum.GBT28181 |
| 425 | + ? 'block' | ||
| 426 | + : 'none' | ||
| 415 | };color:#fff;background-color:#409eff;padding:4px; border-radius:4px;">${ | 427 | };color:#fff;background-color:#409eff;padding:4px; border-radius:4px;">${ |
| 416 | deviceType !== 'GATEWAY' ? '历史数据' : '' | 428 | deviceType !== 'GATEWAY' ? '历史数据' : '' |
| 417 | }</button> | 429 | }</button> |
| @@ -443,10 +455,12 @@ | @@ -443,10 +455,12 @@ | ||
| 443 | 455 | ||
| 444 | // 设备信息 | 456 | // 设备信息 |
| 445 | const openDeviceInfoDrawer = async () => { | 457 | const openDeviceInfoDrawer = async () => { |
| 446 | - const { id, tbDeviceId } = globalRecord; | 458 | + const { id, tbDeviceId, transportType, deviceType } = globalRecord; |
| 447 | openDrawer(true, { | 459 | openDrawer(true, { |
| 448 | id, | 460 | id, |
| 449 | tbDeviceId, | 461 | tbDeviceId, |
| 462 | + transportType, | ||
| 463 | + deviceType, | ||
| 450 | }); | 464 | }); |
| 451 | }; | 465 | }; |
| 452 | 466 |
| @@ -144,7 +144,7 @@ export const list = [ | @@ -144,7 +144,7 @@ export const list = [ | ||
| 144 | }, | 144 | }, |
| 145 | { | 145 | { |
| 146 | deviceType: '网关/直连设备', | 146 | deviceType: '网关/直连设备', |
| 147 | - function: '服务端响应共享属性集客户端属性给设备端', | 147 | + function: '服务端响应共享属性及客户端属性给设备端', |
| 148 | release: 'v1/devices/me/attributes/response/$request_id', | 148 | release: 'v1/devices/me/attributes/response/$request_id', |
| 149 | subscribe: 'v1/devices/me/attributes/response/+', | 149 | subscribe: 'v1/devices/me/attributes/response/+', |
| 150 | platform: '发布', | 150 | platform: '发布', |
| @@ -54,6 +54,7 @@ export const typeList = [ | @@ -54,6 +54,7 @@ export const typeList = [ | ||
| 54 | { label: '阿里', value: 'ALI_CLOUD' }, | 54 | { label: '阿里', value: 'ALI_CLOUD' }, |
| 55 | { label: '钉钉', value: 'DING_TALK' }, | 55 | { label: '钉钉', value: 'DING_TALK' }, |
| 56 | { label: '腾讯', value: 'TENCENT_CLOUD' }, | 56 | { label: '腾讯', value: 'TENCENT_CLOUD' }, |
| 57 | + { label: '阿里云语音', value: 'ALI_VOICE' }, | ||
| 57 | ]; | 58 | ]; |
| 58 | 59 | ||
| 59 | export const searchFormSchema: FormSchema[] = [ | 60 | export const searchFormSchema: FormSchema[] = [ |
| @@ -4,6 +4,7 @@ import { Tinymce } from '/@/components/Tinymce/index'; | @@ -4,6 +4,7 @@ import { Tinymce } from '/@/components/Tinymce/index'; | ||
| 4 | import { getOrganizationList } from '/@/api/system/system'; | 4 | import { getOrganizationList } from '/@/api/system/system'; |
| 5 | import { copyTransFun } from '/@/utils/fnUtils'; | 5 | import { copyTransFun } from '/@/utils/fnUtils'; |
| 6 | import { Tag } from 'ant-design-vue'; | 6 | import { Tag } from 'ant-design-vue'; |
| 7 | +import { useComponentRegister } from '/@/components/Form'; | ||
| 7 | 8 | ||
| 8 | export enum IsOrgEnum { | 9 | export enum IsOrgEnum { |
| 9 | IS_ORG_ENUM = 1, | 10 | IS_ORG_ENUM = 1, |
| @@ -12,6 +13,8 @@ export const isOrg = (type: string) => { | @@ -12,6 +13,8 @@ export const isOrg = (type: string) => { | ||
| 12 | return type === IsOrgEnum.IS_ORG_ENUM; | 13 | return type === IsOrgEnum.IS_ORG_ENUM; |
| 13 | }; | 14 | }; |
| 14 | 15 | ||
| 16 | +useComponentRegister('Tinymce', Tinymce); | ||
| 17 | + | ||
| 15 | export const columns: BasicColumn[] = [ | 18 | export const columns: BasicColumn[] = [ |
| 16 | { | 19 | { |
| 17 | title: '类型', | 20 | title: '类型', |
| @@ -92,21 +95,13 @@ export const formSchema: FormSchema[] = [ | @@ -92,21 +95,13 @@ export const formSchema: FormSchema[] = [ | ||
| 92 | }, | 95 | }, |
| 93 | { | 96 | { |
| 94 | field: 'content', | 97 | field: 'content', |
| 95 | - component: 'Input', | 98 | + component: 'Tinymce', |
| 96 | colProps: { span: 24 }, | 99 | colProps: { span: 24 }, |
| 97 | label: '通知内容', | 100 | label: '通知内容', |
| 98 | required: true, | 101 | required: true, |
| 99 | componentProps: { | 102 | componentProps: { |
| 100 | maxLength: 255, | 103 | maxLength: 255, |
| 101 | }, | 104 | }, |
| 102 | - render: ({ model, field }) => { | ||
| 103 | - return h(Tinymce, { | ||
| 104 | - value: model[field], | ||
| 105 | - onChange: (value: string) => { | ||
| 106 | - model[field] = value; | ||
| 107 | - }, | ||
| 108 | - }); | ||
| 109 | - }, | ||
| 110 | }, | 105 | }, |
| 111 | { | 106 | { |
| 112 | field: 'receiverType', | 107 | field: 'receiverType', |
| 1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
| 2 | - import { useTable, BasicTable } from '/@/components/Table'; | 2 | + import { BasicTable, useTable } from '/@/components/Table'; |
| 3 | import { getColumns } from './config'; | 3 | import { getColumns } from './config'; |
| 4 | - import { getRuleNodeEventList } from '/@/api/ruleDesigner'; | 4 | + import { doClearEvents, getRuleNodeEventList } from '/@/api/ruleDesigner'; |
| 5 | import { BasicNodeFormData, NodeData } from '../../../../types/node'; | 5 | import { BasicNodeFormData, NodeData } from '../../../../types/node'; |
| 6 | import { computed, reactive, ref, unref, watch } from 'vue'; | 6 | import { computed, reactive, ref, unref, watch } from 'vue'; |
| 7 | import { useUserStore } from '/@/store/modules/user'; | 7 | import { useUserStore } from '/@/store/modules/user'; |
| 8 | import { EventSelect } from '../EventSelect'; | 8 | import { EventSelect } from '../EventSelect'; |
| 9 | import { Icon } from '/@/components/Icon'; | 9 | import { Icon } from '/@/components/Icon'; |
| 10 | - import { Tooltip } from 'ant-design-vue'; | ||
| 11 | - import { EventTypeEnum } from '../EventSelect/config'; | 10 | + import { Tooltip, Popconfirm } from 'ant-design-vue'; |
| 11 | + import { EventTypeEnum, EventTypeNameEnum } from '../EventSelect/config'; | ||
| 12 | import { FilterForm } from '../FilterForm'; | 12 | import { FilterForm } from '../FilterForm'; |
| 13 | import { useModal } from '/@/components/Modal'; | 13 | import { useModal } from '/@/components/Modal'; |
| 14 | import { DataActionModeEnum } from '/@/enums/toolEnum'; | 14 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
| @@ -31,6 +31,8 @@ | @@ -31,6 +31,8 @@ | ||
| 31 | data: {} as Recordable, | 31 | data: {} as Recordable, |
| 32 | }); | 32 | }); |
| 33 | 33 | ||
| 34 | + const dataLength = ref(); | ||
| 35 | + | ||
| 34 | const [register, { reload, setColumns, setPagination }] = useTable({ | 36 | const [register, { reload, setColumns, setPagination }] = useTable({ |
| 35 | columns: getColumns(EventTypeEnum.DEBUG_RULE_NODE), | 37 | columns: getColumns(EventTypeEnum.DEBUG_RULE_NODE), |
| 36 | showIndexColumn: false, | 38 | showIndexColumn: false, |
| @@ -56,6 +58,8 @@ | @@ -56,6 +58,8 @@ | ||
| 56 | { ...searchParams.data, eventType: unref(eventType) } | 58 | { ...searchParams.data, eventType: unref(eventType) } |
| 57 | ); | 59 | ); |
| 58 | 60 | ||
| 61 | + dataLength.value = result.totalElements; | ||
| 62 | + | ||
| 59 | return result; | 63 | return result; |
| 60 | }, | 64 | }, |
| 61 | }); | 65 | }); |
| @@ -100,6 +104,17 @@ | @@ -100,6 +104,17 @@ | ||
| 100 | } as ModalParamsType<string>); | 104 | } as ModalParamsType<string>); |
| 101 | }; | 105 | }; |
| 102 | 106 | ||
| 107 | + const handleClearStats = async () => { | ||
| 108 | + const userInfo = useUserStore(); | ||
| 109 | + await doClearEvents({ | ||
| 110 | + ruleId: unref(getNodeId)!, | ||
| 111 | + tenantId: userInfo.getUserInfo.tenantId!, | ||
| 112 | + ...(searchParams.params as Record<'startTime' | 'endTime', string>), | ||
| 113 | + eventType: unref(eventType), | ||
| 114 | + }); | ||
| 115 | + reload(); | ||
| 116 | + }; | ||
| 117 | + | ||
| 103 | watch( | 118 | watch( |
| 104 | () => props.elementInfo?.id, | 119 | () => props.elementInfo?.id, |
| 105 | () => { | 120 | () => { |
| @@ -115,6 +130,20 @@ | @@ -115,6 +130,20 @@ | ||
| 115 | <EventSelect v-model:type="eventType" @change="handleEventTypeChange" /> | 130 | <EventSelect v-model:type="eventType" @change="handleEventTypeChange" /> |
| 116 | </template> | 131 | </template> |
| 117 | <template #toolbar> | 132 | <template #toolbar> |
| 133 | + <Popconfirm | ||
| 134 | + @confirm="handleClearStats" | ||
| 135 | + :title="`清除所有${EventTypeNameEnum[eventType]}`" | ||
| 136 | + :content="`是否确认清除所有${EventTypeNameEnum[eventType]}`" | ||
| 137 | + > | ||
| 138 | + <Tooltip title=""> | ||
| 139 | + <Icon | ||
| 140 | + v-if="dataLength" | ||
| 141 | + icon="ant-design:delete-outlined" | ||
| 142 | + class="cursor-pointer svg:text-2xl" | ||
| 143 | + /> | ||
| 144 | + </Tooltip> | ||
| 145 | + </Popconfirm> | ||
| 146 | + | ||
| 118 | <Tooltip title="过滤器"> | 147 | <Tooltip title="过滤器"> |
| 119 | <Icon | 148 | <Icon |
| 120 | icon="material-symbols:filter-list" | 149 | icon="material-symbols:filter-list" |
| @@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
| 5 | import { BasicForm, FormActionType, ThingsModelForm } from '/@/components/Form'; | 5 | import { BasicForm, FormActionType, ThingsModelForm } from '/@/components/Form'; |
| 6 | import { getFormSchemas, FormFieldsEnum } from './config'; | 6 | import { getFormSchemas, FormFieldsEnum } from './config'; |
| 7 | import { FlipFlop } from '../FlipFlop'; | 7 | import { FlipFlop } from '../FlipFlop'; |
| 8 | - import { ComponentPublicInstance, ref, unref } from 'vue'; | 8 | + import { ComponentPublicInstance, ref, unref, watch } from 'vue'; |
| 9 | import { ExecutionActionListRefItemType } from './type'; | 9 | import { ExecutionActionListRefItemType } from './type'; |
| 10 | import { useExecutionActionData } from './useExecutionActionData'; | 10 | import { useExecutionActionData } from './useExecutionActionData'; |
| 11 | import { createNewExecutionActionItem } from '.'; | 11 | import { createNewExecutionActionItem } from '.'; |
| @@ -63,6 +63,20 @@ | @@ -63,6 +63,20 @@ | ||
| 63 | hasAlarmNotify | 63 | hasAlarmNotify |
| 64 | ); | 64 | ); |
| 65 | 65 | ||
| 66 | + const { organizationId } = useSceneLinkageDrawerContext(); | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * @description on organization change | ||
| 70 | + */ | ||
| 71 | + watch(organizationId, () => { | ||
| 72 | + unref(executionActionListRef).forEach((flipFlopItem) => | ||
| 73 | + flipFlopItem.ref?.setFieldsValue({ | ||
| 74 | + [FormFieldsEnum.ENTITY_ID]: [], | ||
| 75 | + [FormFieldsEnum.ALARM_PROFILED]: null, | ||
| 76 | + }) | ||
| 77 | + ); | ||
| 78 | + }); | ||
| 79 | + | ||
| 66 | defineExpose({ | 80 | defineExpose({ |
| 67 | getFieldsValue, | 81 | getFieldsValue, |
| 68 | setFieldsValue, | 82 | setFieldsValue, |
| @@ -97,8 +97,8 @@ | @@ -97,8 +97,8 @@ | ||
| 97 | 97 | ||
| 98 | onMounted(() => { | 98 | onMounted(() => { |
| 99 | const platform = getPlatFormInfo(); | 99 | const platform = getPlatFormInfo(); |
| 100 | - defaultLogo.value = platform.logo || ''; | ||
| 101 | - show.value = !platform.background; | 100 | + defaultLogo.value = platform?.logo || ''; |
| 101 | + show.value = !platform?.background; | ||
| 102 | }); | 102 | }); |
| 103 | </script> | 103 | </script> |
| 104 | <style lang="less"> | 104 | <style lang="less"> |
| @@ -9,6 +9,9 @@ | @@ -9,6 +9,9 @@ | ||
| 9 | <div style="height: 50vh"> | 9 | <div style="height: 50vh"> |
| 10 | <BasicForm @register="registerForm"> | 10 | <BasicForm @register="registerForm"> |
| 11 | <template #organizationId="{ model, field }"> | 11 | <template #organizationId="{ model, field }"> |
| 12 | + <Button type="link" @click="handleOpenCreate" style="padding: 0; z-index: 9999" | ||
| 13 | + >新增组织 | ||
| 14 | + </Button> | ||
| 12 | <BasicTree | 15 | <BasicTree |
| 13 | v-if="organizationTreeData.length" | 16 | v-if="organizationTreeData.length" |
| 14 | v-model:value="model[field]" | 17 | v-model:value="model[field]" |
| @@ -44,6 +47,8 @@ | @@ -44,6 +47,8 @@ | ||
| 44 | </a-select> | 47 | </a-select> |
| 45 | </template> | 48 | </template> |
| 46 | </BasicForm> | 49 | </BasicForm> |
| 50 | + | ||
| 51 | + <OrganizationDrawer @register="registerDrawer" @success="handleReload" /> | ||
| 47 | </div> | 52 | </div> |
| 48 | </BasicModal> | 53 | </BasicModal> |
| 49 | <RoleDrawer @register="registerRoleDrawer" @success="handleSuccess" /> | 54 | <RoleDrawer @register="registerRoleDrawer" @success="handleSuccess" /> |
| @@ -53,6 +58,7 @@ | @@ -53,6 +58,7 @@ | ||
| 53 | import { BasicModal, useModalInner } from '/@/components/Modal'; | 58 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
| 54 | import { BasicForm, useForm } from '/@/components/Form/index'; | 59 | import { BasicForm, useForm } from '/@/components/Form/index'; |
| 55 | import { accountFormSchema } from './account.data'; | 60 | import { accountFormSchema } from './account.data'; |
| 61 | + import { Button } from 'ant-design-vue'; | ||
| 56 | import { | 62 | import { |
| 57 | findCurrentUserRelation, | 63 | findCurrentUserRelation, |
| 58 | SaveOrUpdateUserInfo, | 64 | SaveOrUpdateUserInfo, |
| @@ -67,13 +73,16 @@ | @@ -67,13 +73,16 @@ | ||
| 67 | import { PlusOutlined } from '@ant-design/icons-vue'; | 73 | import { PlusOutlined } from '@ant-design/icons-vue'; |
| 68 | import { useDrawer } from '/@/components/Drawer'; | 74 | import { useDrawer } from '/@/components/Drawer'; |
| 69 | import RoleDrawer from '../role/RoleDrawer.vue'; | 75 | import RoleDrawer from '../role/RoleDrawer.vue'; |
| 76 | + import OrganizationDrawer from '/@/views/system/organization/OrganizationDrawer.vue'; | ||
| 70 | 77 | ||
| 71 | export default defineComponent({ | 78 | export default defineComponent({ |
| 72 | name: 'AccountModal', | 79 | name: 'AccountModal', |
| 73 | components: { | 80 | components: { |
| 74 | BasicModal, | 81 | BasicModal, |
| 75 | BasicForm, | 82 | BasicForm, |
| 83 | + Button, | ||
| 76 | BasicTree, | 84 | BasicTree, |
| 85 | + OrganizationDrawer, | ||
| 77 | PlusOutlined, | 86 | PlusOutlined, |
| 78 | RoleDrawer, | 87 | RoleDrawer, |
| 79 | VNodes: (_, { attrs }) => { | 88 | VNodes: (_, { attrs }) => { |
| @@ -273,6 +282,17 @@ | @@ -273,6 +282,17 @@ | ||
| 273 | ]; | 282 | ]; |
| 274 | }; | 283 | }; |
| 275 | 284 | ||
| 285 | + const [registerDrawer, { openDrawer: addOpenDrawer }] = useDrawer(); | ||
| 286 | + | ||
| 287 | + const handleOpenCreate = () => { | ||
| 288 | + addOpenDrawer(true, { isUpdate: false }); | ||
| 289 | + }; | ||
| 290 | + const handleReload = async () => { | ||
| 291 | + const groupListModel = await findCurrentUserGroups(); | ||
| 292 | + copyTransTreeFun(groupListModel); | ||
| 293 | + organizationTreeData.value = groupListModel; | ||
| 294 | + }; | ||
| 295 | + | ||
| 276 | return { | 296 | return { |
| 277 | registerModal, | 297 | registerModal, |
| 278 | registerForm, | 298 | registerForm, |
| @@ -291,6 +311,9 @@ | @@ -291,6 +311,9 @@ | ||
| 291 | handleCheckClick, | 311 | handleCheckClick, |
| 292 | handleUnSelectAll, | 312 | handleUnSelectAll, |
| 293 | handleStrictlyStatus, | 313 | handleStrictlyStatus, |
| 314 | + handleOpenCreate, | ||
| 315 | + registerDrawer, | ||
| 316 | + handleReload, | ||
| 294 | }; | 317 | }; |
| 295 | }, | 318 | }, |
| 296 | }); | 319 | }); |
| @@ -298,6 +321,8 @@ | @@ -298,6 +321,8 @@ | ||
| 298 | <style scoped lang="less"> | 321 | <style scoped lang="less"> |
| 299 | :deep(.vben-basic-tree) { | 322 | :deep(.vben-basic-tree) { |
| 300 | width: 100% !important; | 323 | width: 100% !important; |
| 324 | + margin-top: -28px !important; | ||
| 325 | + padding: 0; | ||
| 301 | } | 326 | } |
| 302 | 327 | ||
| 303 | :deep(.is-unflod) { | 328 | :deep(.is-unflod) { |
| @@ -31,7 +31,7 @@ | @@ -31,7 +31,7 @@ | ||
| 31 | </template> | 31 | </template> |
| 32 | 32 | ||
| 33 | <script lang="ts"> | 33 | <script lang="ts"> |
| 34 | - import { defineComponent, onMounted, ref, computed } from 'vue'; | 34 | + import { defineComponent, onMounted, ref, computed, watch } from 'vue'; |
| 35 | import { Card } from 'ant-design-vue'; | 35 | import { Card } from 'ant-design-vue'; |
| 36 | import { BasicForm, useForm } from '/@/components/Form/index'; | 36 | import { BasicForm, useForm } from '/@/components/Form/index'; |
| 37 | import { schemas, provSchemas } from '../config/enterPriseInfo.config'; | 37 | import { schemas, provSchemas } from '../config/enterPriseInfo.config'; |
| @@ -73,7 +73,10 @@ | @@ -73,7 +73,10 @@ | ||
| 73 | loading: false, | 73 | loading: false, |
| 74 | tip: '拼命加载中...', | 74 | tip: '拼命加载中...', |
| 75 | }); | 75 | }); |
| 76 | - const [registerForm, { getFieldsValue, setFieldsValue, validate, clearValidate }] = useForm({ | 76 | + const [ |
| 77 | + registerForm, | ||
| 78 | + { getFieldsValue, setFieldsValue, validate, clearValidate, validateFields }, | ||
| 79 | + ] = useForm({ | ||
| 77 | labelWidth: 80, | 80 | labelWidth: 80, |
| 78 | schemas, | 81 | schemas, |
| 79 | showResetButton: false, | 82 | showResetButton: false, |
| @@ -101,6 +104,13 @@ | @@ -101,6 +104,13 @@ | ||
| 101 | const handleSetCodeImgUrl = (d) => { | 104 | const handleSetCodeImgUrl = (d) => { |
| 102 | qrcodePic.value = d; | 105 | qrcodePic.value = d; |
| 103 | }; | 106 | }; |
| 107 | + watch( | ||
| 108 | + () => qrcodePic.value, | ||
| 109 | + (newValue) => { | ||
| 110 | + if (!newValue) validateFields(['qrcode']); | ||
| 111 | + else clearValidate('qrcode'); | ||
| 112 | + } | ||
| 113 | + ); | ||
| 104 | // 更新 | 114 | // 更新 |
| 105 | const handleUpdateInfo = async () => { | 115 | const handleUpdateInfo = async () => { |
| 106 | try { | 116 | try { |
| 1 | import { BasicColumn, FormSchema } from '/@/components/Table'; | 1 | import { BasicColumn, FormSchema } from '/@/components/Table'; |
| 2 | import moment from 'moment'; | 2 | import moment from 'moment'; |
| 3 | -import { findDictItemByCode } from '/@/api/system/dict'; | ||
| 4 | import { format } from '../util'; | 3 | import { format } from '../util'; |
| 5 | import { h } from 'vue'; | 4 | import { h } from 'vue'; |
| 6 | import { Tag } from 'ant-design-vue'; | 5 | import { Tag } from 'ant-design-vue'; |
| 7 | 6 | ||
| 8 | -//格式化资源类型和操作类型 | ||
| 9 | -let formatText; | ||
| 10 | -let actionTypeText; | ||
| 11 | -async function formatTextFunc() { | ||
| 12 | - formatText = await findDictItemByCode({ dictCode: 'operate_log' }); | ||
| 13 | - actionTypeText = await findDictItemByCode({ dictCode: 'exception_log' }); | ||
| 14 | -} | ||
| 15 | -formatTextFunc(); | ||
| 16 | // 表格数据 | 7 | // 表格数据 |
| 17 | export const columns: BasicColumn[] = [ | 8 | export const columns: BasicColumn[] = [ |
| 18 | { | 9 | { |
| @@ -21,24 +12,6 @@ export const columns: BasicColumn[] = [ | @@ -21,24 +12,6 @@ export const columns: BasicColumn[] = [ | ||
| 21 | width: 120, | 12 | width: 120, |
| 22 | }, | 13 | }, |
| 23 | { | 14 | { |
| 24 | - title: '客户名称', | ||
| 25 | - dataIndex: 'customerName', | ||
| 26 | - width: 120, | ||
| 27 | - }, | ||
| 28 | - { | ||
| 29 | - title: '资源类型', | ||
| 30 | - dataIndex: 'entityType', | ||
| 31 | - width: 180, | ||
| 32 | - format: (_, record) => { | ||
| 33 | - const text = formatText.find((f) => { | ||
| 34 | - if (f.itemValue == record.entityType) { | ||
| 35 | - return f.itemText; | ||
| 36 | - } | ||
| 37 | - }); | ||
| 38 | - return text?.itemText; | ||
| 39 | - }, | ||
| 40 | - }, | ||
| 41 | - { | ||
| 42 | title: '资源名称', | 15 | title: '资源名称', |
| 43 | dataIndex: 'entityName', | 16 | dataIndex: 'entityName', |
| 44 | width: 180, | 17 | width: 180, |
| @@ -49,19 +22,6 @@ export const columns: BasicColumn[] = [ | @@ -49,19 +22,6 @@ export const columns: BasicColumn[] = [ | ||
| 49 | width: 180, | 22 | width: 180, |
| 50 | }, | 23 | }, |
| 51 | { | 24 | { |
| 52 | - title: '操作类型', | ||
| 53 | - dataIndex: 'actionType', | ||
| 54 | - width: 180, | ||
| 55 | - format: (_, record) => { | ||
| 56 | - const text = actionTypeText.find((f) => { | ||
| 57 | - if (f.itemValue == record.actionType) { | ||
| 58 | - return f.itemText; | ||
| 59 | - } | ||
| 60 | - }); | ||
| 61 | - return text?.itemText; | ||
| 62 | - }, | ||
| 63 | - }, | ||
| 64 | - { | ||
| 65 | title: '操作状态', | 25 | title: '操作状态', |
| 66 | dataIndex: 'actionStatus', | 26 | dataIndex: 'actionStatus', |
| 67 | width: 180, | 27 | width: 180, |
| @@ -116,22 +76,7 @@ export const formSchema: FormSchema[] = [ | @@ -116,22 +76,7 @@ export const formSchema: FormSchema[] = [ | ||
| 116 | disabled: true, | 76 | disabled: true, |
| 117 | }, | 77 | }, |
| 118 | }, | 78 | }, |
| 119 | - { | ||
| 120 | - field: 'customerName', | ||
| 121 | - label: '客户名称', | ||
| 122 | - component: 'Input', | ||
| 123 | - componentProps: { | ||
| 124 | - disabled: true, | ||
| 125 | - }, | ||
| 126 | - }, | ||
| 127 | - { | ||
| 128 | - field: 'entityType', | ||
| 129 | - label: '资源类型', | ||
| 130 | - component: 'Input', | ||
| 131 | - componentProps: { | ||
| 132 | - disabled: true, | ||
| 133 | - }, | ||
| 134 | - }, | 79 | + |
| 135 | { | 80 | { |
| 136 | field: 'entityName', | 81 | field: 'entityName', |
| 137 | label: '资源名称', | 82 | label: '资源名称', |
| @@ -150,14 +95,6 @@ export const formSchema: FormSchema[] = [ | @@ -150,14 +95,6 @@ export const formSchema: FormSchema[] = [ | ||
| 150 | }, | 95 | }, |
| 151 | }, | 96 | }, |
| 152 | { | 97 | { |
| 153 | - field: 'actionType', | ||
| 154 | - label: '操作类型', | ||
| 155 | - component: 'Input', | ||
| 156 | - componentProps: { | ||
| 157 | - disabled: true, | ||
| 158 | - }, | ||
| 159 | - }, | ||
| 160 | - { | ||
| 161 | field: 'actionStatus', | 98 | field: 'actionStatus', |
| 162 | label: '操作状态', | 99 | label: '操作状态', |
| 163 | component: 'Input', | 100 | component: 'Input', |
| 1 | import { BasicColumn } from '/@/components/Table'; | 1 | import { BasicColumn } from '/@/components/Table'; |
| 2 | import { FormSchema } from '/@/components/Table'; | 2 | import { FormSchema } from '/@/components/Table'; |
| 3 | import { useI18n } from '/@/hooks/web/useI18n'; | 3 | import { useI18n } from '/@/hooks/web/useI18n'; |
| 4 | +import { createOrganizationSearch } from '/@/utils/organizationSearch'; | ||
| 4 | const { t } = useI18n(); | 5 | const { t } = useI18n(); |
| 5 | 6 | ||
| 6 | export const columns: BasicColumn[] = [ | 7 | export const columns: BasicColumn[] = [ |
| @@ -38,6 +39,7 @@ export const formSchema: FormSchema[] = [ | @@ -38,6 +39,7 @@ export const formSchema: FormSchema[] = [ | ||
| 38 | value: 'id', | 39 | value: 'id', |
| 39 | }, | 40 | }, |
| 40 | maxTagCount: 10, | 41 | maxTagCount: 10, |
| 42 | + ...createOrganizationSearch(), | ||
| 41 | getPopupContainer: () => document.body, | 43 | getPopupContainer: () => document.body, |
| 42 | }, | 44 | }, |
| 43 | }, | 45 | }, |
| @@ -13,6 +13,7 @@ export enum FormFieldEnum { | @@ -13,6 +13,7 @@ export enum FormFieldEnum { | ||
| 13 | 13 | ||
| 14 | DEVICE_ID = 'deviceId', | 14 | DEVICE_ID = 'deviceId', |
| 15 | CHANNEL_ID = 'channelId', | 15 | CHANNEL_ID = 'channelId', |
| 16 | + PLAY_PROTOCOL = 'playProtocol', | ||
| 16 | } | 17 | } |
| 17 | 18 | ||
| 18 | export interface DataSourceValueType { | 19 | export interface DataSourceValueType { |
| @@ -70,6 +71,7 @@ export const formSchemas: FormSchema[] = [ | @@ -70,6 +71,7 @@ export const formSchemas: FormSchema[] = [ | ||
| 70 | component: 'Input', | 71 | component: 'Input', |
| 71 | label: '地址', | 72 | label: '地址', |
| 72 | colProps: { span: 16 }, | 73 | colProps: { span: 16 }, |
| 74 | + required: true, | ||
| 73 | ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.CUSTOM, | 75 | ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.CUSTOM, |
| 74 | componentProps: { | 76 | componentProps: { |
| 75 | placeholder: '请输入自定义地址', | 77 | placeholder: '请输入自定义地址', |
| @@ -79,6 +81,7 @@ export const formSchemas: FormSchema[] = [ | @@ -79,6 +81,7 @@ export const formSchemas: FormSchema[] = [ | ||
| 79 | field: FormFieldEnum.ORGANIZATION_ID, | 81 | field: FormFieldEnum.ORGANIZATION_ID, |
| 80 | component: 'OrgTreeSelect', | 82 | component: 'OrgTreeSelect', |
| 81 | label: '组织', | 83 | label: '组织', |
| 84 | + required: true, | ||
| 82 | colProps: { span: 8 }, | 85 | colProps: { span: 8 }, |
| 83 | ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.VIDEO_MANAGE, | 86 | ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.VIDEO_MANAGE, |
| 84 | componentProps: ({ formActionType }) => { | 87 | componentProps: ({ formActionType }) => { |
| @@ -105,6 +108,7 @@ export const formSchemas: FormSchema[] = [ | @@ -105,6 +108,7 @@ export const formSchemas: FormSchema[] = [ | ||
| 105 | field: FormFieldEnum.VIDEO_ID, | 108 | field: FormFieldEnum.VIDEO_ID, |
| 106 | component: 'ApiSelect', | 109 | component: 'ApiSelect', |
| 107 | label: '地址', | 110 | label: '地址', |
| 111 | + required: true, | ||
| 108 | colProps: { span: 8 }, | 112 | colProps: { span: 8 }, |
| 109 | ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.VIDEO_MANAGE, | 113 | ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.VIDEO_MANAGE, |
| 110 | componentProps: ({ formActionType, formModel }) => { | 114 | componentProps: ({ formActionType, formModel }) => { |
| @@ -129,6 +133,7 @@ export const formSchemas: FormSchema[] = [ | @@ -129,6 +133,7 @@ export const formSchemas: FormSchema[] = [ | ||
| 129 | accessMode === AccessMode.GBT28181 ? options.params?.channelNo : null, | 133 | accessMode === AccessMode.GBT28181 ? options.params?.channelNo : null, |
| 130 | [FormFieldEnum.DEVICE_ID]: | 134 | [FormFieldEnum.DEVICE_ID]: |
| 131 | accessMode === AccessMode.GBT28181 ? options.params?.deviceId : null, | 135 | accessMode === AccessMode.GBT28181 ? options.params?.deviceId : null, |
| 136 | + [FormFieldEnum.PLAY_PROTOCOL]: options?.playProtocol, | ||
| 132 | }); | 137 | }); |
| 133 | }, | 138 | }, |
| 134 | }; | 139 | }; |
| @@ -146,4 +151,10 @@ export const formSchemas: FormSchema[] = [ | @@ -146,4 +151,10 @@ export const formSchemas: FormSchema[] = [ | ||
| 146 | component: 'Input', | 151 | component: 'Input', |
| 147 | ifShow: false, | 152 | ifShow: false, |
| 148 | }, | 153 | }, |
| 154 | + { | ||
| 155 | + field: FormFieldEnum.PLAY_PROTOCOL, | ||
| 156 | + label: '播放协议', | ||
| 157 | + component: 'InputNumber', | ||
| 158 | + ifShow: false, | ||
| 159 | + }, | ||
| 149 | ]; | 160 | ]; |
| 1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
| 2 | import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type'; | 2 | import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type'; |
| 3 | import { option } from './config'; | 3 | import { option } from './config'; |
| 4 | - import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video'; | ||
| 5 | - import { computed, onMounted, onUnmounted, toRaw } from 'vue'; | ||
| 6 | - import { VideoJsPlayerOptions } from 'video.js'; | ||
| 7 | - import { AccessMode } from '/@/views/camera/manage/config.data'; | ||
| 8 | - import { unref } from 'vue'; | ||
| 9 | - import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '/@/api/camera/cameraManager'; | 4 | + import { XGPlayer } from '/@/components/Video'; |
| 5 | + import { onMounted, onUnmounted } from 'vue'; | ||
| 6 | + import { getPlayUrl } from '/@/views/camera/manage/config.data'; | ||
| 7 | + import { closeFlvPlay } from '/@/api/camera/cameraManager'; | ||
| 10 | import { ref } from 'vue'; | 8 | import { ref } from 'vue'; |
| 11 | import { Spin } from 'ant-design-vue'; | 9 | import { Spin } from 'ant-design-vue'; |
| 12 | import { useFingerprint } from '/@/utils/useFingerprint'; | 10 | import { useFingerprint } from '/@/utils/useFingerprint'; |
| 13 | - import { isRtspProtocol, VideoPlayerType } from '/@/components/Video/src/utils'; | ||
| 14 | - import { isShareMode } from '/@/views/sys/share/hook'; | ||
| 15 | - import { getJwtToken, getShareJwtToken } from '/@/utils/auth'; | ||
| 16 | - import { getVideoControlStart } from '/@/api/device/videoChannel'; | 11 | + import { isRtspProtocol } from '/@/components/Video/src/utils'; |
| 12 | + import { CameraRecord } from '/@/api/camera/model/cameraModel'; | ||
| 17 | 13 | ||
| 18 | const props = defineProps<{ | 14 | const props = defineProps<{ |
| 19 | config: ComponentPropsConfigType<typeof option>; | 15 | config: ComponentPropsConfigType<typeof option>; |
| @@ -21,24 +17,12 @@ | @@ -21,24 +17,12 @@ | ||
| 21 | 17 | ||
| 22 | const loading = ref(true); | 18 | const loading = ref(true); |
| 23 | 19 | ||
| 24 | - const basicVideoPlayEl = ref<Nullable<InstanceType<typeof BasicVideoPlay>>>(null); | ||
| 25 | - | ||
| 26 | - const withToken = ref(false); | ||
| 27 | - | ||
| 28 | - const playSource = ref<Record<'src' | 'type', string>>( | ||
| 29 | - {} as unknown as Record<'src' | 'type', string> | ||
| 30 | - ); | 20 | + const playUrl = ref<string>(); |
| 21 | + const playType = ref<string>(); | ||
| 31 | 22 | ||
| 32 | const exampleVideoPlay = | 23 | const exampleVideoPlay = |
| 33 | 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm'; | 24 | 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm'; |
| 34 | 25 | ||
| 35 | - const getOptions = computed<VideoJsPlayerOptions>(() => { | ||
| 36 | - return { | ||
| 37 | - width: '100%' as unknown as number, | ||
| 38 | - height: '100%' as unknown as number, | ||
| 39 | - }; | ||
| 40 | - }); | ||
| 41 | - | ||
| 42 | const { getResult } = useFingerprint(); | 26 | const { getResult } = useFingerprint(); |
| 43 | 27 | ||
| 44 | const handleGetVideoPlayUrl = async () => { | 28 | const handleGetVideoPlayUrl = async () => { |
| @@ -46,45 +30,19 @@ | @@ -46,45 +30,19 @@ | ||
| 46 | const { config } = props; | 30 | const { config } = props; |
| 47 | const { option } = config; | 31 | const { option } = config; |
| 48 | const { videoConfig, uuid } = option || {}; | 32 | const { videoConfig, uuid } = option || {}; |
| 33 | + const { type, url } = await getPlayUrl({ | ||
| 34 | + id: videoConfig?.id, | ||
| 35 | + accessMode: videoConfig?.accessMode, | ||
| 36 | + playProtocol: videoConfig?.playProtocol, | ||
| 37 | + videoUrl: videoConfig?.videoUrl, | ||
| 38 | + params: { | ||
| 39 | + deviceId: videoConfig?.deviceId, | ||
| 40 | + channelNo: videoConfig?.channelId, | ||
| 41 | + }, | ||
| 42 | + } as unknown as CameraRecord); | ||
| 43 | + playType.value = type; | ||
| 44 | + playUrl.value = url; | ||
| 49 | if (!uuid) return; | 45 | if (!uuid) return; |
| 50 | - const { url, id, accessMode, deviceId, channelId } = videoConfig || {}; | ||
| 51 | - let type = getVideoTypeByUrl(url!); | ||
| 52 | - let playUrl = url; | ||
| 53 | - if (accessMode === AccessMode.Streaming && id) { | ||
| 54 | - const { data: { url } = { url: '' } } = await getStreamingPlayUrl(id!); | ||
| 55 | - playUrl = url; | ||
| 56 | - playUrl && (type = getVideoTypeByUrl(playUrl!)); | ||
| 57 | - } else if (accessMode === AccessMode.GBT28181 && deviceId && channelId) { | ||
| 58 | - const { | ||
| 59 | - data: { flv }, | ||
| 60 | - } = await getVideoControlStart({ channelId, deviceId }); | ||
| 61 | - | ||
| 62 | - playUrl = flv; | ||
| 63 | - type = VideoPlayerType.flv; | ||
| 64 | - } | ||
| 65 | - | ||
| 66 | - if (isRtspProtocol(url!)) { | ||
| 67 | - const result = await getResult(); | ||
| 68 | - const { visitorId } = result; | ||
| 69 | - playUrl = getFlvPlayUrl(playUrl!, visitorId); | ||
| 70 | - withToken.value = true; | ||
| 71 | - } | ||
| 72 | - | ||
| 73 | - playSource.value = { | ||
| 74 | - src: playUrl!, | ||
| 75 | - type, | ||
| 76 | - }; | ||
| 77 | - | ||
| 78 | - const instance = unref(basicVideoPlayEl)?.customInit((options) => { | ||
| 79 | - if (unref(withToken)) { | ||
| 80 | - (options as any).flvjs.config.headers = { | ||
| 81 | - 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | ||
| 82 | - }; | ||
| 83 | - } | ||
| 84 | - return { ...options, sources: [toRaw(unref(playSource))] } as VideoJsPlayerOptions; | ||
| 85 | - }); | ||
| 86 | - | ||
| 87 | - instance?.play(); | ||
| 88 | } finally { | 46 | } finally { |
| 89 | loading.value = false; | 47 | loading.value = false; |
| 90 | } | 48 | } |
| @@ -92,20 +50,8 @@ | @@ -92,20 +50,8 @@ | ||
| 92 | 50 | ||
| 93 | const handleSelectPreview = () => { | 51 | const handleSelectPreview = () => { |
| 94 | loading.value = false; | 52 | loading.value = false; |
| 95 | - const instance = unref(basicVideoPlayEl)?.customInit((options) => { | ||
| 96 | - withToken.value = true; | ||
| 97 | - | ||
| 98 | - if (unref(withToken)) { | ||
| 99 | - (options as any).flvjs.config.headers = { | ||
| 100 | - 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | ||
| 101 | - }; | ||
| 102 | - } | ||
| 103 | - return { | ||
| 104 | - ...options, | ||
| 105 | - sources: [{ type: getVideoTypeByUrl(exampleVideoPlay), src: exampleVideoPlay }], | ||
| 106 | - } as VideoJsPlayerOptions; | ||
| 107 | - }); | ||
| 108 | - instance?.play(); | 53 | + playUrl.value = exampleVideoPlay; |
| 54 | + playType.value = 'auto'; | ||
| 109 | }; | 55 | }; |
| 110 | 56 | ||
| 111 | onMounted(() => { | 57 | onMounted(() => { |
| @@ -126,11 +72,10 @@ | @@ -126,11 +72,10 @@ | ||
| 126 | <template> | 72 | <template> |
| 127 | <main class="w-full h-full flex flex-col justify-center items-center p-2"> | 73 | <main class="w-full h-full flex flex-col justify-center items-center p-2"> |
| 128 | <Spin :spinning="loading" wrapper-class-name="video-spin"> | 74 | <Spin :spinning="loading" wrapper-class-name="video-spin"> |
| 129 | - <BasicVideoPlay | ||
| 130 | - ref="basicVideoPlayEl" | ||
| 131 | - :options="getOptions" | ||
| 132 | - :with-token="withToken" | ||
| 133 | - :immediateInitOnMounted="false" | 75 | + <XGPlayer |
| 76 | + :url="playUrl" | ||
| 77 | + :stream-type="playType" | ||
| 78 | + :config="{ width: '100%', height: '100%' }" | ||
| 134 | /> | 79 | /> |
| 135 | </Spin> | 80 | </Spin> |
| 136 | </main> | 81 | </main> |