/** * 结局场景 */ import BaseScene from './BaseScene'; export default class EndingScene extends BaseScene { constructor(main, params) { super(main, params); this.storyId = params.storyId; this.ending = params.ending; console.log('EndingScene 接收到的结局:', JSON.stringify(this.ending)); this.showButtons = false; this.fadeIn = 0; this.particles = []; this.isLiked = false; this.isCollected = false; // AI改写面板 this.showRewritePanel = false; this.rewritePrompt = ''; this.rewriteTags = ['主角逆袭', '甜蜜HE', '虐心BE', '反转剧情', '意外重逢']; this.selectedTag = -1; // 改写历史 this.rewriteHistory = []; this.currentHistoryIndex = -1; // 配额信息 this.aiQuota = { daily: 3, used: 0, purchased: 0 }; // 加载状态 this.isRewriting = false; this.rewriteProgress = 0; this.initParticles(); this.loadQuota(); } init() { setTimeout(() => { this.showButtons = true; }, 1500); } async loadQuota() { try { const quota = await this.main.userManager.getAIQuota(); if (quota) { this.aiQuota = quota; } } catch (e) { console.error('加载配额失败:', e); } } initParticles() { for (let i = 0; i < 50; i++) { this.particles.push({ x: Math.random() * this.screenWidth, y: Math.random() * this.screenHeight, size: Math.random() * 3 + 1, speedY: Math.random() * 0.5 + 0.2, alpha: Math.random() * 0.5 + 0.3 }); } } update() { if (this.fadeIn < 1) { this.fadeIn += 0.02; } this.particles.forEach(p => { p.y -= p.speedY; if (p.y < 0) { p.y = this.screenHeight; p.x = Math.random() * this.screenWidth; } }); } render(ctx) { this.renderBackground(ctx); this.renderParticles(ctx); this.renderEndingContent(ctx); if (this.showButtons) { this.renderButtons(ctx); } // AI改写面板 if (this.showRewritePanel) { this.renderRewritePanel(ctx); } } renderBackground(ctx) { const gradient = ctx.createLinearGradient(0, 0, 0, this.screenHeight); switch (this.ending?.type) { case 'good': gradient.addColorStop(0, '#0f2027'); gradient.addColorStop(0.5, '#203a43'); gradient.addColorStop(1, '#2c5364'); break; case 'bad': gradient.addColorStop(0, '#1a0a0a'); gradient.addColorStop(0.5, '#3a1515'); gradient.addColorStop(1, '#2d1f1f'); break; case 'hidden': gradient.addColorStop(0, '#1a1a0a'); gradient.addColorStop(0.5, '#3a3515'); gradient.addColorStop(1, '#2d2d1f'); break; case 'rewrite': gradient.addColorStop(0, '#1a0a2e'); gradient.addColorStop(0.5, '#2d1b4e'); gradient.addColorStop(1, '#4a1942'); break; default: gradient.addColorStop(0, '#0f0c29'); gradient.addColorStop(0.5, '#302b63'); gradient.addColorStop(1, '#24243e'); } ctx.fillStyle = gradient; ctx.fillRect(0, 0, this.screenWidth, this.screenHeight); } renderParticles(ctx) { this.particles.forEach(p => { ctx.fillStyle = `rgba(255, 255, 255, ${p.alpha * this.fadeIn})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); }); } renderEndingContent(ctx) { const centerX = this.screenWidth / 2; const alpha = this.fadeIn; const padding = 20; // 结局卡片背景 const cardY = 80; const cardHeight = 320; ctx.fillStyle = `rgba(255, 255, 255, ${0.05 * alpha})`; this.roundRect(ctx, padding, cardY, this.screenWidth - padding * 2, cardHeight, 20); ctx.fill(); // 装饰线 const lineGradient = ctx.createLinearGradient(padding + 30, cardY + 20, this.screenWidth - padding - 30, cardY + 20); lineGradient.addColorStop(0, 'transparent'); lineGradient.addColorStop(0.5, this.getEndingColorRgba(alpha * 0.5)); lineGradient.addColorStop(1, 'transparent'); ctx.strokeStyle = lineGradient; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(padding + 30, cardY + 20); ctx.lineTo(this.screenWidth - padding - 30, cardY + 20); ctx.stroke(); // 结局标签 ctx.fillStyle = `rgba(255, 255, 255, ${alpha * 0.6})`; ctx.font = '13px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('— 达成结局 —', centerX, cardY + 50); // 结局名称(自动调整字号) const endingName = this.ending?.name || '未知结局'; let fontSize = 24; ctx.font = `bold ${fontSize}px sans-serif`; while (ctx.measureText(endingName).width > this.screenWidth - padding * 2 - 40 && fontSize > 14) { fontSize -= 2; ctx.font = `bold ${fontSize}px sans-serif`; } ctx.fillStyle = this.getEndingColorRgba(alpha); ctx.fillText(endingName, centerX, cardY + 90); // 结局类型标签 const typeLabel = this.getTypeLabel(); if (typeLabel) { ctx.font = '11px sans-serif'; const labelWidth = ctx.measureText(typeLabel).width + 20; ctx.fillStyle = this.getEndingColorRgba(alpha * 0.3); this.roundRect(ctx, centerX - labelWidth / 2, cardY + 100, labelWidth, 22, 11); ctx.fill(); ctx.fillStyle = `rgba(255, 255, 255, ${alpha * 0.9})`; ctx.fillText(typeLabel, centerX, cardY + 115); } // 评分 if (this.ending?.score !== undefined) { this.renderScore(ctx, centerX, cardY + 155, alpha); } // 结局描述(居中显示,限制在卡片内) ctx.fillStyle = `rgba(255, 255, 255, ${alpha * 0.6})`; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; const content = this.ending?.content || ''; const lastParagraph = content.split('\n').filter(p => p.trim()).pop() || ''; const maxWidth = this.screenWidth - padding * 2 - 30; // 限制只显示2行,居中 this.wrapTextCentered(ctx, lastParagraph, centerX, cardY + 250, maxWidth, 20, 2); } getTypeLabel() { switch (this.ending?.type) { case 'good': return '✨ 完美结局'; case 'bad': return '💔 悲伤结局'; case 'hidden': return '🔮 隐藏结局'; case 'rewrite': return '🤖 AI改写结局'; default: return '📖 普通结局'; } } truncateText(ctx, text, maxWidth) { if (!text) return ''; if (ctx.measureText(text).width <= maxWidth) return text; let truncated = text; while (truncated.length > 0 && ctx.measureText(truncated + '...').width > maxWidth) { truncated = truncated.slice(0, -1); } return truncated + '...'; } renderScore(ctx, x, y, alpha) { const score = this.ending?.score || 0; const stars = Math.ceil(score / 20); // 星星 const starSize = 22; const gap = 6; const totalWidth = 5 * starSize + 4 * gap; const startX = x - totalWidth / 2; for (let i = 0; i < 5; i++) { const filled = i < stars; ctx.fillStyle = filled ? `rgba(255, 215, 0, ${alpha})` : `rgba(100, 100, 100, ${alpha * 0.5})`; ctx.font = `${starSize}px sans-serif`; ctx.textAlign = 'left'; ctx.fillText(filled ? '★' : '☆', startX + i * (starSize + gap), y); } // 分数 ctx.fillStyle = `rgba(255, 215, 0, ${alpha})`; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`${score}分`, x, y + 28); } getEndingColorRgba(alpha) { switch (this.ending?.type) { case 'good': return `rgba(100, 255, 150, ${alpha})`; case 'bad': return `rgba(255, 100, 100, ${alpha})`; case 'hidden': return `rgba(255, 215, 0, ${alpha})`; case 'rewrite': return `rgba(168, 85, 247, ${alpha})`; default: return `rgba(150, 150, 255, ${alpha})`; } } renderButtons(ctx) { const padding = 15; const buttonHeight = 38; const buttonMargin = 8; const startY = this.screenHeight - 220; // AI改写按钮(带配额提示) const remaining = this.aiQuota.daily - this.aiQuota.used + this.aiQuota.purchased; const aiBtnText = remaining > 0 ? '✨ AI改写结局' : '⚠️ 次数不足'; this.renderGradientButton(ctx, padding, startY, this.screenWidth - padding * 2, buttonHeight, aiBtnText, ['#a855f7', '#ec4899']); // 分享按钮 const row2Y = startY + buttonHeight + buttonMargin; const buttonWidth = (this.screenWidth - padding * 2 - buttonMargin) / 2; this.renderGradientButton(ctx, padding, row2Y, buttonWidth, buttonHeight, '分享结局', ['#ff6b6b', '#ffd700']); // 章节选择按钮 this.renderGradientButton(ctx, padding + buttonWidth + buttonMargin, row2Y, buttonWidth, buttonHeight, '章节选择', ['#667eea', '#764ba2']); // 从头开始 const row3Y = row2Y + buttonHeight + buttonMargin; ctx.fillStyle = 'rgba(255,255,255,0.1)'; this.roundRect(ctx, padding, row3Y, buttonWidth, buttonHeight, 19); ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,0.2)'; ctx.lineWidth = 1; this.roundRect(ctx, padding, row3Y, buttonWidth, buttonHeight, 19); ctx.stroke(); ctx.fillStyle = '#ffffff'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('从头开始', padding + buttonWidth / 2, row3Y + 24); // 返回首页 ctx.fillStyle = 'rgba(255,255,255,0.1)'; this.roundRect(ctx, padding + buttonWidth + buttonMargin, row3Y, buttonWidth, buttonHeight, 19); ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,0.2)'; this.roundRect(ctx, padding + buttonWidth + buttonMargin, row3Y, buttonWidth, buttonHeight, 19); ctx.stroke(); ctx.fillStyle = '#ffffff'; ctx.fillText('返回首页', padding + buttonWidth + buttonMargin + buttonWidth / 2, row3Y + 24); // 点赞和收藏 const actionY = row3Y + buttonHeight + 18; const centerX = this.screenWidth / 2; ctx.font = '20px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(this.isLiked ? '❤️' : '🤍', centerX - 40, actionY); ctx.fillStyle = 'rgba(255,255,255,0.6)'; ctx.font = '10px sans-serif'; ctx.fillText('点赞', centerX - 40, actionY + 16); ctx.font = '20px sans-serif'; ctx.fillText(this.isCollected ? '⭐' : '☆', centerX + 40, actionY); ctx.fillStyle = 'rgba(255,255,255,0.6)'; ctx.font = '10px sans-serif'; ctx.fillText('收藏', centerX + 40, actionY + 16); } renderGradientButton(ctx, x, y, width, height, text, colors) { const gradient = ctx.createLinearGradient(x, y, x + width, y); gradient.addColorStop(0, colors[0]); gradient.addColorStop(1, colors[1]); ctx.fillStyle = gradient; this.roundRect(ctx, x, y, width, height, 22); ctx.fill(); ctx.fillStyle = '#ffffff'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(text, x + width / 2, y + height / 2 + 5); } renderRewritePanel(ctx) { const padding = 20; const panelWidth = this.screenWidth - padding * 2; const panelHeight = 450; const panelX = padding; const panelY = (this.screenHeight - panelHeight) / 2; // 遮罩层 ctx.fillStyle = 'rgba(0, 0, 0, 0.85)'; ctx.fillRect(0, 0, this.screenWidth, this.screenHeight); // 面板背景渐变 const panelGradient = ctx.createLinearGradient(panelX, panelY, panelX, panelY + panelHeight); panelGradient.addColorStop(0, '#1a1a3e'); panelGradient.addColorStop(1, '#0d0d1a'); ctx.fillStyle = panelGradient; this.roundRect(ctx, panelX, panelY, panelWidth, panelHeight, 20); ctx.fill(); // 面板边框渐变 const borderGradient = ctx.createLinearGradient(panelX, panelY, panelX + panelWidth, panelY); borderGradient.addColorStop(0, '#a855f7'); borderGradient.addColorStop(1, '#ec4899'); ctx.strokeStyle = borderGradient; ctx.lineWidth = 2; this.roundRect(ctx, panelX, panelY, panelWidth, panelHeight, 20); ctx.stroke(); // 标题栏 ctx.fillStyle = '#ffffff'; ctx.font = 'bold 18px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('✨ AI改写结局', this.screenWidth / 2, panelY + 35); // 配额提示 const remaining = this.aiQuota.daily - this.aiQuota.used + this.aiQuota.purchased; ctx.fillStyle = remaining > 0 ? 'rgba(255,255,255,0.6)' : 'rgba(255,100,100,0.8)'; ctx.font = '11px sans-serif'; ctx.textAlign = 'right'; ctx.fillText(`剩余次数:${remaining}`, panelX + panelWidth - 15, panelY + 35); // 副标题 ctx.fillStyle = 'rgba(255,255,255,0.6)'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('输入你想要的剧情走向,AI将为你重新创作', this.screenWidth / 2, panelY + 58); // 分隔线 const lineGradient = ctx.createLinearGradient(panelX + 20, panelY + 75, panelX + panelWidth - 20, panelY + 75); lineGradient.addColorStop(0, 'transparent'); lineGradient.addColorStop(0.5, 'rgba(168,85,247,0.5)'); lineGradient.addColorStop(1, 'transparent'); ctx.strokeStyle = lineGradient; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(panelX + 20, panelY + 75); ctx.lineTo(panelX + panelWidth - 20, panelY + 75); ctx.stroke(); // 快捷标签标题 ctx.fillStyle = 'rgba(255,255,255,0.8)'; ctx.font = '13px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('快捷选择:', panelX + 15, panelY + 105); // 快捷标签 const tagStartX = panelX + 15; const tagY = panelY + 120; const tagHeight = 32; const tagGap = 8; let currentX = tagStartX; let currentY = tagY; this.tagRects = []; this.rewriteTags.forEach((tag, index) => { ctx.font = '12px sans-serif'; const tagWidth = ctx.measureText(tag).width + 24; // 换行 if (currentX + tagWidth > panelX + panelWidth - 15) { currentX = tagStartX; currentY += tagHeight + tagGap; } // 标签背景 const isSelected = index === this.selectedTag; if (isSelected) { const tagGradient = ctx.createLinearGradient(currentX, currentY, currentX + tagWidth, currentY); tagGradient.addColorStop(0, '#a855f7'); tagGradient.addColorStop(1, '#ec4899'); ctx.fillStyle = tagGradient; } else { ctx.fillStyle = 'rgba(255,255,255,0.1)'; } this.roundRect(ctx, currentX, currentY, tagWidth, tagHeight, 16); ctx.fill(); // 标签边框 ctx.strokeStyle = isSelected ? 'transparent' : 'rgba(255,255,255,0.2)'; ctx.lineWidth = 1; this.roundRect(ctx, currentX, currentY, tagWidth, tagHeight, 16); ctx.stroke(); // 标签文字 ctx.fillStyle = '#ffffff'; ctx.textAlign = 'center'; ctx.fillText(tag, currentX + tagWidth / 2, currentY + 21); // 存储标签位置 this.tagRects.push({ x: currentX, y: currentY, width: tagWidth, height: tagHeight, index }); currentX += tagWidth + tagGap; }); // 自定义输入提示 ctx.fillStyle = 'rgba(255,255,255,0.8)'; ctx.font = '13px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('或自定义输入:', panelX + 15, panelY + 215); // 输入框背景 const inputY = panelY + 230; const inputHeight = 45; ctx.fillStyle = 'rgba(255,255,255,0.08)'; this.roundRect(ctx, panelX + 15, inputY, panelWidth - 30, inputHeight, 12); ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,0.2)'; ctx.lineWidth = 1; this.roundRect(ctx, panelX + 15, inputY, panelWidth - 30, inputHeight, 12); ctx.stroke(); // 输入框文字或占位符 ctx.font = '14px sans-serif'; ctx.textAlign = 'left'; if (this.rewritePrompt) { ctx.fillStyle = '#ffffff'; ctx.fillText(this.rewritePrompt, panelX + 28, inputY + 28); } else { ctx.fillStyle = 'rgba(255,255,255,0.4)'; ctx.fillText('点击输入你的改写想法...', panelX + 28, inputY + 28); } // 按钮 const btnY = panelY + panelHeight - 70; const btnWidth = (panelWidth - 50) / 2; const btnHeight = 44; // 取消按钮 ctx.fillStyle = 'rgba(255,255,255,0.1)'; this.roundRect(ctx, panelX + 15, btnY, btnWidth, btnHeight, 22); ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,0.3)'; ctx.lineWidth = 1; this.roundRect(ctx, panelX + 15, btnY, btnWidth, btnHeight, 22); ctx.stroke(); ctx.fillStyle = '#ffffff'; ctx.font = '14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('取消', panelX + 15 + btnWidth / 2, btnY + 28); // 确认按钮 const confirmGradient = ctx.createLinearGradient(panelX + 35 + btnWidth, btnY, panelX + 35 + btnWidth * 2, btnY); confirmGradient.addColorStop(0, '#a855f7'); confirmGradient.addColorStop(1, '#ec4899'); ctx.fillStyle = confirmGradient; this.roundRect(ctx, panelX + 35 + btnWidth, btnY, btnWidth, btnHeight, 22); ctx.fill(); ctx.fillStyle = '#ffffff'; ctx.font = 'bold 14px sans-serif'; ctx.fillText('✨ 开始改写', panelX + 35 + btnWidth + btnWidth / 2, btnY + 28); // 存储按钮区域 this.cancelBtnRect = { x: panelX + 15, y: btnY, width: btnWidth, height: btnHeight }; this.confirmBtnRect = { x: panelX + 35 + btnWidth, y: btnY, width: btnWidth, height: btnHeight }; this.inputRect = { x: panelX + 15, y: inputY, width: panelWidth - 30, height: inputHeight }; } // 圆角矩形 roundRect(ctx, x, y, width, height, radius) { ctx.beginPath(); ctx.moveTo(x + radius, y); ctx.lineTo(x + width - radius, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius); ctx.lineTo(x + width, y + height - radius); ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); ctx.lineTo(x + radius, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - radius); ctx.lineTo(x, y + radius); ctx.quadraticCurveTo(x, y, x + radius, y); ctx.closePath(); } // 文字换行 wrapText(ctx, text, x, y, maxWidth, lineHeight) { if (!text) return; let line = ''; let lineY = y; for (let char of text) { const testLine = line + char; if (ctx.measureText(testLine).width > maxWidth) { ctx.fillText(line, x, lineY); line = char; lineY += lineHeight; } else { line = testLine; } } ctx.fillText(line, x, lineY); } // 限制行数的文字换行 wrapTextLimited(ctx, text, x, y, maxWidth, lineHeight, maxLines) { if (!text) return; let line = ''; let lineY = y; let lineCount = 0; for (let i = 0; i < text.length; i++) { const char = text[i]; const testLine = line + char; if (ctx.measureText(testLine).width > maxWidth) { lineCount++; if (lineCount >= maxLines) { // 最后一行加省略号 while (line.length > 0 && ctx.measureText(line + '...').width > maxWidth) { line = line.slice(0, -1); } ctx.fillText(line + '...', x, lineY); return; } ctx.fillText(line, x, lineY); line = char; lineY += lineHeight; } else { line = testLine; } } ctx.fillText(line, x, lineY); } // 居中显示的限制行数文字换行 wrapTextCentered(ctx, text, centerX, y, maxWidth, lineHeight, maxLines) { if (!text) return; // 先分行 const lines = []; let line = ''; for (let i = 0; i < text.length; i++) { const char = text[i]; const testLine = line + char; if (ctx.measureText(testLine).width > maxWidth) { lines.push(line); line = char; if (lines.length >= maxLines) break; } else { line = testLine; } } if (line && lines.length < maxLines) { lines.push(line); } // 如果超出行数,最后一行加省略号 if (lines.length >= maxLines && line) { let lastLine = lines[maxLines - 1]; while (lastLine.length > 0 && ctx.measureText(lastLine + '...').width > maxWidth) { lastLine = lastLine.slice(0, -1); } lines[maxLines - 1] = lastLine + '...'; } // 居中绘制 lines.slice(0, maxLines).forEach((l, i) => { ctx.fillText(l, centerX, y + i * lineHeight); }); } onTouchEnd(e) { const touch = e.changedTouches[0]; const x = touch.clientX; const y = touch.clientY; // 如果改写面板打开,优先处理 if (this.showRewritePanel) { this.handleRewritePanelTouch(x, y); return; } if (!this.showButtons) return; const padding = 15; const buttonHeight = 38; const buttonMargin = 8; const startY = this.screenHeight - 220; const buttonWidth = (this.screenWidth - padding * 2 - buttonMargin) / 2; // AI改写按钮 if (this.isInRect(x, y, padding, startY, this.screenWidth - padding * 2, buttonHeight)) { this.handleAIRewrite(); return; } // 分享按钮 const row2Y = startY + buttonHeight + buttonMargin; if (this.isInRect(x, y, padding, row2Y, buttonWidth, buttonHeight)) { this.handleShare(); return; } // 章节选择按钮 if (this.isInRect(x, y, padding + buttonWidth + buttonMargin, row2Y, buttonWidth, buttonHeight)) { this.handleChapterSelect(); return; } // 从头开始 const row3Y = row2Y + buttonHeight + buttonMargin; if (this.isInRect(x, y, padding, row3Y, buttonWidth, buttonHeight)) { this.handleReplay(); return; } // 返回首页 if (this.isInRect(x, y, padding + buttonWidth + buttonMargin, row3Y, buttonWidth, buttonHeight)) { this.main.sceneManager.switchScene('home'); return; } // 点赞收藏 const actionY = row3Y + buttonHeight + 18; const centerX = this.screenWidth / 2; if (this.isInRect(x, y, centerX - 70, actionY - 20, 60, 45)) { this.handleLike(); return; } if (this.isInRect(x, y, centerX + 10, actionY - 20, 60, 45)) { this.handleCollect(); return; } } isInRect(x, y, rx, ry, rw, rh) { return x >= rx && x <= rx + rw && y >= ry && y <= ry + rh; } handleShare() { wx.shareAppMessage({ title: `我在《星域故事汇》达成了「${this.ending?.name}」结局!`, imageUrl: '', query: `storyId=${this.storyId}` }); } handleChapterSelect() { this.main.sceneManager.switchScene('chapter', { storyId: this.storyId }); } handleAIRewrite() { // 检查配额 const remaining = this.aiQuota.daily - this.aiQuota.used + this.aiQuota.purchased; if (remaining <= 0) { this.showQuotaModal(); return; } // 显示AI改写面板 this.showRewritePanel = true; this.rewritePrompt = ''; this.selectedTag = -1; } handleRewritePanelTouch(x, y) { // 点击标签 if (this.tagRects) { for (const tag of this.tagRects) { if (this.isInRect(x, y, tag.x, tag.y, tag.width, tag.height)) { this.selectedTag = tag.index; this.rewritePrompt = this.rewriteTags[tag.index]; return true; } } } // 点击输入框 if (this.inputRect && this.isInRect(x, y, this.inputRect.x, this.inputRect.y, this.inputRect.width, this.inputRect.height)) { this.showCustomInput(); return true; } // 点击取消 if (this.cancelBtnRect && this.isInRect(x, y, this.cancelBtnRect.x, this.cancelBtnRect.y, this.cancelBtnRect.width, this.cancelBtnRect.height)) { this.showRewritePanel = false; return true; } // 点击确认 if (this.confirmBtnRect && this.isInRect(x, y, this.confirmBtnRect.x, this.confirmBtnRect.y, this.confirmBtnRect.width, this.confirmBtnRect.height)) { if (this.rewritePrompt) { this.showRewritePanel = false; this.callAIRewrite(this.rewritePrompt); } else { wx.showToast({ title: '请选择或输入改写内容', icon: 'none' }); } return true; } return false; } showCustomInput() { wx.showModal({ title: '输入改写想法', editable: true, placeholderText: '例如:让主角获得逆袭', content: this.rewritePrompt, success: (res) => { if (res.confirm && res.content) { this.rewritePrompt = res.content; this.selectedTag = -1; } } }); } async callAIRewrite(prompt) { // 检查配额 const remaining = this.aiQuota.daily - this.aiQuota.used + this.aiQuota.purchased; if (remaining <= 0) { this.showQuotaModal(); return; } this.isRewriting = true; this.rewriteProgress = 0; // 显示加载动画 wx.showLoading({ title: 'AI创作中...', mask: true }); // 模拟进度条效果 const progressInterval = setInterval(() => { this.rewriteProgress += Math.random() * 20; if (this.rewriteProgress > 90) this.rewriteProgress = 90; }, 500); try { const result = await this.main.storyManager.rewriteEnding( this.storyId, this.ending, prompt ); clearInterval(progressInterval); this.rewriteProgress = 100; wx.hideLoading(); if (result && result.content) { // 记录改写历史 this.rewriteHistory.push({ prompt: prompt, content: result.content, timestamp: Date.now() }); this.currentHistoryIndex = this.rewriteHistory.length - 1; // 扣除配额 this.aiQuota.used += 1; // 成功提示 wx.showToast({ title: '改写成功!', icon: 'success', duration: 1500 }); // 延迟跳转到故事场景播放新内容 setTimeout(() => { this.main.sceneManager.switchScene('story', { storyId: this.storyId, aiContent: result }); }, 1500); } else { wx.showToast({ title: '改写失败,请重试', icon: 'none' }); } } catch (error) { clearInterval(progressInterval); wx.hideLoading(); console.error('改写失败:', error); wx.showToast({ title: error.message || '网络错误', icon: 'none' }); } finally { this.isRewriting = false; this.rewriteProgress = 0; } } showQuotaModal() { wx.showModal({ title: 'AI次数不足', content: `今日剩余免费次数已用完。\n\n观看广告可获得1次 AI改写机会`, confirmText: '看广告', cancelText: '取消', success: (res) => { if (res.confirm) { this.watchAdForQuota(); } } }); } watchAdForQuota() { // 模拟看广告获得配额 wx.showToast({ title: '获得1次 AI 次数', icon: 'success' }); this.aiQuota.purchased += 1; } handleReplay() { this.main.storyManager.resetStory(); this.main.sceneManager.switchScene('story', { storyId: this.storyId }); } handleLike() { this.isLiked = !this.isLiked; this.main.userManager.likeStory(this.storyId, this.isLiked); this.main.storyManager.likeStory(this.isLiked); } handleCollect() { this.isCollected = !this.isCollected; this.main.userManager.collectStory(this.storyId, this.isCollected); } }