110 lines
4.9 KiB
Vue
110 lines
4.9 KiB
Vue
<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);
|
|
|
|
const fetchLibrary = async () => {
|
|
try {
|
|
const res = await userApi.getLibrary();
|
|
libraryItems.value = res || [];
|
|
} catch (e) {
|
|
console.error('Fetch library failed', e);
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
onMounted(() => {
|
|
fetchLibrary();
|
|
});
|
|
|
|
const getTypeIcon = (type) => {
|
|
switch(type) {
|
|
case 'video': return 'pi-video';
|
|
case 'audio': return 'pi-volume-up';
|
|
default: return 'pi-file';
|
|
}
|
|
};
|
|
|
|
const getStatusLabel = (item) => {
|
|
// 根据后端返回的字段判断,假设后端有是否过期的逻辑或字段
|
|
if (item.status === 'unpublished') return '已下架';
|
|
if (item.expired) return '已过期';
|
|
return '永久有效';
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="bg-white rounded-xl shadow-sm border border-slate-100 min-h-[600px] p-8">
|
|
<div class="flex items-center justify-between mb-8">
|
|
<h1 class="text-2xl font-bold text-slate-900">已购内容</h1>
|
|
<div class="flex gap-4">
|
|
<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" placeholder="搜索已购内容..." class="h-10 pl-9 pr-4 rounded-lg border border-slate-200 text-sm focus:border-primary-500 focus:outline-none w-48 transition-all focus:w-64">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content List (Vertical Stack) -->
|
|
<div class="space-y-6">
|
|
<div
|
|
v-for="item in libraryItems"
|
|
:key="item.id"
|
|
@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'"
|
|
>
|
|
|
|
<!-- Cover -->
|
|
<div class="w-full sm:w-64 aspect-video bg-slate-100 relative overflow-hidden flex-shrink-0">
|
|
<img :src="item.cover" class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" :class="{ 'grayscale opacity-70': item.status !== 'published' }">
|
|
|
|
<!-- Type Icon -->
|
|
<div class="absolute bottom-2 left-2 w-8 h-8 bg-black/60 rounded-full flex items-center justify-center text-white backdrop-blur-sm">
|
|
<i class="pi" :class="getTypeIcon(item.type)"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Info -->
|
|
<div class="p-6 flex flex-col flex-1 min-w-0">
|
|
<div class="flex items-start justify-between gap-4 mb-2">
|
|
<h3 class="font-bold text-slate-900 text-lg sm:text-xl line-clamp-2 group-hover:text-primary-600 transition-colors">{{ item.title }}</h3>
|
|
<span class="flex-shrink-0 px-2 py-1 bg-green-100 text-green-700 text-xs font-bold rounded">{{ getStatusLabel(item) }}</span>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-3 text-sm text-slate-500 mb-4">
|
|
<img :src="item.author_avatar || `https://api.dicebear.com/7.x/avataaars/svg?seed=${item.author_id}`" class="w-6 h-6 rounded-full">
|
|
<span class="font-medium text-slate-700">{{ item.author_name }}</span>
|
|
<span class="w-1 h-1 bg-slate-300 rounded-full"></span>
|
|
<span>{{ item.create_time }} 发布</span>
|
|
</div>
|
|
|
|
<!-- Action -->
|
|
<div class="mt-auto pt-4 border-t border-slate-50 flex items-center justify-end">
|
|
<button v-if="item.status === 'published'" class="px-6 py-2 bg-primary-600 text-white text-base font-bold rounded-lg hover:bg-primary-700 transition-colors shadow-sm shadow-primary-200">
|
|
立即阅读
|
|
</button>
|
|
<button v-else class="px-6 py-2 bg-slate-100 text-slate-400 text-base font-bold rounded-lg cursor-not-allowed">
|
|
暂不可看
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<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="tenantRoute('/')" class="mt-4 text-primary-600 font-medium hover:underline">去发现好内容</router-link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|