index.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import { View, Text } from '@tarojs/components'
  2. import { useState, useEffect } from 'react'
  3. import Taro from '@tarojs/taro'
  4. import { useAuth } from '@/utils/auth'
  5. import { cn } from '@/utils/cn'
  6. import { Button } from '@/components/ui/button'
  7. import { Input } from '@/components/ui/input'
  8. import Navbar from '@/components/ui/navbar'
  9. import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form'
  10. import { z } from 'zod'
  11. import { zodResolver } from '@hookform/resolvers/zod'
  12. import { useForm } from 'react-hook-form'
  13. import './index.css'
  14. const loginSchema = z.object({
  15. username: z
  16. .string()
  17. .min(3, '用户名至少3个字符')
  18. .max(20, '用户名最多20个字符')
  19. .regex(/^\S+$/, '用户名不能包含空格'),
  20. password: z
  21. .string()
  22. .min(6, '密码至少6位')
  23. .max(20, '密码最多20位'),
  24. })
  25. type LoginFormData = z.infer<typeof loginSchema>
  26. export default function Login() {
  27. const [showPassword, setShowPassword] = useState(false)
  28. const { login, isLoading } = useAuth()
  29. const form = useForm<LoginFormData>({
  30. resolver: zodResolver(loginSchema),
  31. defaultValues: {
  32. username: '',
  33. password: '',
  34. },
  35. })
  36. // 设置导航栏标题
  37. useEffect(() => {
  38. Taro.setNavigationBarTitle({
  39. title: '用户登录'
  40. })
  41. }, [])
  42. const onSubmit = async (data: LoginFormData) => {
  43. try {
  44. Taro.showLoading({
  45. title: '登录中...',
  46. mask: true
  47. })
  48. await login({
  49. username: data.username.trim(),
  50. password: data.password.trim()
  51. })
  52. Taro.hideLoading()
  53. Taro.showToast({
  54. title: '登录成功',
  55. icon: 'success',
  56. duration: 1500
  57. })
  58. setTimeout(() => {
  59. Taro.switchTab({ url: '/pages/index/index' })
  60. }, 1500)
  61. } catch (error: any) {
  62. Taro.hideLoading()
  63. const errorMessage = error.message || '登录失败'
  64. if (errorMessage.includes('用户名或密码错误')) {
  65. Taro.showToast({
  66. title: '用户名或密码错误',
  67. icon: 'none',
  68. duration: 3000
  69. })
  70. } else if (errorMessage.includes('网络')) {
  71. Taro.showModal({
  72. title: '网络错误',
  73. content: '请检查网络连接后重试',
  74. showCancel: false,
  75. confirmText: '确定'
  76. })
  77. } else {
  78. Taro.showToast({
  79. title: errorMessage,
  80. icon: 'none',
  81. duration: 3000
  82. })
  83. }
  84. }
  85. }
  86. const goToRegister = () => {
  87. Taro.navigateTo({
  88. url: '/pages/register/index'
  89. })
  90. }
  91. return (
  92. <View className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50">
  93. <Navbar
  94. title="用户登录"
  95. backgroundColor="bg-transparent"
  96. textColor="text-gray-900"
  97. border={false}
  98. />
  99. <View className="flex-1 px-6 py-12">
  100. {/* Logo区域 */}
  101. <View className="flex flex-col items-center mb-10">
  102. <View className="w-20 h-20 mb-4 rounded-full bg-white shadow-lg flex items-center justify-center">
  103. <View className="i-heroicons-user-circle-20-solid w-12 h-12 text-blue-500" />
  104. </View>
  105. <Text className="text-2xl font-bold text-gray-900 mb-1">欢迎回来</Text>
  106. <Text className="text-gray-600 text-sm">请使用您的账号登录系统</Text>
  107. </View>
  108. {/* 登录表单 */}
  109. <View className="bg-white rounded-2xl shadow-sm p-6">
  110. <Form {...form}>
  111. <View className="space-y-5">
  112. <FormField
  113. control={form.control}
  114. name="username"
  115. render={({ field }) => (
  116. <FormItem>
  117. <FormLabel>用户名</FormLabel>
  118. <FormControl>
  119. <Input
  120. leftIcon="i-heroicons-user-20-solid"
  121. placeholder="请输入用户名"
  122. maxlength={20}
  123. type="text"
  124. confirmType="next"
  125. size="lg"
  126. variant="filled"
  127. {...field}
  128. />
  129. </FormControl>
  130. <FormMessage />
  131. </FormItem>
  132. )}
  133. />
  134. <FormField
  135. control={form.control}
  136. name="password"
  137. render={({ field }) => (
  138. <FormItem>
  139. <FormLabel>密码</FormLabel>
  140. <FormControl>
  141. <Input
  142. leftIcon="i-heroicons-lock-closed-20-solid"
  143. rightIcon={showPassword ? "i-heroicons-eye-20-solid" : "i-heroicons-eye-slash-20-solid"}
  144. placeholder="请输入密码"
  145. password={!showPassword}
  146. maxlength={20}
  147. confirmType="done"
  148. size="lg"
  149. variant="filled"
  150. onRightIconClick={() => setShowPassword(!showPassword)}
  151. {...field}
  152. />
  153. </FormControl>
  154. <FormMessage />
  155. </FormItem>
  156. )}
  157. />
  158. {/* 忘记密码 */}
  159. <View className="flex justify-end">
  160. <Text className="text-sm text-blue-500 hover:text-blue-600">忘记密码?</Text>
  161. </View>
  162. {/* 登录按钮 */}
  163. <Button
  164. className={cn(
  165. "w-full",
  166. !form.formState.isValid || isLoading
  167. ? "bg-gray-300"
  168. : "bg-blue-500 hover:bg-blue-600"
  169. )}
  170. size="lg"
  171. variant="default"
  172. onClick={form.handleSubmit(onSubmit) as any}
  173. disabled={!form.formState.isValid || isLoading}
  174. >
  175. {isLoading ? (
  176. <View className="flex items-center justify-center">
  177. <View className="i-heroicons-arrow-path-20-solid animate-spin w-5 h-5 mr-2" />
  178. 登录中...
  179. </View>
  180. ) : (
  181. '安全登录'
  182. )}
  183. </Button>
  184. </View>
  185. </Form>
  186. {/* 微信登录 */}
  187. <View className="mt-6">
  188. <View className="relative">
  189. <View className="absolute inset-0 flex items-center">
  190. <View className="w-full border-t border-gray-300" />
  191. </View>
  192. <View className="relative flex justify-center text-sm">
  193. <Text className="px-2 bg-white text-gray-500">其他登录方式</Text>
  194. </View>
  195. </View>
  196. <View className="mt-6">
  197. <Button
  198. className={cn(
  199. "w-full",
  200. "bg-green-500 text-white hover:bg-green-600",
  201. "border-none"
  202. )}
  203. size="lg"
  204. variant="default"
  205. onClick={() => {
  206. Taro.navigateTo({
  207. url: '/pages/login/wechat-login'
  208. })
  209. }}
  210. >
  211. <View className="i-heroicons-chat-bubble-left-right-20-solid w-5 h-5 mr-2" />
  212. 微信一键登录
  213. </Button>
  214. </View>
  215. </View>
  216. {/* 注册链接 */}
  217. <View className="mt-6 text-center">
  218. <Text className="text-sm text-gray-600">
  219. 还没有账号?
  220. <Text
  221. className="text-blue-500 font-medium hover:text-blue-600"
  222. onClick={goToRegister}
  223. >
  224. 立即注册
  225. </Text>
  226. </Text>
  227. </View>
  228. </View>
  229. {/* 协议声明 */}
  230. <View className="mt-8 text-center">
  231. <Text className="text-xs text-gray-500">
  232. 登录即表示您同意
  233. <Text className="text-blue-500 mx-1">用户协议</Text>
  234. <Text className="text-blue-500 mx-1">隐私政策</Text>
  235. </Text>
  236. </View>
  237. </View>
  238. </View>
  239. )
  240. }