feat: add creator coupons and portal lint
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
<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';
|
||||
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);
|
||||
@@ -12,296 +12,479 @@ const bannerItems = ref([]);
|
||||
const trendingItems = ref([]);
|
||||
const recommendedCreators = ref([]);
|
||||
const matchedCreators = ref([]);
|
||||
const searchKeyword = ref('');
|
||||
const searchKeyword = ref("");
|
||||
const loading = ref(true);
|
||||
const page = ref(1);
|
||||
const hasMore = ref(false);
|
||||
const activeBannerIndex = ref(0);
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const [bannerRes, trendingRes, creatorsRes, feedRes] = await Promise.all([
|
||||
contentApi.list({ is_pinned: true, limit: 5 }),
|
||||
contentApi.list({ sort: 'hot', limit: 3 }),
|
||||
tenantApi.list({ limit: 5 }),
|
||||
contentApi.list({ page: 1, limit: 10, sort: 'latest' })
|
||||
]);
|
||||
loading.value = true;
|
||||
try {
|
||||
const [bannerRes, trendingRes, creatorsRes, feedRes] = await Promise.all([
|
||||
contentApi.list({ is_pinned: true, limit: 5 }),
|
||||
contentApi.list({ sort: "hot", limit: 3 }),
|
||||
tenantApi.list({ limit: 5 }),
|
||||
contentApi.list({ page: 1, limit: 10, sort: "latest" }),
|
||||
]);
|
||||
|
||||
if (bannerRes.items && bannerRes.items.length > 0) {
|
||||
bannerItems.value = bannerRes.items;
|
||||
} else if (feedRes.items && feedRes.items.length > 0) {
|
||||
bannerItems.value = feedRes.items.slice(0, 5);
|
||||
}
|
||||
if (bannerRes.items && bannerRes.items.length > 0) {
|
||||
bannerItems.value = bannerRes.items;
|
||||
} else if (feedRes.items && feedRes.items.length > 0) {
|
||||
bannerItems.value = feedRes.items.slice(0, 5);
|
||||
}
|
||||
|
||||
trendingItems.value = trendingRes.items || [];
|
||||
recommendedCreators.value = creatorsRes.items || [];
|
||||
trendingItems.value = trendingRes.items || [];
|
||||
recommendedCreators.value = creatorsRes.items || [];
|
||||
|
||||
contents.value = feedRes.items || [];
|
||||
hasMore.value = (feedRes.total > contents.value.length);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
contents.value = feedRes.items || [];
|
||||
hasMore.value = feedRes.total > contents.value.length;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
page.value = 1;
|
||||
loading.value = true;
|
||||
matchedCreators.value = [];
|
||||
try {
|
||||
const promises = [
|
||||
contentApi.list({ page: 1, limit: 10, keyword: searchKeyword.value })
|
||||
];
|
||||
if (searchKeyword.value) {
|
||||
promises.push(tenantApi.list({ keyword: searchKeyword.value, limit: 5 }));
|
||||
}
|
||||
page.value = 1;
|
||||
loading.value = true;
|
||||
matchedCreators.value = [];
|
||||
try {
|
||||
const promises = [
|
||||
contentApi.list({ page: 1, limit: 10, keyword: searchKeyword.value }),
|
||||
];
|
||||
if (searchKeyword.value) {
|
||||
promises.push(tenantApi.list({ keyword: searchKeyword.value, limit: 5 }));
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
const contentRes = results[0];
|
||||
const results = await Promise.all(promises);
|
||||
const contentRes = results[0];
|
||||
|
||||
contents.value = contentRes.items || [];
|
||||
hasMore.value = (contentRes.total > contents.value.length);
|
||||
contents.value = contentRes.items || [];
|
||||
hasMore.value = contentRes.total > contents.value.length;
|
||||
|
||||
if (results[1]) {
|
||||
matchedCreators.value = results[1].items || [];
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
if (results[1]) {
|
||||
matchedCreators.value = results[1].items || [];
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadMore = async () => {
|
||||
page.value++;
|
||||
const res = await contentApi.list({
|
||||
page: page.value,
|
||||
limit: 10,
|
||||
keyword: searchKeyword.value
|
||||
});
|
||||
if (res.items) {
|
||||
contents.value.push(...res.items);
|
||||
hasMore.value = (res.total > contents.value.length);
|
||||
}
|
||||
page.value++;
|
||||
const res = await contentApi.list({
|
||||
page: page.value,
|
||||
limit: 10,
|
||||
keyword: searchKeyword.value,
|
||||
});
|
||||
if (res.items) {
|
||||
contents.value.push(...res.items);
|
||||
hasMore.value = res.total > contents.value.length;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(fetchData);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto max-w-screen-xl py-8">
|
||||
<!-- Hero Banner -->
|
||||
<div class="relative w-full h-[400px] rounded-2xl overflow-hidden bg-slate-900 mb-8 group"
|
||||
v-if="bannerItems.length > 0">
|
||||
<div v-for="(item, index) in bannerItems" :key="item.id"
|
||||
class="absolute inset-0 transition-opacity duration-700"
|
||||
:class="{ 'opacity-100 z-10': activeBannerIndex === index, 'opacity-0 z-0': activeBannerIndex !== index }">
|
||||
<img :src="item.cover" class="w-full h-full object-cover opacity-80" alt="Banner">
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent"></div>
|
||||
<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(tenantRoute(`/contents/${item.id}`))">{{ item.title }}</h2>
|
||||
<p class="text-lg text-slate-200 line-clamp-2">{{ item.description || item.title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Arrows -->
|
||||
<button @click="activeBannerIndex = (activeBannerIndex - 1 + bannerItems.length) % bannerItems.length"
|
||||
class="absolute left-4 top-1/2 -translate-y-1/2 w-12 h-12 bg-black/30 hover:bg-black/50 text-white rounded-full flex items-center justify-center backdrop-blur-sm transition-all z-20"><i
|
||||
class="pi pi-chevron-left text-xl"></i></button>
|
||||
<button @click="activeBannerIndex = (activeBannerIndex + 1) % bannerItems.length"
|
||||
class="absolute right-4 top-1/2 -translate-y-1/2 w-12 h-12 bg-black/30 hover:bg-black/50 text-white rounded-full flex items-center justify-center backdrop-blur-sm transition-all z-20"><i
|
||||
class="pi pi-chevron-right text-xl"></i></button>
|
||||
|
||||
<!-- Indicators -->
|
||||
<div class="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 z-20">
|
||||
<span v-for="(item, index) in bannerItems" :key="index"
|
||||
class="w-2 h-2 rounded-full cursor-pointer transition-colors"
|
||||
:class="activeBannerIndex === index ? 'bg-white' : 'bg-white/50'"
|
||||
@click="activeBannerIndex = index"></span>
|
||||
</div>
|
||||
<div class="mx-auto max-w-screen-xl py-8">
|
||||
<!-- Hero Banner -->
|
||||
<div
|
||||
class="relative w-full h-[400px] rounded-2xl overflow-hidden bg-slate-900 mb-8 group"
|
||||
v-if="bannerItems.length > 0"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in bannerItems"
|
||||
:key="item.id"
|
||||
class="absolute inset-0 transition-opacity duration-700"
|
||||
:class="{
|
||||
'opacity-100 z-10': activeBannerIndex === index,
|
||||
'opacity-0 z-0': activeBannerIndex !== index,
|
||||
}"
|
||||
>
|
||||
<img
|
||||
:src="item.cover"
|
||||
class="w-full h-full object-cover opacity-80"
|
||||
alt="Banner"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent"
|
||||
></div>
|
||||
<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(tenantRoute(`/contents/${item.id}`))"
|
||||
>
|
||||
{{ item.title }}
|
||||
</h2>
|
||||
<p class="text-lg text-slate-200 line-clamp-2">
|
||||
{{ item.description || item.title }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<div class="mb-8">
|
||||
<div
|
||||
class="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-6 border-b border-slate-200 pb-4">
|
||||
<div class="flex items-center gap-8">
|
||||
<button
|
||||
class="text-lg font-bold text-primary-600 border-b-2 border-primary-600 -mb-4.5 pb-4 px-2">推荐</button>
|
||||
<button
|
||||
class="text-lg font-medium text-slate-500 hover:text-slate-800 -mb-4.5 pb-4 px-2 transition-colors">最新</button>
|
||||
<button
|
||||
class="text-lg font-medium text-slate-500 hover:text-slate-800 -mb-4.5 pb-4 px-2 transition-colors">热门</button>
|
||||
</div>
|
||||
<!-- Arrows -->
|
||||
<button
|
||||
@click="
|
||||
activeBannerIndex =
|
||||
(activeBannerIndex - 1 + bannerItems.length) % bannerItems.length
|
||||
"
|
||||
class="absolute left-4 top-1/2 -translate-y-1/2 w-12 h-12 bg-black/30 hover:bg-black/50 text-white rounded-full flex items-center justify-center backdrop-blur-sm transition-all z-20"
|
||||
>
|
||||
<i class="pi pi-chevron-left text-xl"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="
|
||||
activeBannerIndex = (activeBannerIndex + 1) % bannerItems.length
|
||||
"
|
||||
class="absolute right-4 top-1/2 -translate-y-1/2 w-12 h-12 bg-black/30 hover:bg-black/50 text-white rounded-full flex items-center justify-center backdrop-blur-sm transition-all z-20"
|
||||
>
|
||||
<i class="pi pi-chevron-right text-xl"></i>
|
||||
</button>
|
||||
|
||||
<!-- Global Search -->
|
||||
<div class="relative">
|
||||
<i class="pi pi-search absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>
|
||||
<input type="text" v-model="searchKeyword" @keyup.enter="handleSearch" placeholder="搜索全站内容..."
|
||||
class="h-10 pl-10 pr-4 rounded-full border border-slate-200 bg-slate-50 text-sm focus:bg-white focus:border-primary-500 focus:outline-none w-full md:w-64 transition-all">
|
||||
<!-- Indicators -->
|
||||
<div class="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 z-20">
|
||||
<span
|
||||
v-for="(item, index) in bannerItems"
|
||||
:key="index"
|
||||
class="w-2 h-2 rounded-full cursor-pointer transition-colors"
|
||||
:class="activeBannerIndex === index ? 'bg-white' : 'bg-white/50'"
|
||||
@click="activeBannerIndex = index"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<div class="mb-8">
|
||||
<div
|
||||
class="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-6 border-b border-slate-200 pb-4"
|
||||
>
|
||||
<div class="flex items-center gap-8">
|
||||
<button
|
||||
class="text-lg font-bold text-primary-600 border-b-2 border-primary-600 -mb-4.5 pb-4 px-2"
|
||||
>
|
||||
推荐
|
||||
</button>
|
||||
<button
|
||||
class="text-lg font-medium text-slate-500 hover:text-slate-800 -mb-4.5 pb-4 px-2 transition-colors"
|
||||
>
|
||||
最新
|
||||
</button>
|
||||
<button
|
||||
class="text-lg font-medium text-slate-500 hover:text-slate-800 -mb-4.5 pb-4 px-2 transition-colors"
|
||||
>
|
||||
热门
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Global Search -->
|
||||
<div class="relative">
|
||||
<i
|
||||
class="pi pi-search absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"
|
||||
></i>
|
||||
<input
|
||||
type="text"
|
||||
v-model="searchKeyword"
|
||||
@keyup.enter="handleSearch"
|
||||
placeholder="搜索全站内容..."
|
||||
class="h-10 pl-10 pr-4 rounded-full border border-slate-200 bg-slate-50 text-sm focus:bg-white focus:border-primary-500 focus:outline-none w-full md:w-64 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tags -->
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-slate-900 text-white text-sm font-medium"
|
||||
>
|
||||
全部
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-slate-100 text-slate-600 hover:bg-slate-200 text-sm font-medium transition-colors"
|
||||
>
|
||||
京剧
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-slate-100 text-slate-600 hover:bg-slate-200 text-sm font-medium transition-colors"
|
||||
>
|
||||
昆曲
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-slate-100 text-slate-600 hover:bg-slate-200 text-sm font-medium transition-colors"
|
||||
>
|
||||
越剧
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-slate-100 text-slate-600 hover:bg-slate-200 text-sm font-medium transition-colors"
|
||||
>
|
||||
名家名段
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-slate-100 text-slate-600 hover:bg-slate-200 text-sm font-medium transition-colors"
|
||||
>
|
||||
戏曲教学
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Layout: Grid 9:3 -->
|
||||
<div class="grid grid-cols-12 gap-8">
|
||||
<!-- Main Feed (Left 9) -->
|
||||
<div class="col-span-12 lg:col-span-8 xl:col-span-9 space-y-6">
|
||||
<!-- Matched Creators (Search Result) -->
|
||||
<div
|
||||
v-if="searchKeyword && matchedCreators.length > 0"
|
||||
class="bg-white rounded-2xl shadow-sm border border-slate-100 p-6"
|
||||
>
|
||||
<h3 class="font-bold text-slate-900 mb-4">相关频道</h3>
|
||||
<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(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">
|
||||
<div class="font-bold text-slate-900 truncate">
|
||||
{{ creator.name }}
|
||||
</div>
|
||||
<div class="text-xs text-slate-500 truncate">
|
||||
{{ creator.bio || "暂无简介" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tags -->
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<button class="px-4 py-1.5 rounded-full bg-slate-900 text-white text-sm font-medium">全部</button>
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-slate-100 text-slate-600 hover:bg-slate-200 text-sm font-medium transition-colors">京剧</button>
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-slate-100 text-slate-600 hover:bg-slate-200 text-sm font-medium transition-colors">昆曲</button>
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-slate-100 text-slate-600 hover:bg-slate-200 text-sm font-medium transition-colors">越剧</button>
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-slate-100 text-slate-600 hover:bg-slate-200 text-sm font-medium transition-colors">名家名段</button>
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-slate-100 text-slate-600 hover:bg-slate-200 text-sm font-medium transition-colors">戏曲教学</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<span
|
||||
v-if="item.price === 0"
|
||||
class="px-2 py-0.5 rounded text-xs font-bold bg-green-500 text-white"
|
||||
>免费</span
|
||||
>
|
||||
<span
|
||||
v-else
|
||||
class="px-2 py-0.5 rounded text-xs font-bold bg-red-600 text-white"
|
||||
>付费</span
|
||||
>
|
||||
<span
|
||||
class="text-xs font-bold text-slate-700 bg-slate-50 border border-slate-100 px-2 py-0.5 rounded-full"
|
||||
>{{ item.genre }}</span
|
||||
>
|
||||
</div>
|
||||
<h3
|
||||
class="text-xl font-bold text-slate-900 mb-3 leading-snug group-hover:text-primary-600 transition-colors"
|
||||
>
|
||||
{{ item.title }}
|
||||
</h3>
|
||||
<p
|
||||
class="text-base text-slate-500 line-clamp-2 mb-6 leading-relaxed"
|
||||
>
|
||||
{{ item.description || item.title }}
|
||||
</p>
|
||||
<div class="mt-auto flex items-center justify-between">
|
||||
<div class="flex items-center gap-3 text-sm text-slate-500">
|
||||
<img
|
||||
:src="
|
||||
item.author_avatar ||
|
||||
'https://api.dicebear.com/7.x/avataaars/svg?seed=' +
|
||||
item.author_id
|
||||
"
|
||||
class="w-7 h-7 rounded-full ring-2 ring-white"
|
||||
/>
|
||||
<span class="font-medium text-slate-700">{{
|
||||
item.author_name || "Unknown"
|
||||
}}</span>
|
||||
<span class="text-slate-300">|</span>
|
||||
<span>{{ item.create_time || "刚刚" }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-sm text-slate-400 font-medium"
|
||||
><i class="pi pi-eye mr-1"></i> {{ item.views }}</span
|
||||
>
|
||||
<span class="text-xl font-bold text-red-600"
|
||||
>¥ {{ item.price }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.cover"
|
||||
class="w-[280px] h-[157px] flex-shrink-0 rounded-xl overflow-hidden relative bg-slate-100 hidden sm:block shadow-inner"
|
||||
>
|
||||
<img
|
||||
:src="item.cover"
|
||||
class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-0 bg-black/10 group-hover:bg-black/0 transition-colors"
|
||||
></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<i
|
||||
class="pi pi-play-circle text-5xl text-white opacity-0 group-hover:opacity-100 transition-all scale-75 group-hover:scale-100 drop-shadow-lg"
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<!-- Load More -->
|
||||
<div class="pt-4 text-center" v-if="hasMore">
|
||||
<button
|
||||
@click="loadMore"
|
||||
:disabled="loading"
|
||||
class="px-8 py-3 bg-white border border-slate-200 rounded-full text-slate-600 hover:bg-slate-50 hover:text-primary-600 font-medium transition-all shadow-sm disabled:opacity-50"
|
||||
>
|
||||
<span v-if="loading">加载中...</span>
|
||||
<span v-else>点击加载更多内容</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Layout: Grid 9:3 -->
|
||||
<div class="grid grid-cols-12 gap-8">
|
||||
<!-- Main Feed (Left 9) -->
|
||||
<div class="col-span-12 lg:col-span-8 xl:col-span-9 space-y-6">
|
||||
<!-- Sidebar (Right 3) -->
|
||||
<div class="hidden lg:block lg:col-span-4 xl:col-span-3 space-y-6">
|
||||
<!-- Announcement -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-5">
|
||||
<h3 class="font-bold text-slate-900 mb-4 flex items-center gap-2">
|
||||
<i class="pi pi-megaphone text-orange-500"></i> 公告
|
||||
</h3>
|
||||
<ul class="space-y-3 text-sm text-slate-600">
|
||||
<li class="line-clamp-1 hover:text-primary-600 cursor-pointer">
|
||||
• 关于调整创作者收益结算周期的通知
|
||||
</li>
|
||||
<li class="line-clamp-1 hover:text-primary-600 cursor-pointer">
|
||||
• “国粹传承”戏曲短视频大赛开启!
|
||||
</li>
|
||||
<li class="line-clamp-1 hover:text-primary-600 cursor-pointer">
|
||||
• 平台系统维护升级公告 (12.30)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Matched Creators (Search Result) -->
|
||||
<div v-if="searchKeyword && matchedCreators.length > 0"
|
||||
class="bg-white rounded-2xl shadow-sm border border-slate-100 p-6">
|
||||
<h3 class="font-bold text-slate-900 mb-4">相关频道</h3>
|
||||
<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(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">
|
||||
<div class="font-bold text-slate-900 truncate">{{ creator.name }}</div>
|
||||
<div class="text-xs text-slate-500 truncate">{{ creator.bio || '暂无简介' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Recommended Tenants -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-5">
|
||||
<h3 class="font-bold text-slate-900 mb-4">推荐名家</h3>
|
||||
<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(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(tenantRoute(`/t/${creator.id}`))"
|
||||
>
|
||||
{{ creator.name }}
|
||||
</div>
|
||||
<div class="text-xs text-slate-500 truncate">
|
||||
粉丝 {{ creator.stats?.followers || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="px-3 py-1 bg-primary-50 text-primary-600 text-xs font-bold rounded-full hover:bg-primary-100"
|
||||
>
|
||||
关注
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<span v-if="item.price === 0"
|
||||
class="px-2 py-0.5 rounded text-xs font-bold bg-green-500 text-white">免费</span>
|
||||
<span v-else class="px-2 py-0.5 rounded text-xs font-bold bg-red-600 text-white">付费</span>
|
||||
<span
|
||||
class="text-xs font-bold text-slate-700 bg-slate-50 border border-slate-100 px-2 py-0.5 rounded-full">{{
|
||||
item.genre }}</span>
|
||||
</div>
|
||||
<h3
|
||||
class="text-xl font-bold text-slate-900 mb-3 leading-snug group-hover:text-primary-600 transition-colors">
|
||||
{{ item.title }}</h3>
|
||||
<p class="text-base text-slate-500 line-clamp-2 mb-6 leading-relaxed">{{ item.description ||
|
||||
item.title }}</p>
|
||||
<div class="mt-auto flex items-center justify-between">
|
||||
<div class="flex items-center gap-3 text-sm text-slate-500">
|
||||
<img
|
||||
:src="item.author_avatar || 'https://api.dicebear.com/7.x/avataaars/svg?seed=' + item.author_id"
|
||||
class="w-7 h-7 rounded-full ring-2 ring-white">
|
||||
<span class="font-medium text-slate-700">{{ item.author_name || 'Unknown' }}</span>
|
||||
<span class="text-slate-300">|</span>
|
||||
<span>{{ item.create_time || '刚刚' }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-sm text-slate-400 font-medium"><i class="pi pi-eye mr-1"></i> {{ item.views
|
||||
}}</span>
|
||||
<span class="text-xl font-bold text-red-600">¥ {{ item.price }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.cover"
|
||||
class="w-[280px] h-[157px] flex-shrink-0 rounded-xl overflow-hidden relative bg-slate-100 hidden sm:block shadow-inner">
|
||||
<img :src="item.cover"
|
||||
class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110">
|
||||
<div class="absolute inset-0 bg-black/10 group-hover:bg-black/0 transition-colors"></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<i
|
||||
class="pi pi-play-circle text-5xl text-white opacity-0 group-hover:opacity-100 transition-all scale-75 group-hover:scale-100 drop-shadow-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<!-- Load More -->
|
||||
<div class="pt-4 text-center" v-if="hasMore">
|
||||
<button @click="loadMore" :disabled="loading"
|
||||
class="px-8 py-3 bg-white border border-slate-200 rounded-full text-slate-600 hover:bg-slate-50 hover:text-primary-600 font-medium transition-all shadow-sm disabled:opacity-50">
|
||||
<span v-if="loading">加载中...</span>
|
||||
<span v-else>点击加载更多内容</span>
|
||||
</button>
|
||||
<div
|
||||
v-if="recommendedCreators.length === 0"
|
||||
class="text-center text-slate-400 text-sm"
|
||||
>
|
||||
暂无推荐
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar (Right 3) -->
|
||||
<div class="hidden lg:block lg:col-span-4 xl:col-span-3 space-y-6">
|
||||
<!-- Announcement -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-5">
|
||||
<h3 class="font-bold text-slate-900 mb-4 flex items-center gap-2">
|
||||
<i class="pi pi-megaphone text-orange-500"></i> 公告
|
||||
</h3>
|
||||
<ul class="space-y-3 text-sm text-slate-600">
|
||||
<li class="line-clamp-1 hover:text-primary-600 cursor-pointer">• 关于调整创作者收益结算周期的通知</li>
|
||||
<li class="line-clamp-1 hover:text-primary-600 cursor-pointer">• “国粹传承”戏曲短视频大赛开启!</li>
|
||||
<li class="line-clamp-1 hover:text-primary-600 cursor-pointer">• 平台系统维护升级公告 (12.30)</li>
|
||||
</ul>
|
||||
<!-- Trending List -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-5">
|
||||
<h3 class="font-bold text-slate-900 mb-4 flex items-center gap-2">
|
||||
<i class="pi pi-chart-line text-red-500"></i> 本周热门
|
||||
</h3>
|
||||
<ul class="space-y-4">
|
||||
<li
|
||||
v-for="(item, index) in trendingItems"
|
||||
:key="item.id"
|
||||
class="flex gap-3 items-start"
|
||||
>
|
||||
<span
|
||||
class="font-bold italic text-lg w-4"
|
||||
: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(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
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
<div
|
||||
v-if="trendingItems.length === 0"
|
||||
class="text-center text-slate-400 text-sm"
|
||||
>
|
||||
暂无热门
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Recommended Tenants -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-5">
|
||||
<h3 class="font-bold text-slate-900 mb-4">推荐名家</h3>
|
||||
<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(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(tenantRoute(`/t/${creator.id}`))">{{ creator.name }}</div>
|
||||
<div class="text-xs text-slate-500 truncate">粉丝 {{ creator.stats?.followers || 0 }}</div>
|
||||
</div>
|
||||
<button
|
||||
class="px-3 py-1 bg-primary-50 text-primary-600 text-xs font-bold rounded-full hover:bg-primary-100">关注</button>
|
||||
</div>
|
||||
<div v-if="recommendedCreators.length === 0" class="text-center text-slate-400 text-sm">暂无推荐</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Trending List -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-5">
|
||||
<h3 class="font-bold text-slate-900 mb-4 flex items-center gap-2">
|
||||
<i class="pi pi-chart-line text-red-500"></i> 本周热门
|
||||
</h3>
|
||||
<ul class="space-y-4">
|
||||
<li v-for="(item, index) in trendingItems" :key="item.id" class="flex gap-3 items-start">
|
||||
<span class="font-bold italic text-lg w-4"
|
||||
: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(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>
|
||||
</div>
|
||||
</li>
|
||||
<div v-if="trendingItems.length === 0" class="text-center text-slate-400 text-sm">暂无热门</div>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Ad / Promo -->
|
||||
<div class="rounded-xl overflow-hidden shadow-sm">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1557683316-973673baf926?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60"
|
||||
class="w-full h-40 object-cover">
|
||||
<div class="bg-white p-3 flex justify-between items-center">
|
||||
<span class="text-xs text-slate-400 border border-slate-200 px-1 rounded">广告</span>
|
||||
<span class="text-sm font-medium text-slate-700">戏曲周边商城上线啦</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Ad / Promo -->
|
||||
<div class="rounded-xl overflow-hidden shadow-sm">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1557683316-973673baf926?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60"
|
||||
class="w-full h-40 object-cover"
|
||||
/>
|
||||
<div class="bg-white p-3 flex justify-between items-center">
|
||||
<span
|
||||
class="text-xs text-slate-400 border border-slate-200 px-1 rounded"
|
||||
>广告</span
|
||||
>
|
||||
<span class="text-sm font-medium text-slate-700"
|
||||
>戏曲周边商城上线啦</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user