|
- <template>
- <view class="profile">
- <ai-nav-title title="个人中心" />
-
- <scroll-view class="page-scroll" scroll-y>
- <view class="profile-content">
- <view class="balance-card" :class="{ 'guest-card': !userStore.isLogin }">
- <image class="balance-texture" src="https://cdn.aionline.cc/ai/profile/balance_texture.png" mode="aspectFit" />
- <view v-if="userStore.isLogin" class="balance-copy">
- <text class="hello">{{ `Hi,${userStore.nickname || 'AI在线用户'}!` }}</text>
- <text class="balance-label">点数余额</text>
- <view class="balance-bottom">
- <text class="balance-value">{{ userStore.pointsText }}</text>
- <view class="balance-actions">
- <view class="outline-btn" @click="go('bill')">账单</view>
- <view class="solid-btn" @click="go('recharge')">充值</view>
- </view>
- </view>
- </view>
- <view v-else class="guest-copy">
- <view class="guest-main">
- <view class="guest-avatar">
- <up-icon name="account-fill" color="#2384f8" size="28" />
- </view>
- <view class="guest-text">
- <text class="guest-title">登录 AI在线</text>
- <text class="guest-description">同步点数、作品与创作记录</text>
- </view>
- </view>
- <view class="guest-bottom">
- <text class="guest-tip">登录后可跨设备查看创作数据</text>
- <view class="login-btn" hover-class="login-btn--pressed" @click="requireLogin">
- <text>立即登录</text>
- <up-icon name="arrow-right" color="#2384f8" size="14" />
- </view>
- </view>
- </view>
- </view>
-
- <!-- 按用途分组,组间留白,避免一长条列表 -->
- <view v-for="group in menuGroups" :key="group.key" class="menu-group">
- <text class="group-title">{{ group.title }}</text>
- <view class="menu-panel">
- <view v-for="item in group.items" :key="item.key" class="menu-row" @click="onMenu(item)">
- <view class="menu-icon" :style="{ background: item.iconBg }">
- <up-icon :name="item.icon" :color="item.iconColor" size="20" />
- </view>
- <text class="menu-name">{{ item.name }}</text>
- <text v-if="item.hint" class="menu-hint">{{ item.hint }}</text>
- <up-icon name="arrow-right" color="#c3cddb" size="14" />
- </view>
- </view>
- </view>
- </view>
- </scroll-view>
- </view>
- </template>
-
- <script setup>
- import { computed, ref } from 'vue'
- import { onShow } from '@dcloudio/uni-app'
- import { inviteApi, pointApi } from '@/api/index.js'
- import { useUserStore } from '@/store/user.js'
- import { useApp } from '@/utils/useApp.js'
-
- const userStore = useUserStore()
- const { toast } = useApp()
-
- // 图标统一用 uview-plus 的图标字体,不再各自维护 png
- const earnMenuConfig = {
- key: 'earn',
- name: '获得点数',
- hint: '完成任务获得点数',
- icon: 'gift',
- iconColor: '#ff8a00',
- iconBg: '#fff5e8',
- auth: false
- }
-
- const inviteMenuConfig = {
- key: 'invite',
- name: '邀请好友',
- hint: '得100点分享奖励',
- icon: 'man-add',
- iconColor: '#17a65a',
- iconBg: '#eefaf3',
- auth: true
- }
-
- // 福利组:签到、邀请与看广告都受后台开关控制,关掉就整项移除
- const benefitMenus = ref([{ ...inviteMenuConfig }])
-
- const serviceMenus = [
- { key: 'official', name: '关注公众号', icon: 'weixin-fill', iconColor: '#07c160', iconBg: '#eafaf0' },
- { key: 'contact', name: '联系我们', icon: 'kefu-ermai', iconColor: '#1f8cff', iconBg: '#eef7ff' },
- { key: 'feedback', name: '意见反馈', icon: 'edit-pen', iconColor: '#7a5af8', iconBg: '#f3f0ff', auth: true }
- ]
-
- // 服务条款、隐私政策、当前版本、退出登录都收进设置二级页
- const otherMenus = [
- { key: 'setting', name: '设置', icon: 'setting', iconColor: '#64748b', iconBg: '#f1f5fa' }
- ]
-
- const menuGroups = computed(() => {
- const groups = []
- if (benefitMenus.value.length) {
- groups.push({ key: 'benefit', title: '福利', items: benefitMenus.value })
- }
- groups.push({ key: 'service', title: '服务与帮助', items: serviceMenus })
- groups.push({ key: 'other', title: '其他', items: otherMenus })
- return groups
- })
-
- const MENU_ROUTE = {
- earn: '/pages/point/earn',
- invite: '/pages/invite/invite',
- official: '/pages/my/wechat',
- feedback: '/pages/my/feedback',
- contact: '/pages/my/contact',
- setting: '/pages/my/setting'
- }
-
- function requireLogin() {
- if (userStore.isLogin) return true
- uni.navigateTo({ url: '/pages/login/login' })
- return false
- }
-
- function onMenu(item) {
- if (item.auth && !requireLogin()) return
- if (item.soon) return toast(`${item.name} 即将上线`)
- const route = MENU_ROUTE[item.key]
- if (route) return uni.navigateTo({ url: route })
- toast(`${item.name}(待开发)`)
- }
-
- function go(page) {
- if (!requireLogin()) return
- if (page === 'bill') return uni.navigateTo({ url: '/pages/bill/bill?type=recharge' })
- if (page === 'recharge') return uni.navigateTo({ url: '/pages/recharge/recharge' })
- toast(`${page}(待开发)`)
- }
-
- function syncInviteMenu(config = {}) {
- const currentIndex = benefitMenus.value.findIndex((item) => item.key === 'invite')
- if (Number(config.status) !== 1) {
- if (currentIndex > -1) benefitMenus.value.splice(currentIndex, 1)
- return
- }
-
- const points = Number(config.inviter_points || 0)
- const item = {
- ...inviteMenuConfig,
- hint: points > 0 ? `得${points}点分享奖励` : '分享给好友'
- }
- if (currentIndex > -1) {
- benefitMenus.value.splice(currentIndex, 1, item)
- return
- }
-
- // 排在「获得点数」之后,没有该入口时排在最前
- const earnIndex = benefitMenus.value.findIndex((menu) => menu.key === 'earn')
- benefitMenus.value.splice(earnIndex > -1 ? earnIndex + 1 : 0, 0, item)
- }
-
- async function loadProfile() {
- if (!userStore.isLogin) return
- try {
- await Promise.all([userStore.fetchUserInfo(), userStore.fetchBalance()])
- } catch (e) {
- // 登录失效由 request 统一处理
- }
- }
-
- async function loadInviteConfig() {
- try {
- const res = await inviteApi.getConfig()
- const config = (res.data && res.data.config) || {}
- syncInviteMenu(config)
- } catch (e) {}
- }
-
- /** 所有免费任务都关闭时不显示入口,而不是显示后再提示不可用 */
- function syncEarnMenu(enabled, hint) {
- const currentIndex = benefitMenus.value.findIndex((item) => item.key === 'earn')
- if (!enabled) {
- if (currentIndex > -1) benefitMenus.value.splice(currentIndex, 1)
- return
- }
- const item = { ...earnMenuConfig, hint: hint || earnMenuConfig.hint }
- if (currentIndex > -1) {
- benefitMenus.value.splice(currentIndex, 1, item)
- return
- }
- // 获得点数放在福利组最前面
- benefitMenus.value.splice(0, 0, item)
- }
-
- async function loadEarnConfig() {
- try {
- const res = await pointApi.getEarnConfig()
- const data = res.data || {}
- const checkinPoints = Number((data.checkin && data.checkin.points_per_checkin) || 0)
- const adPoints = Number((data.ad && data.ad.points_per_ad) || 0)
- let hint = '完成任务获得点数'
- if (data.checkin && data.checkin.enabled && checkinPoints > 0) hint = `每日签到获得${checkinPoints}点`
- else if (data.ad && data.ad.enabled && adPoints > 0) hint = `看视频获得${adPoints}点`
- syncEarnMenu(Boolean(data.entry_enabled), hint)
- } catch (e) {}
- }
-
- onShow(() => {
- loadInviteConfig()
- loadEarnConfig()
- loadProfile()
- })
- </script>
-
- <style lang="scss" scoped>
- .profile {
- height: 100vh;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- background: #f3f3f3;
- }
-
- .page-scroll {
- flex: 1;
- height: 0;
- }
-
- .profile-content {
- min-height: 100vh;
- padding: 30rpx 30rpx 120rpx;
- box-sizing: border-box;
- background: #f3f3f3;
- }
-
- .balance-card {
- position: relative;
- height: 266rpx;
- border-radius: 24rpx;
- overflow: hidden;
- background: linear-gradient(135deg, #3f94ff 0%, #2384f8 100%);
- box-sizing: border-box;
- }
-
- .guest-card {
- background: linear-gradient(135deg, #4298ff 0%, #1677ef 100%);
- }
-
- .balance-texture {
- position: absolute;
- right: 13rpx;
- top: -44rpx;
- width: 269rpx;
- height: 288rpx;
- }
-
- .balance-copy {
- position: relative;
- z-index: 1;
- height: 100%;
- padding: 44rpx 62rpx;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- }
-
- .guest-copy {
- position: relative;
- z-index: 1;
- height: 100%;
- padding: 36rpx 42rpx 34rpx;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- }
-
- .guest-main {
- display: flex;
- align-items: center;
- min-width: 0;
- }
-
- .guest-avatar {
- width: 84rpx;
- height: 84rpx;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- background: rgba(255, 255, 255, 0.96);
- box-shadow: 0 10rpx 24rpx rgba(16, 87, 174, 0.2);
- }
-
- .guest-text {
- margin-left: 24rpx;
- min-width: 0;
- display: flex;
- flex-direction: column;
- }
-
- .guest-title {
- color: #ffffff;
- font-size: 34rpx;
- font-weight: 700;
- line-height: 1.25;
- }
-
- .guest-description {
- margin-top: 8rpx;
- color: rgba(255, 255, 255, 0.78);
- font-size: 24rpx;
- line-height: 1.4;
- }
-
- .guest-bottom {
- margin-top: auto;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 20rpx;
- }
-
- .guest-tip {
- color: rgba(255, 255, 255, 0.68);
- font-size: 22rpx;
- line-height: 1.35;
- }
-
- .login-btn {
- min-width: 180rpx;
- height: 64rpx;
- padding: 0 24rpx 0 28rpx;
- border-radius: 32rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 6rpx;
- flex-shrink: 0;
- color: #2384f8;
- font-size: 27rpx;
- font-weight: 700;
- background: #ffffff;
- box-shadow: 0 10rpx 24rpx rgba(16, 87, 174, 0.18);
- box-sizing: border-box;
- }
-
- .login-btn--pressed {
- opacity: 0.84;
- transform: scale(0.98);
- }
-
- .hello {
- max-width: 430rpx;
- color: #ffffff;
- font-size: 27rpx;
- line-height: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .balance-label {
- margin-top: auto;
- color: rgba(255, 255, 255, 0.65);
- font-size: 25rpx;
- line-height: 1;
- }
-
- .balance-value {
- color: #ffffff;
- font-size: 40rpx;
- font-weight: 700;
- line-height: 1;
- }
-
- .balance-bottom {
- margin-top: 16rpx;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 24rpx;
- }
-
- .balance-actions {
- display: flex;
- gap: 22rpx;
- flex-shrink: 0;
- }
-
- .outline-btn,
- .solid-btn {
- width: 127rpx;
- height: 58rpx;
- border-radius: 29rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 27rpx;
- box-sizing: border-box;
- }
-
- .outline-btn {
- color: #ffffff;
- border: 1rpx solid rgba(255, 255, 255, 0.75);
- background: rgba(255, 255, 255, 0.08);
- }
-
- .solid-btn {
- color: #ffffff;
- border: 1rpx solid rgba(255, 255, 255, 0.75);
- background: rgba(255, 255, 255, 0.08);
- }
-
- .menu-group {
- margin-top: 26rpx;
- }
-
- .group-title {
- display: block;
- margin: 0 8rpx 12rpx;
- color: #8a95a6;
- font-size: 23rpx;
- font-weight: 800;
- }
-
- .menu-panel {
- border-radius: 21rpx;
- background: #ffffff;
- overflow: hidden;
- box-sizing: border-box;
- }
-
- .menu-row {
- position: relative;
- min-height: 108rpx;
- padding: 20rpx 27rpx;
- display: flex;
- align-items: center;
- box-sizing: border-box;
-
- /* 分隔线只在图标右侧起始,视觉上更整齐 */
- &::after {
- content: '';
- position: absolute;
- left: 105rpx;
- right: 24rpx;
- bottom: 0;
- height: 1rpx;
- background: #f1f5fa;
- }
-
- &:last-child::after {
- display: none;
- }
- }
-
- .menu-icon {
- width: 68rpx;
- height: 68rpx;
- border-radius: 18rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
-
- .menu-name {
- margin-left: 22rpx;
- color: #172033;
- font-size: 29rpx;
- line-height: 1.3;
- flex: 1;
- min-width: 0;
- }
-
- .menu-hint {
- margin-left: 20rpx;
- max-width: 280rpx;
- color: #8a95a6;
- font-size: 24rpx;
- line-height: 1.3;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- </style>
|