|
|
@@ -31,7 +31,10 @@
|
|
|
>
|
|
|
<view class="book-header" @click="openBook(book)">
|
|
|
<text class="book-title">{{ book.title }}</text>
|
|
|
- <GenerationStatusBadge :status="book.isPublished ? 'published' : (book.genStage || book.status || 'draft')"></GenerationStatusBadge>
|
|
|
+ <view class="header-right">
|
|
|
+ <text class="edit-icon" @click.stop="goToEdit(book)">✏️</text>
|
|
|
+ <GenerationStatusBadge :status="book.isPublished ? 'published' : (book.genStage || book.status || 'draft')"></GenerationStatusBadge>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
<text class="book-desc" @click="openBook(book)">{{ book.description }}</text>
|
|
|
<view class="book-meta" @click="openBook(book)">
|
|
|
@@ -212,6 +215,10 @@ async function openBook(book: Book) {
|
|
|
uni.navigateTo({ url: `/pages/book-generator/detail?id=${book.id}` });
|
|
|
}
|
|
|
|
|
|
+function goToEdit(book: Book) {
|
|
|
+ uni.navigateTo({ url: `/pages/book-generator/create?editId=${book.id}` });
|
|
|
+}
|
|
|
+
|
|
|
// 辅助函数:清理指定书籍的轮询定时器
|
|
|
function clearAudioPollTimer(bookId: string) {
|
|
|
if (audioPollTimers.value[bookId]) {
|
|
|
@@ -303,34 +310,41 @@ async function handleMergeChapterVideo(book: Book) {
|
|
|
|
|
|
async function handleGenerateAllVideo(book: Book) {
|
|
|
if (generatingVideo.value[book.id]) return;
|
|
|
- generatingVideo.value[book.id] = true;
|
|
|
- // 清理可能存在的旧定时器
|
|
|
- clearVideoPollTimer(book.id);
|
|
|
- try {
|
|
|
- const result = await api.generateAllChaptersVideo(book.id);
|
|
|
- uni.showToast({ title: `已启动 ${result.totalChapters} 个章节的视频生成`, icon: 'none', duration: 2500 });
|
|
|
- // 开始轮询视频生成状态
|
|
|
- videoPollTimers.value[book.id] = setInterval(async () => {
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认重新生成视频',
|
|
|
+ content: `确定要重新生成《${book.title}》的所有视频吗?这将清除现有视频并重新生成。`,
|
|
|
+ success: async (res) => {
|
|
|
+ if (!res.confirm) return;
|
|
|
+ generatingVideo.value[book.id] = true;
|
|
|
+ // 清理可能存在的旧定时器
|
|
|
+ clearVideoPollTimer(book.id);
|
|
|
try {
|
|
|
- const status = await api.getVideoStatus(book.id);
|
|
|
- if (status.allCompleted) {
|
|
|
- clearVideoPollTimer(book.id);
|
|
|
- generatingVideo.value[book.id] = false;
|
|
|
- await loadBooks();
|
|
|
- uni.showToast({ title: '视频生成完成', icon: 'success' });
|
|
|
- // 写入通知
|
|
|
- const notifStore = useNotificationStore();
|
|
|
- notifStore.add({ type: 'video_complete', title: '视频生成完成', message: `《${book.title}》视频已全部生成完毕`, bookId: String(book.id) });
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error('轮询视频状态失败:', e);
|
|
|
+ const result = await api.generateAllChaptersVideo(book.id);
|
|
|
+ uni.showToast({ title: `已启动 ${result.totalChapters} 个章节的视频生成`, icon: 'none', duration: 2500 });
|
|
|
+ // 开始轮询视频生成状态
|
|
|
+ videoPollTimers.value[book.id] = setInterval(async () => {
|
|
|
+ try {
|
|
|
+ const status = await api.getVideoStatus(book.id);
|
|
|
+ if (status.allCompleted) {
|
|
|
+ clearVideoPollTimer(book.id);
|
|
|
+ generatingVideo.value[book.id] = false;
|
|
|
+ await loadBooks();
|
|
|
+ uni.showToast({ title: '视频生成完成', icon: 'success' });
|
|
|
+ // 写入通知
|
|
|
+ const notifStore = useNotificationStore();
|
|
|
+ notifStore.add({ type: 'video_complete', title: '视频生成完成', message: `《${book.title}》视频已全部生成完毕`, bookId: String(book.id) });
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('轮询视频状态失败:', e);
|
|
|
+ }
|
|
|
+ }, 5000);
|
|
|
+ } catch (e: any) {
|
|
|
+ clearVideoPollTimer(book.id);
|
|
|
+ generatingVideo.value[book.id] = false;
|
|
|
+ uni.showToast({ title: e.message || '生成失败', icon: 'none' });
|
|
|
}
|
|
|
- }, 5000);
|
|
|
- } catch (e: any) {
|
|
|
- clearVideoPollTimer(book.id);
|
|
|
- generatingVideo.value[book.id] = false;
|
|
|
- uni.showToast({ title: e.message || '生成失败', icon: 'none' });
|
|
|
- }
|
|
|
+ },
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
async function handleTogglePublish(book: any) {
|
|
|
@@ -370,6 +384,10 @@ function showMoreActions(book: Book) {
|
|
|
if (videoStatus.status === 'completed') {
|
|
|
actions.push('🔄 重新生成视频');
|
|
|
}
|
|
|
+ // 编辑书籍
|
|
|
+ actions.push('✏️ 编辑书籍');
|
|
|
+ // 删除书籍
|
|
|
+ actions.push('🗑 删除书籍');
|
|
|
|
|
|
// 如果没有可用操作,提示用户
|
|
|
if (actions.length === 0) {
|
|
|
@@ -393,6 +411,27 @@ function showMoreActions(book: Book) {
|
|
|
handleGenerateAllVideo(book);
|
|
|
} else if (action === '🔄 重新生成视频') {
|
|
|
handleGenerateAllVideo(book);
|
|
|
+ } else if (action === '✏️ 编辑书籍') {
|
|
|
+ goToEdit(book);
|
|
|
+ } else if (action === '🗑 删除书籍') {
|
|
|
+ handleDeleteBook(book);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+async function handleDeleteBook(book: Book) {
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认删除',
|
|
|
+ content: `确定要删除《${book.title}》吗?此操作不可恢复。`,
|
|
|
+ success: async (res) => {
|
|
|
+ if (!res.confirm) return;
|
|
|
+ try {
|
|
|
+ await api.deleteBook(book.id);
|
|
|
+ uni.showToast({ title: '删除成功', icon: 'success' });
|
|
|
+ await loadBooks();
|
|
|
+ } catch (e: any) {
|
|
|
+ uni.showToast({ title: e.message || '删除失败', icon: 'none' });
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
@@ -431,6 +470,9 @@ onUnmounted(() => {
|
|
|
.book-card { background: #ffffff; border-radius: 20rpx; padding: 28rpx; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); }
|
|
|
.book-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
|
|
|
.book-title { font-size: 30rpx; font-weight: 600; color: #1f2937; flex: 1; }
|
|
|
+.header-right { display: flex; align-items: center; gap: 12rpx; flex-shrink: 0; }
|
|
|
+.edit-icon { font-size: 28rpx; padding: 4rpx; opacity: 0.5; }
|
|
|
+.edit-icon:active { opacity: 1; }
|
|
|
/* 状态标签由 GenerationStatusBadge 组件统一管理 */
|
|
|
.book-desc { font-size: 26rpx; color: #6b7280; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 16rpx; }
|
|
|
.book-meta { display: flex; gap: 24rpx; font-size: 24rpx; color: #9ca3af; margin-bottom: 12rpx; }
|