library-picturebook-activity/frontend/src/views/public/components/WorkSelector.vue
aid 418aa57ea8 Day4: 超管端设计优化 + UGC绘本创作社区P0实现
一、超管端设计优化
- 文档管理SOP体系建立,docs目录重组
- 统一用户管理:跨租户全局视角,合并用户管理+公众用户
- 活动监管全模块重构:全部活动(统计卡片+阶段筛选+SuperDetail详情页)、报名数据/作品数据/评审进度(两层合一扁平列表)、成果发布(去Tab+统计+隐藏写操作)
- 菜单精简:移除评委管理/评审规则/通知管理
- Bug修复:租户编辑丢失隐藏菜单、pageSize限制、主色统一

二、UGC绘本创作社区P0
- 数据库:10张新表(user_works/user_work_pages/work_tags等)
- 子女账号独立化:Child升级为独立User,家长切换+独立登录
- 用户作品库:CRUD+发布审核,8个API
- AI创作流程:提交→生成→保存到作品库,4个API
- 作品广场:首页改造为推荐流,标签+搜索+排序
- 内容审核(超管端):作品审核+作品管理+标签管理
- 活动联动:WorkSelector作品选择器
- 布局改造:底部5Tab(发现/创作/活动/作品库/我的)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:20:25 +08:00

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>