Commit fa8080ad9f58db95254f70f2d92f680ba578bdd7
Merge branch 'ww' into 'main'
fix: BUG in teambition && implement data component map comoponent && implement data component control comoponent See merge request huang/yun-teng-iot-front!407
Showing
40 changed files
with
1114 additions
and
656 deletions
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' }, |
src/api/alarm/position/model/index.ts
0 → 100644
@@ -4,9 +4,11 @@ import { | @@ -4,9 +4,11 @@ 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, |
11 | + SendCommandParams, | ||
10 | UpdateDataBoardLayoutParams, | 12 | UpdateDataBoardLayoutParams, |
11 | UpdateDataBoardParams, | 13 | UpdateDataBoardParams, |
12 | UpdateDataComponentParams, | 14 | UpdateDataComponentParams, |
@@ -32,10 +34,14 @@ enum DataBoardShareUrl { | @@ -32,10 +34,14 @@ enum DataBoardShareUrl { | ||
32 | GET_DATA_COMPONENT = '/noauth/share/data_board', | 34 | GET_DATA_COMPONENT = '/noauth/share/data_board', |
33 | } | 35 | } |
34 | 36 | ||
37 | +enum SendCommand { | ||
38 | + ONEWAY = '/plugins/rpc/oneway', | ||
39 | +} | ||
40 | + | ||
35 | enum DeviceUrl { | 41 | enum DeviceUrl { |
36 | GET_DEVICE = '/device/list/master', | 42 | GET_DEVICE = '/device/list/master', |
37 | GET_SLAVE_DEVICE = '/device/list/slave', | 43 | GET_SLAVE_DEVICE = '/device/list/slave', |
38 | - GET_DEVICE_ATTRIBUTE = '/plugins/telemetry', | 44 | + GET_DEVICE_ATTRIBUTE = '/device/attributes', |
39 | } | 45 | } |
40 | 46 | ||
41 | /** | 47 | /** |
@@ -184,14 +190,22 @@ export const getGatewaySlaveDevice = (params: { organizationId: string; masterId | @@ -184,14 +190,22 @@ export const getGatewaySlaveDevice = (params: { organizationId: string; masterId | ||
184 | * @param params | 190 | * @param params |
185 | * @returns | 191 | * @returns |
186 | */ | 192 | */ |
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`, | 193 | +export const getDeviceAttributes = (params: DeviceAttributeParams) => { |
194 | + const { deviceProfileId, dataType } = params; | ||
195 | + return defHttp.get<DeviceAttributeRecord[]>({ | ||
196 | + url: `${DeviceUrl.GET_DEVICE_ATTRIBUTE}/${deviceProfileId}`, | ||
197 | + params: { | ||
198 | + dataType, | ||
192 | }, | 199 | }, |
200 | + }); | ||
201 | +}; | ||
202 | + | ||
203 | +export const sendCommandOneway = (params: SendCommandParams) => { | ||
204 | + return defHttp.post( | ||
193 | { | 205 | { |
194 | - joinPrefix: false, | ||
195 | - } | 206 | + url: `${SendCommand.ONEWAY}/${params.deviceId}`, |
207 | + params: params.value, | ||
208 | + }, | ||
209 | + { joinPrefix: false } | ||
196 | ); | 210 | ); |
197 | }; | 211 | }; |
@@ -133,10 +133,31 @@ export interface UpdateDataComponentParams { | @@ -133,10 +133,31 @@ 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 | +} | ||
159 | + | ||
160 | +export interface SendCommandParams { | ||
161 | + deviceId: string; | ||
162 | + value: any; | ||
163 | +} |
@@ -34,6 +34,7 @@ | @@ -34,6 +34,7 @@ | ||
34 | let { width = 300, height = 150 } = unref(getOptions); | 34 | let { width = 300, height = 150 } = unref(getOptions); |
35 | width = isNumber(width) ? (`${width}px` as unknown as number) : width; | 35 | width = isNumber(width) ? (`${width}px` as unknown as number) : width; |
36 | height = isNumber(height) ? (`${height}px` as unknown as number) : height; | 36 | height = isNumber(height) ? (`${height}px` as unknown as number) : height; |
37 | + console.log({ getOptions }); | ||
37 | return { width, height } as CSSProperties; | 38 | return { width, height } as CSSProperties; |
38 | }); | 39 | }); |
39 | 40 |
@@ -10,7 +10,9 @@ | @@ -10,7 +10,9 @@ | ||
10 | :showOkBtn="false" | 10 | :showOkBtn="false" |
11 | @cancel="handleCancel" | 11 | @cancel="handleCancel" |
12 | > | 12 | > |
13 | - <div class="flex items-center justify-center bg-dark-900 w-full h-full min-h-52"> | 13 | + <div |
14 | + class="flex items-center justify-center bg-dark-900 w-full h-full min-h-52 video-container" | ||
15 | + > | ||
14 | <BasicVideoPlay v-if="showVideo" :options="options" /> | 16 | <BasicVideoPlay v-if="showVideo" :options="options" /> |
15 | </div> | 17 | </div> |
16 | </BasicModal> | 18 | </BasicModal> |
@@ -63,3 +65,13 @@ | @@ -63,3 +65,13 @@ | ||
63 | showVideo.value = false; | 65 | showVideo.value = false; |
64 | }; | 66 | }; |
65 | </script> | 67 | </script> |
68 | + | ||
69 | +<style lang="less" scoped> | ||
70 | + .video-container:deep(.vben-basic-video-play) { | ||
71 | + min-height: 13rem; | ||
72 | + } | ||
73 | + | ||
74 | + .video-container:deep(.video-js) { | ||
75 | + min-height: 13rem; | ||
76 | + } | ||
77 | +</style> |
@@ -61,6 +61,9 @@ | @@ -61,6 +61,9 @@ | ||
61 | 61 | ||
62 | for (const item of items) { | 62 | for (const item of items) { |
63 | (item as CameraRecordItem).isTransform = false; | 63 | (item as CameraRecordItem).isTransform = false; |
64 | + (item as CameraRecordItem).videoPlayerOptions = { | ||
65 | + ...basicVideoPlayOptions, | ||
66 | + }; | ||
64 | beforeVideoPlay(item); | 67 | beforeVideoPlay(item); |
65 | } | 68 | } |
66 | if (items.length < pagination.pageSize) { | 69 | if (items.length < pagination.pageSize) { |
@@ -117,6 +120,7 @@ | @@ -117,6 +120,7 @@ | ||
117 | const index = unref(cameraList).findIndex((item) => item.id === record.id); | 120 | const index = unref(cameraList).findIndex((item) => item.id === record.id); |
118 | if (~index) unref(cameraList)[index].isTransform = true; | 121 | if (~index) unref(cameraList)[index].isTransform = true; |
119 | } | 122 | } |
123 | + console.log(unref(cameraList)); | ||
120 | } | 124 | } |
121 | }; | 125 | }; |
122 | 126 |
@@ -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', |
@@ -57,7 +57,14 @@ | @@ -57,7 +57,14 @@ | ||
57 | v-show="!isNull" | 57 | v-show="!isNull" |
58 | /> | 58 | /> |
59 | </BasicModal> | 59 | </BasicModal> |
60 | - <DeviceDetailDrawer @register="registerDetailDrawer" /> | 60 | + <DeviceDetailDrawer |
61 | + @register="registerDetailDrawer" | ||
62 | + @open-tb-device-detail="handleOpenTbDeviceDetail" | ||
63 | + @open-gateway-device-detail="handleOpenGatewayDetail" | ||
64 | + /> | ||
65 | + <DeviceDetailDrawer @register="registerTbDetailDrawer" /> | ||
66 | + | ||
67 | + <DeviceDetailDrawer @register="registerGatewayDetailDrawer" /> | ||
61 | </div> | 68 | </div> |
62 | </template> | 69 | </template> |
63 | <script lang="ts"> | 70 | <script lang="ts"> |
@@ -94,6 +101,7 @@ | @@ -94,6 +101,7 @@ | ||
94 | import { QueryWay, SchemaFiled, AggregateDataEnum } from './cpns/TimePeriodForm/config'; | 101 | import { QueryWay, SchemaFiled, AggregateDataEnum } from './cpns/TimePeriodForm/config'; |
95 | import { dateUtil } from '/@/utils/dateUtil'; | 102 | import { dateUtil } from '/@/utils/dateUtil'; |
96 | import { Spin } from 'ant-design-vue'; | 103 | import { Spin } from 'ant-design-vue'; |
104 | + import { useAsyncQueue } from './useAsyncQueue'; | ||
97 | 105 | ||
98 | interface DeviceInfo { | 106 | interface DeviceInfo { |
99 | alarmStatus: 0 | 1; | 107 | alarmStatus: 0 | 1; |
@@ -139,6 +147,8 @@ | @@ -139,6 +147,8 @@ | ||
139 | const isNull = ref(true); | 147 | const isNull = ref(true); |
140 | const { toPromise } = useScript({ src: BAI_DU_MAP_URL }); | 148 | const { toPromise } = useScript({ src: BAI_DU_MAP_URL }); |
141 | const [registerDetailDrawer, { openDrawer }] = useDrawer(); | 149 | const [registerDetailDrawer, { openDrawer }] = useDrawer(); |
150 | + const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer(); | ||
151 | + const [registerGatewayDetailDrawer, { openDrawer: openGatewayDetailDrawer }] = useDrawer(); | ||
142 | const [registerModal, { openModal }] = useModal(); | 152 | const [registerModal, { openModal }] = useModal(); |
143 | const BMapInstance = ref<Nullable<any>>(null); | 153 | const BMapInstance = ref<Nullable<any>>(null); |
144 | 154 | ||
@@ -159,15 +169,31 @@ | @@ -159,15 +169,31 @@ | ||
159 | showSizeChanger: true, | 169 | showSizeChanger: true, |
160 | }, | 170 | }, |
161 | afterFetch: async (data: DeviceInfo[]) => { | 171 | afterFetch: async (data: DeviceInfo[]) => { |
172 | + if (!(window as any).BMap) { | ||
173 | + clearTask(); | ||
174 | + setTask(createMarket.bind(null, data)); | ||
175 | + return data; | ||
176 | + } | ||
162 | createMarket(data); | 177 | createMarket(data); |
163 | return data; | 178 | return data; |
164 | }, | 179 | }, |
165 | }); | 180 | }); |
181 | + const { setTask, clearTask, executeFlag } = useAsyncQueue(); | ||
182 | + | ||
183 | + let interval: Nullable<NodeJS.Timer> = setInterval(() => { | ||
184 | + if ((window as any).BMap) { | ||
185 | + executeFlag.value = true; | ||
186 | + clearInterval(interval!); | ||
187 | + interval = null; | ||
188 | + } | ||
189 | + }, 1000); | ||
166 | 190 | ||
167 | async function createMarket(data: DeviceInfo[]) { | 191 | async function createMarket(data: DeviceInfo[]) { |
168 | const BMap = (window as any).BMap; | 192 | const BMap = (window as any).BMap; |
169 | - if (!BMap) return; | ||
170 | - unref(BMapInstance).clearOverlays(); | 193 | + if (!BMap) { |
194 | + return; | ||
195 | + } | ||
196 | + unref(BMapInstance)?.clearOverlays(); | ||
171 | const markerList: MarkerList[] = []; | 197 | const markerList: MarkerList[] = []; |
172 | data.forEach((item) => { | 198 | data.forEach((item) => { |
173 | const { | 199 | const { |
@@ -242,6 +268,7 @@ | @@ -242,6 +268,7 @@ | ||
242 | }; | 268 | }; |
243 | const { name, organizationDTO, deviceState, deviceProfile, deviceType } = record; | 269 | const { name, organizationDTO, deviceState, deviceProfile, deviceType } = record; |
244 | const { address, longitude, latitude } = record.deviceInfo; | 270 | const { address, longitude, latitude } = record.deviceInfo; |
271 | + | ||
245 | // 创建信息窗口对象 | 272 | // 创建信息窗口对象 |
246 | const res = await getDeviceActiveTime(entityId); | 273 | const res = await getDeviceActiveTime(entityId); |
247 | 274 | ||
@@ -454,6 +481,14 @@ | @@ -454,6 +481,14 @@ | ||
454 | (window as any).openHistoryModal = null; | 481 | (window as any).openHistoryModal = null; |
455 | }); | 482 | }); |
456 | 483 | ||
484 | + const handleOpenTbDeviceDetail = (data: { id: string; tbDeviceId: string }) => { | ||
485 | + openTbDeviceDrawer(true, data); | ||
486 | + }; | ||
487 | + | ||
488 | + const handleOpenGatewayDetail = (data: { id: string; tbDeviceId: string }) => { | ||
489 | + openGatewayDetailDrawer(true, data); | ||
490 | + }; | ||
491 | + | ||
457 | return { | 492 | return { |
458 | wrapRef, | 493 | wrapRef, |
459 | registerTable, | 494 | registerTable, |
@@ -467,6 +502,10 @@ | @@ -467,6 +502,10 @@ | ||
467 | spinning, | 502 | spinning, |
468 | timePeriodRegister, | 503 | timePeriodRegister, |
469 | handleCancelModal, | 504 | handleCancelModal, |
505 | + registerTbDetailDrawer, | ||
506 | + registerGatewayDetailDrawer, | ||
507 | + handleOpenTbDeviceDetail, | ||
508 | + handleOpenGatewayDetail, | ||
470 | }; | 509 | }; |
471 | }, | 510 | }, |
472 | }); | 511 | }); |
src/views/device/localtion/useAsyncQueue.ts
0 → 100644
1 | +import { ref, watchEffect } from 'vue'; | ||
2 | + | ||
3 | +export function useAsyncQueue() { | ||
4 | + const queue: Fn<any, Promise<any>>[] = []; | ||
5 | + | ||
6 | + const executeFlag = ref(false); | ||
7 | + | ||
8 | + const setTask = (fn: Fn<any, Promise<any>>) => { | ||
9 | + queue.push(fn); | ||
10 | + }; | ||
11 | + | ||
12 | + const removeTask = (fn: Fn<any, Promise<any>>) => { | ||
13 | + const index = queue.findIndex((item) => item === fn); | ||
14 | + ~index && queue.splice(index, 1); | ||
15 | + }; | ||
16 | + | ||
17 | + const clearTask = () => { | ||
18 | + queue.length = 0; | ||
19 | + }; | ||
20 | + | ||
21 | + watchEffect(() => { | ||
22 | + if (executeFlag.value) { | ||
23 | + queue.forEach((item) => item()); | ||
24 | + executeFlag.value = false; | ||
25 | + } | ||
26 | + }); | ||
27 | + | ||
28 | + return { setTask, removeTask, clearTask, executeFlag }; | ||
29 | +} |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | import DeviceConfigurationStep from './step/DeviceConfigurationStep.vue'; | 28 | import DeviceConfigurationStep from './step/DeviceConfigurationStep.vue'; |
29 | import TransportConfigurationStep from './step/TransportConfigurationStep.vue'; | 29 | import TransportConfigurationStep from './step/TransportConfigurationStep.vue'; |
30 | import PhysicalModelManagementStep from './step/PhysicalModelManagementStep.vue'; | 30 | import PhysicalModelManagementStep from './step/PhysicalModelManagementStep.vue'; |
31 | - import { ref, unref } from 'vue'; | 31 | + import { nextTick, ref, unref } from 'vue'; |
32 | import { deviceConfigGetDetail } from '/@/api/device/deviceConfigApi'; | 32 | import { deviceConfigGetDetail } from '/@/api/device/deviceConfigApi'; |
33 | import { DeviceRecord } from '/@/api/device/model/deviceModel'; | 33 | import { DeviceRecord } from '/@/api/device/model/deviceModel'; |
34 | import TopicPanel from './step/TopicPanel.vue'; | 34 | import TopicPanel from './step/TopicPanel.vue'; |
@@ -49,6 +49,7 @@ | @@ -49,6 +49,7 @@ | ||
49 | unref(DevConStRef)?.setFormData(res); | 49 | unref(DevConStRef)?.setFormData(res); |
50 | }; | 50 | }; |
51 | const setTransConfFormData = async (res: Recordable) => { | 51 | const setTransConfFormData = async (res: Recordable) => { |
52 | + await nextTick(); | ||
52 | unref(TransConStRef)?.setFormData(res); | 53 | unref(TransConStRef)?.setFormData(res); |
53 | }; | 54 | }; |
54 | 55 |
@@ -9,6 +9,13 @@ import { FormField, FunctionType } from './step/cpns/physical/cpns/config'; | @@ -9,6 +9,13 @@ import { FormField, FunctionType } from './step/cpns/physical/cpns/config'; | ||
9 | import { h } from 'vue'; | 9 | import { h } from 'vue'; |
10 | import { Tag } from 'ant-design-vue'; | 10 | import { Tag } from 'ant-design-vue'; |
11 | 11 | ||
12 | +export enum ModelOfMatterPermission { | ||
13 | + CREATE = 'api:yt:things_model:post', | ||
14 | + UPDATE = 'api:yt:things_model:put', | ||
15 | + DELETE = 'api:yt:things_model:delete', | ||
16 | + RELEASE = 'api:yt:things_model:release', | ||
17 | +} | ||
18 | + | ||
12 | export const steps = [ | 19 | export const steps = [ |
13 | { | 20 | { |
14 | title: '产品', | 21 | title: '产品', |
@@ -27,18 +27,16 @@ | @@ -27,18 +27,16 @@ | ||
27 | </div> | 27 | </div> |
28 | <div class="flex justify-between items-end"> | 28 | <div class="flex justify-between items-end"> |
29 | <div class="flex gap-2"> | 29 | <div class="flex gap-2"> |
30 | - <Authority value=""> | 30 | + <Authority :value="ModelOfMatterPermission.CREATE"> |
31 | <Button v-if="isShowBtn" type="primary" @click="handleCreateOrEdit()"> | 31 | <Button v-if="isShowBtn" type="primary" @click="handleCreateOrEdit()"> |
32 | 新增物模型 | 32 | 新增物模型 |
33 | </Button> | 33 | </Button> |
34 | - <Button type="primary" @click="handleOpenTsl"> 物模型TSL </Button> | ||
35 | - <Button v-if="isShowBtn" type="primary" @click="handleImportModel" | ||
36 | - >导入物模型</Button | ||
37 | - > | ||
38 | </Authority> | 34 | </Authority> |
35 | + <Button type="primary" @click="handleOpenTsl"> 物模型TSL </Button> | ||
36 | + <Button v-if="isShowBtn" type="primary" @click="handleImportModel">导入物模型</Button> | ||
39 | </div> | 37 | </div> |
40 | <div class="flex gap-2"> | 38 | <div class="flex gap-2"> |
41 | - <Authority value=""> | 39 | + <Authority :value="ModelOfMatterPermission.RELEASE"> |
42 | <Popconfirm | 40 | <Popconfirm |
43 | title="是否需要发布上线?" | 41 | title="是否需要发布上线?" |
44 | ok-text="确定" | 42 | ok-text="确定" |
@@ -49,9 +47,12 @@ | @@ -49,9 +47,12 @@ | ||
49 | 发布上线 | 47 | 发布上线 |
50 | </Button> | 48 | </Button> |
51 | </Popconfirm> | 49 | </Popconfirm> |
52 | - <Button v-if="isShowBtn" class="!bg-gray-200" type="text" @click="handleReturn"> | ||
53 | - 返回 | ||
54 | - </Button> | 50 | + </Authority> |
51 | + | ||
52 | + <Button v-if="isShowBtn" class="!bg-gray-200" type="text" @click="handleReturn"> | ||
53 | + 返回 | ||
54 | + </Button> | ||
55 | + <Authority :value="ModelOfMatterPermission.DELETE"> | ||
55 | <Popconfirm | 56 | <Popconfirm |
56 | title="您确定要批量删除数据" | 57 | title="您确定要批量删除数据" |
57 | ok-text="确定" | 58 | ok-text="确定" |
@@ -84,14 +85,14 @@ | @@ -84,14 +85,14 @@ | ||
84 | { | 85 | { |
85 | label: '编辑', | 86 | label: '编辑', |
86 | icon: 'clarity:note-edit-line', | 87 | icon: 'clarity:note-edit-line', |
87 | - auth: '', | 88 | + auth: ModelOfMatterPermission.UPDATE, |
88 | onClick: handleCreateOrEdit.bind(null, record), | 89 | onClick: handleCreateOrEdit.bind(null, record), |
89 | ifShow: !isShowBtn ? false : true, | 90 | ifShow: !isShowBtn ? false : true, |
90 | }, | 91 | }, |
91 | { | 92 | { |
92 | label: '删除', | 93 | label: '删除', |
93 | icon: 'ant-design:delete-outlined', | 94 | icon: 'ant-design:delete-outlined', |
94 | - auth: '', | 95 | + auth: ModelOfMatterPermission.DELETE, |
95 | color: 'error', | 96 | color: 'error', |
96 | ifShow: !isShowBtn ? false : true, | 97 | ifShow: !isShowBtn ? false : true, |
97 | popConfirm: { | 98 | popConfirm: { |
@@ -114,7 +115,11 @@ | @@ -114,7 +115,11 @@ | ||
114 | <script lang="ts" setup> | 115 | <script lang="ts" setup> |
115 | import { BasicTable, useTable, TableAction } from '/@/components/Table'; | 116 | import { BasicTable, useTable, TableAction } from '/@/components/Table'; |
116 | import { useModal } from '/@/components/Modal'; | 117 | import { useModal } from '/@/components/Modal'; |
117 | - import { modelOfMatterForm, physicalColumn } from '../device.profile.data'; | 118 | + import { |
119 | + modelOfMatterForm, | ||
120 | + ModelOfMatterPermission, | ||
121 | + physicalColumn, | ||
122 | + } from '../device.profile.data'; | ||
118 | import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; | 123 | import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; |
119 | import { Authority } from '/@/components/Authority'; | 124 | import { Authority } from '/@/components/Authority'; |
120 | import PhysicalModelModal from './cpns/physical/PhysicalModelModal.vue'; | 125 | import PhysicalModelModal from './cpns/physical/PhysicalModelModal.vue'; |
@@ -78,11 +78,12 @@ | @@ -78,11 +78,12 @@ | ||
78 | submitOnReset: false, | 78 | submitOnReset: false, |
79 | showActionButtonGroup: props.ifShowBtn ? true : false, | 79 | showActionButtonGroup: props.ifShowBtn ? true : false, |
80 | }); | 80 | }); |
81 | - const setFormData = (v) => { | 81 | + const setFormData = async (v) => { |
82 | setFieldsValue({ | 82 | setFieldsValue({ |
83 | transportType: v?.profileData?.transportConfiguration?.type, | 83 | transportType: v?.profileData?.transportConfiguration?.type, |
84 | }); | 84 | }); |
85 | isMqttType.value = v?.profileData?.transportConfiguration?.type; | 85 | isMqttType.value = v?.profileData?.transportConfiguration?.type; |
86 | + await nextTick(); | ||
86 | mqttRef.value?.setFormData(v?.profileData?.transportConfiguration); | 87 | mqttRef.value?.setFormData(v?.profileData?.transportConfiguration); |
87 | coapRef.value?.setFormData(v?.profileData?.transportConfiguration); | 88 | coapRef.value?.setFormData(v?.profileData?.transportConfiguration); |
88 | lwm2mRef.value?.setFormData(v?.profileData?.transportConfiguration); | 89 | lwm2mRef.value?.setFormData(v?.profileData?.transportConfiguration); |
@@ -5,8 +5,10 @@ | @@ -5,8 +5,10 @@ | ||
5 | </script> | 5 | </script> |
6 | <script lang="ts" setup> | 6 | <script lang="ts" setup> |
7 | import { RadioRecord } from '../../detail/config/util'; | 7 | import { RadioRecord } from '../../detail/config/util'; |
8 | + import { ControlComponentDefaultConfig, ControlComponentValue } from './control.config'; | ||
9 | + import { useSendCommand } from './useSendCommand'; | ||
8 | 10 | ||
9 | - interface VisualComponentProps<Layout = Recordable, Value = Recordable> { | 11 | + interface VisualComponentProps<Layout = Recordable, Value = ControlComponentValue> { |
10 | value?: Value; | 12 | value?: Value; |
11 | layout?: Layout; | 13 | layout?: Layout; |
12 | radio?: RadioRecord; | 14 | radio?: RadioRecord; |
@@ -15,29 +17,40 @@ | @@ -15,29 +17,40 @@ | ||
15 | update?: () => void; | 17 | update?: () => void; |
16 | remove?: (key: string) => void; | 18 | remove?: (key: string) => void; |
17 | } | 19 | } |
20 | + | ||
18 | const props = defineProps<VisualComponentProps>(); | 21 | const props = defineProps<VisualComponentProps>(); |
19 | 22 | ||
20 | const emit = defineEmits(['update:value', 'change']); | 23 | const emit = defineEmits(['update:value', 'change']); |
21 | 24 | ||
25 | + const { sendCommand } = useSendCommand(); | ||
22 | const handleChange = (event: Event) => { | 26 | const handleChange = (event: Event) => { |
23 | const _value = (event.target as HTMLInputElement).checked; | 27 | const _value = (event.target as HTMLInputElement).checked; |
24 | emit('update:value', _value); | 28 | emit('update:value', _value); |
25 | emit('change', _value); | 29 | emit('change', _value); |
30 | + sendCommand(props.value?.slaveDeviceId || props.value?.deviceId, _value); | ||
26 | }; | 31 | }; |
27 | </script> | 32 | </script> |
28 | 33 | ||
29 | <template> | 34 | <template> |
30 | - <label class="sliding-switch"> | ||
31 | - <input | ||
32 | - :value="props.value?.value" | ||
33 | - type="checkbox" | ||
34 | - :checked="props.value?.value" | ||
35 | - @change="handleChange" | ||
36 | - /> | ||
37 | - <span class="slider"></span> | ||
38 | - <span class="on">ON</span> | ||
39 | - <span class="off">OFF</span> | ||
40 | - </label> | 35 | + <div class="flex flex-col justify-center"> |
36 | + <label class="sliding-switch"> | ||
37 | + <input | ||
38 | + :value="props.value?.value" | ||
39 | + type="checkbox" | ||
40 | + :checked="props.value?.value" | ||
41 | + @change="handleChange" | ||
42 | + /> | ||
43 | + <span class="slider"></span> | ||
44 | + <span class="on">ON</span> | ||
45 | + <span class="off">OFF</span> | ||
46 | + </label> | ||
47 | + <div | ||
48 | + class="text-center mt-2 text-gray-700" | ||
49 | + :style="{ color: props?.value?.fontColor || ControlComponentDefaultConfig.fontColor }" | ||
50 | + > | ||
51 | + {{ props.value?.attributeRename || props.value?.attribute }}</div | ||
52 | + > | ||
53 | + </div> | ||
41 | </template> | 54 | </template> |
42 | 55 | ||
43 | <style scoped lang="less"> | 56 | <style scoped lang="less"> |
@@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
5 | </script> | 5 | </script> |
6 | <script lang="ts" setup> | 6 | <script lang="ts" setup> |
7 | import { Switch } from 'ant-design-vue'; | 7 | import { Switch } from 'ant-design-vue'; |
8 | - import { computed } from 'vue'; | 8 | + import { computed, ref, watchEffect } from 'vue'; |
9 | import { DEFAULT_RADIO_RECORD, fontSize, RadioRecord } from '../../detail/config/util'; | 9 | import { DEFAULT_RADIO_RECORD, fontSize, RadioRecord } from '../../detail/config/util'; |
10 | import SvgIcon from '/@/components/Icon/src/SvgIcon.vue'; | 10 | import SvgIcon from '/@/components/Icon/src/SvgIcon.vue'; |
11 | import { | 11 | import { |
@@ -13,6 +13,7 @@ | @@ -13,6 +13,7 @@ | ||
13 | ControlComponentValue, | 13 | ControlComponentValue, |
14 | ControlComponentLayout, | 14 | ControlComponentLayout, |
15 | } from './control.config'; | 15 | } from './control.config'; |
16 | + import { useSendCommand } from './useSendCommand'; | ||
16 | const props = withDefaults( | 17 | const props = withDefaults( |
17 | defineProps<{ | 18 | defineProps<{ |
18 | layout?: ControlComponentLayout; | 19 | layout?: ControlComponentLayout; |
@@ -26,6 +27,18 @@ | @@ -26,6 +27,18 @@ | ||
26 | const getRadio = computed(() => { | 27 | const getRadio = computed(() => { |
27 | return props.radio || DEFAULT_RADIO_RECORD; | 28 | return props.radio || DEFAULT_RADIO_RECORD; |
28 | }); | 29 | }); |
30 | + | ||
31 | + const checked = ref(!!Number(props.value.value)); | ||
32 | + | ||
33 | + const { sendCommand } = useSendCommand(); | ||
34 | + const handleChange = (value: boolean) => { | ||
35 | + console.log(props.value); | ||
36 | + sendCommand(props.value.slaveDeviceId! || props.value.deviceId!, value); | ||
37 | + }; | ||
38 | + | ||
39 | + watchEffect(() => { | ||
40 | + checked.value = !!Number(props.value.value); | ||
41 | + }); | ||
29 | </script> | 42 | </script> |
30 | 43 | ||
31 | <template> | 44 | <template> |
@@ -40,8 +53,13 @@ | @@ -40,8 +53,13 @@ | ||
40 | height: fontSize({ radioRecord: getRadio, basic: 30, min: 16 }), | 53 | height: fontSize({ radioRecord: getRadio, basic: 30, min: 16 }), |
41 | }" | 54 | }" |
42 | /> | 55 | /> |
43 | - <span class="flex-auto mx-4 flex items-center truncate inline-block">属性名</span> | 56 | + <span |
57 | + class="flex-auto mx-4 flex items-center truncate inline-block text-gray-700" | ||
58 | + :style="{ color: props.value.fontColor || ControlComponentDefaultConfig.fontColor }" | ||
59 | + > | ||
60 | + {{ props.value.attributeRename || props.value.attribute }} | ||
61 | + </span> | ||
44 | </div> | 62 | </div> |
45 | - <Switch /> | 63 | + <Switch v-model:checked="checked" @change="handleChange" /> |
46 | </div> | 64 | </div> |
47 | </template> | 65 | </template> |
@@ -6,7 +6,8 @@ | @@ -6,7 +6,8 @@ | ||
6 | <script lang="ts" setup> | 6 | <script lang="ts" setup> |
7 | import { computed } from '@vue/reactivity'; | 7 | import { computed } from '@vue/reactivity'; |
8 | import { DEFAULT_RADIO_RECORD, fontSize, RadioRecord } from '../../detail/config/util'; | 8 | import { DEFAULT_RADIO_RECORD, fontSize, RadioRecord } from '../../detail/config/util'; |
9 | - import { ControlComponentValue } from './control.config'; | 9 | + import { ControlComponentDefaultConfig, ControlComponentValue } from './control.config'; |
10 | + import { useSendCommand } from './useSendCommand'; | ||
10 | 11 | ||
11 | const props = defineProps<{ | 12 | const props = defineProps<{ |
12 | value?: ControlComponentValue; | 13 | value?: ControlComponentValue; |
@@ -16,10 +17,12 @@ | @@ -16,10 +17,12 @@ | ||
16 | 17 | ||
17 | const emit = defineEmits(['update:value', 'change']); | 18 | const emit = defineEmits(['update:value', 'change']); |
18 | 19 | ||
20 | + const { sendCommand } = useSendCommand(); | ||
19 | const handleChange = (event: Event) => { | 21 | const handleChange = (event: Event) => { |
20 | const _value = (event.target as HTMLInputElement).checked; | 22 | const _value = (event.target as HTMLInputElement).checked; |
21 | emit('update:value', _value); | 23 | emit('update:value', _value); |
22 | emit('change', _value); | 24 | emit('change', _value); |
25 | + sendCommand(props.value?.slaveDeviceId || props.value?.deviceId, _value); | ||
23 | }; | 26 | }; |
24 | 27 | ||
25 | const getRadio = computed(() => { | 28 | const getRadio = computed(() => { |
@@ -28,28 +31,36 @@ | @@ -28,28 +31,36 @@ | ||
28 | </script> | 31 | </script> |
29 | 32 | ||
30 | <template> | 33 | <template> |
31 | - <div | ||
32 | - class="toggle-switch" | ||
33 | - :style="{ | ||
34 | - width: fontSize({ radioRecord: getRadio, basic: 75, max: 75, min: 60 }), | ||
35 | - height: fontSize({ radioRecord: getRadio, basic: 97.5, max: 97.5, min: 80 }), | ||
36 | - }" | ||
37 | - > | ||
38 | - <label class="switch"> | ||
39 | - <input | ||
40 | - :value="props.value?.value" | ||
41 | - type="checkbox" | ||
42 | - :checked="props.value?.value" | ||
43 | - @change="handleChange" | ||
44 | - /> | ||
45 | - <div class="button"> | ||
46 | - <div class="light"></div> | ||
47 | - <div class="dots"></div> | ||
48 | - <div class="characters"></div> | ||
49 | - <div class="shine"></div> | ||
50 | - <div class="shadow"></div> | ||
51 | - </div> | ||
52 | - </label> | 34 | + <div class="flex flex-col"> |
35 | + <div | ||
36 | + class="toggle-switch" | ||
37 | + :style="{ | ||
38 | + width: fontSize({ radioRecord: getRadio, basic: 75, max: 75, min: 60 }), | ||
39 | + height: fontSize({ radioRecord: getRadio, basic: 97.5, max: 97.5, min: 80 }), | ||
40 | + }" | ||
41 | + > | ||
42 | + <label class="switch"> | ||
43 | + <input | ||
44 | + :value="props.value?.value" | ||
45 | + type="checkbox" | ||
46 | + :checked="props.value?.value" | ||
47 | + @change="handleChange" | ||
48 | + /> | ||
49 | + <div class="button"> | ||
50 | + <div class="light"></div> | ||
51 | + <div class="dots"></div> | ||
52 | + <div class="characters"></div> | ||
53 | + <div class="shine"></div> | ||
54 | + <div class="shadow"></div> | ||
55 | + </div> | ||
56 | + </label> | ||
57 | + </div> | ||
58 | + <div | ||
59 | + class="text-center mt-2 text-gray-700" | ||
60 | + :style="{ color: props?.value?.fontColor || ControlComponentDefaultConfig.fontColor }" | ||
61 | + > | ||
62 | + {{ props.value?.attributeRename || props.value?.attribute }}</div | ||
63 | + > | ||
53 | </div> | 64 | </div> |
54 | </template> | 65 | </template> |
55 | 66 |
@@ -6,14 +6,19 @@ export interface ControlComponentLayout { | @@ -6,14 +6,19 @@ export interface ControlComponentLayout { | ||
6 | 6 | ||
7 | export interface ControlComponentValue { | 7 | export interface ControlComponentValue { |
8 | value?: boolean; | 8 | value?: boolean; |
9 | - name?: string; | 9 | + attribute?: string; |
10 | + attributeRename?: string; | ||
10 | icon?: string; | 11 | icon?: string; |
11 | iconColor?: string; | 12 | iconColor?: string; |
13 | + deviceId?: string; | ||
14 | + fontColor?: string; | ||
15 | + slaveDeviceId?: string; | ||
12 | } | 16 | } |
13 | 17 | ||
14 | export const ControlComponentDefaultConfig: ControlComponentValue = { | 18 | export const ControlComponentDefaultConfig: ControlComponentValue = { |
15 | icon: 'shuiwen', | 19 | icon: 'shuiwen', |
16 | iconColor: '#367BFF', | 20 | iconColor: '#367BFF', |
21 | + fontColor: '#000', | ||
17 | }; | 22 | }; |
18 | 23 | ||
19 | export const transformControlConfig = ( | 24 | export const transformControlConfig = ( |
@@ -23,8 +28,11 @@ export const transformControlConfig = ( | @@ -23,8 +28,11 @@ export const transformControlConfig = ( | ||
23 | ) => { | 28 | ) => { |
24 | return { | 29 | return { |
25 | value: { | 30 | value: { |
26 | - value: dataSourceRecord.componentInfo.value, | ||
27 | - icon: dataSourceRecord.componentInfo.icon, | 31 | + ...dataSourceRecord.componentInfo, |
32 | + attribute: dataSourceRecord.attribute, | ||
33 | + attributeRename: dataSourceRecord.attributeRename, | ||
34 | + deviceId: dataSourceRecord.deviceId, | ||
35 | + slaveDeviceId: dataSourceRecord.slaveDeviceId, | ||
28 | } as ControlComponentValue, | 36 | } as ControlComponentValue, |
29 | }; | 37 | }; |
30 | }; | 38 | }; |
1 | +import { sendCommandOneway } from '/@/api/dataBoard'; | ||
2 | +import { useMessage } from '/@/hooks/web/useMessage'; | ||
3 | + | ||
4 | +const { createMessage } = useMessage(); | ||
5 | +export function useSendCommand() { | ||
6 | + const sendCommand = async (deviceId: string, value: any) => { | ||
7 | + if (!deviceId) return; | ||
8 | + try { | ||
9 | + await sendCommandOneway({ | ||
10 | + deviceId, | ||
11 | + value: { | ||
12 | + params: Number(value), | ||
13 | + persistent: true, | ||
14 | + additionalInfo: { | ||
15 | + cmdType: 'API', | ||
16 | + }, | ||
17 | + method: 'methodThingskit', | ||
18 | + }, | ||
19 | + }); | ||
20 | + createMessage.success('命令下发成功'); | ||
21 | + } catch (error) {} | ||
22 | + }; | ||
23 | + return { | ||
24 | + sendCommand, | ||
25 | + }; | ||
26 | +} |
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 | ||
115 | + title="历史轨迹" | ||
116 | + @register="registerModal" | ||
117 | + @ok="handleOk" | ||
118 | + :ok-button-props="{ loading }" | ||
119 | + > | ||
120 | + <BasicForm @register="registerForm" /> | ||
121 | + </BasicModal> | ||
122 | +</template> |
1 | <script lang="ts"> | 1 | <script lang="ts"> |
2 | export default { | 2 | export default { |
3 | + components: { Spin }, | ||
3 | inheritAttrs: false, | 4 | inheritAttrs: false, |
4 | }; | 5 | }; |
5 | </script> | 6 | </script> |
6 | <script lang="ts" setup> | 7 | <script lang="ts" setup> |
7 | - import { computed, onMounted, ref, unref } from 'vue'; | 8 | + import { computed, onMounted, reactive, ref, unref, watchEffect } from 'vue'; |
8 | import { RadioRecord } from '../../detail/config/util'; | 9 | import { RadioRecord } from '../../detail/config/util'; |
9 | import { MapComponentLayout, MapComponentValue } from './map.config'; | 10 | import { MapComponentLayout, MapComponentValue } from './map.config'; |
10 | import { | 11 | import { |
@@ -12,11 +13,18 @@ | @@ -12,11 +13,18 @@ | ||
12 | PlayCircleOutlined, | 13 | PlayCircleOutlined, |
13 | PauseCircleOutlined, | 14 | PauseCircleOutlined, |
14 | } from '@ant-design/icons-vue'; | 15 | } from '@ant-design/icons-vue'; |
15 | - import { Button, Tooltip } from 'ant-design-vue'; | 16 | + import { Button, Spin, Tooltip } from 'ant-design-vue'; |
16 | import { FrontComponent } from '../../const/const'; | 17 | import { FrontComponent } from '../../const/const'; |
17 | import { buildUUID } from '/@/utils/uuid'; | 18 | import { buildUUID } from '/@/utils/uuid'; |
19 | + import { useModal } from '/@/components/Modal'; | ||
20 | + import HistoryDataModel from './HistoryDataModel.vue'; | ||
21 | + import { HistoryModalOkEmitParams, HistoryModalParams } from './type'; | ||
22 | + import { formatToDateTime } from '/@/utils/dateUtil'; | ||
23 | + import { isEqual } from 'lodash-es'; | ||
24 | + import { useAsyncQueue } from '/@/views/device/localtion/useAsyncQueue'; | ||
18 | 25 | ||
19 | // useVisualBoardContext(); | 26 | // useVisualBoardContext(); |
27 | + type TrackRecord = Record<'lng' | 'lat' | 'ts', number>; | ||
20 | 28 | ||
21 | const startMethodName = `trackPlayMethod_${buildUUID()}`; | 29 | const startMethodName = `trackPlayMethod_${buildUUID()}`; |
22 | 30 | ||
@@ -44,19 +52,52 @@ | @@ -44,19 +52,52 @@ | ||
44 | const trackAni = ref<Nullable<any>>(null); | 52 | const trackAni = ref<Nullable<any>>(null); |
45 | let mapInstance: Nullable<Recordable> = null; | 53 | let mapInstance: Nullable<Recordable> = null; |
46 | 54 | ||
55 | + const trackList = ref<TrackRecord[]>([]); | ||
56 | + | ||
57 | + watchEffect(() => { | ||
58 | + if ( | ||
59 | + props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_REAL && | ||
60 | + props.value?.track?.length | ||
61 | + ) { | ||
62 | + const lng = props.value.track.at(0)!; | ||
63 | + const lat = props.value.track.at(1)!; | ||
64 | + const record = { | ||
65 | + lng: lng.value, | ||
66 | + lat: lat.value, | ||
67 | + ts: lng.ts, | ||
68 | + }; | ||
69 | + if (unref(trackList).length && isEqual(unref(trackList).at(-1), record)) return; | ||
70 | + trackList.value.push(record); | ||
71 | + | ||
72 | + randomAnimation(unref(trackList)); | ||
73 | + // marketPoint(record); | ||
74 | + } | ||
75 | + }); | ||
76 | + | ||
77 | + // function marketPoint(params: Record<'lng' | 'lat', number>) { | ||
78 | + // const { lng, lat } = params; | ||
79 | + // const BMap = (window as any).BMapGL; | ||
80 | + // const marker = new BMap.Marker(new BMap.Point(lng, lat)); | ||
81 | + // unref(mapInstance)?.centerAndZoom(new BMap.Point(lng, lat)); | ||
82 | + // unref(mapInstance)?.addOverlay(marker); | ||
83 | + // } | ||
84 | + | ||
85 | + const prepare = ref(false); | ||
47 | async function initMap() { | 86 | async function initMap() { |
48 | const wrapEl = unref(wrapRef); | 87 | const wrapEl = unref(wrapRef); |
49 | if (!wrapEl) return; | 88 | if (!wrapEl) return; |
50 | const BMapGL = (window as any).BMapGL; | 89 | const BMapGL = (window as any).BMapGL; |
51 | mapInstance = new BMapGL.Map(wrapId); | 90 | mapInstance = new BMapGL.Map(wrapId); |
52 | - const point = new BMapGL.Point(116.404, 39.915); | 91 | + const point = new BMapGL.Point(104.09457, 30.53189); |
53 | mapInstance!.centerAndZoom(point, 15); | 92 | mapInstance!.centerAndZoom(point, 15); |
54 | mapInstance!.enableScrollWheelZoom(true); | 93 | mapInstance!.enableScrollWheelZoom(true); |
55 | - props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_HISTORY && randomAnimation(); | 94 | + props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_HISTORY && |
95 | + props.random && | ||
96 | + randomAnimation(); | ||
56 | } | 97 | } |
57 | 98 | ||
58 | - const randomAnimation = () => { | ||
59 | - const path = [ | 99 | + const randomAnimation = (path?: Record<'lng' | 'lat', number>[], clearOverlays = true) => { |
100 | + path = path || [ | ||
60 | { | 101 | { |
61 | lng: 116.297611, | 102 | lng: 116.297611, |
62 | lat: 40.047363, | 103 | lat: 40.047363, |
@@ -90,6 +131,8 @@ | @@ -90,6 +131,8 @@ | ||
90 | const point: any[] = []; | 131 | const point: any[] = []; |
91 | const BMapGL = (window as any).BMapGL; | 132 | const BMapGL = (window as any).BMapGL; |
92 | 133 | ||
134 | + clearOverlays && unref(mapInstance)?.clearOverlays(); | ||
135 | + | ||
93 | for (const { lng, lat } of path) { | 136 | for (const { lng, lat } of path) { |
94 | point.push(new BMapGL.Point(lng, lat)); | 137 | point.push(new BMapGL.Point(lng, lat)); |
95 | } | 138 | } |
@@ -99,10 +142,10 @@ | @@ -99,10 +142,10 @@ | ||
99 | 142 | ||
100 | const dynamicPlayMethod = { | 143 | const dynamicPlayMethod = { |
101 | [startMethodName]() { | 144 | [startMethodName]() { |
102 | - trackAni.value = new BMapGLLib.TrackAnimation(mapInstance, pl, { | 145 | + trackAni.value = new BMapGLLib.TrackAnimation(unref(mapInstance), pl, { |
103 | overallView: true, | 146 | overallView: true, |
104 | tilt: 30, | 147 | tilt: 30, |
105 | - duration: 20000, | 148 | + duration: 5000, |
106 | delay: 300, | 149 | delay: 300, |
107 | }); | 150 | }); |
108 | trackAni.value!.start(); | 151 | trackAni.value!.start(); |
@@ -114,15 +157,43 @@ | @@ -114,15 +157,43 @@ | ||
114 | setTimeout(`${startMethodName}()`); | 157 | setTimeout(`${startMethodName}()`); |
115 | }; | 158 | }; |
116 | 159 | ||
160 | + const { setTask, executeFlag } = useAsyncQueue(); | ||
117 | onMounted(() => { | 161 | onMounted(() => { |
162 | + let interval: Nullable<NodeJS.Timer> = setInterval(() => { | ||
163 | + if ((window as any).BMapGL) { | ||
164 | + prepare.value = true; | ||
165 | + executeFlag.value = true; | ||
166 | + clearInterval(interval!); | ||
167 | + interval = null; | ||
168 | + } | ||
169 | + }, 1000); | ||
170 | + if (!(window as any).BMapGL) { | ||
171 | + setTask(initMap); | ||
172 | + return; | ||
173 | + } | ||
118 | initMap(); | 174 | initMap(); |
119 | }); | 175 | }); |
120 | 176 | ||
177 | + const timeRange = reactive<Record<'start' | 'end', Nullable<number>>>({ | ||
178 | + start: null, | ||
179 | + end: null, | ||
180 | + }); | ||
121 | const getTimeRange = computed(() => { | 181 | const getTimeRange = computed(() => { |
122 | - return ` - 从 ${'2020-10-20 10:10:10'} 到 ${'2020-10-20 10:10:10'}`; | 182 | + const { start, end } = timeRange; |
183 | + if (!start || !end) return `- 请选择`; | ||
184 | + return ` - 从 ${formatToDateTime(start, 'YYYY-MM-DD HH:mm:ss')} 到 ${formatToDateTime( | ||
185 | + end, | ||
186 | + 'YYYY-MM-DD HH:mm:ss' | ||
187 | + )}`; | ||
123 | }); | 188 | }); |
124 | 189 | ||
125 | - const handleTrackSwitch = () => {}; | 190 | + const [register, { openModal }] = useModal(); |
191 | + | ||
192 | + const handleTrackSwitch = () => { | ||
193 | + openModal(true, { | ||
194 | + dataSource: props.value?.dataSource || [], | ||
195 | + } as HistoryModalParams); | ||
196 | + }; | ||
126 | 197 | ||
127 | const getTrackPlayStatus = computed(() => { | 198 | const getTrackPlayStatus = computed(() => { |
128 | return (trackAni.value || {})._status; | 199 | return (trackAni.value || {})._status; |
@@ -133,10 +204,17 @@ | @@ -133,10 +204,17 @@ | ||
133 | else if (unref(getTrackPlayStatus) === TrackAnimationStatus.PLAY) unref(trackAni).pause(); | 204 | else if (unref(getTrackPlayStatus) === TrackAnimationStatus.PLAY) unref(trackAni).pause(); |
134 | else if (unref(getTrackPlayStatus) === TrackAnimationStatus.PAUSE) unref(trackAni).continue(); | 205 | else if (unref(getTrackPlayStatus) === TrackAnimationStatus.PAUSE) unref(trackAni).continue(); |
135 | }; | 206 | }; |
207 | + | ||
208 | + const handleRenderHistroyData = (params: HistoryModalOkEmitParams) => { | ||
209 | + const { track, value } = params; | ||
210 | + track.length && randomAnimation(track as unknown as Record<'lng' | 'lat', number>[]); | ||
211 | + timeRange.start = value.startTs as number; | ||
212 | + timeRange.end = value.endTs as number; | ||
213 | + }; | ||
136 | </script> | 214 | </script> |
137 | 215 | ||
138 | <template> | 216 | <template> |
139 | - <div class="w-full h-full flex justify-center items-center flex-col"> | 217 | + <div class="w-full h-full flex justify-center items-center flex-col p-2 no-drag"> |
140 | <div | 218 | <div |
141 | class="w-full flex" | 219 | class="w-full flex" |
142 | v-if="props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_HISTORY" | 220 | v-if="props.layout?.componentType === FrontComponent.MAP_COMPONENT_TRACK_HISTORY" |
@@ -144,7 +222,7 @@ | @@ -144,7 +222,7 @@ | ||
144 | <Button type="text" class="!px-2 flex-auto !text-left truncate" @click="handleTrackSwitch"> | 222 | <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"> | 223 | <div class="w-full truncate text-gray-500 flex items-center"> |
146 | <ClockCircleOutlined /> | 224 | <ClockCircleOutlined /> |
147 | - <span class="mx-1">实时</span> | 225 | + <span class="mx-1">历史</span> |
148 | <Tooltip :title="getTimeRange.replace('-', '')"> | 226 | <Tooltip :title="getTimeRange.replace('-', '')"> |
149 | <span class="truncate"> | 227 | <span class="truncate"> |
150 | {{ getTimeRange }} | 228 | {{ getTimeRange }} |
@@ -154,12 +232,17 @@ | @@ -154,12 +232,17 @@ | ||
154 | </Button> | 232 | </Button> |
155 | <Button type="text" class="!px-2 !text-gray-500" @click="handlePlay"> | 233 | <Button type="text" class="!px-2 !text-gray-500" @click="handlePlay"> |
156 | <PlayCircleOutlined v-show="getTrackPlayStatus !== TrackAnimationStatus.PLAY" /> | 234 | <PlayCircleOutlined v-show="getTrackPlayStatus !== TrackAnimationStatus.PLAY" /> |
157 | - <PauseCircleOutlined v-show="getTrackPlayStatus === TrackAnimationStatus.PLAY" /> | 235 | + <PauseCircleOutlined |
236 | + class="!ml-0" | ||
237 | + v-show="getTrackPlayStatus === TrackAnimationStatus.PLAY" | ||
238 | + /> | ||
158 | <span> | 239 | <span> |
159 | {{ getTrackPlayStatus !== TrackAnimationStatus.PLAY ? '播放轨迹' : '暂停播放' }} | 240 | {{ getTrackPlayStatus !== TrackAnimationStatus.PLAY ? '播放轨迹' : '暂停播放' }} |
160 | </span> | 241 | </span> |
161 | </Button> | 242 | </Button> |
162 | </div> | 243 | </div> |
244 | + <Spin class="w-full h-full" :spinning="!prepare" tip="Loading..." /> | ||
163 | <div ref="wrapRef" :id="wrapId" class="w-full h-full"></div> | 245 | <div ref="wrapRef" :id="wrapId" class="w-full h-full"></div> |
246 | + <HistoryDataModel @register="register" @ok="handleRenderHistroyData" /> | ||
164 | </div> | 247 | </div> |
165 | </template> | 248 | </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,39 @@ export const MapRealTrackConfig: Config = { | @@ -22,14 +24,39 @@ 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[], config: Config) => { | ||
28 | + if (dataSource.length >= 2) { | ||
29 | + const trackRecord = dataSource.slice(0, 2); | ||
30 | + if ( | ||
31 | + !trackRecord.every((item) => item.componentInfo.value) && | ||
32 | + config.componentType === FrontComponent.MAP_COMPONENT_TRACK_REAL | ||
33 | + ) | ||
34 | + return { track: [], dataSource: [] }; | ||
35 | + | ||
36 | + const track = trackRecord.map((item) => { | ||
37 | + return { | ||
38 | + ts: item.componentInfo.updateTime, | ||
39 | + value: item.componentInfo.value || 0, | ||
40 | + }; | ||
41 | + }); | ||
42 | + return { track, dataSource }; | ||
43 | + } | ||
44 | + return { track: [], dataSource: [] }; | ||
45 | +}; | ||
46 | + | ||
25 | export const transfromMapComponentConfig: ComponentConfig['transformConfig'] = ( | 47 | export const transfromMapComponentConfig: ComponentConfig['transformConfig'] = ( |
26 | componentConfig: Config, | 48 | componentConfig: Config, |
27 | _record, | 49 | _record, |
28 | - _dataSourceRecord | 50 | + dataSourceRecord |
29 | ) => { | 51 | ) => { |
52 | + const { track, dataSource } = getTrack(dataSourceRecord as DataSource[], componentConfig); | ||
30 | return { | 53 | return { |
31 | layout: { | 54 | layout: { |
32 | ...componentConfig, | 55 | ...componentConfig, |
33 | - }, | 56 | + } as MapComponentLayout, |
57 | + value: { | ||
58 | + track, | ||
59 | + dataSource, | ||
60 | + } as MapComponentValue, | ||
34 | }; | 61 | }; |
35 | }; | 62 | }; |
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: true, | ||
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,47 @@ | @@ -192,36 +265,47 @@ | ||
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 | - 选择设备 | ||
201 | - </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" | 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 | ||
278 | + :frontId="$props.frontId" | ||
279 | + :is="dataSourceComponent" | ||
280 | + :ref="(el) => setFormEl(el, item.id)" | ||
221 | /> | 281 | /> |
222 | - </Tooltip> | 282 | + </div> |
283 | + <div class="flex justify-center gap-3 w-28"> | ||
284 | + <Tooltip title="复制"> | ||
285 | + <CopyOutlined @click="handleCopy(item)" class="cursor-pointer text-lg !leading-32px" /> | ||
286 | + </Tooltip> | ||
287 | + <Tooltip title="设置"> | ||
288 | + <SettingOutlined | ||
289 | + v-show="showSettingButton" | ||
290 | + @click="handleSetting(item)" | ||
291 | + class="cursor-pointer text-lg !leading-32px" | ||
292 | + /> | ||
293 | + </Tooltip> | ||
294 | + <Tooltip title="拖拽排序"> | ||
295 | + <SwapOutlined | ||
296 | + class="cursor-pointer text-lg !leading-32px svg:transform svg:rotate-90 sort-icon" | ||
297 | + /> | ||
298 | + </Tooltip> | ||
299 | + <Tooltip title="删除"> | ||
300 | + <DeleteOutlined | ||
301 | + @click="handleDelete(item)" | ||
302 | + class="cursor-pointer text-lg !leading-32px" | ||
303 | + /> | ||
304 | + </Tooltip> | ||
305 | + </div> | ||
223 | </div> | 306 | </div> |
224 | - </div> | 307 | + </section> |
308 | + | ||
225 | <div class="text-center"> | 309 | <div class="text-center"> |
226 | <Button type="primary" @click="handleAdd">添加数据源</Button> | 310 | <Button type="primary" @click="handleAdd">添加数据源</Button> |
227 | </div> | 311 | </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) { |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | import { ref } from 'vue'; | 2 | import { ref } from 'vue'; |
3 | + import { FrontComponent } from '../../../const/const'; | ||
3 | import { dataSourceSchema } from '../../config/basicConfiguration'; | 4 | import { dataSourceSchema } from '../../config/basicConfiguration'; |
4 | import { FormActionType } from '/@/components/Form'; | 5 | import { FormActionType } from '/@/components/Form'; |
5 | import BasicForm from '/@/components/Form/src/BasicForm.vue'; | 6 | import BasicForm from '/@/components/Form/src/BasicForm.vue'; |
6 | const formEl = ref<Nullable<FormActionType>>(null); | 7 | const formEl = ref<Nullable<FormActionType>>(null); |
7 | 8 | ||
9 | + defineProps<{ | ||
10 | + frontId?: FrontComponent; | ||
11 | + }>(); | ||
12 | + | ||
8 | defineExpose({ formActionType: formEl }); | 13 | defineExpose({ formActionType: formEl }); |
9 | </script> | 14 | </script> |
10 | 15 | ||
11 | <template> | 16 | <template> |
12 | <BasicForm | 17 | <BasicForm |
13 | ref="formEl" | 18 | ref="formEl" |
14 | - :schemas="dataSourceSchema" | 19 | + :schemas="dataSourceSchema($props.frontId)" |
15 | class="w-full flex-1 data-source-form" | 20 | class="w-full flex-1 data-source-form" |
16 | :show-action-button-group="false" | 21 | :show-action-button-group="false" |
17 | :row-props="{ | 22 | :row-props="{ |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | import { ref, unref } from 'vue'; | 2 | import { ref, unref } from 'vue'; |
3 | import { BasicForm, FormActionType } from '/@/components/Form'; | 3 | import { BasicForm, FormActionType } from '/@/components/Form'; |
4 | - import { controlFormSchema } from '../../config/basicConfiguration'; | 4 | + import { dataSourceSchema } from '../../config/basicConfiguration'; |
5 | + import { FrontComponent } from '../../../const/const'; | ||
6 | + | ||
7 | + defineProps<{ | ||
8 | + frontId?: FrontComponent; | ||
9 | + }>(); | ||
5 | 10 | ||
6 | const formEl = ref<Nullable<FormActionType>>(); | 11 | const formEl = ref<Nullable<FormActionType>>(); |
7 | 12 | ||
@@ -33,7 +38,7 @@ | @@ -33,7 +38,7 @@ | ||
33 | <div class="w-full flex-1"> | 38 | <div class="w-full flex-1"> |
34 | <BasicForm | 39 | <BasicForm |
35 | :ref="(el) => setFormEl(el)" | 40 | :ref="(el) => setFormEl(el)" |
36 | - :schemas="controlFormSchema" | 41 | + :schemas="dataSourceSchema($props.frontId)" |
37 | class="w-full flex-1 data-source-form" | 42 | class="w-full flex-1 data-source-form" |
38 | :show-action-button-group="false" | 43 | :show-action-button-group="false" |
39 | :row-props="{ | 44 | :row-props="{ |
1 | -<script lang="ts" setup> | ||
2 | - import { ref, unref } from 'vue'; | ||
3 | - import { BasicForm, FormActionType } from '/@/components/Form'; | ||
4 | - import { mapFormSchema } from '../../config/basicConfiguration'; | ||
5 | - | ||
6 | - const formEl = ref<Nullable<FormActionType>>(); | ||
7 | - | ||
8 | - const setFormEl = (el: any) => { | ||
9 | - formEl.value = el; | ||
10 | - }; | ||
11 | - | ||
12 | - const getFieldsValue = () => { | ||
13 | - return unref(formEl)!.getFieldsValue(); | ||
14 | - }; | ||
15 | - | ||
16 | - const validate = async () => { | ||
17 | - await unref(formEl)!.validate(); | ||
18 | - }; | ||
19 | - | ||
20 | - const setFieldsValue = async (record: Recordable) => { | ||
21 | - await unref(formEl)!.setFieldsValue(record); | ||
22 | - }; | ||
23 | - | ||
24 | - const clearValidate = async (name?: string | string[]) => { | ||
25 | - await unref(formEl)!.clearValidate(name); | ||
26 | - }; | ||
27 | - defineExpose({ | ||
28 | - formActionType: { getFieldsValue, validate, setFieldsValue, clearValidate }, | ||
29 | - }); | ||
30 | -</script> | ||
31 | - | ||
32 | -<template> | ||
33 | - <div class="w-full flex-1"> | ||
34 | - <BasicForm | ||
35 | - :ref="(el) => setFormEl(el)" | ||
36 | - :schemas="mapFormSchema" | ||
37 | - class="w-full flex-1 data-source-form" | ||
38 | - :show-action-button-group="false" | ||
39 | - :row-props="{ | ||
40 | - gutter: 10, | ||
41 | - }" | ||
42 | - layout="horizontal" | ||
43 | - :label-col="{ span: 0 }" | ||
44 | - /> | ||
45 | - </div> | ||
46 | -</template> |
1 | import { Component } from 'vue'; | 1 | 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'; | ||
5 | -import MapDataSourceForm from './MapDataSourceForm.vue'; | 4 | +// import ControlDataSourceForm from './ControlDataSourceForm.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); | ||
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) => { | 8 | export const getDataSourceComponent = (frontId: FrontComponent) => { |
14 | if (dataSourceComponentMap.has(frontId)) return dataSourceComponentMap.get(frontId)!; | 9 | if (dataSourceComponentMap.has(frontId)) return dataSourceComponentMap.get(frontId)!; |
15 | return BasicDataSourceForm; | 10 | 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> |
@@ -2,18 +2,18 @@ import { getAllDeviceByOrg, getDeviceAttributes, getGatewaySlaveDevice } from '/ | @@ -2,18 +2,18 @@ import { getAllDeviceByOrg, getDeviceAttributes, getGatewaySlaveDevice } from '/ | ||
2 | import { getOrganizationList } from '/@/api/system/system'; | 2 | import { getOrganizationList } from '/@/api/system/system'; |
3 | import { FormSchema } from '/@/components/Form'; | 3 | 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'; | ||
6 | -import { unref } from 'vue'; | 5 | +import { DeviceAttributeParams, MasterDeviceList } from '/@/api/dataBoard/model'; |
6 | +import { FrontComponent } from '../../const/const'; | ||
7 | 7 | ||
8 | export enum BasicConfigField { | 8 | export enum BasicConfigField { |
9 | NAME = 'name', | 9 | NAME = 'name', |
10 | REMARK = 'remark', | 10 | REMARK = 'remark', |
11 | } | 11 | } |
12 | 12 | ||
13 | -const getDeviceAttribute = async (deviceId: string) => { | 13 | +const getDeviceAttribute = async (params: DeviceAttributeParams) => { |
14 | try { | 14 | try { |
15 | - const data = await getDeviceAttributes({ deviceId }); | ||
16 | - if (data) return data.map((item) => ({ label: item, value: item })); | 15 | + const data = await getDeviceAttributes(params); |
16 | + if (data) return data.map((item) => ({ label: item.name, value: item.identifier })); | ||
17 | } catch (error) {} | 17 | } catch (error) {} |
18 | return []; | 18 | return []; |
19 | }; | 19 | }; |
@@ -23,6 +23,8 @@ export enum DataSourceField { | @@ -23,6 +23,8 @@ export enum DataSourceField { | ||
23 | ORIGINATION_ID = 'organizationId', | 23 | ORIGINATION_ID = 'organizationId', |
24 | DEVICE_ID = 'deviceId', | 24 | DEVICE_ID = 'deviceId', |
25 | SLAVE_DEVICE_ID = 'slaveDeviceId', | 25 | SLAVE_DEVICE_ID = 'slaveDeviceId', |
26 | + DEVICE_PROFILE_ID = 'deviceProfileId', | ||
27 | + SLAVE_DEVICE_PROFILE_ID = 'slaveDeviceProfileId', | ||
26 | ATTRIBUTE = 'attribute', | 28 | ATTRIBUTE = 'attribute', |
27 | ATTRIBUTE_RENAME = 'attributeRename', | 29 | ATTRIBUTE_RENAME = 'attributeRename', |
28 | DEVICE_NAME = 'deviceName', | 30 | DEVICE_NAME = 'deviceName', |
@@ -31,6 +33,18 @@ export enum DataSourceField { | @@ -31,6 +33,18 @@ export enum DataSourceField { | ||
31 | LATITUDE_ATTRIBUTE = 'latitudeAttribute', | 33 | LATITUDE_ATTRIBUTE = 'latitudeAttribute', |
32 | } | 34 | } |
33 | 35 | ||
36 | +const isControlComponent = (frontId: FrontComponent) => { | ||
37 | + const list = [ | ||
38 | + FrontComponent.CONTROL_COMPONENT_SLIDING_SWITCH, | ||
39 | + FrontComponent.CONTROL_COMPONENT_SWITCH_WITH_ICON, | ||
40 | + FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH, | ||
41 | + ]; | ||
42 | + if (list.includes(frontId)) { | ||
43 | + return true; | ||
44 | + } | ||
45 | + return false; | ||
46 | +}; | ||
47 | + | ||
34 | export const basicSchema: FormSchema[] = [ | 48 | export const basicSchema: FormSchema[] = [ |
35 | { | 49 | { |
36 | field: BasicConfigField.NAME, | 50 | field: BasicConfigField.NAME, |
@@ -52,388 +66,214 @@ export const basicSchema: FormSchema[] = [ | @@ -52,388 +66,214 @@ export const basicSchema: FormSchema[] = [ | ||
52 | }, | 66 | }, |
53 | ]; | 67 | ]; |
54 | 68 | ||
55 | -export const dataSourceSchema: FormSchema[] = [ | ||
56 | - { | ||
57 | - field: DataSourceField.IS_GATEWAY_DEVICE, | ||
58 | - component: 'Switch', | ||
59 | - label: '是否是网关设备', | ||
60 | - show: false, | ||
61 | - }, | ||
62 | - { | ||
63 | - field: DataSourceField.DEVICE_NAME, | ||
64 | - component: 'Input', | ||
65 | - label: '设备名', | ||
66 | - show: false, | ||
67 | - }, | ||
68 | - { | ||
69 | - field: DataSourceField.ORIGINATION_ID, | ||
70 | - component: 'ApiTreeSelect', | ||
71 | - label: '组织', | ||
72 | - colProps: { span: 8 }, | ||
73 | - rules: [{ required: true, message: '组织为必填项' }], | ||
74 | - componentProps({ formActionType }) { | ||
75 | - const { setFieldsValue } = formActionType; | ||
76 | - return { | ||
77 | - placeholder: '请选择组织', | ||
78 | - api: async () => { | ||
79 | - const data = await getOrganizationList(); | ||
80 | - copyTransFun(data as any as any[]); | ||
81 | - return data; | ||
82 | - }, | ||
83 | - onChange() { | ||
84 | - setFieldsValue({ | ||
85 | - [DataSourceField.DEVICE_ID]: null, | ||
86 | - [DataSourceField.ATTRIBUTE]: null, | ||
87 | - [DataSourceField.SLAVE_DEVICE_ID]: null, | ||
88 | - [DataSourceField.IS_GATEWAY_DEVICE]: false, | ||
89 | - }); | ||
90 | - }, | ||
91 | - getPopupContainer: () => document.body, | ||
92 | - }; | ||
93 | - }, | ||
94 | - }, | ||
95 | - { | ||
96 | - field: DataSourceField.DEVICE_ID, | ||
97 | - component: 'ApiSelect', | ||
98 | - label: '设备', | ||
99 | - colProps: { span: 8 }, | ||
100 | - rules: [{ required: true, message: '设备名称为必填项' }], | ||
101 | - componentProps({ formModel, formActionType }) { | ||
102 | - const { setFieldsValue } = formActionType; | ||
103 | - const organizationId = formModel[DataSourceField.ORIGINATION_ID]; | ||
104 | - return { | ||
105 | - api: async () => { | ||
106 | - if (organizationId) { | ||
107 | - try { | ||
108 | - const data = await getAllDeviceByOrg(organizationId); | ||
109 | - if (data) | ||
110 | - return data.map((item) => ({ | ||
111 | - label: item.name, | ||
112 | - value: item.id, | ||
113 | - deviceType: item.deviceType, | ||
114 | - })); | ||
115 | - } catch (error) {} | ||
116 | - } | ||
117 | - return []; | ||
118 | - }, | ||
119 | - onChange(_value, record: Record<'value' | 'label' | 'deviceType', string>) { | ||
120 | - setFieldsValue({ | ||
121 | - [DataSourceField.ATTRIBUTE]: null, | ||
122 | - [DataSourceField.IS_GATEWAY_DEVICE]: record?.deviceType === 'GATEWAY', | ||
123 | - [DataSourceField.SLAVE_DEVICE_ID]: null, | ||
124 | - [DataSourceField.DEVICE_NAME]: record?.label, | ||
125 | - }); | ||
126 | - }, | ||
127 | - placeholder: '请选择设备', | ||
128 | - getPopupContainer: () => document.body, | ||
129 | - }; | ||
130 | - }, | ||
131 | - }, | ||
132 | - { | ||
133 | - field: DataSourceField.SLAVE_DEVICE_ID, | ||
134 | - label: '网关子设备', | ||
135 | - component: 'ApiSelect', | ||
136 | - colProps: { span: 8 }, | ||
137 | - rules: [{ required: true, message: '网关子设备为必填项' }], | ||
138 | - ifShow({ model }) { | ||
139 | - return model[DataSourceField.IS_GATEWAY_DEVICE]; | ||
140 | - }, | ||
141 | - dynamicRules({ model }) { | ||
142 | - return [{ required: model[DataSourceField.IS_GATEWAY_DEVICE], message: '请选择网关子设备' }]; | 69 | +export const dataSourceSchema = (frontId?: FrontComponent): FormSchema[] => { |
70 | + return [ | ||
71 | + { | ||
72 | + field: DataSourceField.IS_GATEWAY_DEVICE, | ||
73 | + component: 'Switch', | ||
74 | + label: '是否是网关设备', | ||
75 | + show: false, | ||
143 | }, | 76 | }, |
144 | - componentProps({ formModel, formActionType }) { | ||
145 | - const { setFieldsValue } = formActionType; | ||
146 | - const organizationId = formModel[DataSourceField.ORIGINATION_ID]; | ||
147 | - const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE]; | ||
148 | - const deviceId = formModel[DataSourceField.DEVICE_ID]; | ||
149 | - return { | ||
150 | - api: async () => { | ||
151 | - if (organizationId && isGatewayDevice) { | ||
152 | - try { | ||
153 | - const data = await getGatewaySlaveDevice({ organizationId, masterId: deviceId }); | ||
154 | - if (data) | ||
155 | - return data.map((item) => ({ | ||
156 | - label: item.name, | ||
157 | - value: item.id, | ||
158 | - deviceType: item.deviceType, | ||
159 | - })); | ||
160 | - } catch (error) {} | ||
161 | - } | ||
162 | - return []; | ||
163 | - }, | ||
164 | - onChange(_value, record: Record<'value' | 'label' | 'deviceType', string>) { | ||
165 | - setFieldsValue({ | ||
166 | - [DataSourceField.ATTRIBUTE]: null, | ||
167 | - [DataSourceField.DEVICE_NAME]: record?.label, | ||
168 | - }); | ||
169 | - }, | ||
170 | - placeholder: '请选择网关子设备', | ||
171 | - getPopupContainer: () => document.body, | ||
172 | - }; | 77 | + { |
78 | + field: DataSourceField.DEVICE_NAME, | ||
79 | + component: 'Input', | ||
80 | + label: '设备名', | ||
81 | + show: false, | ||
173 | }, | 82 | }, |
174 | - }, | ||
175 | - { | ||
176 | - field: DataSourceField.ATTRIBUTE, | ||
177 | - component: 'ApiSelect', | ||
178 | - label: '属性', | ||
179 | - colProps: { span: 8 }, | ||
180 | - rules: [{ required: true, message: '属性为必填项' }], | ||
181 | - componentProps({ formModel }) { | ||
182 | - const organizationId = formModel[DataSourceField.ORIGINATION_ID]; | ||
183 | - const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE]; | ||
184 | - const deviceId = formModel[DataSourceField.DEVICE_ID]; | ||
185 | - const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID]; | ||
186 | - return { | ||
187 | - api: async () => { | ||
188 | - if (organizationId && deviceId) { | ||
189 | - try { | ||
190 | - if (isGatewayDevice && slaveDeviceId) { | ||
191 | - return await getDeviceAttribute(slaveDeviceId); | ||
192 | - } | ||
193 | - if (!isGatewayDevice) { | ||
194 | - return await getDeviceAttribute(deviceId); | ||
195 | - } | ||
196 | - } catch (error) {} | ||
197 | - } | ||
198 | - return []; | ||
199 | - }, | ||
200 | - placeholder: '请选择属性', | ||
201 | - getPopupContainer: () => document.body, | ||
202 | - }; | 83 | + { |
84 | + field: DataSourceField.ORIGINATION_ID, | ||
85 | + component: 'ApiTreeSelect', | ||
86 | + label: '组织', | ||
87 | + colProps: { span: 8 }, | ||
88 | + rules: [{ required: true, message: '组织为必填项' }], | ||
89 | + componentProps({ formActionType }) { | ||
90 | + const { setFieldsValue } = formActionType; | ||
91 | + return { | ||
92 | + placeholder: '请选择组织', | ||
93 | + api: async () => { | ||
94 | + const data = await getOrganizationList(); | ||
95 | + copyTransFun(data as any as any[]); | ||
96 | + return data; | ||
97 | + }, | ||
98 | + onChange() { | ||
99 | + setFieldsValue({ | ||
100 | + [DataSourceField.DEVICE_ID]: null, | ||
101 | + [DataSourceField.ATTRIBUTE]: null, | ||
102 | + [DataSourceField.SLAVE_DEVICE_ID]: null, | ||
103 | + [DataSourceField.IS_GATEWAY_DEVICE]: false, | ||
104 | + [DataSourceField.DEVICE_PROFILE_ID]: null, | ||
105 | + [DataSourceField.SLAVE_DEVICE_PROFILE_ID]: null, | ||
106 | + }); | ||
107 | + }, | ||
108 | + getPopupContainer: () => document.body, | ||
109 | + }; | ||
110 | + }, | ||
203 | }, | 111 | }, |
204 | - }, | ||
205 | - { | ||
206 | - field: DataSourceField.DEVICE_RENAME, | ||
207 | - component: 'Input', | ||
208 | - label: '设备', | ||
209 | - colProps: { span: 8 }, | ||
210 | - componentProps: { | ||
211 | - placeholder: '设备重命名', | 112 | + { |
113 | + field: DataSourceField.DEVICE_PROFILE_ID, | ||
114 | + component: 'Input', | ||
115 | + label: '', | ||
116 | + show: false, | ||
212 | }, | 117 | }, |
213 | - }, | ||
214 | - { | ||
215 | - field: DataSourceField.ATTRIBUTE_RENAME, | ||
216 | - component: 'Input', | ||
217 | - label: '属性', | ||
218 | - colProps: { span: 8 }, | ||
219 | - componentProps: { | ||
220 | - placeholder: '属性重命名', | ||
221 | - }, | ||
222 | - }, | ||
223 | -]; | 118 | + { |
119 | + field: DataSourceField.DEVICE_ID, | ||
120 | + component: 'ApiSelect', | ||
121 | + label: '设备', | ||
122 | + colProps: { span: 8 }, | ||
123 | + rules: [{ required: true, message: '设备名称为必填项' }], | ||
124 | + componentProps({ formModel, formActionType }) { | ||
125 | + const { setFieldsValue } = formActionType; | ||
126 | + const organizationId = formModel[DataSourceField.ORIGINATION_ID]; | ||
127 | + const deviceId = formModel[DataSourceField.DEVICE_ID]; | ||
128 | + return { | ||
129 | + api: async () => { | ||
130 | + if (organizationId) { | ||
131 | + try { | ||
132 | + const data = await getAllDeviceByOrg(organizationId); | ||
133 | + if (deviceId) { | ||
134 | + const record = data.find((item) => item.id === deviceId); | ||
135 | + setFieldsValue({ [DataSourceField.DEVICE_PROFILE_ID]: record?.deviceProfileId }); | ||
136 | + } | ||
137 | + if (data) | ||
138 | + return data.map((item) => ({ | ||
139 | + ...item, | ||
140 | + label: item.name, | ||
141 | + value: item.id, | ||
142 | + deviceType: item.deviceType, | ||
143 | + })); | ||
144 | + } catch (error) {} | ||
145 | + } | ||
146 | + return []; | ||
147 | + }, | ||
224 | 148 | ||
225 | -export const controlFormSchema: FormSchema[] = [ | ||
226 | - { | ||
227 | - field: DataSourceField.DEVICE_RENAME, | ||
228 | - component: 'Input', | ||
229 | - label: '设备', | ||
230 | - colProps: { span: 8 }, | ||
231 | - componentProps: { | ||
232 | - placeholder: '设备重命名', | 149 | + onChange(_value, record: MasterDeviceList) { |
150 | + setFieldsValue({ | ||
151 | + [DataSourceField.ATTRIBUTE]: null, | ||
152 | + [DataSourceField.IS_GATEWAY_DEVICE]: record?.deviceType === 'GATEWAY', | ||
153 | + [DataSourceField.DEVICE_PROFILE_ID]: record?.deviceProfileId, | ||
154 | + [DataSourceField.SLAVE_DEVICE_ID]: null, | ||
155 | + [DataSourceField.SLAVE_DEVICE_PROFILE_ID]: null, | ||
156 | + [DataSourceField.DEVICE_NAME]: record?.label, | ||
157 | + }); | ||
158 | + }, | ||
159 | + placeholder: '请选择设备', | ||
160 | + getPopupContainer: () => document.body, | ||
161 | + }; | ||
162 | + }, | ||
233 | }, | 163 | }, |
234 | - }, | ||
235 | - { | ||
236 | - field: DataSourceField.ATTRIBUTE_RENAME, | ||
237 | - component: 'Input', | ||
238 | - label: '属性', | ||
239 | - colProps: { span: 8 }, | ||
240 | - componentProps: { | ||
241 | - placeholder: '属性重命名', | 164 | + { |
165 | + field: DataSourceField.SLAVE_DEVICE_PROFILE_ID, | ||
166 | + component: 'Input', | ||
167 | + label: '', | ||
168 | + show: false, | ||
242 | }, | 169 | }, |
243 | - }, | ||
244 | -]; | ||
245 | - | ||
246 | -export const mapFormSchema: FormSchema[] = [ | ||
247 | - { | ||
248 | - field: DataSourceField.IS_GATEWAY_DEVICE, | ||
249 | - component: 'Switch', | ||
250 | - label: '是否是网关设备', | ||
251 | - show: false, | ||
252 | - }, | ||
253 | - { | ||
254 | - field: DataSourceField.DEVICE_NAME, | ||
255 | - component: 'Input', | ||
256 | - label: '设备名', | ||
257 | - show: false, | ||
258 | - }, | ||
259 | - { | ||
260 | - field: DataSourceField.ORIGINATION_ID, | ||
261 | - component: 'ApiTreeSelect', | ||
262 | - label: '组织', | ||
263 | - colProps: { span: 8 }, | ||
264 | - rules: [{ required: true, message: '组织为必填项' }], | ||
265 | - componentProps({ formActionType }) { | ||
266 | - const { setFieldsValue } = formActionType; | ||
267 | - return { | ||
268 | - placeholder: '请选择组织', | ||
269 | - api: async () => { | ||
270 | - const data = await getOrganizationList(); | ||
271 | - copyTransFun(data as any as any[]); | ||
272 | - return data; | ||
273 | - }, | ||
274 | - onChange() { | ||
275 | - setFieldsValue({ | ||
276 | - [DataSourceField.DEVICE_ID]: null, | ||
277 | - [DataSourceField.LATITUDE_ATTRIBUTE]: null, | ||
278 | - [DataSourceField.LONGITUDE_ATTRIBUTE]: null, | ||
279 | - [DataSourceField.SLAVE_DEVICE_ID]: null, | ||
280 | - [DataSourceField.IS_GATEWAY_DEVICE]: false, | ||
281 | - }); | ||
282 | - }, | ||
283 | - getPopupContainer: () => document.body, | ||
284 | - }; | 170 | + { |
171 | + field: DataSourceField.SLAVE_DEVICE_ID, | ||
172 | + label: '网关子设备', | ||
173 | + component: 'ApiSelect', | ||
174 | + colProps: { span: 8 }, | ||
175 | + rules: [{ required: true, message: '网关子设备为必填项' }], | ||
176 | + ifShow({ model }) { | ||
177 | + return model[DataSourceField.IS_GATEWAY_DEVICE]; | ||
178 | + }, | ||
179 | + dynamicRules({ model }) { | ||
180 | + return [ | ||
181 | + { required: model[DataSourceField.IS_GATEWAY_DEVICE], message: '请选择网关子设备' }, | ||
182 | + ]; | ||
183 | + }, | ||
184 | + componentProps({ formModel, formActionType }) { | ||
185 | + const { setFieldsValue } = formActionType; | ||
186 | + const organizationId = formModel[DataSourceField.ORIGINATION_ID]; | ||
187 | + const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE]; | ||
188 | + const deviceId = formModel[DataSourceField.DEVICE_ID]; | ||
189 | + const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID]; | ||
190 | + return { | ||
191 | + api: async () => { | ||
192 | + if (organizationId && isGatewayDevice) { | ||
193 | + try { | ||
194 | + const data = await getGatewaySlaveDevice({ organizationId, masterId: deviceId }); | ||
195 | + if (slaveDeviceId) { | ||
196 | + const record = data.find((item) => item.id === slaveDeviceId); | ||
197 | + setFieldsValue({ [DataSourceField.DEVICE_PROFILE_ID]: record?.deviceProfileId }); | ||
198 | + } | ||
199 | + if (data) | ||
200 | + return data.map((item) => ({ | ||
201 | + ...item, | ||
202 | + label: item.name, | ||
203 | + value: item.id, | ||
204 | + deviceType: item.deviceType, | ||
205 | + })); | ||
206 | + } catch (error) {} | ||
207 | + } | ||
208 | + return []; | ||
209 | + }, | ||
210 | + onChange(_value, record: MasterDeviceList) { | ||
211 | + setFieldsValue({ | ||
212 | + [DataSourceField.ATTRIBUTE]: null, | ||
213 | + [DataSourceField.SLAVE_DEVICE_PROFILE_ID]: record.deviceProfileId, | ||
214 | + [DataSourceField.DEVICE_NAME]: record?.label, | ||
215 | + }); | ||
216 | + }, | ||
217 | + placeholder: '请选择网关子设备', | ||
218 | + getPopupContainer: () => document.body, | ||
219 | + }; | ||
220 | + }, | ||
285 | }, | 221 | }, |
286 | - }, | ||
287 | - { | ||
288 | - field: DataSourceField.DEVICE_ID, | ||
289 | - component: 'ApiSelect', | ||
290 | - label: '设备', | ||
291 | - colProps: { span: 8 }, | ||
292 | - rules: [{ required: true, message: '设备名称为必填项' }], | ||
293 | - componentProps({ formModel, formActionType }) { | ||
294 | - const { setFieldsValue } = formActionType; | ||
295 | - const organizationId = formModel[DataSourceField.ORIGINATION_ID]; | ||
296 | - return { | ||
297 | - api: async () => { | ||
298 | - if (organizationId) { | ||
299 | - try { | ||
300 | - const data = await getAllDeviceByOrg(organizationId); | ||
301 | - if (data) | ||
302 | - return data.map((item) => ({ | ||
303 | - label: item.name, | ||
304 | - value: item.id, | ||
305 | - deviceType: item.deviceType, | ||
306 | - })); | ||
307 | - } catch (error) {} | ||
308 | - } | ||
309 | - return []; | ||
310 | - }, | ||
311 | - onChange(_value, record: Record<'value' | 'label' | 'deviceType', string>) { | ||
312 | - setFieldsValue({ | ||
313 | - [DataSourceField.LONGITUDE_ATTRIBUTE]: null, | ||
314 | - [DataSourceField.LATITUDE_ATTRIBUTE]: null, | ||
315 | - [DataSourceField.IS_GATEWAY_DEVICE]: record?.deviceType === 'GATEWAY', | ||
316 | - [DataSourceField.SLAVE_DEVICE_ID]: null, | ||
317 | - [DataSourceField.DEVICE_NAME]: record?.label, | ||
318 | - }); | ||
319 | - }, | ||
320 | - placeholder: '请选择设备', | ||
321 | - getPopupContainer: () => document.body, | ||
322 | - }; | ||
323 | - }, | ||
324 | - }, | ||
325 | - { | ||
326 | - field: DataSourceField.SLAVE_DEVICE_ID, | ||
327 | - label: '网关子设备', | ||
328 | - component: 'ApiSelect', | ||
329 | - colProps: { span: 8 }, | ||
330 | - rules: [{ required: true, message: '网关子设备为必填项' }], | ||
331 | - ifShow({ model }) { | ||
332 | - return model[DataSourceField.IS_GATEWAY_DEVICE]; | ||
333 | - }, | ||
334 | - dynamicRules({ model }) { | ||
335 | - return [{ required: model[DataSourceField.IS_GATEWAY_DEVICE], message: '请选择网关子设备' }]; | ||
336 | - }, | ||
337 | - componentProps({ formModel, formActionType }) { | ||
338 | - const { setFieldsValue } = formActionType; | ||
339 | - const organizationId = formModel[DataSourceField.ORIGINATION_ID]; | ||
340 | - const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE]; | ||
341 | - const deviceId = formModel[DataSourceField.DEVICE_ID]; | ||
342 | - return { | ||
343 | - api: async () => { | ||
344 | - if (organizationId && isGatewayDevice) { | ||
345 | - try { | ||
346 | - const data = await getGatewaySlaveDevice({ organizationId, masterId: deviceId }); | ||
347 | - if (data) | ||
348 | - return data.map((item) => ({ | ||
349 | - label: item.name, | ||
350 | - value: item.id, | ||
351 | - deviceType: item.deviceType, | ||
352 | - })); | ||
353 | - } catch (error) {} | ||
354 | - } | ||
355 | - return []; | ||
356 | - }, | ||
357 | - onChange(_value, record: Record<'value' | 'label' | 'deviceType', string>) { | ||
358 | - setFieldsValue({ | ||
359 | - [DataSourceField.LATITUDE_ATTRIBUTE]: null, | ||
360 | - [DataSourceField.LONGITUDE_ATTRIBUTE]: null, | ||
361 | - [DataSourceField.DEVICE_NAME]: record?.label, | ||
362 | - }); | ||
363 | - }, | ||
364 | - placeholder: '请选择网关子设备', | ||
365 | - getPopupContainer: () => document.body, | ||
366 | - }; | 222 | + { |
223 | + field: DataSourceField.ATTRIBUTE, | ||
224 | + component: 'ApiSelect', | ||
225 | + label: '属性', | ||
226 | + colProps: { span: 8 }, | ||
227 | + rules: [{ required: true, message: '属性为必填项' }], | ||
228 | + componentProps({ formModel }) { | ||
229 | + const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE]; | ||
230 | + const deviceId = formModel[DataSourceField.DEVICE_ID]; | ||
231 | + const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID]; | ||
232 | + const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID]; | ||
233 | + const slaveDeviceProfileId = formModel[DataSourceField.SLAVE_DEVICE_PROFILE_ID]; | ||
234 | + | ||
235 | + return { | ||
236 | + api: async () => { | ||
237 | + if (deviceId) { | ||
238 | + try { | ||
239 | + if (isGatewayDevice && slaveDeviceId && slaveDeviceProfileId) { | ||
240 | + return await getDeviceAttribute({ | ||
241 | + deviceProfileId: slaveDeviceProfileId, | ||
242 | + dataType: isControlComponent(frontId!) ? 'BOOL' : undefined, | ||
243 | + }); | ||
244 | + } | ||
245 | + if (!isGatewayDevice && deviceProfileId) { | ||
246 | + return await getDeviceAttribute({ | ||
247 | + deviceProfileId, | ||
248 | + dataType: isControlComponent(frontId!) ? 'BOOL' : undefined, | ||
249 | + }); | ||
250 | + } | ||
251 | + } catch (error) {} | ||
252 | + } | ||
253 | + return []; | ||
254 | + }, | ||
255 | + placeholder: '请选择属性', | ||
256 | + getPopupContainer: () => document.body, | ||
257 | + }; | ||
258 | + }, | ||
367 | }, | 259 | }, |
368 | - }, | ||
369 | - { | ||
370 | - field: DataSourceField.LONGITUDE_ATTRIBUTE, | ||
371 | - component: 'ApiSearchSelect', | ||
372 | - label: '经度属性', | ||
373 | - colProps: { span: 8 }, | ||
374 | - rules: [{ required: true, message: '属性为必填项' }], | ||
375 | - componentProps({ formModel }) { | ||
376 | - const organizationId = formModel[DataSourceField.ORIGINATION_ID]; | ||
377 | - const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE]; | ||
378 | - const deviceId = formModel[DataSourceField.DEVICE_ID]; | ||
379 | - const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID]; | ||
380 | - return { | ||
381 | - api: async () => { | ||
382 | - if (organizationId && deviceId) { | ||
383 | - try { | ||
384 | - if (isGatewayDevice && slaveDeviceId) { | ||
385 | - return await getDeviceAttribute(slaveDeviceId); | ||
386 | - } | ||
387 | - if (!isGatewayDevice) { | ||
388 | - return await getDeviceAttribute(deviceId); | ||
389 | - } | ||
390 | - } catch (error) {} | ||
391 | - } | ||
392 | - return []; | ||
393 | - }, | ||
394 | - placeholder: '请选择经度属性', | ||
395 | - dropdownVisibleChangeHook: ({ options }: OnChangeHookParams) => { | ||
396 | - options.value = unref(options).filter( | ||
397 | - (item) => item.value !== formModel[DataSourceField.LATITUDE_ATTRIBUTE] | ||
398 | - ); | ||
399 | - }, | ||
400 | - getPopupContainer: () => document.body, | ||
401 | - }; | 260 | + { |
261 | + field: DataSourceField.DEVICE_RENAME, | ||
262 | + component: 'Input', | ||
263 | + label: '设备', | ||
264 | + colProps: { span: 8 }, | ||
265 | + componentProps: { | ||
266 | + placeholder: '设备重命名', | ||
267 | + }, | ||
402 | }, | 268 | }, |
403 | - }, | ||
404 | - { | ||
405 | - field: DataSourceField.LATITUDE_ATTRIBUTE, | ||
406 | - component: 'ApiSearchSelect', | ||
407 | - label: '纬度属性', | ||
408 | - colProps: { span: 8 }, | ||
409 | - rules: [{ required: true, message: '属性为必填项' }], | ||
410 | - componentProps({ formModel }) { | ||
411 | - const organizationId = formModel[DataSourceField.ORIGINATION_ID]; | ||
412 | - const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE]; | ||
413 | - const deviceId = formModel[DataSourceField.DEVICE_ID]; | ||
414 | - const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID]; | ||
415 | - return { | ||
416 | - api: async () => { | ||
417 | - if (organizationId && deviceId) { | ||
418 | - try { | ||
419 | - if (isGatewayDevice && slaveDeviceId) { | ||
420 | - return getDeviceAttribute(slaveDeviceId); | ||
421 | - } | ||
422 | - if (!isGatewayDevice) { | ||
423 | - return await getDeviceAttribute(deviceId); | ||
424 | - } | ||
425 | - } catch (error) {} | ||
426 | - } | ||
427 | - return []; | ||
428 | - }, | ||
429 | - dropdownVisibleChangeHook: ({ options }: OnChangeHookParams) => { | ||
430 | - options.value = unref(options).filter( | ||
431 | - (item) => item.value !== formModel[DataSourceField.LONGITUDE_ATTRIBUTE] | ||
432 | - ); | ||
433 | - }, | ||
434 | - placeholder: '请选择纬度属性', | ||
435 | - getPopupContainer: () => document.body, | ||
436 | - }; | 269 | + { |
270 | + field: DataSourceField.ATTRIBUTE_RENAME, | ||
271 | + component: 'Input', | ||
272 | + label: '属性', | ||
273 | + colProps: { span: 8 }, | ||
274 | + componentProps: { | ||
275 | + placeholder: '属性重命名', | ||
276 | + }, | ||
437 | }, | 277 | }, |
438 | - }, | ||
439 | -]; | 278 | + ]; |
279 | +}; |
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 | +} |
@@ -178,6 +178,39 @@ export const modeFour: FormSchema[] = [ | @@ -178,6 +178,39 @@ export const modeFour: FormSchema[] = [ | ||
178 | }, | 178 | }, |
179 | ]; | 179 | ]; |
180 | 180 | ||
181 | +export const modeFive: FormSchema[] = [ | ||
182 | + { | ||
183 | + field: visualOptionField.FONT_COLOR, | ||
184 | + label: '数值字体颜色', | ||
185 | + component: 'ColorPicker', | ||
186 | + changeEvent: 'update:value', | ||
187 | + componentProps: { | ||
188 | + defaultValue: '#000', | ||
189 | + }, | ||
190 | + }, | ||
191 | + { | ||
192 | + field: visualOptionField.ICON_COLOR, | ||
193 | + label: '图标颜色', | ||
194 | + component: 'ColorPicker', | ||
195 | + changeEvent: 'update:value', | ||
196 | + componentProps: { | ||
197 | + defaultValue: '#367BFF', | ||
198 | + }, | ||
199 | + }, | ||
200 | + { | ||
201 | + field: visualOptionField.ICON, | ||
202 | + label: '图标', | ||
203 | + component: 'IconDrawer', | ||
204 | + changeEvent: 'update:value', | ||
205 | + componentProps({ formModel }) { | ||
206 | + const color = formModel[visualOptionField.ICON_COLOR]; | ||
207 | + return { | ||
208 | + color, | ||
209 | + }; | ||
210 | + }, | ||
211 | + }, | ||
212 | +]; | ||
213 | + | ||
181 | export const schemasMap = new Map<FrontComponent, FormSchema[]>(); | 214 | export const schemasMap = new Map<FrontComponent, FormSchema[]>(); |
182 | 215 | ||
183 | schemasMap.set(FrontComponent.TEXT_COMPONENT_1, modeOne); | 216 | schemasMap.set(FrontComponent.TEXT_COMPONENT_1, modeOne); |
@@ -188,3 +221,6 @@ schemasMap.set(FrontComponent.TEXT_COMPONENT_5, modeTwo); | @@ -188,3 +221,6 @@ schemasMap.set(FrontComponent.TEXT_COMPONENT_5, modeTwo); | ||
188 | schemasMap.set(FrontComponent.INSTRUMENT_COMPONENT_1, modeOne); | 221 | schemasMap.set(FrontComponent.INSTRUMENT_COMPONENT_1, modeOne); |
189 | schemasMap.set(FrontComponent.INSTRUMENT_COMPONENT_2, modeThree); | 222 | schemasMap.set(FrontComponent.INSTRUMENT_COMPONENT_2, modeThree); |
190 | schemasMap.set(FrontComponent.DIGITAL_DASHBOARD_COMPONENT, modeFour); | 223 | schemasMap.set(FrontComponent.DIGITAL_DASHBOARD_COMPONENT, modeFour); |
224 | +schemasMap.set(FrontComponent.CONTROL_COMPONENT_SWITCH_WITH_ICON, modeFive); | ||
225 | +schemasMap.set(FrontComponent.CONTROL_COMPONENT_SLIDING_SWITCH, modeOne); | ||
226 | +schemasMap.set(FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH, modeOne); |
@@ -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,9 +375,13 @@ | @@ -375,9 +375,13 @@ | ||
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(); | ||
380 | - await injectBaiDuMapTrackAniMationLib(); | 383 | + injectBaiDuMapLib(); |
384 | + injectBaiDuMapTrackAniMationLib(); | ||
381 | getDataBoardComponent(); | 385 | getDataBoardComponent(); |
382 | }); | 386 | }); |
383 | </script> | 387 | </script> |
@@ -438,10 +442,12 @@ | @@ -438,10 +442,12 @@ | ||
438 | @resize="itemResize" | 442 | @resize="itemResize" |
439 | @moved="itemMoved" | 443 | @moved="itemMoved" |
440 | @container-resized="itemContainerResized" | 444 | @container-resized="itemContainerResized" |
445 | + drag-ignore-from=".no-drag" | ||
441 | > | 446 | > |
442 | <WidgetWrapper | 447 | <WidgetWrapper |
443 | :key="item.i" | 448 | :key="item.i" |
444 | :ref="(el: Element) => setComponentRef(el, item)" | 449 | :ref="(el: Element) => setComponentRef(el, item)" |
450 | + :record="item.record" | ||
445 | :data-source="item.record.dataSource" | 451 | :data-source="item.record.dataSource" |
446 | > | 452 | > |
447 | <template #header> | 453 | <template #header> |
@@ -455,7 +461,7 @@ | @@ -455,7 +461,7 @@ | ||
455 | <Tooltip title="趋势"> | 461 | <Tooltip title="趋势"> |
456 | <img | 462 | <img |
457 | :src="trendIcon" | 463 | :src="trendIcon" |
458 | - v-if="!getIsSharePage" | 464 | + v-if="!getIsSharePage && hasHistoryTrend(item)" |
459 | class="cursor-pointer w-4.5 h-4.5" | 465 | class="cursor-pointer w-4.5 h-4.5" |
460 | @click="handleOpenHistroyDataModal(item.record.dataSource)" | 466 | @click="handleOpenHistroyDataModal(item.record.dataSource)" |
461 | /> | 467 | /> |
@@ -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 |