index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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-users-20-solid',
  103. title: '乘车人管理',
  104. onClick: () => Taro.navigateTo({ url: '/pages/passengers/passengers' }),
  105. color: 'text-orange-500'
  106. },
  107. {
  108. icon: 'i-heroicons-cog-6-tooth-20-solid',
  109. title: '设置',
  110. onClick: handleSettings,
  111. color: 'text-gray-500'
  112. },
  113. {
  114. icon: 'i-heroicons-shield-check-20-solid',
  115. title: '隐私政策',
  116. onClick: () => Taro.showToast({ title: '功能开发中...', icon: 'none' }),
  117. color: 'text-green-500'
  118. },
  119. {
  120. icon: 'i-heroicons-question-mark-circle-20-solid',
  121. title: '帮助与反馈',
  122. onClick: () => Taro.showToast({ title: '功能开发中...', icon: 'none' }),
  123. color: 'text-purple-500'
  124. }
  125. ]
  126. if (loading) {
  127. return (
  128. <TabBarLayout activeKey="profile">
  129. <View className="flex-1 flex items-center justify-center">
  130. <View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
  131. </View>
  132. </TabBarLayout>
  133. )
  134. }
  135. if (!userProfile) {
  136. return (
  137. <TabBarLayout activeKey="profile">
  138. <Navbar
  139. title="个人中心"
  140. leftIcon=""
  141. />
  142. <View className="flex-1 flex flex-col items-center justify-center">
  143. <View className="flex flex-col items-center">
  144. <View className="i-heroicons-exclamation-circle-20-solid w-12 h-12 text-gray-400 mx-auto mb-4" />
  145. <Text className="text-gray-600 mb-4">请先登录</Text>
  146. <Button
  147. variant="default"
  148. size="lg"
  149. onClick={() => Taro.navigateTo({ url: '/pages/login/index' })}
  150. >
  151. 去登录
  152. </Button>
  153. </View>
  154. </View>
  155. </TabBarLayout>
  156. )
  157. }
  158. return (
  159. <TabBarLayout activeKey="profile">
  160. <Navbar
  161. title="个人中心"
  162. rightIcon="i-heroicons-cog-6-tooth-20-solid"
  163. onClickRight={handleSettings}
  164. leftIcon=""
  165. backgroundColor="bg-primary"
  166. textColor="text-white"
  167. border={false}
  168. />
  169. <ScrollView className="flex-1 bg-[#F8F9FA] pb-10">
  170. {/* 用户信息区域 - 渐变背景 */}
  171. <View className="bg-gradient-to-br from-[#4A90C2] to-[#357ABD] p-[40rpx_32rpx_32rpx_32rpx] text-white">
  172. <View className="flex items-center">
  173. <View className="relative mr-[32rpx]">
  174. <AvatarUpload
  175. currentAvatar={userProfile.avatarFile?.fullUrl}
  176. onUploadSuccess={handleAvatarUpload}
  177. onUploadError={handleAvatarUploadError}
  178. size={100}
  179. editable={!updatingAvatar}
  180. className="border-3 border-white/30"
  181. />
  182. </View>
  183. <View className="flex-1">
  184. <Text className="text-[32rpx] font-bold text-white">{userProfile.username}</Text>
  185. <Text className="text-[22rpx] opacity-80 mt-1">
  186. ID: {String(userProfile.id).slice(-4)}
  187. </Text>
  188. </View>
  189. </View>
  190. </View>
  191. {/* 会员信息卡片 */}
  192. <View className="m-[24rpx_32rpx] hidden">
  193. <View
  194. className="bg-gradient-to-br from-[#667eea] to-[#764ba2] rounded-[20rpx] p-[24rpx] text-white shadow-[0_6rpx_24rpx_rgba(102,126,234,0.3)]"
  195. onClick={() => Taro.showToast({ title: '会员功能开发中...', icon: 'none' })}
  196. >
  197. <View className="flex justify-between items-center mb-[20rpx]">
  198. <View className="flex items-center">
  199. <View className="i-heroicons-star-20-solid text-[32rpx] mr-[10rpx]" />
  200. <Text className="text-[32rpx] font-bold">普通会员</Text>
  201. </View>
  202. <View className="text-[28rpx] opacity-80">{'>'}</View>
  203. </View>
  204. <View className="flex justify-around">
  205. <View className="text-center">
  206. <Text className="text-[32rpx] font-bold block mb-[6rpx]">0</Text>
  207. <Text className="text-[22rpx] opacity-80">积分</Text>
  208. </View>
  209. <View className="text-center">
  210. <Text className="text-[32rpx] font-bold block mb-[6rpx]">¥0</Text>
  211. <Text className="text-[22rpx] opacity-80">累计消费</Text>
  212. </View>
  213. </View>
  214. {/* 会员进度条 */}
  215. <View className="bg-white/10 rounded-[16rpx] p-[20rpx] mt-[20rpx]">
  216. <Text className="text-[24rpx] opacity-90 mb-[12rpx] text-center">
  217. 距离升级还需消费 ¥1000
  218. </Text>
  219. <View className="h-[8rpx] bg-white/20 rounded-[4rpx] overflow-hidden">
  220. <View
  221. className="h-full bg-gradient-to-r from-[#FFD700] to-[#FFA500] rounded-[4rpx] transition-all duration-300"
  222. style={{ width: '30%' }}
  223. />
  224. </View>
  225. </View>
  226. </View>
  227. </View>
  228. {/* 功能菜单 */}
  229. <View className="m-[24rpx_32rpx]">
  230. <Text className="text-[30rpx] font-bold text-[#333] mb-[20rpx]">我的服务</Text>
  231. <View className="bg-white rounded-[20rpx] shadow-[0_4rpx_20rpx_rgba(0,0,0,0.08)] border border-[#E5E5EA] overflow-hidden">
  232. {menuItems.map((item, index) => (
  233. <View
  234. key={index}
  235. className="flex items-center p-[28rpx_32rpx] border-b-2 border-[#E5E5EA] active:bg-[#F8F9FA] transition-colors duration-300"
  236. onClick={item.onClick}
  237. >
  238. <View className={cn("w-6 h-6 mr-3", item.color, item.icon)} />
  239. <View className="flex-1 ml-0">
  240. <Text className="text-[30rpx] font-bold text-[#333] block">{item.title}</Text>
  241. <Text className="text-[24rpx] text-[#666] mt-[6rpx]">
  242. {item.title === '编辑资料' && '修改个人信息'}
  243. {item.title === '乘车人管理' && '管理乘车人信息'}
  244. {item.title === '设置' && '应用设置'}
  245. {item.title === '隐私政策' && '查看隐私政策'}
  246. {item.title === '帮助与反馈' && '获取帮助和反馈'}
  247. </Text>
  248. </View>
  249. <View className="text-[28rpx] text-[#ccc] font-bold">{'>'}</View>
  250. </View>
  251. ))}
  252. </View>
  253. </View>
  254. {/* 客服与帮助区域 */}
  255. <View className="m-[24rpx_32rpx]">
  256. <Text className="text-[30rpx] font-bold text-[#333] mb-[20rpx]">客服与帮助</Text>
  257. <View className="bg-white rounded-[20rpx] shadow-[0_4rpx_20rpx_rgba(0,0,0,0.08)] border border-[#E5E5EA] overflow-hidden">
  258. {[
  259. { title: '联系客服', desc: '7x24小时在线客服', icon: 'i-heroicons-phone-20-solid', color: 'text-blue-500' },
  260. { title: '常见问题', desc: '查看常见问题解答', icon: 'i-heroicons-question-mark-circle-20-solid', color: 'text-green-500' },
  261. { title: '意见反馈', desc: '提出宝贵意见', icon: 'i-heroicons-chat-bubble-left-ellipsis-20-solid', color: 'text-orange-500' }
  262. ].map((item, index) => (
  263. <View
  264. key={index}
  265. className="flex items-center p-[28rpx_32rpx] border-b-2 border-[#E5E5EA] active:bg-[#F8F9FA] transition-colors duration-300"
  266. onClick={() => Taro.showToast({ title: `${item.title}功能开发中...`, icon: 'none' })}
  267. >
  268. <View className={cn("w-6 h-6 mr-3", item.color, item.icon)} />
  269. <View className="flex-1 ml-0">
  270. <Text className="text-[30rpx] font-bold text-[#333] block">{item.title}</Text>
  271. <Text className="text-[24rpx] text-[#666]">{item.desc}</Text>
  272. </View>
  273. <View className="text-[28rpx] text-[#ccc] font-bold">{'>'}</View>
  274. </View>
  275. ))}
  276. </View>
  277. </View>
  278. {/* 退出登录按钮 */}
  279. <View className="m-[24rpx_32rpx]">
  280. <View className="bg-white rounded-[20rpx] shadow-[0_4rpx_20rpx_rgba(0,0,0,0.08)] border border-[#E5E5EA] overflow-hidden">
  281. <View
  282. className="flex items-center justify-center p-[28rpx_32rpx] active:bg-[#F8F9FA] transition-colors duration-300"
  283. onClick={handleLogout}
  284. >
  285. <View className="i-heroicons-arrow-left-on-rectangle-20-solid w-6 h-6 text-red-500 mr-3" />
  286. <Text className="text-[30rpx] font-bold text-red-500">退出登录</Text>
  287. </View>
  288. </View>
  289. </View>
  290. {/* 版本信息 */}
  291. <View className="text-center py-[32rpx]">
  292. <Text className="text-[22rpx] text-[#999]">去看出行 v1.0.0</Text>
  293. </View>
  294. </ScrollView>
  295. </TabBarLayout>
  296. )
  297. }
  298. export default ProfilePage