index.tsx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import { useState, useEffect } from 'react'
  2. import { View, Text, ScrollView } from '@tarojs/components'
  3. import Taro from '@tarojs/taro'
  4. import { TabBarLayout } from '@/layouts/tab-bar-layout'
  5. import { useAuth , type User as UserProfile} from '@/utils/auth'
  6. import { cn } from '@/utils/cn'
  7. import { Button } from '@/components/ui/button'
  8. import { Image } from '@/components/ui/image'
  9. import { Navbar } from '@/components/ui/navbar'
  10. import { AvatarUpload } from '@/components/ui/avatar-upload'
  11. import { uploadFromChoose, type UploadResult } from '@/utils/minio'
  12. import './index.css'
  13. const ProfilePage: React.FC = () => {
  14. const { user: userProfile, logout, isLoading: loading, updateUser } = useAuth()
  15. const [updatingAvatar, setUpdatingAvatar] = useState(false)
  16. const handleLogout = async () => {
  17. try {
  18. Taro.showModal({
  19. title: '退出登录',
  20. content: '确定要退出登录吗?',
  21. success: async (res) => {
  22. if (res.confirm) {
  23. Taro.showLoading({ title: '退出中...' })
  24. await logout()
  25. Taro.hideLoading()
  26. Taro.showToast({
  27. title: '已退出登录',
  28. icon: 'success',
  29. duration: 1500
  30. })
  31. setTimeout(() => {
  32. Taro.reLaunch({ url: '/pages/index/index' })
  33. }, 1500)
  34. }
  35. }
  36. })
  37. } catch (error) {
  38. Taro.hideLoading()
  39. Taro.showToast({
  40. title: '退出失败,请重试',
  41. icon: 'none'
  42. })
  43. }
  44. }
  45. const handleAvatarUpload = async (result: UploadResult) => {
  46. try {
  47. setUpdatingAvatar(true)
  48. Taro.showLoading({ title: '更新头像...' })
  49. // 这里应该调用更新用户头像的API
  50. // 假设有一个更新用户信息的API
  51. console.log('头像上传成功:', result)
  52. // 更新本地用户数据
  53. if (userProfile) {
  54. const updatedUser = {
  55. ...userProfile,
  56. avatarFile: {
  57. ...userProfile.avatarFile,
  58. fullUrl: result.fileUrl
  59. }
  60. }
  61. updateUser(updatedUser)
  62. }
  63. Taro.hideLoading()
  64. Taro.showToast({
  65. title: '头像更新成功',
  66. icon: 'success'
  67. })
  68. } catch (error) {
  69. console.error('更新头像失败:', error)
  70. Taro.hideLoading()
  71. Taro.showToast({
  72. title: '更新头像失败',
  73. icon: 'none'
  74. })
  75. } finally {
  76. setUpdatingAvatar(false)
  77. }
  78. }
  79. const handleAvatarUploadError = (error: Error) => {
  80. console.error('头像上传失败:', error)
  81. Taro.showToast({
  82. title: '上传失败,请重试',
  83. icon: 'none'
  84. })
  85. }
  86. const handleEditProfile = () => {
  87. Taro.showToast({
  88. title: '功能开发中...',
  89. icon: 'none'
  90. })
  91. }
  92. const handleSettings = () => {
  93. Taro.showToast({
  94. title: '功能开发中...',
  95. icon: 'none'
  96. })
  97. }
  98. const menuItems = [
  99. {
  100. icon: 'i-heroicons-user-circle-20-solid',
  101. title: '编辑资料',
  102. onClick: handleEditProfile,
  103. color: 'text-blue-500'
  104. },
  105. {
  106. icon: 'i-heroicons-cog-6-tooth-20-solid',
  107. title: '设置',
  108. onClick: handleSettings,
  109. color: 'text-gray-500'
  110. },
  111. {
  112. icon: 'i-heroicons-shield-check-20-solid',
  113. title: '隐私政策',
  114. onClick: () => Taro.showToast({ title: '功能开发中...', icon: 'none' }),
  115. color: 'text-green-500'
  116. },
  117. {
  118. icon: 'i-heroicons-question-mark-circle-20-solid',
  119. title: '帮助与反馈',
  120. onClick: () => Taro.showToast({ title: '功能开发中...', icon: 'none' }),
  121. color: 'text-purple-500'
  122. }
  123. ]
  124. if (loading) {
  125. return (
  126. <TabBarLayout activeKey="profile">
  127. <View className="flex-1 flex items-center justify-center">
  128. <View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
  129. </View>
  130. </TabBarLayout>
  131. )
  132. }
  133. if (!userProfile) {
  134. return (
  135. <TabBarLayout activeKey="profile">
  136. <Navbar
  137. title="个人中心"
  138. leftIcon=""
  139. />
  140. <View className="flex-1 flex flex-col items-center justify-center">
  141. <View className="flex flex-col items-center">
  142. <View className="i-heroicons-exclamation-circle-20-solid w-12 h-12 text-gray-400 mx-auto mb-4" />
  143. <Text className="text-gray-600 mb-4">请先登录</Text>
  144. <Button
  145. variant="default"
  146. size="lg"
  147. onClick={() => Taro.navigateTo({ url: '/pages/login/index' })}
  148. >
  149. 去登录
  150. </Button>
  151. </View>
  152. </View>
  153. </TabBarLayout>
  154. )
  155. }
  156. return (
  157. <TabBarLayout activeKey="profile">
  158. <Navbar
  159. title="个人中心"
  160. rightIcon="i-heroicons-cog-6-tooth-20-solid"
  161. onClickRight={handleSettings}
  162. leftIcon=""
  163. />
  164. <ScrollView className="flex-1 bg-gray-50">
  165. {/* 用户信息卡片 */}
  166. <View className="bg-white rounded-b-3xl shadow-sm pb-8">
  167. <View className="flex flex-col items-center pt-8 pb-6">
  168. <View className="relative">
  169. <AvatarUpload
  170. currentAvatar={userProfile.avatarFile?.fullUrl}
  171. onUploadSuccess={handleAvatarUpload}
  172. onUploadError={handleAvatarUploadError}
  173. size={96}
  174. editable={!updatingAvatar}
  175. />
  176. </View>
  177. <Text className="text-xl font-bold text-gray-900 mt-4">{userProfile.username}</Text>
  178. {userProfile.email && (
  179. <Text className="text-sm text-gray-600 mt-1">{userProfile.email}</Text>
  180. )}
  181. <View className="flex items-center mt-2">
  182. <View className="i-heroicons-calendar-20-solid w-4 h-4 text-gray-400 mr-1" />
  183. <Text className="text-xs text-gray-500">
  184. 注册于 {new Date(userProfile.createdAt).toLocaleDateString('zh-CN')}
  185. </Text>
  186. </View>
  187. </View>
  188. {/* 统计信息 */}
  189. <View className="px-6">
  190. <View className="grid grid-cols-3 gap-4 text-center">
  191. <View className="bg-gray-50 rounded-xl p-4">
  192. <Text className="text-2xl font-bold text-blue-500">0</Text>
  193. <Text className="text-xs text-gray-600 mt-1">收藏</Text>
  194. </View>
  195. <View className="bg-gray-50 rounded-xl p-4">
  196. <Text className="text-2xl font-bold text-green-500">0</Text>
  197. <Text className="text-xs text-gray-600 mt-1">点赞</Text>
  198. </View>
  199. <View className="bg-gray-50 rounded-xl p-4">
  200. <Text className="text-2xl font-bold text-purple-500">0</Text>
  201. <Text className="text-xs text-gray-600 mt-1">关注</Text>
  202. </View>
  203. </View>
  204. </View>
  205. </View>
  206. {/* 功能菜单 */}
  207. <View className="px-4 pt-6">
  208. <View className="bg-white rounded-2xl shadow-sm overflow-hidden">
  209. {menuItems.map((item, index) => (
  210. <View
  211. key={index}
  212. className="flex items-center px-4 py-4 active:bg-gray-50 transition-colors duration-150"
  213. onClick={item.onClick}
  214. >
  215. <View className={cn("w-6 h-6 mr-3", item.color, item.icon)} />
  216. <Text className="flex-1 text-gray-800">{item.title}</Text>
  217. <View className="i-heroicons-chevron-right-20-solid w-5 h-5 text-gray-400" />
  218. </View>
  219. ))}
  220. </View>
  221. </View>
  222. {/* 账号信息 */}
  223. <View className="px-4 pt-6">
  224. <View className="bg-white rounded-2xl shadow-sm p-4">
  225. <Text className="text-sm font-medium text-gray-700 mb-3">账号信息</Text>
  226. <View className="space-y-3">
  227. <View className="flex justify-between items-center">
  228. <Text className="text-sm text-gray-600">用户ID</Text>
  229. <Text className="text-sm text-gray-900 font-mono">{userProfile.id}</Text>
  230. </View>
  231. {userProfile.updatedAt && (
  232. <View className="flex justify-between items-center">
  233. <Text className="text-sm text-gray-600">最近登录</Text>
  234. <Text className="text-sm text-gray-900">
  235. {new Date(userProfile.updatedAt).toLocaleString('zh-CN')}
  236. </Text>
  237. </View>
  238. )}
  239. </View>
  240. </View>
  241. </View>
  242. {/* 退出登录按钮 */}
  243. <View className="px-4 pt-6 pb-8">
  244. <Button
  245. variant="destructive"
  246. size="lg"
  247. className="w-full"
  248. onClick={handleLogout}
  249. >
  250. <View className="flex items-center justify-center">
  251. <View className="i-heroicons-arrow-left-on-rectangle-20-solid w-5 h-5 mr-2" />
  252. 退出登录
  253. </View>
  254. </Button>
  255. </View>
  256. {/* 版本信息 */}
  257. <View className="pb-8">
  258. <Text className="text-center text-xs text-gray-400">
  259. v1.0.0 - 小程序版
  260. </Text>
  261. </View>
  262. </ScrollView>
  263. </TabBarLayout>
  264. )
  265. }
  266. export default ProfilePage