Commit bddcb179d4ce25bcb948fc110d535d7d90a9957d

Authored by xp.Huang
2 parents 2d4e8f73 14204ba0

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 72 "sortablejs": "^1.14.0",
73 73 "tinymce": "^5.8.2",
74 74 "vditor": "^3.8.6",
75   - "video.js": "^7.20.3",
76   - "videojs-flvjs-es6": "^1.0.1",
77 75 "vue": "3.3.4",
78 76 "vue-i18n": "9.1.7",
79 77 "vue-json-pretty": "^2.0.4",
80 78 "vue-router": "^4.0.11",
81 79 "vue-types": "^4.0.3",
82 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 85 "xlsx": "^0.17.0"
84 86 },
85 87 "devDependencies": {
... ... @@ -101,7 +103,6 @@
101 103 "@types/qrcode": "^1.4.1",
102 104 "@types/qs": "^6.9.7",
103 105 "@types/sortablejs": "^1.10.7",
104   - "@types/video.js": "^7.3.49",
105 106 "@typescript-eslint/eslint-plugin": "^4.29.1",
106 107 "@typescript-eslint/parser": "^4.29.1",
107 108 "@vitejs/plugin-legacy": "^1.5.1",
... ...
... ... @@ -109,7 +109,7 @@ export const deleteStreamingMediaRecord = (params: StreamingMediaDeleteParam) =>
109 109 * @returns
110 110 */
111 111 export const getStreamingPlayUrl = (entityId: string) => {
112   - return defHttp.get(
  112 + return defHttp.get<{ data: { url: string } }>(
113 113 {
114 114 url: `${CameraManagerApi.STREAMING_PLAY_GET_URL}/${entityId}`,
115 115 },
... ...
... ... @@ -35,6 +35,7 @@ export interface CameraModel {
35 35 deviceId: string;
36 36 deviceName: string;
37 37 };
  38 + playProtocol?: number;
38 39 }
39 40
40 41 export interface StreamingManageRecord {
... ... @@ -106,4 +107,14 @@ export interface CameraRecord {
106 107 channelNo: string;
107 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 2 import { TBPaginationResult } from '/#/axios';
3 3 import { defHttp } from '/@/utils/http/axios';
4 4 import { RuleChainDetail, RuleChainType } from '/@/views/rule/designer/types/ruleNode';
... ... @@ -75,3 +75,26 @@ export const getRuleNodeEventList = (
75 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 3 export interface RuleChainPaginationItemType {
2 4 id: Id;
3 5 createdTime: number;
... ... @@ -19,3 +21,11 @@ export interface Id {
19 21 export interface AdditionalInfo {
20 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 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 8 <template #[item]="data" v-for="item in Object.keys($slots)">
4 9 <slot :name="item" v-bind="data || {}"></slot>
5 10 </template>
... ... @@ -60,6 +65,18 @@
60 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 80 async function fetch() {
64 81 const { api } = props;
65 82 if (!api || !isFunction(api)) return;
... ... @@ -80,7 +97,7 @@
80 97 isFirstLoaded.value = true;
81 98 emit('options-change', treeData.value);
82 99 }
83   - return { getAttrs, loading, handleChange };
  100 + return { getAttrs, loading, handleChange, filterTreeNode };
84 101 },
85 102 });
86 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 3 export { getVideoTypeByUrl } from './src/utils';
5   -
6   -export const BasicVideoPlay = withInstall(VideoPlay);
... ...
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>
  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>
... ...
  1 +import { IPlayerOptions } from 'xgplayer/es/player';
  2 +
  3 +export type StreamType = 'flv' | 'mp4' | 'hls' | 'auto';
  4 +export interface XGPlayerProps {
  5 + streamType?: StreamType;
  6 + autoPlay?: boolean;
  7 + url?: string;
  8 + withToken?: boolean;
  9 + config?: Omit<IPlayerOptions, 'url'>;
  10 +}
... ...
  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 +};
... ...
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 80 </BasicTable>
81 81 </PageWrapper>
82 82 <CameraDrawer @register="registerDrawer" @success="handleSuccess" />
83   - <VideoPreviewModal @register="registerModal" />
84   - <VideoModal @register="registerModal1" />
  83 + <VideoModal @register="registerModal" />
85 84 <GBTDrawer @register="registerGBTDrawer" @success="handleGBTSuccess" />
86 85 </div>
87 86 </template>
... ... @@ -103,14 +102,16 @@
103 102 CameraPermission,
104 103 VideoPlatformEnum,
105 104 accessModeConfig,
  105 + getPlayUrl,
106 106 } from './config.data';
107   - import VideoPreviewModal from './DialogPreviewVideo.vue';
108 107 import { useModal } from '/@/components/Modal';
109 108 import { Authority } from '/@/components/Authority';
110 109 import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
111 110 import { Popconfirm } from 'ant-design-vue';
112 111 import { Tag } from 'ant-design-vue';
113 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 116 export default defineComponent({
116 117 components: {
... ... @@ -119,7 +120,6 @@
119 120 BasicTable,
120 121 TableAction,
121 122 CameraDrawer,
122   - VideoPreviewModal,
123 123 VideoModal,
124 124 TableImg,
125 125 Authority,
... ... @@ -131,9 +131,8 @@
131 131 setup(_, { emit }) {
132 132 const searchInfo = reactive<Recordable>({});
133 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 136 // 表格hooks
138 137 const [registerTable, { reload, setProps, clearSelectedRowKeys }] = useTable({
139 138 title: '视频列表',
... ... @@ -197,28 +196,25 @@
197 196 searchInfo.organizationId = organizationId;
198 197 handleSuccess();
199 198 };
200   - const handleViewVideo = (record) => {
201   - const { videoPlatformDTO } = record;
  199 + const handleViewVideo = (record: CameraRecord) => {
  200 + const { videoPlatformDTO, params } = record;
202 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 220 const handleSwitchMode = () => {
... ... @@ -252,7 +248,6 @@
252 248 organizationIdTreeRef,
253 249 handleViewVideo,
254 250 registerModal,
255   - registerModal1,
256 251 AccessMode,
257 252 handleSwitchMode,
258 253 CameraPermission,
... ...
... ... @@ -3,24 +3,21 @@
3 3 import OrganizationIdTree from '../../common/organizationIdTree/src/OrganizationIdTree.vue';
4 4 import { onMounted, reactive, Ref, ref, unref, watch } from 'vue';
5 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 7 import { CameraRecord } from '/@/api/camera/model/cameraModel';
8 8 import { useFullscreen } from '@vueuse/core';
9 9 import CameraDrawer from './CameraDrawer.vue';
10 10 import GBTDrawer from './components/GBTDrawer.vue';
11 11 import { useDrawer } from '/@/components/Drawer';
12   - import { AccessMode, CameraPermission, PageMode } from './config.data';
  12 + import { CameraPermission, getPlayUrl, PageMode } from './config.data';
13 13 import SvgIcon from '/@/components/Icon/src/SvgIcon.vue';
14   - import { getStreamingPlayUrl } from '/@/api/camera/cameraManager';
15 14 import { buildUUID } from '/@/utils/uuid';
16   - import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video';
17   - import { VideoJsPlayerOptions } from 'video.js';
18 15 import { getBoundingClientRect } from '/@/utils/domUtils';
19 16 import { Authority } from '/@/components/Authority';
20 17 import { isRtspProtocol } from '/@/components/Video/src/utils';
21 18 import { useFingerprint } from '/@/utils/useFingerprint';
22 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 22 const props = defineProps({
26 23 mode: {
... ... @@ -29,17 +26,12 @@
29 26 },
30 27 });
31 28 type CameraRecordItem = CameraRecord & {
  29 + placeholder?: boolean;
32 30 canPlay?: boolean;
33 31 isTransform?: boolean;
34 32 withToken?: boolean;
35   - videoPlayerOptions?: VideoJsPlayerOptions;
36 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 37 const emit = defineEmits(['switchMode']);
... ... @@ -88,10 +80,7 @@
88 80
89 81 for (const item of unref(cameraList)) {
90 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 85 } catch (error) {
97 86 } finally {
... ... @@ -100,75 +89,12 @@
100 89 };
101 90
102 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 100 const gridLayout = ref({ gutter: 1, column: 2 });
... ... @@ -312,11 +238,11 @@
312 238 ref="listEl"
313 239 :loading="loading"
314 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 242 :grid="(gridLayout as any)"
317 243 :style="{ '--height': `${100 / pagination.colNumber}%` }"
318 244 >
319   - <template #renderItem="{ item }">
  245 + <template #renderItem="{ item }: { item: CameraRecordItem }">
320 246 <List.Item>
321 247 <div class="box-border w-full !h-full p-1px">
322 248 <div
... ... @@ -332,17 +258,19 @@
332 258 v-show="!item.isTransform"
333 259 :spinning="!item.isTransform"
334 260 />
335   - <BasicVideoPlay
  261 + <XGPlayer
336 262 v-if="item.isTransform"
337   - :options="item.videoPlayerOptions"
  263 + :url="item.playSourceUrl"
  264 + :stream-type="item.streamType"
338 265 :with-token="item.withToken"
  266 + :config="{ width: '100%', height: '100%' }"
339 267 @on-unmounted="handleCloseFlvPlayUrl(item)"
340 268 />
341 269 <div
342 270 class="video-container-mask absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center"
343 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 274 </div>
347 275 </div>
348 276 </div>
... ... @@ -366,8 +294,10 @@
366 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 303 .video-container {
... ...
... ... @@ -6,7 +6,6 @@ import { h } from 'vue';
6 6 import SnHelpMessage from './SnHelpMessage.vue';
7 7 import SnHelpMessage1 from './SnHelpMessage1.vue';
8 8 import { OrgTreeSelect } from '../../common/OrgTreeSelect';
9   -import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
10 9 import { createImgPreview } from '/@/components/Preview';
11 10 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
12 11 import { findDictItemByCode } from '/@/api/system/dict';
... ... @@ -15,7 +14,24 @@ import { DataSourceField } from '../../visual/packages/config/common.config';
15 14 import { getMeetTheConditionsDevice } from '/@/api/dataBoard';
16 15 import { useMessage } from '/@/hooks/web/useMessage';
17 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 36 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
21 37
... ... @@ -321,7 +337,7 @@ export const formSchema: QFormSchema[] = [
321 337 if (data)
322 338 return data.map((item) => ({
323 339 ...item,
324   - label: item.name,
  340 + label: item.name || item.channelId,
325 341 value: item.channelId,
326 342 }));
327 343 } catch (error) {}
... ... @@ -604,3 +620,37 @@ export const formGBTSchema: QFormSchema[] = [
604 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 165 values.icon = file.url || null;
166 166 }
167 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 176 delete values.deviceAddress;
169 177 emit('next', values);
170 178 // 获取输入的数据
... ... @@ -419,6 +427,7 @@
419 427 const file = (value.icon || []).at(0) || {};
420 428 value.icon = file.url || null;
421 429 }
  430 +
422 431 return {
423 432 ...value,
424 433 ...(value?.code || value?.addressCode
... ...
1 1 import { h } from 'vue';
2 2 import { BasicColumn, FormSchema } from '/@/components/Table';
3 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 15 export enum GBT28181_DEVICE_PERMISSION_ENUM {
... ... @@ -50,7 +55,7 @@ export const configColumns: BasicColumn[] = [
50 55 return h(
51 56 Tag,
52 57 {
53   - color: text === ChannelStatusEnum.ONLINE ? 'green' : 'blue',
  58 + color: text === ChannelStatusEnum.ONLINE ? 'green' : 'red',
54 59 },
55 60 () => (text === ChannelStatusEnum.ONLINE ? '在线' : '离线')
56 61 );
... ...
... ... @@ -8,7 +8,7 @@
8 8 <a-button type="primary" @click="handleSyncPlay">通道同步</a-button>
9 9 </Authority>
10 10 </template>
11   - <template #hasAudio="{ record }">
  11 + <template #hasAudio="{ record }: { record: VideoChannelItemType }">
12 12 <Switch
13 13 :checked="record.status === 1"
14 14 :loading="record.pendingStatus"
... ... @@ -25,6 +25,7 @@
25 25 auth: GBT28181_DEVICE_PERMISSION_ENUM.PLAY_SYNC,
26 26 icon: 'ant-design:play-circle-outlined',
27 27 onClick: handlePlay.bind(null, record),
  28 + ifShow: record?.status === 'ONLINE', //在线则显示播放按钮
28 29 },
29 30 {
30 31 label: '停止',
... ... @@ -41,16 +42,22 @@
41 42 </template>
42 43
43 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 51 import { BasicTable, useTable, TableAction } from '/@/components/Table';
46 52 import { Switch } from 'ant-design-vue';
47 53 import { DeviceRecord } from '/@/api/device/model/deviceModel';
48 54 import VideoModal from './videoModal.vue';
49 55 import { useModal } from '/@/components/Modal';
50 56 import { useMessage } from '/@/hooks/web/useMessage';
51   - import { getVideoChannelList } from '/@/api/device/videoChannel';
  57 + import { getVideoChannelList, getVideoControlStart } from '/@/api/device/videoChannel';
52 58 import { stopOnDemandVideoApiGet, syncVideoApiGet } from '/@/api/camera/cameraManager';
53 59 import { Authority } from '/@/components/Authority';
  60 + import { VideoChannelItemType } from '/@/api/device/model/videoChannelModel';
54 61
55 62 const props = defineProps<{ deviceDetail: DeviceRecord }>();
56 63
... ... @@ -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 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 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 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 4 import { QuestionCircleOutlined } from '@ant-design/icons-vue';
12 5 import {
13 6 CaretUpOutlined,
... ... @@ -19,106 +12,50 @@
19 12 ZoomOutOutlined,
20 13 } from '@ant-design/icons-vue';
21 14 import { Button, Slider } from 'ant-design-vue';
22   - import { nextTick } from 'vue';
23 15 import { controlling } from '/@/api/camera/cameraManager';
24 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 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 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 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 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 57 const handleGBTControl = (command: string, action?: number | string) => {
121   - const { tbDeviceId, channelId } = props.GBTOption;
  58 + const { tbDeviceId, channelId } = props.options || {};
122 59 setVideoControl(tbDeviceId, channelId, {
123 60 command,
124 61 horizonSpeed: action,
... ... @@ -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 70 handleGBTControl(action, unref(sliderValue));
158 71 return;
159 72 }
... ... @@ -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 79 handleGBTControl('STOP', unref(sliderValue));
167 80 return;
168 81 }
169 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 88 </script>
184 89
185 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 107 <Tooltip>
201   - <template #title
202   - >长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。</template
203   - >
  108 + <template #title>
  109 + 长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。
  110 + </template>
204 111 <label class="validate-dot">云台控制</label>
205 112 <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" />
206 113 </Tooltip>
... ... @@ -248,7 +155,7 @@
248 155 <Button class="circle" @click="handleClick" />
249 156 </div>
250 157 </div>
251   - <div class="mt-5" v-if="getGBT">
  158 + <div class="mt-5" v-if="options.isGBT">
252 159 <Slider v-model:value="sliderValue" :min="0" :max="255" style="width: 130px" />
253 160 </div>
254 161 <div class="flex justify-center mt-8">
... ... @@ -274,16 +181,6 @@
274 181 </template>
275 182
276 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 184 .child {
288 185 position: absolute;
289 186 width: 3rem;
... ...
1 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 16 </template>
28 17 <script setup lang="ts">
29   - import { ref, reactive } from 'vue';
  18 + import { ref } from 'vue';
30 19 import { BasicModal, useModalInner } from '/@/components/Modal';
31   - import { VideoJsPlayerOptions } from 'video.js';
32 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 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 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 29 try {
  30 + playUrl.value = null;
  31 + options.value = null;
70 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 41 } catch (error) {
87 42 } finally {
88 43 setModalProps({ loading: false });
... ... @@ -91,18 +46,6 @@
91 46 );
92 47
93 48 const handleCancel = () => {
94   - showVideo.value = false;
95   - withToken.value = false;
96 49 emit('reloadTable');
97 50 };
98 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 140 EnumTableChartMode,
141 141 ModeSwitchButton,
142 142 } from '/@/components/Widget';
  143 + import { TransportTypeEnum } from '/@/enums/deviceEnum';
143 144
144 145 interface DeviceInfo {
145 146 alarmStatus: 0 | 1;
... ... @@ -152,6 +153,7 @@
152 153 deviceType?: string;
153 154 alias?: string;
154 155 deviceProfileId: string;
  156 + transportType?: string;
155 157 }
156 158 type MarkerList = DeviceInfo & { marker: any; label: any };
157 159
... ... @@ -377,7 +379,15 @@
377 379 width: 330, // 信息窗口宽度
378 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 391 const { address, longitude, latitude } = record.deviceInfo;
382 392
383 393 // 创建信息窗口对象
... ... @@ -411,7 +421,9 @@
411 421 <div style="display:flex;justify-content:end; margin-top:10px">
412 422 <button onclick="openDeviceInfoDrawer()" style="margin-right:10px;color:#fff;background-color:#409eff;padding:4px; border-radius:4px;">设备信息</button>
413 423 <button onclick="openHistoryModal()" style="display:${
414   - deviceType !== 'GATEWAY' ? 'block' : 'none'
  424 + deviceType !== 'GATEWAY' && transportType !== TransportTypeEnum.GBT28181
  425 + ? 'block'
  426 + : 'none'
415 427 };color:#fff;background-color:#409eff;padding:4px; border-radius:4px;">${
416 428 deviceType !== 'GATEWAY' ? '历史数据' : ''
417 429 }</button>
... ... @@ -443,10 +455,12 @@
443 455
444 456 // 设备信息
445 457 const openDeviceInfoDrawer = async () => {
446   - const { id, tbDeviceId } = globalRecord;
  458 + const { id, tbDeviceId, transportType, deviceType } = globalRecord;
447 459 openDrawer(true, {
448 460 id,
449 461 tbDeviceId,
  462 + transportType,
  463 + deviceType,
450 464 });
451 465 };
452 466
... ...
... ... @@ -144,7 +144,7 @@ export const list = [
144 144 },
145 145 {
146 146 deviceType: '网关/直连设备',
147   - function: '服务端响应共享属性客户端属性给设备端',
  147 + function: '服务端响应共享属性客户端属性给设备端',
148 148 release: 'v1/devices/me/attributes/response/$request_id',
149 149 subscribe: 'v1/devices/me/attributes/response/+',
150 150 platform: '发布',
... ...
... ... @@ -54,6 +54,7 @@ export const typeList = [
54 54 { label: '阿里', value: 'ALI_CLOUD' },
55 55 { label: '钉钉', value: 'DING_TALK' },
56 56 { label: '腾讯', value: 'TENCENT_CLOUD' },
  57 + { label: '阿里云语音', value: 'ALI_VOICE' },
57 58 ];
58 59
59 60 export const searchFormSchema: FormSchema[] = [
... ...
... ... @@ -4,6 +4,7 @@ import { Tinymce } from '/@/components/Tinymce/index';
4 4 import { getOrganizationList } from '/@/api/system/system';
5 5 import { copyTransFun } from '/@/utils/fnUtils';
6 6 import { Tag } from 'ant-design-vue';
  7 +import { useComponentRegister } from '/@/components/Form';
7 8
8 9 export enum IsOrgEnum {
9 10 IS_ORG_ENUM = 1,
... ... @@ -12,6 +13,8 @@ export const isOrg = (type: string) => {
12 13 return type === IsOrgEnum.IS_ORG_ENUM;
13 14 };
14 15
  16 +useComponentRegister('Tinymce', Tinymce);
  17 +
15 18 export const columns: BasicColumn[] = [
16 19 {
17 20 title: '类型',
... ... @@ -92,21 +95,13 @@ export const formSchema: FormSchema[] = [
92 95 },
93 96 {
94 97 field: 'content',
95   - component: 'Input',
  98 + component: 'Tinymce',
96 99 colProps: { span: 24 },
97 100 label: '通知内容',
98 101 required: true,
99 102 componentProps: {
100 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 107 field: 'receiverType',
... ...
1 1 <script lang="ts" setup>
2   - import { useTable, BasicTable } from '/@/components/Table';
  2 + import { BasicTable, useTable } from '/@/components/Table';
3 3 import { getColumns } from './config';
4   - import { getRuleNodeEventList } from '/@/api/ruleDesigner';
  4 + import { doClearEvents, getRuleNodeEventList } from '/@/api/ruleDesigner';
5 5 import { BasicNodeFormData, NodeData } from '../../../../types/node';
6 6 import { computed, reactive, ref, unref, watch } from 'vue';
7 7 import { useUserStore } from '/@/store/modules/user';
8 8 import { EventSelect } from '../EventSelect';
9 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 12 import { FilterForm } from '../FilterForm';
13 13 import { useModal } from '/@/components/Modal';
14 14 import { DataActionModeEnum } from '/@/enums/toolEnum';
... ... @@ -31,6 +31,8 @@
31 31 data: {} as Recordable,
32 32 });
33 33
  34 + const dataLength = ref();
  35 +
34 36 const [register, { reload, setColumns, setPagination }] = useTable({
35 37 columns: getColumns(EventTypeEnum.DEBUG_RULE_NODE),
36 38 showIndexColumn: false,
... ... @@ -56,6 +58,8 @@
56 58 { ...searchParams.data, eventType: unref(eventType) }
57 59 );
58 60
  61 + dataLength.value = result.totalElements;
  62 +
59 63 return result;
60 64 },
61 65 });
... ... @@ -100,6 +104,17 @@
100 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 118 watch(
104 119 () => props.elementInfo?.id,
105 120 () => {
... ... @@ -115,6 +130,20 @@
115 130 <EventSelect v-model:type="eventType" @change="handleEventTypeChange" />
116 131 </template>
117 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 147 <Tooltip title="过滤器">
119 148 <Icon
120 149 icon="material-symbols:filter-list"
... ...
... ... @@ -5,7 +5,7 @@
5 5 import { BasicForm, FormActionType, ThingsModelForm } from '/@/components/Form';
6 6 import { getFormSchemas, FormFieldsEnum } from './config';
7 7 import { FlipFlop } from '../FlipFlop';
8   - import { ComponentPublicInstance, ref, unref } from 'vue';
  8 + import { ComponentPublicInstance, ref, unref, watch } from 'vue';
9 9 import { ExecutionActionListRefItemType } from './type';
10 10 import { useExecutionActionData } from './useExecutionActionData';
11 11 import { createNewExecutionActionItem } from '.';
... ... @@ -63,6 +63,20 @@
63 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 80 defineExpose({
67 81 getFieldsValue,
68 82 setFieldsValue,
... ...
... ... @@ -97,8 +97,8 @@
97 97
98 98 onMounted(() => {
99 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 103 </script>
104 104 <style lang="less">
... ...
... ... @@ -9,6 +9,9 @@
9 9 <div style="height: 50vh">
10 10 <BasicForm @register="registerForm">
11 11 <template #organizationId="{ model, field }">
  12 + <Button type="link" @click="handleOpenCreate" style="padding: 0; z-index: 9999"
  13 + >新增组织
  14 + </Button>
12 15 <BasicTree
13 16 v-if="organizationTreeData.length"
14 17 v-model:value="model[field]"
... ... @@ -44,6 +47,8 @@
44 47 </a-select>
45 48 </template>
46 49 </BasicForm>
  50 +
  51 + <OrganizationDrawer @register="registerDrawer" @success="handleReload" />
47 52 </div>
48 53 </BasicModal>
49 54 <RoleDrawer @register="registerRoleDrawer" @success="handleSuccess" />
... ... @@ -53,6 +58,7 @@
53 58 import { BasicModal, useModalInner } from '/@/components/Modal';
54 59 import { BasicForm, useForm } from '/@/components/Form/index';
55 60 import { accountFormSchema } from './account.data';
  61 + import { Button } from 'ant-design-vue';
56 62 import {
57 63 findCurrentUserRelation,
58 64 SaveOrUpdateUserInfo,
... ... @@ -67,13 +73,16 @@
67 73 import { PlusOutlined } from '@ant-design/icons-vue';
68 74 import { useDrawer } from '/@/components/Drawer';
69 75 import RoleDrawer from '../role/RoleDrawer.vue';
  76 + import OrganizationDrawer from '/@/views/system/organization/OrganizationDrawer.vue';
70 77
71 78 export default defineComponent({
72 79 name: 'AccountModal',
73 80 components: {
74 81 BasicModal,
75 82 BasicForm,
  83 + Button,
76 84 BasicTree,
  85 + OrganizationDrawer,
77 86 PlusOutlined,
78 87 RoleDrawer,
79 88 VNodes: (_, { attrs }) => {
... ... @@ -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 296 return {
277 297 registerModal,
278 298 registerForm,
... ... @@ -291,6 +311,9 @@
291 311 handleCheckClick,
292 312 handleUnSelectAll,
293 313 handleStrictlyStatus,
  314 + handleOpenCreate,
  315 + registerDrawer,
  316 + handleReload,
294 317 };
295 318 },
296 319 });
... ... @@ -298,6 +321,8 @@
298 321 <style scoped lang="less">
299 322 :deep(.vben-basic-tree) {
300 323 width: 100% !important;
  324 + margin-top: -28px !important;
  325 + padding: 0;
301 326 }
302 327
303 328 :deep(.is-unflod) {
... ...
... ... @@ -31,7 +31,7 @@
31 31 </template>
32 32
33 33 <script lang="ts">
34   - import { defineComponent, onMounted, ref, computed } from 'vue';
  34 + import { defineComponent, onMounted, ref, computed, watch } from 'vue';
35 35 import { Card } from 'ant-design-vue';
36 36 import { BasicForm, useForm } from '/@/components/Form/index';
37 37 import { schemas, provSchemas } from '../config/enterPriseInfo.config';
... ... @@ -73,7 +73,10 @@
73 73 loading: false,
74 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 80 labelWidth: 80,
78 81 schemas,
79 82 showResetButton: false,
... ... @@ -101,6 +104,13 @@
101 104 const handleSetCodeImgUrl = (d) => {
102 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 115 const handleUpdateInfo = async () => {
106 116 try {
... ...
1 1 import { BasicColumn, FormSchema } from '/@/components/Table';
2 2 import moment from 'moment';
3   -import { findDictItemByCode } from '/@/api/system/dict';
4 3 import { format } from '../util';
5 4 import { h } from 'vue';
6 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 8 export const columns: BasicColumn[] = [
18 9 {
... ... @@ -21,24 +12,6 @@ export const columns: BasicColumn[] = [
21 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 15 title: '资源名称',
43 16 dataIndex: 'entityName',
44 17 width: 180,
... ... @@ -49,19 +22,6 @@ export const columns: BasicColumn[] = [
49 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 25 title: '操作状态',
66 26 dataIndex: 'actionStatus',
67 27 width: 180,
... ... @@ -116,22 +76,7 @@ export const formSchema: FormSchema[] = [
116 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 81 field: 'entityName',
137 82 label: '资源名称',
... ... @@ -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 98 field: 'actionStatus',
162 99 label: '操作状态',
163 100 component: 'Input',
... ...
1 1 import { BasicColumn } from '/@/components/Table';
2 2 import { FormSchema } from '/@/components/Table';
3 3 import { useI18n } from '/@/hooks/web/useI18n';
  4 +import { createOrganizationSearch } from '/@/utils/organizationSearch';
4 5 const { t } = useI18n();
5 6
6 7 export const columns: BasicColumn[] = [
... ... @@ -38,6 +39,7 @@ export const formSchema: FormSchema[] = [
38 39 value: 'id',
39 40 },
40 41 maxTagCount: 10,
  42 + ...createOrganizationSearch(),
41 43 getPopupContainer: () => document.body,
42 44 },
43 45 },
... ...
... ... @@ -13,6 +13,7 @@ export enum FormFieldEnum {
13 13
14 14 DEVICE_ID = 'deviceId',
15 15 CHANNEL_ID = 'channelId',
  16 + PLAY_PROTOCOL = 'playProtocol',
16 17 }
17 18
18 19 export interface DataSourceValueType {
... ... @@ -70,6 +71,7 @@ export const formSchemas: FormSchema[] = [
70 71 component: 'Input',
71 72 label: '地址',
72 73 colProps: { span: 16 },
  74 + required: true,
73 75 ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.CUSTOM,
74 76 componentProps: {
75 77 placeholder: '请输入自定义地址',
... ... @@ -79,6 +81,7 @@ export const formSchemas: FormSchema[] = [
79 81 field: FormFieldEnum.ORGANIZATION_ID,
80 82 component: 'OrgTreeSelect',
81 83 label: '组织',
  84 + required: true,
82 85 colProps: { span: 8 },
83 86 ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.VIDEO_MANAGE,
84 87 componentProps: ({ formActionType }) => {
... ... @@ -105,6 +108,7 @@ export const formSchemas: FormSchema[] = [
105 108 field: FormFieldEnum.VIDEO_ID,
106 109 component: 'ApiSelect',
107 110 label: '地址',
  111 + required: true,
108 112 colProps: { span: 8 },
109 113 ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.VIDEO_MANAGE,
110 114 componentProps: ({ formActionType, formModel }) => {
... ... @@ -129,6 +133,7 @@ export const formSchemas: FormSchema[] = [
129 133 accessMode === AccessMode.GBT28181 ? options.params?.channelNo : null,
130 134 [FormFieldEnum.DEVICE_ID]:
131 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 151 component: 'Input',
147 152 ifShow: false,
148 153 },
  154 + {
  155 + field: FormFieldEnum.PLAY_PROTOCOL,
  156 + label: '播放协议',
  157 + component: 'InputNumber',
  158 + ifShow: false,
  159 + },
149 160 ];
... ...
1 1 <script lang="ts" setup>
2 2 import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 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 8 import { ref } from 'vue';
11 9 import { Spin } from 'ant-design-vue';
12 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 14 const props = defineProps<{
19 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -21,24 +17,12 @@
21 17
22 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 23 const exampleVideoPlay =
33 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 26 const { getResult } = useFingerprint();
43 27
44 28 const handleGetVideoPlayUrl = async () => {
... ... @@ -46,45 +30,19 @@
46 30 const { config } = props;
47 31 const { option } = config;
48 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 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 46 } finally {
89 47 loading.value = false;
90 48 }
... ... @@ -92,20 +50,8 @@
92 50
93 51 const handleSelectPreview = () => {
94 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 57 onMounted(() => {
... ... @@ -126,11 +72,10 @@
126 72 <template>
127 73 <main class="w-full h-full flex flex-col justify-center items-center p-2">
128 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 80 </Spin>
136 81 </main>
... ...