diff --git a/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/service/PublicContentReviewService.java b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/service/PublicContentReviewService.java index f938065..5099ff6 100644 --- a/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/service/PublicContentReviewService.java +++ b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/service/PublicContentReviewService.java @@ -13,9 +13,11 @@ import com.lesingle.modules.sys.entity.SysUser; import com.lesingle.modules.sys.mapper.SysUserMapper; import com.lesingle.modules.ugc.entity.UgcReviewLog; import com.lesingle.modules.ugc.entity.UgcWork; +import com.lesingle.modules.ugc.entity.UgcWorkPage; import com.lesingle.modules.ugc.enums.WorkPublishStatus; import com.lesingle.modules.ugc.mapper.UgcReviewLogMapper; import com.lesingle.modules.ugc.mapper.UgcWorkMapper; +import com.lesingle.modules.ugc.mapper.UgcWorkPageMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -34,6 +36,7 @@ import java.util.stream.Stream; public class PublicContentReviewService { private final UgcWorkMapper ugcWorkMapper; + private final UgcWorkPageMapper ugcWorkPageMapper; private final UgcReviewLogMapper ugcReviewLogMapper; private final SysUserMapper sysUserMapper; @@ -442,6 +445,13 @@ public class PublicContentReviewService { vo.put("creator", userInfo); } } + + List pages = ugcWorkPageMapper.selectList( + new LambdaQueryWrapper() + .eq(UgcWorkPage::getWorkId, work.getId()) + .orderByAsc(UgcWorkPage::getPageNo)); + vo.put("pages", pages); + return vo; } } diff --git a/lesingle-creation-frontend/src/views/content/WorkManagement.vue b/lesingle-creation-frontend/src/views/content/WorkManagement.vue index d5ccc27..7c1196c 100644 --- a/lesingle-creation-frontend/src/views/content/WorkManagement.vue +++ b/lesingle-creation-frontend/src/views/content/WorkManagement.vue @@ -124,10 +124,14 @@ {{ formatDate(detailData.publishTime) }} - -
-

作品简介

-

{{ detailData.description }}

+ +
{{ formatDetailDateTime(detailData) }}
+ + +
+

作品介绍

+
{{ detailData.description }}
+ 暂无介绍
@@ -138,13 +142,16 @@
- +
-

绘本内容预览

+

作品详情

{{ detailData.pages[previewPage].text }}

-
+
+
+
上一页 {{ previewPage + 1 }} / {{ detailData.pages.length }} 下一页 @@ -268,6 +275,13 @@ const columns = [ const formatDate = (d: string) => d ? dayjs(d).format('YYYY-MM-DD HH:mm') : '-' +/** 详情抽屉顶部时间:优先提交审核时间,与审核台一致 */ +function formatDetailDateTime(d: Record) { + const raw = (d.submitReviewTime ?? d.createTime) as string | undefined + if (!raw) return '-' + return dayjs(raw).format('YYYY.MM.DD HH:mm') +} + const fetchStats = async () => { try { statsRaw.value = await request.get('/content-review/management/stats') as any @@ -444,17 +458,27 @@ $primary: #6366f1; .cover-thumb { width: 48px; height: 64px; object-fit: cover; border-radius: 6px; } .cover-empty { width: 48px; height: 64px; background: #f3f4f6; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 10px; color: #d1d5db; } +.detail-submit-time { + color: #666; + font-size: 14px; + margin: 16px 0 20px; +} + // 详情区块 .detail-section { margin-top: 16px; h4 { font-size: 13px; font-weight: 600; color: #374151; margin: 0 0 8px; } - .detail-desc { font-size: 13px; color: #6b7280; line-height: 1.6; margin: 0; } + .section-title { font-size: 14px; font-weight: 600; color: #1e1b4b; margin: 0 0 10px; padding-bottom: 8px; border-bottom: 1px solid #f0ecf9; } + .detail-desc { font-size: 13px; color: #6b7280; line-height: 1.6; margin: 0; white-space: pre-wrap; } + .detail-empty { font-size: 13px; color: #9ca3af; } } .preview-section { margin-top: 20px; h4 { font-size: 14px; font-weight: 600; color: #1e1b4b; margin: 0 0 12px; padding-bottom: 8px; border-bottom: 1px solid #f0ecf9; } } .page-preview { .preview-img { width: 100%; max-height: 300px; object-fit: contain; border-radius: 8px; margin-bottom: 8px; } .preview-text { font-size: 13px; color: #374151; line-height: 1.6; padding: 8px 0; } + .preview-audio-wrap { margin: 8px 0 12px; } + .preview-audio { width: 100%; max-height: 40px; } .preview-nav { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; span { font-size: 12px; color: #6b7280; } } } diff --git a/lesingle-creation-frontend/src/views/content/WorkReview.vue b/lesingle-creation-frontend/src/views/content/WorkReview.vue index dbe442a..fa88460 100644 --- a/lesingle-creation-frontend/src/views/content/WorkReview.vue +++ b/lesingle-creation-frontend/src/views/content/WorkReview.vue @@ -158,13 +158,16 @@
- +

绘本内容预览

{{ detailData.pages[previewPage].text }}

-
+
+
+
上一页 {{ previewPage + 1 }} / {{ detailData.pages.length }} 下一页 @@ -565,6 +568,8 @@ $primary: #6366f1; .page-preview { .preview-img { width: 100%; max-height: 300px; object-fit: contain; border-radius: 8px; margin-bottom: 8px; } .preview-text { font-size: 13px; color: #374151; line-height: 1.6; padding: 8px 0; } + .preview-audio-wrap { margin: 8px 0 12px; } + .preview-audio { width: 100%; max-height: 40px; } .preview-nav { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; span { font-size: 12px; color: #6b7280; } } } .review-actions { margin-top: 20px; padding-top: 16px; border-top: 1px solid #f0ecf9; diff --git a/lesingle-creation-frontend/src/views/public/create/views/DubbingView.vue b/lesingle-creation-frontend/src/views/public/create/views/DubbingView.vue index 5857d86..579f837 100644 --- a/lesingle-creation-frontend/src/views/public/create/views/DubbingView.vue +++ b/lesingle-creation-frontend/src/views/public/create/views/DubbingView.vue @@ -15,7 +15,7 @@ export default { name: 'DubbingView' }
-
{{ currentPage.pageNum === 0 ? '封面' : 'P' + currentPage.pageNum }}
+
{{ idx === 0 ? '封面' : 'P' + currentPage.pageNum }}
@@ -222,6 +222,39 @@ function showToast(msg: string) { setTimeout(() => { toast.value = '' }, 2500) } +/** + * 乐读派 voicedPages:pageNum 从 0(封面)起。 + * 本库 pageList:pageNum 与 t_ugc_work_page.page_no 从 1 起。 + * 对每个返回项:换算目标 pageNum → 在 pages 中查找同 pageNum 的分页 → 写入 audioUrl。 + */ +function applyVoicedPagesFromApi(voicedPages: { pageNum: number; audioUrl: string }[]) { + voicedPages.forEach(vp => { + const index = pages.value.findIndex(p => (p.pageNum - 1) === vp.pageNum) + if (index !== -1) { + pages.value[index].audioUrl = vp.audioUrl + pages.value[index].localBlob = null + pages.value[index].isAiVoice = true + } + }) + + // for (const vp of voicedPages) { + // const apiPageNum = Number(vp.pageNum) + // if (Number.isNaN(apiPageNum) || !vp.audioUrl) continue + + // const targetPageNum = apiPageNum + 1 + // let item = pages.value.find(p => p.pageNum === targetPageNum) + // if (!item) { + // item = pages.value.find(p => p.pageNum === apiPageNum) + // } + + // if (item) { + // item.audioUrl = vp.audioUrl + // item.localBlob = null + // item.isAiVoice = true + // } + // } +} + // -- 确认弹窗 -- const confirmVisible = ref(false) const confirmTitle = ref('') @@ -387,17 +420,10 @@ function autoAdvance() { async function voiceSingle() { voicingSingle.value = true try { - const res = await voicePage({ workId: workId.value, voiceAll: false, pageNum: currentPage.value.pageNum }) + const res = await voicePage({ workId: workId.value, voiceAll: false, pageNum: idx.value }) const data = res if (data.voicedPages?.length) { - for (const vp of data.voicedPages) { - const p = pages.value.find(x => x.pageNum === vp.pageNum) - if (p) { - p.audioUrl = vp.audioUrl - p.localBlob = null - p.isAiVoice = true - } - } + applyVoicedPagesFromApi(data.voicedPages) showToast('AI 配音成功') } else { showToast('配音失败,请重试') @@ -422,14 +448,7 @@ async function voiceAllConfirm() { const res = await voicePage({ workId: workId.value, voiceAll: true }) const data = res if (data.voicedPages) { - for (const vp of data.voicedPages) { - const p = pages.value.find(x => x.pageNum === vp.pageNum) - if (p) { - p.audioUrl = vp.audioUrl - p.localBlob = null - p.isAiVoice = true - } - } + applyVoicedPagesFromApi(data.voicedPages) } const failed = data.failedPages?.length || 0 showToast(failed > 0 ? `${data.totalSucceeded} 页成功,${failed} 页失败` : '全部 AI 配音完成')