131 lines
3.5 KiB
Vue
131 lines
3.5 KiB
Vue
|
|
<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>
|