Commit ffa666b6b9dff088e40ed4a5ae92c42c6407020f

Authored by ww
1 parent 1fd30052

feat: basic implement data board map component

... ... @@ -33,3 +33,4 @@ pnpm-debug.log*
33 33 *.sw?
34 34
35 35 yarn.lock
  36 +.vscode
... ...
... ... @@ -5,6 +5,7 @@
5 5 "public/resource/tinymce/langs"
6 6 ],
7 7 "cSpell.words": [
  8 + "inited",
8 9 "unref",
9 10 "VITE"
10 11 ]
... ...
  1 +import { HistoryData } from './model';
1 2 import { defHttp } from '/@/utils/http/axios';
2 3
3 4 // 获取设备配置
... ... @@ -9,7 +10,7 @@ export const getDeviceProfile = () => {
9 10
10 11 // 获取历史数据
11 12 export const getDeviceHistoryInfo = (params) => {
12   - return defHttp.get(
  13 + return defHttp.get<HistoryData>(
13 14 {
14 15 url: `/plugins/telemetry/DEVICE/${params.entityId}/values/timeseries`,
15 16 params: { ...params, entityId: null, orderBy: 'ASC' },
... ...
  1 +export interface HistoryData {
  2 + [key: string]: { ts: number; value: string }[];
  3 +}
... ...
... ... @@ -4,8 +4,9 @@ import {
4 4 ComponentInfoDetail,
5 5 DataBoardList,
6 6 DataComponentRecord,
  7 + DeviceAttributeParams,
  8 + DeviceAttributeRecord,
7 9 GetDataBoardParams,
8   - Layout,
9 10 MasterDeviceList,
10 11 UpdateDataBoardLayoutParams,
11 12 UpdateDataBoardParams,
... ... @@ -35,7 +36,7 @@ enum DataBoardShareUrl {
35 36 enum DeviceUrl {
36 37 GET_DEVICE = '/device/list/master',
37 38 GET_SLAVE_DEVICE = '/device/list/slave',
38   - GET_DEVICE_ATTRIBUTE = '/plugins/telemetry',
  39 + GET_DEVICE_ATTRIBUTE = '/device/attributes',
39 40 }
40 41
41 42 /**
... ... @@ -184,14 +185,12 @@ export const getGatewaySlaveDevice = (params: { organizationId: string; masterId
184 185 * @param params
185 186 * @returns
186 187 */
187   -export const getDeviceAttributes = (params: { entityType?: string; deviceId: string }) => {
188   - const { entityType = 'DEVICE', deviceId } = params;
189   - return defHttp.get<string[]>(
190   - {
191   - url: `${DeviceUrl.GET_DEVICE_ATTRIBUTE}/${entityType}/${deviceId}/keys/timeseries`,
  188 +export const getDeviceAttributes = (params: DeviceAttributeParams) => {
  189 + const { deviceProfileId, dataType } = params;
  190 + return defHttp.get<DeviceAttributeRecord[]>({
  191 + url: `${DeviceUrl.GET_DEVICE_ATTRIBUTE}/${deviceProfileId}`,
  192 + params: {
  193 + dataType,
192 194 },
193   - {
194   - joinPrefix: false,
195   - }
196   - );
  195 + });
197 196 };
... ...
... ... @@ -133,10 +133,26 @@ export interface UpdateDataComponentParams {
133 133
134 134 export interface MasterDeviceList {
135 135 deviceType: 'DIRECT_CONNECTION' | 'GATEWAY';
  136 + deviceProfileId: string;
136 137 id: string;
137 138 name: string;
  139 + label?: string;
  140 + value?: string;
138 141 }
139 142
140 143 export interface ComponentInfoDetail {
141 144 data: { componentData: DataComponentRecord[]; componentLayout: Layout[] };
142 145 }
  146 +
  147 +export type DataType = 'BOOL' | 'DOUBLE' | 'INT' | 'STRUCT' | 'TExT';
  148 +
  149 +export interface DeviceAttributeParams {
  150 + deviceProfileId: string;
  151 + dataType?: DataType;
  152 +}
  153 +
  154 +export interface DeviceAttributeRecord {
  155 + name: string;
  156 + identifier: string;
  157 + detail: DataType;
  158 +}
... ...
... ... @@ -479,7 +479,7 @@ export const selectDeviceAttrSchema: FormSchema[] = [
479 479 },
480 480 ];
481 481
482   -export const eChartOptions = (series, keys): EChartsOption => {
  482 +export const eChartOptions = (series: EChartsOption['series'], keys: string[]): EChartsOption => {
483 483 return {
484 484 tooltip: {
485 485 trigger: 'axis',
... ...
  1 +<script lang="ts" setup>
  2 + import { BasicForm, useForm } from '/@/components/Form';
  3 + import { BasicModal, useModalInner } from '/@/components/Modal';
  4 + import {
  5 + formSchema,
  6 + getHistorySearchParams,
  7 + SchemaFiled,
  8 + } from '../../../board/detail/config/historyTrend.config';
  9 + import { HistoryModalOkEmitParams, HistoryModalParams } from './type';
  10 + import { DataSource } from '/@/api/dataBoard/model';
  11 + import { ref } from 'vue';
  12 + import { getAllDeviceByOrg } from '/@/api/dataBoard';
  13 + import { getDeviceHistoryInfo } from '/@/api/alarm/position';
  14 +
  15 + const emit = defineEmits(['register', 'ok']);
  16 +
  17 + const [registerForm, { updateSchema, setFieldsValue, validate, getFieldsValue }] = useForm({
  18 + schemas: formSchema,
  19 + showActionButtonGroup: false,
  20 + fieldMapToTime: [
  21 + [SchemaFiled.DATE_RANGE, [SchemaFiled.START_TS, SchemaFiled.END_TS], 'YYYY-MM-DD HH:ss'],
  22 + ],
  23 + });
  24 +
  25 + const [registerModal, { closeModal }] = useModalInner(async (params: HistoryModalParams) => {
  26 + try {
  27 + const { dataSource = [] } = params;
  28 + const deviceRecord = dataSource?.at(0) || ({} as DataSource);
  29 + if (!deviceRecord.organizationId) return;
  30 + const deviceList = await getAllDeviceByOrg(deviceRecord.organizationId);
  31 + const options = deviceList
  32 + .filter((item) => item.id === deviceRecord.deviceId)
  33 + .map((item) => ({ ...item, label: item.name, value: item.id }));
  34 + const attKey = dataSource.map((item) => ({
  35 + ...item,
  36 + label: item.attribute,
  37 + value: item.attribute,
  38 + }));
  39 + updateSchema([
  40 + {
  41 + field: SchemaFiled.DEVICE_ID,
  42 + componentProps: {
  43 + options,
  44 + },
  45 + },
  46 + {
  47 + field: SchemaFiled.KEYS,
  48 + component: 'Select',
  49 + defaultValue: attKey.map((item) => item.value),
  50 + componentProps: {
  51 + options: attKey,
  52 + mode: 'multiple',
  53 + disabled: true,
  54 + },
  55 + },
  56 + ]);
  57 +
  58 + setFieldsValue({
  59 + [SchemaFiled.DEVICE_ID]: deviceRecord.deviceId,
  60 + [SchemaFiled.KEYS]: attKey.map((item) => item.value),
  61 + });
  62 + } catch (error) {
  63 + throw error;
  64 + }
  65 + });
  66 +
  67 + const loading = ref(false);
  68 + const handleOk = async () => {
  69 + try {
  70 + await validate();
  71 + let value = getFieldsValue();
  72 +
  73 + value = getHistorySearchParams(value);
  74 +
  75 + loading.value = true;
  76 +
  77 + const res = await getDeviceHistoryInfo({
  78 + ...value,
  79 + [SchemaFiled.KEYS]: value[SchemaFiled.KEYS].join(','),
  80 + });
  81 +
  82 + let timespanList = Object.keys(res).reduce((prev, next) => {
  83 + const ts = res[next].map((item) => item.ts);
  84 + return [...prev, ...ts];
  85 + }, [] as number[]);
  86 + timespanList = [...new Set(timespanList)];
  87 +
  88 + const track: Record<'lng' | 'lat', number>[] = [];
  89 + const keys = Object.keys(res);
  90 +
  91 + for (const ts of timespanList) {
  92 + const list: { ts: number; value: number }[] = [];
  93 + for (const key of keys) {
  94 + const record = res[key].find((item) => ts === item.ts);
  95 + list.push(record as any);
  96 + }
  97 + if (list.every(Boolean)) {
  98 + const lng = list.at(0)?.value;
  99 + const lat = list.at(1)?.value;
  100 + if (lng && lat) track.push({ lng, lat });
  101 + }
  102 + }
  103 + emit('ok', { track, value } as HistoryModalOkEmitParams);
  104 + closeModal();
  105 + } catch (error) {
  106 + throw error;
  107 + } finally {
  108 + loading.value = false;
  109 + }
  110 + };
  111 +</script>
  112 +
  113 +<template>
  114 + <BasicModal @register="registerModal" @ok="handleOk" :ok-button-props="{ loading }">
  115 + <BasicForm @register="registerForm" />
  116 + </BasicModal>
  117 +</template>
... ...
... ... @@ -4,7 +4,7 @@
4 4 };
5 5 </script>
6 6 <script lang="ts" setup>
7   - import { computed, onMounted, ref, unref } from 'vue';
  7 + import { computed, onMounted, reactive, ref, unref, watchEffect } from 'vue';
8 8 import { RadioRecord } from '../../detail/config/util';
9 9 import { MapComponentLayout, MapComponentValue } from './map.config';
10 10 import {
... ... @@ -15,8 +15,14 @@
15 15 import { Button, Tooltip } from 'ant-design-vue';
16 16 import { FrontComponent } from '../../const/const';
17 17 import { buildUUID } from '/@/utils/uuid';
  18 + import { useModal } from '/@/components/Modal';
  19 + import HistoryDataModel from './HistoryDataModel.vue';
  20 + import { HistoryModalOkEmitParams, HistoryModalParams } from './type';
  21 + import { formatToDateTime } from '/@/utils/dateUtil';
  22 + import { isEqual } from 'lodash-es';
18 23
19 24 // useVisualBoardContext();
  25 + type TrackRecord = Record<'lng' | 'lat' | 'ts', number>;
20 26
21 27 const startMethodName = `trackPlayMethod_${buildUUID()}`;
22 28
... ... @@ -44,19 +50,51 @@
44 50 const trackAni = ref<Nullable<any>>(null);
45 51 let mapInstance: Nullable<Recordable> = null;
46 52
  53 + const trackList = ref<TrackRecord[]>([]);
  54 +
  55 + watchEffect(() => {
  56 + if (
  57 + props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_REAL &&
  58 + props.value?.track?.length
  59 + ) {
  60 + const lng = props.value.track.at(0)!;
  61 + const lat = props.value.track.at(1)!;
  62 + const record = {
  63 + lng: lng.value,
  64 + lat: lat.value,
  65 + ts: lng.ts,
  66 + };
  67 + if (unref(trackList).length && isEqual(unref(trackList).at(-1), record)) return;
  68 + trackList.value.push(record);
  69 +
  70 + randomAnimation(unref(trackList));
  71 + // marketPoint(record);
  72 + }
  73 + });
  74 +
  75 + // function marketPoint(params: Record<'lng' | 'lat', number>) {
  76 + // const { lng, lat } = params;
  77 + // const BMap = (window as any).BMapGL;
  78 + // const marker = new BMap.Marker(new BMap.Point(lng, lat));
  79 + // unref(mapInstance)?.centerAndZoom(new BMap.Point(lng, lat));
  80 + // unref(mapInstance)?.addOverlay(marker);
  81 + // }
  82 +
47 83 async function initMap() {
48 84 const wrapEl = unref(wrapRef);
49 85 if (!wrapEl) return;
50 86 const BMapGL = (window as any).BMapGL;
51 87 mapInstance = new BMapGL.Map(wrapId);
52   - const point = new BMapGL.Point(116.404, 39.915);
  88 + const point = new BMapGL.Point(104.09457, 30.53189);
53 89 mapInstance!.centerAndZoom(point, 15);
54 90 mapInstance!.enableScrollWheelZoom(true);
55   - props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_HISTORY && randomAnimation();
  91 + props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_HISTORY &&
  92 + props.random &&
  93 + randomAnimation();
56 94 }
57 95
58   - const randomAnimation = () => {
59   - const path = [
  96 + const randomAnimation = (path?: Record<'lng' | 'lat', number>[], clearOverlays = true) => {
  97 + path = path || [
60 98 {
61 99 lng: 116.297611,
62 100 lat: 40.047363,
... ... @@ -90,6 +128,8 @@
90 128 const point: any[] = [];
91 129 const BMapGL = (window as any).BMapGL;
92 130
  131 + clearOverlays && unref(mapInstance)?.clearOverlays();
  132 +
93 133 for (const { lng, lat } of path) {
94 134 point.push(new BMapGL.Point(lng, lat));
95 135 }
... ... @@ -99,10 +139,10 @@
99 139
100 140 const dynamicPlayMethod = {
101 141 [startMethodName]() {
102   - trackAni.value = new BMapGLLib.TrackAnimation(mapInstance, pl, {
  142 + trackAni.value = new BMapGLLib.TrackAnimation(unref(mapInstance), pl, {
103 143 overallView: true,
104 144 tilt: 30,
105   - duration: 20000,
  145 + duration: 5000,
106 146 delay: 300,
107 147 });
108 148 trackAni.value!.start();
... ... @@ -118,11 +158,26 @@
118 158 initMap();
119 159 });
120 160
  161 + const timeRange = reactive<Record<'start' | 'end', Nullable<number>>>({
  162 + start: null,
  163 + end: null,
  164 + });
121 165 const getTimeRange = computed(() => {
122   - return ` - 从 ${'2020-10-20 10:10:10'} 到 ${'2020-10-20 10:10:10'}`;
  166 + const { start, end } = timeRange;
  167 + if (!start || !end) return `- 请选择`;
  168 + return ` - 从 ${formatToDateTime(start, 'YYYY-MM-DD HH:mm:ss')} 到 ${formatToDateTime(
  169 + end,
  170 + 'YYYY-MM-DD HH:mm:ss'
  171 + )}`;
123 172 });
124 173
125   - const handleTrackSwitch = () => {};
  174 + const [register, { openModal }] = useModal();
  175 +
  176 + const handleTrackSwitch = () => {
  177 + openModal(true, {
  178 + dataSource: props.value?.dataSource || [],
  179 + } as HistoryModalParams);
  180 + };
126 181
127 182 const getTrackPlayStatus = computed(() => {
128 183 return (trackAni.value || {})._status;
... ... @@ -133,10 +188,17 @@
133 188 else if (unref(getTrackPlayStatus) === TrackAnimationStatus.PLAY) unref(trackAni).pause();
134 189 else if (unref(getTrackPlayStatus) === TrackAnimationStatus.PAUSE) unref(trackAni).continue();
135 190 };
  191 +
  192 + const handleRenderHistroyData = (params: HistoryModalOkEmitParams) => {
  193 + const { track, value } = params;
  194 + track.length && randomAnimation(track as unknown as Record<'lng' | 'lat', number>[]);
  195 + timeRange.start = value.startTs as number;
  196 + timeRange.end = value.endTs as number;
  197 + };
136 198 </script>
137 199
138 200 <template>
139   - <div class="w-full h-full flex justify-center items-center flex-col">
  201 + <div class="w-full h-full flex justify-center items-center flex-col p-2">
140 202 <div
141 203 class="w-full flex"
142 204 v-if="props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_HISTORY"
... ... @@ -144,7 +206,7 @@
144 206 <Button type="text" class="!px-2 flex-auto !text-left truncate" @click="handleTrackSwitch">
145 207 <div class="w-full truncate text-gray-500 flex items-center">
146 208 <ClockCircleOutlined />
147   - <span class="mx-1">实时</span>
  209 + <span class="mx-1">历史</span>
148 210 <Tooltip :title="getTimeRange.replace('-', '')">
149 211 <span class="truncate">
150 212 {{ getTimeRange }}
... ... @@ -154,12 +216,16 @@
154 216 </Button>
155 217 <Button type="text" class="!px-2 !text-gray-500" @click="handlePlay">
156 218 <PlayCircleOutlined v-show="getTrackPlayStatus !== TrackAnimationStatus.PLAY" />
157   - <PauseCircleOutlined v-show="getTrackPlayStatus === TrackAnimationStatus.PLAY" />
  219 + <PauseCircleOutlined
  220 + class="!ml-0"
  221 + v-show="getTrackPlayStatus === TrackAnimationStatus.PLAY"
  222 + />
158 223 <span>
159 224 {{ getTrackPlayStatus !== TrackAnimationStatus.PLAY ? '播放轨迹' : '暂停播放' }}
160 225 </span>
161 226 </Button>
162 227 </div>
163 228 <div ref="wrapRef" :id="wrapId" class="w-full h-full"></div>
  229 + <HistoryDataModel @register="register" @ok="handleRenderHistroyData" />
164 230 </div>
165 231 </template>
... ...
1 1 import { FrontComponent } from '../../const/const';
2 2 import { ComponentConfig } from '../../types/type';
  3 +import { DataSource } from '/@/api/dataBoard/model';
3 4
4 5 export interface MapComponentLayout {
5 6 componentType?: FrontComponent;
... ... @@ -7,7 +8,8 @@ export interface MapComponentLayout {
7 8
8 9 export interface MapComponentValue {
9 10 icon?: string;
10   - track?: Recordable[];
  11 + track?: Record<'ts' | 'value', number>[];
  12 + dataSource?: DataSource[];
11 13 }
12 14
13 15 interface Config {
... ... @@ -22,14 +24,36 @@ export const MapRealTrackConfig: Config = {
22 24 componentType: FrontComponent.MAP_COMPONENT_TRACK_REAL,
23 25 };
24 26
  27 +const getTrack = (dataSource: DataSource[]) => {
  28 + if (dataSource.length >= 2) {
  29 + const trackRecord = dataSource.slice(0, 2);
  30 + if (!trackRecord.every((item) => item.componentInfo.value))
  31 + return { track: [], dataSource: [] };
  32 +
  33 + const track = trackRecord.map((item) => {
  34 + return {
  35 + ts: item.componentInfo.updateTime,
  36 + value: item.componentInfo.value || 0,
  37 + };
  38 + });
  39 + return { track, dataSource };
  40 + }
  41 + return { track: [], dataSource: [] };
  42 +};
  43 +
25 44 export const transfromMapComponentConfig: ComponentConfig['transformConfig'] = (
26 45 componentConfig: Config,
27 46 _record,
28   - _dataSourceRecord
  47 + dataSourceRecord
29 48 ) => {
  49 + const { track, dataSource } = getTrack(dataSourceRecord as DataSource[]);
30 50 return {
31 51 layout: {
32 52 ...componentConfig,
33   - },
  53 + } as MapComponentLayout,
  54 + value: {
  55 + track,
  56 + dataSource,
  57 + } as MapComponentValue,
34 58 };
35 59 };
... ...
  1 +import { SchemaFiled } from '../../detail/config/historyTrend.config';
  2 +import { DataSource } from '/@/api/dataBoard/model';
  3 +
  4 +export interface HistoryModalParams {
  5 + dataSource?: DataSource[];
  6 +}
  7 +
  8 +export interface HistoryModalOkEmitParams {
  9 + track: {
  10 + lng: number | string;
  11 + lat: number | string;
  12 + }[];
  13 + value: Record<SchemaFiled, string | number>;
  14 +}
... ...
... ... @@ -2,11 +2,22 @@
2 2 import { useUpdateCenter } from '../../hook/useUpdateCenter';
3 3 import { FrontDataSourceRecord } from '../../types/type';
4 4 import { createVisualBoardContext } from '../../hook/useVisualBoardContext';
  5 + import { DataComponentRecord } from '/@/api/dataBoard/model';
  6 + import { computed } from 'vue';
  7 + import { frontComponentMap } from '../help';
  8 + import { FrontComponent } from '../../const/const';
5 9
6 10 const props = defineProps<{
  11 + record: DataComponentRecord;
7 12 dataSource: FrontDataSourceRecord[];
8 13 }>();
9 14
  15 + const isMultipleDataSource = computed(() => {
  16 + const { record } = props;
  17 + const componentInfo = frontComponentMap.get(record.frontId as FrontComponent);
  18 + return componentInfo?.isMultipleDataSource;
  19 + });
  20 +
10 21 const { update, add, remove } = useUpdateCenter();
11 22
12 23 createVisualBoardContext({ update, add, remove });
... ... @@ -19,24 +30,41 @@
19 30 <slot name="header"></slot>
20 31
21 32 <div class="widget-content">
22   - <div
23   - v-for="item in props.dataSource"
24   - :key="item.id"
25   - :style="{ width: `${item.width || 100}%`, height: `${item.height || 100}%` }"
26   - class="widget-item"
27   - >
28   - <div class="widget-box">
29   - <div class="widget-controls-container">
30   - <slot
31   - name="controls"
32   - :record="item"
33   - :add="add"
34   - :remove="remove"
35   - :update="update"
36   - ></slot>
  33 + <template v-if="!isMultipleDataSource">
  34 + <div
  35 + v-for="item in props.dataSource"
  36 + :key="item.id"
  37 + :style="{ width: `${item.width || 100}%`, height: `${item.height || 100}%` }"
  38 + class="widget-item"
  39 + >
  40 + <div class="widget-box">
  41 + <div class="widget-controls-container">
  42 + <slot
  43 + name="controls"
  44 + :record="item"
  45 + :add="add"
  46 + :remove="remove"
  47 + :update="update"
  48 + ></slot>
  49 + </div>
  50 + </div>
  51 + </div>
  52 + </template>
  53 + <template v-if="isMultipleDataSource">
  54 + <div :style="{ width: `${100}%`, height: `${100}%` }" class="widget-item">
  55 + <div class="widget-box">
  56 + <div class="widget-controls-container">
  57 + <slot
  58 + name="controls"
  59 + :record="props.dataSource"
  60 + :add="add"
  61 + :remove="remove"
  62 + :update="update"
  63 + ></slot>
  64 + </div>
37 65 </div>
38 66 </div>
39   - </div>
  67 + </template>
40 68 </div>
41 69 <slot name="footer"></slot>
42 70 </section>
... ...
... ... @@ -45,6 +45,9 @@ frontComponentMap.set(FrontComponent.TEXT_COMPONENT_1, {
45 45 ComponentKey: FrontComponent.TEXT_COMPONENT_1,
46 46 ComponentConfig: TextComponent1Config,
47 47 ComponentCategory: FrontComponentCategory.TEXT,
  48 + isMultipleDataSource: false,
  49 + hasHistoryTrend: true,
  50 + hasSetting: true,
48 51 transformConfig: transformTextComponentConfig,
49 52 });
50 53
... ... @@ -54,6 +57,9 @@ frontComponentMap.set(FrontComponent.TEXT_COMPONENT_3, {
54 57 ComponentKey: FrontComponent.TEXT_COMPONENT_3,
55 58 ComponentConfig: TextComponent3Config,
56 59 ComponentCategory: FrontComponentCategory.TEXT,
  60 + isMultipleDataSource: false,
  61 + hasHistoryTrend: true,
  62 + hasSetting: true,
57 63 transformConfig: transformTextComponentConfig,
58 64 });
59 65
... ... @@ -63,6 +69,9 @@ frontComponentMap.set(FrontComponent.TEXT_COMPONENT_4, {
63 69 ComponentKey: FrontComponent.TEXT_COMPONENT_4,
64 70 ComponentConfig: TextComponent4Config,
65 71 ComponentCategory: FrontComponentCategory.TEXT,
  72 + isMultipleDataSource: false,
  73 + hasHistoryTrend: true,
  74 + hasSetting: true,
66 75 transformConfig: transformTextComponentConfig,
67 76 });
68 77
... ... @@ -72,6 +81,9 @@ frontComponentMap.set(FrontComponent.TEXT_COMPONENT_5, {
72 81 ComponentKey: FrontComponent.TEXT_COMPONENT_5,
73 82 ComponentConfig: TextComponent5Config,
74 83 ComponentCategory: FrontComponentCategory.TEXT,
  84 + isMultipleDataSource: false,
  85 + hasHistoryTrend: true,
  86 + hasSetting: true,
75 87 transformConfig: transformTextComponentConfig,
76 88 });
77 89
... ... @@ -84,6 +96,9 @@ frontComponentMap.set(FrontComponent.INSTRUMENT_COMPONENT_1, {
84 96 componentType: FrontComponent.INSTRUMENT_COMPONENT_1,
85 97 } as DashboardComponentLayout,
86 98 ComponentCategory: FrontComponentCategory.INSTRUMENT,
  99 + isMultipleDataSource: false,
  100 + hasHistoryTrend: true,
  101 + hasSetting: true,
87 102 transformConfig: transformDashboardComponentConfig,
88 103 });
89 104
... ... @@ -96,6 +111,9 @@ frontComponentMap.set(FrontComponent.INSTRUMENT_COMPONENT_2, {
96 111 componentType: FrontComponent.INSTRUMENT_COMPONENT_2,
97 112 } as DashboardComponentLayout,
98 113 ComponentCategory: FrontComponentCategory.INSTRUMENT,
  114 + isMultipleDataSource: false,
  115 + hasHistoryTrend: true,
  116 + hasSetting: true,
99 117 transformConfig: transformDashboardComponentConfig,
100 118 });
101 119
... ... @@ -104,6 +122,9 @@ frontComponentMap.set(FrontComponent.DIGITAL_DASHBOARD_COMPONENT, {
104 122 ComponentName: '数字仪表盘',
105 123 ComponentKey: FrontComponent.DIGITAL_DASHBOARD_COMPONENT,
106 124 ComponentCategory: FrontComponentCategory.INSTRUMENT,
  125 + isMultipleDataSource: false,
  126 + hasHistoryTrend: true,
  127 + hasSetting: true,
107 128 transformConfig: transformDashboardComponentConfig,
108 129 });
109 130
... ... @@ -112,6 +133,9 @@ frontComponentMap.set(FrontComponent.PICTURE_COMPONENT_1, {
112 133 ComponentName: '图片组件',
113 134 ComponentKey: FrontComponent.PICTURE_COMPONENT_1,
114 135 ComponentCategory: FrontComponentCategory.PICTURE,
  136 + isMultipleDataSource: false,
  137 + hasHistoryTrend: true,
  138 + hasSetting: false,
115 139 transformConfig: transformPictureConfig,
116 140 });
117 141
... ... @@ -120,6 +144,9 @@ frontComponentMap.set(FrontComponent.CONTROL_COMPONENT_SWITCH_WITH_ICON, {
120 144 ComponentName: '控制按钮1',
121 145 ComponentKey: FrontComponent.CONTROL_COMPONENT_SWITCH_WITH_ICON,
122 146 ComponentCategory: FrontComponentCategory.CONTROL,
  147 + isMultipleDataSource: false,
  148 + hasHistoryTrend: true,
  149 + hasSetting: false,
123 150 transformConfig: transformControlConfig,
124 151 });
125 152
... ... @@ -128,6 +155,9 @@ frontComponentMap.set(FrontComponent.CONTROL_COMPONENT_SLIDING_SWITCH, {
128 155 ComponentName: '控制按钮2',
129 156 ComponentKey: FrontComponent.CONTROL_COMPONENT_SLIDING_SWITCH,
130 157 ComponentCategory: FrontComponentCategory.CONTROL,
  158 + isMultipleDataSource: false,
  159 + hasHistoryTrend: true,
  160 + hasSetting: false,
131 161 transformConfig: transformControlConfig,
132 162 });
133 163
... ... @@ -136,6 +166,9 @@ frontComponentMap.set(FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH, {
136 166 ComponentName: '控制按钮3',
137 167 ComponentKey: FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH,
138 168 ComponentCategory: FrontComponentCategory.CONTROL,
  169 + isMultipleDataSource: false,
  170 + hasHistoryTrend: true,
  171 + hasSetting: false,
139 172 transformConfig: transformControlConfig,
140 173 });
141 174
... ... @@ -145,6 +178,9 @@ frontComponentMap.set(FrontComponent.MAP_COMPONENT_TRACK_REAL, {
145 178 ComponentKey: FrontComponent.MAP_COMPONENT_TRACK_REAL,
146 179 ComponentConfig: MapRealTrackConfig,
147 180 ComponentCategory: FrontComponentCategory.MAP,
  181 + isMultipleDataSource: true,
  182 + hasHistoryTrend: false,
  183 + hasSetting: false,
148 184 transformConfig: transfromMapComponentConfig,
149 185 });
150 186
... ... @@ -154,6 +190,9 @@ frontComponentMap.set(FrontComponent.MAP_COMPONENT_TRACK_HISTORY, {
154 190 ComponentKey: FrontComponent.MAP_COMPONENT_TRACK_HISTORY,
155 191 ComponentConfig: MaphistoryTrackConfig,
156 192 ComponentCategory: FrontComponentCategory.MAP,
  193 + isMultipleDataSource: true,
  194 + hasHistoryTrend: false,
  195 + hasSetting: false,
157 196 transformConfig: transfromMapComponentConfig,
158 197 });
159 198
... ...
1 1 <script lang="ts" setup>
2   - import { CopyOutlined, DeleteOutlined, SettingOutlined } from '@ant-design/icons-vue';
  2 + import {
  3 + CopyOutlined,
  4 + DeleteOutlined,
  5 + SettingOutlined,
  6 + SwapOutlined,
  7 + } from '@ant-design/icons-vue';
3 8 import { Tooltip, Button } from 'ant-design-vue';
4 9 import { FormActionType, useForm } from '/@/components/Form';
5   - import { basicSchema } from '../config/basicConfiguration';
  10 + import { basicSchema, DataSourceField } from '../config/basicConfiguration';
6 11 import BasicForm from '/@/components/Form/src/BasicForm.vue';
7   - import { ref, shallowReactive, unref, nextTick, watch, computed } from 'vue';
  12 + import { ref, shallowReactive, unref, nextTick, watch, computed, onMounted } from 'vue';
8 13 import VisualOptionsModal from './VisualOptionsModal.vue';
9 14 import { useModal } from '/@/components/Modal';
10 15 import { buildUUID } from '/@/utils/uuid';
... ... @@ -13,6 +18,10 @@
13 18 import { DataBoardLayoutInfo } from '../../types/type';
14 19 import { getDataSourceComponent } from './DataSourceForm/help';
15 20 import { FrontComponent } from '../../const/const';
  21 + import { isNullAndUnDef } from '/@/utils/is';
  22 + import { useSortable } from '/@/hooks/web/useSortable';
  23 + import { cloneDeep } from 'lodash-es';
  24 + import { frontComponentMap } from '../../components/help';
16 25
17 26 type DataSourceFormEL = { [key: string]: Nullable<FormActionType> };
18 27
... ... @@ -61,27 +70,55 @@
61 70
62 71 const validateDataSourceField = async () => {
63 72 const hasExistEl = Object.keys(dataSourceEl).filter((key) => dataSourceEl[key]);
64   - const _dataSource: boolean[] = [];
  73 + const _dataSource: Record<DataSourceField, string>[] = [];
65 74 for (const id of hasExistEl) {
66   - const flag = (await (dataSourceEl[id] as FormActionType).validate()) as boolean;
67   - _dataSource.push(flag);
  75 + const flag = (await (dataSourceEl[id] as FormActionType).validate()) as Record<
  76 + DataSourceField,
  77 + string
  78 + >;
  79 + flag && _dataSource.push(flag);
  80 + }
  81 +
  82 + if (
  83 + [
  84 + FrontComponent.MAP_COMPONENT_TRACK_HISTORY,
  85 + FrontComponent.MAP_COMPONENT_TRACK_REAL,
  86 + ].includes(props.frontId!)
  87 + ) {
  88 + await validateMapComponent(_dataSource);
68 89 }
69 90 return _dataSource;
70 91 };
71 92
  93 + const validateMapComponent = async (dataSource: Record<DataSourceField, string>[]) => {
  94 + if (dataSource.length) {
  95 + const firstRecord = dataSource.at(0)!;
  96 + const { deviceId, slaveDeviceId } = firstRecord;
  97 + const flag = dataSource.every(
  98 + (item) => item.deviceId === deviceId && item.slaveDeviceId === slaveDeviceId
  99 + );
  100 + if (!flag) {
  101 + createMessage.warning('地图组件绑定的数据源应该一致');
  102 + return Promise.reject(false);
  103 + }
  104 + }
  105 + };
  106 +
72 107 const getDataSourceField = () => {
73 108 const hasExistEl = Object.keys(dataSourceEl).filter((key) => dataSourceEl[key]);
74 109 const _dataSource: DataSource[] = [];
  110 +
75 111 for (const id of hasExistEl) {
76 112 const index = unref(dataSource).findIndex((item) => item.id === id);
77 113 const value = (dataSourceEl[id] as FormActionType).getFieldsValue() as DataSource;
78 114 if (!~index) continue;
79 115 const componentInfo = unref(dataSource)[index].componentInfo || {};
80   - _dataSource.unshift({
  116 + _dataSource[index] = {
81 117 ...value,
82 118 componentInfo: { ...(props.defaultConfig || {}), ...componentInfo },
83   - });
  119 + };
84 120 }
  121 +
85 122 return _dataSource;
86 123 };
87 124
... ... @@ -160,7 +197,8 @@
160 197 };
161 198
162 199 const showSettingButton = computed(() => {
163   - return props.frontId !== FrontComponent.PICTURE_COMPONENT_1;
  200 + const flag = frontComponentMap.get(props.frontId!)?.hasSetting;
  201 + return flag;
164 202 });
165 203
166 204 watch(
... ... @@ -179,6 +217,41 @@
179 217 return getDataSourceComponent(props.frontId as FrontComponent);
180 218 });
181 219
  220 + let inited = false;
  221 + const formListEl = ref<HTMLElement>();
  222 + async function handleSort() {
  223 + if (inited) return;
  224 + await nextTick();
  225 + const formList = unref(formListEl);
  226 + if (!formList) return;
  227 +
  228 + const { initSortable } = useSortable(unref(formList), {
  229 + handle: '.sort-icon',
  230 + onEnd: (evt) => {
  231 + const { oldIndex, newIndex } = evt;
  232 + if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
  233 + return;
  234 + }
  235 +
  236 + const _dataSource = cloneDeep(unref(dataSource));
  237 +
  238 + if (oldIndex > newIndex) {
  239 + _dataSource.splice(newIndex, 0, _dataSource[oldIndex]);
  240 + _dataSource.splice(oldIndex + 1, 1);
  241 + } else {
  242 + _dataSource.splice(newIndex + 1, 0, _dataSource[oldIndex]);
  243 + _dataSource.splice(oldIndex, 1);
  244 + }
  245 +
  246 + dataSource.value = _dataSource;
  247 + },
  248 + });
  249 + initSortable();
  250 + inited = true;
  251 + }
  252 +
  253 + onMounted(() => handleSort());
  254 +
182 255 defineExpose({
183 256 getAllDataSourceFieldValue,
184 257 validate,
... ... @@ -192,36 +265,43 @@
192 265 <BasicForm @register="basicRegister" class="w-full" />
193 266 </div>
194 267 <h3 class="w-24 flex-shrink-0 text-right pr-2 my-4">选择数据源</h3>
195   - <div v-for="item in dataSource" :key="item.id" class="flex">
196   - <div
197   - class="w-24 text-right leading-30px pr-8px flex right"
198   - style="flex: 0 0 96px; justify-content: right"
199   - >
200   - 选择设备
  268 + <section ref="formListEl">
  269 + <div v-for="item in dataSource" :data-id="item.id" :key="item.id" class="flex bg-light-50">
  270 + <div
  271 + class="w-24 text-right leading-30px pr-8px flex right"
  272 + style="flex: 0 0 96px; justify-content: right"
  273 + >
  274 + 选择设备
  275 + </div>
  276 + <div class="pl-2 flex-auto">
  277 + <component :is="dataSourceComponent" :ref="(el) => setFormEl(el, item.id)" />
  278 + </div>
  279 + <div class="flex justify-center gap-3 w-28">
  280 + <Tooltip title="复制">
  281 + <CopyOutlined @click="handleCopy(item)" class="cursor-pointer text-lg !leading-32px" />
  282 + </Tooltip>
  283 + <Tooltip title="设置">
  284 + <SettingOutlined
  285 + v-show="showSettingButton"
  286 + @click="handleSetting(item)"
  287 + class="cursor-pointer text-lg !leading-32px"
  288 + />
  289 + </Tooltip>
  290 + <Tooltip title="拖拽排序">
  291 + <SwapOutlined
  292 + class="cursor-pointer text-lg !leading-32px svg:transform svg:rotate-90 sort-icon"
  293 + />
  294 + </Tooltip>
  295 + <Tooltip title="删除">
  296 + <DeleteOutlined
  297 + @click="handleDelete(item)"
  298 + class="cursor-pointer text-lg !leading-32px"
  299 + />
  300 + </Tooltip>
  301 + </div>
201 302 </div>
202   - <div class="pl-2 flex-auto">
203   - <!-- <BasicDataSourceForm /> -->
204   - <component :is="dataSourceComponent" :ref="(el) => setFormEl(el, item.id)" />
205   - </div>
206   - <div class="flex justify-center gap-3 w-24">
207   - <Tooltip title="复制">
208   - <CopyOutlined @click="handleCopy(item)" class="cursor-pointer text-lg !leading-32px" />
209   - </Tooltip>
210   - <Tooltip title="设置">
211   - <SettingOutlined
212   - v-show="showSettingButton"
213   - @click="handleSetting(item)"
214   - class="cursor-pointer text-lg !leading-32px"
215   - />
216   - </Tooltip>
217   - <Tooltip title="删除">
218   - <DeleteOutlined
219   - @click="handleDelete(item)"
220   - class="cursor-pointer text-lg !leading-32px"
221   - />
222   - </Tooltip>
223   - </div>
224   - </div>
  303 + </section>
  304 +
225 305 <div class="text-center">
226 306 <Button type="primary" @click="handleAdd">添加数据源</Button>
227 307 </div>
... ...
... ... @@ -66,6 +66,7 @@
66 66 const { getAllDataSourceFieldValue, validate } = unref(basicConfigurationEl)!;
67 67 await validate();
68 68 const value = getAllDataSourceFieldValue();
  69 +
69 70 unref(isEdit) ? handleUpdateComponent(value) : handleAddComponent(value);
70 71 resetForm();
71 72 } catch (error: unknown) {
... ...
... ... @@ -2,14 +2,10 @@ import { Component } from 'vue';
2 2 import { FrontComponent } from '../../../const/const';
3 3 import BasicDataSourceForm from './BasicDataSourceForm.vue';
4 4 import ControlDataSourceForm from './ControlDataSourceForm.vue';
5   -import MapDataSourceForm from './MapDataSourceForm.vue';
6 5
7 6 const dataSourceComponentMap = new Map<FrontComponent, Component>();
8 7
9 8 dataSourceComponentMap.set(FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH, ControlDataSourceForm);
10   -dataSourceComponentMap.set(FrontComponent.MAP_COMPONENT_TRACK_REAL, MapDataSourceForm);
11   -dataSourceComponentMap.set(FrontComponent.MAP_COMPONENT_TRACK_HISTORY, MapDataSourceForm);
12   -
13 9 export const getDataSourceComponent = (frontId: FrontComponent) => {
14 10 if (dataSourceComponentMap.has(frontId)) return dataSourceComponentMap.get(frontId)!;
15 11 return BasicDataSourceForm;
... ...
... ... @@ -8,20 +8,24 @@
8 8 import { AggregateDataEnum, eChartOptions } from '/@/views/device/localtion/config.data';
9 9 import { useGridLayout } from '/@/hooks/component/useGridLayout';
10 10 import { ColEx } from '/@/components/Form/src/types';
11   - import { DataSource } from '/@/api/dataBoard/model';
  11 + import { DataSource, DeviceAttributeRecord } from '/@/api/dataBoard/model';
12 12 import { useForm, BasicForm } from '/@/components/Form';
13 13 import { formSchema, QueryWay, SchemaFiled } from '../config/historyTrend.config';
14 14 import { DEFAULT_DATE_FORMAT } from '../config/util';
15 15 import { Loading } from '/@/components/Loading';
16 16 import BasicModal from '/@/components/Modal/src/BasicModal.vue';
17 17 import { useModalInner } from '/@/components/Modal';
18   - import { getDeviceAttributes } from '/@/api/dataBoard';
  18 + import { getAllDeviceByOrg, getDeviceAttributes } from '/@/api/dataBoard';
  19 + import { HistoryData } from '/@/api/alarm/position/model';
  20 + import { EChartsOption } from 'echarts';
  21 +
  22 + type DeviceOption = Record<'label' | 'value' | 'organizationId', string>;
19 23
20 24 defineEmits(['register']);
21 25
22 26 const chartRef = ref();
23 27
24   - const deviceAttrs = ref<string[]>([]);
  28 + const deviceAttrs = ref<DeviceAttributeRecord[]>([]);
25 29
26 30 const loading = ref(false);
27 31
... ... @@ -29,26 +33,27 @@
29 33
30 34 function getSearchParams(value: Recordable) {
31 35 const { startTs, endTs, interval, agg, limit, keys, way, deviceId } = value;
  36 + const basicRecord = {
  37 + entityId: deviceId,
  38 + keys: keys
  39 + ? keys
  40 + : unref(deviceAttrs)
  41 + .map((item) => item.identifier)
  42 + .join(),
  43 + interval,
  44 + agg,
  45 + limit,
  46 + };
32 47 if (way === QueryWay.LATEST) {
33   - return {
34   - entityId: deviceId,
35   - keys: keys ? keys : unref(deviceAttrs).join(),
  48 + return Object.assign(basicRecord, {
36 49 startTs: moment().subtract(startTs, 'ms').valueOf(),
37 50 endTs: Date.now(),
38   - interval,
39   - agg,
40   - limit,
41   - };
  51 + });
42 52 } else {
43   - return {
44   - entityId: deviceId,
45   - keys: keys ? keys : unref(deviceAttrs).join(),
  53 + return Object.assign(basicRecord, {
46 54 startTs: moment(startTs).valueOf(),
47 55 endTs: moment(endTs).valueOf(),
48   - interval,
49   - agg,
50   - limit,
51   - };
  56 + });
52 57 }
53 58 }
54 59
... ... @@ -60,24 +65,24 @@
60 65 }
61 66 }
62 67
63   - function setChartOptions(data, keys?) {
  68 + function setChartOptions(data: HistoryData, keys?: string | string[]) {
64 69 const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
65 70
66   - const dataArray: any[] = [];
  71 + const dataArray: [string, string, string][] = [];
67 72 for (const key in data) {
68 73 for (const item1 of data[key]) {
69 74 let { ts, value } = item1;
70 75 const time = dateUtil(ts).format(DEFAULT_DATE_FORMAT);
71 76 value = Number(value).toFixed(2);
72   - dataArray.push([time, value, key]);
  77 + dataArray.push([time, value, key as string]);
73 78 }
74 79 }
75   - keys = keys ? [keys] : unref(deviceAttrs);
76   - const series: any = keys.map((item) => {
  80 + keys = keys ? [keys as string] : unref(deviceAttrs).map((item) => item.identifier);
  81 + const series: EChartsOption['series'] = (keys as string[]).map((item) => {
77 82 return {
78 83 name: item,
79 84 type: 'line',
80   - data: dataArray.filter((item1) => item1[2] === item),
  85 + data: dataArray.filter((temp) => temp[2] === item),
81 86 };
82 87 });
83 88 // 设置数据
... ... @@ -118,18 +123,23 @@
118 123 },
119 124 });
120 125
121   - const getDeviceDataKey = async (deviceId: string) => {
122   - if (!deviceId) return;
  126 + const getDeviceDataKey = async (record: DeviceOption) => {
  127 + const { organizationId, value } = record;
123 128 try {
124   - deviceAttrs.value = (await getDeviceAttributes({ deviceId })) || [];
  129 + const options = await getAllDeviceByOrg(organizationId);
  130 + const record = options.find((item) => item.id === value);
  131 + const { deviceProfileId } = record!;
  132 + deviceAttrs.value = (await getDeviceAttributes({ deviceProfileId })) || [];
125 133 await nextTick();
126 134 method.updateSchema({
127 135 field: SchemaFiled.KEYS,
128 136 componentProps: {
129   - options: unref(deviceAttrs).map((item) => ({ label: item, value: item })),
  137 + options: unref(deviceAttrs).map((item) => ({ label: item.name, value: item.identifier })),
130 138 },
131 139 });
132   - } catch (error) {}
  140 + } catch (error) {
  141 + throw error;
  142 + }
133 143 };
134 144
135 145 const handleModalOpen = async () => {
... ... @@ -147,7 +157,9 @@
147 157
148 158 const res = await getDeviceHistoryInfo({
149 159 entityId: deviceId,
150   - keys: unref(deviceAttrs).join(),
  160 + keys: unref(deviceAttrs)
  161 + .map((item) => item.identifier)
  162 + .join(),
151 163 startTs: Date.now() - 1 * 24 * 60 * 60 * 1000,
152 164 endTs: Date.now(),
153 165 agg: AggregateDataEnum.NONE,
... ... @@ -166,9 +178,10 @@
166 178
167 179 const generateDeviceOptions = (dataSource: DataSource[]) => {
168 180 const record: { [key: string]: boolean } = {};
169   - const options: Record<'label' | 'value', string>[] = [];
  181 +
  182 + const options: DeviceOption[] = [];
170 183 for (const item of dataSource) {
171   - const { deviceName, gatewayDevice, slaveDeviceId } = item;
  184 + const { deviceName, gatewayDevice, slaveDeviceId, organizationId } = item;
172 185 let { deviceId } = item;
173 186 if (gatewayDevice) {
174 187 deviceId = slaveDeviceId;
... ... @@ -177,6 +190,7 @@
177 190 options.push({
178 191 label: deviceName,
179 192 value: deviceId,
  193 + organizationId,
180 194 });
181 195 record[deviceId] = true;
182 196 }
... ... @@ -195,8 +209,8 @@
195 209 const { setFieldsValue } = formActionType;
196 210 return {
197 211 options,
198   - onChange(value: string) {
199   - getDeviceDataKey(value);
  212 + onChange(_, record: DeviceOption) {
  213 + getDeviceDataKey(record);
200 214 setFieldsValue({ [SchemaFiled.KEYS]: null });
201 215 },
202 216 };
... ... @@ -204,17 +218,10 @@
204 218 });
205 219
206 220 if (options.length && options.at(0)?.value) {
207   - const value = options.at(0)!.value;
208   - getDeviceDataKey(value);
  221 + const record = options.at(0)!;
  222 + await getDeviceDataKey(record);
209 223 try {
210   - deviceAttrs.value = (await getDeviceAttributes({ deviceId: value })) || [];
211   - method.updateSchema({
212   - field: SchemaFiled.KEYS,
213   - componentProps: {
214   - options: unref(deviceAttrs).map((item) => ({ label: item, value: item })),
215   - },
216   - });
217   - await method.setFieldsValue({ [SchemaFiled.DEVICE_ID]: value });
  224 + await method.setFieldsValue({ [SchemaFiled.DEVICE_ID]: record.value });
218 225 } catch (error) {}
219 226 }
220 227
... ... @@ -231,7 +238,7 @@
231 238 <section class="bg-white my-3 p-2">
232 239 <BasicForm @register="register" />
233 240 </section>
234   - <section class="bg-white p-3" style="min-hight: 350px">
  241 + <section class="bg-white p-3" style="min-height: 350px">
235 242 <div v-show="isNull" ref="chartRef" :style="{ height: '350px', width: '100%' }">
236 243 <Loading :loading="loading" :absolute="true" />
237 244 </div>
... ...
... ... @@ -4,16 +4,17 @@ import { FormSchema } from '/@/components/Form';
4 4 import { copyTransFun } from '/@/utils/fnUtils';
5 5 import { OnChangeHookParams } from '/@/components/Form/src/components/ApiSearchSelect.vue';
6 6 import { unref } from 'vue';
  7 +import { MasterDeviceList } from '/@/api/dataBoard/model';
7 8
8 9 export enum BasicConfigField {
9 10 NAME = 'name',
10 11 REMARK = 'remark',
11 12 }
12 13
13   -const getDeviceAttribute = async (deviceId: string) => {
  14 +const getDeviceAttribute = async (deviceProfileId: string) => {
14 15 try {
15   - const data = await getDeviceAttributes({ deviceId });
16   - if (data) return data.map((item) => ({ label: item, value: item }));
  16 + const data = await getDeviceAttributes({ deviceProfileId });
  17 + if (data) return data.map((item) => ({ label: item.name, value: item.identifier }));
17 18 } catch (error) {}
18 19 return [];
19 20 };
... ... @@ -23,6 +24,8 @@ export enum DataSourceField {
23 24 ORIGINATION_ID = 'organizationId',
24 25 DEVICE_ID = 'deviceId',
25 26 SLAVE_DEVICE_ID = 'slaveDeviceId',
  27 + DEVICE_PROFILE_ID = 'deviceProfileId',
  28 + SLAVE_DEVICE_PROFILE_ID = 'slaveDeviceProfileId',
26 29 ATTRIBUTE = 'attribute',
27 30 ATTRIBUTE_RENAME = 'attributeRename',
28 31 DEVICE_NAME = 'deviceName',
... ... @@ -86,6 +89,8 @@ export const dataSourceSchema: FormSchema[] = [
86 89 [DataSourceField.ATTRIBUTE]: null,
87 90 [DataSourceField.SLAVE_DEVICE_ID]: null,
88 91 [DataSourceField.IS_GATEWAY_DEVICE]: false,
  92 + [DataSourceField.DEVICE_PROFILE_ID]: null,
  93 + [DataSourceField.SLAVE_DEVICE_PROFILE_ID]: null,
89 94 });
90 95 },
91 96 getPopupContainer: () => document.body,
... ... @@ -93,6 +98,12 @@ export const dataSourceSchema: FormSchema[] = [
93 98 },
94 99 },
95 100 {
  101 + field: DataSourceField.DEVICE_PROFILE_ID,
  102 + component: 'Input',
  103 + label: '',
  104 + show: false,
  105 + },
  106 + {
96 107 field: DataSourceField.DEVICE_ID,
97 108 component: 'ApiSelect',
98 109 label: '设备',
... ... @@ -101,13 +112,19 @@ export const dataSourceSchema: FormSchema[] = [
101 112 componentProps({ formModel, formActionType }) {
102 113 const { setFieldsValue } = formActionType;
103 114 const organizationId = formModel[DataSourceField.ORIGINATION_ID];
  115 + const deviceId = formModel[DataSourceField.DEVICE_ID];
104 116 return {
105 117 api: async () => {
106 118 if (organizationId) {
107 119 try {
108 120 const data = await getAllDeviceByOrg(organizationId);
  121 + if (deviceId) {
  122 + const record = data.find((item) => item.id === deviceId);
  123 + setFieldsValue({ [DataSourceField.DEVICE_PROFILE_ID]: record?.deviceProfileId });
  124 + }
109 125 if (data)
110 126 return data.map((item) => ({
  127 + ...item,
111 128 label: item.name,
112 129 value: item.id,
113 130 deviceType: item.deviceType,
... ... @@ -116,11 +133,14 @@ export const dataSourceSchema: FormSchema[] = [
116 133 }
117 134 return [];
118 135 },
119   - onChange(_value, record: Record<'value' | 'label' | 'deviceType', string>) {
  136 +
  137 + onChange(_value, record: MasterDeviceList) {
120 138 setFieldsValue({
121 139 [DataSourceField.ATTRIBUTE]: null,
122 140 [DataSourceField.IS_GATEWAY_DEVICE]: record?.deviceType === 'GATEWAY',
  141 + [DataSourceField.DEVICE_PROFILE_ID]: record?.deviceProfileId,
123 142 [DataSourceField.SLAVE_DEVICE_ID]: null,
  143 + [DataSourceField.SLAVE_DEVICE_PROFILE_ID]: null,
124 144 [DataSourceField.DEVICE_NAME]: record?.label,
125 145 });
126 146 },
... ... @@ -130,6 +150,12 @@ export const dataSourceSchema: FormSchema[] = [
130 150 },
131 151 },
132 152 {
  153 + field: DataSourceField.SLAVE_DEVICE_PROFILE_ID,
  154 + component: 'Input',
  155 + label: '',
  156 + show: false,
  157 + },
  158 + {
133 159 field: DataSourceField.SLAVE_DEVICE_ID,
134 160 label: '网关子设备',
135 161 component: 'ApiSelect',
... ... @@ -146,13 +172,19 @@ export const dataSourceSchema: FormSchema[] = [
146 172 const organizationId = formModel[DataSourceField.ORIGINATION_ID];
147 173 const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE];
148 174 const deviceId = formModel[DataSourceField.DEVICE_ID];
  175 + const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID];
149 176 return {
150 177 api: async () => {
151 178 if (organizationId && isGatewayDevice) {
152 179 try {
153 180 const data = await getGatewaySlaveDevice({ organizationId, masterId: deviceId });
  181 + if (slaveDeviceId) {
  182 + const record = data.find((item) => item.id === slaveDeviceId);
  183 + setFieldsValue({ [DataSourceField.DEVICE_PROFILE_ID]: record?.deviceProfileId });
  184 + }
154 185 if (data)
155 186 return data.map((item) => ({
  187 + ...item,
156 188 label: item.name,
157 189 value: item.id,
158 190 deviceType: item.deviceType,
... ... @@ -161,9 +193,10 @@ export const dataSourceSchema: FormSchema[] = [
161 193 }
162 194 return [];
163 195 },
164   - onChange(_value, record: Record<'value' | 'label' | 'deviceType', string>) {
  196 + onChange(_value, record: MasterDeviceList) {
165 197 setFieldsValue({
166 198 [DataSourceField.ATTRIBUTE]: null,
  199 + [DataSourceField.SLAVE_DEVICE_PROFILE_ID]: record.deviceProfileId,
167 200 [DataSourceField.DEVICE_NAME]: record?.label,
168 201 });
169 202 },
... ... @@ -179,19 +212,21 @@ export const dataSourceSchema: FormSchema[] = [
179 212 colProps: { span: 8 },
180 213 rules: [{ required: true, message: '属性为必填项' }],
181 214 componentProps({ formModel }) {
182   - const organizationId = formModel[DataSourceField.ORIGINATION_ID];
183 215 const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE];
184 216 const deviceId = formModel[DataSourceField.DEVICE_ID];
  217 + const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
185 218 const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID];
  219 + const slaveDeviceProfileId = formModel[DataSourceField.SLAVE_DEVICE_PROFILE_ID];
  220 +
186 221 return {
187 222 api: async () => {
188   - if (organizationId && deviceId) {
  223 + if (deviceId) {
189 224 try {
190   - if (isGatewayDevice && slaveDeviceId) {
191   - return await getDeviceAttribute(slaveDeviceId);
  225 + if (isGatewayDevice && slaveDeviceId && slaveDeviceProfileId) {
  226 + return await getDeviceAttribute(slaveDeviceProfileId);
192 227 }
193   - if (!isGatewayDevice) {
194   - return await getDeviceAttribute(deviceId);
  228 + if (!isGatewayDevice && deviceProfileId) {
  229 + return await getDeviceAttribute(deviceProfileId);
195 230 }
196 231 } catch (error) {}
197 232 }
... ...
1 1 import moment from 'moment';
2 2 import { Moment } from 'moment';
3   -import { getDeviceAttributes } from '/@/api/dataBoard';
4 3 import { FormSchema } from '/@/components/Form';
5 4 import { ColEx } from '/@/components/Form/src/types';
6 5 import { useGridLayout } from '/@/hooks/component/useGridLayout';
... ... @@ -134,7 +133,6 @@ export const formSchema: FormSchema[] = [
134 133 field: SchemaFiled.AGG,
135 134 label: '数据聚合功能',
136 135 component: 'Select',
137   - // defaultValue: AggregateDataEnum.NONE,
138 136 componentProps: {
139 137 getPopupContainer: () => document.body,
140 138 options: [
... ... @@ -181,7 +179,6 @@ export const formSchema: FormSchema[] = [
181 179 field: SchemaFiled.LIMIT,
182 180 label: '最大条数',
183 181 component: 'InputNumber',
184   - // defaultValue: 7,
185 182 ifShow({ values }) {
186 183 return values[SchemaFiled.AGG] === AggregateDataEnum.NONE;
187 184 },
... ... @@ -201,34 +198,30 @@ export const formSchema: FormSchema[] = [
201 198 componentProps: {
202 199 getPopupContainer: () => document.body,
203 200 },
204   - // componentProps({ formModel }) {
205   - // const deviceId = formModel[SchemaFiled.DEVICE_ID];
206   - // return {
207   - // api: async () => {
208   - // if (deviceId) {
209   - // try {
210   - // try {
211   - // const data = await getDeviceAttributes({ deviceId });
212   - // if (data) return data.map((item) => ({ label: item, value: item }));
213   - // } catch (error) {}
214   - // return [];
215   - // } catch (error) {}
216   - // }
217   - // return [];
218   - // },
219   - // getPopupContainer: () => document.body,
220   - // };
221   - // },
222 201 },
223 202 ];
224 203
225   -// export const selectDeviceAttrSchema: FormSchema[] = [
226   -// {
227   -// field: 'keys',
228   -// label: '设备属性',
229   -// component: 'Select',
230   -// componentProps: {
231   -// getPopupContainer: () => document.body,
232   -// },
233   -// },
234   -// ];
  204 +export function getHistorySearchParams(value: Recordable) {
  205 + const { startTs, endTs, interval, agg, limit, way, keys, deviceId } = value;
  206 + if (way === QueryWay.LATEST) {
  207 + return {
  208 + keys,
  209 + entityId: deviceId,
  210 + startTs: moment().subtract(startTs, 'ms').valueOf(),
  211 + endTs: Date.now(),
  212 + interval,
  213 + agg,
  214 + limit,
  215 + };
  216 + } else {
  217 + return {
  218 + keys,
  219 + entityId: deviceId,
  220 + startTs: moment(startTs).valueOf(),
  221 + endTs: moment(endTs).valueOf(),
  222 + interval,
  223 + agg,
  224 + limit,
  225 + };
  226 + }
  227 +}
... ...
... ... @@ -119,7 +119,7 @@
119 119 data.width = newWPx;
120 120 data.height = newHPx;
121 121
122   - data.record.dataSource = data?.record.dataSource.map((item) => {
  122 + data.record.dataSource = data.record.dataSource.map((item) => {
123 123 if (!item.uuid) item.uuid = buildUUID();
124 124 return {
125 125 ...item,
... ... @@ -265,9 +265,9 @@
265 265 record: item,
266 266 };
267 267 });
268   -
269 268 beginSendMessage();
270 269 } catch (error) {
  270 + throw error;
271 271 } finally {
272 272 loading.value = false;
273 273 }
... ... @@ -310,7 +310,7 @@
310 310
311 311 const getComponentConfig = (
312 312 record: DataBoardLayoutInfo['record'],
313   - dataSourceRecord: DataSource
  313 + dataSourceRecord: DataSource | DataSource[]
314 314 ) => {
315 315 const frontComponent = record.frontId;
316 316 const component = frontComponentMap.get(frontComponent as FrontComponent);
... ... @@ -375,6 +375,10 @@
375 375 historyDataModalMethod.openModal(true, record);
376 376 };
377 377
  378 + const hasHistoryTrend = (item: DataBoardLayoutInfo) => {
  379 + return frontComponentMap.get(item.record.frontId as FrontComponent)?.hasHistoryTrend;
  380 + };
  381 +
378 382 onMounted(async () => {
379 383 await injectBaiDuMapLib();
380 384 await injectBaiDuMapTrackAniMationLib();
... ... @@ -442,6 +446,7 @@
442 446 <WidgetWrapper
443 447 :key="item.i"
444 448 :ref="(el: Element) => setComponentRef(el, item)"
  449 + :record="item.record"
445 450 :data-source="item.record.dataSource"
446 451 >
447 452 <template #header>
... ... @@ -455,7 +460,7 @@
455 460 <Tooltip title="趋势">
456 461 <img
457 462 :src="trendIcon"
458   - v-if="!getIsSharePage"
  463 + v-if="!getIsSharePage && hasHistoryTrend(item)"
459 464 class="cursor-pointer w-4.5 h-4.5"
460 465 @click="handleOpenHistroyDataModal(item.record.dataSource)"
461 466 />
... ...
... ... @@ -18,10 +18,13 @@ export interface ComponentConfig {
18 18 ComponentKey: FrontComponent;
19 19 ComponentConfig?: Recordable;
20 20 ComponentCategory: FrontComponentCategory;
  21 + isMultipleDataSource?: boolean;
  22 + hasHistoryTrend?: boolean;
  23 + hasSetting?: boolean;
21 24 transformConfig: (
22 25 componentConfig: Recordable,
23 26 record: DataComponentRecord,
24   - dataSourceRecord: DataSource
  27 + dataSourceRecord: DataSource | DataSource[]
25 28 ) => Recordable;
26 29 }
27 30
... ...