@ledvance/group-ui-biz-bundle 1.0.140 → 1.0.142

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.
Files changed (22) hide show
  1. package/package.json +1 -1
  2. package/src/modules/biorhythm/IconSelect.tsx +92 -29
  3. package/src/modules/energyConsumption/EnergyConsumptionActions.ts +42 -16
  4. package/src/modules/energyConsumption/EnergyConsumptionCard.tsx +3 -3
  5. package/src/modules/energyConsumption/EnergyConsumptionChart/ChartSection.tsx +103 -15
  6. package/src/modules/energyConsumption/EnergyConsumptionChart/LandscapeView.tsx +1 -0
  7. package/src/modules/energyConsumption/EnergyConsumptionChart/PortraitView.tsx +3 -27
  8. package/src/modules/energyConsumption/EnergyConsumptionChart/styles.ts +13 -4
  9. package/src/modules/energyConsumption/EnergyConsumptionChart/useEnergyData.ts +28 -12
  10. package/src/modules/energyConsumption/EnergyConsumptionChart.tsx +4 -0
  11. package/src/modules/energyConsumption/EnergyConsumptionDetail.tsx +41 -31
  12. package/src/modules/energyConsumption/EnergyConsumptionPage.tsx +52 -3
  13. package/src/modules/energyConsumption/component/DateSelectedItem.tsx +1 -1
  14. package/src/modules/energyConsumption/component/DateSwitch.tsx +10 -8
  15. package/src/modules/energyConsumption/component/DateTypeItem.tsx +1 -1
  16. package/src/modules/energyConsumption/component/EnergyModal.tsx +2 -2
  17. package/src/modules/energyConsumption/component/NewBarChart.tsx +221 -153
  18. package/src/modules/energyConsumption/component/Overview.tsx +1 -1
  19. package/src/modules/flags/FlagInfo.tsx +1204 -1054
  20. package/src/modules/flags/FlagItem.tsx +1 -2
  21. package/src/modules/flags/FlagPage.tsx +19 -15
  22. package/src/modules/mood_new/MoodItem.tsx +0 -1
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "name": "@ledvance/group-ui-biz-bundle",
5
5
  "pid": [],
6
6
  "uiid": "",
7
- "version": "1.0.140",
7
+ "version": "1.0.142",
8
8
  "scripts": {},
9
9
  "dependencies": {
10
10
  "@ledvance/base": "^1.x",
@@ -1,33 +1,43 @@
1
- import {useNavigation} from '@react-navigation/native'
2
- import React, {useEffect, useState} from 'react'
3
- import {Image, ScrollView, Text, TouchableOpacity, View} from 'react-native'
4
- import {Utils} from 'tuya-panel-kit'
5
- import iconList from './iconListData'
1
+ import { useNavigation, useRoute } from '@react-navigation/native'
2
+ import React, { useCallback, useEffect, useMemo, useState } from 'react'
3
+ // 导入 Dimensions API 来获取屏幕宽度
4
+ import { Dimensions, Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
5
+ import { Utils } from 'tuya-panel-kit'
6
+ import iconList from '../biorhythm/iconListData'
6
7
  import LDVTopBar from '@ledvance/base/src/components/ldvTopBar'
7
8
  import I18n from '@ledvance/base/src/i18n'
8
- import { useParams } from '@ledvance/base/src/hooks/Hooks'
9
9
  import ThemeType from '@ledvance/base/src/config/themeType'
10
+ import { xLog } from '@ledvance/base/src/utils'
10
11
 
11
- const cx = Utils.RatioUtils.convertX
12
+ const { convertX: cx } = Utils.RatioUtils
12
13
  const { withTheme } = Utils.ThemeUtils
13
14
 
14
- interface IconSelectPageParams {
15
- id: string | number
16
- setIcon: (id: string | number) => void,
15
+ interface SceneDetailPageParams {
16
+ backText?: string
17
+ id: any
18
+ setIcon: (id) => void,
17
19
  iconIdList: any
18
20
  }
19
21
 
20
- function IconSelectPage(props: { theme?: ThemeType }) {
22
+ // --- 新增:定义常量,方便维护 ---
23
+ const ICON_WIDTH = cx(32)
24
+ const ICON_MARGIN = cx(10)
25
+ const CONTAINER_HORIZONTAL_PADDING = cx(24)
26
+ // 每个图标占据的总宽度(图片宽度 + 左右margin)
27
+ const ITEM_TOTAL_WIDTH = ICON_WIDTH + ICON_MARGIN * 2
28
+
29
+ function IconSelect(props: { theme?: ThemeType }) {
21
30
  const [list, setList] = useState(iconList)
22
31
  const navigation = useNavigation()
23
- const params = useParams<IconSelectPageParams>()
32
+ const params = useRoute().params as SceneDetailPageParams
33
+
24
34
  const setColor = id => {
25
35
  const newList = list?.map(item => {
26
36
  return {
27
37
  ...item,
28
38
  selectStatus: item?.id === id,
29
39
  }
30
- })
40
+ }) as typeof list
31
41
  setList(newList)
32
42
  }
33
43
  useEffect(() => {
@@ -39,24 +49,74 @@ function IconSelectPage(props: { theme?: ThemeType }) {
39
49
  selectStatus: item?.id === iconId,
40
50
  disabled: iconIdList?.some(val => val === item?.id && val !== iconId),
41
51
  }
42
- })
52
+ }) as typeof list
43
53
  setList(newList)
44
54
  }, [])
45
55
 
56
+ const getStyles = useCallback((theme?: ThemeType) => StyleSheet.create({
57
+ container: { flex: 1, flexDirection: 'column' },
58
+ scrollView: { marginHorizontal: CONTAINER_HORIZONTAL_PADDING },
59
+ titleView: { marginTop: cx(40), marginBottom: cx(20) },
60
+ title: { fontSize: cx(24), color: theme?.global.brand },
61
+ iconContainer: {
62
+ flexDirection: 'row',
63
+ flex: 1,
64
+ flexWrap: 'wrap',
65
+ justifyContent: 'space-between',
66
+ alignItems: 'center'
67
+ },
68
+ ghostItem: {
69
+ width: ICON_WIDTH,
70
+ height: 0,
71
+ margin: ICON_MARGIN,
72
+ }
73
+ }), [CONTAINER_HORIZONTAL_PADDING, ICON_WIDTH, ICON_MARGIN])
74
+
75
+ const styles = useMemo(() => getStyles(props.theme), [props.theme])
76
+
77
+ // --- 新增:动态计算需要渲染的幽灵元素数量 ---
78
+ const ghostElements = useMemo(() => {
79
+
80
+ // 1. 获取容器的可用宽度
81
+ const containerWidth = cx(Dimensions.get('window').width) - CONTAINER_HORIZONTAL_PADDING * 2
82
+
83
+ // 2. 计算每行可以容纳多少个元素
84
+ const itemsPerRow = Math.ceil(containerWidth / ITEM_TOTAL_WIDTH)
85
+ // 如果无法容纳任何元素,则不渲染幽灵元素
86
+ if (itemsPerRow <= 0) {
87
+ xLog('No ghost elements needed.')
88
+ return null
89
+ }
90
+
91
+ // 3. 计算需要补充的幽灵元素数量
92
+ const numberOfItems = list.length
93
+ const itemsInLastRow = numberOfItems % itemsPerRow
94
+
95
+ // 如果最后一行为空或已满,则不需要幽灵元素
96
+ if (itemsInLastRow === 0) {
97
+ return null
98
+ }
99
+
100
+ const numberOfGhosts = itemsPerRow - itemsInLastRow
101
+
102
+ // 4. 返回一个包含正确数量幽灵元素的数组
103
+ return Array.from({ length: numberOfGhosts }).map((_, index) => (
104
+ <View key={`ghost-${index}`} style={styles.ghostItem}/>
105
+ ))
106
+ }, [list.length]) // 依赖项:当图标总数变化时重新计算
107
+
46
108
  return (
47
- <View style={{flex: 1, flexDirection: 'column'}}>
109
+ <View style={styles.container}>
48
110
  <LDVTopBar
49
- title={I18n.getLang('add_new_trigger_time_system_back_text')}
50
- onBackPress={() => {
51
- navigation.goBack()
52
- }}
111
+ title={params.backText ?? I18n.getLang('add_new_trigger_time_system_back_text')}
112
+ onBackPress={() => navigation.goBack()}
53
113
  />
54
- <ScrollView nestedScrollEnabled={true} style={{marginHorizontal: cx(24)}}>
55
- <View style={{marginTop: cx(40), marginBottom: cx(20)}}>
56
- <Text style={{fontSize: cx(24), color: props.theme?.global.brand}}>{I18n.getLang('add_new_trigger_time_icon_selection_headline_text')}</Text>
114
+ <ScrollView nestedScrollEnabled={true} style={styles.scrollView}>
115
+ <View style={styles.titleView}>
116
+ <Text style={styles.title}>{I18n.getLang('add_new_trigger_time_icon_selection_headline_text')}</Text>
57
117
  </View>
58
- <View
59
- style={{flexDirection: 'row', flex: 1, flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'flex-start'}}>
118
+ <View style={styles.iconContainer}>
119
+ {/* 渲染真实的图标 */}
60
120
  {list?.map(item => {
61
121
  return <TouchableOpacity
62
122
  onPress={() => {
@@ -67,20 +127,23 @@ function IconSelectPage(props: { theme?: ThemeType }) {
67
127
  key={item.id}
68
128
  >
69
129
  <Image
70
- source={{uri: item?.icon}}
130
+ source={{ uri: item?.icon }}
71
131
  style={{
72
- width: cx(32),
73
- height: cx(32),
74
- margin: cx(10),
132
+ width: ICON_WIDTH,
133
+ height: cx(32), // 高度保持不变
134
+ margin: ICON_MARGIN,
75
135
  tintColor: item?.selectStatus ? props.theme?.icon.primary : item?.disabled && props.theme?.icon.disable || props.theme?.icon.normal,
76
136
  }}
77
137
  />
78
138
  </TouchableOpacity>
79
139
  })}
140
+
141
+ {/* 渲染精确计算出的幽灵元素 */}
142
+ {ghostElements}
80
143
  </View>
81
144
  </ScrollView>
82
145
  </View>
83
146
  )
84
147
  }
85
148
 
86
- export default withTheme(IconSelectPage)
149
+ export default withTheme(IconSelect)
@@ -12,8 +12,8 @@ import { OverviewItem } from "./EnergyConsumptionPage";
12
12
  import dayjs from "dayjs";
13
13
  import { isNumber } from "lodash";
14
14
  import { EnergyData, UnitList } from "./component/EnergyModal";
15
- import I18n from "@ledvance/base/src/i18n";
16
15
  import {NativeApi} from "@ledvance/base/src/api/native";
16
+ import {xLog, retryWithBackoff} from "@ledvance/base/src/utils"
17
17
 
18
18
  interface LightConfig {
19
19
  energyConsumption?: EnergyData
@@ -50,17 +50,18 @@ export async function getElectricity(devIdGroup: string[], addEleDpCode: string,
50
50
  return res
51
51
  }
52
52
 
53
- let dpResultByMonthCache: DpResultByMonthResData[] | undefined
53
+ let dpResultByMonthCache: {key: DpResultByMonthResData[]} = {}
54
54
  const getDpResultByYear = async (devIdGroup: string[], addEleDpCode: string, dateStr: string): Promise<OverviewItem[]> => {
55
55
  const year = dateStr;
56
- let successGroup = dpResultByMonthCache;
56
+ const key = devIdGroup.join()
57
+ let successGroup = dpResultByMonthCache[key];
57
58
  if (!successGroup) {
58
59
  const promiseGroup = devIdGroup.map(devId =>
59
60
  getDpResultByMonth(devId, addEleDpCode, 'sum').catch(error => ({ error }))
60
61
  );
61
62
  // @ts-ignore
62
- dpResultByMonthCache = (await Promise.all(promiseGroup)).filter(v => !v.error);
63
- successGroup = dpResultByMonthCache
63
+ dpResultByMonthCache[key] = (await Promise.all(promiseGroup)).filter(v => !v.error);
64
+ successGroup = dpResultByMonthCache[key]
64
65
  }
65
66
  if (!successGroup) {
66
67
  return []
@@ -79,7 +80,7 @@ const getDpResultByYear = async (devIdGroup: string[], addEleDpCode: string, dat
79
80
  })
80
81
  }
81
82
  })
82
- const curMonthList = Object.keys(mergedData).sort((a, b) => parseInt(b) - parseInt(a));
83
+ const curMonthList = Object.keys(mergedData).sort((a, b) => parseInt(a) - parseInt(b));
83
84
  return curMonthList.map(month => {
84
85
  return {
85
86
  key: `${monthFormat(month)} ${year}`,
@@ -134,11 +135,12 @@ const getDpResultByYearMonth = async (deviceIdGroup: string[], addEleDpCode: str
134
135
 
135
136
 
136
137
  const getDpResultByDate = async (deviceIdGroup: string[], addEleDpCode: string, date: string): Promise<OverviewItem[]> => {
137
- if (overDays(date, 7)) {
138
- console.log("getDpResultByDate overDays true")
138
+ const newDate = date.replaceAll('/', '')
139
+ if (overDays(newDate, 7)) {
140
+ xLog("getDpResultByDate overDays true")
139
141
  return []
140
142
  }
141
- const promiseGroup = deviceIdGroup.map(devId => getDpResultByHour(devId, addEleDpCode, date, 'sum').catch(error => ({ error })))
143
+ const promiseGroup = deviceIdGroup.map(devId => getDpResultByHour(devId, addEleDpCode, newDate, 'sum').catch(error => ({ error })))
142
144
  const res = await Promise.all(promiseGroup);
143
145
  // @ts-ignore
144
146
  const successGroup: DpResultByDataWithSpecifiedResData[] = res.filter(v => !v.error);
@@ -174,8 +176,7 @@ const getDpResultByDate = async (deviceIdGroup: string[], addEleDpCode: string,
174
176
  return list
175
177
  }
176
178
 
177
- export const exportEnergyCsv = (values: any[][], unit: string) => {
178
- const headers = [I18n.getLang('date'), `${I18n.getLang('consumption_data_annual_bar_chart_system_back_text')} (kWh)`, `Price(${unit})`]
179
+ export const exportEnergyCsv = (headers: string[], values: any[][]) => {
179
180
  const functionName = 'EnergyConsumption'
180
181
  exportCsvFile(headers, values, functionName)
181
182
  }
@@ -195,12 +196,37 @@ export interface EnergyGeneration {
195
196
  history: EnergyHistory[]
196
197
  }
197
198
 
199
+ async function getJsonWithError(devId: string, key: string) {
200
+ const res = await NativeApi.getJson(devId, key)
201
+ if (!res.success) {
202
+ xLog(`getJsonWithError ${devId} ${key} failed`, res)
203
+ throw new Error(`API失败: ${res.msg || '未知错误'}`, { cause: res })
204
+ }
205
+ return res
206
+ }
207
+
198
208
  export async function getEnergyGenerationValue(devId: string): Promise<EnergyGeneration | undefined> {
199
- const res = await NativeApi.getJson(devId, EnergyGenerationId)
200
- if (res.success && res.data) {
201
- return JSON.parse(res.data)
202
- } else {
203
- console.log('getEnergyGenerationValue failed', res)
209
+ try {
210
+ const res = await retryWithBackoff(
211
+ () => getJsonWithError(devId, EnergyGenerationId),
212
+ {
213
+ // 自定义判断哪些错误需要重试
214
+ shouldRetry: (error) => {
215
+ // 网络错误、超时错误或服务器错误(5xx)通常需要重试
216
+ const isNetworkError = !error.cause?.success && error.cause?.msg === 'connection closed'
217
+
218
+ return isNetworkError;
219
+ }
220
+ }
221
+ )
222
+ if (res.success && res.data) {
223
+ return JSON.parse(res.data)
224
+ } else {
225
+ return undefined
226
+ }
227
+ } catch (error) {
228
+ xLog('getEnergyGenerationValue error', error.cause)
204
229
  return undefined
205
230
  }
231
+
206
232
  }
@@ -174,7 +174,7 @@ const EnergyConsumptionCard = (props: EnergyConsumptionProp) => {
174
174
  consumedEnergyItemUnit: {
175
175
  color: props.theme?.global.secondFontColor,
176
176
  fontSize: cx(14),
177
- fontFamily: 'helvetica_neue_lt_std_roman',
177
+ // fontFamily: 'helvetica_neue_lt_std_roman',
178
178
  textAlign: 'center'
179
179
  },
180
180
  subContent: {
@@ -188,13 +188,13 @@ const EnergyConsumptionCard = (props: EnergyConsumptionProp) => {
188
188
  color: props.theme?.global.secondBrand,
189
189
  },
190
190
  titleText: {
191
- fontFamily: 'helvetica_neue_lt_std_roman',
191
+ // fontFamily: 'helvetica_neue_lt_std_roman',
192
192
  fontSize: cx(14),
193
193
  color: props.theme?.global.secondFontColor,
194
194
  textAlign: 'center',
195
195
  },
196
196
  unitText: {
197
- fontFamily: 'helvetica_neue_lt_std_roman',
197
+ // fontFamily: 'helvetica_neue_lt_std_roman',
198
198
  fontSize: cx(14),
199
199
  color: props.theme?.global.secondFontColor,
200
200
  },
@@ -1,13 +1,18 @@
1
1
  import I18n from '@ledvance/base/src/i18n'
2
2
  import React, { useCallback } from 'react'
3
- import { View } from 'react-native'
3
+ import { Image, TouchableOpacity, View } from 'react-native'
4
4
  import { DateType } from '../co2Data'
5
- import DateSelectedItem from '../component/DateSelectedItem'
6
5
  import DateTypeItem from '../component/DateTypeItem'
6
+ import DateSwitch from '../component/DateSwitch'
7
7
  import NewBarChart from '../component/NewBarChart'
8
8
  import { EmptyDataView } from './EmptyDataView'
9
+ import { exportEnergyCsv } from '../EnergyConsumptionActions'
10
+ import { Utils } from 'tuya-panel-kit'
11
+ import res from '@ledvance/base/src/res'
9
12
 
10
- export const ChartSection = ({ state, actions, params, styles, theme, chartHeight }) => {
13
+ const { convertX: cx } = Utils.RatioUtils
14
+
15
+ export const ChartSection = ({ isLandscape, state, actions, params, styles, theme, chartHeight }) => {
11
16
  const getEmptyDataTip = useCallback(() => {
12
17
  if (state.over365Days && state.dateType !== DateType.Day) {
13
18
  return I18n.getLang('energyconsumption_Daylimit')
@@ -18,7 +23,77 @@ export const ChartSection = ({ state, actions, params, styles, theme, chartHeigh
18
23
  return I18n.getLang('energyconsumption_emptydata')
19
24
  }, [state.dateType, state.over365Days, state.over7Days])
20
25
 
21
- const isDataEmpty = state.chartData.length <= 0
26
+ const handleExportCsv = useCallback(() => {
27
+ const displayMode = state.displayMode || 'consumption'
28
+ const consumedData = state.consumptionChartData
29
+ const generatedData = state.generationChartData
30
+ const price = params.price
31
+ const unit = params.unit
32
+ const consumedName = I18n.getLang('chart_legend_consumption')
33
+ const generatedName = I18n.getLang('chart_legend_generation')
34
+ // 1. 创建CSV头部 (与之前相同)
35
+ const header = [I18n.getLang('date')]
36
+ if (displayMode === 'consumption' || displayMode === 'both') {
37
+ header.push(`${consumedName} (kWh)`, `${consumedName} (${unit})`)
38
+ }
39
+ if (displayMode === 'generation' || displayMode === 'both') {
40
+ header.push(`${generatedName} (kWh)`, `${generatedName} (${unit})`)
41
+ }
42
+ const rows = []
43
+ let i = 0 // 指向 consumedData 的指针
44
+ let j = 0 // 指向 generatedData 的指针
45
+ // 2. 双指针合并算法
46
+ while (i < consumedData.length || j < generatedData.length) {
47
+ const consumedItem = consumedData[i]
48
+ const generatedItem = generatedData[j]
49
+ const consumedKey = consumedItem?.headlineText
50
+ const generatedKey = generatedItem?.headlineText
51
+ let rowData = []
52
+ let dateKey = ''
53
+ if (consumedKey === generatedKey) {
54
+ dateKey = consumedItem.key
55
+ const consumedValue = Number(consumedItem.value)
56
+ const generatedValue = Number(generatedItem.value)
57
+ if (displayMode === 'consumption' || displayMode === 'both') {
58
+ rowData.push(consumedValue.toFixed(2), (consumedValue * price).toFixed(2))
59
+ }
60
+ if (displayMode === 'generation' || displayMode === 'both') {
61
+ rowData.push(generatedValue.toFixed(2), (generatedValue * price).toFixed(2))
62
+ }
63
+ i++
64
+ j++
65
+ } else if (consumedKey > generatedKey || !generatedKey) {
66
+ dateKey = consumedItem.key
67
+ const consumedValue = Number(consumedItem.value)
68
+ if (displayMode === 'consumption' || displayMode === 'both') {
69
+ rowData.push(consumedValue.toFixed(2), (consumedValue * price).toFixed(2))
70
+ }
71
+ if (displayMode === 'generation' || displayMode === 'both') {
72
+ // 在 'generation' 或 'both' 模式下,为缺失的产生数据补0
73
+ rowData.push('0.00', '0.00')
74
+ }
75
+ i++
76
+ } else {
77
+ dateKey = generatedItem.key
78
+ const generatedValue = Number(generatedItem.value)
79
+ if (displayMode === 'consumption' || displayMode === 'both') {
80
+ // 在 'consumption' 或 'both' 模式下,为缺失的消耗数据补0
81
+ rowData.push('0.00', '0.00')
82
+ }
83
+ if (displayMode === 'generation' || displayMode === 'both') {
84
+ rowData.push(generatedValue.toFixed(2), (generatedValue * price).toFixed(2))
85
+ }
86
+ j++
87
+ }
88
+
89
+ // 将日期和处理好的数据行组合起来
90
+ rows.push([dateKey, ...rowData])
91
+ }
92
+
93
+ exportEnergyCsv(header, rows)
94
+ }, [state.displayMode, state.consumptionChartData, state.generationChartData, params.price, params.unit])
95
+
96
+ const isDataEmpty = state.consumptionChartData.length <= 0 && state.generationChartData.length <= 0
22
97
 
23
98
  return (
24
99
  <>
@@ -28,10 +103,11 @@ export const ChartSection = ({ state, actions, params, styles, theme, chartHeigh
28
103
  dateType={state.dateType}
29
104
  onDateTypeChange={actions.setDateType}
30
105
  />
31
- <DateSelectedItem
32
- style={styles.dateSelectedItem}
33
- dateType={state.dateType}
106
+ <DateSwitch
107
+ style={{ flex: 1 }}
34
108
  date={state.date}
109
+ dateType={state.dateType}
110
+ headlineText={state.headlineText}
35
111
  onDateChange={actions.setDate}
36
112
  />
37
113
  </View>
@@ -39,14 +115,26 @@ export const ChartSection = ({ state, actions, params, styles, theme, chartHeigh
39
115
  {isDataEmpty ? (
40
116
  <EmptyDataView text={getEmptyDataTip()} theme={theme} styles={styles}/>
41
117
  ) : (
42
- !state.loading && (
43
- <NewBarChart
44
- height={chartHeight}
45
- data={state.chartData}
46
- price={state.price}
47
- unit={params.unit}
48
- />
49
- )
118
+ !state.loading && <>
119
+ {!isLandscape && <View style={styles.downloadContainer}>
120
+ <TouchableOpacity
121
+ style={{ width: cx(30) }}
122
+ onPress={handleExportCsv}>
123
+ <Image
124
+ style={styles.downloadIcon}
125
+ source={{ uri: res.download_icon }}/>
126
+ </TouchableOpacity>
127
+ </View>}
128
+ <NewBarChart
129
+ height={chartHeight}
130
+ data={state.chartData}
131
+ displayMode={state.displayMode}
132
+ consumedData={state.consumptionChartData}
133
+ generatedData={state.generationChartData}
134
+ price={state.price}
135
+ unit={params.unit}
136
+ />
137
+ </>
50
138
  )}
51
139
  </>
52
140
  )
@@ -31,6 +31,7 @@ export const LandscapeView = ({ state, actions, params, styles, theme, screenWid
31
31
 
32
32
  <View style={styles.landscapeContent}>
33
33
  <ChartSection
34
+ isLandscape={true}
34
35
  state={state}
35
36
  actions={actions}
36
37
  params={params}
@@ -1,21 +1,13 @@
1
1
  import Page from '@ledvance/base/src/components/Page'
2
2
  import res from '@ledvance/base/src/res'
3
- import React, { useCallback } from 'react'
4
- import { Image, ScrollView, TouchableOpacity, View } from 'react-native'
3
+ import React from 'react'
4
+ import { ScrollView, } from 'react-native'
5
5
  import { Utils } from 'tuya-panel-kit'
6
- import DateSwitch from '../component/DateSwitch'
7
- import { exportEnergyCsv } from '../EnergyConsumptionActions'
8
6
  import { ChartSection } from './ChartSection'
9
7
 
10
8
  const { convertX: cx } = Utils.RatioUtils
11
9
 
12
10
  export const PortraitView = ({ state, actions, params, styles, theme }) => {
13
-
14
- const handleExportCsv = useCallback(() => {
15
- const values = state.chartData.map(item => [item.key, item.value, (Number(params.price) * Number(item.value)).toFixed(2)])
16
- exportEnergyCsv(values, params.unit)
17
- }, [state.chartData, params.price, params.unit])
18
-
19
11
  return (
20
12
  <Page
21
13
  backText={params.backTitle}
@@ -25,26 +17,10 @@ export const PortraitView = ({ state, actions, params, styles, theme }) => {
25
17
  rightButtonIcon={state.isSupportLandscape ? res.screen_full : undefined}
26
18
  rightButtonStyle={styles.fullScreenIcon}
27
19
  rightButtonIconClick={actions.toggleLandscape}
28
- headlineContent={
29
- <View style={styles.headlineContainer}>
30
- <DateSwitch
31
- style={styles.dateSwitch}
32
- date={state.date}
33
- dateType={state.dateType}
34
- headlineText={state.headlineText}
35
- onDateChange={actions.setDate}
36
- />
37
- <TouchableOpacity style={styles.downloadButton} onPress={handleExportCsv}>
38
- <Image
39
- style={styles.downloadIcon}
40
- source={{ uri: !(state.chartData.length <= 0) ? res.download_icon : undefined }}
41
- />
42
- </TouchableOpacity>
43
- </View>
44
- }
45
20
  >
46
21
  <ScrollView nestedScrollEnabled={true} style={styles.scrollViewContent}>
47
22
  <ChartSection
23
+ isLandscape={false}
48
24
  state={state}
49
25
  actions={actions}
50
26
  params={params}
@@ -9,11 +9,16 @@ export const getStyles = (theme: ThemeType | undefined) => StyleSheet.create({
9
9
  scrollViewContent: {
10
10
  marginHorizontal: cx(24),
11
11
  },
12
+ dateSwitchContainer: {
13
+ width: '100%',
14
+ flexDirection: 'row',
15
+ marginVertical: cx(15),
16
+ },
12
17
  dateControlsContainer: {
13
18
  flexDirection: 'row',
14
19
  },
15
20
  dateTypeItem: {
16
- flex: 1,
21
+ flex: 0.5,
17
22
  },
18
23
  dateSelectedItem: {
19
24
  flex: 1,
@@ -72,12 +77,16 @@ export const getStyles = (theme: ThemeType | undefined) => StyleSheet.create({
72
77
  downloadButton: {
73
78
  width: cx(30),
74
79
  },
80
+ downloadContainer: {
81
+ flexDirection: 'row',
82
+ justifyContent: 'flex-end',
83
+ // marginHorizontal: cx(24),
84
+ marginTop: cx(15),
85
+ marginBottom: cx(-15)
86
+ },
75
87
  downloadIcon: {
76
88
  width: cx(24),
77
89
  height: cx(24),
78
90
  tintColor: theme?.global.brand,
79
- position: 'absolute',
80
- right: 0,
81
- top: cx(10),
82
91
  },
83
92
  })
@@ -3,13 +3,17 @@ import { overDays } from '@ledvance/base/src/utils'
3
3
  import { loopsText, monthFormat } from '@ledvance/base/src/utils/common'
4
4
  import { useReactive, useUpdateEffect } from 'ahooks'
5
5
  import dayjs from 'dayjs'
6
- import { useCallback } from 'react'
6
+ import { useCallback, useEffect } from 'react'
7
7
  import { DateType } from '../co2Data'
8
8
  import { getElectricity } from '../EnergyConsumptionActions'
9
9
  import { EnergyConsumptionChartProps } from '../EnergyConsumptionChart'
10
+ import {useIsPad} from "@ledvance/base/src/models/modules/NativePropsSlice"
11
+
12
+ export type EnergyDisplayMode = 'consumption' | 'generation' | 'both';
10
13
 
11
14
  export const useEnergyData = (params: EnergyConsumptionChartProps) => {
12
- const { addEleDpCode, price, date, over365Days, over7Days, chartData, deviceIdGroup } = params
15
+ const isPad = useIsPad()
16
+ const { addEleDpCode, price, unit, date, over365Days, over7Days, chartData, deviceIdGroup, consumptionDeviceIds, generationDeviceIds } = params
13
17
 
14
18
  const getDateType = useCallback((d: string) => {
15
19
  const datejs = dayjs(d)
@@ -24,13 +28,17 @@ export const useEnergyData = (params: EnergyConsumptionChartProps) => {
24
28
  const initialDateType = getDateType(date)
25
29
  const state = useReactive({
26
30
  loading: false,
27
- isSupportLandscape: OrientationService.isSupported(),
31
+ isSupportLandscape: OrientationService.isSupported() && !isPad,
28
32
  isLandscape: false,
29
33
  dateType: initialDateType,
30
34
  date: date,
31
35
  headlineText: initialDateType === DateType.Year ? date : params.headlineText,
32
36
  chartData: chartData.filter((item) => initialDateType !== DateType.Year || item.headlineText.startsWith(date)),
37
+ displayMode: params.displayMode || 'consumption',
38
+ consumptionChartData: [],
39
+ generationChartData: [],
33
40
  price: isNaN(Number(price)) ? 0 : Number(price),
41
+ unit: unit,
34
42
  over365Days: over365Days,
35
43
  over7Days: over7Days,
36
44
  })
@@ -53,16 +61,17 @@ export const useEnergyData = (params: EnergyConsumptionChartProps) => {
53
61
  }
54
62
  }, [state.dateType])
55
63
 
56
- useUpdateEffect(() => {
64
+ useEffect(() => {
57
65
  state.loading = true
58
- getElectricity(deviceIdGroup, addEleDpCode, state.date, state.dateType)
59
- .then((res) => {
60
- state.chartData = res
61
- state.loading = false
62
- })
63
- .catch(() => {
64
- state.loading = false
65
- })
66
+ const consumptionData = getElectricity(consumptionDeviceIds, addEleDpCode, state.date, state.dateType)
67
+ const generationData = getElectricity(generationDeviceIds, addEleDpCode, state.date, state.dateType)
68
+ Promise.all([consumptionData, generationData]).then(([consumptionRes, generationRes]) => {
69
+ state.consumptionChartData = consumptionRes
70
+ state.generationChartData = generationRes
71
+ state.loading = false
72
+ }).catch(() => {
73
+ state.loading = false
74
+ })
66
75
  }, [state.date])
67
76
 
68
77
  useUpdateEffect(() => {
@@ -96,6 +105,9 @@ export const useEnergyData = (params: EnergyConsumptionChartProps) => {
96
105
  setDateType: useCallback((type: DateType) => {
97
106
  state.dateType = type
98
107
  }, []),
108
+ setDisplayMode: useCallback((mode: EnergyDisplayMode) => {
109
+ state.displayMode = mode
110
+ }, []),
99
111
  toggleLandscape: useCallback(() => {
100
112
  if (!state.isSupportLandscape) return
101
113
  const newIsLandscape = !state.isLandscape
@@ -106,6 +118,10 @@ export const useEnergyData = (params: EnergyConsumptionChartProps) => {
106
118
  OrientationService.lockToPortrait()
107
119
  }
108
120
  }, []),
121
+ setPriceAndUnit: useCallback((newPrice: string, newUnit: string) => {
122
+ state.price = isNaN(Number(newPrice)) ? 0 : Number(newPrice)
123
+ state.unit = newUnit
124
+ }, []),
109
125
  }
110
126
 
111
127
  return { state, actions }
@@ -1,4 +1,5 @@
1
1
  import { OverviewItem } from './EnergyConsumptionPage'
2
+ import { EnergyDisplayMode } from './EnergyConsumptionChart/useEnergyData'
2
3
 
3
4
  export interface EnergyConsumptionChartProps {
4
5
  addEleDpCode: string
@@ -8,6 +9,9 @@ export interface EnergyConsumptionChartProps {
8
9
  over365Days?: boolean
9
10
  over7Days?: boolean,
10
11
  deviceIdGroup: string[];
12
+ displayMode?: EnergyDisplayMode;
13
+ consumptionDeviceIds: string[];
14
+ generationDeviceIds: string[];
11
15
  price: string,
12
16
  unit: string,
13
17
  date: string,