@ledvance/ui-biz-bundle 1.1.147 → 1.1.149
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.
- package/package.json +2 -1
- package/src/newModules/energyConsumption/EnergyConsumptionActions.ts +59 -44
- package/src/newModules/energyConsumption/EnergyConsumptionChart/EmptyDataView.tsx +21 -0
- package/src/newModules/energyConsumption/EnergyConsumptionChart/EnergyChartSection.tsx +75 -0
- package/src/newModules/energyConsumption/EnergyConsumptionChart/LandscapeView.tsx +46 -0
- package/src/newModules/energyConsumption/EnergyConsumptionChart/PortraitView.tsx +43 -0
- package/src/newModules/energyConsumption/EnergyConsumptionChart/PowerChartSection.tsx +42 -0
- package/src/newModules/energyConsumption/EnergyConsumptionChart/index.tsx +43 -0
- package/src/newModules/energyConsumption/EnergyConsumptionChart/styles.ts +95 -0
- package/src/newModules/energyConsumption/EnergyConsumptionChart/useEnergyData.ts +147 -0
- package/src/newModules/energyConsumption/EnergyConsumptionChart/useScreenDimensions.ts +36 -0
- package/src/newModules/energyConsumption/EnergyConsumptionChart.tsx +0 -327
- package/src/newModules/energyConsumption/EnergyConsumptionPage.tsx +29 -25
- package/src/newModules/energyConsumption/Router.ts +1 -1
- package/src/newModules/energyConsumption/component/DateSelectedItem.tsx +1 -1
- package/src/newModules/energyConsumption/component/PowerLineChart.tsx +2 -2
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"name": "@ledvance/ui-biz-bundle",
|
|
5
5
|
"pid": [],
|
|
6
6
|
"uiid": "",
|
|
7
|
-
"version": "1.1.
|
|
7
|
+
"version": "1.1.149",
|
|
8
8
|
"scripts": {},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@ledvance/base": "^1.x",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"prop-types": "^15.6.1",
|
|
17
17
|
"react": "16.8.3",
|
|
18
18
|
"react-native": "0.59.10",
|
|
19
|
+
"react-native-orientation-locker": "^1.7.0",
|
|
19
20
|
"react-native-svg": "5.5.1",
|
|
20
21
|
"react-redux": "^7.2.1",
|
|
21
22
|
"tuya-panel-kit": "^4.9.4"
|
|
@@ -150,7 +150,7 @@ export interface PowerDataItem {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
// 常量抽离(便于维护)
|
|
153
|
-
const DATA_POINT_INTERVAL_SEC =
|
|
153
|
+
const DATA_POINT_INTERVAL_SEC = 30; // 预设数据间隔(秒)
|
|
154
154
|
const TIME_UNIT = 'minute' as const; // 时间单位(与 interval 配合)
|
|
155
155
|
const RETRY_CONFIG = {
|
|
156
156
|
maxRetries: 5,
|
|
@@ -160,11 +160,26 @@ const RETRY_CONFIG = {
|
|
|
160
160
|
} as const;
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
|
-
*
|
|
163
|
+
* 创建一个0值填充的数据点
|
|
164
|
+
* @param timePoint Dayjs对象
|
|
165
|
+
* @returns PowerDataItem
|
|
166
|
+
*/
|
|
167
|
+
function createZeroPaddingPoint(timePoint: dayjs.Dayjs): PowerDataItem {
|
|
168
|
+
const timeMs = timePoint.valueOf();
|
|
169
|
+
return {
|
|
170
|
+
key: timePoint.format('HH:mm:ss'),
|
|
171
|
+
chartTitle: timePoint.format('MM/DD/YYYY HH:mm:ss'),
|
|
172
|
+
time: timeMs,
|
|
173
|
+
value: 0,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 获取设备功率数据(智能填充,高性能,避免在小间隔内插入0值)
|
|
164
179
|
* @param devId 设备ID
|
|
165
180
|
* @param powerDpCode 功率数据点编码
|
|
166
181
|
* @param interval 时间区间(分钟)
|
|
167
|
-
* @returns
|
|
182
|
+
* @returns 按时间排序的完整数据
|
|
168
183
|
*/
|
|
169
184
|
export async function getPowerData(
|
|
170
185
|
devId: string,
|
|
@@ -173,29 +188,12 @@ export async function getPowerData(
|
|
|
173
188
|
): Promise<PowerDataItem[]> {
|
|
174
189
|
try {
|
|
175
190
|
const now = dayjs();
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const startTimeMs = startTime.valueOf();
|
|
191
|
+
const endTime = now;
|
|
192
|
+
const startTime = now.add(-interval, TIME_UNIT);
|
|
179
193
|
const endTimeMs = endTime.valueOf();
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const expectedDataPoints = Math.floor(totalSeconds / DATA_POINT_INTERVAL_SEC);
|
|
184
|
-
const presetData: PowerDataItem[] = [];
|
|
185
|
-
for (let i = 0; i < expectedDataPoints; i++) {
|
|
186
|
-
const timePoint = startTime.add(i * DATA_POINT_INTERVAL_SEC, 'second');
|
|
187
|
-
const timeMs = timePoint.valueOf();
|
|
188
|
-
// 确保预设数据不超出查询区间(防止边界计算误差)
|
|
189
|
-
if (timeMs > endTimeMs) break;
|
|
190
|
-
presetData.push({
|
|
191
|
-
key: timePoint.format('HH:mm:ss'),
|
|
192
|
-
chartTitle: timePoint.format('MM/DD/YYYY HH:mm:ss'),
|
|
193
|
-
time: timeMs,
|
|
194
|
-
value: 0,
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// 2. 请求并处理实际数据(保留所有实际数据,不过滤)
|
|
194
|
+
const startTimeMs = startTime.valueOf();
|
|
195
|
+
const paddingIntervalMs = DATA_POINT_INTERVAL_SEC * 1000;
|
|
196
|
+
// 1. 请求已排序的实际数据
|
|
199
197
|
const dpResult = await getSpecifiedTimeDpReportLogs(
|
|
200
198
|
devId,
|
|
201
199
|
[powerDpCode],
|
|
@@ -204,37 +202,54 @@ export async function getPowerData(
|
|
|
204
202
|
endTimeMs.toString(),
|
|
205
203
|
RETRY_CONFIG
|
|
206
204
|
);
|
|
207
|
-
// 类型校验 + 格式转换(避免异常)
|
|
208
205
|
const validDpResult = Array.isArray(dpResult) ? (dpResult as DpReportSataData[]) : [];
|
|
209
206
|
const actualData: PowerDataItem[] = validDpResult
|
|
210
207
|
.map((dp) => {
|
|
211
|
-
const timeMs = dp.timeStamp * 1000;
|
|
212
|
-
// 过滤超出查询区间的实际数据(可选,根据业务需求)
|
|
208
|
+
const timeMs = dp.timeStamp * 1000;
|
|
213
209
|
if (timeMs < startTimeMs || timeMs > endTimeMs) return null;
|
|
214
210
|
return {
|
|
215
|
-
key: dayjs.unix(dp.timeStamp).format('HH:mm:ss'),
|
|
211
|
+
key: dayjs.unix(dp.timeStamp).format('HH:mm:ss'),
|
|
216
212
|
chartTitle: dayjs.unix(dp.timeStamp).format('MM/DD/YYYY HH:mm:ss'),
|
|
217
213
|
time: timeMs,
|
|
218
|
-
value: parseFloat(dp.value) / 10,
|
|
214
|
+
value: parseFloat(dp.value) / 10,
|
|
219
215
|
};
|
|
220
216
|
})
|
|
221
|
-
.filter((item): item is PowerDataItem => item !== null);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
217
|
+
.filter((item): item is PowerDataItem => item !== null);
|
|
218
|
+
// 如果没有任何真实数据,则生成完整的预设0值数据作为兜底
|
|
219
|
+
if (actualData.length === 0) {
|
|
220
|
+
const finalData: PowerDataItem[] = [];
|
|
221
|
+
let currentTime = startTime;
|
|
222
|
+
while (currentTime.valueOf() < endTimeMs) {
|
|
223
|
+
finalData.push(createZeroPaddingPoint(currentTime));
|
|
224
|
+
currentTime = currentTime.add(DATA_POINT_INTERVAL_SEC, 'second');
|
|
225
|
+
}
|
|
226
|
+
return finalData;
|
|
227
|
+
}
|
|
228
|
+
// 2. 高性能线性填充
|
|
229
|
+
const finalData: PowerDataItem[] = [];
|
|
230
|
+
let lastTimeMs = startTimeMs; // 游标从查询区间的开始时间算起
|
|
231
|
+
// 遍历所有真实数据点
|
|
232
|
+
actualData.forEach((currentPoint) => {
|
|
233
|
+
// 从上一个点的时间开始,用 while 循环填充,直到下一个真实数据点之前
|
|
234
|
+
let paddingTimeMs = lastTimeMs + paddingIntervalMs;
|
|
235
|
+
while (paddingTimeMs < currentPoint.time) {
|
|
236
|
+
finalData.push(createZeroPaddingPoint(dayjs(paddingTimeMs)));
|
|
237
|
+
paddingTimeMs += paddingIntervalMs;
|
|
238
|
+
}
|
|
233
239
|
|
|
234
|
-
|
|
240
|
+
// 添加当前的真实数据点
|
|
241
|
+
finalData.push(currentPoint);
|
|
242
|
+
lastTimeMs = currentPoint.time; // 更新游标
|
|
243
|
+
});
|
|
244
|
+
// 3. 填充查询末尾的空白区域(从最后一个真实数据点到查询结束时间)
|
|
245
|
+
let paddingTimeMs = lastTimeMs + paddingIntervalMs;
|
|
246
|
+
while (paddingTimeMs < endTimeMs) {
|
|
247
|
+
finalData.push(createZeroPaddingPoint(dayjs(paddingTimeMs)));
|
|
248
|
+
paddingTimeMs += paddingIntervalMs;
|
|
249
|
+
}
|
|
250
|
+
return finalData;
|
|
235
251
|
} catch (error) {
|
|
236
252
|
console.error(`[getPowerData] 失败:devId=${devId}, powerDpCode=${powerDpCode}`, error);
|
|
237
|
-
// 兜底返回空数组(或仅返回预设数据,根据业务容错需求调整)
|
|
238
253
|
return [];
|
|
239
254
|
}
|
|
240
255
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import InfoText from '@ledvance/base/src/components/InfoText'
|
|
2
|
+
import Spacer from '@ledvance/base/src/components/Spacer'
|
|
3
|
+
import res from '@ledvance/base/src/res'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import { Image, View } from 'react-native'
|
|
6
|
+
import { Utils } from 'tuya-panel-kit'
|
|
7
|
+
|
|
8
|
+
const { convertX: cx } = Utils.RatioUtils
|
|
9
|
+
|
|
10
|
+
export const EmptyDataView = ({ text, theme, styles, height }) => (
|
|
11
|
+
<View style={[styles.listEmptyView, { height }]}>
|
|
12
|
+
<Image style={styles.listEmptyImage} source={{ uri: res.ldv_timer_empty }}/>
|
|
13
|
+
<Spacer height={cx(5)}/>
|
|
14
|
+
<InfoText
|
|
15
|
+
text={text}
|
|
16
|
+
icon={res.ic_info}
|
|
17
|
+
textStyle={styles.listEmptyText}
|
|
18
|
+
contentColor={theme?.global.fontColor}
|
|
19
|
+
/>
|
|
20
|
+
</View>
|
|
21
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import I18n from '@ledvance/base/src/i18n'
|
|
2
|
+
import res from '@ledvance/base/src/res'
|
|
3
|
+
import React, { useCallback } from 'react'
|
|
4
|
+
import { Image, TouchableOpacity, View } from 'react-native'
|
|
5
|
+
import { Utils } from 'tuya-panel-kit'
|
|
6
|
+
import DateSelectedItem from '../component/DateSelectedItem'
|
|
7
|
+
import DateSwitch from '../component/DateSwitch'
|
|
8
|
+
import { DateType } from '../co2Data'
|
|
9
|
+
import DateTypeItem from '../component/DateTypeItem'
|
|
10
|
+
import NewBarChart from '../component/NewBarChart'
|
|
11
|
+
import { EmptyDataView } from './EmptyDataView'
|
|
12
|
+
import { exportEnergyCsv } from '../EnergyConsumptionActions'
|
|
13
|
+
|
|
14
|
+
const { convertX: cx } = Utils.RatioUtils
|
|
15
|
+
|
|
16
|
+
export const EnergyChartSection = ({ isLandscape, state, actions, params, styles, theme, chartHeight }) => {
|
|
17
|
+
const getEmptyDataTip = useCallback(() => {
|
|
18
|
+
if (state.over365Days && state.dateType !== DateType.Day) {
|
|
19
|
+
return I18n.getLang('energyconsumption_Daylimit')
|
|
20
|
+
}
|
|
21
|
+
if (state.dateType === DateType.Day && state.over7Days) {
|
|
22
|
+
return I18n.getLang('energyconsumption_hourlylimit')
|
|
23
|
+
}
|
|
24
|
+
return I18n.getLang('energyconsumption_emptydata')
|
|
25
|
+
}, [state.dateType, state.over365Days, state.over7Days])
|
|
26
|
+
|
|
27
|
+
const isDataEmpty = state.chartData.length <= 0
|
|
28
|
+
|
|
29
|
+
const handleExportCsv = useCallback(() => {
|
|
30
|
+
const values = state.chartData.map(item => [item.key, item.value, (Number(params.price) * Number(item.value)).toFixed(2)])
|
|
31
|
+
exportEnergyCsv(values, params.unit)
|
|
32
|
+
}, [state.chartData, params.price, params.unit])
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
{!isLandscape && (
|
|
37
|
+
<View style={styles.dateSwitchContainer}>
|
|
38
|
+
<DateSwitch
|
|
39
|
+
style={{ flex: 1 }}
|
|
40
|
+
date={state.date}
|
|
41
|
+
dateType={state.dateType}
|
|
42
|
+
headlineText={state.headlineText}
|
|
43
|
+
onDateChange={actions.setDate}
|
|
44
|
+
/>
|
|
45
|
+
<TouchableOpacity style={{ width: cx(30) }} onPress={handleExportCsv}>
|
|
46
|
+
<Image
|
|
47
|
+
style={styles.downloadIcon}
|
|
48
|
+
source={{ uri: !isDataEmpty ? res.download_icon : undefined }}
|
|
49
|
+
/>
|
|
50
|
+
</TouchableOpacity>
|
|
51
|
+
</View>
|
|
52
|
+
)}
|
|
53
|
+
<View style={styles.dateTypeContainer}>
|
|
54
|
+
<DateTypeItem
|
|
55
|
+
style={{ flex: 1, marginHorizontal: cx(5) }}
|
|
56
|
+
dateType={state.dateType}
|
|
57
|
+
onDateTypeChange={actions.setDateType}
|
|
58
|
+
/>
|
|
59
|
+
<DateSelectedItem
|
|
60
|
+
style={{ flex: 1 }}
|
|
61
|
+
dateType={state.dateType}
|
|
62
|
+
date={state.date}
|
|
63
|
+
onDateChange={actions.setDate}
|
|
64
|
+
/>
|
|
65
|
+
</View>
|
|
66
|
+
|
|
67
|
+
{(state.loading || isDataEmpty) ? (
|
|
68
|
+
<EmptyDataView text={getEmptyDataTip()} theme={theme} styles={styles}/>
|
|
69
|
+
) : (
|
|
70
|
+
!state.loading &&
|
|
71
|
+
<NewBarChart height={chartHeight} data={state.chartData} price={state.price} unit={params.unit}/>
|
|
72
|
+
)}
|
|
73
|
+
</>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import I18n from '@ledvance/base/src/i18n'
|
|
2
|
+
import res from '@ledvance/base/src/res'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { Image, Text, TouchableOpacity, View } from 'react-native'
|
|
5
|
+
import { Utils } from 'tuya-panel-kit'
|
|
6
|
+
import { ChartType } from '../co2Data'
|
|
7
|
+
import { EnergyChartSection } from './EnergyChartSection'
|
|
8
|
+
import { PowerChartSection } from './PowerChartSection'
|
|
9
|
+
|
|
10
|
+
const { convertX: cx, width } = Utils.RatioUtils
|
|
11
|
+
|
|
12
|
+
export const LandscapeView = ({ state, actions, params, styles, theme, screenWidth, screenHeight }) => (
|
|
13
|
+
<View style={[styles.landscapeContainer, { width: screenWidth, height: screenHeight }]}>
|
|
14
|
+
<View style={styles.landscapeHeader}>
|
|
15
|
+
<Text style={styles.landscapeTitle}>
|
|
16
|
+
{I18n.getLang(state.chartType === ChartType.kWh ? 'chartdisplay_energy' : 'chartdisplay_power')}
|
|
17
|
+
</Text>
|
|
18
|
+
<TouchableOpacity onPress={actions.toggleLandscape}>
|
|
19
|
+
<Image source={{ uri: res.screen_normal }} style={styles.normalScreenIcon}/>
|
|
20
|
+
</TouchableOpacity>
|
|
21
|
+
</View>
|
|
22
|
+
|
|
23
|
+
<View style={styles.landscapeContent}>
|
|
24
|
+
{state.chartType === ChartType.kWh ? (
|
|
25
|
+
<EnergyChartSection
|
|
26
|
+
isLandscape={true}
|
|
27
|
+
state={state}
|
|
28
|
+
actions={actions}
|
|
29
|
+
params={params}
|
|
30
|
+
styles={styles}
|
|
31
|
+
theme={theme}
|
|
32
|
+
chartHeight={screenHeight - cx(110)}
|
|
33
|
+
/>
|
|
34
|
+
) : (
|
|
35
|
+
<PowerChartSection
|
|
36
|
+
isLandscape={true}
|
|
37
|
+
state={state}
|
|
38
|
+
actions={actions}
|
|
39
|
+
styles={styles}
|
|
40
|
+
theme={theme}
|
|
41
|
+
chartHeight={screenHeight - cx(110)}
|
|
42
|
+
/>
|
|
43
|
+
)}
|
|
44
|
+
</View>
|
|
45
|
+
</View>
|
|
46
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import Page from '@ledvance/base/src/components/Page'
|
|
2
|
+
import Segmented from '@ledvance/base/src/components/Segmented'
|
|
3
|
+
import Spacer from '@ledvance/base/src/components/Spacer'
|
|
4
|
+
import I18n from '@ledvance/base/src/i18n'
|
|
5
|
+
import res from '@ledvance/base/src/res'
|
|
6
|
+
import React from 'react'
|
|
7
|
+
import { ScrollView } from 'react-native'
|
|
8
|
+
import { Utils } from 'tuya-panel-kit'
|
|
9
|
+
import { ChartType } from '../co2Data'
|
|
10
|
+
import { EnergyChartSection } from './EnergyChartSection'
|
|
11
|
+
import { PowerChartSection } from './PowerChartSection'
|
|
12
|
+
|
|
13
|
+
const { convertX: cx } = Utils.RatioUtils
|
|
14
|
+
|
|
15
|
+
export const PortraitView = ({ state, actions, params, styles, theme }) => (
|
|
16
|
+
<Page
|
|
17
|
+
backText={params.backTitle}
|
|
18
|
+
showGreenery={false}
|
|
19
|
+
loading={state.loading}
|
|
20
|
+
greeneryIcon={res.energy_consumption_greenery}
|
|
21
|
+
rightButtonIcon={state.isSupportLandscape ? res.screen_full : undefined}
|
|
22
|
+
rightButtonStyle={styles.fullScreenIcon}
|
|
23
|
+
rightButtonIconClick={actions.toggleLandscape}
|
|
24
|
+
>
|
|
25
|
+
<ScrollView nestedScrollEnabled={true} style={styles.scrollViewContent}>
|
|
26
|
+
<Spacer/>
|
|
27
|
+
<Segmented
|
|
28
|
+
options={[
|
|
29
|
+
{ label: I18n.getLang('chartdisplay_energy'), value: ChartType.kWh },
|
|
30
|
+
{ label: I18n.getLang('chartdisplay_power'), value: ChartType.Watt },
|
|
31
|
+
]}
|
|
32
|
+
value={state.chartType}
|
|
33
|
+
onChange={actions.setChartType}
|
|
34
|
+
/>
|
|
35
|
+
{state.chartType === ChartType.kWh ? (
|
|
36
|
+
<EnergyChartSection state={state} actions={actions} params={params} styles={styles} theme={theme}
|
|
37
|
+
chartHeight={cx(400)}/>
|
|
38
|
+
) : (
|
|
39
|
+
<PowerChartSection state={state} actions={actions} styles={styles} theme={theme} chartHeight={cx(400)}/>
|
|
40
|
+
)}
|
|
41
|
+
</ScrollView>
|
|
42
|
+
</Page>
|
|
43
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import I18n from '@ledvance/base/src/i18n'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { Text, TouchableOpacity, View } from 'react-native'
|
|
4
|
+
import PowerLineChart from '../component/PowerLineChart'
|
|
5
|
+
import { EmptyDataView } from './EmptyDataView'
|
|
6
|
+
|
|
7
|
+
const INTERVAL_OPTIONS = [
|
|
8
|
+
{ label: 'charttime_type1', value: 24 * 60 },
|
|
9
|
+
{ label: 'charttime_type2', value: 6 * 60 },
|
|
10
|
+
{ label: 'charttime_type3', value: 60 },
|
|
11
|
+
{ label: 'charttime_type4', value: 5 },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
export const PowerChartSection = ({ isLandscape, state, actions, styles, theme, chartHeight }) => {
|
|
15
|
+
const isDataEmpty = state.powerData.length <= 0
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
{isDataEmpty ? (
|
|
20
|
+
<EmptyDataView text={I18n.getLang('power_chart_empty')} theme={theme} styles={styles} height={chartHeight}/>
|
|
21
|
+
) : (
|
|
22
|
+
<PowerLineChart height={chartHeight} data={state.powerData}/>
|
|
23
|
+
)}
|
|
24
|
+
<View style={[styles.intervalContainer, isLandscape && styles.landscapeIntervalContainer]}>
|
|
25
|
+
{INTERVAL_OPTIONS.map(({ label, value }) => {
|
|
26
|
+
const isChecked = value === state.interval
|
|
27
|
+
return (
|
|
28
|
+
<TouchableOpacity
|
|
29
|
+
key={value}
|
|
30
|
+
style={[styles.intervalItem, isChecked && styles.intervalItemChecked]}
|
|
31
|
+
onPress={() => actions.setInterval(value)}
|
|
32
|
+
>
|
|
33
|
+
<Text style={isChecked ? styles.intervalTextChecked : styles.intervalText}>
|
|
34
|
+
{I18n.getLang(label)}
|
|
35
|
+
</Text>
|
|
36
|
+
</TouchableOpacity>
|
|
37
|
+
)
|
|
38
|
+
})}
|
|
39
|
+
</View>
|
|
40
|
+
</>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import ThemeType from '@ledvance/base/src/config/themeType'
|
|
2
|
+
import { useDeviceId } from '@ledvance/base/src/models/modules/NativePropsSlice'
|
|
3
|
+
import { useRoute } from '@react-navigation/core'
|
|
4
|
+
import React, { useMemo } from 'react'
|
|
5
|
+
import { Utils } from 'tuya-panel-kit'
|
|
6
|
+
import { EnergyConsumptionChartProps } from '../EnergyConsumptionChart'
|
|
7
|
+
import { LandscapeView } from './LandscapeView'
|
|
8
|
+
import { PortraitView } from './PortraitView'
|
|
9
|
+
import { getStyles } from './styles'
|
|
10
|
+
|
|
11
|
+
import { useEnergyData } from './useEnergyData'
|
|
12
|
+
import { useScreenDimensions } from './useScreenDimensions'
|
|
13
|
+
|
|
14
|
+
const { withTheme } = Utils.ThemeUtils
|
|
15
|
+
|
|
16
|
+
const EnergyConsumptionChartComponent = (props: { theme?: ThemeType }) => {
|
|
17
|
+
const devId = useDeviceId()
|
|
18
|
+
const params = useRoute().params as EnergyConsumptionChartProps
|
|
19
|
+
|
|
20
|
+
// Use the custom Hook to get all state and logic
|
|
21
|
+
const { state, actions } = useEnergyData(params, devId)
|
|
22
|
+
|
|
23
|
+
// Use useMemo to prevent re-creating styles on every render
|
|
24
|
+
const styles = useMemo(() => getStyles(props.theme), [props.theme])
|
|
25
|
+
|
|
26
|
+
const { width: screenWidth, height: screenHeight } = useScreenDimensions()
|
|
27
|
+
// Conditionally render the correct view based on orientation state
|
|
28
|
+
if (state.isLandscape) {
|
|
29
|
+
return <LandscapeView
|
|
30
|
+
state={state}
|
|
31
|
+
actions={actions}
|
|
32
|
+
params={params}
|
|
33
|
+
styles={styles}
|
|
34
|
+
theme={props.theme}
|
|
35
|
+
screenWidth={screenWidth}
|
|
36
|
+
screenHeight={screenHeight}
|
|
37
|
+
/>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return <PortraitView state={state} actions={actions} params={params} styles={styles} theme={props.theme}/>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default withTheme(EnergyConsumptionChartComponent)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import ThemeType from '@ledvance/base/src/config/themeType'
|
|
2
|
+
import { StyleSheet } from 'react-native'
|
|
3
|
+
import { Utils } from 'tuya-panel-kit'
|
|
4
|
+
|
|
5
|
+
const { convertX: cx, width, height } = Utils.RatioUtils
|
|
6
|
+
|
|
7
|
+
export const getStyles = (theme: ThemeType | undefined) => StyleSheet.create({
|
|
8
|
+
listEmptyView: {
|
|
9
|
+
alignItems: 'center',
|
|
10
|
+
justifyContent: 'center',
|
|
11
|
+
},
|
|
12
|
+
listEmptyImage: {
|
|
13
|
+
width: cx(180),
|
|
14
|
+
height: cx(180),
|
|
15
|
+
},
|
|
16
|
+
listEmptyText: {
|
|
17
|
+
flex: 0,
|
|
18
|
+
},
|
|
19
|
+
downloadIcon: {
|
|
20
|
+
width: cx(24),
|
|
21
|
+
height: cx(24),
|
|
22
|
+
tintColor: theme?.global.brand,
|
|
23
|
+
position: 'absolute',
|
|
24
|
+
right: 0,
|
|
25
|
+
top: cx(10),
|
|
26
|
+
},
|
|
27
|
+
intervalContainer: {
|
|
28
|
+
flexDirection: 'row',
|
|
29
|
+
justifyContent: 'center',
|
|
30
|
+
alignItems: 'center',
|
|
31
|
+
marginTop: cx(-10),
|
|
32
|
+
},
|
|
33
|
+
intervalItem: {
|
|
34
|
+
flex: 1,
|
|
35
|
+
marginHorizontal: cx(5),
|
|
36
|
+
padding: cx(5),
|
|
37
|
+
borderWidth: cx(1),
|
|
38
|
+
borderRadius: cx(25),
|
|
39
|
+
borderColor: theme?.icon.normal,
|
|
40
|
+
},
|
|
41
|
+
intervalItemChecked: {
|
|
42
|
+
borderColor: theme?.icon.primary,
|
|
43
|
+
},
|
|
44
|
+
intervalText: {
|
|
45
|
+
textAlign: 'center',
|
|
46
|
+
color: theme?.global.fontColor,
|
|
47
|
+
},
|
|
48
|
+
intervalTextChecked: {
|
|
49
|
+
textAlign: 'center',
|
|
50
|
+
color: theme?.icon.primary,
|
|
51
|
+
},
|
|
52
|
+
landscapeContainer: {
|
|
53
|
+
backgroundColor: theme?.global.background,
|
|
54
|
+
// width: cx(height + 20), // In landscape, width is screen height
|
|
55
|
+
// height: width, // In landscape, height is screen width
|
|
56
|
+
paddingVertical: cx(30),
|
|
57
|
+
paddingHorizontal: cx(20),
|
|
58
|
+
},
|
|
59
|
+
landscapeHeader: {
|
|
60
|
+
flexDirection: 'row',
|
|
61
|
+
justifyContent: 'space-between',
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
},
|
|
64
|
+
landscapeTitle: {
|
|
65
|
+
color: theme?.global.fontColor,
|
|
66
|
+
fontSize: cx(16),
|
|
67
|
+
fontWeight: 'bold',
|
|
68
|
+
},
|
|
69
|
+
landscapeContent: {
|
|
70
|
+
height: cx(width - 120),
|
|
71
|
+
},
|
|
72
|
+
landscapeIntervalContainer: {
|
|
73
|
+
marginTop: cx(-10),
|
|
74
|
+
},
|
|
75
|
+
dateSwitchContainer: {
|
|
76
|
+
width: '100%',
|
|
77
|
+
flexDirection: 'row',
|
|
78
|
+
marginVertical: cx(15),
|
|
79
|
+
},
|
|
80
|
+
dateTypeContainer: {
|
|
81
|
+
flexDirection: 'row',
|
|
82
|
+
marginBottom: cx(15),
|
|
83
|
+
},
|
|
84
|
+
scrollViewContent: {
|
|
85
|
+
marginHorizontal: cx(24),
|
|
86
|
+
},
|
|
87
|
+
fullScreenIcon: {
|
|
88
|
+
tintColor: theme?.global.brand,
|
|
89
|
+
},
|
|
90
|
+
normalScreenIcon: {
|
|
91
|
+
width: cx(50),
|
|
92
|
+
height: cx(50),
|
|
93
|
+
tintColor: theme?.icon.normal
|
|
94
|
+
}
|
|
95
|
+
})
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { queryDpIds } from '@ledvance/base/src/api/native'
|
|
2
|
+
import { OrientationService } from '@ledvance/base/src/api/OrientationService'
|
|
3
|
+
import { overDays } from '@ledvance/base/src/utils'
|
|
4
|
+
import { loopsText, monthFormat } from '@ledvance/base/src/utils/common'
|
|
5
|
+
import { useIsFocused } from '@react-navigation/native'
|
|
6
|
+
import { useInterval, useReactive, useUpdateEffect } from 'ahooks'
|
|
7
|
+
import dayjs from 'dayjs'
|
|
8
|
+
import { useCallback, useEffect } from 'react'
|
|
9
|
+
import { ChartType, DateType } from '../co2Data'
|
|
10
|
+
import { getElectricity, getPowerData, PowerDataItem } from '../EnergyConsumptionActions'
|
|
11
|
+
import { EnergyConsumptionChartProps } from '../EnergyConsumptionChart'
|
|
12
|
+
|
|
13
|
+
export const useEnergyData = (params: EnergyConsumptionChartProps, devId: string) => {
|
|
14
|
+
const isFocused = useIsFocused()
|
|
15
|
+
const { addEleDpCode, powerDpCode, price, date, over365Days, over7Days, chartData } = params
|
|
16
|
+
|
|
17
|
+
const getDateType = useCallback((d: string) => {
|
|
18
|
+
const datejs = dayjs(d)
|
|
19
|
+
if (datejs.isValid()) {
|
|
20
|
+
if (datejs.format('YYYY') === d) return DateType.Year
|
|
21
|
+
if (datejs.format('YYYYMM') === d) return DateType.Month
|
|
22
|
+
if (datejs.format('YYYY/MM/DD') === d) return DateType.Day
|
|
23
|
+
}
|
|
24
|
+
return DateType.Day
|
|
25
|
+
}, [])
|
|
26
|
+
|
|
27
|
+
const state = useReactive({
|
|
28
|
+
loading: false,
|
|
29
|
+
isSupportLandscape: OrientationService.isSupported(),
|
|
30
|
+
isLandscape: false,
|
|
31
|
+
chartType: ChartType.kWh as ChartType,
|
|
32
|
+
dateType: getDateType(date),
|
|
33
|
+
date: date,
|
|
34
|
+
headlineText: params.headlineText,
|
|
35
|
+
chartData: chartData.filter((item) => getDateType(date) !== DateType.Year || item.headlineText.startsWith(date)),
|
|
36
|
+
price: isNaN(Number(price)) ? 0 : Number(price),
|
|
37
|
+
over365Days: over365Days,
|
|
38
|
+
over7Days: over7Days,
|
|
39
|
+
interval: 5,
|
|
40
|
+
powerData: [] as PowerDataItem[],
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// 定时查询 DP 点
|
|
44
|
+
useInterval(() => {
|
|
45
|
+
if (isFocused) {
|
|
46
|
+
const jsonData = JSON.stringify([powerDpCode])
|
|
47
|
+
queryDpIds(jsonData, devId).then()
|
|
48
|
+
}
|
|
49
|
+
}, 5000, { immediate: true })
|
|
50
|
+
|
|
51
|
+
// 副作用:获取数据
|
|
52
|
+
useUpdateEffect(() => {
|
|
53
|
+
state.loading = true
|
|
54
|
+
if (state.chartType === ChartType.kWh) {
|
|
55
|
+
getElectricity(devId, addEleDpCode, state.date, state.dateType)
|
|
56
|
+
.then(res => {
|
|
57
|
+
state.chartData = res
|
|
58
|
+
state.loading = false
|
|
59
|
+
})
|
|
60
|
+
.catch(() => {
|
|
61
|
+
state.loading = false
|
|
62
|
+
})
|
|
63
|
+
} else {
|
|
64
|
+
getPowerData(devId, powerDpCode, state.interval)
|
|
65
|
+
.then((res: PowerDataItem[]) => {
|
|
66
|
+
state.powerData = res
|
|
67
|
+
state.loading = false
|
|
68
|
+
})
|
|
69
|
+
.catch(() => {
|
|
70
|
+
state.loading = false
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
}, [state.chartType, state.date, state.interval])
|
|
74
|
+
|
|
75
|
+
// 副作用:更新日期相关的状态和标题
|
|
76
|
+
const updateHeadlineText = useCallback((d: dayjs.Dayjs) => {
|
|
77
|
+
const year = d.year().toString()
|
|
78
|
+
const month = (d.month() + 1).toString().padStart(2, '0')
|
|
79
|
+
const day = d.date().toString().padStart(2, '0')
|
|
80
|
+
const dayOfWeek = d.day() % 7
|
|
81
|
+
switch (state.dateType) {
|
|
82
|
+
case DateType.Year:
|
|
83
|
+
state.headlineText = year
|
|
84
|
+
break
|
|
85
|
+
case DateType.Month:
|
|
86
|
+
state.headlineText = `${monthFormat(month)} ${year}`
|
|
87
|
+
break
|
|
88
|
+
case DateType.Day:
|
|
89
|
+
state.headlineText = `${day}.${month}.${year} (${loopsText[dayOfWeek]})`
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
}, [state.dateType])
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
state.over365Days = overDays(state.date, 365)
|
|
96
|
+
state.over7Days = overDays(state.date, 7)
|
|
97
|
+
updateHeadlineText(dayjs(state.date))
|
|
98
|
+
}, [state.date])
|
|
99
|
+
|
|
100
|
+
// 副作用:根据 dateType 自动切换到今天的日期
|
|
101
|
+
useUpdateEffect(() => {
|
|
102
|
+
const d = dayjs()
|
|
103
|
+
const year = d.year().toString()
|
|
104
|
+
const month = (d.month() + 1).toString().padStart(2, '0')
|
|
105
|
+
const day = d.date().toString().padStart(2, '0')
|
|
106
|
+
switch (state.dateType) {
|
|
107
|
+
case DateType.Year:
|
|
108
|
+
state.date = year
|
|
109
|
+
break
|
|
110
|
+
case DateType.Month:
|
|
111
|
+
state.date = `${year}${month}`
|
|
112
|
+
break
|
|
113
|
+
case DateType.Day:
|
|
114
|
+
state.date = `${year}${month}${day}`
|
|
115
|
+
break
|
|
116
|
+
}
|
|
117
|
+
}, [state.dateType])
|
|
118
|
+
|
|
119
|
+
// 封装所有修改状态的函数
|
|
120
|
+
const actions = {
|
|
121
|
+
setChartType: useCallback((type: ChartType) => {
|
|
122
|
+
state.chartType = type
|
|
123
|
+
}, []),
|
|
124
|
+
setDate: useCallback((newDate: string) => {
|
|
125
|
+
state.date = newDate
|
|
126
|
+
}, []),
|
|
127
|
+
setDateType: useCallback((type: DateType) => {
|
|
128
|
+
state.dateType = type
|
|
129
|
+
}, []),
|
|
130
|
+
setInterval: useCallback((newInterval: number) => {
|
|
131
|
+
state.interval = newInterval
|
|
132
|
+
state.powerData = []
|
|
133
|
+
}, []),
|
|
134
|
+
toggleLandscape: useCallback(() => {
|
|
135
|
+
if (!state.isSupportLandscape) return
|
|
136
|
+
const newIsLandscape = !state.isLandscape
|
|
137
|
+
state.isLandscape = newIsLandscape
|
|
138
|
+
if (newIsLandscape) {
|
|
139
|
+
OrientationService.lockToLandscape()
|
|
140
|
+
} else {
|
|
141
|
+
OrientationService.lockToPortrait()
|
|
142
|
+
}
|
|
143
|
+
}, []),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { state, actions }
|
|
147
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { Dimensions, ScaledSize, Platform } from 'react-native'
|
|
3
|
+
import { Utils } from 'tuya-panel-kit'
|
|
4
|
+
|
|
5
|
+
const { width, height, statusBarHeight } = Utils.RatioUtils
|
|
6
|
+
|
|
7
|
+
export const useScreenDimensions = () => {
|
|
8
|
+
const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
|
|
9
|
+
const [screenData, setScreenData] = useState({
|
|
10
|
+
width: screenWidth,
|
|
11
|
+
height: screenHeight,
|
|
12
|
+
})
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (Platform.OS === 'android') {
|
|
15
|
+
setScreenData({
|
|
16
|
+
width: height,
|
|
17
|
+
height: width - statusBarHeight,
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
const onChange = (result: { window: ScaledSize }) => {
|
|
21
|
+
if (Platform.OS === 'ios') {
|
|
22
|
+
setScreenData(result.window)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Subscribe to dimension changes
|
|
26
|
+
Dimensions.addEventListener('change', onChange)
|
|
27
|
+
// Don't forget to unsubscribe on cleanup
|
|
28
|
+
return () => {
|
|
29
|
+
Dimensions.removeEventListener('change', onChange)
|
|
30
|
+
}
|
|
31
|
+
}, [])
|
|
32
|
+
return {
|
|
33
|
+
...screenData,
|
|
34
|
+
isLandscape: screenData.width > screenData.height,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,33 +1,5 @@
|
|
|
1
|
-
import { queryDpIds } from '@ledvance/base/src/api/native'
|
|
2
|
-
import InfoText from '@ledvance/base/src/components/InfoText'
|
|
3
|
-
import Page from '@ledvance/base/src/components/Page'
|
|
4
|
-
import Segmented from '@ledvance/base/src/components/Segmented'
|
|
5
|
-
import Spacer from '@ledvance/base/src/components/Spacer'
|
|
6
|
-
import ThemeType from '@ledvance/base/src/config/themeType'
|
|
7
|
-
import I18n from '@ledvance/base/src/i18n'
|
|
8
|
-
import { useDeviceId } from '@ledvance/base/src/models/modules/NativePropsSlice'
|
|
9
|
-
import res from '@ledvance/base/src/res'
|
|
10
|
-
import { loopsText, monthFormat } from '@ledvance/base/src/utils/common'
|
|
11
|
-
import { overDays } from '@ledvance/base/src/utils'
|
|
12
|
-
import { useIsFocused } from '@react-navigation/core'
|
|
13
|
-
import PowerLineChart from './component/PowerLineChart'
|
|
14
|
-
import { useRoute } from '@react-navigation/core'
|
|
15
|
-
import { useInterval, useReactive, useUpdateEffect } from 'ahooks'
|
|
16
|
-
import dayjs from 'dayjs'
|
|
17
|
-
import React, { useCallback, useMemo } from 'react'
|
|
18
|
-
import { Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
|
|
19
|
-
import { Utils } from 'tuya-panel-kit'
|
|
20
|
-
import { ChartType, DateType } from './co2Data'
|
|
21
|
-
import DateSelectedItem from './component/DateSelectedItem'
|
|
22
|
-
import DateSwitch from './component/DateSwitch'
|
|
23
|
-
import DateTypeItem from './component/DateTypeItem'
|
|
24
|
-
import NewBarChart from './component/NewBarChart'
|
|
25
|
-
import { exportEnergyCsv, getElectricity, getPowerData, PowerDataItem } from './EnergyConsumptionActions'
|
|
26
1
|
import { OverviewItem } from './EnergyConsumptionPage'
|
|
27
2
|
|
|
28
|
-
const { convertX: cx } = Utils.RatioUtils
|
|
29
|
-
const { withTheme } = Utils.ThemeUtils
|
|
30
|
-
|
|
31
3
|
export interface EnergyConsumptionChartProps {
|
|
32
4
|
addEleDpCode: string
|
|
33
5
|
powerDpCode: string
|
|
@@ -40,302 +12,3 @@ export interface EnergyConsumptionChartProps {
|
|
|
40
12
|
unit: string,
|
|
41
13
|
date: string,
|
|
42
14
|
}
|
|
43
|
-
|
|
44
|
-
const EnergyConsumptionChart = (props: { theme?: ThemeType }) => {
|
|
45
|
-
const devId = useDeviceId()
|
|
46
|
-
const isFocused = useIsFocused()
|
|
47
|
-
|
|
48
|
-
const params = useRoute().params as EnergyConsumptionChartProps
|
|
49
|
-
const { backTitle, price, unit, date, addEleDpCode, powerDpCode, over365Days, over7Days } = params
|
|
50
|
-
|
|
51
|
-
useInterval(() => {
|
|
52
|
-
if (isFocused) {
|
|
53
|
-
const jsonData = JSON.stringify([powerDpCode])
|
|
54
|
-
queryDpIds(jsonData, devId).then()
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
5000,
|
|
58
|
-
{immediate: true}
|
|
59
|
-
)
|
|
60
|
-
const styles = StyleSheet.create({
|
|
61
|
-
listEmptyView: {
|
|
62
|
-
alignItems: 'center',
|
|
63
|
-
},
|
|
64
|
-
listEmptyImage: {
|
|
65
|
-
width: cx(200),
|
|
66
|
-
height: cx(200),
|
|
67
|
-
},
|
|
68
|
-
listEmptyText: {
|
|
69
|
-
flex: 0
|
|
70
|
-
},
|
|
71
|
-
downloadIcon: {
|
|
72
|
-
width: cx(24),
|
|
73
|
-
height: cx(24),
|
|
74
|
-
tintColor: props.theme?.global.brand,
|
|
75
|
-
position: 'absolute',
|
|
76
|
-
right: 0,
|
|
77
|
-
top: cx(10)
|
|
78
|
-
},
|
|
79
|
-
intervalContainer: {
|
|
80
|
-
flexDirection: 'row',
|
|
81
|
-
justifyContent: 'center',
|
|
82
|
-
alignItems: 'center',
|
|
83
|
-
},
|
|
84
|
-
intervalItem: {
|
|
85
|
-
flex: 1,
|
|
86
|
-
marginHorizontal: cx(5),
|
|
87
|
-
padding: cx(5),
|
|
88
|
-
borderWidth: cx(1),
|
|
89
|
-
borderRadius: cx(25),
|
|
90
|
-
borderColor: props.theme?.icon.normal,
|
|
91
|
-
},
|
|
92
|
-
intervalItemChecked: {
|
|
93
|
-
borderColor: props.theme?.icon.primary,
|
|
94
|
-
},
|
|
95
|
-
intervalText: {
|
|
96
|
-
textAlign: 'center',
|
|
97
|
-
color: props.theme?.global.fontColor,
|
|
98
|
-
},
|
|
99
|
-
intervalTextChecked: {
|
|
100
|
-
textAlign: 'center',
|
|
101
|
-
color: props.theme?.icon.primary,
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
const getDateType = useCallback((date: string) => {
|
|
106
|
-
const datejs = dayjs(date)
|
|
107
|
-
if (datejs.isValid()) {
|
|
108
|
-
if (datejs.format('YYYY') === date) {
|
|
109
|
-
return DateType.Year
|
|
110
|
-
} else if (datejs.format('YYYYMM') === date) {
|
|
111
|
-
return DateType.Month
|
|
112
|
-
} else if (datejs.format('YYYY/MM/DD') === date) {
|
|
113
|
-
return DateType.Day
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return DateType.Day
|
|
117
|
-
}, [])
|
|
118
|
-
const dateType = getDateType(date)
|
|
119
|
-
const intervalLabels = useMemo(() => {
|
|
120
|
-
return ['charttime_type1', 'charttime_type2', 'charttime_type3', 'charttime_type4']
|
|
121
|
-
}, [])
|
|
122
|
-
const state = useReactive({
|
|
123
|
-
loading: false,
|
|
124
|
-
chartType: ChartType.kWh,
|
|
125
|
-
dateType: dateType,
|
|
126
|
-
date: date,
|
|
127
|
-
headlineText: dateType === DateType.Year ? date : params.headlineText,
|
|
128
|
-
chartData: params.chartData.filter((item) => {
|
|
129
|
-
return dateType !== DateType.Year || item.headlineText.startsWith(date)
|
|
130
|
-
}),
|
|
131
|
-
price: isNaN(Number(price)) ? 0 : Number(price),
|
|
132
|
-
over365Days: over365Days,
|
|
133
|
-
over7Days: over7Days,
|
|
134
|
-
interval: 5,
|
|
135
|
-
powerData: [] as PowerDataItem[]
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
useUpdateEffect(() => {
|
|
139
|
-
state.over365Days = overDays(state.date, 365)
|
|
140
|
-
state.over7Days = overDays(state.date, 7)
|
|
141
|
-
updateHeadlineText(dayjs(state.date))
|
|
142
|
-
state.loading = true
|
|
143
|
-
if (state.chartType === ChartType.kWh) {
|
|
144
|
-
getElectricity(devId, addEleDpCode, state.date, state.dateType).then((res) => {
|
|
145
|
-
state.chartData = res
|
|
146
|
-
state.loading = false
|
|
147
|
-
}).catch(() => {
|
|
148
|
-
state.loading = false
|
|
149
|
-
})
|
|
150
|
-
} else {
|
|
151
|
-
getPowerData(devId, powerDpCode, state.interval).then((res: PowerDataItem[]) => {
|
|
152
|
-
state.powerData = res
|
|
153
|
-
state.loading = false
|
|
154
|
-
}).catch(() => {
|
|
155
|
-
state.loading = false
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
}, [state.chartType, state.date])
|
|
159
|
-
|
|
160
|
-
const getEmptyDataTip = useCallback(() => {
|
|
161
|
-
if (state.over365Days && state.dateType !== DateType.Day) {
|
|
162
|
-
return I18n.getLang('energyconsumption_Daylimit')
|
|
163
|
-
}
|
|
164
|
-
if (state.dateType === DateType.Day && state.over7Days) {
|
|
165
|
-
return I18n.getLang('energyconsumption_hourlylimit')
|
|
166
|
-
}
|
|
167
|
-
return I18n.getLang('energyconsumption_emptydata')
|
|
168
|
-
}, [state.dateType, state.over365Days, state.over7Days])
|
|
169
|
-
|
|
170
|
-
const updateHeadlineText = useCallback((date: dayjs.Dayjs) => {
|
|
171
|
-
const year = date.year().toString()
|
|
172
|
-
const month = (date.month() + 1).toString().padStart(2, '0')
|
|
173
|
-
const day = date.date().toString().padStart(2, '0')
|
|
174
|
-
const dayOfWeek = date.day() % 7
|
|
175
|
-
switch (state.dateType) {
|
|
176
|
-
case DateType.Year:
|
|
177
|
-
state.headlineText = year
|
|
178
|
-
break
|
|
179
|
-
case DateType.Month:
|
|
180
|
-
state.headlineText = `${monthFormat(month)} ${year}`
|
|
181
|
-
break
|
|
182
|
-
case DateType.Day:
|
|
183
|
-
state.headlineText = `${day}.${month}.${year} (${loopsText[dayOfWeek]})`
|
|
184
|
-
break
|
|
185
|
-
}
|
|
186
|
-
}, [state.dateType, state.headlineText])
|
|
187
|
-
|
|
188
|
-
useUpdateEffect(() => {
|
|
189
|
-
const date = dayjs()
|
|
190
|
-
const year = date.year().toString()
|
|
191
|
-
const month = (date.month() + 1).toString().padStart(2, '0')
|
|
192
|
-
const day = date.date().toString().padStart(2, '0')
|
|
193
|
-
switch (state.dateType) {
|
|
194
|
-
case DateType.Year:
|
|
195
|
-
state.date = year
|
|
196
|
-
break
|
|
197
|
-
case DateType.Month:
|
|
198
|
-
state.date = `${year}${month}`
|
|
199
|
-
break
|
|
200
|
-
case DateType.Day:
|
|
201
|
-
state.date = `${year}${month}${day}`
|
|
202
|
-
break
|
|
203
|
-
}
|
|
204
|
-
}, [state.dateType])
|
|
205
|
-
|
|
206
|
-
const requestPowerData = useCallback((interval: number) => {
|
|
207
|
-
state.loading = true
|
|
208
|
-
getPowerData(devId, powerDpCode, interval).then((res: PowerDataItem[]) => {
|
|
209
|
-
state.powerData = res
|
|
210
|
-
state.loading = false
|
|
211
|
-
}).catch(() => {
|
|
212
|
-
state.loading = false
|
|
213
|
-
})
|
|
214
|
-
}, [])
|
|
215
|
-
|
|
216
|
-
return (
|
|
217
|
-
<Page
|
|
218
|
-
backText={backTitle}
|
|
219
|
-
showGreenery={false}
|
|
220
|
-
loading={state.loading}
|
|
221
|
-
greeneryIcon={res.energy_consumption_greenery}
|
|
222
|
-
>
|
|
223
|
-
<ScrollView nestedScrollEnabled={true} style={{ marginHorizontal: cx(24) }}>
|
|
224
|
-
<Spacer/>
|
|
225
|
-
|
|
226
|
-
<Segmented
|
|
227
|
-
options={[
|
|
228
|
-
{ label: I18n.getLang('chartdisplay_energy'), value: ChartType.kWh },
|
|
229
|
-
{ label: I18n.getLang('chartdisplay_power'), value: ChartType.Watt }
|
|
230
|
-
]}
|
|
231
|
-
value={state.chartType}
|
|
232
|
-
onChange={(chartType: ChartType) => {
|
|
233
|
-
state.chartType = chartType
|
|
234
|
-
}}/>
|
|
235
|
-
{
|
|
236
|
-
state.chartType === ChartType.kWh ? (
|
|
237
|
-
<>
|
|
238
|
-
<View style={{ width: '100%', flexDirection: 'row', marginVertical: cx(15) }}>
|
|
239
|
-
<DateSwitch
|
|
240
|
-
style={{ flex: 1 }}
|
|
241
|
-
date={state.date}
|
|
242
|
-
dateType={state.dateType}
|
|
243
|
-
headlineText={state.headlineText}
|
|
244
|
-
onDateChange={(date) => {
|
|
245
|
-
state.date = date
|
|
246
|
-
}}/>
|
|
247
|
-
<TouchableOpacity
|
|
248
|
-
style={{ width: cx(30) }}
|
|
249
|
-
onPress={() => {
|
|
250
|
-
const values = state.chartData.map(item => [item.key, item.value, (Number(params.price) * Number(item.value)).toFixed(2)])
|
|
251
|
-
exportEnergyCsv(values, params.unit)
|
|
252
|
-
}}>
|
|
253
|
-
<Image
|
|
254
|
-
style={styles.downloadIcon}
|
|
255
|
-
source={{ uri: state.chartData?.length ? res.download_icon : undefined }}/>
|
|
256
|
-
</TouchableOpacity>
|
|
257
|
-
</View>
|
|
258
|
-
<View style={{ flexDirection: 'row', marginBottom: cx(15) }}>
|
|
259
|
-
<DateTypeItem
|
|
260
|
-
style={{ flex: 1, marginHorizontal: cx(5) }}
|
|
261
|
-
dateType={state.dateType}
|
|
262
|
-
onDateTypeChange={(dateType) => {
|
|
263
|
-
state.dateType = dateType
|
|
264
|
-
}}/>
|
|
265
|
-
<DateSelectedItem
|
|
266
|
-
style={{ flex: 1 }}
|
|
267
|
-
dateType={state.dateType}
|
|
268
|
-
date={state.date}
|
|
269
|
-
onDateChange={date => {
|
|
270
|
-
state.date = date
|
|
271
|
-
}}
|
|
272
|
-
/>
|
|
273
|
-
</View>
|
|
274
|
-
{
|
|
275
|
-
(state.chartData.length <= 0) ? (
|
|
276
|
-
<View style={styles.listEmptyView}>
|
|
277
|
-
<Spacer height={cx(26)}/>
|
|
278
|
-
<Image
|
|
279
|
-
style={styles.listEmptyImage}
|
|
280
|
-
source={{ uri: res.ldv_timer_empty }}/>
|
|
281
|
-
<Spacer height={cx(14)}/>
|
|
282
|
-
<InfoText
|
|
283
|
-
text={getEmptyDataTip()}
|
|
284
|
-
icon={res.ic_info}
|
|
285
|
-
textStyle={styles.listEmptyText}
|
|
286
|
-
contentColor={props.theme?.global.fontColor}
|
|
287
|
-
/>
|
|
288
|
-
</View>
|
|
289
|
-
) : (
|
|
290
|
-
!state.loading && <NewBarChart height={cx(400)} data={state.chartData} price={state.price} unit={unit}/>
|
|
291
|
-
)
|
|
292
|
-
}
|
|
293
|
-
</>
|
|
294
|
-
) : (
|
|
295
|
-
<>
|
|
296
|
-
{
|
|
297
|
-
(state.loading || state.powerData.length <= 0) ? (
|
|
298
|
-
<View style={[styles.listEmptyView, {marginVertical: cx(70)}]}>
|
|
299
|
-
<Spacer height={cx(26)}/>
|
|
300
|
-
<Image
|
|
301
|
-
style={styles.listEmptyImage}
|
|
302
|
-
source={{ uri: res.ldv_timer_empty }}/>
|
|
303
|
-
<Spacer height={cx(14)}/>
|
|
304
|
-
<InfoText
|
|
305
|
-
text={I18n.getLang('power_chart_empty')}
|
|
306
|
-
icon={res.ic_info}
|
|
307
|
-
textStyle={styles.listEmptyText}
|
|
308
|
-
contentColor={props.theme?.global.fontColor}
|
|
309
|
-
/>
|
|
310
|
-
</View>
|
|
311
|
-
) : (
|
|
312
|
-
<PowerLineChart height={cx(400)} data={state.powerData}/>
|
|
313
|
-
)
|
|
314
|
-
}
|
|
315
|
-
<View style={styles.intervalContainer}>
|
|
316
|
-
{
|
|
317
|
-
[24 * 60, 6 * 60, 60, 5].map((item, index) => {
|
|
318
|
-
const isChecked = item === state.interval
|
|
319
|
-
return (
|
|
320
|
-
<TouchableOpacity
|
|
321
|
-
key={index}
|
|
322
|
-
style={[styles.intervalItem, isChecked ? styles.intervalItemChecked : {}]}
|
|
323
|
-
onPress={() => {
|
|
324
|
-
state.interval = item
|
|
325
|
-
requestPowerData(item)
|
|
326
|
-
}}>
|
|
327
|
-
<Text
|
|
328
|
-
style={isChecked ? styles.intervalTextChecked : styles.intervalText}>{I18n.getLang(intervalLabels[index])}</Text>
|
|
329
|
-
</TouchableOpacity>)
|
|
330
|
-
})
|
|
331
|
-
}
|
|
332
|
-
</View>
|
|
333
|
-
</>
|
|
334
|
-
)
|
|
335
|
-
}
|
|
336
|
-
</ScrollView>
|
|
337
|
-
</Page>
|
|
338
|
-
)
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
export default withTheme(EnergyConsumptionChart)
|
|
@@ -389,32 +389,36 @@ const EnergyConsumptionPage = (props: {theme?: ThemeType}) => {
|
|
|
389
389
|
<Card
|
|
390
390
|
style={styles.cardContainer}
|
|
391
391
|
>
|
|
392
|
-
<View style={{ flexDirection: 'row', alignItems: 'center'
|
|
393
|
-
<
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
/>
|
|
413
|
-
</TouchableOpacity>}
|
|
392
|
+
<View style={{ flexDirection: 'row', alignItems: 'center'}}>
|
|
393
|
+
<View>
|
|
394
|
+
<View style={{height: cx(35), justifyContent: 'center'}}>
|
|
395
|
+
<Text style={[styles.cardTitle]}>{I18n.getLang('consumption_data_field1_headline_text')}</Text>
|
|
396
|
+
</View>
|
|
397
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', height: cx(35) }}>
|
|
398
|
+
<Text style={styles.cardTitle}>{I18n.getLang('consumption_data_field3_headline_text')}</Text>
|
|
399
|
+
{ state.energyGeneration.history.length > 0 && <TouchableOpacity
|
|
400
|
+
onPress={() => {
|
|
401
|
+
state.showPopup = true
|
|
402
|
+
state.popupType = 'history'
|
|
403
|
+
}}
|
|
404
|
+
>
|
|
405
|
+
<Image
|
|
406
|
+
source={{uri: res.co2_icon}}
|
|
407
|
+
resizeMode="contain"
|
|
408
|
+
style={{ height: cx(20), width: cx(20), tintColor: props.theme?.button.primary }}
|
|
409
|
+
/>
|
|
410
|
+
</TouchableOpacity>}
|
|
411
|
+
</View>
|
|
414
412
|
</View>
|
|
415
|
-
<View
|
|
416
|
-
<
|
|
417
|
-
|
|
413
|
+
<View>
|
|
414
|
+
<View style={{ flexDirection: 'row', alignItems: 'flex-end', marginLeft: cx(10) }}>
|
|
415
|
+
<Text style={[styles.consumptionNum, { fontSize: cx(30), marginRight: cx(8) }]}>{localeNumber(state.todayElectricity)}</Text>
|
|
416
|
+
<Text style={[styles.consumptionNum, { fontSize: cx(20), marginBottom: cx(4) }]}>kWh</Text>
|
|
417
|
+
</View>
|
|
418
|
+
<View style={{ flexDirection: 'row', alignItems: 'flex-end', marginLeft: cx(10) }}>
|
|
419
|
+
<Text style={[styles.consumptionNum, { fontSize: cx(30), marginRight: cx(8) }]}>{localeNumber(totalElectricity)}</Text>
|
|
420
|
+
<Text style={[styles.consumptionNum, { fontSize: cx(20), marginBottom: cx(4) }]}>kWh</Text>
|
|
421
|
+
</View>
|
|
418
422
|
</View>
|
|
419
423
|
</View>
|
|
420
424
|
<Spacer />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {NavigationRoute} from "tuya-panel-kit";
|
|
2
2
|
import EnergyConsumptionPage from "./EnergyConsumptionPage";
|
|
3
3
|
import EnergyConsumptionDetail from "./EnergyConsumptionDetail";
|
|
4
|
-
import EnergyConsumptionChart from "./EnergyConsumptionChart";
|
|
4
|
+
import EnergyConsumptionChart from "./EnergyConsumptionChart/index";
|
|
5
5
|
import {ui_biz_routerKey} from "../../navigation/Routers";
|
|
6
6
|
|
|
7
7
|
const EnergyConsumptionPageRouters: NavigationRoute[] = [
|
|
@@ -5,7 +5,7 @@ import {DatePicker, Modal, Utils} from "tuya-panel-kit";
|
|
|
5
5
|
import {useReactive} from "ahooks";
|
|
6
6
|
import I18n from "@ledvance/base/src/i18n/index";
|
|
7
7
|
import dayjs from "dayjs";
|
|
8
|
-
import { DateType } from "
|
|
8
|
+
import { DateType } from "../co2Data";
|
|
9
9
|
|
|
10
10
|
const {convertX: cx} = Utils.RatioUtils
|
|
11
11
|
const {withTheme} = Utils.ThemeUtils
|
|
@@ -29,7 +29,7 @@ const PowerLineChart = (props: PowerLineChartProps) => {
|
|
|
29
29
|
position: ['30%', '50%']
|
|
30
30
|
},
|
|
31
31
|
grid: {
|
|
32
|
-
right:
|
|
32
|
+
right: '5%'
|
|
33
33
|
},
|
|
34
34
|
xAxis: {
|
|
35
35
|
type: 'time',
|
|
@@ -92,7 +92,7 @@ const PowerLineChart = (props: PowerLineChartProps) => {
|
|
|
92
92
|
customMapData: {}
|
|
93
93
|
}
|
|
94
94
|
return (
|
|
95
|
-
<View style={{
|
|
95
|
+
<View style={{ height: height, width: '100%' }}>
|
|
96
96
|
<ECharts
|
|
97
97
|
option={option}
|
|
98
98
|
ref={echarts}
|