Commit 24f768d2fbe1160d0df9ea192b952ff4e420fd18

Authored by ww
1 parent 8439fe09

feat: 新增流量计与视频组件

... ... @@ -14,6 +14,7 @@ enum CameraManagerApi {
14 14 CAMERA_GET_URL = '/video',
15 15 CAMERA_DELETE_URL = '/video',
16 16 CAMERA_GET_DETAIL_URL = '/video',
  17 + CAMERA_LIST_URL = '/video/list',
17 18 STREAMING_GET_URL = '/video/platform',
18 19 STREAMING_POST_URL = '/video/platform',
19 20 STREAMING_DELETE_URL = '/video/platform',
... ... @@ -27,6 +28,13 @@ export const cameraPage = (params: CameraQueryParam) => {
27 28 });
28 29 };
29 30
  31 +export const getCameraList = (params: Record<'organizationId', string>) => {
  32 + return defHttp.get<{ data: CameraModel[] }>({
  33 + url: CameraManagerApi.CAMERA_LIST_URL,
  34 + params,
  35 + });
  36 +};
  37 +
30 38 /**
31 39 * 删除视频
32 40 * @param ids 删除的ids
... ...
1   -import { RadioRecord } from '../../../views/visual/board/detail/config/util';
2   -import { DeviceTypeEnum } from '../../device/model/deviceModel';
3 1 import { DataType } from '../../device/model/modelOfMatterModel';
4 2 import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config';
  3 +import { DataSource } from '/@/views/visual/palette/types';
5 4
6 5 export interface AddDataBoardParams {
7 6 name: string;
... ... @@ -74,34 +73,6 @@ export interface ComponentInfo {
74 73 showDeviceName: boolean;
75 74 }
76 75
77   -export interface DataSource {
78   - attribute: string;
79   - deviceId: string;
80   - organizationId: string;
81   - attributeRename: string;
82   - deviceRename: string;
83   - slaveDeviceId: string;
84   - gatewayDevice: boolean;
85   - componentInfo: ComponentInfo;
86   - deviceName: string;
87   - deviceProfileId: string;
88   - tbDeviceId: string;
89   - customCommand: {
90   - transportType?: string;
91   - commandType?: string;
92   - command?: string;
93   - service?: string;
94   - };
95   -
96   - // front usage
97   - uuid?: string;
98   - width?: number;
99   - height?: number;
100   - radio?: RadioRecord;
101   - deviceType?: DeviceTypeEnum;
102   - [key: string]: any;
103   -}
104   -
105 76 export interface DataComponentRecord {
106 77 dataBoardId: string;
107 78 dataSource: DataSource[];
... ...
1   -export enum VideoPlayerType {
2   - m3u8 = 'application/x-mpegURL',
3   - mp4 = 'video/mp4',
4   - webm = 'video/webm',
5   -}
6   -
7   -export const getVideoTypeByUrl = (url: string) => {
8   - const splitExtReg = /(?:.*)(?<=\.)/;
9   - const type = url.replace(splitExtReg, '');
10   - /**
11   - * https://vcsplay.scjtonline.cn:8200/live/HD_1569b634-4789-11eb-ab67-3cd2e55e0b20.m3u8?auth_key=1681179278-0-0-5c54a376f2ca32d05c4a152ee96336e9
12   - * 如果是这种格式的m3u8,则截取的是这一部分.m3u8?auth_key=1681179278-0-0-5c54a376f2ca32d05c4a152ee96336e9
13   - */
14   - if (type.startsWith('m3u8')) return VideoPlayerType.m3u8;
15   - if (type.startsWith('mp4')) return VideoPlayerType.mp4;
16   - if (type.startsWith('webm')) return VideoPlayerType.webm;
17   - return;
18   -};
  1 +export enum VideoPlayerType {
  2 + m3u8 = 'application/x-mpegURL',
  3 + mp4 = 'video/mp4',
  4 + webm = 'video/webm',
  5 +}
  6 +
  7 +export const getVideoTypeByUrl = (url: string) => {
  8 + const splitExtReg = /(?:.*)(?<=\.)/;
  9 + const type = url.replace(splitExtReg, '');
  10 + /**
  11 + * https://vcsplay.scjtonline.cn:8200/live/HD_1569b634-4789-11eb-ab67-3cd2e55e0b20.m3u8?auth_key=1681179278-0-0-5c54a376f2ca32d05c4a152ee96336e9
  12 + * 如果是这种格式的m3u8,则截取的是这一部分.m3u8?auth_key=1681179278-0-0-5c54a376f2ca32d05c4a152ee96336e9
  13 + */
  14 + if (type.startsWith('m3u8')) return VideoPlayerType.m3u8;
  15 + if (type.startsWith('mp4')) return VideoPlayerType.mp4;
  16 + if (type.startsWith('webm')) return VideoPlayerType.webm;
  17 + return VideoPlayerType.webm;
  18 +};
... ...
... ... @@ -15,9 +15,13 @@
15 15 const getDesign = computed(() => {
16 16 const { option, persetOption } = props.config;
17 17 const { componentInfo } = option;
18   - const { flowmeterConfig, unit } = componentInfo || {};
  18 + const { flowmeterConfig, unit, fontColor } = componentInfo || {};
19 19 const { backgroundColor, waveFirst, waveSecond, waveThird } = flowmeterConfig || {};
20   - const { flowmeterConfig: presetFlowmeterConfig, unit: persetUnit } = persetOption || {};
  20 + const {
  21 + flowmeterConfig: presetFlowmeterConfig,
  22 + unit: persetUnit,
  23 + fontColor: presetFontColor,
  24 + } = persetOption || {};
21 25 const {
22 26 backgroundColor: presetBackgroundColor,
23 27 waveFirst: presetWaveFirst,
... ... @@ -30,6 +34,7 @@
30 34 waveSecond: waveSecond ?? presetWaveSecond,
31 35 waveThird: waveThird ?? presetWaveThird,
32 36 unit: unit ?? persetUnit,
  37 + fontColor: fontColor ?? presetFontColor,
33 38 };
34 39 });
35 40
... ... @@ -93,6 +98,7 @@
93 98 </svg>
94 99 <div
95 100 class="absolute w-full h-full top-0 left-0 text-center text-lg flex items-center justify-center flex-col"
  101 + :style="{ color: getDesign.fontColor }"
96 102 >
97 103 <div>{{ currentValue }}</div>
98 104 <div class="ml-1">{{ getDesign.unit }}</div>
... ...
... ... @@ -7,11 +7,9 @@ import {
7 7 PublicPresetOptions,
8 8 } from '/@/views/visual/packages/index.type';
9 9 import { PublicConfigClass, componentInitConfig } from '/@/views/visual/packages/publicConfig';
10   -import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
11 10
12 11 export const option: PublicPresetOptions = {
13   - [ComponentConfigFieldEnum.FONT_COLOR]: '#',
14   - [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  12 + componetDesign: false,
15 13 };
16 14
17 15 export default class Config extends PublicConfigClass implements CreateComponentType {
... ...
  1 +import { getCameraList } from '/@/api/camera/cameraManager';
  2 +import { CameraModel } from '/@/api/camera/model/cameraModel';
  3 +import { FormSchema } from '/@/components/Form';
  4 +
  5 +export enum FormFieldEnum {
  6 + VIDEO_TYPE = 'type',
  7 + VIDEO_PLAY_URL = 'url',
  8 + VIDEO_CONFIG = 'videoConfig',
  9 + ACCESS_MODE = 'accessMode',
  10 + VIDEO_ID = 'id',
  11 + ORGANIZATION_ID = 'organizationId',
  12 +}
  13 +
  14 +export interface DataSourceValueType {
  15 + [FormFieldEnum.VIDEO_TYPE]?: string;
  16 + [FormFieldEnum.VIDEO_PLAY_URL]?: string;
  17 + [FormFieldEnum.ACCESS_MODE]?: number;
  18 + [FormFieldEnum.VIDEO_ID]?: string;
  19 + [FormFieldEnum.ORGANIZATION_ID]?: string;
  20 +}
  21 +
  22 +export enum VideoOriginalEnum {
  23 + CUSTOM = 'CUSTOM',
  24 + VIDEO_MANAGE = 'VIDEO_MANAGE',
  25 +}
  26 +
  27 +export enum VideoOriginalNameEnum {
  28 + CUSTOM = '自定义',
  29 + VIDEO_MANAGE = '流媒体获取',
  30 +}
  31 +
  32 +export const formSchemas: FormSchema[] = [
  33 + {
  34 + field: FormFieldEnum.VIDEO_TYPE,
  35 + component: 'Select',
  36 + label: '视频来源',
  37 + colProps: { span: 8 },
  38 + defaultValue: VideoOriginalEnum.CUSTOM,
  39 + componentProps: {
  40 + placeholder: '请选择视频来源',
  41 + options: [
  42 + { label: VideoOriginalNameEnum[VideoOriginalEnum.CUSTOM], value: VideoOriginalEnum.CUSTOM },
  43 + {
  44 + label: VideoOriginalNameEnum[VideoOriginalEnum.VIDEO_MANAGE],
  45 + value: VideoOriginalEnum.VIDEO_MANAGE,
  46 + },
  47 + ],
  48 + },
  49 + },
  50 + {
  51 + field: FormFieldEnum.ACCESS_MODE,
  52 + component: 'InputNumber',
  53 + label: '视频流获取方式',
  54 + show: false,
  55 + },
  56 + {
  57 + field: FormFieldEnum.VIDEO_PLAY_URL,
  58 + component: 'Input',
  59 + label: '地址',
  60 + colProps: { span: 16 },
  61 + ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.CUSTOM,
  62 + componentProps: {
  63 + placeholder: '请输入自定义地址',
  64 + },
  65 + },
  66 + {
  67 + field: FormFieldEnum.ORGANIZATION_ID,
  68 + component: 'OrgTreeSelect',
  69 + label: '组织',
  70 + colProps: { span: 8 },
  71 + ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.VIDEO_MANAGE,
  72 + componentProps: {
  73 + showCreate: false,
  74 + allowClean: true,
  75 + },
  76 + },
  77 + {
  78 + field: FormFieldEnum.VIDEO_ID,
  79 + component: 'ApiSelect',
  80 + label: '地址',
  81 + colProps: { span: 8 },
  82 + ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.VIDEO_MANAGE,
  83 + componentProps: ({ formActionType, formModel }) => {
  84 + const { setFieldsValue } = formActionType;
  85 + const organizationId = formModel[FormFieldEnum.ORGANIZATION_ID];
  86 + return {
  87 + placeholder: '请选择视频',
  88 + params: { organizationId },
  89 + api: getCameraList,
  90 + labelField: 'name',
  91 + valueField: 'id',
  92 + resultField: 'data',
  93 + onChange(value: string, options: CameraModel) {
  94 + console.log(options);
  95 + setFieldsValue({
  96 + [FormFieldEnum.ACCESS_MODE]: value ? (options || {}).accessMode : null,
  97 + [FormFieldEnum.VIDEO_PLAY_URL]: value ? (options || {}).videoUrl : null,
  98 + });
  99 + },
  100 + };
  101 + },
  102 + },
  103 +];
... ...
1 1 <script lang="ts" setup>
2 2 import { CreateComponentType } from '/@/views/visual/packages/index.type';
3 3 import { BasicForm, useForm } from '/@/components/Form';
4   - import { dataSourceSchema } from '/@/views/visual/board/detail/config/basicConfiguration';
5 4 import {
6 5 PublicComponentValueType,
7 6 PublicFormInstaceType,
8 7 } from '/@/views/visual/dataSourceBindPanel/index.type';
  8 + import { DataSourceValueType, FormFieldEnum, formSchemas } from './datasource.config';
  9 + import { DataSource, VideoConfigType } from '/@/views/visual/palette/types';
9 10
10   - const props = defineProps<{
  11 + defineProps<{
11 12 values: PublicComponentValueType;
12 13 componentConfig: CreateComponentType;
13 14 }>();
... ... @@ -17,15 +18,19 @@
17 18 showActionButtonGroup: false,
18 19 layout: 'horizontal',
19 20 labelCol: { span: 0 },
20   - schemas: dataSourceSchema(false, props.componentConfig.componentConfig.key),
  21 + schemas: formSchemas,
21 22 });
22 23
23 24 const getFormValues = () => {
24   - return getFieldsValue();
  25 + const value = getFieldsValue() as DataSourceValueType;
  26 + return {
  27 + [FormFieldEnum.VIDEO_CONFIG]: value as VideoConfigType,
  28 + };
25 29 };
26 30
27   - const setFormValues = (record: Recordable) => {
28   - return setFieldsValue(record);
  31 + const setFormValues = (record: DataSource) => {
  32 + const { videoConfig = {} } = record;
  33 + return setFieldsValue(videoConfig);
29 34 };
30 35
31 36 defineExpose({
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '/@/views/visual/packages/index.type';
  2 + import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
5   - import { BasicVideoPlay } from '/@/components/Video';
  4 + import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video';
6 5 import { computed } from 'vue';
7 6 import { VideoJsPlayerOptions } from 'video.js';
  7 + import { AccessMode } from '/@/views/camera/manage/config.data';
  8 + import { onMounted } from 'vue';
  9 + import { unref } from 'vue';
  10 + import { getStreamingPlayUrl } from '/@/api/camera/cameraManager';
  11 + import { ref } from 'vue';
  12 + import { Spin } from 'ant-design-vue';
8 13
9 14 const props = defineProps<{
10 15 config: ComponentPropsConfigType<typeof option>;
11 16 }>();
12 17
  18 + const loading = ref(true);
  19 +
  20 + const getIsSelectPreviewMode = computed(() => {
  21 + return props.config.option.mode === ComponentMode.SELECT_PREVIEW;
  22 + });
  23 +
  24 + const getIsStreamingMode = computed(() => {
  25 + const { option } = props.config;
  26 + const { videoConfig } = option;
  27 + return videoConfig?.accessMode === AccessMode.Streaming;
  28 + });
  29 +
  30 + const playSource = ref<Record<'src' | 'type', string>>(
  31 + {} as unknown as Record<'src' | 'type', string>
  32 + );
  33 +
  34 + const exampleVideoPlay =
  35 + 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm';
  36 +
  37 + const getVideoPlaySources = computed(() => {
  38 + const { option } = props.config;
  39 + const { videoConfig } = option;
  40 + const { url } = videoConfig || {};
  41 + return Object.assign(
  42 + {
  43 + src: unref(getIsSelectPreviewMode) ? exampleVideoPlay : url,
  44 + type: getVideoTypeByUrl(url || ''),
  45 + },
  46 + unref(playSource)
  47 + );
  48 + });
  49 +
13 50 const getOptions = computed<VideoJsPlayerOptions>(() => {
14 51 const { option } = props.config;
15 52 const { itemHeightRatio, itemWidthRatio, widthPx, heightPx } = option;
... ... @@ -18,21 +55,48 @@
18 55 return {
19 56 width: currentW - 8,
20 57 height: currentH - 8,
21   - sources: [
22   - {
23   - src: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm',
24   - },
25   - ],
  58 + sources: unref(getVideoPlaySources).src
  59 + ? ([unref(getVideoPlaySources)] as VideoJsPlayerOptions['sources'])
  60 + : [],
26 61 };
27 62 });
28 63
29   - const updateFn: DataFetchUpdateFn = (_message) => {};
  64 + const handleGetVideoPlayUrl = async () => {
  65 + try {
  66 + if (unref(getIsStreamingMode) && !unref(getVideoPlaySources).src) {
  67 + const { option } = props.config;
  68 + const { videoConfig } = option;
  69 + if (!videoConfig?.id) return;
  70 + const { data: { url } = { url: '' } } = await getStreamingPlayUrl(videoConfig?.id);
  71 + playSource.value = {
  72 + src: url,
  73 + type: getVideoTypeByUrl(url),
  74 + };
  75 + }
  76 + } finally {
  77 + loading.value = false;
  78 + }
  79 + };
30 80
31   - useDataFetch(props, updateFn);
  81 + onMounted(() => {
  82 + handleGetVideoPlayUrl();
  83 + });
32 84 </script>
33 85
34 86 <template>
35 87 <main class="w-full h-full flex flex-col justify-center items-center p-2">
36   - <BasicVideoPlay :options="getOptions" />
  88 + <Spin :spinning="loading" wrapper-class-name="video-spin">
  89 + <BasicVideoPlay :options="getOptions" />
  90 + </Spin>
37 91 </main>
38 92 </template>
  93 +
  94 +<style lang="less" scoped>
  95 + .video-spin {
  96 + @apply w-full h-full;
  97 +
  98 + :deep(.ant-spin-container) {
  99 + @apply w-full h-full;
  100 + }
  101 + }
  102 +</style>
... ...
1 1 import { createComponent } from '..';
2 2 import { transformComponentKey } from '../componentMap';
3   -import { ConfigType, CreateComponentType } from '../index.type';
  3 +import { ConfigType, CreateComponentType, PublicComponentOptions } from '../index.type';
4 4
5 5 export const useGetComponentConfig = (
6 6 key: string,
7   - options: Recordable = {}
  7 + options: Partial<PublicComponentOptions> = {}
8 8 ): CreateComponentType => {
9 9 try {
10 10 const config = createComponent({ key: transformComponentKey(key) } as ConfigType, options);
... ...
... ... @@ -98,12 +98,25 @@ export interface PublicPresetOptions {
98 98 [key: string]: any;
99 99 }
100 100
  101 +export enum ComponentMode {
  102 + /**
  103 + * @description 选择预览
  104 + */
  105 + SELECT_PREVIEW = 'SELECT_PREVIEW',
  106 +
  107 + /**
  108 + * @description 绑定数据源后
  109 + */
  110 + BOUND_PREVIEW = 'BOUND_PREVIEW',
  111 +}
  112 +
101 113 export interface PublicComponentOptions {
102 114 uuid: string;
103 115 widthPx: number;
104 116 heightPx: number;
105 117 itemWidthRatio: number;
106 118 itemHeightRatio: number;
  119 + mode: ComponentMode;
107 120
108 121 dataSource?: DataSource[];
109 122 [key: string]: any;
... ...
... ... @@ -2,7 +2,7 @@
2 2 import { computed } from 'vue';
3 3 import { WidgetDataType } from '../../hooks/useDataSource';
4 4 import { fetchViewComponent } from '../../../packages';
5   - import { ConfigType } from '/@/views/visual/packages/index.type';
  5 + import { ComponentMode, ConfigType } from '/@/views/visual/packages/index.type';
6 6 import { ExtraDataSource } from '../../types';
7 7 import { CSSProperties } from 'vue';
8 8 import { toRaw } from 'vue';
... ... @@ -36,6 +36,7 @@
36 36 heightPx,
37 37 itemWidthRatio,
38 38 itemHeightRatio,
  39 + mode: ComponentMode.BOUND_PREVIEW,
39 40 });
40 41 });
41 42
... ...
... ... @@ -26,6 +26,7 @@ export interface DataSource {
26 26 attributeRename: string;
27 27 componentInfo: ComponentInfo;
28 28 customCommand: CustomCommand;
  29 + videoConfig?: VideoConfigType;
29 30 }
30 31
31 32 export interface ExtraDataSource extends DataSource, PublicComponentOptions {
... ... @@ -78,3 +79,11 @@ export interface ApiDataBoardDataType {
78 79 export interface ApiDataBoardInfoType {
79 80 data: ApiDataBoardDataType;
80 81 }
  82 +
  83 +export interface VideoConfigType {
  84 + organizationId?: string;
  85 + id?: string;
  86 + accessMode?: number;
  87 + type?: string;
  88 + url?: string;
  89 +}
... ...
... ... @@ -4,6 +4,7 @@
4 4 import { computed, ref } from 'vue';
5 5 import { Tabs } from 'ant-design-vue';
6 6 import {
  7 + ComponentMode,
7 8 ConfigType,
8 9 PackagesCategoryEnum,
9 10 PackagesCategoryNameEnum,
... ... @@ -41,7 +42,7 @@
41 42 });
42 43
43 44 const getBindConfig = (key: string) => {
44   - const config = createComponent({ key } as ConfigType);
  45 + const config = createComponent({ key } as ConfigType, { mode: ComponentMode.SELECT_PREVIEW });
45 46 return {
46 47 config,
47 48 };
... ...