feat: 添加测试用户到种子数据, AI改写功能优化, 前端联调修复

This commit is contained in:
2026-03-09 23:00:15 +08:00
parent 5e931424ab
commit 9948ccba8f
12 changed files with 1082 additions and 151 deletions

View File

@@ -20,6 +20,11 @@ export default class EndingScene extends BaseScene {
this.rewritePrompt = '';
this.rewriteTags = ['主角逆袭', '甜蜜HE', '虐心BE', '反转剧情', '意外重逢'];
this.selectedTag = -1;
// AI续写面板
this.showContinuePanel = false;
this.continuePrompt = '';
this.continueTags = ['故事未完', '新的冒险', '多年以后', '意外转折', '番外篇'];
this.selectedContinueTag = -1;
// 改写历史
this.rewriteHistory = [];
this.currentHistoryIndex = -1;
@@ -85,6 +90,10 @@ export default class EndingScene extends BaseScene {
if (this.showRewritePanel) {
this.renderRewritePanel(ctx);
}
// AI续写面板
if (this.showContinuePanel) {
this.renderContinuePanel(ctx);
}
}
renderBackground(ctx) {
@@ -257,15 +266,17 @@ export default class EndingScene extends BaseScene {
const buttonHeight = 38;
const buttonMargin = 8;
const startY = this.screenHeight - 220;
const buttonWidth = (this.screenWidth - padding * 2 - buttonMargin) / 2;
// AI改写按钮(带配额提示
// AI改写按钮和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 rewriteBtnText = remaining > 0 ? '✨ AI改写' : '⚠️ 次数不足';
const continueBtnText = remaining > 0 ? '📖 AI续写' : '⚠️ 次数不足';
this.renderGradientButton(ctx, padding, startY, buttonWidth, buttonHeight, rewriteBtnText, ['#a855f7', '#ec4899']);
this.renderGradientButton(ctx, padding + buttonWidth + buttonMargin, startY, buttonWidth, buttonHeight, continueBtnText, ['#10b981', '#059669']);
// 分享按钮
const row2Y = startY + buttonHeight + buttonMargin;
const buttonWidth = (this.screenWidth - padding * 2 - buttonMargin) / 2;
this.renderGradientButton(ctx, padding, row2Y, buttonWidth, buttonHeight, '分享结局', ['#ff6b6b', '#ffd700']);
// 章节选择按钮
@@ -503,6 +514,183 @@ export default class EndingScene extends BaseScene {
this.inputRect = { x: panelX + 15, y: inputY, width: panelWidth - 30, height: inputHeight };
}
renderContinuePanel(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, '#0d2818');
panelGradient.addColorStop(1, '#0a1a10');
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, '#10b981');
borderGradient.addColorStop(1, '#059669');
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(16,185,129,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.continueTagRects = [];
this.continueTags.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.selectedContinueTag;
if (isSelected) {
const tagGradient = ctx.createLinearGradient(currentX, currentY, currentX + tagWidth, currentY);
tagGradient.addColorStop(0, '#10b981');
tagGradient.addColorStop(1, '#059669');
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.continueTagRects.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.continuePrompt) {
ctx.fillStyle = '#ffffff';
ctx.fillText(this.continuePrompt, 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, '#10b981');
confirmGradient.addColorStop(1, '#059669');
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.continueCancelBtnRect = { x: panelX + 15, y: btnY, width: btnWidth, height: btnHeight };
this.continueConfirmBtnRect = { x: panelX + 35 + btnWidth, y: btnY, width: btnWidth, height: btnHeight };
this.continueInputRect = { x: panelX + 15, y: inputY, width: panelWidth - 30, height: inputHeight };
}
// 圆角矩形
roundRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
@@ -610,6 +798,12 @@ export default class EndingScene extends BaseScene {
return;
}
// 如果续写面板打开,优先处理
if (this.showContinuePanel) {
this.handleContinuePanelTouch(x, y);
return;
}
if (!this.showButtons) return;
const padding = 15;
@@ -618,12 +812,18 @@ export default class EndingScene extends BaseScene {
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)) {
// AI改写按钮(左)
if (this.isInRect(x, y, padding, startY, buttonWidth, buttonHeight)) {
this.handleAIRewrite();
return;
}
// AI续写按钮
if (this.isInRect(x, y, padding + buttonWidth + buttonMargin, startY, buttonWidth, buttonHeight)) {
this.handleAIContinue();
return;
}
// 分享按钮
const row2Y = startY + buttonHeight + buttonMargin;
if (this.isInRect(x, y, padding, row2Y, buttonWidth, buttonHeight)) {
@@ -696,6 +896,20 @@ export default class EndingScene extends BaseScene {
this.selectedTag = -1;
}
handleAIContinue() {
// 检查配额
const remaining = this.aiQuota.daily - this.aiQuota.used + this.aiQuota.purchased;
if (remaining <= 0) {
this.showQuotaModal();
return;
}
// 显示AI续写面板
this.showContinuePanel = true;
this.continuePrompt = '';
this.selectedContinueTag = -1;
}
handleRewritePanelTouch(x, y) {
// 点击标签
if (this.tagRects) {
@@ -734,6 +948,44 @@ export default class EndingScene extends BaseScene {
return false;
}
handleContinuePanelTouch(x, y) {
// 点击标签
if (this.continueTagRects) {
for (const tag of this.continueTagRects) {
if (this.isInRect(x, y, tag.x, tag.y, tag.width, tag.height)) {
this.selectedContinueTag = tag.index;
this.continuePrompt = this.continueTags[tag.index];
return true;
}
}
}
// 点击输入框
if (this.continueInputRect && this.isInRect(x, y, this.continueInputRect.x, this.continueInputRect.y, this.continueInputRect.width, this.continueInputRect.height)) {
this.showContinueInput();
return true;
}
// 点击取消
if (this.continueCancelBtnRect && this.isInRect(x, y, this.continueCancelBtnRect.x, this.continueCancelBtnRect.y, this.continueCancelBtnRect.width, this.continueCancelBtnRect.height)) {
this.showContinuePanel = false;
return true;
}
// 点击确认
if (this.continueConfirmBtnRect && this.isInRect(x, y, this.continueConfirmBtnRect.x, this.continueConfirmBtnRect.y, this.continueConfirmBtnRect.width, this.continueConfirmBtnRect.height)) {
if (this.continuePrompt) {
this.showContinuePanel = false;
this.callAIContinue(this.continuePrompt);
} else {
wx.showToast({ title: '请选择或输入续写方向', icon: 'none' });
}
return true;
}
return false;
}
showCustomInput() {
wx.showModal({
title: '输入改写想法',
@@ -749,6 +1001,21 @@ export default class EndingScene extends BaseScene {
});
}
showContinueInput() {
wx.showModal({
title: '输入续写想法',
editable: true,
placeholderText: '例如:主角开启新的冒险',
content: this.continuePrompt,
success: (res) => {
if (res.confirm && res.content) {
this.continuePrompt = res.content;
this.selectedContinueTag = -1;
}
}
});
}
async callAIRewrite(prompt) {
// 检查配额
const remaining = this.aiQuota.daily - this.aiQuota.used + this.aiQuota.purchased;
@@ -758,68 +1025,94 @@ export default class EndingScene extends BaseScene {
}
this.isRewriting = true;
this.rewriteProgress = 0;
// 显示加载动画
wx.showLoading({
title: 'AI创作中...',
title: '提交中...',
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(
const userId = this.main.userManager.userId || 1;
const result = await this.main.storyManager.rewriteEndingAsync(
this.storyId,
this.ending,
prompt
prompt,
userId
);
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;
if (result && result.draftId) {
// 扣除配额
this.aiQuota.used += 1;
// 成功提示
wx.showToast({
title: '改写成功',
icon: 'success',
duration: 1500
// 提交成功提示
wx.showModal({
title: '提交成功',
content: 'AI正在后台生成新结局完成后会通知您。\n您可以在草稿箱中查看。',
showCancel: false,
confirmText: '知道了'
});
// 延迟跳转到故事场景播放新内容
setTimeout(() => {
this.main.sceneManager.switchScene('story', {
storyId: this.storyId,
aiContent: result
});
}, 1500);
} else {
wx.showToast({ title: '改写失败,请重试', icon: 'none' });
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;
}
}
async callAIContinue(prompt) {
// 检查配额
const remaining = this.aiQuota.daily - this.aiQuota.used + this.aiQuota.purchased;
if (remaining <= 0) {
this.showQuotaModal();
return;
}
this.isRewriting = true;
// 显示加载动画
wx.showLoading({
title: '提交中...',
mask: true
});
try {
const userId = this.main.userManager.userId || 1;
const result = await this.main.storyManager.continueEndingAsync(
this.storyId,
this.ending,
prompt,
userId
);
wx.hideLoading();
if (result && result.draftId) {
// 扣除配额
this.aiQuota.used += 1;
// 提交成功提示
wx.showModal({
title: '提交成功',
content: 'AI正在后台生成续写剧情完成后会通知您。\n您可以在草稿箱中查看。',
showCancel: false,
confirmText: '知道了'
});
} else {
wx.showToast({ title: '提交失败,请重试', icon: 'none' });
}
} catch (error) {
wx.hideLoading();
console.error('续写失败:', error);
wx.showToast({ title: error.message || '网络错误', icon: 'none' });
} finally {
this.isRewriting = false;
}
}