Commit b1cb3fac48f9c9118358d2ad88b434f7ad65641f

Authored by ww
1 parent 93a107ee

fix: all history data charts

... ... @@ -9,7 +9,7 @@ export const getDeviceProfile = () => {
9 9 };
10 10
11 11 // 获取历史数据
12   -export const getDeviceHistoryInfo = (params) => {
  12 +export const getDeviceHistoryInfo = (params: Recordable) => {
13 13 return defHttp.get<HistoryData>(
14 14 {
15 15 url: `/plugins/telemetry/DEVICE/${params.entityId}/values/timeseries`,
... ...
1 1 <script lang="ts" setup>
2   - import moment from 'moment';
3 2 import { nextTick, onMounted, onUnmounted, Ref, ref, unref } from 'vue';
4   - import { getDeviceDataKeys, getDeviceHistoryInfo } from '/@/api/alarm/position';
  3 + import { getDeviceHistoryInfo } from '/@/api/alarm/position';
5 4 import { Empty, Spin } from 'ant-design-vue';
6 5 import { useECharts } from '/@/hooks/web/useECharts';
7   - import { dateUtil } from '/@/utils/dateUtil';
8   - import {
9   - AggregateDataEnum,
10   - eChartOptions,
11   - selectDeviceAttrSchema,
12   - } from '/@/views/device/localtion/config.data';
  6 + import { AggregateDataEnum, selectDeviceAttrSchema } from '/@/views/device/localtion/config.data';
13 7 import { useTimePeriodForm } from '/@/views/device/localtion/cpns/TimePeriodForm';
14   - import { defaultSchemas, QueryWay } from '/@/views/device/localtion/cpns/TimePeriodForm/config';
  8 + import { defaultSchemas } from '/@/views/device/localtion/cpns/TimePeriodForm/config';
15 9 import TimePeriodForm from '/@/views/device/localtion/cpns/TimePeriodForm/TimePeriodForm.vue';
16 10 import { SchemaFiled } from '/@/views/report/config/config.data';
17 11 import { useGridLayout } from '/@/hooks/component/useGridLayout';
18 12 import { ColEx } from '/@/components/Form/src/types';
  13 + import { useHistoryData } from '../../hook/useHistoryData';
19 14
20 15 interface DeviceDetail {
21 16 tbDeviceId: string;
  17 + deviceProfileId: string;
22 18 }
23 19
24 20 const props = defineProps<{
... ... @@ -28,36 +24,12 @@
28 24
29 25 const chartRef = ref();
30 26
31   - const deviceAttrs = ref<string[]>([]);
32   -
33 27 const loading = ref(false);
34 28
35 29 const isNull = ref(false);
36 30
37   - function getSearchParams(value: Recordable) {
38   - const { startTs, endTs, interval, agg, limit, keys, way } = value;
39   - if (way === QueryWay.LATEST) {
40   - return {
41   - entityId: props.deviceDetail.tbDeviceId,
42   - keys: keys ? keys : unref(deviceAttrs).join(),
43   - startTs: moment().subtract(startTs, 'ms').valueOf(),
44   - endTs: Date.now(),
45   - interval,
46   - agg,
47   - limit,
48   - };
49   - } else {
50   - return {
51   - entityId: props.deviceDetail.tbDeviceId,
52   - keys: keys ? keys : unref(deviceAttrs).join(),
53   - startTs: moment(startTs).valueOf(),
54   - endTs: moment(endTs).valueOf(),
55   - interval,
56   - agg,
57   - limit,
58   - };
59   - }
60   - }
  31 + const { deviceAttrs, getDeviceKeys, getSearchParams, setChartOptions, getDeviceAttribute } =
  32 + useHistoryData();
61 33
62 34 function hasDeviceAttr() {
63 35 if (!unref(deviceAttrs).length) {
... ... @@ -69,28 +41,6 @@
69 41
70 42 const { setOptions, getInstance } = useECharts(chartRef as Ref<HTMLDivElement>);
71 43
72   - function setChartOptions(data, keys?) {
73   - const dataArray: any[] = [];
74   - for (const key in data) {
75   - for (const item1 of data[key]) {
76   - let { ts, value } = item1;
77   - const time = dateUtil(ts).format('YYYY-MM-DD HH:mm:ss');
78   - value = Number(value).toFixed(2);
79   - dataArray.push([time, value, key]);
80   - }
81   - }
82   - keys = keys ? [keys] : unref(deviceAttrs);
83   - const series: any = keys.map((item) => {
84   - return {
85   - name: item,
86   - type: 'line',
87   - data: dataArray.filter((item1) => item1[2] === item),
88   - };
89   - });
90   - // 设置数据
91   - setOptions(eChartOptions(series, keys));
92   - }
93   -
94 44 const [register, method] = useTimePeriodForm({
95 45 schemas: [...defaultSchemas, ...selectDeviceAttrSchema],
96 46 baseColProps: useGridLayout(2, 3, 4) as unknown as ColEx,
... ... @@ -103,7 +53,10 @@
103 53 if (!hasDeviceAttr()) return;
104 54 // 发送请求
105 55 loading.value = true;
106   - const res = await getDeviceHistoryInfo(searchParams);
  56 + const res = await getDeviceHistoryInfo({
  57 + ...searchParams,
  58 + entityId: props.deviceDetail.tbDeviceId,
  59 + });
107 60 // 判断数据对象是否为空
108 61 if (!Object.keys(res).length) {
109 62 isNull.value = false;
... ... @@ -111,16 +64,19 @@
111 64 } else {
112 65 isNull.value = true;
113 66 }
114   - setChartOptions(res, value.keys);
  67 +
  68 + const selectedKeys = unref(deviceAttrs).find(
  69 + (item) => item.identifier === value[SchemaFiled.KEYS]
  70 + );
  71 +
  72 + setOptions(setChartOptions(res, selectedKeys));
115 73 loading.value = false;
116 74 },
117 75 });
118 76
119 77 const getDeviceDataKey = async () => {
120   - const { tbDeviceId } = props.deviceDetail || {};
121 78 try {
122   - deviceAttrs.value = (await getDeviceDataKeys(tbDeviceId)) || [];
123   -
  79 + await getDeviceAttribute(props.deviceDetail);
124 80 if (props.attr) {
125 81 method.setFieldsValue({ keys: props.attr });
126 82 }
... ... @@ -132,7 +88,7 @@
132 88 method.updateSchema({
133 89 field: 'keys',
134 90 componentProps: {
135   - options: unref(deviceAttrs).map((item) => ({ label: item, value: item })),
  91 + options: unref(deviceAttrs).map((item) => ({ label: item.name, value: item.identifier })),
136 92 },
137 93 });
138 94
... ... @@ -144,7 +100,7 @@
144 100
145 101 if (!hasDeviceAttr()) return;
146 102
147   - const keys = props.attr ? props.attr : unref(deviceAttrs).join();
  103 + const keys = props.attr ? props.attr : unref(getDeviceKeys).join();
148 104
149 105 const res = await getDeviceHistoryInfo({
150 106 entityId: props.deviceDetail.tbDeviceId,
... ... @@ -162,7 +118,9 @@
162 118 } else {
163 119 isNull.value = true;
164 120 }
165   - setChartOptions(res, props.attr);
  121 + const selectedKeys = unref(deviceAttrs).find((item) => item.identifier === props.attr);
  122 +
  123 + setOptions(setChartOptions(res, selectedKeys));
166 124 };
167 125
168 126 onMounted(async () => {
... ...
  1 +import { EChartsOption } from 'echarts';
  2 +import moment from 'moment';
  3 +import { computed, ref, unref } from 'vue';
  4 +import { eChartOptions } from '../../localtion/config.data';
  5 +import { HistoryData } from '/@/api/alarm/position/model';
  6 +import { getDeviceAttributes } from '/@/api/dataBoard';
  7 +import { DeviceAttributeRecord } from '/@/api/dataBoard/model';
  8 +import { dateUtil } from '/@/utils/dateUtil';
  9 +import { QueryWay } from '/@/views/report/config/config.data';
  10 +import { SchemaFiled } from '/@/views/visual/board/detail/config/historyTrend.config';
  11 +import { DEFAULT_DATE_FORMAT } from '/@/views/visual/board/detail/config/util';
  12 +
  13 +interface DeviceOption {
  14 + deviceProfileId: string;
  15 +}
  16 +
  17 +export function useHistoryData() {
  18 + const deviceAttrs = ref<DeviceAttributeRecord[]>([]);
  19 +
  20 + const getDeviceKeys = computed(() => {
  21 + return unref(deviceAttrs).map((item) => item.identifier);
  22 + });
  23 +
  24 + const getDeviceAttribute = async (record: DeviceOption) => {
  25 + try {
  26 + const { deviceProfileId } = record;
  27 + deviceAttrs.value = (await getDeviceAttributes({ deviceProfileId })) || [];
  28 + } catch (error) {
  29 + throw error;
  30 + }
  31 + };
  32 +
  33 + function getSearchParams(value: Partial<Record<SchemaFiled, string>>) {
  34 + const { startTs, endTs, interval, agg, limit, keys, way, deviceId } = value;
  35 + const basicRecord = {
  36 + entityId: deviceId,
  37 + keys: keys ? keys : unref(getDeviceKeys).join(),
  38 + interval,
  39 + agg,
  40 + limit,
  41 + };
  42 + if (way === QueryWay.LATEST) {
  43 + return Object.assign(basicRecord, {
  44 + startTs: moment().subtract(startTs, 'ms').valueOf(),
  45 + endTs: Date.now(),
  46 + });
  47 + } else {
  48 + return Object.assign(basicRecord, {
  49 + startTs: moment(startTs).valueOf(),
  50 + endTs: moment(endTs).valueOf(),
  51 + });
  52 + }
  53 + }
  54 +
  55 + function setChartOptions(
  56 + data: HistoryData,
  57 + keys?: DeviceAttributeRecord | DeviceAttributeRecord[]
  58 + ) {
  59 + const dataArray: [string, string, string][] = [];
  60 + for (const key in data) {
  61 + for (const item of data[key]) {
  62 + let { value } = item;
  63 + const { ts } = item;
  64 + const time = dateUtil(ts).format(DEFAULT_DATE_FORMAT);
  65 + value = Number(value).toFixed(2);
  66 + dataArray.push([time, value, key as string]);
  67 + }
  68 + }
  69 +
  70 + keys = keys ? [keys as DeviceAttributeRecord] : unref(deviceAttrs);
  71 + const legend = keys.map((item) => item.name);
  72 +
  73 + const series: EChartsOption['series'] = (keys as DeviceAttributeRecord[]).map((item) => {
  74 + return {
  75 + name: item.name,
  76 + type: 'line',
  77 + data: dataArray.filter((temp) => temp[2] === item.identifier),
  78 + };
  79 + });
  80 +
  81 + return eChartOptions(series, legend);
  82 + }
  83 +
  84 + return {
  85 + deviceAttrs,
  86 + getDeviceKeys,
  87 + getDeviceAttribute,
  88 + getSearchParams,
  89 + setChartOptions,
  90 + };
  91 +}
... ...
... ... @@ -78,11 +78,7 @@
78 78 import { BAI_DU_MAP_URL } from '/@/utils/fnUtils';
79 79 import { useModal, BasicModal } from '/@/components/Modal';
80 80 import { useECharts } from '/@/hooks/web/useECharts';
81   - import {
82   - getDeviceHistoryInfo,
83   - getDeviceDataKeys,
84   - getDeviceActiveTime,
85   - } from '/@/api/alarm/position';
  81 + import { getDeviceHistoryInfo, getDeviceActiveTime } from '/@/api/alarm/position';
86 82 import { useDrawer } from '/@/components/Drawer';
87 83 import DeviceDetailDrawer from '/@/views/device/list/cpns/modal/DeviceDetailDrawer.vue';
88 84 import moment from 'moment';
... ... @@ -96,12 +92,12 @@
96 92 import lx1 from '/@/assets/images/lx1.png';
97 93 import Loading from '/@/components/Loading/src/Loading.vue';
98 94 import { TimePeriodForm, useTimePeriodForm } from './cpns/TimePeriodForm';
99   - import { selectDeviceAttrSchema, eChartOptions } from './config.data';
  95 + import { selectDeviceAttrSchema } from './config.data';
100 96 import { defaultSchemas } from './cpns/TimePeriodForm/config';
101 97 import { QueryWay, SchemaFiled, AggregateDataEnum } from './cpns/TimePeriodForm/config';
102   - import { dateUtil } from '/@/utils/dateUtil';
103 98 import { Spin } from 'ant-design-vue';
104 99 import { useAsyncQueue } from './useAsyncQueue';
  100 + import { useHistoryData } from '../list/hook/useHistoryData';
105 101
106 102 interface DeviceInfo {
107 103 alarmStatus: 0 | 1;
... ... @@ -112,6 +108,7 @@
112 108 deviceProfile: { default: boolean; enabled: boolean; name: string; transportType: string };
113 109 deviceInfo: { longitude: string; latitude: string; address: string };
114 110 deviceType?: string;
  111 + deviceProfileId: string;
115 112 }
116 113 type MarkerList = DeviceInfo & { marker: any; label: any };
117 114
... ... @@ -142,8 +139,7 @@
142 139 let globalRecord: any = {};
143 140 const wrapRef = ref<HTMLDivElement | null>(null);
144 141 const chartRef = ref<HTMLDivElement | null>(null);
145   - const deviceAttrs = ref<string[]>([]);
146   - const { setOptions, getInstance, resize } = useECharts(chartRef as Ref<HTMLDivElement>);
  142 + const { setOptions, getInstance } = useECharts(chartRef as Ref<HTMLDivElement>);
147 143 const isNull = ref(true);
148 144 const { toPromise } = useScript({ src: BAI_DU_MAP_URL });
149 145 const [registerDetailDrawer, { openDrawer }] = useDrawer();
... ... @@ -154,6 +150,9 @@
154 150
155 151 const loading = ref(false);
156 152
  153 + const { deviceAttrs, getDeviceKeys, getDeviceAttribute, getSearchParams, setChartOptions } =
  154 + useHistoryData();
  155 +
157 156 const [registerTable] = useTable({
158 157 api: devicePage,
159 158 columns,
... ... @@ -259,7 +258,7 @@
259 258 entityId = record.tbDeviceId;
260 259 globalRecord = record;
261 260 try {
262   - deviceAttrs.value = (await getDeviceDataKeys(entityId)) || [];
  261 + await getDeviceAttribute(record);
263 262 } catch (error) {}
264 263
265 264 const options = {
... ... @@ -350,7 +349,7 @@
350 349 if (!hasDeviceAttr()) return;
351 350 // 发送请求
352 351 loading.value = true;
353   - const res = await getDeviceHistoryInfo(searchParams);
  352 + const res = await getDeviceHistoryInfo({ ...searchParams, entityId });
354 353 loading.value = false;
355 354 // 判断数据对象是否为空
356 355 if (!Object.keys(res).length) {
... ... @@ -359,35 +358,13 @@
359 358 } else {
360 359 isNull.value = true;
361 360 }
362   - setChartOptions(res, value.keys);
  361 + const selectedKeys = unref(deviceAttrs).find(
  362 + (item) => item.identifier === value[SchemaFiled.KEYS]
  363 + );
  364 + setOptions(setChartOptions(res, selectedKeys));
363 365 },
364 366 });
365 367
366   - function getSearchParams(value: Recordable) {
367   - const { startTs, endTs, interval, agg, limit, keys, way } = value;
368   - if (way === QueryWay.LATEST) {
369   - return {
370   - entityId,
371   - keys: keys ? keys : unref(deviceAttrs).join(),
372   - startTs: moment().subtract(startTs, 'ms').valueOf(),
373   - endTs: Date.now(),
374   - interval,
375   - agg,
376   - limit,
377   - };
378   - } else {
379   - return {
380   - entityId,
381   - keys: keys ? keys : unref(deviceAttrs).join(),
382   - startTs: moment(startTs).valueOf(),
383   - endTs: moment(endTs).valueOf(),
384   - interval,
385   - agg,
386   - limit,
387   - };
388   - }
389   - }
390   -
391 368 const openHistoryModal = async () => {
392 369 openModal(true);
393 370
... ... @@ -395,7 +372,10 @@
395 372 method.updateSchema({
396 373 field: 'keys',
397 374 componentProps: {
398   - options: unref(deviceAttrs).map((item) => ({ label: item, value: item })),
  375 + options: unref(deviceAttrs).map((item) => ({
  376 + label: item.name,
  377 + value: item.identifier,
  378 + })),
399 379 },
400 380 });
401 381
... ... @@ -409,7 +389,7 @@
409 389
410 390 const res = await getDeviceHistoryInfo({
411 391 entityId,
412   - keys: unref(deviceAttrs).join(),
  392 + keys: unref(getDeviceKeys).join(),
413 393 startTs: Date.now() - 1 * 24 * 60 * 60 * 1000,
414 394 endTs: Date.now(),
415 395 agg: AggregateDataEnum.NONE,
... ... @@ -423,7 +403,7 @@
423 403 } else {
424 404 isNull.value = true;
425 405 }
426   - setChartOptions(res);
  406 + setOptions(setChartOptions(res));
427 407 };
428 408 function hasDeviceAttr() {
429 409 if (!unref(deviceAttrs).length) {
... ... @@ -435,29 +415,6 @@
435 415 }
436 416 }
437 417
438   - function setChartOptions(data, keys?) {
439   - const dataArray: any[] = [];
440   - for (const key in data) {
441   - for (const item1 of data[key]) {
442   - let { ts, value } = item1;
443   - const time = dateUtil(ts).format('YYYY-MM-DD HH:mm:ss');
444   - value = Number(value).toFixed(2);
445   - dataArray.push([time, value, key]);
446   - }
447   - }
448   - keys = keys ? [keys] : unref(deviceAttrs);
449   - const series: any = keys.map((item) => {
450   - return {
451   - name: item,
452   - type: 'line',
453   - data: dataArray.filter((item1) => item1[2] === item),
454   - };
455   - });
456   - resize();
457   - // 设置数据
458   - setOptions(eChartOptions(series, keys));
459   - }
460   -
461 418 const handleCancelModal = () => {
462 419 method.setFieldsValue({
463 420 [SchemaFiled.WAY]: QueryWay.LATEST,
... ...
... ... @@ -18,7 +18,7 @@
18 18 schemas: formSchema(),
19 19 showActionButtonGroup: false,
20 20 fieldMapToTime: [
21   - [SchemaFiled.DATE_RANGE, [SchemaFiled.START_TS, SchemaFiled.END_TS], 'YYYY-MM-DD HH:ss'],
  21 + [SchemaFiled.DATE_RANGE, [SchemaFiled.START_TS, SchemaFiled.END_TS], 'YYYY-MM-DD HH:mm:ss'],
22 22 ],
23 23 });
24 24
... ...
1 1 <script lang="ts" setup>
2   - import moment from 'moment';
3 2 import { nextTick, Ref, ref, unref } from 'vue';
4 3 import { getDeviceHistoryInfo } from '/@/api/alarm/position';
5 4 import { Empty } from 'ant-design-vue';
6 5 import { useECharts } from '/@/hooks/web/useECharts';
7   - import { dateUtil } from '/@/utils/dateUtil';
8   - import { AggregateDataEnum, eChartOptions } from '/@/views/device/localtion/config.data';
  6 + import { AggregateDataEnum } from '/@/views/device/localtion/config.data';
9 7 import { useGridLayout } from '/@/hooks/component/useGridLayout';
10 8 import { ColEx } from '/@/components/Form/src/types';
11   - import { DataSource, DeviceAttributeRecord } from '/@/api/dataBoard/model';
  9 + import { DataSource } from '/@/api/dataBoard/model';
12 10 import { useForm, BasicForm } from '/@/components/Form';
13   - import { formSchema, QueryWay, SchemaFiled } from '../config/historyTrend.config';
14   - import { DEFAULT_DATE_FORMAT } from '../config/util';
  11 + import { formSchema, SchemaFiled } from '../config/historyTrend.config';
15 12 import { Loading } from '/@/components/Loading';
16 13 import BasicModal from '/@/components/Modal/src/BasicModal.vue';
17 14 import { useModalInner } from '/@/components/Modal';
18   - import { getAllDeviceByOrg, getDeviceAttributes } from '/@/api/dataBoard';
19   - import { HistoryData } from '/@/api/alarm/position/model';
20   - import { EChartsOption } from 'echarts';
  15 + import { getAllDeviceByOrg } from '/@/api/dataBoard';
  16 + import { useHistoryData } from '/@/views/device/list/hook/useHistoryData';
21 17
22 18 type DeviceOption = Record<'label' | 'value' | 'organizationId', string>;
23 19
... ... @@ -25,37 +21,14 @@
25 21
26 22 const chartRef = ref();
27 23
28   - const deviceAttrs = ref<DeviceAttributeRecord[]>([]);
29   -
30 24 const loading = ref(false);
31 25
32 26 const isNull = ref(false);
33 27
34   - function getSearchParams(value: Recordable) {
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   - };
47   - if (way === QueryWay.LATEST) {
48   - return Object.assign(basicRecord, {
49   - startTs: moment().subtract(startTs, 'ms').valueOf(),
50   - endTs: Date.now(),
51   - });
52   - } else {
53   - return Object.assign(basicRecord, {
54   - startTs: moment(startTs).valueOf(),
55   - endTs: moment(endTs).valueOf(),
56   - });
57   - }
58   - }
  28 + const { deviceAttrs, getDeviceKeys, getSearchParams, setChartOptions, getDeviceAttribute } =
  29 + useHistoryData();
  30 +
  31 + const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
59 32
60 33 function hasDeviceAttr() {
61 34 if (!unref(deviceAttrs).length) {
... ... @@ -65,30 +38,6 @@
65 38 }
66 39 }
67 40
68   - function setChartOptions(data: HistoryData, keys?: string | string[]) {
69   - const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
70   -
71   - const dataArray: [string, string, string][] = [];
72   - for (const key in data) {
73   - for (const item1 of data[key]) {
74   - let { ts, value } = item1;
75   - const time = dateUtil(ts).format(DEFAULT_DATE_FORMAT);
76   - value = Number(value).toFixed(2);
77   - dataArray.push([time, value, key as string]);
78   - }
79   - }
80   - keys = keys ? [keys as string] : unref(deviceAttrs).map((item) => item.identifier);
81   - const series: EChartsOption['series'] = (keys as string[]).map((item) => {
82   - return {
83   - name: item,
84   - type: 'line',
85   - data: dataArray.filter((temp) => temp[2] === item),
86   - };
87   - });
88   - // 设置数据
89   - setOptions(eChartOptions(series, keys));
90   - }
91   -
92 41 const [register, method] = useForm({
93 42 schemas: formSchema(),
94 43 baseColProps: useGridLayout(2, 3, 4) as unknown as ColEx,
... ... @@ -119,7 +68,11 @@
119 68 } else {
120 69 isNull.value = true;
121 70 }
122   - setChartOptions(res, value.keys);
  71 +
  72 + const selectedKeys = unref(deviceAttrs).find(
  73 + (item) => item.identifier === value[SchemaFiled.KEYS]
  74 + );
  75 + setOptions(setChartOptions(res, selectedKeys));
123 76 },
124 77 });
125 78
... ... @@ -128,8 +81,7 @@
128 81 try {
129 82 const options = await getAllDeviceByOrg(organizationId);
130 83 const record = options.find((item) => item.id === value);
131   - const { deviceProfileId } = record!;
132   - deviceAttrs.value = (await getDeviceAttributes({ deviceProfileId })) || [];
  84 + await getDeviceAttribute(record!);
133 85 await nextTick();
134 86 method.updateSchema({
135 87 field: SchemaFiled.KEYS,
... ... @@ -157,9 +109,7 @@
157 109
158 110 const res = await getDeviceHistoryInfo({
159 111 entityId: deviceId,
160   - keys: unref(deviceAttrs)
161   - .map((item) => item.identifier)
162   - .join(),
  112 + keys: unref(getDeviceKeys).join(),
163 113 startTs: Date.now() - 1 * 24 * 60 * 60 * 1000,
164 114 endTs: Date.now(),
165 115 agg: AggregateDataEnum.NONE,
... ... @@ -173,7 +123,7 @@
173 123 } else {
174 124 isNull.value = true;
175 125 }
176   - setChartOptions(res);
  126 + setOptions(setChartOptions(res));
177 127 };
178 128
179 129 const generateDeviceOptions = (dataSource: DataSource[]) => {
... ...