index.tsx 9.0 KB

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