chargingDetailsPageComponent.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import { View, Text, Pressable, Dimensions,Image, StyleSheet, ScrollView } from 'react-native';
  2. import { SafeAreaView } from 'react-native-safe-area-context';
  3. import { router, useLocalSearchParams } from 'expo-router';
  4. import { CrossLogoSvg } from '../global/SVG';
  5. import { useEffect, useMemo, useState, useRef } from 'react';
  6. import { chargeStationService } from '../../service/chargeStationService';
  7. import { ChargingDetails, Remark, ElectricityPrice, Special } from '../../service/type/chargeStationType';
  8. import { format, parseISO } from 'date-fns';
  9. const ChargingDetailsPageComponent = () => {
  10. const screenHeight = Dimensions.get('window').height;
  11. const params = useLocalSearchParams();
  12. const [list, setList] = useState<ChargingDetails>({} as ChargingDetails);
  13. const [remark, setRemark] = useState<Remark>({} as Remark);
  14. const [time, setTime] = useState<string>('');
  15. useEffect(() => {
  16. const fetchData = async () => {
  17. const res = await chargeStationService.fetchChargingDetails(params.id.toString())
  18. if (res) {
  19. const firstItem = Array.isArray(res) && res.length > 0 ? res[0] : null;
  20. // 设置充电详情
  21. setList(firstItem || ({} as ChargingDetails));
  22. // 安全地解析 remark 字段
  23. let parsedRemark: Remark = {} as Remark;
  24. if (firstItem?.remark) {
  25. try {
  26. parsedRemark = JSON.parse(firstItem.remark) as Remark;
  27. } catch (e) {
  28. console.warn('Failed to parse remark:', e);
  29. parsedRemark = {} as Remark;
  30. }
  31. }
  32. setRemark(parsedRemark);
  33. // 解析并格式化日期
  34. const startTime = res[0].actual_start_time? parseISO(res[0].actual_start_time): null
  35. const endTime = res[0].actual_end_time?parseISO(res[0].actual_end_time): null
  36. let formattedDate = '';
  37. if (startTime && endTime) {
  38. // 格式化为指定格式
  39. formattedDate = `${format(startTime, 'yyyy/MM/dd HH:mm:ss')}-${format(endTime, 'HH:mm:ss')}`;
  40. } else {
  41. formattedDate = 'Invalid Time';
  42. }
  43. setTime(formattedDate)
  44. }
  45. };
  46. fetchData();
  47. }, [])
  48. const totalPrice = useMemo(() => {
  49. if (list.promotion_name) {
  50. const price = list?.connector?.EquipmentID?.StationID?.price
  51. if (price && remark.TotalPower) {
  52. return `${(price * remark.TotalPower).toFixed(1)}`
  53. }
  54. return '0'
  55. } else {
  56. if (list.total_fee && list.withdraw_fee) {
  57. let actual_fee = list.total_fee - list.withdraw_fee
  58. const value = actual_fee <= 0? '0': actual_fee % 1 === 0? `${actual_fee}`: `${actual_fee.toFixed(1)}`
  59. return value
  60. }
  61. return '0';
  62. }
  63. }, [list]);
  64. const couponPrice = useMemo(() => {
  65. if (list.promotion_name) {
  66. let actual_fee = (list.total_fee - list.withdraw_fee) > 0 ? (list.total_fee - list.withdraw_fee) : 0;
  67. return actual_fee.toFixed(1)
  68. }
  69. }, [list])
  70. return (
  71. <SafeAreaView className="flex-1 bg-white" edges={['top']}>
  72. <ScrollView>
  73. <View style={{ minHeight: screenHeight, flex: 1 }}>
  74. <View className="mx-[3%]" style={{ marginTop: 25}}>
  75. <Pressable
  76. onPress={() => {
  77. if (router.canGoBack()) {
  78. router.back();
  79. } else {
  80. router.replace('/accountMainPage');
  81. }
  82. }}
  83. >
  84. <CrossLogoSvg />
  85. </Pressable>
  86. <View className="items-center px-3">
  87. <Image
  88. source={require('../../assets/ccLogo.png')}
  89. resizeMode="contain"
  90. style={{
  91. width: 100,
  92. height: 100
  93. }}
  94. />
  95. <Text style={styles.totalPrice}>{list.promotion_name? couponPrice:totalPrice}</Text>
  96. <View style={styles.viewLine}></View>
  97. <View className='w-full flex-row justify-between mt-6'>
  98. <Text style={styles.leftLable}>訂單编號: </Text>
  99. <View style={{ flex: 1, marginLeft: 5 }}>
  100. <Text style={styles.rightLable}>{list.format_order_id}</Text>
  101. </View>
  102. </View>
  103. <View className='w-full flex-row justify-between my-3'>
  104. <Text style={styles.leftLable}>充電時間: </Text>
  105. <View style={{ flex: 1, marginLeft: 5 }}>
  106. <Text style={styles.rightLable}>{time}</Text>
  107. </View>
  108. </View>
  109. <View className='w-full flex-row justify-between mb-4'>
  110. <Text style={styles.leftLable}>充電站位置:</Text>
  111. <View style={{ flex: 1, marginLeft: 5 }}>
  112. <Text style={styles.rightLable}>{params.chargeStationName}</Text>
  113. </View>
  114. </View>
  115. <View style={styles.viewLine}></View>
  116. <ChargingDataComponent list={list} remark={remark} totalPrice={totalPrice}/>
  117. <View style={styles.viewLine}></View>
  118. <View className='w-full flex-row justify-between mt-6'>
  119. <View>
  120. <Text style={styles.leftLable}>實付:</Text>
  121. {list.promotion_name ? <Text style={{fontSize: 12, color:'#888888'}}>優惠券支付</Text>: null}
  122. </View>
  123. <Text style={styles.rightLable}>${list.promotion_name? couponPrice:totalPrice}</Text>
  124. </View>
  125. </View>
  126. </View>
  127. </View>
  128. </ScrollView>
  129. </SafeAreaView>
  130. );
  131. };
  132. interface ChargingDataComponentProps {
  133. list: ChargingDetails;
  134. totalPrice: string;
  135. remark: Remark;
  136. }
  137. const ChargingDataComponent: React.FC<ChargingDataComponentProps> = ({
  138. list,
  139. totalPrice,
  140. remark
  141. }) => {
  142. const hasFetchedPrice = useRef(false); // 添加 ref 跟踪是否已获取过价格
  143. const [rush, setRush] = useState<Special>({} as Special)
  144. const [elses, setElses ] = useState<Special>({} as Special)
  145. const [off, setOff] = useState<Special>({} as Special)
  146. useEffect(() => {
  147. if (!list.promotion_name && !hasFetchedPrice.current && list.actual_start_time) {
  148. chargeStationService.fetchElectricityPrice(list.pricemodel_id || 'a').then(res => {
  149. hasFetchedPrice.current = true; // 标记为已调用
  150. const date = new Date(list.actual_start_time);
  151. const str = (date.toLocaleString('en-US', { weekday: 'short' })).toLowerCase();
  152. if (res && res.length > 0) {
  153. if (remark.RushKwh) {
  154. const obj = (res[1][str as keyof ElectricityPrice]) as Special
  155. setRush(obj)
  156. }
  157. if (remark.ElseKwh) {
  158. const obj = (res[2][str as keyof ElectricityPrice]) as Special
  159. setElses(obj)
  160. }
  161. if (remark.OffKwh) {
  162. const obj = (res[0][str as keyof ElectricityPrice]) as Special
  163. setOff(obj)
  164. }
  165. }
  166. })
  167. }
  168. }, [list.actual_start_time])
  169. if (!list.promotion_name) {
  170. return (
  171. <View>
  172. {(remark.RushKwh) ?
  173. <View>
  174. <View className='w-full flex-row justify-between mt-4'>
  175. <Text style={styles.leftLable}>峰時總電量: </Text>
  176. <Text style={styles.rightLable}>{remark.RushKwh?.toFixed(1)}</Text>
  177. </View>
  178. <View className='w-full flex-row justify-between my-3'>
  179. <Text style={styles.leftLable}>峰時電價({rush.from}-{rush.to}):</Text>
  180. <Text style={styles.rightLable}>${rush.price}</Text>
  181. </View>
  182. <View className='w-full flex-row justify-between mb-3'>
  183. <Text style={styles.leftLable}>峰時總電費:</Text>
  184. <Text style={styles.rightLable}>${remark.RushCharge?.toFixed(1)}</Text>
  185. </View>
  186. </View>: null }
  187. {(remark.ElseKwh) ?
  188. <View>
  189. <View className='w-full flex-row justify-between mt-4'>
  190. <Text style={styles.leftLable}>平時總電量: </Text>
  191. <Text style={styles.rightLable}>{remark.ElseKwh?.toFixed(1)}</Text>
  192. </View>
  193. <View className='w-full flex-row justify-between my-3'>
  194. <Text style={styles.leftLable}>平時電價({elses.from}-{elses.to}):</Text>
  195. <Text style={styles.rightLable}>${elses.price}</Text>
  196. </View>
  197. <View className='w-full flex-row justify-between mb-3'>
  198. <Text style={styles.leftLable}>平時總電費:</Text>
  199. <Text style={styles.rightLable}>${remark.ElseCharge?.toFixed(1)}</Text>
  200. </View>
  201. </View>: null }
  202. {(remark.OffKwh) ?
  203. <View>
  204. <View className='w-full flex-row justify-between mt-4'>
  205. <Text style={styles.leftLable}>穀時總電量: </Text>
  206. <Text style={styles.rightLable}>{remark.OffKwh?.toFixed(1)}</Text>
  207. </View>
  208. <View className='w-full flex-row justify-between my-3'>
  209. <Text style={styles.leftLable}>穀時電價({off.from}-{off.to}): </Text>
  210. <Text style={styles.rightLable}>${off.price}</Text>
  211. </View>
  212. <View className='w-full flex-row justify-between mb-3'>
  213. <Text style={styles.leftLable}>穀時總電費:</Text>
  214. <Text style={styles.rightLable}>${remark.OffCharge?.toFixed(1)}</Text>
  215. </View>
  216. </View>: null }
  217. </View>
  218. )
  219. } else {
  220. return (
  221. <View>
  222. <View className='w-full flex-row justify-between mt-4'>
  223. <Text style={styles.leftLable}>總電量: </Text>
  224. <Text style={styles.rightLable}>{remark.TotalPower?.toFixed(1)}</Text>
  225. </View>
  226. <View className='w-full flex-row justify-between my-3'>
  227. <Text style={styles.leftLable}>電價: </Text>
  228. <Text style={styles.rightLable}>${list?.connector?.EquipmentID?.StationID?.price}</Text>
  229. </View>
  230. <View className='w-full flex-row justify-between mb-3'>
  231. <Text style={styles.leftLable}>總電費: </Text>
  232. <Text style={styles.rightLable}>${totalPrice}</Text>
  233. </View>
  234. </View>
  235. )
  236. }
  237. }
  238. const styles = StyleSheet.create({
  239. viewLine: {
  240. width: '100%',
  241. height: 1,
  242. backgroundColor: '#E5E5E5',
  243. },
  244. totalPrice: {
  245. fontSize: 26,
  246. fontWeight: 'bold',
  247. marginBottom: 25,
  248. },
  249. leftLable: {
  250. fontSize: 18,
  251. color:'#888888',
  252. },
  253. rightLable: {
  254. fontSize: 17,
  255. flex: 1,
  256. flexWrap: 'wrap',
  257. textAlign: 'right',
  258. },
  259. })
  260. export default ChargingDetailsPageComponent;