chargingDetailsPageComponent.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import { View, Text, Pressable, Dimensions,Image, StyleSheet } 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. <View style={{ minHeight: screenHeight, flex: 1 }}>
  73. <View className="mx-[5%]" style={{ marginTop: 25}}>
  74. <Pressable
  75. onPress={() => {
  76. if (router.canGoBack()) {
  77. router.back();
  78. } else {
  79. router.replace('/accountMainPage');
  80. }
  81. }}
  82. >
  83. <CrossLogoSvg />
  84. </Pressable>
  85. <View className="items-center px-3">
  86. <Image
  87. source={require('../../assets/ccLogo.png')}
  88. resizeMode="contain"
  89. style={{
  90. width: screenHeight > 750 ? 200 : 110,
  91. height: screenHeight > 750 ? 200 : 110
  92. }}
  93. />
  94. <Text style={styles.totalPrice}>{list.promotion_name? couponPrice:totalPrice}</Text>
  95. <View style={styles.viewLine}></View>
  96. <View className='w-full flex-row justify-between mt-6 pr-10'>
  97. <Text style={styles.leftLable}>訂單编號: </Text>
  98. <Text style={styles.rightLable}>{list.format_order_id}</Text>
  99. </View>
  100. <View className='w-full flex-row justify-between my-3 pr-10'>
  101. <Text style={styles.leftLable}>充電時間: </Text>
  102. <Text style={styles.rightLable}>{time}</Text>
  103. </View>
  104. <View className='w-full flex-row justify-between pr-10 mb-4'>
  105. <Text style={styles.leftLable}>充電站位置:</Text>
  106. <Text style={styles.rightLable}>{params.chargeStationName}</Text>
  107. </View>
  108. <View style={styles.viewLine}></View>
  109. <ChargingDataComponent list={list} remark={remark} totalPrice={totalPrice}/>
  110. <View style={styles.viewLine}></View>
  111. <View className='w-full flex-row justify-between mt-6 pr-10'>
  112. <View>
  113. <Text style={styles.leftLable}>實付:</Text>
  114. {list.promotion_name ? <Text style={{fontSize: 12, color:'#888888'}}>優惠券支付</Text>: null}
  115. </View>
  116. <Text style={styles.rightLable}>${list.promotion_name? couponPrice:totalPrice}</Text>
  117. </View>
  118. </View>
  119. </View>
  120. <View style={{ width: "100%",height: 130 }} />
  121. </View>
  122. </SafeAreaView>
  123. );
  124. };
  125. interface ChargingDataComponentProps {
  126. list: ChargingDetails;
  127. totalPrice: string;
  128. remark: Remark;
  129. }
  130. const ChargingDataComponent: React.FC<ChargingDataComponentProps> = ({
  131. list,
  132. totalPrice,
  133. remark
  134. }) => {
  135. const hasFetchedPrice = useRef(false); // 添加 ref 跟踪是否已获取过价格
  136. const [rush, setRush] = useState<Special>({} as Special)
  137. const [elses, setElses ] = useState<Special>({} as Special)
  138. const [off, setOff] = useState<Special>({} as Special)
  139. useEffect(() => {
  140. if (!list.promotion_name && !hasFetchedPrice.current && list.actual_start_time) {
  141. chargeStationService.fetchElectricityPrice(list.pricemodel_id || 'a').then(res => {
  142. hasFetchedPrice.current = true; // 标记为已调用
  143. const date = new Date(list.actual_start_time);
  144. const str = (date.toLocaleString('en-US', { weekday: 'short' })).toLowerCase();
  145. if (res && res.length > 0) {
  146. if (remark.RushKwh) {
  147. const obj = (res[1][str as keyof ElectricityPrice]) as Special
  148. setRush(obj)
  149. }
  150. if (remark.ElseKwh) {
  151. const obj = (res[2][str as keyof ElectricityPrice]) as Special
  152. setElses(obj)
  153. }
  154. if (remark.OffKwh) {
  155. const obj = (res[0][str as keyof ElectricityPrice]) as Special
  156. setOff(obj)
  157. }
  158. }
  159. })
  160. }
  161. }, [list.actual_start_time])
  162. if (!list.promotion_name) {
  163. return (
  164. <View>
  165. {(remark.RushKwh) ?
  166. <View>
  167. <View className='w-full flex-row justify-between mt-4 pr-10'>
  168. <Text style={styles.leftLable}>峰時總電量: </Text>
  169. <Text style={styles.rightLable}>{remark.RushKwh?.toFixed(1)}</Text>
  170. </View>
  171. <View className='w-full flex-row justify-between my-3 pr-10'>
  172. <Text style={styles.leftLable}>峰時電價({rush.from}-{rush.to}):</Text>
  173. <Text style={styles.rightLable}>${rush.price}</Text>
  174. </View>
  175. <View className='w-full flex-row justify-between pr-10 mb-3'>
  176. <Text style={styles.leftLable}>峰時總電費:</Text>
  177. <Text style={styles.rightLable}>${remark.RushCharge?.toFixed(1)}</Text>
  178. </View>
  179. </View>: null }
  180. {(remark.ElseKwh) ?
  181. <View>
  182. <View className='w-full flex-row justify-between mt-4 pr-10'>
  183. <Text style={styles.leftLable}>平時總電量: </Text>
  184. <Text style={styles.rightLable}>{remark.ElseKwh?.toFixed(1)}</Text>
  185. </View>
  186. <View className='w-full flex-row justify-between my-3 pr-10'>
  187. <Text style={styles.leftLable}>平時電價({elses.from}-{elses.to}):</Text>
  188. <Text style={styles.rightLable}>${elses.price}</Text>
  189. </View>
  190. <View className='w-full flex-row justify-between pr-10 mb-3'>
  191. <Text style={styles.leftLable}>平時總電費:</Text>
  192. <Text style={styles.rightLable}>${remark.ElseCharge?.toFixed(1)}</Text>
  193. </View>
  194. </View>: null }
  195. {(remark.OffKwh) ?
  196. <View>
  197. <View className='w-full flex-row justify-between mt-4 pr-10'>
  198. <Text style={styles.leftLable}>穀時總電量: </Text>
  199. <Text style={styles.rightLable}>{remark.OffKwh?.toFixed(1)}</Text>
  200. </View>
  201. <View className='w-full flex-row justify-between my-3 pr-10'>
  202. <Text style={styles.leftLable}>穀時電價({off.from}-{off.to}): </Text>
  203. <Text style={styles.rightLable}>${off.price}</Text>
  204. </View>
  205. <View className='w-full flex-row justify-between pr-10 mb-3'>
  206. <Text style={styles.leftLable}>穀時總電費:</Text>
  207. <Text style={styles.rightLable}>${remark.OffCharge?.toFixed(1)}</Text>
  208. </View>
  209. </View>: null }
  210. </View>
  211. )
  212. } else {
  213. return (
  214. <View>
  215. <View className='w-full flex-row justify-between mt-4 pr-10'>
  216. <Text style={styles.leftLable}>總電量: </Text>
  217. <Text style={styles.rightLable}>{remark.TotalPower?.toFixed(1)}</Text>
  218. </View>
  219. <View className='w-full flex-row justify-between my-3 pr-10'>
  220. <Text style={styles.leftLable}>電價: </Text>
  221. <Text style={styles.rightLable}>${list?.connector?.EquipmentID?.StationID?.price}</Text>
  222. </View>
  223. <View className='w-full flex-row justify-between pr-10 mb-3'>
  224. <Text style={styles.leftLable}>總電費: </Text>
  225. <Text style={styles.rightLable}>${totalPrice}</Text>
  226. </View>
  227. </View>
  228. )
  229. }
  230. }
  231. const styles = StyleSheet.create({
  232. viewLine: {
  233. width: '100%',
  234. height: 1,
  235. backgroundColor: '#E5E5E5',
  236. },
  237. totalPrice: {
  238. fontSize: 26,
  239. fontWeight: 'bold',
  240. marginBottom: 25,
  241. },
  242. leftLable: {
  243. fontSize: 18,
  244. color:'#888888',
  245. },
  246. rightLable: {
  247. fontSize: 17
  248. },
  249. })
  250. export default ChargingDetailsPageComponent;