|
@@ -56,8 +56,8 @@
|
|
|
<view v-if="book.progress > 0 && book.progress < 100" class="progress-bar" @click="openBook(book)">
|
|
<view v-if="book.progress > 0 && book.progress < 100" class="progress-bar" @click="openBook(book)">
|
|
|
<view class="progress-fill" :style="{ width: book.progress + '%' }"></view>
|
|
<view class="progress-fill" :style="{ width: book.progress + '%' }"></view>
|
|
|
</view>
|
|
</view>
|
|
|
- <!-- 批量生成音频按钮 -->
|
|
|
|
|
- <view v-if="book.status === 'completed'" class="book-actions">
|
|
|
|
|
|
|
+ <!-- 批量生成音频按钮 - 只要有章节就显示 -->
|
|
|
|
|
+ <view v-if="book.chapters && book.chapters.length > 0" class="book-actions">
|
|
|
<button
|
|
<button
|
|
|
class="audio-btn"
|
|
class="audio-btn"
|
|
|
:disabled="generatingAudio"
|
|
:disabled="generatingAudio"
|
|
@@ -266,7 +266,7 @@
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 中断提示 -->
|
|
<!-- 中断提示 -->
|
|
|
- <view v-if="currentBook.status === 'interrupted'" class="interrupted-tip">
|
|
|
|
|
|
|
+ <view v-if="currentBook.status === 'failed' && currentBook?.error?.includes('额度')" class="interrupted-tip">
|
|
|
<text class="interrupted-icon">⚠️</text>
|
|
<text class="interrupted-icon">⚠️</text>
|
|
|
<text class="interrupted-text">生成已中断:额度不足,已保存当前进度</text>
|
|
<text class="interrupted-text">生成已中断:额度不足,已保存当前进度</text>
|
|
|
<text class="interrupted-hint">可升级套餐后继续生成</text>
|
|
<text class="interrupted-hint">可升级套餐后继续生成</text>
|
|
@@ -303,55 +303,65 @@
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 操作按钮 -->
|
|
|
|
|
- <view class="card action-card">
|
|
|
|
|
- <!-- 未生成大纲:显示所有生成方式 -->
|
|
|
|
|
- <view v-if="!currentBook?.outline && currentBook?.status !== 'planning'" class="btn-col">
|
|
|
|
|
- <view class="btn-row">
|
|
|
|
|
- <button
|
|
|
|
|
- class="action-btn"
|
|
|
|
|
- :disabled="generating"
|
|
|
|
|
- @click="handleGenerateOutline"
|
|
|
|
|
- >
|
|
|
|
|
- {{ generating ? '生成中...' : '📋 生成大纲' }}
|
|
|
|
|
- </button>
|
|
|
|
|
- <button
|
|
|
|
|
- class="action-btn primary"
|
|
|
|
|
- :disabled="generating"
|
|
|
|
|
- @click="handleGenerateAll"
|
|
|
|
|
- >
|
|
|
|
|
- {{ generating ? '生成中...' : '🚀 一键生成' }}
|
|
|
|
|
- </button>
|
|
|
|
|
- <button
|
|
|
|
|
- class="action-btn langgraph-btn"
|
|
|
|
|
- :disabled="generating"
|
|
|
|
|
- @click="handleLangGraphGenerate"
|
|
|
|
|
- >
|
|
|
|
|
- {{ generating ? '生成中...' : '🤖 LangGraph' }}
|
|
|
|
|
- </button>
|
|
|
|
|
|
|
+ <!-- 完整内容区 - 前言/后记/章节 -->
|
|
|
|
|
+ <view v-if="currentBook?.chapters && currentBook.chapters.length > 0" class="card content-card">
|
|
|
|
|
+ <text class="card-title">📖 完整内容</text>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 前言 -->
|
|
|
|
|
+ <view v-if="currentBook?.metadata?.foreword" class="content-item foreword" @click="viewForeword">
|
|
|
|
|
+ <view class="content-item-left">
|
|
|
|
|
+ <text class="content-label">前言</text>
|
|
|
|
|
+ <text class="content-word-count">{{ currentBook?.metadata?.foreword?.length || 0 }}字</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="content-item-right">
|
|
|
|
|
+ <text class="expand-icon">→</text>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 大纲已生成,显示所有生成方式 -->
|
|
|
|
|
- <view v-if="currentBook?.outline && currentBook.status !== 'generating'" class="btn-col">
|
|
|
|
|
- <view class="btn-row">
|
|
|
|
|
- <button class="action-btn primary" :disabled="generating" @click="handleGenerateAll">
|
|
|
|
|
- {{ generating ? '生成中...' : '🚀 一键生成' }}
|
|
|
|
|
- </button>
|
|
|
|
|
- <button class="action-btn langgraph-btn" :disabled="generating" @click="handleLangGraphGenerate">
|
|
|
|
|
- {{ generating ? '生成中...' : '🤖 LangGraph' }}
|
|
|
|
|
- </button>
|
|
|
|
|
|
|
+ <!-- 章节列表 -->
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="chapter in currentBook.chapters"
|
|
|
|
|
+ :key="chapter.id"
|
|
|
|
|
+ class="content-item chapter-item"
|
|
|
|
|
+ @click="goToChapterDetail(chapter)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view class="content-item-header">
|
|
|
|
|
+ <view class="content-item-left">
|
|
|
|
|
+ <text class="chapter-num-badge">第{{ chapter.number }}章</text>
|
|
|
|
|
+ <text class="chapter-title-short">{{ chapter.title }}</text>
|
|
|
|
|
+ <text v-if="chapter.audioUrl" class="audio-badge">🎵</text>
|
|
|
|
|
+ <text v-if="chapter.videoUrl" class="video-badge">🎬</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="content-item-right">
|
|
|
|
|
+ <text class="expand-icon">→</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 后记 -->
|
|
|
|
|
+ <view v-if="currentBook?.metadata?.afterword" class="content-item afterword" @click="viewAfterword">
|
|
|
|
|
+ <view class="content-item-left">
|
|
|
|
|
+ <text class="content-label">后记</text>
|
|
|
|
|
+ <text class="content-word-count">{{ currentBook?.metadata?.afterword?.length || 0 }}字</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="content-item-right">
|
|
|
|
|
+ <text class="expand-icon">→</text>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 操作按钮 -->
|
|
|
|
|
+ <view class="card action-card">
|
|
|
|
|
+ <!-- LangGraph 生成按钮 -->
|
|
|
|
|
+ <view class="btn-col">
|
|
|
<view class="btn-row">
|
|
<view class="btn-row">
|
|
|
- <button class="action-btn" @click="handleGenerateForeword">前言</button>
|
|
|
|
|
<button
|
|
<button
|
|
|
- class="action-btn"
|
|
|
|
|
|
|
+ class="action-btn langgraph-btn"
|
|
|
:disabled="generating"
|
|
:disabled="generating"
|
|
|
- @click="handleGenerateAllChapters"
|
|
|
|
|
|
|
+ @click="handleLangGraphGenerate"
|
|
|
>
|
|
>
|
|
|
- {{ generating ? '生成中...' : '📝 章节' }}
|
|
|
|
|
|
|
+ {{ generating ? '生成中...' : '🤖 开始生成' }}
|
|
|
</button>
|
|
</button>
|
|
|
- <button class="action-btn" @click="handleGenerateAfterword">后记</button>
|
|
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
@@ -360,118 +370,26 @@
|
|
|
<text>正在生成中,请稍候...</text>
|
|
<text>正在生成中,请稍候...</text>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 查看完整内容 -->
|
|
|
|
|
- <button
|
|
|
|
|
- v-if="currentBook?.status === 'completed'"
|
|
|
|
|
- class="action-btn primary full-width"
|
|
|
|
|
- @click="showFullContent"
|
|
|
|
|
- >
|
|
|
|
|
- 📖 查看完整书籍
|
|
|
|
|
- </button>
|
|
|
|
|
|
|
+ <!-- 无章节警告 -->
|
|
|
|
|
+ <view v-if="currentBook?.status === 'completed' && (!currentBook.chapters || currentBook.chapters.length === 0)" class="warning-tip">
|
|
|
|
|
+ <text class="warning-icon">⚠️</text>
|
|
|
|
|
+ <text class="warning-text">该书籍状态标记为已完成,但没有任何章节内容。</text>
|
|
|
|
|
+ <text class="warning-hint">请尝试点击上方按钮重新生成。</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
|
|
|
<!-- 返回列表 -->
|
|
<!-- 返回列表 -->
|
|
|
- <button class="action-btn full-width" @click="currentView = 'list'; loadBooks()">
|
|
|
|
|
|
|
+ <button class="action-btn full-width" @click="backToList">
|
|
|
返回书籍列表
|
|
返回书籍列表
|
|
|
</button>
|
|
</button>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
-
|
|
|
|
|
- <!-- 目录视图 -->
|
|
|
|
|
- <view v-if="currentView === 'toc'" class="toc-view">
|
|
|
|
|
- <!-- 顶部导航 -->
|
|
|
|
|
- <view class="nav-bar">
|
|
|
|
|
- <view class="nav-content">
|
|
|
|
|
- <view class="nav-left" @click="currentView = 'detail'">
|
|
|
|
|
- <text class="back-icon">←</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- <text class="page-title">目录</text>
|
|
|
|
|
- <view class="nav-right"></view>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="toc-content">
|
|
|
|
|
- <!-- 前言入口 -->
|
|
|
|
|
- <view v-if="currentBook?.metadata?.foreword" class="toc-item foreword" @click="showForeword">
|
|
|
|
|
- <text class="toc-label">前言</text>
|
|
|
|
|
- <text class="toc-arrow">→</text>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 章节列表 -->
|
|
|
|
|
- <view
|
|
|
|
|
- v-for="chapter in currentBook?.chapters"
|
|
|
|
|
- :key="chapter.id"
|
|
|
|
|
- :class="['toc-item', 'chapter', chapter.status === 'completed' ? 'done' : 'pending']"
|
|
|
|
|
- >
|
|
|
|
|
- <view class="toc-left" @click="openChapter(chapter)">
|
|
|
|
|
- <text class="chapter-num">第{{ chapter.number }}章</text>
|
|
|
|
|
- <view class="chapter-info-col">
|
|
|
|
|
- <text class="chapter-title">{{ chapter.title }}</text>
|
|
|
|
|
- <text v-if="chapter.audioUrl" class="audio-status">🎵 已生成音频</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view class="toc-actions">
|
|
|
|
|
- <!-- 生成音频按钮 -->
|
|
|
|
|
- <button
|
|
|
|
|
- v-if="chapter.status === 'completed' && !chapter.audioUrl"
|
|
|
|
|
- class="mini-audio-btn"
|
|
|
|
|
- :disabled="generatingAudio"
|
|
|
|
|
- @click.stop="handleGenerateChapterAudio(chapter)"
|
|
|
|
|
- >
|
|
|
|
|
- 🎵
|
|
|
|
|
- </button>
|
|
|
|
|
- <!-- 生成视频按钮 -->
|
|
|
|
|
- <button
|
|
|
|
|
- v-if="chapter.status === 'completed' && chapter.audioUrl && !chapter.videoUrl"
|
|
|
|
|
- class="mini-video-btn"
|
|
|
|
|
- :disabled="generatingVideo"
|
|
|
|
|
- @click.stop="handleGenerateChapterVideo(chapter)"
|
|
|
|
|
- >
|
|
|
|
|
- 🎬
|
|
|
|
|
- </button>
|
|
|
|
|
- <text v-if="chapter.videoUrl" class="video-status">🎬 已生成</text>
|
|
|
|
|
- <text class="toc-arrow" @click="openChapter(chapter)">→</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 后记入口 -->
|
|
|
|
|
- <view v-if="currentBook?.metadata?.afterword" class="toc-item afterword" @click="showAfterword">
|
|
|
|
|
- <text class="toc-label">后记</text>
|
|
|
|
|
- <text class="toc-arrow">→</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 章节详情视图 -->
|
|
|
|
|
- <view v-if="currentView === 'chapter-detail'" class="chapter-detail-view">
|
|
|
|
|
- <!-- 顶部导航 -->
|
|
|
|
|
- <view class="nav-bar">
|
|
|
|
|
- <view class="nav-content">
|
|
|
|
|
- <view class="nav-left" @click="currentView = 'toc'">
|
|
|
|
|
- <text class="back-icon">←</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- <text class="page-title">第{{ currentChapter?.number }}章</text>
|
|
|
|
|
- <view class="nav-right"></view>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="chapter-content">
|
|
|
|
|
- <scroll-view class="chapter-scroll" scroll-y>
|
|
|
|
|
- <view class="chapter-header">
|
|
|
|
|
- <text class="chapter-title-large">{{ currentChapter?.title }}</text>
|
|
|
|
|
- <text class="chapter-word-count">{{ currentChapter?.wordCount || 0 }}字</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view class="chapter-body">
|
|
|
|
|
- <text class="chapter-text">{{ currentChapter?.content }}</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- </scroll-view>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
</view>
|
|
</view>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { ref, computed, onMounted, watch, nextTick } from 'vue';
|
|
import { ref, computed, onMounted, watch, nextTick } from 'vue';
|
|
|
|
|
+import { onShow } from '@dcloudio/uni-app';
|
|
|
import * as api from '../../utils/book-generator-api';
|
|
import * as api from '../../utils/book-generator-api';
|
|
|
import type { Book, BookOutline, Chapter } from '../../utils/book-generator-api';
|
|
import type { Book, BookOutline, Chapter } from '../../utils/book-generator-api';
|
|
|
|
|
|
|
@@ -497,9 +415,6 @@ const books = ref<Book[]>([]);
|
|
|
// 当前书籍
|
|
// 当前书籍
|
|
|
const currentBook = ref<Book | null>(null);
|
|
const currentBook = ref<Book | null>(null);
|
|
|
|
|
|
|
|
-// 当前查看的章节
|
|
|
|
|
-const currentChapter = ref<Chapter | null>(null);
|
|
|
|
|
-
|
|
|
|
|
// 创建表单
|
|
// 创建表单
|
|
|
const showCreateModal = ref(false);
|
|
const showCreateModal = ref(false);
|
|
|
const creating = ref(false);
|
|
const creating = ref(false);
|
|
@@ -727,6 +642,8 @@ async function openBook(book: Book) {
|
|
|
try {
|
|
try {
|
|
|
currentBook.value = await api.getBook(book.id);
|
|
currentBook.value = await api.getBook(book.id);
|
|
|
currentView.value = 'detail';
|
|
currentView.value = 'detail';
|
|
|
|
|
+ // 更新 URL hash,带上书籍 ID
|
|
|
|
|
+ updateUrlWithBookId(book.id);
|
|
|
// 加载配额信息
|
|
// 加载配额信息
|
|
|
loadQuotaInfo();
|
|
loadQuotaInfo();
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
@@ -734,6 +651,57 @@ async function openBook(book: Book) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 更新 URL,带上书籍 ID
|
|
|
|
|
+function updateUrlWithBookId(bookId: string) {
|
|
|
|
|
+ if (typeof window !== 'undefined') {
|
|
|
|
|
+ const url = new URL(window.location.href);
|
|
|
|
|
+ url.hash = `/pages/book-generator/index?id=${bookId}`;
|
|
|
|
|
+ window.history.pushState({}, '', url.toString());
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 从 URL 获取书籍 ID
|
|
|
|
|
+function getBookIdFromUrl(): string | null {
|
|
|
|
|
+ if (typeof window !== 'undefined') {
|
|
|
|
|
+ const hash = window.location.hash;
|
|
|
|
|
+ const match = hash.match(/id=([^&]+)/);
|
|
|
|
|
+ return match ? match[1] : null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 加载指定 ID 的书籍
|
|
|
|
|
+async function loadBookById(bookId: string) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ currentBook.value = await api.getBook(bookId);
|
|
|
|
|
+ currentView.value = 'detail';
|
|
|
|
|
+ loadQuotaInfo();
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('加载书籍失败:', e);
|
|
|
|
|
+ uni.showToast({ title: '加载失败', icon: 'none' });
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 返回列表
|
|
|
|
|
+function backToList() {
|
|
|
|
|
+ currentView.value = 'list';
|
|
|
|
|
+ loadBooks();
|
|
|
|
|
+ // 清除 URL 参数
|
|
|
|
|
+ if (typeof window !== 'undefined') {
|
|
|
|
|
+ const url = new URL(window.location.href);
|
|
|
|
|
+ url.hash = '/pages/book-generator/index';
|
|
|
|
|
+ window.history.pushState({}, '', url.toString());
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 页面显示时检查 URL 参数
|
|
|
|
|
+onShow(() => {
|
|
|
|
|
+ const bookId = getBookIdFromUrl();
|
|
|
|
|
+ if (bookId && currentView.value === 'list') {
|
|
|
|
|
+ loadBookById(bookId);
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
// 加载用户配额信息
|
|
// 加载用户配额信息
|
|
|
async function loadQuotaInfo() {
|
|
async function loadQuotaInfo() {
|
|
|
try {
|
|
try {
|
|
@@ -950,60 +918,55 @@ async function handleGenerateAfterword() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 显示完整内容 - 现在显示目录
|
|
|
|
|
-async function showFullContent() {
|
|
|
|
|
|
|
+// 跳转到章节详情页
|
|
|
|
|
+function goToChapterDetail(chapter: Chapter) {
|
|
|
if (!currentBook.value) return;
|
|
if (!currentBook.value) return;
|
|
|
-
|
|
|
|
|
- // 刷新书籍详情获取最新章节
|
|
|
|
|
- currentBook.value = await api.getBook(currentBook.value.id);
|
|
|
|
|
- currentView.value = 'toc';
|
|
|
|
|
|
|
+ // 跳转到独立的章节详情页面
|
|
|
|
|
+ uni.navigateTo({
|
|
|
|
|
+ url: `/pages/book-generator/chapter-detail?bookId=${encodeURIComponent(currentBook.value.id)}&chapter=${chapter.number}`
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 打开章节详情
|
|
|
|
|
-function openChapter(chapter: Chapter) {
|
|
|
|
|
- if (chapter.status !== 'completed') {
|
|
|
|
|
- uni.showToast({ title: '该章节尚未生成', icon: 'none' });
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- currentChapter.value = chapter;
|
|
|
|
|
- currentView.value = 'chapter-detail';
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 显示前言
|
|
|
|
|
-function showForeword() {
|
|
|
|
|
- if (!currentBook.value?.metadata?.foreword) {
|
|
|
|
|
- uni.showToast({ title: '前言尚未生成', icon: 'none' });
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- // 创建一个临时章节对象显示前言
|
|
|
|
|
- currentChapter.value = {
|
|
|
|
|
- id: 'foreword',
|
|
|
|
|
- bookId: currentBook.value.id,
|
|
|
|
|
- number: 0,
|
|
|
|
|
|
|
+// 查看前言
|
|
|
|
|
+function viewForeword() {
|
|
|
|
|
+ uni.showModal({
|
|
|
title: '前言',
|
|
title: '前言',
|
|
|
- content: currentBook.value.metadata.foreword,
|
|
|
|
|
- wordCount: currentBook.value.metadata.foreword.length,
|
|
|
|
|
- status: 'completed',
|
|
|
|
|
- };
|
|
|
|
|
- currentView.value = 'chapter-detail';
|
|
|
|
|
|
|
+ content: currentBook.value?.metadata?.foreword || '',
|
|
|
|
|
+ showCancel: true,
|
|
|
|
|
+ cancelText: '关闭',
|
|
|
|
|
+ confirmText: '复制',
|
|
|
|
|
+ success: (res) => {
|
|
|
|
|
+ if (res.confirm) {
|
|
|
|
|
+ uni.setClipboardData({
|
|
|
|
|
+ data: currentBook.value?.metadata?.foreword || '',
|
|
|
|
|
+ success: () => {
|
|
|
|
|
+ uni.showToast({ title: '已复制', icon: 'success' });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 显示后记
|
|
|
|
|
-function showAfterword() {
|
|
|
|
|
- if (!currentBook.value?.metadata?.afterword) {
|
|
|
|
|
- uni.showToast({ title: '后记尚未生成', icon: 'none' });
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- currentChapter.value = {
|
|
|
|
|
- id: 'afterword',
|
|
|
|
|
- bookId: currentBook.value.id,
|
|
|
|
|
- number: currentBook.value.chapters.length + 1,
|
|
|
|
|
|
|
+// 查看后记
|
|
|
|
|
+function viewAfterword() {
|
|
|
|
|
+ uni.showModal({
|
|
|
title: '后记',
|
|
title: '后记',
|
|
|
- content: currentBook.value.metadata.afterword,
|
|
|
|
|
- wordCount: currentBook.value.metadata.afterword.length,
|
|
|
|
|
- status: 'completed',
|
|
|
|
|
- };
|
|
|
|
|
- currentView.value = 'chapter-detail';
|
|
|
|
|
|
|
+ content: currentBook.value?.metadata?.afterword || '',
|
|
|
|
|
+ showCancel: true,
|
|
|
|
|
+ cancelText: '关闭',
|
|
|
|
|
+ confirmText: '复制',
|
|
|
|
|
+ success: (res) => {
|
|
|
|
|
+ if (res.confirm) {
|
|
|
|
|
+ uni.setClipboardData({
|
|
|
|
|
+ data: currentBook.value?.metadata?.afterword || '',
|
|
|
|
|
+ success: () => {
|
|
|
|
|
+ uni.showToast({ title: '已复制', icon: 'success' });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ============ 音频生成 ============
|
|
// ============ 音频生成 ============
|
|
@@ -2007,6 +1970,136 @@ onMounted(() => {
|
|
|
text-align: justify;
|
|
text-align: justify;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* ==================== 完整内容区样式 ==================== */
|
|
|
|
|
+.content-card {
|
|
|
|
|
+ margin-top: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ background: #f9fafb;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ margin-top: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-item.foreword,
|
|
|
|
|
+.content-item.afterword {
|
|
|
|
|
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-item-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-item-left {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 16rpx;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-item-right {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ margin-left: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-label {
|
|
|
|
|
+ font-size: 30rpx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #92400e;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-item.foreword .content-label,
|
|
|
|
|
+.content-item.afterword .content-label {
|
|
|
|
|
+ color: #92400e;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.chapter-num-badge {
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ background: #4f46e5;
|
|
|
|
|
+ padding: 6rpx 12rpx;
|
|
|
|
|
+ border-radius: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.chapter-title-short {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.audio-badge,
|
|
|
|
|
+.video-badge {
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-word-count {
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ color: #9ca3af;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.expand-icon {
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: #9ca3af;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-body {
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ background: #ffffff;
|
|
|
|
|
+ border-radius: 12rpx;
|
|
|
|
|
+ margin-top: 12rpx;
|
|
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-meta {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 16rpx;
|
|
|
|
|
+ padding-bottom: 16rpx;
|
|
|
|
|
+ border-bottom: 1px solid #f3f4f6;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.mini-btn {
|
|
|
|
|
+ padding: 8rpx 16rpx;
|
|
|
|
|
+ border-radius: 8rpx;
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.mini-btn.audio-btn {
|
|
|
|
|
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.mini-btn.video-btn {
|
|
|
|
|
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-text {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ line-height: 1.9;
|
|
|
|
|
+ color: #374151;
|
|
|
|
|
+ text-align: justify;
|
|
|
|
|
+ white-space: pre-wrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/* ==================== 音频生成相关样式 ==================== */
|
|
/* ==================== 音频生成相关样式 ==================== */
|
|
|
|
|
|
|
|
/* 书籍卡片操作区 */
|
|
/* 书籍卡片操作区 */
|
|
@@ -2170,4 +2263,39 @@ onMounted(() => {
|
|
|
font-size: 24rpx;
|
|
font-size: 24rpx;
|
|
|
color: #b45309;
|
|
color: #b45309;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/* 无章节警告 */
|
|
|
|
|
+.warning-tip {
|
|
|
|
|
+ margin-top: 20rpx;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ background: rgba(239, 68, 68, 0.1);
|
|
|
|
|
+ border: 1px solid #fca5a5;
|
|
|
|
|
+ border-radius: 12rpx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.warning-icon {
|
|
|
|
|
+ font-size: 48rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.warning-text {
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ color: #dc2626;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.warning-hint {
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: #991b1b;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.warning-tip .action-btn {
|
|
|
|
|
+ margin-top: 8rpx;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|