library-picturebook-activity/frontend/src/views/public/mine/Favorites.vue
2026-04-08 17:16:13 +08:00

153 lines
4.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="favorites-page">
<div class="page-header">
<h2>我的收藏</h2>
</div>
<div v-if="loading" class="loading-wrap"><a-spin /></div>
<div v-else-if="list.length === 0" class="empty-wrap">
<a-empty description="还没有收藏任何作品">
<a-button type="primary" shape="round" @click="$router.push('/p/gallery')">
去发现作品
</a-button>
</a-empty>
</div>
<div v-else class="works-grid">
<div
v-for="item in list"
:key="item.favoriteId ?? item.workId"
class="work-card"
@click="$router.push(`/p/works/${item.workId}`)"
>
<div class="card-cover">
<img v-if="item.coverUrl" :src="item.coverUrl" :alt="item.title" />
<div v-else class="cover-placeholder">
<picture-outlined />
</div>
</div>
<div class="card-body">
<h3>{{ item.title }}</h3>
<div v-if="item.favoriteTime" class="card-meta">
{{ formatFavoriteTime(item.favoriteTime) }}
</div>
<div class="card-stats">
<span><heart-outlined /> {{ item.likeCount ?? 0 }}</span>
<span><eye-outlined /> {{ item.viewCount ?? 0 }}</span>
</div>
</div>
</div>
</div>
<div v-if="total > pageSize" class="pagination-wrap">
<a-pagination
v-model:current="page"
:total="total"
:page-size="pageSize"
simple
@change="fetchList"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { PictureOutlined, HeartOutlined, EyeOutlined } from '@ant-design/icons-vue'
import { publicInteractionApi } from '@/api/public'
/** 与后端 /public/mine/favorites 返回结构一致(扁平字段,无嵌套 work */
interface FavoriteListItem {
favoriteId: number
favoriteTime?: string
workId: number
title: string
coverUrl?: string | null
likeCount?: number
viewCount?: number
}
const list = ref<FavoriteListItem[]>([])
const loading = ref(true)
const page = ref(1)
const pageSize = 12
const total = ref(0)
/** 收藏时间展示 */
const formatFavoriteTime = (iso: string) => {
const d = new Date(iso)
if (Number.isNaN(d.getTime())) return ''
return `收藏于 ${d.toLocaleString('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' })}`
}
const fetchList = async () => {
loading.value = true
try {
const res = await publicInteractionApi.myFavorites({ page: page.value, pageSize })
list.value = res.list
total.value = res.total
} catch {
message.error('获取收藏列表失败')
} finally {
loading.value = false
}
}
onMounted(fetchList)
</script>
<style scoped lang="scss">
$primary: #6366f1;
.favorites-page { max-width: 700px; margin: 0 auto; }
.page-header {
margin-bottom: 16px;
h2 { font-size: 20px; font-weight: 700; color: #1e1b4b; margin: 0; }
}
.loading-wrap, .empty-wrap { padding: 60px 0; display: flex; justify-content: center; }
.works-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
@media (min-width: 640px) { grid-template-columns: repeat(3, 1fr); }
}
.work-card {
background: #fff;
border-radius: 14px;
overflow: hidden;
cursor: pointer;
transition: all 0.2s;
border: 1px solid rgba($primary, 0.04);
&:hover { box-shadow: 0 4px 20px rgba($primary, 0.1); transform: translateY(-2px); }
.card-cover {
aspect-ratio: 3/4;
background: #f5f3ff;
img { width: 100%; height: 100%; object-fit: cover; }
.cover-placeholder { display: flex; align-items: center; justify-content: center; height: 100%; font-size: 28px; color: #d1d5db; }
}
.card-body {
padding: 10px 12px;
h3 { font-size: 13px; font-weight: 600; color: #1e1b4b; margin: 0 0 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-meta {
font-size: 11px; color: #9ca3af; margin-bottom: 6px;
}
.card-stats {
display: flex; gap: 12px;
span { font-size: 11px; color: #9ca3af; display: flex; align-items: center; gap: 3px; }
}
}
}
.pagination-wrap { display: flex; justify-content: center; padding: 24px 0; }
</style>