153 lines
4.2 KiB
Vue
153 lines
4.2 KiB
Vue
<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>
|