feat: 添加测试用户到种子数据, AI改写功能优化, 前端联调修复
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user