library-picturebook-activity/frontend/src/views/public/components/WorkSelector.vue

131 lines
3.5 KiB
Vue
Raw Normal View History

<template>
<a-modal
:open="open"
title="从作品库选择作品"
:width="600"
@cancel="$emit('update:open', false)"
@ok="handleConfirm"
:ok-button-props="{ disabled: !selectedWork }"
ok-text="确认提交"
>
<div v-if="loading" style="text-align: center; padding: 40px"><a-spin /></div>
<div v-else-if="works.length === 0" style="text-align: center; padding: 40px">
<a-empty description="作品库中没有可提交的作品">
<a-button type="primary" shape="round" @click="$router.push('/p/create'); $emit('update:open', false)">
去创作
</a-button>
</a-empty>
</div>
<div v-else class="work-selector-grid">
<div
v-for="work in works"
:key="work.id"
:class="['selector-card', { selected: selectedWork?.id === work.id }]"
@click="selectedWork = work"
>
<img v-if="work.coverUrl" :src="work.coverUrl" class="selector-cover" />
<div v-else class="selector-cover-empty"><picture-outlined /></div>
<div class="selector-info">
<h4>{{ work.title }}</h4>
<span>{{ work._count?.pages || 0 }}</span>
</div>
<check-circle-filled v-if="selectedWork?.id === work.id" class="check-icon" />
</div>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import { PictureOutlined, CheckCircleFilled } from '@ant-design/icons-vue'
import { publicUserWorksApi, type UserWork } from '@/api/public'
const props = defineProps<{ open: boolean }>()
const emit = defineEmits<{
(e: 'update:open', v: boolean): void
(e: 'select', work: UserWork): void
}>()
const works = ref<UserWork[]>([])
const loading = ref(false)
const selectedWork = ref<UserWork | null>(null)
const fetchWorks = async () => {
loading.value = true
try {
// 获取所有非草稿、非被拒绝的作品(包括已发布和私密的)
const res = await publicUserWorksApi.list({ page: 1, pageSize: 100 })
works.value = res.list.filter((w) => w.status !== 'rejected' && w.status !== 'taken_down')
} catch { /* */ }
finally { loading.value = false }
}
const handleConfirm = () => {
if (selectedWork.value) {
emit('select', selectedWork.value)
emit('update:open', false)
}
}
watch(() => props.open, (v) => {
if (v) { selectedWork.value = null; fetchWorks() }
})
</script>
<style scoped lang="scss">
$primary: #6366f1;
.work-selector-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
max-height: 400px;
overflow-y: auto;
}
.selector-card {
position: relative;
border: 2px solid #f3f4f6;
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: all 0.2s;
&:hover { border-color: rgba($primary, 0.3); }
&.selected { border-color: $primary; box-shadow: 0 0 0 2px rgba($primary, 0.2); }
.selector-cover {
width: 100%;
aspect-ratio: 3/4;
object-fit: cover;
}
.selector-cover-empty {
width: 100%;
aspect-ratio: 3/4;
background: #f5f3ff;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #d1d5db;
}
.selector-info {
padding: 8px 10px;
h4 { font-size: 12px; font-weight: 600; color: #1e1b4b; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
span { font-size: 10px; color: #9ca3af; }
}
.check-icon {
position: absolute;
top: 8px;
right: 8px;
font-size: 20px;
color: $primary;
}
}
</style>