|
@@ -50,6 +50,10 @@
|
|
|
<text class="stage-label">当前阶段:</text>
|
|
<text class="stage-label">当前阶段:</text>
|
|
|
<text class="stage-value">{{ getCurrentStage() }}</text>
|
|
<text class="stage-value">{{ getCurrentStage() }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ <view v-if="currentGeneratingChapter" class="current-chapter-info">
|
|
|
|
|
+ <text class="chapter-label">正在处理:</text>
|
|
|
|
|
+ <text class="chapter-name">第{{ currentGeneratingChapter.number }}{{ currentGeneratingChapter.levelLabel }} {{ currentGeneratingChapter.title }}</text>
|
|
|
|
|
+ </view>
|
|
|
<view v-if="currentBook.error" class="status-error">
|
|
<view v-if="currentBook.error" class="status-error">
|
|
|
<text class="error-icon">⚠️</text>
|
|
<text class="error-icon">⚠️</text>
|
|
|
<text class="error-text">{{ currentBook.error }}</text>
|
|
<text class="error-text">{{ currentBook.error }}</text>
|
|
@@ -120,6 +124,26 @@
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
|
|
+ <!-- 章节搜索与折叠工具栏 -->
|
|
|
|
|
+ <view class="chapter-toolbar">
|
|
|
|
|
+ <view class="search-box">
|
|
|
|
|
+ <text class="search-icon-small">🔍</text>
|
|
|
|
|
+ <input
|
|
|
|
|
+ v-model="searchKeyword"
|
|
|
|
|
+ class="search-input"
|
|
|
|
|
+ placeholder="搜索章节标题、摘要..."
|
|
|
|
|
+ placeholder-class="search-placeholder-text"
|
|
|
|
|
+ />
|
|
|
|
|
+ <text v-if="searchKeyword" class="search-clear" @click="searchKeyword = ''">✕</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="toggle-btn" @click="toggleExpandAll">
|
|
|
|
|
+ <text>{{ allExpanded ? '📁 折叠全部' : '📂 展开全部' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="searchKeyword && filteredChaptersLevel1.length === 0" class="search-empty">
|
|
|
|
|
+ <text>未找到匹配的章节</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
<!-- 前言 -->
|
|
<!-- 前言 -->
|
|
|
<view v-if="currentBook?.metadata?.foreword" class="content-item foreword" @click="viewForeword">
|
|
<view v-if="currentBook?.metadata?.foreword" class="content-item foreword" @click="viewForeword">
|
|
|
<view class="content-item-left">
|
|
<view class="content-item-left">
|
|
@@ -134,7 +158,7 @@
|
|
|
<!-- 章节列表 -->
|
|
<!-- 章节列表 -->
|
|
|
<view class="content-tree-list">
|
|
<view class="content-tree-list">
|
|
|
<view
|
|
<view
|
|
|
- v-for="chapter in chaptersLevel1"
|
|
|
|
|
|
|
+ v-for="chapter in filteredChaptersLevel1"
|
|
|
:key="chapter.id"
|
|
:key="chapter.id"
|
|
|
class="content-tree-item content-chapter"
|
|
class="content-tree-item content-chapter"
|
|
|
>
|
|
>
|
|
@@ -168,6 +192,8 @@
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
|
|
+ <!-- 节和小节包裹折叠控制 -->
|
|
|
|
|
+ <view v-show="allExpanded">
|
|
|
<!-- 节 -->
|
|
<!-- 节 -->
|
|
|
<view v-if="getChapterSectionsFromDB(chapter.id) && getChapterSectionsFromDB(chapter.id).length > 0" class="content-sections-list">
|
|
<view v-if="getChapterSectionsFromDB(chapter.id) && getChapterSectionsFromDB(chapter.id).length > 0" class="content-sections-list">
|
|
|
<view
|
|
<view
|
|
@@ -229,6 +255,7 @@
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
@@ -258,13 +285,28 @@
|
|
|
</view>
|
|
</view>
|
|
|
<!-- 音频生成按钮 - 内容生成完成后显示 -->
|
|
<!-- 音频生成按钮 - 内容生成完成后显示 -->
|
|
|
<view v-if="isContentCompleted" class="btn-row">
|
|
<view v-if="isContentCompleted" class="btn-row">
|
|
|
- <button
|
|
|
|
|
- class="action-btn audio-btn"
|
|
|
|
|
- :disabled="generatingAudio"
|
|
|
|
|
- @click="handleGenerateAudio"
|
|
|
|
|
- >
|
|
|
|
|
- {{ generatingAudio ? '生成中...' : '🎵 生成全部音频' }}
|
|
|
|
|
- </button>
|
|
|
|
|
|
|
+ <view class="btn-with-quota">
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-if="hasEnoughQuota"
|
|
|
|
|
+ class="action-btn audio-btn"
|
|
|
|
|
+ :disabled="generatingAudio"
|
|
|
|
|
+ @click="handleGenerateAudio"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ generatingAudio ? '生成中...' : '🎵 生成全部音频' }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-else
|
|
|
|
|
+ class="action-btn upgrade-btn"
|
|
|
|
|
+ @click="goToSubscription"
|
|
|
|
|
+ >
|
|
|
|
|
+ 🚀 升级套餐
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <view class="quota-info-inline">
|
|
|
|
|
+ <text class="quota-text">预估消耗 {{ estimatedAudioMinutes }} 分钟</text>
|
|
|
|
|
+ <text v-if="quotaInfo" class="quota-text">剩余 {{ quotaInfo.remainingMinutes }} 分钟</text>
|
|
|
|
|
+ <text v-if="!hasEnoughQuota" class="quota-warn">⚠️ 配额不足</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
@@ -290,14 +332,19 @@
|
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
|
import { onLoad, onShow } from '@dcloudio/uni-app';
|
|
import { onLoad, onShow } from '@dcloudio/uni-app';
|
|
|
import * as api from '../../utils/book-generator-api';
|
|
import * as api from '../../utils/book-generator-api';
|
|
|
-import { post } from '../../utils/request';
|
|
|
|
|
|
|
+import { post, get } from '../../utils/request';
|
|
|
import type { Book, Chapter } from '../../utils/book-generator-api';
|
|
import type { Book, Chapter } from '../../utils/book-generator-api';
|
|
|
|
|
+import { useNotificationStore } from '../../store/notification';
|
|
|
import GenerationStatusBadge from '../../components/GenerationStatusBadge.vue';
|
|
import GenerationStatusBadge from '../../components/GenerationStatusBadge.vue';
|
|
|
// 批量操作状态
|
|
// 批量操作状态
|
|
|
const isBatchMode = ref(false);
|
|
const isBatchMode = ref(false);
|
|
|
const selectedChapterIds = ref<string[]>([]);
|
|
const selectedChapterIds = ref<string[]>([]);
|
|
|
const isAllSelected = ref(false);
|
|
const isAllSelected = ref(false);
|
|
|
|
|
|
|
|
|
|
+// 章节搜索与折叠
|
|
|
|
|
+const searchKeyword = ref('');
|
|
|
|
|
+const allExpanded = ref(true);
|
|
|
|
|
+
|
|
|
// 当前书籍
|
|
// 当前书籍
|
|
|
const currentBook = ref<Book | null>(null);
|
|
const currentBook = ref<Book | null>(null);
|
|
|
|
|
|
|
@@ -341,6 +388,38 @@ const chaptersLevel1 = computed(() => {
|
|
|
return currentBook.value.chapters.filter((c) => c.level === 1);
|
|
return currentBook.value.chapters.filter((c) => c.level === 1);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+// 当前正在生成的章节信息
|
|
|
|
|
+const currentGeneratingChapter = computed(() => {
|
|
|
|
|
+ if (!currentBook.value?.chapters) return null;
|
|
|
|
|
+ const generating = currentBook.value.chapters.find(
|
|
|
|
|
+ (c: any) => c.genStage === 'content_generating' || c.genStage === 'audio_generating' || c.genStage === 'video_generating' || c.contentStatus === 'generating'
|
|
|
|
|
+ );
|
|
|
|
|
+ if (!generating) return null;
|
|
|
|
|
+ const levelLabels = ['', '章', '节', '小节'];
|
|
|
|
|
+ const levelLabel = levelLabels[generating.level || 1] || '章';
|
|
|
|
|
+ return { number: generating.number, title: generating.title, levelLabel };
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 根据搜索关键词过滤章节
|
|
|
|
|
+const filteredChaptersLevel1 = computed(() => {
|
|
|
|
|
+ const chapters = chaptersLevel1.value;
|
|
|
|
|
+ if (!searchKeyword.value.trim()) return chapters;
|
|
|
|
|
+ const keyword = searchKeyword.value.trim().toLowerCase();
|
|
|
|
|
+ return chapters.filter(ch => {
|
|
|
|
|
+ if (ch.title?.toLowerCase().includes(keyword)) return true;
|
|
|
|
|
+ const outline = getChapterOutline(ch.number);
|
|
|
|
|
+ if (outline?.summary?.toLowerCase().includes(keyword)) return true;
|
|
|
|
|
+ if (outline?.keyPoints?.some((kp: string) => kp.toLowerCase().includes(keyword))) return true;
|
|
|
|
|
+ const sections = getChapterSectionsFromDB(ch.id);
|
|
|
|
|
+ if (sections.some((s: any) => s.title?.toLowerCase().includes(keyword) || s.summary?.toLowerCase().includes(keyword))) return true;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+function toggleExpandAll() {
|
|
|
|
|
+ allExpanded.value = !allExpanded.value;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const usedAudioMinutes = computed(() => {
|
|
const usedAudioMinutes = computed(() => {
|
|
|
if (!currentBook.value) return 0;
|
|
if (!currentBook.value) return 0;
|
|
|
const totalWords = currentBook.value.chapters
|
|
const totalWords = currentBook.value.chapters
|
|
@@ -349,6 +428,51 @@ const usedAudioMinutes = computed(() => {
|
|
|
return Math.ceil(totalWords / 150);
|
|
return Math.ceil(totalWords / 150);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+// 预估全部音频时长(基于所有叶子节点字数)
|
|
|
|
|
+const estimatedAudioMinutes = computed(() => {
|
|
|
|
|
+ if (!currentBook.value) return 0;
|
|
|
|
|
+ const leafLevel = getBookLeafLevel(currentBook.value);
|
|
|
|
|
+ const leafNodes = getBookLeafNodes(currentBook.value, leafLevel);
|
|
|
|
|
+ const totalWords = leafNodes.reduce((sum, c: any) => sum + (c.wordCount || 0), 0);
|
|
|
|
|
+ return Math.max(1, Math.ceil(totalWords / 150));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 配额是否足够
|
|
|
|
|
+const hasEnoughQuota = computed(() => {
|
|
|
|
|
+ if (!quotaInfo.value) return true; // 未加载配额信息时不限制
|
|
|
|
|
+ const remaining = quotaInfo.value.remainingMinutes;
|
|
|
|
|
+ // 如果启用了超额,总是允许
|
|
|
|
|
+ if (quotaInfo.value.overageEnabled) return true;
|
|
|
|
|
+ return remaining >= estimatedAudioMinutes.value;
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+function goToSubscription() {
|
|
|
|
|
+ uni.navigateTo({ url: '/pages/subscription/index' });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取用户音频配额信息
|
|
|
|
|
+async function fetchQuotaInfo() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await get<{
|
|
|
|
|
+ totalMinutes: number;
|
|
|
|
|
+ usedMinutes: number;
|
|
|
|
|
+ remainingMinutes: number;
|
|
|
|
|
+ overageEnabled: boolean;
|
|
|
|
|
+ }>('/subscription/audio-balance');
|
|
|
|
|
+ if (res) {
|
|
|
|
|
+ quotaInfo.value = {
|
|
|
|
|
+ totalMinutes: res.totalMinutes,
|
|
|
|
|
+ usedMinutes: res.usedMinutes,
|
|
|
|
|
+ remainingMinutes: res.remainingMinutes,
|
|
|
|
|
+ overageEnabled: res.overageEnabled,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // 未登录或获取失败时不显示配额信息
|
|
|
|
|
+ quotaInfo.value = null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const remainingAudioMinutes = computed(() => {
|
|
const remainingAudioMinutes = computed(() => {
|
|
|
if (!quotaInfo.value) return 0;
|
|
if (!quotaInfo.value) return 0;
|
|
|
return Math.max(0, quotaInfo.value.totalMinutes - quotaInfo.value.usedMinutes);
|
|
return Math.max(0, quotaInfo.value.totalMinutes - quotaInfo.value.usedMinutes);
|
|
@@ -533,6 +657,10 @@ function startPollingProgress(bookId: string) {
|
|
|
currentBook.value = book;
|
|
currentBook.value = book;
|
|
|
if (book.genStage === 'content_completed' || book.genStage === 'audio_completed' || book.genStage === 'video_completed') {
|
|
if (book.genStage === 'content_completed' || book.genStage === 'audio_completed' || book.genStage === 'video_completed') {
|
|
|
uni.showToast({ title: '生成完成!', icon: 'success' });
|
|
uni.showToast({ title: '生成完成!', icon: 'success' });
|
|
|
|
|
+ // 写入通知
|
|
|
|
|
+ const notifStore = useNotificationStore();
|
|
|
|
|
+ const stageLabel = book.genStage === 'content_completed' ? '内容' : book.genStage === 'audio_completed' ? '音频' : '视频';
|
|
|
|
|
+ notifStore.add({ type: 'content_complete', title: `${stageLabel}生成完成`, message: `《${currentBook.value?.title || '书籍'}》${stageLabel}已生成完毕`, bookId });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
@@ -558,6 +686,9 @@ function startPollingAudioStatus(bookId: string) {
|
|
|
generatingAudio.value = false;
|
|
generatingAudio.value = false;
|
|
|
await loadBook(bookId);
|
|
await loadBook(bookId);
|
|
|
uni.showToast({ title: '音频生成完成', icon: 'success' });
|
|
uni.showToast({ title: '音频生成完成', icon: 'success' });
|
|
|
|
|
+ // 写入通知
|
|
|
|
|
+ const notifStore = useNotificationStore();
|
|
|
|
|
+ notifStore.add({ type: 'audio_complete', title: '音频生成完成', message: `《${currentBook.value?.title || '书籍'}》音频已生成完毕`, bookId });
|
|
|
}
|
|
}
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
console.error('轮询音频状态失败:', e);
|
|
console.error('轮询音频状态失败:', e);
|
|
@@ -582,6 +713,9 @@ function startPollingVideoStatus(bookId: string) {
|
|
|
generatingVideo.value = false;
|
|
generatingVideo.value = false;
|
|
|
await loadBook(bookId);
|
|
await loadBook(bookId);
|
|
|
uni.showToast({ title: '视频生成完成', icon: 'success' });
|
|
uni.showToast({ title: '视频生成完成', icon: 'success' });
|
|
|
|
|
+ // 写入通知
|
|
|
|
|
+ const notifStore = useNotificationStore();
|
|
|
|
|
+ notifStore.add({ type: 'video_complete', title: '视频生成完成', message: `《${currentBook.value?.title || '书籍'}》视频已生成完毕`, bookId });
|
|
|
}
|
|
}
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
console.error('轮询视频状态失败:', e);
|
|
console.error('轮询视频状态失败:', e);
|
|
@@ -785,6 +919,8 @@ onMounted(() => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
// #endif
|
|
// #endif
|
|
|
|
|
+ // 获取配额信息
|
|
|
|
|
+ fetchQuotaInfo();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
onShow(() => {
|
|
onShow(() => {
|
|
@@ -792,6 +928,8 @@ onShow(() => {
|
|
|
if (currentBook.value?.id) {
|
|
if (currentBook.value?.id) {
|
|
|
loadBook(currentBook.value.id);
|
|
loadBook(currentBook.value.id);
|
|
|
}
|
|
}
|
|
|
|
|
+ // 刷新配额信息
|
|
|
|
|
+ fetchQuotaInfo();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
onUnmounted(() => {
|
|
@@ -887,6 +1025,9 @@ onUnmounted(() => {
|
|
|
.status-stage { display: flex; align-items: center; padding: 16rpx; background: rgba(255, 255, 255, 0.2); border-radius: 12rpx; margin-bottom: 16rpx; }
|
|
.status-stage { display: flex; align-items: center; padding: 16rpx; background: rgba(255, 255, 255, 0.2); border-radius: 12rpx; margin-bottom: 16rpx; }
|
|
|
.stage-label { font-size: 26rpx; opacity: 0.9; margin-right: 8rpx; }
|
|
.stage-label { font-size: 26rpx; opacity: 0.9; margin-right: 8rpx; }
|
|
|
.stage-value { font-size: 28rpx; font-weight: 600; }
|
|
.stage-value { font-size: 28rpx; font-weight: 600; }
|
|
|
|
|
+.current-chapter-info { display: flex; align-items: center; padding: 16rpx; background: rgba(255, 255, 255, 0.15); border-radius: 12rpx; margin-bottom: 16rpx; }
|
|
|
|
|
+.chapter-label { font-size: 24rpx; opacity: 0.8; margin-right: 8rpx; flex-shrink: 0; }
|
|
|
|
|
+.chapter-name { font-size: 26rpx; font-weight: 600; flex: 1; }
|
|
|
.status-error { display: flex; align-items: flex-start; padding: 16rpx; background: rgba(239, 68, 68, 0.3); border-radius: 12rpx; margin-bottom: 16rpx; }
|
|
.status-error { display: flex; align-items: flex-start; padding: 16rpx; background: rgba(239, 68, 68, 0.3); border-radius: 12rpx; margin-bottom: 16rpx; }
|
|
|
.error-icon { font-size: 28rpx; margin-right: 12rpx; flex-shrink: 0; }
|
|
.error-icon { font-size: 28rpx; margin-right: 12rpx; flex-shrink: 0; }
|
|
|
.error-text { font-size: 24rpx; line-height: 1.6; flex: 1; }
|
|
.error-text { font-size: 24rpx; line-height: 1.6; flex: 1; }
|
|
@@ -906,6 +1047,77 @@ onUnmounted(() => {
|
|
|
|
|
|
|
|
.outline-theme { font-size: 24rpx; color: #6b7280; }
|
|
.outline-theme { font-size: 24rpx; color: #6b7280; }
|
|
|
|
|
|
|
|
|
|
+/* 章节搜索工具栏 */
|
|
|
|
|
+.chapter-toolbar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 16rpx;
|
|
|
|
|
+ margin-top: 20rpx;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-box {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ background: #f3f4f6;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ padding: 12rpx 20rpx;
|
|
|
|
|
+ border: 2rpx solid transparent;
|
|
|
|
|
+ transition: border-color 0.2s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-box:focus-within {
|
|
|
|
|
+ border-color: #4f46e5;
|
|
|
|
|
+ background: #ffffff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-icon-small {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ margin-right: 12rpx;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-input {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-placeholder-text {
|
|
|
|
|
+ color: #9ca3af;
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-clear {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: #9ca3af;
|
|
|
|
|
+ padding: 4rpx 8rpx;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.toggle-btn {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ padding: 12rpx 20rpx;
|
|
|
|
|
+ background: #f3f4f6;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: #4f46e5;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.toggle-btn:active {
|
|
|
|
|
+ background: #e5e7eb;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-empty {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: 40rpx;
|
|
|
|
|
+ color: #9ca3af;
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.content-item { display: flex; align-items: center; justify-content: space-between; padding: 24rpx; background: #f9fafb; border-radius: 16rpx; margin-top: 16rpx; }
|
|
.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.foreword, .content-item.afterword { background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); cursor: pointer; }
|
|
|
.content-item-left { display: flex; align-items: center; gap: 16rpx; }
|
|
.content-item-left { display: flex; align-items: center; gap: 16rpx; }
|
|
@@ -973,6 +1185,11 @@ onUnmounted(() => {
|
|
|
.action-btn[disabled] { opacity: 0.6; }
|
|
.action-btn[disabled] { opacity: 0.6; }
|
|
|
.action-btn.langgraph-btn { background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: #ffffff; }
|
|
.action-btn.langgraph-btn { background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: #ffffff; }
|
|
|
.action-btn.audio-btn { background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); color: #ffffff; }
|
|
.action-btn.audio-btn { background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); color: #ffffff; }
|
|
|
|
|
+.action-btn.upgrade-btn { background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); color: #ffffff; }
|
|
|
|
|
+.btn-with-quota { display: flex; flex-direction: column; gap: 8rpx; width: 100%; }
|
|
|
|
|
+.quota-info-inline { display: flex; flex-wrap: wrap; gap: 12rpx; padding: 8rpx 0; }
|
|
|
|
|
+.quota-text { font-size: 22rpx; color: #6b7280; }
|
|
|
|
|
+.quota-warn { font-size: 22rpx; color: #ef4444; font-weight: 600; }
|
|
|
.generating-tip { text-align: center; padding: 20rpx; color: #6b7280; font-size: 26rpx; }
|
|
.generating-tip { text-align: center; padding: 20rpx; color: #6b7280; font-size: 26rpx; }
|
|
|
|
|
|
|
|
.warning-tip { padding: 24rpx; background: #fef3c7; border-radius: 12rpx; margin-top: 16rpx; }
|
|
.warning-tip { padding: 24rpx; background: #fef3c7; border-radius: 12rpx; margin-top: 16rpx; }
|