fix: resolve frontend build error and order refund bug, add member price filter
This commit is contained in:
@@ -1,29 +1,40 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { contentApi } from '../../api/content';
|
||||
import { tenantApi } from '../../api/tenant';
|
||||
|
||||
const contents = ref([]);
|
||||
const bannerItems = ref([]);
|
||||
const trendingItems = ref([]);
|
||||
const recommendedCreators = ref([]);
|
||||
const matchedCreators = ref([]);
|
||||
const searchKeyword = ref('');
|
||||
const loading = ref(true);
|
||||
const page = ref(1);
|
||||
const hasMore = ref(false);
|
||||
const activeBannerIndex = ref(0);
|
||||
|
||||
const fetchContents = async (append = false) => {
|
||||
const fetchData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: page.value,
|
||||
limit: 10,
|
||||
sort: 'latest',
|
||||
keyword: searchKeyword.value
|
||||
};
|
||||
const res = await contentApi.list(params);
|
||||
if (append) {
|
||||
contents.value.push(...(res.items || []));
|
||||
} else {
|
||||
contents.value = res.items || [];
|
||||
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);
|
||||
}
|
||||
hasMore.value = (res.total > contents.value.length);
|
||||
|
||||
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 {
|
||||
@@ -31,46 +42,78 @@ const fetchContents = async (append = false) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
const handleSearch = async () => {
|
||||
page.value = 1;
|
||||
fetchContents();
|
||||
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];
|
||||
|
||||
contents.value = contentRes.items || [];
|
||||
hasMore.value = (contentRes.total > contents.value.length);
|
||||
|
||||
if (results[1]) {
|
||||
matchedCreators.value = results[1].items || [];
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadMore = () => {
|
||||
const loadMore = async () => {
|
||||
page.value++;
|
||||
fetchContents(true);
|
||||
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(() => fetchContents());
|
||||
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">
|
||||
<!-- Mock Carousel Image -->
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80"
|
||||
class="w-full h-full object-cover opacity-80 transition-transform duration-700 group-hover:scale-105"
|
||||
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">京剧《霸王别姬》全本实录:程派艺术的巅峰演绎</h2>
|
||||
<p class="text-lg text-slate-200 line-clamp-2">梅兰芳大师经典之作,高清修复版独家上线。感受国粹魅力,重温梨园风华。</p>
|
||||
<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(`/contents/${item.id}`)">{{ item.title }}</h2>
|
||||
<p class="text-lg text-slate-200 line-clamp-2">{{ item.description || item.title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Arrows (Always visible as per spec) -->
|
||||
<button
|
||||
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"><i
|
||||
|
||||
<!-- 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
|
||||
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"><i
|
||||
<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">
|
||||
<span class="w-2 h-2 rounded-full bg-white"></span>
|
||||
<span class="w-2 h-2 rounded-full bg-white/50"></span>
|
||||
<span class="w-2 h-2 rounded-full bg-white/50"></span>
|
||||
<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>
|
||||
|
||||
@@ -111,6 +154,22 @@ onMounted(() => fetchContents());
|
||||
<!-- 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(`/creators/${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>
|
||||
</div>
|
||||
|
||||
<router-link v-for="item in contents" :key="item.id" :to="`/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">
|
||||
@@ -173,32 +232,15 @@ onMounted(() => fetchContents());
|
||||
<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 class="flex items-center gap-3">
|
||||
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=Master1" class="w-10 h-10 rounded-full">
|
||||
<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}`)">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-bold text-slate-900 text-sm truncate">梅派传人小林</div>
|
||||
<div class="text-xs text-slate-500 truncate">粉丝 12.5万</div>
|
||||
<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>
|
||||
<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 class="flex items-center gap-3">
|
||||
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=Master2" class="w-10 h-10 rounded-full">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-bold text-slate-900 text-sm truncate">豫剧李大师</div>
|
||||
<div class="text-xs text-slate-500 truncate">粉丝 8.9万</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 class="flex items-center gap-3">
|
||||
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=Master3" class="w-10 h-10 rounded-full">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-bold text-slate-900 text-sm truncate">越剧小生阿强</div>
|
||||
<div class="text-xs text-slate-500 truncate">粉丝 5.2万</div>
|
||||
</div>
|
||||
<button class="px-3 py-1 bg-slate-100 text-slate-400 text-xs font-bold rounded-full">已关注</button>
|
||||
<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>
|
||||
|
||||
@@ -208,33 +250,16 @@ onMounted(() => fetchContents());
|
||||
<i class="pi pi-chart-line text-red-500"></i> 本周热门
|
||||
</h3>
|
||||
<ul class="space-y-4">
|
||||
<li class="flex gap-3 items-start">
|
||||
<span class="text-red-500 font-bold italic text-lg w-4">1</span>
|
||||
<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
|
||||
<h4 @click="$router.push(`/contents/${item.id}`)"
|
||||
class="text-sm font-medium text-slate-800 line-clamp-2 hover:text-primary-600 cursor-pointer">
|
||||
《智取威虎山》选段:今日痛饮庆功酒</h4>
|
||||
<span class="text-xs text-slate-400 mt-1 block">15.2万 阅读</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-3 items-start">
|
||||
<span class="text-orange-500 font-bold italic text-lg w-4">2</span>
|
||||
<div class="flex-1">
|
||||
<h4
|
||||
class="text-sm font-medium text-slate-800 line-clamp-2 hover:text-primary-600 cursor-pointer">
|
||||
【深度解析】京剧脸谱颜色的含义</h4>
|
||||
<span class="text-xs text-slate-400 mt-1 block">9.8万 阅读</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-3 items-start">
|
||||
<span class="text-yellow-500 font-bold italic text-lg w-4">3</span>
|
||||
<div class="flex-1">
|
||||
<h4
|
||||
class="text-sm font-medium text-slate-800 line-clamp-2 hover:text-primary-600 cursor-pointer">
|
||||
黄梅戏《女驸马》全场高清</h4>
|
||||
<span class="text-xs text-slate-400 mt-1 block">7.5万 阅读</span>
|
||||
{{ 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>
|
||||
|
||||
@@ -251,34 +276,4 @@ onMounted(() => fetchContents());
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { contentApi } from '../api/content';
|
||||
|
||||
const contents = ref([]);
|
||||
const searchKeyword = ref('');
|
||||
const loading = ref(true);
|
||||
|
||||
const fetchContents = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
limit: 20,
|
||||
sort: 'latest',
|
||||
keyword: searchKeyword.value
|
||||
};
|
||||
const res = await contentApi.list(params);
|
||||
contents.value = res.items || [];
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
fetchContents();
|
||||
};
|
||||
|
||||
onMounted(fetchContents);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user