edit.tsx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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. Switch,
  11. Text,
  12. TextInput,
  13. TouchableOpacity,
  14. View
  15. } from 'react-native';
  16. import { useSafeAreaInsets } from 'react-native-safe-area-context';
  17. import { Images } from '@/constants/images';
  18. import { addAddress, Address, getAddressList, updateAddress } from '@/services/address';
  19. export default function AddressEditScreen() {
  20. const { id } = useLocalSearchParams<{ id?: string }>();
  21. const router = useRouter();
  22. const insets = useSafeAreaInsets();
  23. const [loading, setLoading] = useState(!!id);
  24. const [saving, setSaving] = useState(false);
  25. const [contactName, setContactName] = useState('');
  26. const [contactNo, setContactNo] = useState('');
  27. const [province, setProvince] = useState('');
  28. const [city, setCity] = useState('');
  29. const [district, setDistrict] = useState('');
  30. const [address, setAddress] = useState('');
  31. const [isDefault, setIsDefault] = useState(false);
  32. const loadData = useCallback(async () => {
  33. if (!id) return;
  34. setLoading(true);
  35. try {
  36. const list = await getAddressList();
  37. const item = list.find((a: Address) => a.id === id);
  38. if (item) {
  39. setContactName(item.contactName);
  40. setContactNo(item.contactNo);
  41. setProvince(item.province);
  42. setCity(item.city);
  43. setDistrict(item.district || '');
  44. setAddress(item.address);
  45. setIsDefault(item.defaultFlag === 1);
  46. }
  47. } catch (error) {
  48. console.error('加载地址失败:', error);
  49. }
  50. setLoading(false);
  51. }, [id]);
  52. useEffect(() => {
  53. loadData();
  54. }, [loadData]);
  55. const handleSave = async () => {
  56. if (!contactName.trim()) {
  57. Alert.alert('提示', '请输入收货人姓名');
  58. return;
  59. }
  60. if (!contactNo.trim()) {
  61. Alert.alert('提示', '请输入手机号码');
  62. return;
  63. }
  64. if (!province.trim() || !city.trim()) {
  65. Alert.alert('提示', '请输入省市信息');
  66. return;
  67. }
  68. if (!address.trim()) {
  69. Alert.alert('提示', '请输入详细地址');
  70. return;
  71. }
  72. setSaving(true);
  73. try {
  74. const params = {
  75. contactName: contactName.trim(),
  76. contactNo: contactNo.trim(),
  77. province: province.trim(),
  78. city: city.trim(),
  79. district: district.trim(),
  80. address: address.trim(),
  81. defaultFlag: isDefault ? 1 : 0,
  82. };
  83. let success = false;
  84. if (id) {
  85. success = await updateAddress({ id, ...params } as Address);
  86. } else {
  87. success = await addAddress(params);
  88. }
  89. if (success) {
  90. Alert.alert('成功', id ? '地址已更新' : '地址已添加', [
  91. { text: '确定', onPress: () => router.back() },
  92. ]);
  93. }
  94. } catch (error) {
  95. console.error('保存地址失败:', error);
  96. Alert.alert('错误', '保存失败');
  97. }
  98. setSaving(false);
  99. };
  100. const goBack = () => {
  101. router.back();
  102. };
  103. if (loading) {
  104. return (
  105. <View style={styles.loadingContainer}>
  106. <ActivityIndicator size="large" color="#fff" />
  107. </View>
  108. );
  109. }
  110. return (
  111. <View style={styles.container}>
  112. <StatusBar barStyle="light-content" />
  113. <ImageBackground
  114. source={{ uri: Images.mine.kaixinMineBg }}
  115. style={styles.background}
  116. resizeMode="cover"
  117. >
  118. {/* 顶部导航 */}
  119. <View style={[styles.header, { paddingTop: insets.top }]}>
  120. <TouchableOpacity style={styles.backBtn} onPress={goBack}>
  121. <Text style={styles.backText}>←</Text>
  122. </TouchableOpacity>
  123. <Text style={styles.headerTitle}>{id ? '编辑地址' : '新增地址'}</Text>
  124. <View style={styles.placeholder} />
  125. </View>
  126. <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
  127. <ImageBackground
  128. source={{ uri: Images.common.itemBg }}
  129. style={styles.formBg}
  130. resizeMode="stretch"
  131. >
  132. <View style={styles.form}>
  133. <View style={styles.formItem}>
  134. <Text style={styles.label}>收货人</Text>
  135. <TextInput
  136. style={styles.input}
  137. value={contactName}
  138. onChangeText={setContactName}
  139. placeholder="请输入收货人姓名"
  140. placeholderTextColor="#999"
  141. />
  142. </View>
  143. <View style={styles.formItem}>
  144. <Text style={styles.label}>手机号码</Text>
  145. <TextInput
  146. style={styles.input}
  147. value={contactNo}
  148. onChangeText={setContactNo}
  149. placeholder="请输入手机号码"
  150. placeholderTextColor="#999"
  151. keyboardType="phone-pad"
  152. />
  153. </View>
  154. <View style={styles.formItem}>
  155. <Text style={styles.label}>省份</Text>
  156. <TextInput
  157. style={styles.input}
  158. value={province}
  159. onChangeText={setProvince}
  160. placeholder="请输入省份"
  161. placeholderTextColor="#999"
  162. />
  163. </View>
  164. <View style={styles.formItem}>
  165. <Text style={styles.label}>城市</Text>
  166. <TextInput
  167. style={styles.input}
  168. value={city}
  169. onChangeText={setCity}
  170. placeholder="请输入城市"
  171. placeholderTextColor="#999"
  172. />
  173. </View>
  174. <View style={styles.formItem}>
  175. <Text style={styles.label}>区/县</Text>
  176. <TextInput
  177. style={styles.input}
  178. value={district}
  179. onChangeText={setDistrict}
  180. placeholder="请输入区/县(选填)"
  181. placeholderTextColor="#999"
  182. />
  183. </View>
  184. <View style={styles.formItem}>
  185. <Text style={styles.label}>详细地址</Text>
  186. <TextInput
  187. style={[styles.input, styles.textArea]}
  188. value={address}
  189. onChangeText={setAddress}
  190. placeholder="请输入详细地址"
  191. placeholderTextColor="#999"
  192. multiline
  193. numberOfLines={3}
  194. />
  195. </View>
  196. <View style={styles.switchItem}>
  197. <Text style={styles.switchLabel}>设为默认地址</Text>
  198. <Switch value={isDefault} onValueChange={setIsDefault} />
  199. </View>
  200. </View>
  201. </ImageBackground>
  202. <View style={{ height: 100 }} />
  203. </ScrollView>
  204. {/* 底部保存按钮 */}
  205. <View style={[styles.bottomBar, { paddingBottom: insets.bottom + 10 }]}>
  206. <TouchableOpacity
  207. style={[styles.saveBtn, saving && styles.saveBtnDisabled]}
  208. onPress={handleSave}
  209. disabled={saving}
  210. activeOpacity={0.8}
  211. >
  212. <ImageBackground
  213. source={{ uri: Images.common.loginBtn }}
  214. style={styles.saveBtnBg}
  215. resizeMode="stretch"
  216. >
  217. <Text style={styles.saveBtnText}>{saving ? '保存中...' : '保存'}</Text>
  218. </ImageBackground>
  219. </TouchableOpacity>
  220. </View>
  221. </ImageBackground>
  222. </View>
  223. );
  224. }
  225. const styles = StyleSheet.create({
  226. container: {
  227. flex: 1,
  228. backgroundColor: '#1a1a2e',
  229. },
  230. background: {
  231. flex: 1,
  232. },
  233. loadingContainer: {
  234. flex: 1,
  235. backgroundColor: '#1a1a2e',
  236. justifyContent: 'center',
  237. alignItems: 'center',
  238. },
  239. header: {
  240. flexDirection: 'row',
  241. alignItems: 'center',
  242. justifyContent: 'space-between',
  243. paddingHorizontal: 15,
  244. paddingBottom: 10,
  245. },
  246. backBtn: {
  247. width: 40,
  248. height: 40,
  249. justifyContent: 'center',
  250. alignItems: 'center',
  251. },
  252. backText: {
  253. color: '#fff',
  254. fontSize: 24,
  255. },
  256. headerTitle: {
  257. color: '#fff',
  258. fontSize: 18,
  259. fontWeight: '600',
  260. },
  261. placeholder: {
  262. width: 40,
  263. },
  264. scrollView: {
  265. flex: 1,
  266. },
  267. formBg: {
  268. margin: 15,
  269. padding: 15,
  270. borderRadius: 12,
  271. overflow: 'hidden',
  272. },
  273. form: {},
  274. formItem: {
  275. marginBottom: 15,
  276. },
  277. label: {
  278. color: '#333',
  279. fontSize: 14,
  280. marginBottom: 8,
  281. },
  282. input: {
  283. backgroundColor: 'rgba(255,255,255,0.8)',
  284. borderRadius: 8,
  285. paddingHorizontal: 12,
  286. paddingVertical: 12,
  287. color: '#333',
  288. fontSize: 14,
  289. },
  290. textArea: {
  291. height: 80,
  292. textAlignVertical: 'top',
  293. },
  294. switchItem: {
  295. flexDirection: 'row',
  296. justifyContent: 'space-between',
  297. alignItems: 'center',
  298. paddingTop: 10,
  299. },
  300. switchLabel: {
  301. color: '#333',
  302. fontSize: 14,
  303. },
  304. bottomBar: {
  305. paddingHorizontal: 15,
  306. paddingTop: 10,
  307. },
  308. saveBtn: {
  309. height: 50,
  310. overflow: 'hidden',
  311. },
  312. saveBtnBg: {
  313. width: '100%',
  314. height: '100%',
  315. justifyContent: 'center',
  316. alignItems: 'center',
  317. },
  318. saveBtnDisabled: {
  319. opacity: 0.6,
  320. },
  321. saveBtnText: {
  322. color: '#fff',
  323. fontSize: 16,
  324. fontWeight: '600',
  325. },
  326. });