@ledvance/group-ui-biz-bundle 1.0.139 → 1.0.141

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,10 @@
1
+ import {
2
+ ChartSection
3
+ } from '@ledvance/group-ui-biz-bundle/src/modules/energyConsumption/EnergyConsumptionChart/ChartSection'
4
+ import { getStyles } from '@ledvance/group-ui-biz-bundle/src/modules/energyConsumption/EnergyConsumptionChart/styles'
5
+ import {
6
+ useEnergyData
7
+ } from '@ledvance/group-ui-biz-bundle/src/modules/energyConsumption/EnergyConsumptionChart/useEnergyData'
1
8
  import React, { useEffect, useMemo } from 'react';
2
9
  import { ScrollView, StyleSheet, View, Text, Image, TouchableOpacity } from 'react-native';
3
10
  import { useNavigation } from '@react-navigation/core';
@@ -103,6 +110,25 @@ const EnergyConsumptionPage = (props: { theme?: ThemeType }) => {
103
110
  : '';
104
111
  }, [state.solarOverviewList, state.wifiOverviewList, state.isSolarMode]);
105
112
 
113
+ const chartParams = useMemo(() => {
114
+ return {
115
+ backTitle,
116
+ headlineText: chartHeadline,
117
+ chartData: [],
118
+ price: state.price,
119
+ unit: state.unit,
120
+ addEleDpCode: params.addEleDpCode,
121
+ date: (new Date()).getFullYear().toString(),
122
+ deviceIdGroup: [],
123
+ displayMode: state.isSolarMode ? 'generation' : 'consumption',
124
+ consumptionDeviceIds: params.wifiPlugGroup,
125
+ generationDeviceIds: params.solarPlugGroup,
126
+ }
127
+ }, [backTitle, chartHeadline, state.price, state.unit, params.addEleDpCode, state.isSolarMode, params.wifiPlugGroup, params.solarPlugGroup])
128
+
129
+ const { state: chartState, actions } = useEnergyData(chartParams);
130
+ const chartStyles = useMemo(() => getStyles(props.theme), [props.theme]);
131
+
106
132
  useEffect(() => {
107
133
  getElectricity().then();
108
134
  }, []);
@@ -203,6 +229,7 @@ const EnergyConsumptionPage = (props: { theme?: ThemeType }) => {
203
229
  setEnergyData(data).then()
204
230
  state.price = data.price;
205
231
  state.unit = data.unit;
232
+ actions.setPriceAndUnit(data.price, data.unit)
206
233
  };
207
234
 
208
235
  const backTitle = useMemo(() => {
@@ -229,7 +256,7 @@ const EnergyConsumptionPage = (props: { theme?: ThemeType }) => {
229
256
  },
230
257
  consumptionNum: {
231
258
  color: props.theme?.global.secondBrand,
232
- fontFamily: 'helvetica_neue_lt_std_bd',
259
+ // fontFamily: 'helvetica_neue_lt_std_bd',
233
260
  },
234
261
  subContent: {
235
262
  flex: 1,
@@ -242,7 +269,7 @@ const EnergyConsumptionPage = (props: { theme?: ThemeType }) => {
242
269
  color: props.theme?.global.secondBrand,
243
270
  },
244
271
  titleText: {
245
- fontFamily: 'helvetica_neue_lt_std_roman',
272
+ // fontFamily: 'helvetica_neue_lt_std_roman',
246
273
  fontSize: cx(14),
247
274
  color: props.theme?.global.secondFontColor,
248
275
  textAlign: 'center',
@@ -299,8 +326,9 @@ const EnergyConsumptionPage = (props: { theme?: ThemeType }) => {
299
326
  )}
300
327
  headlineIcon={(state.isSolarMode ? state.solarOverviewList : state.wifiOverviewList).length ? res.download_icon : undefined}
301
328
  onHeadlineIconClick={() => {
329
+ const headers = [I18n.getLang('date'), `${I18n.getLang('consumption_data_annual_bar_chart_system_back_text')} (kWh)`, `Price(${state.unit})`]
302
330
  const values = (state.isSolarMode ? state.solarOverviewList : state.wifiOverviewList).map(item => [item.key, item.value, (Number(state.price) * Number(item.value)).toFixed(2)])
303
- exportEnergyCsv(values, state.unit)
331
+ exportEnergyCsv(headers, values)
304
332
  }}
305
333
  showGreenery={state.isSolarMode}
306
334
  greeneryIcon={res.energy_consumption_greenery}
@@ -314,6 +342,7 @@ const EnergyConsumptionPage = (props: { theme?: ThemeType }) => {
314
342
  tabStyle={{paddingHorizontal: cx(15)}}
315
343
  setIsFirst={(v) => {
316
344
  state.isSolarMode = !v
345
+ actions.setDisplayMode(!v ? 'generation' : 'consumption')
317
346
  }} />
318
347
  </View> : undefined
319
348
  }
@@ -345,6 +374,9 @@ const EnergyConsumptionPage = (props: { theme?: ThemeType }) => {
345
374
  addEleDpCode: params.addEleDpCode,
346
375
  date: (new Date()).getFullYear().toString(),
347
376
  deviceIdGroup: state.isSolarMode ? params.solarPlugGroup : params.wifiPlugGroup,
377
+ displayMode: (params.wifiPlugGroup.length && params.solarPlugGroup.length) ? 'both' : (params.wifiPlugGroup.length ? 'consumption' : 'generation'),
378
+ consumptionDeviceIds: params.wifiPlugGroup,
379
+ generationDeviceIds: params.solarPlugGroup,
348
380
  } as EnergyConsumptionChartProps)
349
381
  }}
350
382
  >
@@ -487,6 +519,17 @@ const EnergyConsumptionPage = (props: { theme?: ThemeType }) => {
487
519
  </View>
488
520
  </Card>
489
521
  <Spacer height={cx(30)} />
522
+ <View style={{ marginHorizontal: cx(24) }}>
523
+ <ChartSection
524
+ state={chartState}
525
+ actions={actions}
526
+ params={chartParams}
527
+ styles={chartStyles}
528
+ theme={props.theme}
529
+ chartHeight={cx(400)}
530
+ />
531
+ </View>
532
+ <Spacer height={cx(30)} />
490
533
  {/* Annual overview */}
491
534
  <OverView
492
535
  style={{marginHorizontal: cx(24)}}
@@ -501,6 +544,9 @@ const EnergyConsumptionPage = (props: { theme?: ThemeType }) => {
501
544
  addEleDpCode: params.addEleDpCode,
502
545
  date: (new Date()).getFullYear().toString(),
503
546
  deviceIdGroup: state.isSolarMode ? params.solarPlugGroup : params.wifiPlugGroup,
547
+ displayMode: (params.wifiPlugGroup.length && params.solarPlugGroup.length) ? 'both' : (params.wifiPlugGroup.length ? 'consumption' : 'generation'),
548
+ consumptionDeviceIds: params.wifiPlugGroup,
549
+ generationDeviceIds: params.solarPlugGroup,
504
550
  } as EnergyConsumptionChartProps)
505
551
  }}
506
552
  overviewItemClick={(item) => {
@@ -512,6 +558,9 @@ const EnergyConsumptionPage = (props: { theme?: ThemeType }) => {
512
558
  unit: state.unit,
513
559
  isSolarMode: state.isSolarMode,
514
560
  deviceIdGroup: state.isSolarMode ? params.solarPlugGroup : params.wifiPlugGroup,
561
+ displayMode: (params.wifiPlugGroup.length && params.solarPlugGroup.length) ? 'both' : (params.wifiPlugGroup.length ? 'consumption' : 'generation'),
562
+ consumptionDeviceIds: params.wifiPlugGroup,
563
+ generationDeviceIds: params.solarPlugGroup,
515
564
  updateEnergyData
516
565
  } as EnergyConsumptionDetailProps)
517
566
  }}
@@ -68,7 +68,7 @@ export default withTheme(function DateSelectedItem(props: DateSelectedItemProps)
68
68
  textAlign: 'center',
69
69
  flex: 1,
70
70
  color: props.theme?.textInput.fontColor,
71
- fontFamily: 'helvetica_neue_lt_std_roman',
71
+ // fontFamily: 'helvetica_neue_lt_std_roman',
72
72
  },
73
73
  modalRoot: {paddingTop: cx(20), backgroundColor: theme?.popup.cellBg},
74
74
  modalButtonParent: {flex: 1, height: cx(48)},
@@ -94,7 +94,7 @@ export default withTheme(function DateSwitch(props: DateSwitchProps) {
94
94
  minWidth: cx(200),
95
95
  textAlign: 'center',
96
96
  color: theme?.global.brand,
97
- fontFamily: 'helvetica_neue_lt_std_roman',
97
+ // fontFamily: 'helvetica_neue_lt_std_roman',
98
98
  }}>{state.headlineText}</Text>
99
99
  <TouchableOpacity
100
100
  disabled={state.disableArrowRight}
@@ -61,7 +61,7 @@ export default withTheme(function DateTypeItem(props: DateTypeItemProps) {
61
61
  textAlign: 'center',
62
62
  flex: 1,
63
63
  color: props.theme?.textInput.fontColor,
64
- fontFamily: 'helvetica_neue_lt_std_roman',
64
+ // fontFamily: 'helvetica_neue_lt_std_roman',
65
65
  }}>{state.dateTypeTitle}</Text>
66
66
  </View>
67
67
  </TouchableOpacity>
@@ -114,7 +114,7 @@ const EnergyModal = (props: EnergyModalProps) => {
114
114
  marginEnd: cx(6),
115
115
  fontSize: cx(16),
116
116
  color: props.theme?.textInput.fontColor,
117
- fontFamily: 'helvetica_neue_lt_std_roman',
117
+ // fontFamily: 'helvetica_neue_lt_std_roman',
118
118
  },
119
119
  textInputGroup: {
120
120
  flexDirection: 'row',
@@ -156,7 +156,7 @@ const EnergyModal = (props: EnergyModalProps) => {
156
156
  <View style={{ flexDirection: 'row', alignItems: 'center' }}>
157
157
  <View style={{ flex: 3 }}>
158
158
  <Spacer height={cx(4)} />
159
- <Text style={{ color: props.theme?.global.secondFontColor, marginStart: cx(13), fontFamily: 'helvetica_neue_lt_std_bd' }}>{I18n.getLang('consumption_data_price_per_kwh_headline_text')}</Text>
159
+ <Text style={{ color: props.theme?.global.secondFontColor, marginStart: cx(13) }}>{I18n.getLang('consumption_data_price_per_kwh_headline_text')}</Text>
160
160
  <View style={styles.textInputGroup}>
161
161
  <TextInput
162
162
  value={state.energyData?.price}
@@ -5,178 +5,226 @@ import React, { useMemo, useRef } from 'react'
5
5
  import { View } from 'react-native'
6
6
  import { Utils } from 'tuya-panel-kit'
7
7
  import { OverviewItem } from '../EnergyConsumptionPage'
8
+ import { EnergyDisplayMode } from '../EnergyConsumptionChart/useEnergyData'
8
9
 
9
10
  const { withTheme } = Utils.ThemeUtils
10
- const cx = Utils.RatioUtils.convertX
11
-
12
11
  interface BarChartProps {
13
- theme?: ThemeType
14
- data: OverviewItem[],
15
- price: number,
16
- unit: string,
17
- height: number
12
+ theme?: ThemeType;
13
+ consumedData: OverviewItem[];
14
+ generatedData: OverviewItem[];
15
+ consumedName?: string;
16
+ generatedName?: string;
17
+ price: number;
18
+ unit: string;
19
+ height: number;
20
+ displayMode?: EnergyDisplayMode;
18
21
  }
19
22
 
20
23
  const BarChartWithTouch = (props: BarChartProps) => {
21
- const echarts = useRef()
22
- const { data, height, price, unit, theme } = props
23
- const dataX = data?.map(item => {
24
- return item.chartTitle
25
- })
26
- const dataKwhY = data?.map(item => {
27
- return Number(item.value)
28
- })
29
- const dataPriceY = data?.map(item => {
30
- return ((isNaN(Number(item.value)) ? 0 : Number(item.value)) * price).toFixed(2)
31
- })
32
- const maxValue = useMemo(() => {
33
- let max = Math.max(...dataKwhY)
34
- if (max < 0.1) {
35
- max += 0.02
36
- max = max - (max % 0.02)
37
- } else if (max < 1) {
38
- max += 0.2
39
- max = max - (max % 0.2)
40
- } else if (max < 10) {
41
- max = Math.ceil(max) + 2
42
- max = max - (max % 2)
43
- }
44
- return max
45
- }, [dataKwhY])
46
- const gridRight = useMemo(() => {
47
- const max = Math.max(...dataPriceY.map(it => Number(it)))
48
- return max > 999 ? '15%' : '10%'
49
- }, [dataPriceY])
50
- const option = {
51
- tooltip: {
52
- show: true,
53
- triggerOn: 'click',
54
- trigger: 'axis',
55
- },
56
- legend: {
57
- show: true,
58
- data: ['kWh', unit],
59
- textStyle: {
60
- color: theme?.global.fontColor,
24
+ const echarts = useRef<ECharts>(null)
25
+ const {
26
+ consumedData,
27
+ generatedData,
28
+ consumedName = I18n.getLang('chart_legend_consumption'),
29
+ generatedName = I18n.getLang('chart_legend_generation'),
30
+ price,
31
+ unit,
32
+ height,
33
+ theme,
34
+ displayMode = 'consumption',
35
+ } = props
36
+
37
+ const chartKey = useMemo(() => {
38
+ return `chart-${displayMode}-${price}-${unit}`
39
+ }, [displayMode, price, unit])
40
+
41
+ // 数据对齐(不变)
42
+ const { dataX, alignedConsumed, alignedGenerated, alignedConsumedPrice, alignedGeneratedPrice } = useMemo(() => {
43
+ const combinedDataMap = new Map<string, { title: string; consumed?: number; generated?: number }>()
44
+
45
+ consumedData?.forEach(item => {
46
+ const key = item.headlineText
47
+ if (!combinedDataMap.has(key)) {
48
+ combinedDataMap.set(key, { title: item.chartTitle })
61
49
  }
62
- },
63
- grid: {
64
- right: gridRight,
65
- },
66
- xAxis: {
67
- data: dataX,
68
- axisTick: {
69
- show: false,
70
- },
71
- axisLabel: {
72
- show: true,
73
- color: props.theme?.global.secondFontColor,
74
- interval: 'auto',
75
- rotate: 45,
76
- align: 'right',
77
- verticalAlign: 'top',
78
- showMinLabel: true,
79
- showMaxLabel: true,
50
+ combinedDataMap.get(key)!.consumed = Number(item.value)
51
+ })
52
+
53
+ generatedData?.forEach(item => {
54
+ const key = item.headlineText
55
+ if (!combinedDataMap.has(key)) {
56
+ combinedDataMap.set(key, { title: item.chartTitle })
80
57
  }
81
- },
82
- yAxis: [{
83
- type: 'value',
84
- name: I18n.getLang('consumption_data_annual_bar_chart_text'),
85
- max: Math.max(maxValue, 0.02),
86
- min: 0,
87
- position: 'left',
88
- axisLabel: {
89
- formatter: function (value) {
90
- const kwh = parseFloat(value)
91
- let toFixed = 2
92
- if (kwh >= 100) {
93
- toFixed = 0
94
- } else if (kwh >= 10) {
95
- toFixed = 1
96
- }
97
- return kwh.toFixed(toFixed)
58
+ combinedDataMap.get(key)!.generated = Number(item.value)
59
+ })
60
+
61
+ const sortedKeys = Array.from(combinedDataMap.keys()).sort((a, b) => b.localeCompare(a))
62
+
63
+ const finalDataX: string[] = []
64
+ const finalAlignedConsumed: number[] = []
65
+ const finalAlignedGenerated: number[] = []
66
+ const finalAlignedConsumedPrice: string[] = []
67
+ const finalAlignedGeneratedPrice: string[] = []
68
+
69
+ sortedKeys.forEach(key => {
70
+ const dataPoint = combinedDataMap.get(key)!
71
+ const consumedVal = dataPoint.consumed || 0
72
+ const generatedVal = dataPoint.generated || 0
73
+
74
+ finalDataX.push(dataPoint.title)
75
+ finalAlignedConsumed.push(consumedVal)
76
+ finalAlignedGenerated.push(generatedVal)
77
+ finalAlignedConsumedPrice.push((consumedVal * price).toFixed(2))
78
+ finalAlignedGeneratedPrice.push((generatedVal * price).toFixed(2))
79
+ })
80
+
81
+ return {
82
+ dataX: finalDataX,
83
+ alignedConsumed: finalAlignedConsumed,
84
+ alignedGenerated: finalAlignedGenerated,
85
+ alignedConsumedPrice: finalAlignedConsumedPrice,
86
+ alignedGeneratedPrice: finalAlignedGeneratedPrice,
87
+ }
88
+ }, [consumedData, generatedData, price])
89
+
90
+ // 图例 & series 动态生成
91
+ const { legendData, seriesData } = useMemo(() => {
92
+ const finalLegend: string[] = []
93
+ const finalSeries: any[] = []
94
+
95
+ if (displayMode === 'consumption' || displayMode === 'both') {
96
+ finalLegend.push(consumedName, `${consumedName} ${unit}`)
97
+ finalSeries.push(
98
+ {
99
+ name: consumedName,
100
+ type: 'bar',
101
+ yAxisIndex: 0,
102
+ data: alignedConsumed,
103
+ itemStyle: { color: '#FFC2A9', borderRadius: 2 },
104
+ barMaxWidth: 10,
105
+ },
106
+ {
107
+ name: `${consumedName} ${unit}`,
108
+ type: 'line',
109
+ yAxisIndex: 1,
110
+ data: alignedConsumedPrice,
111
+ smooth: true,
112
+ showSymbol: false,
113
+ color: '#F49431',
114
+ }
115
+ )
116
+ }
117
+
118
+ if (displayMode === 'generation' || displayMode === 'both') {
119
+ finalLegend.push(generatedName, `${generatedName} ${unit}`)
120
+ finalSeries.push(
121
+ {
122
+ name: generatedName,
123
+ type: 'bar',
124
+ yAxisIndex: 0,
125
+ data: alignedGenerated,
126
+ itemStyle: { color: '#A7D7A7', borderRadius: 2 },
127
+ barMaxWidth: 10,
98
128
  },
99
- color: props.theme?.global.secondFontColor,
129
+ {
130
+ name: `${generatedName} ${unit}`,
131
+ type: 'line',
132
+ yAxisIndex: 1,
133
+ data: alignedGeneratedPrice,
134
+ smooth: true,
135
+ showSymbol: false,
136
+ color: '#5A9C5A',
137
+ }
138
+ )
139
+ }
140
+
141
+ return { legendData: finalLegend, seriesData: finalSeries }
142
+ }, [displayMode, unit, consumedName, generatedName, alignedConsumed, alignedGenerated, alignedConsumedPrice, alignedGeneratedPrice])
143
+
144
+ // yAxis 动态
145
+ const maxKwhValue = useMemo(() => {
146
+ const valuesToConsider: number[] = [];
147
+ if (displayMode === 'consumption' || displayMode === 'both') valuesToConsider.push(...alignedConsumed);
148
+ if (displayMode === 'generation' || displayMode === 'both') valuesToConsider.push(...alignedGenerated);
149
+
150
+ if (valuesToConsider.length === 0) return 0.2; // 默认值
151
+ let max = Math.max(...valuesToConsider);
152
+ if (max < 1) max += 0.2;
153
+ else if (max < 10) max = Math.ceil(max) + 2;
154
+ else max = Math.ceil(max * 1.2);
155
+ return max;
156
+ }, [displayMode, alignedConsumed, alignedGenerated]);
157
+ const maxPriceValue = useMemo(() => {
158
+ const valuesToConsider: number[] = [];
159
+ if (displayMode === 'consumption' || displayMode === 'both') valuesToConsider.push(...alignedConsumedPrice.map(Number));
160
+ if (displayMode === 'generation' || displayMode === 'both') valuesToConsider.push(...alignedGeneratedPrice.map(Number));
161
+ if (valuesToConsider.length === 0) return 1; // 默认值
162
+ const max = Math.max(...valuesToConsider);
163
+ return Math.ceil(max * 1.4);
164
+ }, [displayMode, alignedConsumedPrice, alignedGeneratedPrice]);
165
+
166
+ const option = useMemo(
167
+ () => ({
168
+ tooltip: { show: true, triggerOn: 'click', trigger: 'axis' },
169
+ legend: {
170
+ show: true,
171
+ data: legendData,
172
+ textStyle: { color: theme?.global.fontColor },
100
173
  },
101
- nameTextStyle: {
102
- fontSize: 14,
103
- align: 'left',
104
- padding: [0, 0, 0, cx(-30)],
105
- color: props.theme?.global.secondFontColor,
174
+ grid: {
175
+ top: '25%',
176
+ right: '15%',
177
+ left: '15%',
106
178
  },
107
- }, {
108
- type: 'value',
109
- name: I18n.formatValue('format_unit', unit),
110
- position: 'right',
111
- alignTicks: true,
112
- max: Math.ceil(price ? maxValue * price * 1.4 : 0),
113
- min: 0,
114
- minInterval: 1,
115
- axisLabel: {
116
- formatter: function (value) {
117
- const price = parseFloat(value)
118
- let toFixed = 2
119
- if (price >= 100) {
120
- toFixed = 0
121
- } else if (price >= 10) {
122
- toFixed = 1
123
- }
124
- return price.toFixed(toFixed)
179
+ xAxis: {
180
+ data: dataX,
181
+ axisTick: { show: false },
182
+ axisLabel: {
183
+ show: true,
184
+ color: theme?.global.secondFontColor,
185
+ rotate: 45,
186
+ align: 'right',
125
187
  },
126
- color: props.theme?.global.secondFontColor,
127
- },
128
- nameTextStyle: {
129
- fontSize: 14,
130
- align: 'right',
131
- padding: [0, cx(-30), 0, 0],
132
- color: props.theme?.global.secondFontColor,
133
188
  },
134
- }],
135
- series: [
136
- {
137
- name: 'kWh',
138
- type: 'bar',
139
- data: dataKwhY,
140
- itemStyle: {
141
- emphasis: {
142
- color: '#FFC2A9', // Color when bar is clicked
143
- },
144
- color: '#FFC2A9',
145
- borderRadius: 2,
189
+ yAxis: [
190
+ {
191
+ type: 'value',
192
+ name: I18n.getLang('consumption_data_annual_bar_chart_text'),
193
+ max: maxKwhValue,
194
+ min: 0,
195
+ axisLabel: { color: theme?.global.secondFontColor },
146
196
  },
147
- barMaxWidth: 10,
148
- select: {
149
- itemStyle: {
150
- borderColor: '#FFC2A9',
151
- }
197
+ {
198
+ type: 'value',
199
+ name: I18n.formatValue('format_unit', unit),
200
+ position: 'right',
201
+ max: maxPriceValue,
202
+ min: 0,
203
+ minInterval: 1,
204
+ axisLabel: { color: theme?.global.secondFontColor },
152
205
  },
153
- selectedMode: 'single',
206
+ ],
207
+ series: seriesData,
208
+ dataZoom: {
209
+ start: 0,
210
+ type: 'inside',
211
+ zoomLock: true,
154
212
  },
155
- {
156
- name: unit,
157
- type: 'line',
158
- data: dataPriceY,
159
- yAxisIndex: 1,
160
- smooth: true,
161
- showSymbol: false,
162
- color: '#F49431',
163
- selectedMode: 'single',
164
- }
165
- ],
166
- dataZoom: {
167
- start: 0,
168
- type: 'inside',
169
- zoomLock: true,
170
- },
171
- customMapData: {}
172
- }
213
+ }),
214
+ [
215
+ legendData,
216
+ seriesData,
217
+ dataX,
218
+ maxKwhValue,
219
+ maxPriceValue,
220
+ unit,
221
+ theme,
222
+ ]
223
+ )
224
+
173
225
  return (
174
226
  <View style={{ flex: 1 }}>
175
- <ECharts
176
- option={option}
177
- ref={echarts}
178
- height={height}
179
- />
227
+ <ECharts key={chartKey} option={option} ref={echarts} height={height}/>
180
228
  </View>
181
229
  )
182
230
  }
@@ -40,7 +40,7 @@ const OverView = (props: OverViewProps) => {
40
40
  listEmptyText: {
41
41
  color: props.theme?.global.fontColor,
42
42
  fontSize: cx(12),
43
- fontFamily: 'helvetica_neue_lt_std_roman',
43
+ // fontFamily: 'helvetica_neue_lt_std_roman',
44
44
  },
45
45
  overviewItemText: {
46
46
  color: props.theme?.global.fontColor,