index.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. <template>
  2. <view class="page-profile">
  3. <!-- US-5.1 §13: Profile incomplete banner -->
  4. <view v-if="userStore.isLoggedIn && userStore.profileIncomplete" class="banner" @click="goEditProfile">
  5. <text class="banner-text">📝 请完善个人资料,开启能量匹配</text>
  6. <text class="banner-arrow">›</text>
  7. </view>
  8. <!-- User info card -->
  9. <view class="user-card" @click="goLogin">
  10. <view class="avatar">
  11. <image :src="userStore.avatarUrl || '/static/default-avatar.png'" mode="aspectFill" />
  12. </view>
  13. <text class="nickname">{{ userStore.nickname || '未登录' }}</text>
  14. <text v-if="!userStore.isLoggedIn" class="login-hint">点击登录</text>
  15. <view v-if="userStore.isVip" class="vip-badge">VIP 会员</view>
  16. </view>
  17. <!-- VIP status -->
  18. <view v-if="userStore.vipType === 'practitioner'" class="vip-card pro" @click="goPayment">
  19. <view class="vip-info">
  20. <text class="vip-title">能量师会员</text>
  21. <text class="vip-desc">
  22. 有效期至: {{ userStore.vipEndTime?.slice(0, 10) }}
  23. </text>
  24. </view>
  25. <text class="vip-action">续费</text>
  26. </view>
  27. <view v-else-if="userStore.vipType === 'annual'" class="vip-card annual" @click="goUpgradePractitioner">
  28. <view class="vip-info">
  29. <text class="vip-title">C端会员</text>
  30. <text class="vip-desc">
  31. 有效期至: {{ userStore.vipEndTime?.slice(0, 10) }}
  32. </text>
  33. </view>
  34. <text class="vip-action">升级能量师</text>
  35. </view>
  36. <view v-else class="vip-card" @click="goPayment">
  37. <view class="vip-info">
  38. <text class="vip-title">VIP 会员</text>
  39. <text class="vip-desc">
  40. 开通VIP获取完整分析
  41. </text>
  42. </view>
  43. <text class="vip-action">立即开通</text>
  44. </view>
  45. <!-- Daily quota (non-VIP) -->
  46. <view v-if="!userStore.isVip" class="quota-card">
  47. <view class="quota-item">
  48. <text class="quota-label">今日分析</text>
  49. <text class="quota-value">{{ userStore.usedCharts }}/{{ userStore.maxCharts }}</text>
  50. </view>
  51. <view class="quota-divider"></view>
  52. <view class="quota-item">
  53. <text class="quota-label">今日解读</text>
  54. <text class="quota-value">{{ userStore.usedChats }}/{{ userStore.maxChats }}</text>
  55. </view>
  56. </view>
  57. <!-- Menu list -->
  58. <view class="menu-section">
  59. <view class="menu-item" @click="goEditProfile">
  60. <text>✏️ 编辑资料</text>
  61. <text class="menu-arrow">›</text>
  62. </view>
  63. <view class="menu-item" @click="goRecords">
  64. <text>📋 我的分析</text>
  65. <text class="menu-arrow">›</text>
  66. </view>
  67. <view class="menu-item" @click="goCommission">
  68. <text>💰 我的推广</text>
  69. <text class="menu-arrow">›</text>
  70. </view>
  71. <view class="menu-item" @click="copyReferral">
  72. <text>🔗 推广码: {{ userStore.referralCode || '加载中' }}</text>
  73. <text class="menu-tip">点击复制</text>
  74. </view>
  75. </view>
  76. <!-- Actions -->
  77. <view class="menu-section">
  78. <view class="menu-item" @click="handleLogout">
  79. <text style="color: #EF4444;">退出登录</text>
  80. </view>
  81. </view>
  82. </view>
  83. </template>
  84. <script setup>
  85. import { onShow } from '@dcloudio/uni-app'
  86. import { useUserStore } from '@/stores/user'
  87. const userStore = useUserStore()
  88. onShow(() => {
  89. if (userStore.isLoggedIn) {
  90. userStore.fetchProfile()
  91. userStore.fetchQuota()
  92. userStore.fetchCommissionSummary()
  93. }
  94. })
  95. function goEditProfile() {
  96. if (!userStore.isLoggedIn) {
  97. uni.navigateTo({ url: '/pages/login/index' })
  98. return
  99. }
  100. uni.navigateTo({ url: '/pages/profile/edit' })
  101. }
  102. function goLogin() {
  103. if (!userStore.isLoggedIn) {
  104. uni.navigateTo({ url: '/pages/login/index' })
  105. }
  106. }
  107. function goPayment() {
  108. uni.navigateTo({ url: '/pages/payment/index?product=annual' })
  109. }
  110. function goUpgradePractitioner() {
  111. uni.navigateTo({ url: '/pages/payment/index?product=practitioner' })
  112. }
  113. function goRecords() {
  114. uni.switchTab({ url: '/pages/records/index' })
  115. }
  116. function goCommission() {
  117. if (!userStore.isLoggedIn) {
  118. uni.navigateTo({ url: '/pages/login/index' })
  119. return
  120. }
  121. uni.navigateTo({ url: '/pages/commission/index' })
  122. }
  123. function copyReferral() {
  124. if (userStore.referralCode) {
  125. uni.setClipboardData({
  126. data: userStore.referralCode,
  127. success: () => uni.showToast({ title: '已复制推广码', icon: 'success' })
  128. })
  129. }
  130. }
  131. function handleLogout() {
  132. uni.showModal({
  133. title: '确认退出',
  134. content: '确定要退出登录吗?',
  135. success: (res) => {
  136. if (res.confirm) {
  137. userStore.logout()
  138. uni.showToast({ title: '已退出', icon: 'success' })
  139. }
  140. }
  141. })
  142. }
  143. </script>
  144. <style scoped lang="scss">
  145. .page-profile { padding: 16px; }
  146. .user-card {
  147. text-align: center; padding: 30px 0; background: #fff;
  148. border-radius: 12px; margin-bottom: 16px;
  149. }
  150. .avatar {
  151. width: 64px; height: 64px; border-radius: 50%; overflow: hidden;
  152. margin: 0 auto 12px;
  153. }
  154. .avatar image { width: 100%; height: 100%; }
  155. .nickname { font-size: 18px; font-weight: 600; display: block; color: #1F2937; }
  156. .login-hint { font-size: 12px; color: #3B82F6; display: block; margin-top: 4px; }
  157. .vip-badge {
  158. display: inline-block; background: #FEF3C7; color: #D97706;
  159. padding: 2px 12px; border-radius: 12px; font-size: 12px; margin-top: 8px;
  160. }
  161. .vip-card {
  162. background: linear-gradient(135deg, #3B82F6, #2563EB); border-radius: 12px;
  163. padding: 16px; display: flex; justify-content: space-between;
  164. align-items: center; margin-bottom: 16px;
  165. }
  166. .vip-card.annual {
  167. background: linear-gradient(135deg, #f59e0b, #d97706);
  168. }
  169. .vip-card.pro {
  170. background: linear-gradient(135deg, #7c3aed, #6d28d9);
  171. }
  172. .vip-title { color: #fff; font-weight: 600; font-size: 16px; display: block; }
  173. .vip-desc { color: rgba(255,255,255,0.8); font-size: 12px; display: block; margin-top: 4px; }
  174. .vip-action { color: #fff; font-size: 14px; font-weight: 500; }
  175. .menu-section { background: #fff; border-radius: 12px; margin-bottom: 16px; overflow: hidden; }
  176. .menu-item {
  177. display: flex; justify-content: space-between; align-items: center;
  178. padding: 14px 16px; border-bottom: 1px solid #F3F4F6;
  179. }
  180. .menu-item:last-child { border-bottom: none; }
  181. .menu-arrow { color: #9CA3AF; font-size: 18px; }
  182. .menu-tip { color: #3B82F6; font-size: 12px; }
  183. .quota-card {
  184. background: #fff; border-radius: 12px; padding: 16px; margin-bottom: 16px;
  185. display: flex; align-items: center; justify-content: space-around;
  186. }
  187. .quota-item { text-align: center; }
  188. .quota-label { font-size: 12px; color: #6B7280; display: block; margin-bottom: 4px; }
  189. .quota-value { font-size: 20px; font-weight: 700; color: #1F2937; }
  190. .quota-divider { width: 1px; height: 36px; background: #E5E7EB; }
  191. /* US-5.1 §13 profile incomplete banner */
  192. .banner {
  193. background: #FEF3C7; border: 1px solid #FDE68A; border-radius: 10px;
  194. padding: 10px 14px; margin-bottom: 12px;
  195. display: flex; justify-content: space-between; align-items: center;
  196. }
  197. .banner-text { font-size: 13px; color: #92400E; flex: 1; }
  198. .banner-arrow { font-size: 18px; color: #92400E; margin-left: 8px; }
  199. </style>