您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

338 行
7.8 KiB

  1. <template>
  2. <view class="profile">
  3. <ai-nav-title title="AI在线-解放你的生产力" />
  4. <scroll-view class="page-scroll" scroll-y>
  5. <view class="profile-content">
  6. <view class="balance-card">
  7. <image class="balance-texture" src="/static/profile/balance_texture.png" mode="aspectFit" />
  8. <view class="balance-copy">
  9. <text class="hello">{{ userStore.isLogin ? `Hi,${userStore.nickname}!` : 'Hi,手机微信昵称!' }}</text>
  10. <text class="balance-label">点数余额</text>
  11. <view class="balance-bottom">
  12. <text class="balance-value">{{ userStore.isLogin ? userStore.pointsText : '123456' }}</text>
  13. <view class="balance-actions">
  14. <view class="outline-btn" @click="go('bill')">账单</view>
  15. <view class="solid-btn" @click="go('recharge')">充值</view>
  16. </view>
  17. </view>
  18. </view>
  19. </view>
  20. <view class="menu-panel">
  21. <view v-for="item in menus" :key="item.key" class="menu-row" @click="onMenu(item)">
  22. <image class="menu-icon" :src="item.icon" mode="aspectFit" />
  23. <text class="menu-name">{{ item.name }}</text>
  24. <text v-if="item.hint" class="menu-hint">{{ item.hint }}</text>
  25. <image class="arrow" src="/static/profile/icon_arrow.png" mode="aspectFit" />
  26. </view>
  27. </view>
  28. <view class="logout" @click="logout">退出登录</view>
  29. </view>
  30. </scroll-view>
  31. </view>
  32. </template>
  33. <script setup>
  34. import { ref } from 'vue'
  35. import { onShow } from '@dcloudio/uni-app'
  36. import { inviteApi } from '@/api/index.js'
  37. import { useUserStore } from '@/store/user.js'
  38. import { useApp } from '@/utils/useApp.js'
  39. const userStore = useUserStore()
  40. const { toast } = useApp()
  41. const inviteMenuConfig = {
  42. key: 'invite',
  43. name: '邀请好友',
  44. hint: '得100点分享奖励',
  45. icon: '/static/profile/icon_invite.png',
  46. auth: true
  47. }
  48. const menus = ref([
  49. { key: 'account', name: '我的账号', hint: '提示文字#999', icon: '/static/profile/icon_account.png', auth: true },
  50. { ...inviteMenuConfig },
  51. { key: 'news', name: '前沿资讯', hint: '提示文字#999', icon: '/static/profile/icon_news.png', soon: true },
  52. { key: 'tutorial', name: '使用教程', icon: '/static/profile/icon_tutorial.png', soon: true },
  53. { key: 'official', name: '关注公众号', icon: '/static/profile/icon_official.png', soon: true },
  54. { key: 'contact', name: '联系我们', icon: '/static/profile/icon_contact.png' },
  55. { key: 'feedback', name: '意见反馈', hint: '得10点奖励', icon: '/static/profile/icon_feedback.png', soon: true },
  56. { key: 'terms', name: '服务条款', icon: '/static/profile/icon_terms.png' },
  57. { key: 'version', name: '当前版本', icon: '/static/profile/icon_version.png', soon: true }
  58. ])
  59. const MENU_ROUTE = {
  60. invite: '/pages/invite/invite',
  61. contact: '/pages/agreement/agreement?key=contact',
  62. terms: '/pages/agreement/agreement?key=terms'
  63. }
  64. function requireLogin() {
  65. if (userStore.isLogin) return true
  66. uni.navigateTo({ url: '/pages/login/login' })
  67. return false
  68. }
  69. function onMenu(item) {
  70. if (item.auth && !requireLogin()) return
  71. if (item.soon) return toast(`${item.name} 即将上线`)
  72. const route = MENU_ROUTE[item.key]
  73. if (route) return uni.navigateTo({ url: route })
  74. toast(`${item.name}(待开发)`)
  75. }
  76. function go(page) {
  77. if (!requireLogin()) return
  78. if (page === 'bill') return uni.navigateTo({ url: '/pages/bill/bill?type=recharge' })
  79. if (page === 'recharge') return uni.navigateTo({ url: '/pages/recharge/recharge' })
  80. toast(`${page}(待开发)`)
  81. }
  82. function syncInviteMenu(config = {}) {
  83. const currentIndex = menus.value.findIndex((item) => item.key === 'invite')
  84. if (Number(config.status) !== 1) {
  85. if (currentIndex > -1) menus.value.splice(currentIndex, 1)
  86. return
  87. }
  88. const points = Number(config.inviter_points || 0)
  89. const item = {
  90. ...inviteMenuConfig,
  91. hint: points > 0 ? `得${points}点分享奖励` : '分享给好友'
  92. }
  93. if (currentIndex > -1) {
  94. menus.value.splice(currentIndex, 1, item)
  95. return
  96. }
  97. const accountIndex = menus.value.findIndex((menu) => menu.key === 'account')
  98. menus.value.splice(accountIndex > -1 ? accountIndex + 1 : 0, 0, item)
  99. }
  100. async function loadProfile() {
  101. if (!userStore.isLogin) return
  102. try {
  103. await Promise.all([userStore.fetchUserInfo(), userStore.fetchBalance()])
  104. } catch (e) {
  105. // 登录失效由 request 统一处理
  106. }
  107. }
  108. async function loadInviteConfig() {
  109. try {
  110. const res = await inviteApi.getConfig()
  111. const config = (res.data && res.data.config) || {}
  112. syncInviteMenu(config)
  113. } catch (e) {}
  114. }
  115. function logout() {
  116. if (!userStore.isLogin) return requireLogin()
  117. uni.showModal({
  118. title: '提示',
  119. content: '确定退出登录?',
  120. success: (r) => {
  121. if (r.confirm) {
  122. userStore.clear()
  123. toast('已退出登录')
  124. }
  125. }
  126. })
  127. }
  128. onShow(() => {
  129. loadInviteConfig()
  130. loadProfile()
  131. })
  132. </script>
  133. <style lang="scss" scoped>
  134. .profile {
  135. height: 100vh;
  136. display: flex;
  137. flex-direction: column;
  138. overflow: hidden;
  139. background: #f3f3f3;
  140. }
  141. .page-scroll {
  142. flex: 1;
  143. height: 0;
  144. }
  145. .profile-content {
  146. min-height: 100vh;
  147. padding: 30rpx 30rpx 120rpx;
  148. box-sizing: border-box;
  149. background: #f3f3f3;
  150. }
  151. .balance-card {
  152. position: relative;
  153. height: 266rpx;
  154. border-radius: 24rpx;
  155. overflow: hidden;
  156. background: linear-gradient(135deg, #3f94ff 0%, #2384f8 100%);
  157. box-sizing: border-box;
  158. }
  159. .balance-texture {
  160. position: absolute;
  161. right: 13rpx;
  162. top: -44rpx;
  163. width: 269rpx;
  164. height: 288rpx;
  165. }
  166. .balance-copy {
  167. position: relative;
  168. z-index: 1;
  169. height: 100%;
  170. padding: 44rpx 62rpx;
  171. display: flex;
  172. flex-direction: column;
  173. box-sizing: border-box;
  174. }
  175. .hello {
  176. max-width: 430rpx;
  177. color: #ffffff;
  178. font-size: 27rpx;
  179. line-height: 1;
  180. overflow: hidden;
  181. text-overflow: ellipsis;
  182. white-space: nowrap;
  183. }
  184. .balance-label {
  185. margin-top: auto;
  186. color: rgba(255, 255, 255, 0.65);
  187. font-size: 25rpx;
  188. line-height: 1;
  189. }
  190. .balance-value {
  191. color: #ffffff;
  192. font-size: 40rpx;
  193. font-weight: 700;
  194. line-height: 1;
  195. }
  196. .balance-bottom {
  197. margin-top: 16rpx;
  198. display: flex;
  199. align-items: center;
  200. justify-content: space-between;
  201. gap: 24rpx;
  202. }
  203. .balance-actions {
  204. display: flex;
  205. gap: 22rpx;
  206. flex-shrink: 0;
  207. }
  208. .outline-btn,
  209. .solid-btn {
  210. width: 127rpx;
  211. height: 58rpx;
  212. border-radius: 29rpx;
  213. display: flex;
  214. align-items: center;
  215. justify-content: center;
  216. font-size: 27rpx;
  217. box-sizing: border-box;
  218. }
  219. .outline-btn {
  220. color: #ffffff;
  221. border: 1rpx solid rgba(255, 255, 255, 0.75);
  222. background: rgba(255, 255, 255, 0.08);
  223. }
  224. .solid-btn {
  225. color: #ffffff;
  226. border: 1rpx solid rgba(255, 255, 255, 0.75);
  227. background: rgba(255, 255, 255, 0.08);
  228. }
  229. .menu-panel {
  230. margin-top: 28rpx;
  231. padding-top: 18rpx;
  232. padding-bottom: 14rpx;
  233. border-radius: 21rpx;
  234. background: #ffffff;
  235. overflow: hidden;
  236. box-sizing: border-box;
  237. }
  238. .menu-row {
  239. position: relative;
  240. height: 100rpx;
  241. padding: 0 27rpx 0 27rpx;
  242. display: flex;
  243. align-items: center;
  244. box-sizing: border-box;
  245. &::after {
  246. content: '';
  247. position: absolute;
  248. left: 25rpx;
  249. right: 24rpx;
  250. bottom: 0;
  251. height: 1rpx;
  252. background: #eeeeee;
  253. }
  254. &:last-child::after {
  255. display: none;
  256. }
  257. }
  258. .menu-icon {
  259. width: 34rpx;
  260. height: 34rpx;
  261. flex-shrink: 0;
  262. }
  263. .menu-name {
  264. margin-left: 25rpx;
  265. color: #333333;
  266. font-size: 28rpx;
  267. line-height: 1;
  268. flex: 1;
  269. min-width: 0;
  270. }
  271. .menu-hint {
  272. margin-left: 20rpx;
  273. max-width: 300rpx;
  274. color: #999999;
  275. font-size: 26rpx;
  276. line-height: 1;
  277. overflow: hidden;
  278. text-overflow: ellipsis;
  279. white-space: nowrap;
  280. }
  281. .arrow {
  282. width: 14rpx;
  283. height: 22rpx;
  284. margin-left: 20rpx;
  285. flex-shrink: 0;
  286. }
  287. .logout {
  288. height: 104rpx;
  289. color: #333333;
  290. font-size: 27rpx;
  291. display: flex;
  292. align-items: center;
  293. justify-content: center;
  294. }
  295. </style>