index.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import { useFocusEffect, useLocalSearchParams, useRouter } from 'expo-router';
  2. import React, { useCallback, useState } from 'react';
  3. import {
  4. ActivityIndicator,
  5. Alert,
  6. ImageBackground,
  7. ScrollView,
  8. StatusBar,
  9. StyleSheet,
  10. Text,
  11. TouchableOpacity,
  12. View
  13. } from 'react-native';
  14. import { useSafeAreaInsets } from 'react-native-safe-area-context';
  15. import { Images } from '@/constants/images';
  16. import { Address, deleteAddress, getAddressList, updateAddress } from '@/services/address';
  17. export default function AddressListScreen() {
  18. const { type } = useLocalSearchParams<{ type?: string }>();
  19. const router = useRouter();
  20. const insets = useSafeAreaInsets();
  21. const [loading, setLoading] = useState(true);
  22. const [list, setList] = useState<Address[]>([]);
  23. const loadData = useCallback(async () => {
  24. setLoading(true);
  25. try {
  26. const data = await getAddressList();
  27. setList(data);
  28. } catch (error) {
  29. console.error('加载地址失败:', error);
  30. }
  31. setLoading(false);
  32. }, []);
  33. useFocusEffect(
  34. useCallback(() => {
  35. loadData();
  36. }, [loadData])
  37. );
  38. // 选择地址
  39. const selectAddress = (item: Address) => {
  40. if (type === '1') {
  41. // 从结算页面来,选择后返回
  42. router.back();
  43. // 通过全局事件传递选中的地址
  44. }
  45. };
  46. // 设为默认
  47. const setDefault = async (item: Address) => {
  48. try {
  49. await updateAddress({ ...item, defaultFlag: 1 });
  50. loadData();
  51. } catch (error) {
  52. console.error('设置默认地址失败:', error);
  53. }
  54. };
  55. // 删除地址
  56. const handleDelete = (item: Address) => {
  57. Alert.alert('提示', '确定删除该地址吗?', [
  58. { text: '取消', style: 'cancel' },
  59. {
  60. text: '删除',
  61. style: 'destructive',
  62. onPress: async () => {
  63. try {
  64. await deleteAddress(item.id);
  65. loadData();
  66. } catch (error) {
  67. console.error('删除地址失败:', error);
  68. }
  69. },
  70. },
  71. ]);
  72. };
  73. // 编辑地址
  74. const editAddress = (item: Address) => {
  75. router.push(`/address/edit?id=${item.id}` as any);
  76. };
  77. // 新增地址
  78. const addNewAddress = () => {
  79. router.push('/address/edit' as any);
  80. };
  81. const goBack = () => {
  82. router.back();
  83. };
  84. if (loading) {
  85. return (
  86. <View style={styles.loadingContainer}>
  87. <ActivityIndicator size="large" color="#fff" />
  88. </View>
  89. );
  90. }
  91. return (
  92. <View style={styles.container}>
  93. <StatusBar barStyle="light-content" />
  94. <ImageBackground
  95. source={{ uri: Images.mine.kaixinMineBg }}
  96. style={styles.background}
  97. resizeMode="cover"
  98. >
  99. {/* 顶部导航 */}
  100. <View style={[styles.header, { paddingTop: insets.top }]}>
  101. <TouchableOpacity style={styles.backBtn} onPress={goBack}>
  102. <Text style={styles.backText}>←</Text>
  103. </TouchableOpacity>
  104. <Text style={styles.headerTitle}>收货地址</Text>
  105. <View style={styles.placeholder} />
  106. </View>
  107. <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
  108. {list.length === 0 ? (
  109. <View style={styles.emptyContainer}>
  110. <Text style={styles.emptyText}>暂无收货地址</Text>
  111. </View>
  112. ) : (
  113. list.map((item) => (
  114. <TouchableOpacity
  115. key={item.id}
  116. style={styles.addressItem}
  117. onPress={() => selectAddress(item)}
  118. >
  119. <ImageBackground
  120. source={{ uri: Images.common.itemBg }}
  121. style={styles.addressItemBg}
  122. resizeMode="stretch"
  123. >
  124. <View style={styles.addressInfo}>
  125. <View style={styles.nameRow}>
  126. <Text style={styles.name}>{item.contactName}</Text>
  127. <Text style={styles.phone}>{item.contactNo}</Text>
  128. {item.defaultFlag === 1 && (
  129. <View style={styles.defaultTag}>
  130. <Text style={styles.defaultText}>默认</Text>
  131. </View>
  132. )}
  133. </View>
  134. <Text style={styles.addressDetail}>
  135. {item.province}{item.city}{item.district}{item.address}
  136. </Text>
  137. </View>
  138. <View style={styles.actions}>
  139. {item.defaultFlag !== 1 && (
  140. <TouchableOpacity style={styles.actionBtn} onPress={() => setDefault(item)}>
  141. <Text style={styles.actionText}>设为默认</Text>
  142. </TouchableOpacity>
  143. )}
  144. <TouchableOpacity style={styles.actionBtn} onPress={() => editAddress(item)}>
  145. <Text style={styles.actionText}>编辑</Text>
  146. </TouchableOpacity>
  147. <TouchableOpacity style={styles.actionBtn} onPress={() => handleDelete(item)}>
  148. <Text style={[styles.actionText, styles.deleteText]}>删除</Text>
  149. </TouchableOpacity>
  150. </View>
  151. </ImageBackground>
  152. </TouchableOpacity>
  153. ))
  154. )}
  155. <View style={{ height: 100 }} />
  156. </ScrollView>
  157. {/* 底部新增按钮 */}
  158. <View style={[styles.bottomBar, { paddingBottom: insets.bottom + 10 }]}>
  159. <TouchableOpacity style={styles.addBtn} onPress={addNewAddress} activeOpacity={0.8}>
  160. <ImageBackground
  161. source={{ uri: Images.common.loginBtn }}
  162. style={styles.addBtnBg}
  163. resizeMode="stretch"
  164. >
  165. <Text style={styles.addBtnText}>+ 新增收货地址</Text>
  166. </ImageBackground>
  167. </TouchableOpacity>
  168. </View>
  169. </ImageBackground>
  170. </View>
  171. );
  172. }
  173. const styles = StyleSheet.create({
  174. container: {
  175. flex: 1,
  176. backgroundColor: '#1a1a2e',
  177. },
  178. background: {
  179. flex: 1,
  180. },
  181. loadingContainer: {
  182. flex: 1,
  183. backgroundColor: '#1a1a2e',
  184. justifyContent: 'center',
  185. alignItems: 'center',
  186. },
  187. header: {
  188. flexDirection: 'row',
  189. alignItems: 'center',
  190. justifyContent: 'space-between',
  191. paddingHorizontal: 15,
  192. paddingBottom: 10,
  193. },
  194. backBtn: {
  195. width: 40,
  196. height: 40,
  197. justifyContent: 'center',
  198. alignItems: 'center',
  199. },
  200. backText: {
  201. color: '#fff',
  202. fontSize: 24,
  203. },
  204. headerTitle: {
  205. color: '#fff',
  206. fontSize: 18,
  207. fontWeight: '600',
  208. },
  209. placeholder: {
  210. width: 40,
  211. },
  212. scrollView: {
  213. flex: 1,
  214. paddingHorizontal: 15,
  215. },
  216. emptyContainer: {
  217. paddingTop: 100,
  218. alignItems: 'center',
  219. },
  220. emptyText: {
  221. color: '#999',
  222. fontSize: 14,
  223. },
  224. addressItem: {
  225. marginTop: 10,
  226. },
  227. addressItemBg: {
  228. padding: 15,
  229. borderRadius: 12,
  230. overflow: 'hidden',
  231. },
  232. addressInfo: {
  233. borderBottomWidth: 1,
  234. borderBottomColor: 'rgba(0,0,0,0.1)',
  235. paddingBottom: 12,
  236. },
  237. nameRow: {
  238. flexDirection: 'row',
  239. alignItems: 'center',
  240. },
  241. name: {
  242. color: '#333',
  243. fontSize: 16,
  244. fontWeight: '600',
  245. },
  246. phone: {
  247. color: '#333',
  248. fontSize: 14,
  249. marginLeft: 15,
  250. },
  251. defaultTag: {
  252. backgroundColor: '#ff6b00',
  253. borderRadius: 10,
  254. paddingHorizontal: 8,
  255. paddingVertical: 2,
  256. marginLeft: 10,
  257. },
  258. defaultText: {
  259. color: '#fff',
  260. fontSize: 10,
  261. },
  262. addressDetail: {
  263. color: '#666',
  264. fontSize: 13,
  265. marginTop: 8,
  266. lineHeight: 18,
  267. },
  268. actions: {
  269. flexDirection: 'row',
  270. justifyContent: 'flex-end',
  271. paddingTop: 12,
  272. },
  273. actionBtn: {
  274. paddingHorizontal: 12,
  275. paddingVertical: 6,
  276. },
  277. actionText: {
  278. color: '#666',
  279. fontSize: 13,
  280. },
  281. deleteText: {
  282. color: '#ff4d4f',
  283. },
  284. bottomBar: {
  285. paddingHorizontal: 15,
  286. paddingTop: 10,
  287. },
  288. addBtn: {
  289. height: 50,
  290. overflow: 'hidden',
  291. },
  292. addBtnBg: {
  293. width: '100%',
  294. height: '100%',
  295. justifyContent: 'center',
  296. alignItems: 'center',
  297. },
  298. addBtnText: {
  299. color: '#fff',
  300. fontSize: 16,
  301. fontWeight: '600',
  302. },
  303. });