| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996 |
- <template>
- <view class="page">
- <!-- 顶部导航栏 -->
- <view class="nav-bar">
- <view class="nav-content">
- <view class="nav-left" @click="goBack">
- <text class="back-icon">←</text>
- </view>
- <text class="page-title">创建书籍</text>
- </view>
- <!-- 模式切换独立行 -->
- <view class="mode-bar">
- <text class="mode-switch" @click="switchToInteractive">切换到交互模式</text>
- </view>
- </view>
- <!-- 主内容区 -->
- <view class="main-content">
- <view class="card">
- <view class="card-header">
- <text class="card-title">📖 创建新书籍</text>
- </view>
- <!-- 简洁模式提示 -->
- <view class="simple-mode-tip">
- <view class="tip-header">
- <text class="tip-icon">💡</text>
- <text class="tip-title">简洁模式</text>
- </view>
- <text class="tip-content">
- 先填写基本信息创建书籍,高级选项可在创建后修改。
- </text>
- </view>
- <!-- 书名:由AI根据描述自动生成,无需手动输入 -->
- <!-- 描述/主题 -->
- <view class="form-item">
- <text class="form-label">内容描述 *</text>
- <textarea
- v-model="newBook.description"
- class="form-textarea"
- placeholder="描述这本书的内容、主题、写作目的..."
- :maxlength="500"
- @blur="detectScaleFromDesc"
- />
- </view>
- <!-- 面向人群 -->
- <view class="form-item highlight-item">
- <view class="label-with-badge">
- <text class="form-label">面向人群</text>
- <view class="required-badge">重要</view>
- </view>
- <text class="form-hint">选择目标读者,AI会根据人群调整语言风格和表达方式</text>
- <view class="chip-group">
- <view
- v-for="audience in audiences"
- :key="audience.value"
- :class="['chip', 'audience-chip', newBook.targetAudience === audience.value ? 'active' : '']"
- @click="newBook.targetAudience = audience.value"
- >
- <text class="chip-icon">{{ audience.icon }}</text>
- <text class="chip-text">{{ audience.label }}</text>
- </view>
- </view>
- </view>
- <!-- 书籍类型 -->
- <view class="form-item">
- <view class="label-with-badge">
- <text class="form-label">书籍类型</text>
- <view class="required-badge" style="background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);">影响大纲结构</view>
- </view>
- <text class="form-hint-small">选择类型决定大纲层级。不选则由AI自动判断</text>
- <view class="chip-group" style="margin-top: 8rpx;">
- <view
- v-for="bt in bookTypes"
- :key="bt.value"
- :class="['chip', newBook.bookType === bt.value ? 'active' : '']"
- @click="newBook.bookType = bt.value"
- >
- <text class="chip-icon">{{ bt.icon }}</text>
- <text class="chip-text">{{ bt.label }}</text>
- <text class="chip-desc" style="font-size: 20rpx; margin-left: 4rpx;">{{ bt.desc }}</text>
- </view>
- </view>
- </view>
- <!-- 书籍规模 -->
- <view class="form-item">
- <text class="form-label">书籍规模 *</text>
- <view class="scale-picker">
- <view
- v-for="scale in bookScales"
- :key="scale.value"
- :class="['scale-option', { active: newBook.bookScale === scale.value }]"
- @click="selectBookScale(scale.value)"
- >
- <text class="scale-words">{{ scale.words }}</text>
- <text class="scale-label">{{ scale.label }} {{ scale.chapters }}</text>
- <text class="scale-audio">{{ scale.audio }}</text>
- </view>
- </view>
- </view>
- <!-- 知识难度 -->
- <view class="form-item highlight-item">
- <view class="label-with-badge">
- <text class="form-label">知识难度 *</text>
- </view>
- <view class="chip-group">
- <view
- v-for="level in knowledgeLevels"
- :key="level.value"
- :class="['chip', 'level-chip', newBook.knowledgeLevel === level.value ? 'active' : '']"
- @click="newBook.knowledgeLevel = level.value"
- >
- <text class="chip-icon">{{ level.icon }}</text>
- <text class="chip-text">{{ level.label }}</text>
- </view>
- </view>
- </view>
- <!-- 智能推荐按钮 -->
- <view class="form-item smart-recommend-section">
- <button
- class="btn-smart-recommend"
- :disabled="isRecommending || !newBook.description.trim()"
- @click="requestSmartRecommend"
- >
- <text class="recommend-icon">{{ isRecommending ? '⏳' : '✨' }}</text>
- <text class="recommend-text">{{ isRecommending ? '推荐中...' : '智能推荐' }}</text>
- </button>
- <text class="form-hint-small">根据内容描述,AI推荐难度/人群/风格/领域</text>
- </view>
- <!-- 高级选项折叠区 -->
- <view class="advanced-section">
- <view class="advanced-header" @click="showAdvancedOptions = !showAdvancedOptions">
- <text class="advanced-title">高级选项</text>
- <text class="advanced-arrow">{{ showAdvancedOptions ? '▲' : '▼' }}</text>
- </view>
- <!-- 折叠内容 -->
- <view v-if="showAdvancedOptions" class="advanced-content">
- <!-- 副标题 -->
- <view class="form-item">
- <text class="form-label">副标题</text>
- <input
- v-model="newBook.subtitle"
- class="form-input"
- placeholder="例如:一本写给青少年的科普书"
- />
- </view>
- <!-- 快速模板 -->
- <view class="form-item">
- <text class="form-label">📋 快速模板</text>
- <text class="form-hint-small">点击模板自动填充所有配置,也可手动修改</text>
- <view class="template-grid">
- <view
- v-for="template in quickTemplates"
- :key="template.name"
- class="template-card"
- @click="applyTemplate(template)"
- >
- <text class="template-icon">{{ template.icon }}</text>
- <text class="template-name">{{ template.name }}</text>
- <text class="template-desc">{{ template.desc }}</text>
- </view>
- </view>
- </view>
- <!-- 重要提示 -->
- <view class="important-tip">
- <view class="tip-header">
- <text class="tip-icon">💡</text>
- <text class="tip-title">重要提示</text>
- </view>
- <text class="tip-content">
- 知识难度和面向人群会显著影响生成内容的风格和深度。同样的知识,面向不同人群,表达方式完全不同:
- </text>
- <view class="tip-examples">
- <text class="tip-example">• 面向小学生:形象、简单、通俗易懂</text>
- <text class="tip-example">• 面向大学生:系统、专业、有理论深度</text>
- <text class="tip-example">• 面向研究生:前沿、深入、有研究价值</text>
- </view>
- </view>
- <!-- 写作风格 -->
- <view class="form-item">
- <text class="form-label">写作风格</text>
- <view class="chip-group">
- <view
- v-for="style in styles"
- :key="style"
- :class="['chip', newBook.style === style ? 'active' : '']"
- @click="newBook.style = style"
- >
- {{ style }}
- </view>
- </view>
- </view>
- <!-- 行业领域 -->
- <view class="form-item">
- <text class="form-label">行业领域</text>
- <text class="form-hint-small">选择行业领域,AI会使用相关的案例和术语</text>
- <view class="chip-group">
- <view
- v-for="industry in industries"
- :key="industry.value"
- :class="['chip', newBook.industry === industry.value ? 'active' : '']"
- @click="newBook.industry = industry.value"
- >
- <text class="chip-icon">{{ industry.icon }}</text>
- <text class="chip-text">{{ industry.label }}</text>
- </view>
- </view>
- </view>
- <!-- 特殊要求 (多选) -->
- <view class="form-item">
- <text class="form-label">特殊要求</text>
- <text class="form-hint-small">可多选,AI会在生成时包含这些元素</text>
- <view class="chip-group">
- <view
- v-for="feature in specialFeatures"
- :key="feature.value"
- :class="['chip', 'feature-chip', newBook.specialFeatures.includes(feature.value) ? 'active' : '']"
- @click="toggleSpecialFeature(feature.value)"
- >
- <text class="chip-icon">{{ feature.icon }}</text>
- <text class="chip-text">{{ feature.label }}</text>
- </view>
- </view>
- </view>
- </view>
- </view>
- <!-- 大纲层级 -->
- <view class="form-item">
- <view class="label-with-badge">
- <text class="form-label">大纲层级</text>
- <view class="required-badge" style="background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);">高级</view>
- </view>
- <text class="form-hint-small">控制章节结构的详细程度,默认"自动"会根据书籍规模智能选择</text>
- <view class="chip-group" style="margin-top: 8rpx;">
- <view
- v-for="opt in outlineLevelOptions"
- :key="opt.value"
- :class="['chip', 'level-chip', newBook.outlineLevel === opt.value ? 'active' : '']"
- @click="newBook.outlineLevel = opt.value"
- >
- <text class="chip-text">{{ opt.label }}</text>
- <text class="chip-desc" style="font-size: 20rpx; color: #9ca3af; margin-left: 4rpx;">{{ opt.desc }}</text>
- </view>
- </view>
- </view>
- <!-- 预估信息(选择规模后显示) -->
- <view v-if="bookEstimate && bookEstimate.words && bookEstimate.audioMinutes" class="estimate-card">
- <view class="estimate-header">
- <text class="estimate-title">📊 生成预估</text>
- </view>
- <view class="estimate-row">
- <text class="estimate-label">预估字数:</text>
- <text class="estimate-value">{{ bookEstimate.words.min }}~{{ bookEstimate.words.max }}字</text>
- </view>
- <view class="estimate-row">
- <text class="estimate-label">预估时长:</text>
- <text class="estimate-value">{{ bookEstimate.audioMinutes.min }}~{{ bookEstimate.audioMinutes.max }}分钟</text>
- </view>
- <view class="estimate-row">
- <text class="estimate-label">预估章节:</text>
- <text class="estimate-value">约{{ bookEstimate.estimatedChapters }}章</text>
- </view>
- <view v-if="bookEstimate.genLevelInfo" class="estimate-row genlevel-row">
- <text class="estimate-label">大纲层级:</text>
- <view class="genlevel-tag" :class="'level-' + bookEstimate.defaultGenLevel">
- <text class="genlevel-label">{{ bookEstimate.genLevelInfo.label }}</text>
- <text class="genlevel-desc">{{ bookEstimate.genLevelInfo.desc }}</text>
- </view>
- </view>
- <!-- 配额检查结果 -->
- <view v-if="quotaCheck" class="quota-check">
- <view v-if="quotaCheck.allowed" class="quota-ok">
- <text class="quota-icon">✅</text>
- <text class="quota-text">额度充足,可生成</text>
- </view>
- <view v-else class="quota-warning">
- <text class="quota-icon">⚠️</text>
- <text class="quota-text">{{ quotaCheck.reason }}</text>
- </view>
- <!-- 详细配额信息 -->
- <view class="quota-detail">
- <text class="quota-info">您当前额度:{{ quotaCheck.quota.remainingMinutes }}分钟 / {{ quotaCheck.quota.totalMinutes }}分钟</text>
- <view v-if="quotaCheck.costEstimate.overageMinutes > 0" class="quota-overage">
- <text>预计超出:{{ quotaCheck.costEstimate.overageMinutes }}分钟</text>
- <text class="quota-price">额外费用:¥{{ quotaCheck.costEstimate.estimatedPrice }}</text>
- </view>
- </view>
- </view>
- </view>
- <view class="btn-group">
- <button class="btn-cancel" @click="goBack">取消</button>
- <button
- class="btn-primary"
- :disabled="!canCreateBook || creating"
- @click="createNewBook"
- >
- {{ creating ? '创建中...' : '创建书籍' }}
- </button>
- </view>
- </view>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { ref, computed, onMounted } from 'vue';
- import { onLoad } from '@dcloudio/uni-app';
- import * as api from '../../utils/book-generator-api';
- import { getApiBaseUrl } from '../../utils/config';
- const BASE_URL = getApiBaseUrl();
- // 创建表单
- const creating = ref(false);
- const showAdvancedOptions = ref(false);
- const isRecommending = ref(false);
- const newBook = ref({
- title: '',
- subtitle: '',
- description: '',
- targetAudience: '',
- knowledgeLevel: '',
- industry: '',
- specialFeatures: [] as string[],
- style: '',
- bookScale: '',
- bookType: 'auto',
- outlineLevel: 'auto',
- });
- // 书籍预估信息
- const bookEstimate = ref<{
- scale: string;
- words: { min: number; max: number; avg: number };
- audioMinutes: { min: number; max: number; avg: number };
- estimatedChapters: number;
- defaultGenLevel?: number;
- genLevelInfo?: { label: string; desc: string };
- } | null>(null);
- // 配额检查结果
- const quotaCheck = ref<{
- allowed: boolean;
- reason: string | null;
- estimatedWords: { min: number; max: number; avg: number };
- estimatedAudioMinutes: { min: number; max: number; avg: number };
- quota: {
- totalMinutes: number;
- usedMinutes: number;
- remainingMinutes: number;
- overageEnabled: boolean;
- overagePrice: number;
- };
- costEstimate: {
- inQuotaMinutes: number;
- overageMinutes: number;
- estimatedPrice: number;
- displayText: string;
- };
- } | null>(null);
- // 选项配置
- const knowledgeLevels = [
- { value: '入门', label: '入门级', icon: '🌱', desc: '零基础,简单易懂' },
- { value: '基础', label: '基础级', icon: '📚', desc: '基本概念,循序渐进' },
- { value: '进阶', label: '进阶级', icon: '📈', desc: '深入讲解,系统全面' },
- { value: '高级', label: '高级级', icon: '🎯', desc: '专业深入,前沿技术' },
- { value: '专家', label: '专家级', icon: '🔬', desc: '研究级别,底层原理' },
- ];
- const audiences = [
- { value: '儿童', label: '儿童', icon: '👶', desc: '6-12岁,形象生动' },
- { value: '青少年', label: '青少年', icon: '🧑', desc: '13-18岁,通俗易懂' },
- { value: '大学生', label: '大学生', icon: '🎓', desc: '系统专业,有理论' },
- { value: '专业人士', label: '专业人士', icon: '💼', desc: '从业者,实用深入' },
- { value: '研究生', label: '研究生', icon: '🔍', desc: '研究级别,前沿深入' },
- { value: '大众读者', label: '大众读者', icon: '👥', desc: '通俗普及,有趣味' },
- ];
- const industries = [
- { value: '', label: '不选', icon: '➖', desc: '不限定行业领域' },
- { value: 'IT/计算机', label: 'IT/计算机', icon: '💻', desc: '编程、软件、互联网' },
- { value: '金融', label: '金融', icon: '💰', desc: '投资、银行、证券' },
- { value: '医学', label: '医学', icon: '🏥', desc: '医疗、健康、临床' },
- { value: '教育', label: '教育', icon: '📚', desc: '教学、学习、心理学' },
- { value: '文学', label: '文学', icon: '✍️', desc: '小说、散文、传记' },
- { value: '科普', label: '科普', icon: '🔬', desc: '科学、自然、探索' },
- { value: '商业', label: '商业', icon: '📊', desc: '管理、营销、创业' },
- { value: '艺术', label: '艺术', icon: '🎨', desc: '设计、音乐、影视' },
- ];
- const specialFeatures = [
- { value: '案例分析', label: '案例分析', icon: '📖' },
- { value: '代码示例', label: '代码示例', icon: '💻' },
- { value: '习题练习', label: '习题练习', icon: '✍️' },
- { value: '图表插图', label: '图表插图', icon: '📊' },
- { value: '项目实战', label: '项目实战', icon: '🎯' },
- { value: '思维导图', label: '思维导图', icon: '🧠' },
- { value: '总结回顾', label: '总结回顾', icon: '📝' },
- ];
- const quickTemplates = [
- {
- name: '大学教材',
- icon: '🎓',
- desc: '面向大学生,系统专业,有案例习题',
- config: {
- knowledgeLevel: '进阶',
- targetAudience: '大学生',
- industry: '教育',
- specialFeatures: ['案例分析', '习题练习', '总结回顾'],
- style: '专业严谨',
- },
- },
- {
- name: '技术教程',
- icon: '💻',
- desc: '面向初学者,有代码示例和实战项目',
- config: {
- knowledgeLevel: '入门',
- targetAudience: '初学者',
- industry: 'IT/计算机',
- specialFeatures: ['代码示例', '项目实战', '案例分析'],
- style: '通俗易懂',
- },
- },
- {
- name: '科普读物',
- icon: '🔬',
- desc: '面向大众,通俗易懂,有趣味性',
- config: {
- knowledgeLevel: '入门',
- targetAudience: '大众读者',
- industry: '科普',
- specialFeatures: ['案例分析', '图表插图'],
- style: '轻松幽默',
- },
- },
- {
- name: '商业书籍',
- icon: '📊',
- desc: '面向从业者,有真实案例,实用性强',
- config: {
- knowledgeLevel: '进阶',
- targetAudience: '专业人士',
- industry: '商业',
- specialFeatures: ['案例分析', '总结回顾'],
- style: '专业严谨',
- },
- },
- {
- name: '儿童读物',
- icon: '👶',
- desc: '面向儿童,形象生动,有趣味性',
- config: {
- knowledgeLevel: '入门',
- targetAudience: '儿童',
- industry: '教育',
- specialFeatures: ['图表插图'],
- style: '轻松幽默',
- },
- },
- {
- name: '学术研究',
- icon: '🔍',
- desc: '面向研究生,前沿深入,有研究价值',
- config: {
- knowledgeLevel: '专家',
- targetAudience: '研究生',
- industry: 'IT/计算机',
- specialFeatures: ['案例分析', '总结回顾'],
- style: '专业严谨',
- },
- },
- ];
- const styles = ['不填', '通俗易懂', '专业严谨', '轻松幽默', '诗意优美', '故事化'];
- const bookScales = [
- { value: '340000', label: '上2', words: '约34万字', chapters: '170章', audio: '约28小时' },
- { value: '210000', label: '上1', words: '约21万字', chapters: '105章', audio: '约17小时' },
- { value: '130000', label: '标准', words: '约13万字', chapters: '65章', audio: '约11小时' },
- { value: '80000', label: '下1', words: '约8万字', chapters: '40章', audio: '约7小时' },
- { value: '50000', label: '下2', words: '约5万字', chapters: '25章', audio: '约4小时' },
- { value: '31000', label: '下3', words: '约3.1万字', chapters: '16章', audio: '约2.5小时' },
- { value: '19000', label: '下4', words: '约1.9万字', chapters: '10章', audio: '约1.5小时' },
- { value: '12000', label: '下5', words: '约1.2万字', chapters: '6章', audio: '约1小时' },
- { value: '7000', label: '下6', words: '约7千字', chapters: '4章', audio: '约35分钟' },
- { value: '4000', label: '下7', words: '约4千字', chapters: '2章', audio: '约20分钟' },
- { value: '3000', label: '下8', words: '约3千字', chapters: '2章', audio: '约15分钟' },
- { value: '2000', label: '下9', words: '约2千字', chapters: '1章', audio: '约10分钟' },
- { value: '1000', label: '下10', words: '约1千字', chapters: '1章', audio: '约5分钟' },
- ];
- // 书籍类型选项
- const bookTypes = [
- { value: 'auto', label: '自动检测', icon: '🤖', desc: 'AI判断类型决定大纲' },
- { value: '教材', label: '教材/学术', icon: '📚', desc: '3层大纲' },
- { value: '技术教程', label: '技术教程', icon: '💻', desc: '3层大纲' },
- { value: '小说', label: '小说/文学', icon: '📖', desc: '1层大纲' },
- { value: '商业', label: '商业/经管', icon: '📊', desc: '2层大纲' },
- { value: '科普', label: '科普/大众', icon: '🔬', desc: '2层大纲' },
- ];
- // 大纲层级选项
- const outlineLevelOptions = [
- { value: 'auto', label: '自动', desc: '根据规模自动选择' },
- { value: '1', label: '1层', desc: '仅章,无节和小节' },
- { value: '2', label: '2层', desc: '章→节,有节无小节' },
- { value: '3', label: '3层', desc: '章→节→小节,完整三层' },
- ];
- // 计算属性
- const canCreateBook = computed(() => {
- if (quotaCheck.value && !quotaCheck.value.allowed) {
- return false;
- }
- return !!newBook.value.description.trim();
- });
- // 切换特殊要求
- function toggleSpecialFeature(feature: string) {
- const features = newBook.value.specialFeatures;
- const index = features.indexOf(feature);
- if (index > -1) {
- features.splice(index, 1);
- } else {
- features.push(feature);
- }
- }
- // 应用模板
- function applyTemplate(template: typeof quickTemplates[0]) {
- const config = template.config;
- newBook.value.knowledgeLevel = config.knowledgeLevel;
- newBook.value.targetAudience = config.targetAudience;
- newBook.value.industry = config.industry;
- newBook.value.specialFeatures = [...config.specialFeatures];
- newBook.value.style = config.style;
- // 自动展开高级选项
- showAdvancedOptions.value = true;
- uni.showToast({
- title: `已应用${template.name}模板`,
- icon: 'success',
- });
- }
- // 智能推荐
- async function requestSmartRecommend() {
- if (!newBook.value.description.trim() || isRecommending.value) return;
- isRecommending.value = true;
- try {
- const response = await uni.request({
- url: `${BASE_URL}/book-generator/langgraph/smart-recommend`,
- method: 'POST',
- data: {
- description: newBook.value.description.trim(),
- title: newBook.value.title.trim(),
- },
- });
- const res = response.data as any;
- if (res.code === 0 && res.data) {
- const rec = res.data;
- if (rec.knowledgeLevel) {
- newBook.value.knowledgeLevel = rec.knowledgeLevel;
- }
- if (rec.targetAudience) {
- newBook.value.targetAudience = rec.targetAudience;
- }
- if (rec.style) {
- newBook.value.style = rec.style;
- }
- if (rec.industry) {
- newBook.value.industry = rec.industry;
- }
- // 自动展开高级选项显示推荐结果
- showAdvancedOptions.value = true;
- uni.showToast({
- title: '推荐成功',
- icon: 'success',
- });
- } else {
- uni.showToast({
- title: res.message || '推荐失败',
- icon: 'none',
- });
- }
- } catch (e: any) {
- console.error('智能推荐失败:', e);
- uni.showToast({
- title: '推荐服务暂不可用',
- icon: 'none',
- });
- } finally {
- isRecommending.value = false;
- }
- }
- // 选择书籍规模时加载预估
- async function selectBookScale(scale: string) {
- newBook.value.bookScale = scale;
- try {
- const bt = newBook.value.bookType;
- const bookTypeParam = bt && bt !== 'auto' ? `&bookType=${encodeURIComponent(bt)}` : '';
- const response = await uni.request({
- url: `${BASE_URL}/book-generator/langgraph/estimate?scale=${encodeURIComponent(scale)}&userId=1${bookTypeParam}`,
- method: 'GET'
- });
- const res = response.data as any;
- if (res.code === 0 && res.data && res.data.words && res.data.audioMinutes) {
- bookEstimate.value = {
- scale: res.data.scale,
- words: res.data.words,
- audioMinutes: res.data.audioMinutes,
- estimatedChapters: res.data.estimatedChapters
- };
- if (res.data.quotaCheck) {
- quotaCheck.value = res.data.quotaCheck;
- }
- } else {
- console.warn('预估数据不完整:', res);
- bookEstimate.value = null;
- }
- } catch (e) {
- console.error('加载预估失败:', e);
- bookEstimate.value = null;
- quotaCheck.value = null;
- }
- }
- function switchToInteractive() {
- uni.redirectTo({ url: '/pages/book-generator/interactive' });
- }
- // AI 预览检测规模(用户未手动选时,描述失焦自动触发)
- let detectTimer: any = null;
- async function detectScaleFromDesc() {
- const desc = newBook.value.description?.trim();
- if (!desc || newBook.value.bookScale) return; // 已手动选过,不覆盖
- clearTimeout(detectTimer);
- detectTimer = setTimeout(async () => {
- try {
- const response = await uni.request({
- url: `${BASE_URL}/book-generator/langgraph/detect-scale`,
- method: 'POST',
- data: { description: desc }
- });
- const res = response.data as any;
- if (res.code === 0 && res.data) {
- newBook.value.bookScale = res.data.bookScale;
- bookEstimate.value = {
- scale: res.data.bookScale,
- words: { min: res.data.totalWords, max: res.data.totalWords, avg: res.data.totalWords },
- audioMinutes: { min: 0, max: 0, avg: 0 },
- estimatedChapters: res.data.chapters,
- };
- }
- } catch { /* AI 调用失败不影响用户操作 */ }
- }, 800);
- }
- function goBack() {
- const pages = getCurrentPages();
- if (pages.length > 1) {
- uni.navigateBack();
- } else {
- uni.switchTab({ url: '/pages/index/index' });
- }
- }
- // 创建新书籍
- async function createNewBook() {
- if (!canCreateBook.value) return;
- const configSummary: string[] = [];
- if (newBook.value.knowledgeLevel) configSummary.push(`知识难度:${newBook.value.knowledgeLevel}`);
- if (newBook.value.targetAudience) configSummary.push(`面向人群:${newBook.value.targetAudience}`);
- if (newBook.value.industry) configSummary.push(`行业领域:${newBook.value.industry}`);
- if (newBook.value.specialFeatures.length > 0) configSummary.push(`特殊要求:${newBook.value.specialFeatures.join('、')}`);
- if (newBook.value.style) configSummary.push(`写作风格:${newBook.value.style}`);
- const confirmResult = await new Promise<boolean>((resolve) => {
- uni.showModal({
- title: '📋 生成配置确认',
- content: `${configSummary.join('\n')}\n\n书名将由AI根据描述自动生成\n\n确认开始生成?`,
- confirmText: '确认生成',
- cancelText: '返回修改',
- success: (res) => resolve(res.confirm),
- });
- });
- if (!confirmResult) return;
- creating.value = true;
- try {
- let mergedDesc = newBook.value.description.trim();
- if (newBook.value.knowledgeLevel && newBook.value.knowledgeLevel !== '不填') {
- mergedDesc += `\n知识难度:${newBook.value.knowledgeLevel}`;
- }
- if (newBook.value.targetAudience && newBook.value.targetAudience !== '不填') {
- mergedDesc += `\n面向人群:${newBook.value.targetAudience}`;
- }
- if (newBook.value.industry && newBook.value.industry !== '不填') {
- mergedDesc += `\n行业领域:${newBook.value.industry}`;
- }
- if (newBook.value.specialFeatures && newBook.value.specialFeatures.length > 0) {
- mergedDesc += `\n特殊要求:${newBook.value.specialFeatures.join('、')}`;
- }
- if (newBook.value.style && newBook.value.style !== '不填') {
- mergedDesc += `\n写作风格:${newBook.value.style}`;
- }
- const genLevel = newBook.value.outlineLevel === 'auto' ? undefined : parseInt(newBook.value.outlineLevel);
- const bookType = newBook.value.bookType === 'auto' ? undefined : newBook.value.bookType;
- const result = await api.createBook({
- title: newBook.value.title,
- subtitle: newBook.value.subtitle,
- description: mergedDesc,
- bookScale: newBook.value.bookScale || undefined,
- bookType,
- genLevel,
- });
- uni.showToast({ title: '创建成功', icon: 'success' });
- // 跳转到详情页
- setTimeout(() => {
- uni.navigateTo({
- url: `/pages/book-generator/detail?id=${result.id}`
- });
- }, 1500);
- } catch (e: any) {
- uni.showToast({ title: e.message || '创建失败', icon: 'none' });
- } finally {
- creating.value = false;
- }
- }
- onMounted(() => {
- // 不再默认选中规模,让 AI 根据描述自动判断
- });
- </script>
- <style scoped>
- .page {
- min-height: 100vh;
- background: #f9fafb;
- }
- .nav-bar {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 100;
- background: #ffffff;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- }
- .nav-content {
- display: flex;
- align-items: center;
- height: 88rpx;
- padding: 0 32rpx;
- padding-top: env(safe-area-inset-top);
- }
- .nav-left { width: 60rpx; flex-shrink: 0; }
- .back-icon { font-size: 40rpx; color: #1f2937; }
- .page-title {
- font-size: 32rpx;
- font-weight: 600;
- color: #1f2937;
- flex: 1;
- text-align: center;
- }
- /* 模式切换独立行 */
- .mode-bar { padding: 0 32rpx 16rpx 32rpx; }
- .mode-switch { display: flex; align-items: center; justify-content: center; width: 100%; height: 72rpx; background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%); color: #fff; border-radius: 12rpx; font-size: 26rpx; font-weight: 600; }
- .main-content {
- padding-top: calc(88rpx + 16rpx + 72rpx + 16rpx + env(safe-area-inset-top));
- }
- .card {
- margin: 24rpx;
- padding: 32rpx;
- background: #ffffff;
- border-radius: 16rpx;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- }
- .card-header { margin-bottom: 32rpx; }
- .card-title { font-size: 36rpx; font-weight: 700; color: #1f2937; }
- .form-item { margin-bottom: 32rpx; }
- .form-label { font-size: 28rpx; font-weight: 600; color: #374151; margin-bottom: 12rpx; display: block; }
- .form-hint { font-size: 24rpx; color: #9ca3af; margin-bottom: 16rpx; display: block; }
- .form-hint-small { font-size: 24rpx; color: #9ca3af; margin-bottom: 16rpx; display: block; }
- .form-input {
- width: 100%;
- height: 88rpx;
- padding: 0 24rpx;
- background: #f9fafb;
- border: 2rpx solid #e5e7eb;
- border-radius: 12rpx;
- font-size: 28rpx;
- }
- .form-textarea {
- width: 100%;
- min-height: 200rpx;
- padding: 24rpx;
- background: #f9fafb;
- border: 2rpx solid #e5e7eb;
- border-radius: 12rpx;
- font-size: 28rpx;
- line-height: 1.6;
- }
- .label-with-badge { display: flex; align-items: center; gap: 12rpx; margin-bottom: 12rpx; }
- .required-badge {
- padding: 4rpx 12rpx;
- background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
- color: white;
- border-radius: 8rpx;
- font-size: 20rpx;
- font-weight: 500;
- }
- .optional-badge {
- padding: 4rpx 12rpx;
- background: linear-gradient(135deg, #10b981 0%, #059669 100%);
- color: white;
- border-radius: 8rpx;
- font-size: 20rpx;
- font-weight: 500;
- }
- .highlight-item { padding: 24rpx; background: rgba(79, 70, 229, 0.03); border-radius: 12rpx; border: 1rpx solid rgba(79, 70, 229, 0.1); }
- .chip-group { display: flex; flex-wrap: wrap; gap: 12rpx; }
- .chip {
- display: flex;
- align-items: center;
- gap: 8rpx;
- padding: 12rpx 20rpx;
- background: #f3f4f6;
- border-radius: 24rpx;
- font-size: 24rpx;
- color: #4b5563;
- transition: all 0.2s;
- }
- .chip.active { background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%); color: white; }
- .chip-icon { font-size: 24rpx; }
- .chip-text { font-size: 24rpx; }
- .template-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16rpx; margin-top: 16rpx; }
- .template-card {
- padding: 20rpx;
- background: #f9fafb;
- border: 2rpx solid #e5e7eb;
- border-radius: 12rpx;
- text-align: center;
- }
- .template-icon { font-size: 40rpx; display: block; margin-bottom: 8rpx; }
- .template-name { font-size: 26rpx; font-weight: 600; color: #1f2937; display: block; }
- .template-desc { font-size: 22rpx; color: #9ca3af; display: block; margin-top: 4rpx; }
- .important-tip { padding: 24rpx; background: rgba(79, 70, 229, 0.05); border-radius: 12rpx; margin: 24rpx 0; }
- .tip-header { display: flex; align-items: center; gap: 8rpx; margin-bottom: 12rpx; }
- .tip-icon { font-size: 28rpx; }
- .tip-title { font-size: 28rpx; font-weight: 600; color: #4f46e5; }
- .tip-content { font-size: 24rpx; color: #6b7280; line-height: 1.6; display: block; margin-bottom: 12rpx; }
- .tip-examples { display: flex; flex-direction: column; gap: 8rpx; }
- .tip-example { font-size: 24rpx; color: #9ca3af; }
- .scale-picker { display: flex; flex-wrap: wrap; gap: 12rpx; }
- .scale-option {
- flex: 1;
- min-width: 200rpx;
- padding: 16rpx;
- background: #f9fafb;
- border: 2rpx solid #e5e7eb;
- border-radius: 12rpx;
- text-align: center;
- }
- .scale-option.active { border-color: #4f46e5; background: rgba(79, 70, 229, 0.05); }
- .scale-words { font-size: 28rpx; font-weight: 600; color: #1f2937; display: block; }
- .scale-label { font-size: 24rpx; color: #6b7280; display: block; margin-top: 4rpx; }
- .scale-audio { font-size: 22rpx; color: #9ca3af; display: block; margin-top: 4rpx; }
- .estimate-card { padding: 24rpx; background: rgba(16, 185, 129, 0.05); border-radius: 12rpx; margin-top: 24rpx; }
- .estimate-header { margin-bottom: 16rpx; }
- .estimate-title { font-size: 28rpx; font-weight: 600; color: #059669; }
- .estimate-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8rpx; }
- .estimate-label { font-size: 24rpx; color: #6b7280; }
- .estimate-value { font-size: 24rpx; font-weight: 500; color: #1f2937; }
- .genlevel-row { margin-top: 8rpx; padding-top: 8rpx; border-top: 1rpx solid rgba(16, 185, 129, 0.15); }
- .genlevel-tag { display: flex; align-items: center; gap: 6rpx; padding: 4rpx 12rpx; border-radius: 8rpx; font-size: 22rpx; }
- .genlevel-tag.level-1 { background: rgba(239, 68, 68, 0.1); }
- .genlevel-tag.level-2 { background: rgba(245, 158, 11, 0.1); }
- .genlevel-tag.level-3 { background: rgba(79, 70, 229, 0.1); }
- .genlevel-label { font-weight: 600; color: #1f2937; font-size: 22rpx; }
- .genlevel-desc { color: #9ca3af; font-size: 20rpx; }
- .quota-check { margin-top: 16rpx; padding-top: 16rpx; border-top: 1rpx solid rgba(16, 185, 129, 0.2); }
- .quota-ok { display: flex; align-items: center; gap: 8rpx; margin-bottom: 12rpx; }
- .quota-icon { font-size: 24rpx; }
- .quota-text { font-size: 24rpx; color: #059669; }
- .quota-warning { display: flex; align-items: center; gap: 8rpx; margin-bottom: 12rpx; }
- .quota-detail { margin-top: 8rpx; }
- .quota-info { font-size: 22rpx; color: #9ca3af; display: block; }
- .quota-overage { margin-top: 8rpx; display: flex; justify-content: space-between; }
- .quota-price { font-size: 22rpx; color: #ef4444; }
- .btn-group { display: flex; gap: 16rpx; margin-top: 32rpx; }
- .btn-cancel, .btn-primary {
- flex: 1;
- height: 88rpx;
- border-radius: 16rpx;
- font-size: 28rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- border: none;
- }
- .btn-cancel { background: #f3f4f6; color: #6b7280; }
- .btn-primary { background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%); color: white; }
- .btn-primary[disabled] { opacity: 0.5; }
- /* 简洁模式提示 */
- .simple-mode-tip { padding: 24rpx; background: rgba(16, 185, 129, 0.08); border-radius: 12rpx; margin-bottom: 32rpx; }
- .simple-mode-tip .tip-header { display: flex; align-items: center; gap: 8rpx; margin-bottom: 8rpx; }
- .simple-mode-tip .tip-icon { font-size: 28rpx; }
- .simple-mode-tip .tip-title { font-size: 28rpx; font-weight: 600; color: #059669; }
- .simple-mode-tip .tip-content { font-size: 24rpx; color: #6b7280; line-height: 1.5; }
- /* 智能推荐按钮 */
- .smart-recommend-section { margin-top: 24rpx; }
- .btn-smart-recommend {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8rpx;
- width: 100%;
- height: 80rpx;
- background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
- border-radius: 12rpx;
- border: none;
- margin-bottom: 12rpx;
- }
- .btn-smart-recommend[disabled] { opacity: 0.5; }
- .recommend-icon { font-size: 32rpx; }
- .recommend-text { font-size: 28rpx; font-weight: 600; color: white; }
- /* 高级选项折叠区 */
- .advanced-section { margin-top: 32rpx; border-top: 1rpx solid #e5e7eb; padding-top: 24rpx; }
- .advanced-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 16rpx 0;
- }
- .advanced-title { font-size: 28rpx; font-weight: 600; color: #4b5563; }
- .advanced-arrow { font-size: 24rpx; color: #9ca3af; }
- .advanced-content { margin-top: 16rpx; }
- </style>
|