feat: tenant-scoped routing and portal navigation
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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([
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user