sign-up.tsx 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import { Button, Toast } from '@ant-design/react-native';
  2. import { Ionicons } from '@expo/vector-icons';
  3. import { router } from 'expo-router';
  4. import React, { useRef, useState } from 'react';
  5. import {
  6. KeyboardAvoidingView,
  7. Platform,
  8. Pressable,
  9. ScrollView,
  10. StyleSheet,
  11. Text,
  12. TextInput,
  13. View,
  14. } from 'react-native';
  15. import { SafeAreaView } from 'react-native-safe-area-context';
  16. function FieldLabel({ children }: { children: React.ReactNode }) {
  17. return (
  18. <Text className="mb-2 ml-1 text-xs font-bold uppercase tracking-widest text-outline">
  19. {children}
  20. </Text>
  21. );
  22. }
  23. export default function SignUpScreen() {
  24. const [mobile, setMobile] = useState('');
  25. const [password, setPassword] = useState('');
  26. const [email, setEmail] = useState('');
  27. const [name, setName] = useState('');
  28. const [organization, setOrganization] = useState('');
  29. const [agreed, setAgreed] = useState(false);
  30. const [flushAgree, setFlushAgree] = useState(false);
  31. const [loading, setLoading] = useState(false);
  32. const scrollView = useRef<ScrollView>(null);
  33. const handleRegister = async () => {
  34. setFlushAgree(false);
  35. if (mobile.trim().length !== 11) {
  36. Toast.fail('请输入正确的手机号');
  37. return;
  38. }
  39. if (password.trim().length < 6) {
  40. Toast.fail('请输入不少于 6 位的登录密码');
  41. return;
  42. }
  43. if (!email.includes('@')) {
  44. Toast.fail('请输入正确的邮箱');
  45. return;
  46. }
  47. if (!name.trim()) {
  48. Toast.fail('请输入姓名');
  49. return;
  50. }
  51. if (!organization.trim()) {
  52. Toast.fail('请输入所属机构');
  53. return;
  54. }
  55. if (!agreed) {
  56. Toast.fail('请先阅读并同意协议');
  57. setFlushAgree(true);
  58. scrollView.current?.scrollToEnd({ animated: true });
  59. return;
  60. }
  61. setLoading(true);
  62. const toastKey = Toast.loading('正在创建账号...');
  63. try {
  64. await new Promise((resolve) => setTimeout(resolve, 600));
  65. Toast.success('注册成功,请登录');
  66. router.replace('/sign-in');
  67. } catch (error) {
  68. console.error('注册失败:', error);
  69. Toast.fail('注册失败,请稍后重试');
  70. } finally {
  71. setLoading(false);
  72. Toast.remove(toastKey);
  73. }
  74. };
  75. return (
  76. <SafeAreaView className="flex-1 bg-surface">
  77. <KeyboardAvoidingView
  78. className="flex-1"
  79. behavior={Platform.OS === 'ios' ? 'padding' : undefined}
  80. >
  81. <ScrollView
  82. ref={scrollView}
  83. className="flex-1"
  84. contentContainerClassName="px-8 pt-12 pb-10"
  85. contentContainerStyle={{ flexGrow: 1 }}
  86. keyboardShouldPersistTaps="handled"
  87. showsVerticalScrollIndicator={false}
  88. >
  89. <View className="absolute -top-24 -right-24 h-96 w-96 rounded-full bg-primary-container/10" />
  90. <View className="absolute top-32 -left-20 h-64 w-64 rounded-full bg-secondary-container/20" />
  91. <View className="absolute -bottom-16 right-0 h-48 w-48 rounded-full bg-primary-fixed/50" />
  92. <View className="flex-1 justify-between">
  93. <View>
  94. <View className="mb-12">
  95. <View className="mb-6 h-16 w-16 items-center justify-center rounded-2xl bg-primary-container shadow-lg">
  96. <Ionicons name="sparkles" size={30} color="#ffffff" />
  97. </View>
  98. <Text className="mb-2 text-4xl font-extrabold tracking-tight text-on-surface">
  99. 创建账号
  100. </Text>
  101. <Text className="text-base font-medium leading-7 text-on-surface-variant">
  102. 完善基础信息,开启您的智能借贷助手工作台
  103. </Text>
  104. </View>
  105. <View className="mb-8 rounded-2xl bg-surface-container-lowest p-3 shadow-sm">
  106. <View className="px-2 pb-2">
  107. <FieldLabel>手机号码</FieldLabel>
  108. <View className="mb-5 flex-row items-center rounded-2xl bg-surface-container-low px-5 py-4">
  109. <Text className="text-2xl font-bold text-on-surface">+86</Text>
  110. <View
  111. className="mx-4 h-5 bg-outline-variant/40"
  112. style={{ width: StyleSheet.hairlineWidth }}
  113. />
  114. <TextInput
  115. value={mobile}
  116. onChangeText={setMobile}
  117. editable={!loading}
  118. keyboardType="phone-pad"
  119. maxLength={11}
  120. placeholder="请输入手机号"
  121. placeholderTextColor="#9ca3af"
  122. className="flex-1 p-0 text-xl font-medium text-on-surface"
  123. />
  124. <Ionicons
  125. name="phone-portrait-outline"
  126. size={22}
  127. color="#c3c6d7"
  128. />
  129. </View>
  130. <FieldLabel>登录密码</FieldLabel>
  131. <View className="mb-5 flex-row items-center rounded-2xl bg-surface-container-low px-5 py-4">
  132. <TextInput
  133. value={password}
  134. onChangeText={setPassword}
  135. editable={!loading}
  136. secureTextEntry
  137. maxLength={20}
  138. placeholder="请输入不少于 6 位的密码"
  139. placeholderTextColor="#9ca3af"
  140. className="flex-1 p-0 text-xl font-medium text-on-surface"
  141. />
  142. <Ionicons name="lock-closed-outline" size={22} color="#c3c6d7" />
  143. </View>
  144. <FieldLabel>邮箱</FieldLabel>
  145. <View className="mb-5 flex-row items-center rounded-2xl bg-surface-container-low px-5 py-4">
  146. <TextInput
  147. value={email}
  148. onChangeText={setEmail}
  149. editable={!loading}
  150. keyboardType="email-address"
  151. autoCapitalize="none"
  152. placeholder="请输入常用邮箱"
  153. placeholderTextColor="#9ca3af"
  154. className="flex-1 p-0 text-xl font-medium text-on-surface"
  155. />
  156. <Ionicons name="mail-outline" size={22} color="#c3c6d7" />
  157. </View>
  158. <FieldLabel>姓名</FieldLabel>
  159. <View className="mb-5 flex-row items-center rounded-2xl bg-surface-container-low px-5 py-4">
  160. <TextInput
  161. value={name}
  162. onChangeText={setName}
  163. editable={!loading}
  164. placeholder="请输入姓名"
  165. placeholderTextColor="#9ca3af"
  166. className="flex-1 p-0 text-xl font-medium text-on-surface"
  167. />
  168. <Ionicons name="person-outline" size={22} color="#c3c6d7" />
  169. </View>
  170. <FieldLabel>所属机构</FieldLabel>
  171. <View className="flex-row items-center rounded-2xl bg-surface-container-low px-5 py-4">
  172. <TextInput
  173. value={organization}
  174. onChangeText={setOrganization}
  175. editable={!loading}
  176. placeholder="请输入所属机构"
  177. placeholderTextColor="#9ca3af"
  178. className="flex-1 p-0 text-xl font-medium text-on-surface"
  179. />
  180. <Ionicons name="business-outline" size={22} color="#c3c6d7" />
  181. </View>
  182. </View>
  183. </View>
  184. <View className="mb-8 gap-4">
  185. <Button type="primary" loading={loading} onPress={handleRegister}>
  186. 注册账号
  187. </Button>
  188. <View className="flex-row items-center justify-between px-3">
  189. <Pressable hitSlop={8} onPress={() => router.back()}>
  190. <Text className="text-lg font-medium text-on-surface-variant">
  191. 返回登录
  192. </Text>
  193. </Pressable>
  194. <Pressable hitSlop={8}>
  195. <Text className="text-lg font-medium text-on-surface-variant">
  196. 遇到问题?
  197. </Text>
  198. </Pressable>
  199. </View>
  200. </View>
  201. </View>
  202. <View className="pt-4">
  203. <View
  204. className={`flex-row items-start gap-3 rounded-md border-2 border-transparent px-2 transition delay-300 ${
  205. flushAgree ? 'border-primary/50' : ''
  206. }`}
  207. >
  208. <Pressable
  209. onPress={() => setAgreed((value) => !value)}
  210. hitSlop={8}
  211. className="pt-1"
  212. >
  213. <View
  214. className={`h-6 w-6 items-center justify-center rounded-full ${
  215. agreed
  216. ? 'border-primary bg-primary'
  217. : 'border-outline-variant bg-surface-container-low'
  218. }`}
  219. >
  220. {agreed ? (
  221. <Ionicons name="checkmark" size={14} color="#ffffff" />
  222. ) : null}
  223. </View>
  224. </Pressable>
  225. <Text className="flex-1 text-sm leading-7 text-on-surface-variant">
  226. 注册即代表您已阅读并同意
  227. <Text className="cursor-pointer font-semibold text-primary">
  228. 《用户服务协议》
  229. </Text>
  230. <Text className="cursor-pointer font-semibold text-primary">
  231. 《隐私政策》
  232. </Text>
  233. ,并授权该应用用于账户创建与服务通知。
  234. </Text>
  235. </View>
  236. </View>
  237. </View>
  238. </ScrollView>
  239. </KeyboardAvoidingView>
  240. </SafeAreaView>
  241. );
  242. }