index.tsx 8.0 KB

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