refactor: 首页支持UGC,新增内容源Tab和作者信息
This commit is contained in:
@@ -216,16 +216,17 @@ export default class AICreateScene extends BaseScene {
|
|||||||
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
||||||
ctx.font = '12px sans-serif';
|
ctx.font = '12px sans-serif';
|
||||||
ctx.textAlign = 'left';
|
ctx.textAlign = 'left';
|
||||||
ctx.fillText('热门改写方向:', padding, y + 60);
|
ctx.fillText('热门改写方向:', padding, y + 55);
|
||||||
|
|
||||||
this.renderTags(ctx, this.rewriteTags, padding, y + 75, 'rewrite');
|
const tagEndY = this.renderTags(ctx, this.rewriteTags, padding, y + 70, 'rewrite');
|
||||||
|
|
||||||
// 选择故事
|
// 选择故事 - 位置根据标签高度动态调整
|
||||||
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
||||||
ctx.font = '13px sans-serif';
|
ctx.font = '13px sans-serif';
|
||||||
ctx.fillText('选择要改写的故事:', padding, y + 145);
|
ctx.textAlign = 'left';
|
||||||
|
ctx.fillText('选择要改写的故事:', padding, tagEndY + 25);
|
||||||
|
|
||||||
this.renderStoryList(ctx, y + 160, 'rewrite');
|
this.renderStoryList(ctx, tagEndY + 40, 'rewrite');
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContinueTab(ctx, startY) {
|
renderContinueTab(ctx, startY) {
|
||||||
@@ -240,15 +241,16 @@ export default class AICreateScene extends BaseScene {
|
|||||||
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
||||||
ctx.font = '12px sans-serif';
|
ctx.font = '12px sans-serif';
|
||||||
ctx.textAlign = 'left';
|
ctx.textAlign = 'left';
|
||||||
ctx.fillText('续写方向:', padding, y + 60);
|
ctx.fillText('续写方向:', padding, y + 55);
|
||||||
|
|
||||||
this.renderTags(ctx, this.continueTags, padding, y + 75, 'continue');
|
const tagEndY = this.renderTags(ctx, this.continueTags, padding, y + 70, 'continue');
|
||||||
|
|
||||||
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
||||||
ctx.font = '13px sans-serif';
|
ctx.font = '13px sans-serif';
|
||||||
ctx.fillText('选择要续写的故事:', padding, y + 145);
|
ctx.textAlign = 'left';
|
||||||
|
ctx.fillText('选择要续写的故事:', padding, tagEndY + 25);
|
||||||
|
|
||||||
this.renderStoryList(ctx, y + 160, 'continue');
|
this.renderStoryList(ctx, tagEndY + 40, 'continue');
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCreateTab(ctx, startY) {
|
renderCreateTab(ctx, startY) {
|
||||||
@@ -265,24 +267,27 @@ export default class AICreateScene extends BaseScene {
|
|||||||
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
||||||
ctx.font = '13px sans-serif';
|
ctx.font = '13px sans-serif';
|
||||||
ctx.textAlign = 'left';
|
ctx.textAlign = 'left';
|
||||||
ctx.fillText('选择题材:', padding, y + 60);
|
ctx.fillText('选择题材:', padding, y + 55);
|
||||||
|
|
||||||
this.renderTags(ctx, this.genreTags, padding, y + 75, 'genre');
|
const tagEndY = this.renderTags(ctx, this.genreTags, padding, y + 70, 'genre');
|
||||||
|
|
||||||
// 关键词输入
|
// 关键词输入
|
||||||
ctx.fillText('故事关键词:', padding, y + 145);
|
let currentY = tagEndY + 25;
|
||||||
this.renderInputBox(ctx, padding, y + 160, inputWidth, 45, this.createForm.keywords || '例如:霸总、契约婚姻、追妻火葬场', 'keywords');
|
ctx.fillText('故事关键词:', padding, currentY);
|
||||||
|
this.renderInputBox(ctx, padding, currentY + 15, inputWidth, 45, this.createForm.keywords || '例如:霸总、契约婚姻、追妻火葬场', 'keywords');
|
||||||
|
|
||||||
// 主角设定
|
// 主角设定
|
||||||
ctx.fillText('主角设定:', padding, y + 225);
|
currentY += 80;
|
||||||
this.renderInputBox(ctx, padding, y + 240, inputWidth, 45, this.createForm.protagonist || '例如:独立女性设计师', 'protagonist');
|
ctx.fillText('主角设定:', padding, currentY);
|
||||||
|
this.renderInputBox(ctx, padding, currentY + 15, inputWidth, 45, this.createForm.protagonist || '例如:独立女性设计师', 'protagonist');
|
||||||
|
|
||||||
// 核心冲突
|
// 核心冲突
|
||||||
ctx.fillText('核心冲突:', padding, y + 305);
|
currentY += 80;
|
||||||
this.renderInputBox(ctx, padding, y + 320, inputWidth, 45, this.createForm.conflict || '例如:假结婚变真爱', 'conflict');
|
ctx.fillText('核心冲突:', padding, currentY);
|
||||||
|
this.renderInputBox(ctx, padding, currentY + 15, inputWidth, 45, this.createForm.conflict || '例如:假结婚变真爱', 'conflict');
|
||||||
|
|
||||||
// 开始创作按钮
|
// 开始创作按钮
|
||||||
const btnY = y + 400;
|
const btnY = currentY + 85;
|
||||||
const btnGradient = ctx.createLinearGradient(padding, btnY, this.screenWidth - padding, btnY);
|
const btnGradient = ctx.createLinearGradient(padding, btnY, this.screenWidth - padding, btnY);
|
||||||
btnGradient.addColorStop(0, '#a855f7');
|
btnGradient.addColorStop(0, '#a855f7');
|
||||||
btnGradient.addColorStop(1, '#ec4899');
|
btnGradient.addColorStop(1, '#ec4899');
|
||||||
@@ -303,6 +308,7 @@ export default class AICreateScene extends BaseScene {
|
|||||||
const tagGap = 8;
|
const tagGap = 8;
|
||||||
let currentX = startX;
|
let currentX = startX;
|
||||||
let currentY = startY;
|
let currentY = startY;
|
||||||
|
let maxY = startY + tagHeight; // 记录最大Y坐标
|
||||||
|
|
||||||
if (!this.tagRects) this.tagRects = {};
|
if (!this.tagRects) this.tagRects = {};
|
||||||
this.tagRects[type] = [];
|
this.tagRects[type] = [];
|
||||||
@@ -314,6 +320,7 @@ export default class AICreateScene extends BaseScene {
|
|||||||
if (currentX + tagWidth > this.screenWidth - 15) {
|
if (currentX + tagWidth > this.screenWidth - 15) {
|
||||||
currentX = startX;
|
currentX = startX;
|
||||||
currentY += tagHeight + tagGap;
|
currentY += tagHeight + tagGap;
|
||||||
|
maxY = currentY + tagHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSelected = (type === 'genre' && this.createForm.genre === tag) ||
|
const isSelected = (type === 'genre' && this.createForm.genre === tag) ||
|
||||||
@@ -353,6 +360,8 @@ export default class AICreateScene extends BaseScene {
|
|||||||
|
|
||||||
currentX += tagWidth + tagGap;
|
currentX += tagWidth + tagGap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return maxY; // 返回标签区域结束的Y坐标
|
||||||
}
|
}
|
||||||
|
|
||||||
renderInputBox(ctx, x, y, width, height, placeholder, field) {
|
renderInputBox(ctx, x, y, width, height, placeholder, field) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* 首页场景
|
* 首页场景 - 支持UGC
|
||||||
*/
|
*/
|
||||||
import BaseScene from './BaseScene';
|
import BaseScene from './BaseScene';
|
||||||
|
|
||||||
@@ -12,63 +12,84 @@ export default class HomeScene extends BaseScene {
|
|||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
this.lastTouchY = 0;
|
this.lastTouchY = 0;
|
||||||
this.scrollVelocity = 0;
|
this.scrollVelocity = 0;
|
||||||
this.currentTab = 0; // 0: 首页, 1: 发现, 2: 我的
|
|
||||||
|
// 底部Tab: 首页/发现/创作/我的
|
||||||
|
this.bottomTab = 0;
|
||||||
|
|
||||||
|
// 内容源Tab: 推荐/关注/最新/排行
|
||||||
|
this.contentTab = 0;
|
||||||
|
this.contentTabs = ['推荐', '关注', '最新', '排行'];
|
||||||
|
|
||||||
|
// 分类标签
|
||||||
this.categories = ['全部', '都市言情', '悬疑推理', '古风宫廷', '校园青春', '修仙玄幻', '穿越重生', '职场商战', '科幻未来', '恐怖惊悚', '搞笑轻喜'];
|
this.categories = ['全部', '都市言情', '悬疑推理', '古风宫廷', '校园青春', '修仙玄幻', '穿越重生', '职场商战', '科幻未来', '恐怖惊悚', '搞笑轻喜'];
|
||||||
this.selectedCategory = 0;
|
this.selectedCategory = 0;
|
||||||
|
|
||||||
// 分类横向滚动
|
// 分类横向滚动
|
||||||
this.categoryScrollX = 0;
|
this.categoryScrollX = 0;
|
||||||
this.maxCategoryScrollX = 0;
|
this.maxCategoryScrollX = 0;
|
||||||
this.isCategoryDragging = false;
|
this.isCategoryDragging = false;
|
||||||
this.lastTouchX = 0;
|
this.lastTouchX = 0;
|
||||||
// 存储分类标签位置(用于点击判定)
|
|
||||||
this.categoryRects = [];
|
this.categoryRects = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
// 加载故事列表
|
|
||||||
this.storyList = this.main.storyManager.storyList;
|
this.storyList = this.main.storyManager.storyList;
|
||||||
this.calculateMaxScroll();
|
this.calculateMaxScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取过滤后的故事列表
|
|
||||||
getFilteredStories() {
|
getFilteredStories() {
|
||||||
if (this.selectedCategory === 0) {
|
let stories = this.storyList;
|
||||||
return this.storyList; // 全部
|
|
||||||
|
// 按内容源筛选
|
||||||
|
if (this.contentTab === 1) {
|
||||||
|
// 关注:暂时返回空(需要后端支持)
|
||||||
|
stories = [];
|
||||||
|
} else if (this.contentTab === 2) {
|
||||||
|
// 最新:按时间排序
|
||||||
|
stories = [...stories].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||||
|
} else if (this.contentTab === 3) {
|
||||||
|
// 排行:按播放量排序
|
||||||
|
stories = [...stories].sort((a, b) => b.play_count - a.play_count);
|
||||||
}
|
}
|
||||||
const categoryName = this.categories[this.selectedCategory];
|
|
||||||
return this.storyList.filter(s => s.category === categoryName);
|
// 按分类筛选
|
||||||
|
if (this.selectedCategory > 0) {
|
||||||
|
const categoryName = this.categories[this.selectedCategory];
|
||||||
|
stories = stories.filter(s => s.category === categoryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stories;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateMaxScroll() {
|
calculateMaxScroll() {
|
||||||
// 计算最大滚动距离
|
|
||||||
const stories = this.getFilteredStories();
|
const stories = this.getFilteredStories();
|
||||||
const cardHeight = 120;
|
const cardHeight = 130;
|
||||||
const gap = 15;
|
const gap = 12;
|
||||||
const startY = 150; // 故事列表起始位置
|
const startY = 175;
|
||||||
const tabHeight = 65;
|
const tabHeight = 60;
|
||||||
|
|
||||||
// 内容总高度
|
|
||||||
const contentBottom = startY + stories.length * (cardHeight + gap);
|
const contentBottom = startY + stories.length * (cardHeight + gap);
|
||||||
// 可视区域底部(减去底部Tab栏)
|
|
||||||
const visibleBottom = this.screenHeight - tabHeight;
|
const visibleBottom = this.screenHeight - tabHeight;
|
||||||
|
|
||||||
// 最大滚动距离 = 内容超出可视区域的部分
|
|
||||||
this.maxScrollY = Math.max(0, contentBottom - visibleBottom);
|
this.maxScrollY = Math.max(0, contentBottom - visibleBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
// 滚动惯性
|
|
||||||
if (!this.isDragging && Math.abs(this.scrollVelocity) > 0.5) {
|
if (!this.isDragging && Math.abs(this.scrollVelocity) > 0.5) {
|
||||||
this.scrollY += this.scrollVelocity;
|
this.scrollY += this.scrollVelocity;
|
||||||
this.scrollVelocity *= 0.95;
|
this.scrollVelocity *= 0.95;
|
||||||
|
|
||||||
// 边界检查
|
|
||||||
this.scrollY = Math.max(0, Math.min(this.scrollY, this.maxScrollY));
|
this.scrollY = Math.max(0, Math.min(this.scrollY, this.maxScrollY));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render(ctx) {
|
render(ctx) {
|
||||||
// 绘制背景渐变(深紫蓝色调)
|
this.renderBackground(ctx);
|
||||||
|
this.renderHeader(ctx);
|
||||||
|
this.renderContentTabs(ctx);
|
||||||
|
this.renderCategories(ctx);
|
||||||
|
this.renderStoryList(ctx);
|
||||||
|
this.renderBottomTabBar(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBackground(ctx) {
|
||||||
const gradient = ctx.createLinearGradient(0, 0, 0, this.screenHeight);
|
const gradient = ctx.createLinearGradient(0, 0, 0, this.screenHeight);
|
||||||
gradient.addColorStop(0, '#0f0c29');
|
gradient.addColorStop(0, '#0f0c29');
|
||||||
gradient.addColorStop(0.5, '#302b63');
|
gradient.addColorStop(0.5, '#302b63');
|
||||||
@@ -76,26 +97,9 @@ export default class HomeScene extends BaseScene {
|
|||||||
ctx.fillStyle = gradient;
|
ctx.fillStyle = gradient;
|
||||||
ctx.fillRect(0, 0, this.screenWidth, this.screenHeight);
|
ctx.fillRect(0, 0, this.screenWidth, this.screenHeight);
|
||||||
|
|
||||||
// 添加星空装饰点
|
// 星空点缀
|
||||||
this.renderStars(ctx);
|
|
||||||
|
|
||||||
// 绘制顶部标题栏
|
|
||||||
this.renderHeader(ctx);
|
|
||||||
|
|
||||||
// 绘制分类标签
|
|
||||||
this.renderCategories(ctx);
|
|
||||||
|
|
||||||
// 绘制故事列表
|
|
||||||
this.renderStoryList(ctx);
|
|
||||||
|
|
||||||
// 绘制底部Tab栏
|
|
||||||
this.renderTabBar(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderStars(ctx) {
|
|
||||||
ctx.fillStyle = 'rgba(255,255,255,0.3)';
|
ctx.fillStyle = 'rgba(255,255,255,0.3)';
|
||||||
const stars = [[30, 80], [80, 30], [150, 60], [200, 25], [280, 70], [320, 40]];
|
[[30, 80], [80, 30], [150, 60], [200, 25], [280, 70], [320, 40]].forEach(([x, y]) => {
|
||||||
stars.forEach(([x, y]) => {
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(x, y, 1, 0, Math.PI * 2);
|
ctx.arc(x, y, 1, 0, Math.PI * 2);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
@@ -103,128 +107,129 @@ export default class HomeScene extends BaseScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderHeader(ctx) {
|
renderHeader(ctx) {
|
||||||
// 标题带渐变
|
// 标题
|
||||||
const titleGradient = ctx.createLinearGradient(20, 30, 200, 30);
|
const titleGradient = ctx.createLinearGradient(15, 30, 180, 30);
|
||||||
titleGradient.addColorStop(0, '#ffd700');
|
titleGradient.addColorStop(0, '#ffd700');
|
||||||
titleGradient.addColorStop(1, '#ff6b6b');
|
titleGradient.addColorStop(1, '#ff6b6b');
|
||||||
ctx.fillStyle = titleGradient;
|
ctx.fillStyle = titleGradient;
|
||||||
ctx.font = 'bold 26px sans-serif';
|
ctx.font = 'bold 22px sans-serif';
|
||||||
ctx.textAlign = 'left';
|
ctx.textAlign = 'left';
|
||||||
ctx.fillText('星域故事汇', 20, 50);
|
ctx.fillText('星域故事汇', 15, 42);
|
||||||
|
|
||||||
// 副标题
|
// 搜索按钮
|
||||||
ctx.fillStyle = 'rgba(255,255,255,0.6)';
|
const searchX = this.screenWidth - 45;
|
||||||
ctx.font = '13px sans-serif';
|
ctx.fillStyle = 'rgba(255,255,255,0.1)';
|
||||||
ctx.fillText('每个选择,都是一个新世界', 20, 75);
|
ctx.beginPath();
|
||||||
|
ctx.arc(searchX, 35, 18, 0, Math.PI * 2);
|
||||||
// AI创作入口按钮
|
|
||||||
const btnWidth = 80;
|
|
||||||
const btnHeight = 32;
|
|
||||||
const btnX = this.screenWidth - btnWidth - 15;
|
|
||||||
const btnY = 35;
|
|
||||||
const btnGradient = ctx.createLinearGradient(btnX, btnY, btnX + btnWidth, btnY);
|
|
||||||
btnGradient.addColorStop(0, '#a855f7');
|
|
||||||
btnGradient.addColorStop(1, '#ec4899');
|
|
||||||
ctx.fillStyle = btnGradient;
|
|
||||||
this.roundRect(ctx, btnX, btnY, btnWidth, btnHeight, 16);
|
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.fillStyle = '#ffffff';
|
ctx.fillStyle = 'rgba(255,255,255,0.7)';
|
||||||
ctx.font = 'bold 12px sans-serif';
|
ctx.font = '16px sans-serif';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.fillText('✨ AI创作', btnX + btnWidth / 2, btnY + 21);
|
ctx.fillText('🔍', searchX, 41);
|
||||||
|
this.searchBtnRect = { x: searchX - 18, y: 17, width: 36, height: 36 };
|
||||||
|
}
|
||||||
|
|
||||||
this.aiCreateBtnRect = { x: btnX, y: btnY, width: btnWidth, height: btnHeight };
|
renderContentTabs(ctx) {
|
||||||
|
const tabY = 60;
|
||||||
|
const tabWidth = (this.screenWidth - 30) / 4;
|
||||||
|
const padding = 15;
|
||||||
|
|
||||||
|
this.contentTabRects = [];
|
||||||
|
this.contentTabs.forEach((tab, index) => {
|
||||||
|
const x = padding + index * tabWidth;
|
||||||
|
const isActive = index === this.contentTab;
|
||||||
|
|
||||||
|
// 文字
|
||||||
|
ctx.fillStyle = isActive ? '#ffffff' : 'rgba(255,255,255,0.5)';
|
||||||
|
ctx.font = isActive ? 'bold 14px sans-serif' : '14px sans-serif';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText(tab, x + tabWidth / 2, tabY + 12);
|
||||||
|
|
||||||
|
// 下划线指示器
|
||||||
|
if (isActive) {
|
||||||
|
const lineGradient = ctx.createLinearGradient(x + tabWidth / 2 - 12, tabY + 20, x + tabWidth / 2 + 12, tabY + 20);
|
||||||
|
lineGradient.addColorStop(0, '#ff6b6b');
|
||||||
|
lineGradient.addColorStop(1, '#ffd700');
|
||||||
|
ctx.fillStyle = lineGradient;
|
||||||
|
this.roundRect(ctx, x + tabWidth / 2 - 12, tabY + 18, 24, 3, 1.5);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.contentTabRects.push({ x, y: tabY - 5, width: tabWidth, height: 30, index });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCategories(ctx) {
|
renderCategories(ctx) {
|
||||||
const startY = 95;
|
const startY = 95;
|
||||||
const tagHeight = 30;
|
const tagHeight = 28;
|
||||||
let x = 15 - this.categoryScrollX;
|
let x = 15 - this.categoryScrollX;
|
||||||
const y = startY;
|
|
||||||
|
|
||||||
// 计算总宽度用于滚动限制
|
ctx.font = '12px sans-serif';
|
||||||
ctx.font = '13px sans-serif';
|
|
||||||
let totalWidth = 15;
|
let totalWidth = 15;
|
||||||
this.categories.forEach((cat) => {
|
this.categories.forEach((cat) => {
|
||||||
totalWidth += ctx.measureText(cat).width + 28 + 12;
|
totalWidth += ctx.measureText(cat).width + 24 + 10;
|
||||||
});
|
});
|
||||||
this.maxCategoryScrollX = Math.max(0, totalWidth - this.screenWidth + 15);
|
this.maxCategoryScrollX = Math.max(0, totalWidth - this.screenWidth + 15);
|
||||||
|
|
||||||
// 清空并重新记录位置
|
|
||||||
this.categoryRects = [];
|
this.categoryRects = [];
|
||||||
|
|
||||||
this.categories.forEach((cat, index) => {
|
this.categories.forEach((cat, index) => {
|
||||||
const isSelected = index === this.selectedCategory;
|
const isSelected = index === this.selectedCategory;
|
||||||
const textWidth = ctx.measureText(cat).width + 28;
|
const textWidth = ctx.measureText(cat).width + 24;
|
||||||
|
|
||||||
// 记录每个标签的位置
|
this.categoryRects.push({ left: x + this.categoryScrollX, right: x + this.categoryScrollX + textWidth, index });
|
||||||
this.categoryRects.push({
|
|
||||||
left: x + this.categoryScrollX,
|
|
||||||
right: x + this.categoryScrollX + textWidth,
|
|
||||||
index: index
|
|
||||||
});
|
|
||||||
|
|
||||||
// 只渲染可见的标签
|
|
||||||
if (x + textWidth > 0 && x < this.screenWidth) {
|
if (x + textWidth > 0 && x < this.screenWidth) {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
// 选中态:渐变背景
|
const tagGradient = ctx.createLinearGradient(x, startY, x + textWidth, startY);
|
||||||
const tagGradient = ctx.createLinearGradient(x, y, x + textWidth, y);
|
|
||||||
tagGradient.addColorStop(0, '#ff6b6b');
|
tagGradient.addColorStop(0, '#ff6b6b');
|
||||||
tagGradient.addColorStop(1, '#ffd700');
|
tagGradient.addColorStop(1, '#ffd700');
|
||||||
ctx.fillStyle = tagGradient;
|
ctx.fillStyle = tagGradient;
|
||||||
} else {
|
} else {
|
||||||
ctx.fillStyle = 'rgba(255,255,255,0.08)';
|
ctx.fillStyle = 'rgba(255,255,255,0.08)';
|
||||||
}
|
}
|
||||||
this.roundRect(ctx, x, y, textWidth, tagHeight, 15);
|
this.roundRect(ctx, x, startY, textWidth, tagHeight, 14);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
// 未选中态加边框
|
|
||||||
if (!isSelected) {
|
if (!isSelected) {
|
||||||
ctx.strokeStyle = 'rgba(255,255,255,0.15)';
|
ctx.strokeStyle = 'rgba(255,255,255,0.12)';
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
this.roundRect(ctx, x, y, textWidth, tagHeight, 15);
|
this.roundRect(ctx, x, startY, textWidth, tagHeight, 14);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标签文字
|
ctx.fillStyle = isSelected ? '#ffffff' : 'rgba(255,255,255,0.6)';
|
||||||
ctx.fillStyle = isSelected ? '#ffffff' : 'rgba(255,255,255,0.7)';
|
ctx.font = isSelected ? 'bold 12px sans-serif' : '12px sans-serif';
|
||||||
ctx.font = isSelected ? 'bold 13px sans-serif' : '13px sans-serif';
|
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.fillText(cat, x + textWidth / 2, y + 20);
|
ctx.fillText(cat, x + textWidth / 2, startY + 18);
|
||||||
}
|
}
|
||||||
|
x += textWidth + 10;
|
||||||
x += textWidth + 12;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderStoryList(ctx) {
|
renderStoryList(ctx) {
|
||||||
const startY = 150;
|
const startY = 140;
|
||||||
const cardHeight = 120;
|
const cardHeight = 130;
|
||||||
const cardPadding = 15;
|
const cardMargin = 12;
|
||||||
const cardMargin = 15;
|
|
||||||
|
|
||||||
const stories = this.getFilteredStories();
|
const stories = this.getFilteredStories();
|
||||||
|
|
||||||
if (stories.length === 0) {
|
if (stories.length === 0) {
|
||||||
ctx.fillStyle = '#666666';
|
ctx.fillStyle = 'rgba(255,255,255,0.4)';
|
||||||
ctx.font = '14px sans-serif';
|
ctx.font = '14px sans-serif';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.fillText('暂无该分类的故事', this.screenWidth / 2, 250);
|
const emptyText = this.contentTab === 1 ? '关注作者后,这里会显示他们的故事' : '暂无故事';
|
||||||
|
ctx.fillText(emptyText, this.screenWidth / 2, 280);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置裁剪区域,防止卡片渲染到分类标签区域
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.rect(0, startY, this.screenWidth, this.screenHeight - startY - 65);
|
ctx.rect(0, startY, this.screenWidth, this.screenHeight - startY - 60);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
|
|
||||||
stories.forEach((story, index) => {
|
stories.forEach((story, index) => {
|
||||||
const y = startY + index * (cardHeight + cardMargin) - this.scrollY;
|
const y = startY + index * (cardHeight + cardMargin) - this.scrollY;
|
||||||
|
if (y > startY - cardHeight && y < this.screenHeight - 60) {
|
||||||
// 只渲染可见区域的卡片
|
this.renderStoryCard(ctx, story, 12, y, this.screenWidth - 24, cardHeight);
|
||||||
if (y > startY - cardHeight && y < this.screenHeight - 65) {
|
|
||||||
this.renderStoryCard(ctx, story, cardMargin, y, this.screenWidth - cardMargin * 2, cardHeight);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -232,73 +237,141 @@ export default class HomeScene extends BaseScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderStoryCard(ctx, story, x, y, width, height) {
|
renderStoryCard(ctx, story, x, y, width, height) {
|
||||||
// 卡片背景 - 毛玻璃效果
|
// 卡片背景
|
||||||
ctx.fillStyle = 'rgba(255,255,255,0.06)';
|
ctx.fillStyle = 'rgba(255,255,255,0.06)';
|
||||||
this.roundRect(ctx, x, y, width, height, 16);
|
this.roundRect(ctx, x, y, width, height, 14);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
ctx.strokeStyle = 'rgba(255,255,255,0.08)';
|
||||||
// 卡片高光边框
|
|
||||||
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
|
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
this.roundRect(ctx, x, y, width, height, 16);
|
this.roundRect(ctx, x, y, width, height, 14);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// 封面区域 - 渐变色
|
// 封面
|
||||||
const coverWidth = 85;
|
const coverW = 80, coverH = height - 20;
|
||||||
const coverHeight = height - 24;
|
const coverGradient = ctx.createLinearGradient(x + 10, y + 10, x + 10 + coverW, y + 10 + coverH);
|
||||||
const coverGradient = ctx.createLinearGradient(x + 12, y + 12, x + 12 + coverWidth, y + 12 + coverHeight);
|
|
||||||
const colors = this.getCategoryGradient(story.category);
|
const colors = this.getCategoryGradient(story.category);
|
||||||
coverGradient.addColorStop(0, colors[0]);
|
coverGradient.addColorStop(0, colors[0]);
|
||||||
coverGradient.addColorStop(1, colors[1]);
|
coverGradient.addColorStop(1, colors[1]);
|
||||||
ctx.fillStyle = coverGradient;
|
ctx.fillStyle = coverGradient;
|
||||||
this.roundRect(ctx, x + 12, y + 12, coverWidth, coverHeight, 12);
|
this.roundRect(ctx, x + 10, y + 10, coverW, coverH, 10);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
// 封面上的分类名
|
ctx.fillStyle = 'rgba(255,255,255,0.85)';
|
||||||
ctx.fillStyle = 'rgba(255,255,255,0.9)';
|
ctx.font = 'bold 10px sans-serif';
|
||||||
ctx.font = 'bold 11px sans-serif';
|
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.fillText(story.category || '故事', x + 12 + coverWidth / 2, y + 12 + coverHeight / 2 + 4);
|
ctx.fillText(story.category || '故事', x + 10 + coverW / 2, y + 10 + coverH / 2 + 4);
|
||||||
|
|
||||||
|
const textX = x + 100;
|
||||||
|
const maxW = width - 115;
|
||||||
|
|
||||||
// 故事标题
|
// 故事标题
|
||||||
ctx.fillStyle = '#ffffff';
|
ctx.fillStyle = '#ffffff';
|
||||||
ctx.font = 'bold 17px sans-serif';
|
ctx.font = 'bold 15px sans-serif';
|
||||||
ctx.textAlign = 'left';
|
ctx.textAlign = 'left';
|
||||||
const titleX = x + 110;
|
ctx.fillText(this.truncateText(ctx, story.title, maxW), textX, y + 28);
|
||||||
const maxTextWidth = width - 120; // 可用文字宽度
|
|
||||||
const title = this.truncateText(ctx, story.title, maxTextWidth);
|
|
||||||
ctx.fillText(title, titleX, y + 35);
|
|
||||||
|
|
||||||
// 故事简介
|
// 作者信息
|
||||||
ctx.fillStyle = 'rgba(255,255,255,0.5)';
|
ctx.fillStyle = 'rgba(255,255,255,0.6)';
|
||||||
ctx.font = '12px sans-serif';
|
|
||||||
const desc = story.description || '';
|
|
||||||
const shortDesc = this.truncateText(ctx, desc, maxTextWidth);
|
|
||||||
ctx.fillText(shortDesc, titleX, y + 58);
|
|
||||||
|
|
||||||
// 统计信息带图标
|
|
||||||
ctx.fillStyle = 'rgba(255,255,255,0.4)';
|
|
||||||
ctx.font = '11px sans-serif';
|
ctx.font = '11px sans-serif';
|
||||||
const playText = `▶ ${this.formatNumber(story.play_count)}`;
|
const authorName = story.author_name || '官方';
|
||||||
const likeText = `♥ ${this.formatNumber(story.like_count)}`;
|
ctx.fillText(`@${authorName}`, textX, y + 48);
|
||||||
ctx.fillText(playText, titleX, y + 90);
|
|
||||||
ctx.fillText(likeText, titleX + 70, y + 90);
|
|
||||||
|
|
||||||
// 精选标签 - 更醒目
|
// 来源标签
|
||||||
|
const isOfficial = !story.source_type || story.source_type === 'official';
|
||||||
|
const tagText = isOfficial ? '官方' : (story.source_type === 'ai' ? 'AI' : 'UGC');
|
||||||
|
const tagColor = isOfficial ? '#ffd700' : (story.source_type === 'ai' ? '#a855f7' : '#4ade80');
|
||||||
|
const tagW = ctx.measureText(tagText).width + 12;
|
||||||
|
const tagX = textX + ctx.measureText(`@${authorName}`).width + 8;
|
||||||
|
|
||||||
|
ctx.fillStyle = tagColor + '22';
|
||||||
|
this.roundRect(ctx, tagX, y + 38, tagW, 16, 8);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.fillStyle = tagColor;
|
||||||
|
ctx.font = 'bold 9px sans-serif';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText(tagText, tagX + tagW / 2, y + 49);
|
||||||
|
|
||||||
|
// 简介
|
||||||
|
ctx.fillStyle = 'rgba(255,255,255,0.45)';
|
||||||
|
ctx.font = '11px sans-serif';
|
||||||
|
ctx.textAlign = 'left';
|
||||||
|
ctx.fillText(this.truncateText(ctx, story.description || '', maxW), textX, y + 72);
|
||||||
|
|
||||||
|
// 统计
|
||||||
|
ctx.fillStyle = 'rgba(255,255,255,0.4)';
|
||||||
|
ctx.font = '10px sans-serif';
|
||||||
|
ctx.fillText(`▶ ${this.formatNumber(story.play_count || 0)}`, textX, y + 100);
|
||||||
|
ctx.fillText(`♥ ${this.formatNumber(story.like_count || 0)}`, textX + 55, y + 100);
|
||||||
|
ctx.fillText(`💬 ${this.formatNumber(story.comment_count || 0)}`, textX + 105, y + 100);
|
||||||
|
|
||||||
|
// 精选标签
|
||||||
if (story.is_featured) {
|
if (story.is_featured) {
|
||||||
const tagGradient = ctx.createLinearGradient(x + width - 55, y + 12, x + width - 10, y + 12);
|
const fGradient = ctx.createLinearGradient(x + width - 50, y + 10, x + width - 10, y + 10);
|
||||||
tagGradient.addColorStop(0, '#ff6b6b');
|
fGradient.addColorStop(0, '#ff6b6b');
|
||||||
tagGradient.addColorStop(1, '#ffd700');
|
fGradient.addColorStop(1, '#ffd700');
|
||||||
ctx.fillStyle = tagGradient;
|
ctx.fillStyle = fGradient;
|
||||||
this.roundRect(ctx, x + width - 55, y + 12, 45, 22, 11);
|
this.roundRect(ctx, x + width - 50, y + 10, 40, 20, 10);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.fillStyle = '#ffffff';
|
ctx.fillStyle = '#ffffff';
|
||||||
ctx.font = 'bold 10px sans-serif';
|
ctx.font = 'bold 9px sans-serif';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.fillText('精选', x + width - 32, y + 27);
|
ctx.fillText('精选', x + width - 30, y + 24);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderBottomTabBar(ctx) {
|
||||||
|
const tabH = 60;
|
||||||
|
const y = this.screenHeight - tabH;
|
||||||
|
|
||||||
|
ctx.fillStyle = 'rgba(15, 12, 41, 0.98)';
|
||||||
|
ctx.fillRect(0, y, this.screenWidth, tabH);
|
||||||
|
|
||||||
|
const lineGradient = ctx.createLinearGradient(0, y, this.screenWidth, y);
|
||||||
|
lineGradient.addColorStop(0, 'rgba(255,107,107,0.2)');
|
||||||
|
lineGradient.addColorStop(0.5, 'rgba(255,215,0,0.2)');
|
||||||
|
lineGradient.addColorStop(1, 'rgba(255,107,107,0.2)');
|
||||||
|
ctx.strokeStyle = lineGradient;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, y);
|
||||||
|
ctx.lineTo(this.screenWidth, y);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ icon: '🏠', label: '首页' },
|
||||||
|
{ icon: '🔥', label: '发现' },
|
||||||
|
{ icon: '✨', label: '创作' },
|
||||||
|
{ icon: '👤', label: '我的' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const tabW = this.screenWidth / tabs.length;
|
||||||
|
this.bottomTabRects = [];
|
||||||
|
|
||||||
|
tabs.forEach((tab, index) => {
|
||||||
|
const centerX = index * tabW + tabW / 2;
|
||||||
|
const isActive = index === this.bottomTab;
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
const indGradient = ctx.createLinearGradient(centerX - 16, y + 2, centerX + 16, y + 2);
|
||||||
|
indGradient.addColorStop(0, '#ff6b6b');
|
||||||
|
indGradient.addColorStop(1, '#ffd700');
|
||||||
|
ctx.fillStyle = indGradient;
|
||||||
|
this.roundRect(ctx, centerX - 16, y + 2, 32, 3, 1.5);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.font = index === 2 ? '24px sans-serif' : '20px sans-serif';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText(tab.icon, centerX, y + 28);
|
||||||
|
|
||||||
|
ctx.fillStyle = isActive ? '#ffffff' : 'rgba(255,255,255,0.4)';
|
||||||
|
ctx.font = isActive ? 'bold 10px sans-serif' : '10px sans-serif';
|
||||||
|
ctx.fillText(tab.label, centerX, y + 48);
|
||||||
|
|
||||||
|
this.bottomTabRects.push({ x: index * tabW, y, width: tabW, height: tabH, index });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getCategoryGradient(category) {
|
getCategoryGradient(category) {
|
||||||
const gradients = {
|
const gradients = {
|
||||||
'都市言情': ['#ff758c', '#ff7eb3'],
|
'都市言情': ['#ff758c', '#ff7eb3'],
|
||||||
@@ -315,113 +388,32 @@ export default class HomeScene extends BaseScene {
|
|||||||
return gradients[category] || ['#667eea', '#764ba2'];
|
return gradients[category] || ['#667eea', '#764ba2'];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTabBar(ctx) {
|
|
||||||
const tabHeight = 65;
|
|
||||||
const y = this.screenHeight - tabHeight;
|
|
||||||
|
|
||||||
// Tab栏背景 - 毛玻璃
|
|
||||||
ctx.fillStyle = 'rgba(15, 12, 41, 0.95)';
|
|
||||||
ctx.fillRect(0, y, this.screenWidth, tabHeight);
|
|
||||||
|
|
||||||
// 顶部高光线
|
|
||||||
const lineGradient = ctx.createLinearGradient(0, y, this.screenWidth, y);
|
|
||||||
lineGradient.addColorStop(0, 'rgba(255,107,107,0.3)');
|
|
||||||
lineGradient.addColorStop(0.5, 'rgba(255,215,0,0.3)');
|
|
||||||
lineGradient.addColorStop(1, 'rgba(255,107,107,0.3)');
|
|
||||||
ctx.strokeStyle = lineGradient;
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, y);
|
|
||||||
ctx.lineTo(this.screenWidth, y);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{ icon: '🏠', label: '首页' },
|
|
||||||
{ icon: '🔍', label: '发现' },
|
|
||||||
{ icon: '👤', label: '我的' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const tabWidth = this.screenWidth / tabs.length;
|
|
||||||
|
|
||||||
tabs.forEach((tab, index) => {
|
|
||||||
const centerX = index * tabWidth + tabWidth / 2;
|
|
||||||
const isActive = index === this.currentTab;
|
|
||||||
|
|
||||||
// 选中态指示器
|
|
||||||
if (isActive) {
|
|
||||||
const indicatorGradient = ctx.createLinearGradient(centerX - 20, y + 3, centerX + 20, y + 3);
|
|
||||||
indicatorGradient.addColorStop(0, '#ff6b6b');
|
|
||||||
indicatorGradient.addColorStop(1, '#ffd700');
|
|
||||||
ctx.fillStyle = indicatorGradient;
|
|
||||||
this.roundRect(ctx, centerX - 20, y + 2, 40, 3, 1.5);
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图标
|
|
||||||
ctx.font = '22px sans-serif';
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.fillText(tab.icon, centerX, y + 32);
|
|
||||||
|
|
||||||
// 标签文字
|
|
||||||
ctx.fillStyle = isActive ? '#ffffff' : 'rgba(255,255,255,0.4)';
|
|
||||||
ctx.font = isActive ? 'bold 11px sans-serif' : '11px sans-serif';
|
|
||||||
ctx.fillText(tab.label, centerX, y + 52);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取分类颜色
|
|
||||||
getCategoryColor(category) {
|
|
||||||
const colors = {
|
|
||||||
'都市言情': '#e94560',
|
|
||||||
'悬疑推理': '#4a90d9',
|
|
||||||
'古风宫廷': '#d4a574',
|
|
||||||
'校园青春': '#7ed957',
|
|
||||||
'修仙玄幻': '#9b59b6',
|
|
||||||
'穿越重生': '#f39c12',
|
|
||||||
'职场商战': '#3498db',
|
|
||||||
'科幻未来': '#1abc9c',
|
|
||||||
'恐怖惊悚': '#2c3e50',
|
|
||||||
'搞笑轻喜': '#f1c40f'
|
|
||||||
};
|
|
||||||
return colors[category] || '#666666';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化数字
|
|
||||||
formatNumber(num) {
|
formatNumber(num) {
|
||||||
if (num >= 10000) {
|
if (!num) return '0';
|
||||||
return (num / 10000).toFixed(1) + 'w';
|
if (num >= 10000) return (num / 10000).toFixed(1) + 'w';
|
||||||
}
|
if (num >= 1000) return (num / 1000).toFixed(1) + 'k';
|
||||||
if (num >= 1000) {
|
|
||||||
return (num / 1000).toFixed(1) + 'k';
|
|
||||||
}
|
|
||||||
return num.toString();
|
return num.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 截断文字以适应宽度
|
|
||||||
truncateText(ctx, text, maxWidth) {
|
truncateText(ctx, text, maxWidth) {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
if (ctx.measureText(text).width <= maxWidth) {
|
if (ctx.measureText(text).width <= maxWidth) return text;
|
||||||
return text;
|
let t = text;
|
||||||
}
|
while (t.length > 0 && ctx.measureText(t + '...').width > maxWidth) t = t.slice(0, -1);
|
||||||
let truncated = text;
|
return t + '...';
|
||||||
while (truncated.length > 0 && ctx.measureText(truncated + '...').width > maxWidth) {
|
|
||||||
truncated = truncated.slice(0, -1);
|
|
||||||
}
|
|
||||||
return truncated + '...';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制圆角矩形
|
roundRect(ctx, x, y, w, h, r) {
|
||||||
roundRect(ctx, x, y, width, height, radius) {
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(x + radius, y);
|
ctx.moveTo(x + r, y);
|
||||||
ctx.lineTo(x + width - radius, y);
|
ctx.lineTo(x + w - r, y);
|
||||||
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
||||||
ctx.lineTo(x + width, y + height - radius);
|
ctx.lineTo(x + w, y + h - r);
|
||||||
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
||||||
ctx.lineTo(x + radius, y + height);
|
ctx.lineTo(x + r, y + h);
|
||||||
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
|
||||||
ctx.lineTo(x, y + radius);
|
ctx.lineTo(x, y + r);
|
||||||
ctx.quadraticCurveTo(x, y, x + radius, y);
|
ctx.quadraticCurveTo(x, y, x + r, y);
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,8 +425,7 @@ export default class HomeScene extends BaseScene {
|
|||||||
this.touchStartY = touch.clientY;
|
this.touchStartY = touch.clientY;
|
||||||
this.hasMoved = false;
|
this.hasMoved = false;
|
||||||
|
|
||||||
// 判断是否在分类区域(y: 90-140)
|
if (touch.clientY >= 90 && touch.clientY <= 130) {
|
||||||
if (touch.clientY >= 90 && touch.clientY <= 140) {
|
|
||||||
this.isCategoryDragging = true;
|
this.isCategoryDragging = true;
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -447,26 +438,18 @@ export default class HomeScene extends BaseScene {
|
|||||||
onTouchMove(e) {
|
onTouchMove(e) {
|
||||||
const touch = e.touches[0];
|
const touch = e.touches[0];
|
||||||
|
|
||||||
// 分类区域横向滑动
|
|
||||||
if (this.isCategoryDragging) {
|
if (this.isCategoryDragging) {
|
||||||
const deltaX = this.lastTouchX - touch.clientX;
|
const deltaX = this.lastTouchX - touch.clientX;
|
||||||
if (Math.abs(deltaX) > 2) {
|
if (Math.abs(deltaX) > 2) this.hasMoved = true;
|
||||||
this.hasMoved = true;
|
|
||||||
}
|
|
||||||
this.categoryScrollX += deltaX;
|
this.categoryScrollX += deltaX;
|
||||||
this.categoryScrollX = Math.max(0, Math.min(this.categoryScrollX, this.maxCategoryScrollX));
|
this.categoryScrollX = Math.max(0, Math.min(this.categoryScrollX, this.maxCategoryScrollX));
|
||||||
this.lastTouchX = touch.clientX;
|
this.lastTouchX = touch.clientX;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 故事列表纵向滑动
|
|
||||||
if (this.isDragging) {
|
if (this.isDragging) {
|
||||||
const deltaY = this.lastTouchY - touch.clientY;
|
const deltaY = this.lastTouchY - touch.clientY;
|
||||||
|
if (Math.abs(touch.clientY - this.touchStartY) > 5) this.hasMoved = true;
|
||||||
if (Math.abs(touch.clientY - this.touchStartY) > 5) {
|
|
||||||
this.hasMoved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scrollVelocity = deltaY;
|
this.scrollVelocity = deltaY;
|
||||||
this.scrollY += deltaY;
|
this.scrollY += deltaY;
|
||||||
this.scrollY = Math.max(0, Math.min(this.scrollY, this.maxScrollY));
|
this.scrollY = Math.max(0, Math.min(this.scrollY, this.maxScrollY));
|
||||||
@@ -477,87 +460,85 @@ export default class HomeScene extends BaseScene {
|
|||||||
onTouchEnd(e) {
|
onTouchEnd(e) {
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
this.isCategoryDragging = false;
|
this.isCategoryDragging = false;
|
||||||
|
if (this.hasMoved) return;
|
||||||
|
|
||||||
// 如果有滑动,不处理点击
|
|
||||||
if (this.hasMoved) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测点击
|
|
||||||
const touch = e.changedTouches[0];
|
const touch = e.changedTouches[0];
|
||||||
const x = touch.clientX;
|
const x = touch.clientX;
|
||||||
const y = touch.clientY;
|
const y = touch.clientY;
|
||||||
|
|
||||||
// 检测AI创作按钮点击
|
// 底部Tab点击
|
||||||
if (this.aiCreateBtnRect) {
|
if (y > this.screenHeight - 60) {
|
||||||
const btn = this.aiCreateBtnRect;
|
const tabW = this.screenWidth / 4;
|
||||||
if (x >= btn.x && x <= btn.x + btn.width && y >= btn.y && y <= btn.y + btn.height) {
|
const tabIndex = Math.floor(x / tabW);
|
||||||
this.main.sceneManager.switchScene('aiCreate');
|
this.handleBottomTabClick(tabIndex);
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测Tab栏点击
|
|
||||||
if (y > this.screenHeight - 65) {
|
|
||||||
const tabWidth = this.screenWidth / 3;
|
|
||||||
const tabIndex = Math.floor(x / tabWidth);
|
|
||||||
this.handleTabClick(tabIndex);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测分类标签点击
|
// 内容源Tab点击
|
||||||
if (y >= 90 && y <= 140) {
|
if (this.contentTabRects) {
|
||||||
|
for (const rect of this.contentTabRects) {
|
||||||
|
if (x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height) {
|
||||||
|
if (this.contentTab !== rect.index) {
|
||||||
|
this.contentTab = rect.index;
|
||||||
|
this.scrollY = 0;
|
||||||
|
this.calculateMaxScroll();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分类点击
|
||||||
|
if (y >= 90 && y <= 130) {
|
||||||
this.handleCategoryClick(x);
|
this.handleCategoryClick(x);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测故事卡片点击
|
// 故事卡片点击
|
||||||
this.handleStoryClick(x, y);
|
this.handleStoryClick(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCategoryClick(x) {
|
handleCategoryClick(x) {
|
||||||
// 考虑横向滚动偏移
|
|
||||||
const adjustedX = x + this.categoryScrollX;
|
const adjustedX = x + this.categoryScrollX;
|
||||||
|
|
||||||
// 使用保存的实际位置判断
|
|
||||||
for (const rect of this.categoryRects) {
|
for (const rect of this.categoryRects) {
|
||||||
if (adjustedX >= rect.left && adjustedX <= rect.right) {
|
if (adjustedX >= rect.left && adjustedX <= rect.right) {
|
||||||
if (this.selectedCategory !== rect.index) {
|
if (this.selectedCategory !== rect.index) {
|
||||||
this.selectedCategory = rect.index;
|
this.selectedCategory = rect.index;
|
||||||
this.scrollY = 0;
|
this.scrollY = 0;
|
||||||
this.calculateMaxScroll();
|
this.calculateMaxScroll();
|
||||||
console.log('选中分类:', this.categories[rect.index]);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTabClick(tabIndex) {
|
handleBottomTabClick(tabIndex) {
|
||||||
if (tabIndex === this.currentTab) return;
|
if (tabIndex === this.bottomTab && tabIndex === 0) return;
|
||||||
|
|
||||||
this.currentTab = tabIndex;
|
if (tabIndex === 0) {
|
||||||
|
this.bottomTab = 0;
|
||||||
if (tabIndex === 2) {
|
} else if (tabIndex === 1) {
|
||||||
// 切换到个人中心
|
// 发现页(可跳转或在本页切换)
|
||||||
|
this.bottomTab = 1;
|
||||||
|
} else if (tabIndex === 2) {
|
||||||
|
// 创作页
|
||||||
|
this.main.sceneManager.switchScene('aiCreate');
|
||||||
|
} else if (tabIndex === 3) {
|
||||||
|
// 我的
|
||||||
this.main.sceneManager.switchScene('profile');
|
this.main.sceneManager.switchScene('profile');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStoryClick(x, y) {
|
handleStoryClick(x, y) {
|
||||||
const startY = 150;
|
const startY = 140;
|
||||||
const cardHeight = 120;
|
const cardHeight = 130;
|
||||||
const cardMargin = 15;
|
const cardMargin = 12;
|
||||||
|
|
||||||
const stories = this.getFilteredStories();
|
const stories = this.getFilteredStories();
|
||||||
|
|
||||||
// 计算点击的是哪个故事
|
|
||||||
const adjustedY = y + this.scrollY;
|
const adjustedY = y + this.scrollY;
|
||||||
const index = Math.floor((adjustedY - startY) / (cardHeight + cardMargin));
|
const index = Math.floor((adjustedY - startY) / (cardHeight + cardMargin));
|
||||||
|
|
||||||
if (index >= 0 && index < stories.length) {
|
if (index >= 0 && index < stories.length) {
|
||||||
const story = stories[index];
|
const story = stories[index];
|
||||||
// 跳转到故事播放场景
|
|
||||||
this.main.sceneManager.switchScene('story', { storyId: story.id });
|
this.main.sceneManager.switchScene('story', { storyId: story.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user