|
@@ -85,7 +85,7 @@
|
|
|
<view class="recent-card" v-if="recentCreations.length > 0">
|
|
<view class="recent-card" v-if="recentCreations.length > 0">
|
|
|
<view class="recent-header">
|
|
<view class="recent-header">
|
|
|
<text class="recent-title">最近的创作</text>
|
|
<text class="recent-title">最近的创作</text>
|
|
|
- <text class="recent-more" @click="goHistory">查看更多 ›</text>
|
|
|
|
|
|
|
+ <text class="recent-more" @click="goHistoryPage">查看更多 ›</text>
|
|
|
</view>
|
|
</view>
|
|
|
<view class="recent-list">
|
|
<view class="recent-list">
|
|
|
<view
|
|
<view
|
|
@@ -109,7 +109,7 @@
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 升级会员推广卡片 -->
|
|
<!-- 升级会员推广卡片 -->
|
|
|
- <view class="upgrade-card" v-if="userStore.isLoggedIn && !userStore.isMember" @click="goMember">
|
|
|
|
|
|
|
+ <view class="upgrade-card" v-if="userStore.isLoggedIn && !userStore.isMember" @click="goMemberPage">
|
|
|
<view class="upgrade-left">
|
|
<view class="upgrade-left">
|
|
|
<text class="upgrade-icon">👑</text>
|
|
<text class="upgrade-icon">👑</text>
|
|
|
<view class="upgrade-text">
|
|
<view class="upgrade-text">
|
|
@@ -120,72 +120,20 @@
|
|
|
<text class="upgrade-btn">立即开通</text>
|
|
<text class="upgrade-btn">立即开通</text>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 功能入口 -->
|
|
|
|
|
|
|
+ <!-- 功能入口(动态渲染,后端 /api/navigation/config 返回) -->
|
|
|
<view class="menu-section">
|
|
<view class="menu-section">
|
|
|
- <view class="menu-item" @click="goBookGenerator">
|
|
|
|
|
- <text class="menu-icon">📖</text>
|
|
|
|
|
- <text class="menu-text">书籍生成</text>
|
|
|
|
|
- <text class="menu-arrow">›</text>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="menu-item" @click="goMember">
|
|
|
|
|
- <text class="menu-icon">👑</text>
|
|
|
|
|
- <text class="menu-text">会员中心</text>
|
|
|
|
|
- <text class="menu-arrow">›</text>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="menu-item" @click="goSettings">
|
|
|
|
|
- <text class="menu-icon">⚙️</text>
|
|
|
|
|
- <text class="menu-text">设置</text>
|
|
|
|
|
- <text class="menu-arrow">›</text>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="menu-item" @click="goTokenPacks">
|
|
|
|
|
- <text class="menu-icon">🎁</text>
|
|
|
|
|
- <text class="menu-text">购买积分包</text>
|
|
|
|
|
- <text class="menu-arrow">›</text>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="menu-item" @click="goInvite">
|
|
|
|
|
- <text class="menu-icon">🎁</text>
|
|
|
|
|
- <text class="menu-text">邀请好友 · 双方得10000积分</text>
|
|
|
|
|
- <text class="menu-arrow">›</text>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="menu-item" @click="goOrders">
|
|
|
|
|
- <text class="menu-icon">📋</text>
|
|
|
|
|
- <text class="menu-text">订单记录</text>
|
|
|
|
|
- <text class="menu-arrow">›</text>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="menu-item" @click="goBilling">
|
|
|
|
|
- <text class="menu-icon">💰</text>
|
|
|
|
|
- <text class="menu-text">消费账单</text>
|
|
|
|
|
- <text class="menu-arrow">›</text>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="menu-item" @click="goHistory">
|
|
|
|
|
- <text class="menu-icon">📜</text>
|
|
|
|
|
- <text class="menu-text">历史记录</text>
|
|
|
|
|
- <text class="menu-arrow">›</text>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="menu-item" @click="showAbout">
|
|
|
|
|
- <text class="menu-icon">ℹ️</text>
|
|
|
|
|
- <text class="menu-text">关于我们</text>
|
|
|
|
|
- <text class="menu-arrow">›</text>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="menu-item" @click="showFeedback">
|
|
|
|
|
- <text class="menu-icon">💬</text>
|
|
|
|
|
- <text class="menu-text">意见反馈</text>
|
|
|
|
|
- <text class="menu-arrow">›</text>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="menu-item" @click="goTextToVideo">
|
|
|
|
|
- <text class="menu-icon">🎬</text>
|
|
|
|
|
- <text class="menu-text">文本生成视频</text>
|
|
|
|
|
- <text class="menu-tag">新功能</text>
|
|
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="item in menuList"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ class="menu-item"
|
|
|
|
|
+ hover-class="menu-item-hover"
|
|
|
|
|
+ :data-menu-id="item.id"
|
|
|
|
|
+ @click="handleMenuTap(item)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="menu-icon">{{ item.icon }}</text>
|
|
|
|
|
+ <text class="menu-text">{{ item.name }}</text>
|
|
|
|
|
+ <text v-if="item.tag" class="menu-tag">{{ item.tag }}</text>
|
|
|
|
|
+ <view v-if="getBadgeText(item)" class="menu-badge">{{ getBadgeText(item) }}</view>
|
|
|
<text class="menu-arrow">›</text>
|
|
<text class="menu-arrow">›</text>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
@@ -194,6 +142,9 @@
|
|
|
<view class="logout-section" v-if="userStore.isLoggedIn">
|
|
<view class="logout-section" v-if="userStore.isLoggedIn">
|
|
|
<button class="logout-btn" @click="handleLogout">退出登录</button>
|
|
<button class="logout-btn" @click="handleLogout">退出登录</button>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 全局底部 Tab Bar -->
|
|
|
|
|
+ <CustomTabBar />
|
|
|
</view>
|
|
</view>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
@@ -201,13 +152,46 @@
|
|
|
import { computed, ref, onMounted } from 'vue';
|
|
import { computed, ref, onMounted } from 'vue';
|
|
|
import { onShow } from '@dcloudio/uni-app';
|
|
import { onShow } from '@dcloudio/uni-app';
|
|
|
import { useUserStore } from '../../store/user';
|
|
import { useUserStore } from '../../store/user';
|
|
|
|
|
+import { useNavigationStore } from '../../store/navigation';
|
|
|
import { get } from '../../utils/request';
|
|
import { get } from '../../utils/request';
|
|
|
import { formatNumber } from '../../utils/format';
|
|
import { formatNumber } from '../../utils/format';
|
|
|
import type { AudioItem } from '../../types';
|
|
import type { AudioItem } from '../../types';
|
|
|
|
|
+import type { UserCenterMenuItem } from '../../types/navigation';
|
|
|
import { getCoverGradient, getTitleLetter } from '../../composables/useCoverStyle';
|
|
import { getCoverGradient, getTitleLetter } from '../../composables/useCoverStyle';
|
|
|
import { getTodayStartISO } from '../../utils/time';
|
|
import { getTodayStartISO } from '../../utils/time';
|
|
|
|
|
+import { dispatchMenuAction, type MenuActionContext } from '../../utils/menu-actions';
|
|
|
|
|
+import CustomTabBar from '../../components/CustomTabBar.vue';
|
|
|
|
|
|
|
|
const userStore = useUserStore();
|
|
const userStore = useUserStore();
|
|
|
|
|
+const navStore = useNavigationStore();
|
|
|
|
|
+
|
|
|
|
|
+// 动态菜单列表(从 navigation store 取)
|
|
|
|
|
+const menuList = computed<UserCenterMenuItem[]>(() => navStore.userCenterMenu);
|
|
|
|
|
+
|
|
|
|
|
+// 菜单点击分发器上下文
|
|
|
|
|
+const menuActionCtx = computed<MenuActionContext>(() => ({
|
|
|
|
|
+ navStore: {
|
|
|
|
|
+ switchTabByPath: navStore.switchTabByPath,
|
|
|
|
|
+ },
|
|
|
|
|
+ isLoggedIn: userStore.isLoggedIn,
|
|
|
|
|
+ onLogout: handleLogout,
|
|
|
|
|
+ requireLoginThen: (_item, next) => {
|
|
|
|
|
+ uni.navigateTo({ url: '/pages/login/index' });
|
|
|
|
|
+ // 登录后由 login 页面回跳触发后续(简化:登录后用户手动返回)
|
|
|
|
|
+ // 若需要自动继续,可将 next 存到全局,由登录成功回调消费
|
|
|
|
|
+ },
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+async function handleMenuTap(item: UserCenterMenuItem) {
|
|
|
|
|
+ if (!item) return;
|
|
|
|
|
+ await dispatchMenuAction(item, menuActionCtx.value);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getBadgeText(item: UserCenterMenuItem): string {
|
|
|
|
|
+ if (item.badge === undefined || item.badge === null) return '';
|
|
|
|
|
+ if (item.badge === '' || item.badge === 0) return '';
|
|
|
|
|
+ return String(item.badge);
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
// 积分余额
|
|
// 积分余额
|
|
|
const tokenBalance = ref<any>(null);
|
|
const tokenBalance = ref<any>(null);
|
|
@@ -340,74 +324,6 @@ onShow(() => {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-// 跳转会员页
|
|
|
|
|
-function goMember() {
|
|
|
|
|
- uni.navigateTo({ url: '/pages/member/index' });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 跳转设置页
|
|
|
|
|
-function goSettings() {
|
|
|
|
|
- uni.navigateTo({ url: '/pages/settings/index' });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 跳转日志监控页
|
|
|
|
|
-function goLogs() {
|
|
|
|
|
- uni.navigateTo({ url: '/pages/logs/index' });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 跳转 Token 包购买
|
|
|
|
|
-function goTokenPacks() {
|
|
|
|
|
- uni.navigateTo({ url: '/pages/token-packs/index' });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 跳转邀请页
|
|
|
|
|
-function goInvite() {
|
|
|
|
|
- uni.navigateTo({ url: '/pages/invite/index' });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 跳转订单记录
|
|
|
|
|
-function goOrders() {
|
|
|
|
|
- uni.navigateTo({ url: '/pages/orders/index' });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 跳转消费账单
|
|
|
|
|
-function goBilling() {
|
|
|
|
|
- uni.navigateTo({ url: '/pages/billing/index' });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 跳转历史页
|
|
|
|
|
-function goHistory() {
|
|
|
|
|
- uni.switchTab({ url: '/pages/history/index' });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 跳转文本生成视频页
|
|
|
|
|
-function goTextToVideo() {
|
|
|
|
|
- uni.navigateTo({ url: '/pages/text-to-video/index' });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 跳转书籍生成页
|
|
|
|
|
-function goBookGenerator() {
|
|
|
|
|
- uni.switchTab({ url: '/pages/book-generator/index' });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 关于我们
|
|
|
|
|
-function showAbout() {
|
|
|
|
|
- uni.showModal({
|
|
|
|
|
- title: '关于声工坊',
|
|
|
|
|
- content: '声工坊是一款智能文本转语音工具,帮助您轻松制作高质量音频内容。\n\n版本: 1.0.0',
|
|
|
|
|
- showCancel: false,
|
|
|
|
|
- });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 意见反馈
|
|
|
|
|
-function showFeedback() {
|
|
|
|
|
- uni.showModal({
|
|
|
|
|
- title: '意见反馈',
|
|
|
|
|
- content: '如有问题或建议,请联系客服:\n邮箱:support@example.com',
|
|
|
|
|
- showCancel: false,
|
|
|
|
|
- });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// 退出登录
|
|
// 退出登录
|
|
|
function handleLogout() {
|
|
function handleLogout() {
|
|
|
uni.showModal({
|
|
uni.showModal({
|
|
@@ -423,6 +339,16 @@ function handleLogout() {
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// 升级卡片按钮:跳会员中心
|
|
|
|
|
+function goMemberPage() {
|
|
|
|
|
+ uni.navigateTo({ url: '/pages/member/index' });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 「查看更多」按钮:跳历史记录页
|
|
|
|
|
+function goHistoryPage() {
|
|
|
|
|
+ uni.navigateTo({ url: '/pages/history/index' });
|
|
|
|
|
+}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
@@ -893,6 +819,7 @@ function handleLogout() {
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
padding: 28rpx 32rpx;
|
|
padding: 28rpx 32rpx;
|
|
|
transition: background 0.15s;
|
|
transition: background 0.15s;
|
|
|
|
|
+ position: relative;
|
|
|
}
|
|
}
|
|
|
.menu-item:active {
|
|
.menu-item:active {
|
|
|
background: var(--color-bg-surface, #f9fafb);
|
|
background: var(--color-bg-surface, #f9fafb);
|
|
@@ -922,6 +849,25 @@ function handleLogout() {
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.menu-badge {
|
|
|
|
|
+ min-width: 32rpx;
|
|
|
|
|
+ height: 32rpx;
|
|
|
|
|
+ padding: 0 10rpx;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ background: #ef4444;
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ font-size: 20rpx;
|
|
|
|
|
+ line-height: 32rpx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ margin-right: 12rpx;
|
|
|
|
|
+ box-shadow: 0 2rpx 6rpx rgba(239, 68, 68, 0.3);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.menu-item-hover {
|
|
|
|
|
+ background: var(--color-bg-surface, #f9fafb);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.menu-arrow {
|
|
.menu-arrow {
|
|
|
font-size: 28rpx;
|
|
font-size: 28rpx;
|
|
|
color: var(--color-text-placeholder, #d1d5db);
|
|
color: var(--color-text-placeholder, #d1d5db);
|