Commit ffa666b6b9dff088e40ed4a5ae92c42c6407020f

Authored by ww
1 parent 1fd30052

feat: basic implement data board map component

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