feat: AI改写面板样式优化、封面图片显示、max_tokens调整至8192
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
* 个人中心场景 - 支持创作者功能
|
||||
*/
|
||||
import BaseScene from './BaseScene';
|
||||
import { getStaticUrl } from '../utils/http';
|
||||
|
||||
export default class ProfileScene extends BaseScene {
|
||||
constructor(main, params) {
|
||||
@@ -40,6 +41,9 @@ export default class ProfileScene extends BaseScene {
|
||||
this.lastTouchY = 0;
|
||||
this.scrollVelocity = 0;
|
||||
this.hasMoved = false;
|
||||
|
||||
// 封面图片缓存
|
||||
this.coverImages = {}; // { url: Image对象 }
|
||||
}
|
||||
|
||||
async init() {
|
||||
@@ -69,14 +73,45 @@ export default class ProfileScene extends BaseScene {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载封面图片
|
||||
loadCoverImage(url) {
|
||||
if (!url || this.coverImages[url] !== undefined) return;
|
||||
|
||||
// 标记为加载中
|
||||
this.coverImages[url] = null;
|
||||
|
||||
const img = wx.createImage();
|
||||
img.onload = () => {
|
||||
this.coverImages[url] = img;
|
||||
};
|
||||
img.onerror = () => {
|
||||
this.coverImages[url] = false; // 加载失败
|
||||
};
|
||||
|
||||
// 使用 getStaticUrl 处理 URL(与首页一致)
|
||||
img.src = getStaticUrl(url);
|
||||
}
|
||||
|
||||
// 预加载当前列表的封面图片
|
||||
preloadCoverImages() {
|
||||
const list = this.getCurrentList();
|
||||
list.forEach(item => {
|
||||
const coverUrl = item.coverUrl || item.cover_url;
|
||||
if (coverUrl) {
|
||||
this.loadCoverImage(coverUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
if (this.main.userManager.isLoggedIn) {
|
||||
try {
|
||||
const userId = this.main.userManager.userId;
|
||||
// 加载已发布到创作中心的作品(改写+续写)
|
||||
// 加载已发布到创作中心的作品(改写+续写+创作)
|
||||
const publishedRewrites = await this.main.userManager.getPublishedDrafts('rewrite') || [];
|
||||
const publishedContinues = await this.main.userManager.getPublishedDrafts('continue') || [];
|
||||
this.myWorks = [...publishedRewrites, ...publishedContinues];
|
||||
const publishedCreates = await this.main.userManager.getPublishedDrafts('create') || [];
|
||||
this.myWorks = [...publishedRewrites, ...publishedContinues, ...publishedCreates];
|
||||
// 加载 AI 改写草稿
|
||||
this.drafts = await this.main.storyManager.getDrafts(userId) || [];
|
||||
this.collections = await this.main.userManager.getCollections() || [];
|
||||
@@ -93,6 +128,7 @@ export default class ProfileScene extends BaseScene {
|
||||
}
|
||||
}
|
||||
this.calculateMaxScroll();
|
||||
this.preloadCoverImages();
|
||||
}
|
||||
|
||||
// 刷新草稿列表
|
||||
@@ -539,20 +575,59 @@ export default class ProfileScene extends BaseScene {
|
||||
|
||||
// 封面
|
||||
const coverW = 70, coverH = h - 16;
|
||||
const colors = this.getGradientColors(index);
|
||||
const coverGradient = ctx.createLinearGradient(x + 8, y + 8, x + 8 + coverW, y + 8 + coverH);
|
||||
coverGradient.addColorStop(0, colors[0]);
|
||||
coverGradient.addColorStop(1, colors[1]);
|
||||
ctx.fillStyle = coverGradient;
|
||||
this.roundRect(ctx, x + 8, y + 8, coverW, coverH, 10);
|
||||
ctx.fill();
|
||||
const coverX = x + 8, coverY = y + 8;
|
||||
const coverUrl = item.coverUrl || item.cover_url;
|
||||
const coverImg = coverUrl ? this.coverImages[coverUrl] : null;
|
||||
|
||||
// 尝试加载封面图片
|
||||
if (coverUrl && this.coverImages[coverUrl] === undefined) {
|
||||
this.loadCoverImage(coverUrl);
|
||||
}
|
||||
|
||||
// 绘制封面
|
||||
if (coverImg && coverImg !== false) {
|
||||
// 有封面图片,裁剪绘制
|
||||
ctx.save();
|
||||
this.roundRect(ctx, coverX, coverY, coverW, coverH, 10);
|
||||
ctx.clip();
|
||||
|
||||
// 等比例填充
|
||||
const imgRatio = coverImg.width / coverImg.height;
|
||||
const areaRatio = coverW / coverH;
|
||||
let drawW, drawH, drawX, drawY;
|
||||
if (imgRatio > areaRatio) {
|
||||
drawH = coverH;
|
||||
drawW = drawH * imgRatio;
|
||||
drawX = coverX - (drawW - coverW) / 2;
|
||||
drawY = coverY;
|
||||
} else {
|
||||
drawW = coverW;
|
||||
drawH = drawW / imgRatio;
|
||||
drawX = coverX;
|
||||
drawY = coverY - (drawH - coverH) / 2;
|
||||
}
|
||||
ctx.drawImage(coverImg, drawX, drawY, drawW, drawH);
|
||||
ctx.restore();
|
||||
} else {
|
||||
// 无封面图片,使用渐变色
|
||||
const colors = this.getGradientColors(index);
|
||||
const coverGradient = ctx.createLinearGradient(coverX, coverY, coverX + coverW, coverY + coverH);
|
||||
coverGradient.addColorStop(0, colors[0]);
|
||||
coverGradient.addColorStop(1, colors[1]);
|
||||
ctx.fillStyle = coverGradient;
|
||||
this.roundRect(ctx, coverX, coverY, coverW, coverH, 10);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// 类型标签
|
||||
const typeText = item.draftType === 'continue' ? '续写' : '改写';
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
||||
const typeText = item.draftType === 'continue' ? '续写' : (item.draftType === 'create' ? '创作' : '改写');
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.5)';
|
||||
this.roundRect(ctx, coverX, coverY, 32, 18, 6);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.font = 'bold 9px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(typeText, x + 8 + coverW / 2, y + 8 + coverH / 2 + 3);
|
||||
ctx.fillText(typeText, coverX + 16, coverY + 13);
|
||||
|
||||
const textX = x + 88;
|
||||
const maxW = w - 100;
|
||||
@@ -618,22 +693,57 @@ export default class ProfileScene extends BaseScene {
|
||||
ctx.fill();
|
||||
|
||||
const coverW = 70, coverH = h - 16;
|
||||
const colors = this.getGradientColors(index);
|
||||
const coverGradient = ctx.createLinearGradient(x + 8, y + 8, x + 8 + coverW, y + 8 + coverH);
|
||||
coverGradient.addColorStop(0, colors[0]);
|
||||
coverGradient.addColorStop(1, colors[1]);
|
||||
ctx.fillStyle = coverGradient;
|
||||
this.roundRect(ctx, x + 8, y + 8, coverW, coverH, 10);
|
||||
ctx.fill();
|
||||
const coverX = x + 8, coverY = y + 8;
|
||||
const coverUrl = item.coverUrl || item.cover_url;
|
||||
const coverImg = coverUrl ? this.coverImages[coverUrl] : null;
|
||||
|
||||
// 尝试加载封面图片
|
||||
if (coverUrl && this.coverImages[coverUrl] === undefined) {
|
||||
this.loadCoverImage(coverUrl);
|
||||
}
|
||||
|
||||
// 绘制封面
|
||||
if (coverImg && coverImg !== false) {
|
||||
// 有封面图片,裁剪绘制
|
||||
ctx.save();
|
||||
this.roundRect(ctx, coverX, coverY, coverW, coverH, 10);
|
||||
ctx.clip();
|
||||
|
||||
const imgRatio = coverImg.width / coverImg.height;
|
||||
const areaRatio = coverW / coverH;
|
||||
let drawW, drawH, drawX, drawY;
|
||||
if (imgRatio > areaRatio) {
|
||||
drawH = coverH;
|
||||
drawW = drawH * imgRatio;
|
||||
drawX = coverX - (drawW - coverW) / 2;
|
||||
drawY = coverY;
|
||||
} else {
|
||||
drawW = coverW;
|
||||
drawH = drawW / imgRatio;
|
||||
drawX = coverX;
|
||||
drawY = coverY - (drawH - coverH) / 2;
|
||||
}
|
||||
ctx.drawImage(coverImg, drawX, drawY, drawW, drawH);
|
||||
ctx.restore();
|
||||
} else {
|
||||
// 无封面图片,使用渐变色
|
||||
const colors = this.getGradientColors(index);
|
||||
const coverGradient = ctx.createLinearGradient(coverX, coverY, coverX + coverW, coverY + coverH);
|
||||
coverGradient.addColorStop(0, colors[0]);
|
||||
coverGradient.addColorStop(1, colors[1]);
|
||||
ctx.fillStyle = coverGradient;
|
||||
this.roundRect(ctx, coverX, coverY, coverW, coverH, 10);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// AI标签
|
||||
ctx.fillStyle = '#a855f7';
|
||||
this.roundRect(ctx, x + 8, y + 8, 28, 16, 8);
|
||||
this.roundRect(ctx, coverX, coverY, 28, 16, 8);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.font = 'bold 9px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('AI', x + 22, y + 19);
|
||||
ctx.fillText('AI', coverX + 14, coverY + 11);
|
||||
|
||||
const textX = x + 88;
|
||||
|
||||
@@ -733,18 +843,52 @@ export default class ProfileScene extends BaseScene {
|
||||
ctx.fill();
|
||||
|
||||
const coverW = 60, coverH = h - 16;
|
||||
const colors = this.getGradientColors(index);
|
||||
const coverGradient = ctx.createLinearGradient(x + 8, y + 8, x + 8 + coverW, y + 8 + coverH);
|
||||
coverGradient.addColorStop(0, colors[0]);
|
||||
coverGradient.addColorStop(1, colors[1]);
|
||||
ctx.fillStyle = coverGradient;
|
||||
this.roundRect(ctx, x + 8, y + 8, coverW, coverH, 8);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.85)';
|
||||
ctx.font = 'bold 9px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(item.category || '故事', x + 8 + coverW / 2, y + 8 + coverH / 2 + 3);
|
||||
const coverX = x + 8, coverY = y + 8;
|
||||
const coverUrl = item.coverUrl || item.cover_url;
|
||||
const coverImg = coverUrl ? this.coverImages[coverUrl] : null;
|
||||
|
||||
// 尝试加载封面图片
|
||||
if (coverUrl && this.coverImages[coverUrl] === undefined) {
|
||||
this.loadCoverImage(coverUrl);
|
||||
}
|
||||
|
||||
// 绘制封面
|
||||
if (coverImg && coverImg !== false) {
|
||||
ctx.save();
|
||||
this.roundRect(ctx, coverX, coverY, coverW, coverH, 8);
|
||||
ctx.clip();
|
||||
|
||||
const imgRatio = coverImg.width / coverImg.height;
|
||||
const areaRatio = coverW / coverH;
|
||||
let drawW, drawH, drawX, drawY;
|
||||
if (imgRatio > areaRatio) {
|
||||
drawH = coverH;
|
||||
drawW = drawH * imgRatio;
|
||||
drawX = coverX - (drawW - coverW) / 2;
|
||||
drawY = coverY;
|
||||
} else {
|
||||
drawW = coverW;
|
||||
drawH = drawW / imgRatio;
|
||||
drawX = coverX;
|
||||
drawY = coverY - (drawH - coverH) / 2;
|
||||
}
|
||||
ctx.drawImage(coverImg, drawX, drawY, drawW, drawH);
|
||||
ctx.restore();
|
||||
} else {
|
||||
const colors = this.getGradientColors(index);
|
||||
const coverGradient = ctx.createLinearGradient(coverX, coverY, coverX + coverW, coverY + coverH);
|
||||
coverGradient.addColorStop(0, colors[0]);
|
||||
coverGradient.addColorStop(1, colors[1]);
|
||||
ctx.fillStyle = coverGradient;
|
||||
this.roundRect(ctx, coverX, coverY, coverW, coverH, 8);
|
||||
ctx.fill();
|
||||
|
||||
// 无图片时显示分类
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.85)';
|
||||
ctx.font = 'bold 9px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(item.category || '故事', coverX + coverW / 2, coverY + coverH / 2 + 3);
|
||||
}
|
||||
|
||||
const textX = x + 78;
|
||||
|
||||
@@ -967,7 +1111,11 @@ export default class ProfileScene extends BaseScene {
|
||||
// 检测播放按钮点击(仅已完成状态)
|
||||
if (item.status === 'completed') {
|
||||
if (x >= btnStartX && x <= btnStartX + 50 && relativeY >= btnY && relativeY <= btnY + btnH) {
|
||||
this.main.sceneManager.switchScene('story', { storyId: item.storyId, draftId: item.id });
|
||||
this.main.sceneManager.switchScene('story', {
|
||||
storyId: item.storyId,
|
||||
draftId: item.id,
|
||||
draftType: item.draftType || item.draft_type
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -983,7 +1131,11 @@ export default class ProfileScene extends BaseScene {
|
||||
|
||||
// 点击卡片其他区域
|
||||
if (item.status === 'completed') {
|
||||
this.main.sceneManager.switchScene('story', { storyId: item.storyId, draftId: item.id });
|
||||
this.main.sceneManager.switchScene('story', {
|
||||
storyId: item.storyId,
|
||||
draftId: item.id,
|
||||
draftType: item.draftType || item.draft_type
|
||||
});
|
||||
} else if (item.status === 'failed') {
|
||||
wx.showToast({ title: 'AI改写失败', icon: 'none' });
|
||||
} else {
|
||||
@@ -1057,12 +1209,20 @@ export default class ProfileScene extends BaseScene {
|
||||
// 检测播放按钮点击
|
||||
const playBtnX = padding + cardW - 58;
|
||||
if (x >= playBtnX && x <= playBtnX + 48 && relativeY >= btnY && relativeY <= btnY + btnH) {
|
||||
this.main.sceneManager.switchScene('story', { storyId: item.storyId, draftId: item.id });
|
||||
this.main.sceneManager.switchScene('story', {
|
||||
storyId: item.storyId,
|
||||
draftId: item.id,
|
||||
draftType: item.draftType || item.draft_type
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 点击卡片其他区域也进入播放
|
||||
this.main.sceneManager.switchScene('story', { storyId: item.storyId, draftId: item.id });
|
||||
this.main.sceneManager.switchScene('story', {
|
||||
storyId: item.storyId,
|
||||
draftId: item.id,
|
||||
draftType: item.draftType || item.draft_type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default class StoryScene extends BaseScene {
|
||||
super(main, params);
|
||||
this.storyId = params.storyId;
|
||||
this.draftId = params.draftId || null; // 草稿ID
|
||||
this.draftType = params.draftType || null; // 草稿类型:'create' | 'rewrite' | 'continue'
|
||||
this.playRecordId = params.playRecordId || null; // 游玩记录ID(从记录回放)
|
||||
this.aiContent = params.aiContent || null; // AI改写内容
|
||||
this.story = null;
|
||||
@@ -39,6 +40,14 @@ export default class StoryScene extends BaseScene {
|
||||
this.currentCharacterImg = null;
|
||||
// AI改写相关
|
||||
this.isAIRewriting = false;
|
||||
this.showRewritePanel = false; // 显示改写面板
|
||||
this.rewritePrompt = ''; // 改写输入内容
|
||||
this.selectedRewriteTag = -1; // 选中的快捷标签
|
||||
this.rewriteTags = ['剧情反转', '主角逆袭', '意外相遇', '真相揭露', '危机来临']; // 快捷标签
|
||||
this.rewriteTagRects = []; // 标签位置
|
||||
this.rewriteInputRect = null; // 输入框位置
|
||||
this.rewriteCancelBtn = null; // 取消按钮位置
|
||||
this.rewriteConfirmBtn = null; // 确认按钮位置
|
||||
// 剧情回顾模式
|
||||
this.isRecapMode = false;
|
||||
this.recapData = null;
|
||||
@@ -130,7 +139,7 @@ export default class StoryScene extends BaseScene {
|
||||
|
||||
// 如果是从Draft加载,先获取草稿详情,进入回顾模式
|
||||
if (this.draftId) {
|
||||
this.main.showLoading('加载AI改写内容...');
|
||||
this.main.showLoading('加载AI内容...');
|
||||
|
||||
const draft = await this.main.storyManager.getDraftDetail(this.draftId);
|
||||
|
||||
@@ -139,9 +148,44 @@ export default class StoryScene extends BaseScene {
|
||||
hasAiNodes: !!draft?.aiNodes,
|
||||
aiNodesKeys: draft?.aiNodes ? Object.keys(draft.aiNodes) : [],
|
||||
entryNodeKey: draft?.entryNodeKey,
|
||||
pathHistoryLength: draft?.pathHistory?.length
|
||||
pathHistoryLength: draft?.pathHistory?.length,
|
||||
draftType: this.draftType
|
||||
}));
|
||||
|
||||
// AI创作类型:ai_nodes 包含完整故事,不需要加载原故事
|
||||
if (this.draftType === 'create' && draft && draft.aiNodes) {
|
||||
// 构建虚拟故事对象
|
||||
this.story = {
|
||||
id: this.draftId,
|
||||
title: draft.title || '未命名故事',
|
||||
category: draft.aiNodes.category || '冒险',
|
||||
nodes: draft.aiNodes.nodes || {},
|
||||
characters: draft.aiNodes.characters || []
|
||||
};
|
||||
|
||||
// 设置到 storyManager,使选项选择能正常工作
|
||||
this.main.storyManager.currentStory = this.story;
|
||||
this.main.storyManager.pathHistory = [];
|
||||
|
||||
this.setThemeByCategory(this.story.category);
|
||||
|
||||
// 从起始节点开始播放
|
||||
const startKey = draft.entryNodeKey || draft.aiNodes.startNodeKey || 'start';
|
||||
this.main.storyManager.currentNodeKey = startKey;
|
||||
this.currentNode = this.story.nodes[startKey];
|
||||
|
||||
if (this.currentNode) {
|
||||
this.main.hideLoading();
|
||||
this.startTypewriter(this.currentNode.content);
|
||||
return;
|
||||
}
|
||||
|
||||
this.main.hideLoading();
|
||||
this.main.showError('故事内容加载失败');
|
||||
this.main.sceneManager.switchScene('aiCreate');
|
||||
return;
|
||||
}
|
||||
|
||||
if (draft && draft.aiNodes && draft.storyId) {
|
||||
// 先加载原故事
|
||||
this.story = await this.main.storyManager.loadStoryDetail(draft.storyId);
|
||||
@@ -662,11 +706,14 @@ export default class StoryScene extends BaseScene {
|
||||
// 获取背景图 URL
|
||||
let bgUrl;
|
||||
if (isDraftMode) {
|
||||
// 草稿模式:优先使用节点中的 background_url(需要转成完整URL),否则用草稿路径
|
||||
// 草稿模式:
|
||||
// 1. AI生成的节点有 background_url,使用它
|
||||
// 2. 历史节点没有 background_url,使用原故事的图片路径
|
||||
if (this.currentNode.background_url) {
|
||||
bgUrl = getStaticUrl(this.currentNode.background_url);
|
||||
} else {
|
||||
bgUrl = getDraftNodeBackground(this.storyId, this.draftId, nodeKey);
|
||||
// 历史节点使用原故事的背景图
|
||||
bgUrl = getNodeBackground(this.storyId, nodeKey);
|
||||
}
|
||||
} else {
|
||||
// 普通模式:使用故事节点路径
|
||||
@@ -769,6 +816,11 @@ export default class StoryScene extends BaseScene {
|
||||
ctx.fillStyle = `rgba(0, 0, 0, ${this.fadeAlpha})`;
|
||||
ctx.fillRect(0, 0, this.screenWidth, this.screenHeight);
|
||||
}
|
||||
|
||||
// 7. AI改写面板(最顶层)
|
||||
if (this.showRewritePanel) {
|
||||
this.renderRewritePanel(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
renderSceneBackground(ctx) {
|
||||
@@ -1242,6 +1294,12 @@ export default class StoryScene extends BaseScene {
|
||||
return;
|
||||
}
|
||||
|
||||
// AI改写面板的点击处理(最优先)
|
||||
if (this.showRewritePanel) {
|
||||
this.handleRewritePanelTouch(x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
// 回顾模式下的点击处理
|
||||
if (this.isRecapMode) {
|
||||
// 返回按钮
|
||||
@@ -1351,6 +1409,25 @@ export default class StoryScene extends BaseScene {
|
||||
return;
|
||||
}
|
||||
|
||||
// AI创作模式下,检查当前节点是否是结局(即使没有 is_ending 标记)
|
||||
if (!this.isReplayMode && this.draftType === 'create' && this.currentNode) {
|
||||
// 没有选项或 is_ending=true 都视为结局
|
||||
if (!this.currentNode.choices || this.currentNode.choices.length === 0 || this.currentNode.is_ending) {
|
||||
console.log('[AI创作] 到达结局节点:', this.currentNode);
|
||||
this.main.sceneManager.switchScene('ending', {
|
||||
storyId: this.storyId,
|
||||
draftId: this.draftId,
|
||||
ending: {
|
||||
name: this.currentNode.ending_name || '故事结局',
|
||||
type: this.currentNode.ending_type || 'neutral',
|
||||
content: this.currentNode.content,
|
||||
score: this.currentNode.ending_score || 70
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 回放模式下,如果回放路径已用完或到达原结局
|
||||
if (this.isReplayMode) {
|
||||
const currentNode = this.main.storyManager.getCurrentNode();
|
||||
@@ -1490,19 +1567,12 @@ export default class StoryScene extends BaseScene {
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示AI改写输入框
|
||||
* 显示AI改写面板
|
||||
*/
|
||||
showAIRewriteInput() {
|
||||
wx.showModal({
|
||||
title: 'AI改写剧情',
|
||||
editable: true,
|
||||
placeholderText: '输入你的改写指令,如"让主角暴富"',
|
||||
success: (res) => {
|
||||
if (res.confirm && res.content) {
|
||||
this.doAIRewriteAsync(res.content);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.showRewritePanel = true;
|
||||
this.rewritePrompt = '';
|
||||
this.selectedRewriteTag = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1553,6 +1623,246 @@ export default class StoryScene extends BaseScene {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染AI改写面板(类似结局页的样式)
|
||||
*/
|
||||
renderRewritePanel(ctx) {
|
||||
const padding = 20;
|
||||
const panelWidth = this.screenWidth - padding * 2;
|
||||
const panelHeight = 400;
|
||||
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);
|
||||
|
||||
// 副标题
|
||||
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 + 100);
|
||||
|
||||
// 快捷标签
|
||||
const tagStartX = panelX + 15;
|
||||
const tagY = panelY + 115;
|
||||
const tagHeight = 32;
|
||||
const tagGap = 8;
|
||||
let currentX = tagStartX;
|
||||
let currentY = tagY;
|
||||
|
||||
this.rewriteTagRects = [];
|
||||
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.selectedRewriteTag;
|
||||
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.rewriteTagRects.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 + 200);
|
||||
|
||||
// 输入框背景
|
||||
const inputY = panelY + 215;
|
||||
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();
|
||||
|
||||
// 存储输入框位置
|
||||
this.rewriteInputRect = { x: panelX + 15, y: inputY, width: panelWidth - 30, height: inputHeight };
|
||||
|
||||
// 输入框文字或占位符
|
||||
ctx.font = '14px sans-serif';
|
||||
ctx.textAlign = 'left';
|
||||
if (this.rewritePrompt) {
|
||||
ctx.fillStyle = '#ffffff';
|
||||
const displayText = this.rewritePrompt.length > 20 ? this.rewritePrompt.substring(0, 20) + '...' : this.rewritePrompt;
|
||||
ctx.fillText(displayText, 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 = 45;
|
||||
|
||||
// 取消按钮
|
||||
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 = '15px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('取消', panelX + 15 + btnWidth / 2, btnY + 28);
|
||||
this.rewriteCancelBtn = { x: panelX + 15, y: btnY, width: btnWidth, height: btnHeight };
|
||||
|
||||
// 确认按钮
|
||||
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 15px sans-serif';
|
||||
ctx.fillText('✨ 开始改写', panelX + 35 + btnWidth + btnWidth / 2, btnY + 28);
|
||||
this.rewriteConfirmBtn = { x: panelX + 35 + btnWidth, y: btnY, width: btnWidth, height: btnHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理改写面板的触摸事件
|
||||
*/
|
||||
handleRewritePanelTouch(x, y) {
|
||||
// 点击标签
|
||||
for (const tag of this.rewriteTagRects) {
|
||||
if (x >= tag.x && x <= tag.x + tag.width && y >= tag.y && y <= tag.y + tag.height) {
|
||||
this.selectedRewriteTag = tag.index;
|
||||
this.rewritePrompt = this.rewriteTags[tag.index];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 点击输入框
|
||||
if (this.rewriteInputRect) {
|
||||
const r = this.rewriteInputRect;
|
||||
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height) {
|
||||
this.showCustomRewriteInput();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 点击取消
|
||||
if (this.rewriteCancelBtn) {
|
||||
const r = this.rewriteCancelBtn;
|
||||
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height) {
|
||||
this.showRewritePanel = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 点击确认
|
||||
if (this.rewriteConfirmBtn) {
|
||||
const r = this.rewriteConfirmBtn;
|
||||
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height) {
|
||||
if (this.rewritePrompt) {
|
||||
this.showRewritePanel = false;
|
||||
this.doAIRewriteAsync(this.rewritePrompt);
|
||||
} else {
|
||||
wx.showToast({ title: '请选择或输入改写内容', icon: 'none' });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示自定义输入弹窗
|
||||
*/
|
||||
showCustomRewriteInput() {
|
||||
wx.showModal({
|
||||
title: '输入改写想法',
|
||||
editable: true,
|
||||
placeholderText: '例如:让主角获得逆袭',
|
||||
content: this.rewritePrompt,
|
||||
success: (res) => {
|
||||
if (res.confirm && res.content) {
|
||||
this.rewritePrompt = res.content;
|
||||
this.selectedRewriteTag = -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.main.userManager.isLoggedIn && this.story) {
|
||||
this.main.userManager.saveProgress(
|
||||
|
||||
Reference in New Issue
Block a user