feat: tenant-scoped routing and portal navigation

This commit is contained in:
2026-01-08 21:30:46 +08:00
parent f3aa92078a
commit 3e095c57f3
52 changed files with 1111 additions and 670 deletions

View File

@@ -52,7 +52,7 @@
<!-- Content Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<div v-for="item in contents" :key="item.id" class="bg-white rounded-xl border border-slate-100 overflow-hidden hover:shadow-lg transition-all group cursor-pointer active:scale-[0.99]" @click="$router.push(`/contents/${item.id}`)">
<div v-for="item in contents" :key="item.id" class="bg-white rounded-xl border border-slate-100 overflow-hidden hover:shadow-lg transition-all group cursor-pointer active:scale-[0.99]" @click="$router.push(tenantRoute(`/contents/${item.id}`))">
<!-- Cover -->
<div class="aspect-video bg-slate-100 relative">
<img :src="item.cover || `https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=60`" class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105">
@@ -92,8 +92,12 @@
<script setup>
import { ref, watch, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { contentApi } from '../api/content';
import { tenantPath } from '../utils/tenant';
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const selectedGenre = ref('全部');
const selectedPrice = ref('all');
const sort = ref('latest');
@@ -146,4 +150,4 @@ const filters = {
{ label: '会员专享', value: 'member' }
]
};
</script>
</script>

View File

@@ -1,8 +1,12 @@
<script setup>
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { contentApi } from '../../api/content';
import { tenantApi } from '../../api/tenant';
import { tenantPath } from '../../utils/tenant';
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const contents = ref([]);
const bannerItems = ref([]);
const trendingItems = ref([]);
@@ -97,7 +101,7 @@ onMounted(fetchData);
<div class="absolute bottom-0 left-0 p-10 max-w-2xl text-white">
<div class="inline-block px-3 py-1 bg-red-600 text-white text-xs font-bold rounded mb-3">置顶推荐</div>
<h2 class="text-4xl font-bold mb-4 leading-tight cursor-pointer hover:underline"
@click="$router.push(`/contents/${item.id}`)">{{ item.title }}</h2>
@click="$router.push(tenantRoute(`/contents/${item.id}`))">{{ item.title }}</h2>
<p class="text-lg text-slate-200 line-clamp-2">{{ item.description || item.title }}</p>
</div>
</div>
@@ -167,7 +171,7 @@ onMounted(fetchData);
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div v-for="creator in matchedCreators" :key="creator.id"
class="flex items-center gap-3 p-3 rounded-xl hover:bg-slate-50 transition-colors cursor-pointer border border-transparent hover:border-slate-200"
@click="$router.push(`/creators/${creator.id}`)">
@click="$router.push(tenantRoute(`/t/${creator.id}`))">
<img :src="creator.avatar || `https://api.dicebear.com/7.x/avataaars/svg?seed=${creator.id}`"
class="w-12 h-12 rounded-full border border-slate-100">
<div class="flex-1 min-w-0">
@@ -178,7 +182,7 @@ onMounted(fetchData);
</div>
</div>
<router-link v-for="item in contents" :key="item.id" :to="`/contents/${item.id}`"
<router-link v-for="item in contents" :key="item.id" :to="tenantRoute(`/contents/${item.id}`)"
class="block bg-white rounded-2xl shadow-sm border border-slate-100 p-6 hover:shadow-xl hover:border-primary-100 transition-all duration-300 group cursor-pointer active:scale-[0.99]">
<div class="flex gap-8">
<div class="flex-1 min-w-0 flex flex-col">
@@ -254,10 +258,10 @@ onMounted(fetchData);
<div class="space-y-4">
<div v-for="creator in recommendedCreators" :key="creator.id" class="flex items-center gap-3">
<img :src="creator.avatar || `https://api.dicebear.com/7.x/avataaars/svg?seed=${creator.id}`"
class="w-10 h-10 rounded-full cursor-pointer" @click="$router.push(`/creators/${creator.id}`)">
class="w-10 h-10 rounded-full cursor-pointer" @click="$router.push(tenantRoute(`/t/${creator.id}`))">
<div class="flex-1 min-w-0">
<div class="font-bold text-slate-900 text-sm truncate hover:text-primary-600 cursor-pointer"
@click="$router.push(`/creators/${creator.id}`)">{{ creator.name }}</div>
@click="$router.push(tenantRoute(`/t/${creator.id}`))">{{ creator.name }}</div>
<div class="text-xs text-slate-500 truncate">粉丝 {{ creator.stats?.followers || 0 }}</div>
</div>
<button
@@ -278,7 +282,7 @@ onMounted(fetchData);
:class="index === 0 ? 'text-red-500' : (index === 1 ? 'text-orange-500' : 'text-yellow-500')">{{
index + 1 }}</span>
<div class="flex-1">
<h4 @click="$router.push(`/contents/${item.id}`)"
<h4 @click="$router.push(tenantRoute(`/contents/${item.id}`))"
class="text-sm font-medium text-slate-800 line-clamp-2 hover:text-primary-600 cursor-pointer">
{{ item.title }}</h4>
<span class="text-xs text-slate-400 mt-1 block">{{ item.views }} 阅读</span>
@@ -301,4 +305,3 @@ onMounted(fetchData);
</div>
</div>
</div>

View File

@@ -6,7 +6,7 @@
</div>
<!-- 1. Hero Topic -->
<div class="relative w-full h-[400px] rounded-2xl overflow-hidden mb-12 group cursor-pointer shadow-lg" @click="$router.push(`/explore?topic=${topics[0].id}`)">
<div class="relative w-full h-[400px] rounded-2xl overflow-hidden mb-12 group cursor-pointer shadow-lg" @click="$router.push(tenantRoute(`/explore?topic=${topics[0].id}`))">
<img :src="topics[0].cover" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105">
<div class="absolute inset-0 bg-gradient-to-t from-black/90 via-black/20 to-transparent"></div>
<div class="absolute bottom-0 left-0 p-10 max-w-3xl text-white">
@@ -30,7 +30,7 @@
<div v-for="(topic, idx) in topics.slice(1)" :key="topic.id"
class="group bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden hover:shadow-xl transition-all cursor-pointer flex flex-col"
:class="{ 'lg:col-span-2 flex-row': idx === 0 }"
@click="$router.push(`/explore?topic=${topic.id}`)"
@click="$router.push(tenantRoute(`/explore?topic=${topic.id}`))"
>
<!-- Cover -->
<div class="relative overflow-hidden" :class="idx === 0 ? 'w-1/2' : 'h-48'">
@@ -72,7 +72,11 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { tenantPath } from '../utils/tenant';
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const topics = ref([
{
id: 1,
@@ -120,4 +124,4 @@ const topics = ref([
count: 15
}
]);
</script>
</script>

View File

@@ -1,11 +1,13 @@
<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useRouter, useRoute } from 'vue-router';
import { useToast } from 'primevue/usetoast';
import Toast from 'primevue/toast';
import { authApi } from '../../api/auth';
import { tenantPath } from '../../utils/tenant';
const router = useRouter();
const route = useRoute();
const toast = useToast();
const step = ref(1);
const phone = ref('');
@@ -30,7 +32,7 @@ const login = async () => {
localStorage.setItem('user', JSON.stringify(res.user));
toast.add({ severity: 'success', summary: '登录成功', detail: '欢迎回来', life: 1000 });
setTimeout(() => {
router.push('/');
router.push(tenantPath('/', route));
}, 1000);
} catch (e) {
toast.add({ severity: 'error', summary: '登录失败', detail: e.message, life: 3000 });
@@ -139,4 +141,4 @@ const login = async () => {
</div>
<Toast />
</div>
</template>
</template>

View File

@@ -101,8 +101,8 @@
<p class="text-lg text-slate-600 mb-12 max-w-lg mx-auto">您的入驻申请已成功提交平台将在 1-3 个工作日内完成审核审核结果将通过短信和系统通知发送给您</p>
<div class="flex justify-center gap-4">
<router-link to="/" class="px-8 py-3 bg-white border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 font-medium">返回首页</router-link>
<router-link to="/me" class="px-8 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 font-medium">查看个人中心</router-link>
<router-link :to="tenantRoute('/')" class="px-8 py-3 bg-white border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 font-medium">返回首页</router-link>
<router-link :to="tenantRoute('/me')" class="px-8 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 font-medium">查看个人中心</router-link>
</div>
</div>
@@ -111,7 +111,11 @@
<script setup>
import { ref, reactive, computed } from 'vue';
import { useRoute } from 'vue-router';
import { tenantPath } from '../../utils/tenant';
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const step = ref(1);
const submitting = ref(false);
const fileInput = ref(null);

View File

@@ -228,9 +228,11 @@ import { computed, reactive, ref, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { commonApi } from '../../api/common';
import { creatorApi } from '../../api/creator';
import { tenantPath } from '../../utils/tenant';
const router = useRouter();
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const toast = useToast();
const fileInput = ref(null);
const currentUploadType = ref('');
@@ -499,7 +501,7 @@ const saveContent = async (targetStatus) => {
toast.add({ severity: 'success', summary: targetStatus === 'draft' ? '保存成功' : '发布成功', detail: targetStatus === 'draft' ? '已保存为草稿' : '内容已提交审核', life: 3000 });
}
setTimeout(() => router.push('/creator/contents'), 1500);
setTimeout(() => router.push(tenantRoute('/creator/contents')), 1500);
} catch (e) {
toast.add({ severity: 'error', summary: '操作失败', detail: e.message, life: 3000 });
} finally {

View File

@@ -2,7 +2,7 @@
<div>
<div class="flex items-center justify-between mb-8">
<h1 class="text-2xl font-bold text-slate-900">内容管理</h1>
<router-link to="/creator/contents/new"
<router-link :to="tenantRoute('/creator/contents/new')"
class="px-6 py-2.5 bg-primary-600 text-white rounded-lg font-bold hover:bg-primary-700 transition-colors shadow-sm shadow-primary-200 cursor-pointer active:scale-95 flex items-center gap-2">
<i class="pi pi-plus"></i> 发布新内容
</router-link>
@@ -83,7 +83,7 @@
</div>
<h3 class="text-slate-900 font-bold mb-1">暂无内容</h3>
<p class="text-slate-500 text-sm mb-6">您还没有发布任何内容快去创作吧</p>
<router-link to="/creator/contents/new" class="px-5 py-2 bg-primary-600 text-white rounded-lg text-sm font-bold hover:bg-primary-700 transition-colors">
<router-link :to="tenantRoute('/creator/contents/new')" class="px-5 py-2 bg-primary-600 text-white rounded-lg text-sm font-bold hover:bg-primary-700 transition-colors">
立即发布
</router-link>
</div>
@@ -100,7 +100,7 @@
class="w-full h-full object-cover">
<div
class="absolute inset-0 bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<router-link :to="`/creator/contents/${item.id}`"
<router-link :to="tenantRoute(`/creator/contents/${item.id}`)"
class="text-white text-xs font-bold border border-white px-3 py-1 rounded hover:bg-white hover:text-black transition-colors">编辑</router-link>
</div>
</div>
@@ -119,7 +119,7 @@
class="bg-blue-50 text-blue-600 text-[11px] px-2 py-0.5 rounded-full font-bold whitespace-nowrap">{{
item.key }}</span>
<h3 class="font-bold text-slate-900 text-lg truncate hover:text-primary-600 cursor-pointer transition-colors"
@click="$router.push(`/creator/contents/${item.id}`)">
@click="$router.push(tenantRoute(`/creator/contents/${item.id}`))">
{{ item.title }}</h3>
</div>
<!-- Status Badge -->
@@ -170,7 +170,7 @@
<!-- Actions -->
<div class="flex items-center gap-4 pt-3 border-t border-slate-50 mt-3">
<button class="text-sm text-slate-500 hover:text-primary-600 font-medium cursor-pointer flex items-center gap-1"
@click="$router.push(`/creator/contents/${item.id}`)">
@click="$router.push(tenantRoute(`/creator/contents/${item.id}`))">
<i class="pi pi-file-edit"></i> 编辑
</button>
<button v-if="item.status === 'published'"
@@ -211,13 +211,17 @@
<script setup>
import { onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useRoute, useRouter } from 'vue-router';
import { useToast } from 'primevue/usetoast';
import { useConfirm } from 'primevue/useconfirm';
import ConfirmDialog from 'primevue/confirmdialog';
import Paginator from 'primevue/paginator';
import { commonApi } from '../../api/common';
import { creatorApi } from '../../api/creator';
import { tenantPath } from '../../utils/tenant';
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const router = useRouter();
const toast = useToast();
@@ -407,4 +411,4 @@ const handleDelete = (id) => {
}
});
};
</script>
</script>

View File

@@ -3,7 +3,7 @@
<div class="flex items-center justify-between mb-8">
<h1 class="text-2xl font-bold text-slate-900">管理概览</h1>
<div class="flex gap-4">
<router-link to="/creator/contents/new"
<router-link :to="tenantRoute('/creator/contents/new')"
class="px-6 py-2.5 bg-primary-600 text-white rounded-lg font-bold hover:bg-primary-700 transition-colors shadow-sm shadow-primary-200 cursor-pointer active:scale-95 flex items-center gap-2">
<i class="pi pi-plus"></i> 发布新内容
</router-link>
@@ -39,7 +39,7 @@
<div class="flex gap-4">
<div
class="flex-1 p-4 bg-orange-50 border border-orange-100 rounded-xl flex items-center justify-between cursor-pointer hover:bg-orange-100 transition-colors"
@click="$router.push('/creator/orders')">
@click="$router.push(tenantRoute('/creator/orders'))">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-orange-200 text-orange-700 flex items-center justify-center"><i
class="pi pi-refresh"></i></div>
@@ -83,7 +83,7 @@
<div class="text-xs text-slate-500" v-if="hasPayoutAccount">已绑定{{ payoutAccounts[0].name }} ({{ payoutAccounts[0].account.slice(-4) }})</div>
<div class="text-xs text-orange-600 font-bold flex items-center gap-1" v-else>
<i class="pi pi-exclamation-circle"></i> 未配置收款账户
<router-link to="/creator/settings"
<router-link :to="tenantRoute('/creator/settings')"
class="underline hover:text-orange-800 ml-1">去配置</router-link>
</div>
</div>
@@ -134,8 +134,12 @@ import Dialog from 'primevue/dialog';
import Toast from 'primevue/toast';
import { useToast } from 'primevue/usetoast';
import { ref, onMounted, computed } from 'vue';
import { useRoute } from 'vue-router';
import { creatorApi } from '../../api/creator';
import { tenantPath } from '../../utils/tenant';
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const toast = useToast();
const showWithdraw = ref(false);
const withdrawMethod = ref('wallet');
@@ -207,4 +211,4 @@ const handleWithdraw = async () => {
toast.add({ severity: 'error', summary: '提现失败', detail: '请稍后重试', life: 3000 });
}
};
</script>
</script>

View File

@@ -8,9 +8,17 @@
<h1 class="text-4xl font-bold text-slate-900 mb-4">404</h1>
<p class="text-xl text-slate-600 mb-8">抱歉您访问的页面走丢了</p>
<div class="flex justify-center gap-4">
<router-link to="/" class="px-6 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">返回首页</router-link>
<router-link :to="tenantRoute('/')" class="px-6 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">返回首页</router-link>
<button @click="$router.back()" class="px-6 py-3 border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 transition-colors">返回上一页</button>
</div>
</div>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router';
import { tenantPath } from '../../utils/tenant';
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
</script>

View File

@@ -104,9 +104,11 @@
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { orderApi } from '../../api/order';
import { tenantPath } from '../../utils/tenant';
const route = useRoute();
const router = useRouter();
const tenantRoute = (path) => tenantPath(path, route);
const orderId = route.params.id || '82934712';
const amount = '9.90'; // Should fetch order details first
@@ -135,7 +137,7 @@ const simulateSuccess = () => {
isScanning.value = false;
isSuccess.value = true;
setTimeout(() => {
router.replace(`/me/orders/${orderId}`);
router.replace(tenantRoute(`/me/orders/${orderId}`));
}, 1500);
}, 1000);
};
@@ -153,7 +155,7 @@ onMounted(() => {
isScanning.value = false;
isSuccess.value = true;
clearInterval(pollTimer);
setTimeout(() => router.replace(`/me/orders/${orderId}`), 1500);
setTimeout(() => router.replace(tenantRoute(`/me/orders/${orderId}`)), 1500);
}
} catch (e) {
console.error('Poll status failed', e);
@@ -165,4 +167,4 @@ onUnmounted(() => {
if (timer) clearInterval(timer);
if (pollTimer) clearInterval(pollTimer);
});
</script>
</script>

View File

@@ -91,7 +91,7 @@
<!-- Featured (Pinned) -->
<div class="relative h-[400px] rounded-2xl overflow-hidden group cursor-pointer"
v-if="featuredContent"
@click="$router.push(`/contents/${featuredContent.id}`)">
@click="$router.push(tenantRoute(`/contents/${featuredContent.id}`))">
<img :src="featuredContent.cover" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105">
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent"></div>
<div class="absolute top-4 left-4 px-2 py-1 bg-red-600 text-white text-xs font-bold rounded">置顶</div>
@@ -115,7 +115,7 @@
<div v-for="item in contents" :key="item.id"
v-show="!featuredContent || item.id !== featuredContent.id"
class="bg-white rounded-xl border border-slate-100 p-5 flex gap-6 hover:shadow-md transition-shadow group cursor-pointer"
@click="$router.push(`/contents/${item.id}`)">
@click="$router.push(tenantRoute(`/contents/${item.id}`))">
<div class="w-64 h-36 bg-slate-100 rounded-lg flex-shrink-0 overflow-hidden relative">
<img :src="item.cover || 'https://via.placeholder.com/300x168?text=No+Cover'" class="w-full h-full object-cover">
</div>
@@ -172,8 +172,10 @@ import { reactive, ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { tenantApi } from '../../api/tenant';
import { contentApi } from '../../api/content';
import { tenantPath } from '../../utils/tenant';
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const toast = useToast();
const currentTab = ref('home');
const isFollowing = ref(false);
@@ -280,4 +282,4 @@ const tabs = [
{ label: '主页', value: 'home' },
{ label: '关于', value: 'about' }
];
</script>
</script>

View File

@@ -2,7 +2,7 @@
<div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Stat Cards -->
<router-link to="/me/wallet"
<router-link :to="tenantRoute('/me/wallet')"
class="bg-white p-6 rounded-xl shadow-sm border border-slate-100 flex items-center gap-4 hover:shadow-md hover:border-primary-100 transition-all cursor-pointer">
<div class="w-12 h-12 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center text-xl"><i
class="pi pi-wallet"></i></div>
@@ -42,13 +42,13 @@
<div class="mt-8 bg-white rounded-xl shadow-sm border border-slate-100 p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-bold text-slate-900">最近订单</h2>
<router-link to="/me/orders"
<router-link :to="tenantRoute('/me/orders')"
class="text-sm text-primary-600 hover:text-primary-700 font-medium px-2 py-1 rounded hover:bg-primary-50 transition-colors">查看全部
<i class="pi pi-angle-right"></i></router-link>
</div>
<div class="space-y-4">
<div v-for="order in recentOrders" :key="order.id" @click="$router.push(`/me/orders/${order.id}`)"
<div v-for="order in recentOrders" :key="order.id" @click="$router.push(tenantRoute(`/me/orders/${order.id}`))"
class="flex items-center gap-4 p-4 border border-slate-100 rounded-lg hover:border-primary-100 hover:shadow-sm transition-all cursor-pointer active:scale-[0.99] group">
<div class="w-16 h-16 bg-slate-100 rounded flex-shrink-0 flex items-center justify-center relative overflow-hidden">
<template v-if="order.type === 'recharge' || !order.items?.length">
@@ -89,11 +89,15 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { userApi } from '../../api/user';
import { tenantPath } from '../../utils/tenant';
const wallet = ref({ balance: 0, points: 0 });
const couponCount = ref(0);
const recentOrders = ref([]);
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const statusColor = (status) => {
const map = {

View File

@@ -9,7 +9,7 @@
<!-- Content Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div v-for="item in items" :key="item.id" class="group relative bg-white border border-slate-200 rounded-xl overflow-hidden hover:shadow-md transition-all hover:border-primary-200 cursor-pointer" @click="$router.push(`/contents/${item.id}`)">
<div v-for="item in items" :key="item.id" class="group relative bg-white border border-slate-200 rounded-xl overflow-hidden hover:shadow-md transition-all hover:border-primary-200 cursor-pointer" @click="$router.push(tenantRoute(`/contents/${item.id}`))">
<!-- Cover -->
<div class="aspect-video bg-slate-100 relative overflow-hidden">
@@ -43,7 +43,7 @@
<i class="pi pi-star text-2xl text-slate-300"></i>
</div>
<p class="text-slate-500 text-lg">暂无收藏内容</p>
<router-link to="/" class="mt-4 inline-block text-primary-600 font-medium hover:underline">去发现好内容</router-link>
<router-link :to="tenantRoute('/')" class="mt-4 inline-block text-primary-600 font-medium hover:underline">去发现好内容</router-link>
</div>
<Toast />
@@ -52,15 +52,17 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useRoute } from 'vue-router';
import Toast from 'primevue/toast';
import { useToast } from 'primevue/usetoast';
import { userApi } from '../../api/user';
import { tenantPath } from '../../utils/tenant';
const router = useRouter();
const toast = useToast();
const items = ref([]);
const loading = ref(true);
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const fetchFavorites = async () => {
try {

View File

@@ -1,7 +1,11 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { userApi } from '../../api/user';
import { tenantPath } from '../../utils/tenant';
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const libraryItems = ref([]);
const loading = ref(true);
@@ -53,7 +57,7 @@ const getStatusLabel = (item) => {
<div
v-for="item in libraryItems"
:key="item.id"
@click="item.status === 'published' ? $router.push(`/contents/${item.id}`) : null"
@click="item.status === 'published' ? $router.push(tenantRoute(`/contents/${item.id}`)) : null"
class="group relative bg-white border border-slate-200 rounded-xl overflow-hidden hover:shadow-md transition-all hover:border-primary-200 flex flex-col sm:flex-row"
:class="item.status === 'published' ? 'cursor-pointer active:scale-[0.99]' : 'opacity-75 cursor-not-allowed'"
>
@@ -98,8 +102,8 @@ const getStatusLabel = (item) => {
<div v-if="!loading && libraryItems.length === 0" class="flex flex-col items-center justify-center py-20">
<div class="w-20 h-20 bg-slate-50 rounded-full flex items-center justify-center mb-4"><i class="pi pi-book text-3xl text-slate-300"></i></div>
<p class="text-slate-500">暂无已购内容</p>
<router-link to="/" class="mt-4 text-primary-600 font-medium hover:underline">去发现好内容</router-link>
<router-link :to="tenantRoute('/')" class="mt-4 text-primary-600 font-medium hover:underline">去发现好内容</router-link>
</div>
</div>
</div>
</template>
</template>

View File

@@ -6,7 +6,7 @@
<!-- Content Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div v-for="item in items" :key="item.id" class="group relative bg-white border border-slate-200 rounded-xl overflow-hidden hover:shadow-md transition-all hover:border-primary-200 cursor-pointer" @click="$router.push(`/contents/${item.id}`)">
<div v-for="item in items" :key="item.id" class="group relative bg-white border border-slate-200 rounded-xl overflow-hidden hover:shadow-md transition-all hover:border-primary-200 cursor-pointer" @click="$router.push(tenantRoute(`/contents/${item.id}`))">
<!-- Cover -->
<div class="aspect-video bg-slate-100 relative overflow-hidden">
@@ -38,7 +38,7 @@
<i class="pi pi-thumbs-up text-2xl text-slate-300"></i>
</div>
<p class="text-slate-500 text-lg">暂无点赞内容</p>
<router-link to="/" class="mt-4 inline-block text-primary-600 font-medium hover:underline">去发现好内容</router-link>
<router-link :to="tenantRoute('/')" class="mt-4 inline-block text-primary-600 font-medium hover:underline">去发现好内容</router-link>
</div>
<Toast />
@@ -47,10 +47,14 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import Toast from 'primevue/toast';
import { useToast } from 'primevue/usetoast';
import { tenantPath } from '../../utils/tenant';
const toast = useToast();
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const items = ref([
{

View File

@@ -43,7 +43,7 @@
<!-- Order Body -->
<div class="p-4 flex flex-col sm:flex-row gap-6">
<!-- Product Info (Clickable Area) -->
<div class="flex-1 flex gap-4 cursor-pointer" @click="$router.push(`/me/orders/${order.id}`)">
<div class="flex-1 flex gap-4 cursor-pointer" @click="$router.push(tenantRoute(`/me/orders/${order.id}`))">
<div class="w-24 h-16 bg-slate-100 rounded flex-shrink-0 relative overflow-hidden group-hover:opacity-90 transition-opacity flex items-center justify-center">
<template v-if="order.type === 'recharge' || !order.items?.length">
<i class="pi pi-wallet text-3xl text-primary-500"></i>
@@ -77,7 +77,7 @@
</div>
<div class="flex flex-col gap-2">
<button v-if="order.status === 'created'" class="px-4 py-1.5 bg-primary-600 text-white text-sm font-medium rounded-lg hover:bg-primary-700 transition-colors shadow-sm active:scale-95 cursor-pointer">去支付</button>
<router-link :to="`/me/orders/${order.id}`" v-if="order.status === 'paid' || order.status === 'completed'" class="px-4 py-1.5 border border-slate-300 text-slate-700 text-sm font-medium rounded-lg hover:bg-slate-50 hover:border-slate-400 transition-colors inline-block text-center cursor-pointer active:scale-95">查看详情</router-link>
<router-link :to="tenantRoute(`/me/orders/${order.id}`)" v-if="order.status === 'paid' || order.status === 'completed'" class="px-4 py-1.5 border border-slate-300 text-slate-700 text-sm font-medium rounded-lg hover:bg-slate-50 hover:border-slate-400 transition-colors inline-block text-center cursor-pointer active:scale-95">查看详情</router-link>
<button v-if="order.status === 'completed' && order.type !== 'recharge'" class="px-4 py-1.5 text-primary-600 text-sm hover:underline cursor-pointer">申请售后</button>
<button v-if="order.status === 'created'" class="text-xs text-slate-400 hover:text-slate-600 cursor-pointer">取消订单</button>
</div>
@@ -99,10 +99,12 @@
<script setup>
import { ref, computed, onMounted, watch } from 'vue';
import { useRoute } from 'vue-router';
import { userApi } from '../../api/user';
import { useRouter } from 'vue-router';
import { tenantPath } from '../../utils/tenant';
const router = useRouter();
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const currentTab = ref('all');
const tabs = [
{ label: '全部订单', value: 'all' },
@@ -150,4 +152,4 @@ const statusColor = (status) => {
};
return map[status] || 'text-slate-500';
};
</script>
</script>

View File

@@ -36,7 +36,7 @@
<div class="text-sm text-slate-500">未认证发布内容前需完成认证</div>
</div>
</div>
<button @click="$router.push('/creator/apply')" class="px-4 py-2 text-primary-600 font-medium hover:text-primary-700 text-sm transition-colors">去认证</button>
<button @click="$router.push(tenantRoute('/creator/apply'))" class="px-4 py-2 text-primary-600 font-medium hover:text-primary-700 text-sm transition-colors">去认证</button>
</div>
</div>
@@ -72,16 +72,20 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import Dialog from 'primevue/dialog';
import ConfirmDialog from 'primevue/confirmdialog';
import Toast from 'primevue/toast';
import { useConfirm } from 'primevue/useconfirm';
import { useToast } from 'primevue/usetoast';
import { tenantPath } from '../../utils/tenant';
const confirm = useConfirm();
const toast = useToast();
const verifyDialog = ref(false);
const currentAction = ref('');
const route = useRoute();
const tenantRoute = (path) => tenantPath(path, route);
const openVerify = (action) => {
currentAction.value = action;
@@ -108,4 +112,4 @@ const confirmDelete = () => {
}
});
};
</script>
</script>