UserView.vue 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. <template>
  2. <div class="user-page with-bottom-nav">
  3. <div class="user-header">
  4. <van-image
  5. :src="userStore.avatarUrl || 'https://picsum.photos/seed/avatar/200/200'"
  6. width="60"
  7. height="60"
  8. round
  9. fit="cover"
  10. />
  11. <h3>{{ userStore.isLoggedIn ? userStore.nickname : '游客' }}</h3>
  12. <p class="user-phone" v-if="userStore.phone">{{ userStore.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}</p>
  13. </div>
  14. <div class="user-stats">
  15. <div class="stat-item">
  16. <span class="stat-num">{{ wishCount }}</span>
  17. <span class="stat-label">许愿数</span>
  18. </div>
  19. <div class="stat-item">
  20. <span class="stat-num">{{ totalLikes }}</span>
  21. <span class="stat-label">收到的祝福</span>
  22. </div>
  23. </div>
  24. <van-cell-group inset>
  25. <van-cell title="我的愿望" icon="label-o" to="/my-wishes" is-link />
  26. <van-cell title="最近许愿" icon="clock-o" is-link @click="$router.push('/my-wishes')" />
  27. </van-cell-group>
  28. <div class="logout-section">
  29. <van-button
  30. v-if="userStore.isLoggedIn"
  31. type="danger"
  32. round
  33. plain
  34. block
  35. @click="handleLogout"
  36. >
  37. 退出登录
  38. </van-button>
  39. <van-button
  40. v-else
  41. type="primary"
  42. round
  43. block
  44. to="/login"
  45. >
  46. 登录 / 注册
  47. </van-button>
  48. </div>
  49. </div>
  50. </template>
  51. <script setup lang="ts">
  52. import { ref, onMounted } from 'vue'
  53. import { showConfirmDialog } from 'vant'
  54. import { useUserStore } from '@/stores/user'
  55. import { useRouter } from 'vue-router'
  56. import { logout as logoutApi, fetchUserSummary } from '@/api/auth'
  57. const userStore = useUserStore()
  58. const router = useRouter()
  59. const wishCount = ref(0)
  60. const totalLikes = ref(0)
  61. async function handleLogout() {
  62. await showConfirmDialog({
  63. title: '退出登录',
  64. message: '确定要退出当前账号吗?',
  65. confirmButtonText: '退出',
  66. confirmButtonColor: '#ee0a24',
  67. cancelButtonText: '取消',
  68. })
  69. try { await logoutApi() } catch { /* 即使后端调用失败也清除本地状态 */ }
  70. userStore.logout()
  71. router.push('/')
  72. }
  73. onMounted(async () => {
  74. if (userStore.isLoggedIn) {
  75. try {
  76. const summary = await fetchUserSummary()
  77. wishCount.value = summary.wishCount
  78. totalLikes.value = summary.receiveCount
  79. } catch { /* ignore */ }
  80. }
  81. })
  82. </script>
  83. <style scoped>
  84. .user-page {
  85. min-height: 100vh;
  86. background: #f7f8fa;
  87. }
  88. .user-header {
  89. background: linear-gradient(135deg, #07c160, #05a650);
  90. padding: 40px 20px 30px;
  91. text-align: center;
  92. color: #fff;
  93. }
  94. .user-header h3 {
  95. margin-top: 12px;
  96. font-size: 18px;
  97. }
  98. .user-phone {
  99. font-size: 13px;
  100. opacity: 0.8;
  101. margin-top: 4px;
  102. }
  103. .user-stats {
  104. display: flex;
  105. background: #fff;
  106. margin: -12px 16px 12px;
  107. border-radius: 12px;
  108. padding: 16px;
  109. box-shadow: 0 2px 8px rgba(0,0,0,0.06);
  110. }
  111. .stat-item {
  112. flex: 1;
  113. text-align: center;
  114. }
  115. .stat-num {
  116. display: block;
  117. font-size: 22px;
  118. font-weight: 700;
  119. color: #07c160;
  120. }
  121. .stat-label {
  122. font-size: 12px;
  123. color: #999;
  124. }
  125. .logout-section {
  126. padding: 30px 16px;
  127. }
  128. </style>