372 lines
11 KiB
JavaScript
372 lines
11 KiB
JavaScript
/**
|
||
* 个人中心场景
|
||
*/
|
||
import BaseScene from './BaseScene';
|
||
|
||
export default class ProfileScene extends BaseScene {
|
||
constructor(main, params) {
|
||
super(main, params);
|
||
this.collections = [];
|
||
this.progress = [];
|
||
this.currentTab = 0; // 0: 收藏, 1: 历史
|
||
this.scrollY = 0;
|
||
this.maxScrollY = 0;
|
||
this.isDragging = false;
|
||
this.lastTouchY = 0;
|
||
this.scrollVelocity = 0;
|
||
this.hasMoved = false;
|
||
}
|
||
|
||
async init() {
|
||
await this.loadData();
|
||
}
|
||
|
||
async loadData() {
|
||
if (this.main.userManager.isLoggedIn) {
|
||
this.collections = await this.main.userManager.getCollections() || [];
|
||
this.progress = await this.main.userManager.getProgress() || [];
|
||
}
|
||
this.calculateMaxScroll();
|
||
}
|
||
|
||
calculateMaxScroll() {
|
||
const list = this.currentTab === 0 ? this.collections : this.progress;
|
||
const cardHeight = 90;
|
||
const gap = 12;
|
||
const headerHeight = 300;
|
||
const contentHeight = list.length * (cardHeight + gap) + headerHeight;
|
||
this.maxScrollY = Math.max(0, contentHeight - this.screenHeight + 20);
|
||
}
|
||
|
||
update() {
|
||
if (!this.isDragging && Math.abs(this.scrollVelocity) > 0.5) {
|
||
this.scrollY += this.scrollVelocity;
|
||
this.scrollVelocity *= 0.95;
|
||
this.scrollY = Math.max(0, Math.min(this.scrollY, this.maxScrollY));
|
||
}
|
||
}
|
||
|
||
render(ctx) {
|
||
// 背景渐变
|
||
const gradient = ctx.createLinearGradient(0, 0, 0, this.screenHeight);
|
||
gradient.addColorStop(0, '#0f0c29');
|
||
gradient.addColorStop(0.5, '#302b63');
|
||
gradient.addColorStop(1, '#24243e');
|
||
ctx.fillStyle = gradient;
|
||
ctx.fillRect(0, 0, this.screenWidth, this.screenHeight);
|
||
|
||
// 顶部返回
|
||
this.renderHeader(ctx);
|
||
|
||
// 用户信息卡片
|
||
this.renderUserCard(ctx);
|
||
|
||
// Tab切换
|
||
this.renderTabs(ctx);
|
||
|
||
// 列表内容
|
||
this.renderList(ctx);
|
||
}
|
||
|
||
renderHeader(ctx) {
|
||
// 顶部渐变遮罩
|
||
const headerGradient = ctx.createLinearGradient(0, 0, 0, 60);
|
||
headerGradient.addColorStop(0, 'rgba(0,0,0,0.5)');
|
||
headerGradient.addColorStop(1, 'rgba(0,0,0,0)');
|
||
ctx.fillStyle = headerGradient;
|
||
ctx.fillRect(0, 0, this.screenWidth, 60);
|
||
|
||
// 返回按钮
|
||
ctx.fillStyle = '#ffffff';
|
||
ctx.font = '16px sans-serif';
|
||
ctx.textAlign = 'left';
|
||
ctx.fillText('‹ 返回', 15, 35);
|
||
|
||
// 标题
|
||
ctx.textAlign = 'center';
|
||
ctx.font = 'bold 17px sans-serif';
|
||
ctx.fillText('个人中心', this.screenWidth / 2, 35);
|
||
}
|
||
|
||
renderUserCard(ctx) {
|
||
const cardY = 60;
|
||
const cardHeight = 170;
|
||
const centerX = this.screenWidth / 2;
|
||
const user = this.main.userManager;
|
||
|
||
// 卡片背景
|
||
ctx.fillStyle = 'rgba(255,255,255,0.06)';
|
||
this.roundRect(ctx, 15, cardY, this.screenWidth - 30, cardHeight, 16);
|
||
ctx.fill();
|
||
|
||
// 头像
|
||
const avatarSize = 55;
|
||
const avatarY = cardY + 20;
|
||
const avatarGradient = ctx.createLinearGradient(centerX - 30, avatarY, centerX + 30, avatarY + avatarSize);
|
||
avatarGradient.addColorStop(0, '#ff6b6b');
|
||
avatarGradient.addColorStop(1, '#ffd700');
|
||
ctx.fillStyle = avatarGradient;
|
||
ctx.beginPath();
|
||
ctx.arc(centerX, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// 头像文字
|
||
ctx.fillStyle = '#ffffff';
|
||
ctx.font = 'bold 22px sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(user.nickname ? user.nickname[0] : '游', centerX, avatarY + avatarSize / 2 + 8);
|
||
|
||
// 昵称
|
||
ctx.fillStyle = '#ffffff';
|
||
ctx.font = 'bold 15px sans-serif';
|
||
ctx.fillText(user.nickname || '游客用户', centerX, avatarY + avatarSize + 20);
|
||
|
||
// 分割线
|
||
const lineY = avatarY + avatarSize + 35;
|
||
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
ctx.moveTo(30, lineY);
|
||
ctx.lineTo(this.screenWidth - 30, lineY);
|
||
ctx.stroke();
|
||
|
||
// 统计信息
|
||
const statsY = lineY + 30;
|
||
const statWidth = (this.screenWidth - 30) / 3;
|
||
const statsData = [
|
||
{ num: this.progress.length, label: '游玩' },
|
||
{ num: this.collections.length, label: '收藏' },
|
||
{ num: this.progress.filter(p => p.is_completed).length, label: '结局' }
|
||
];
|
||
|
||
statsData.forEach((stat, i) => {
|
||
const x = 15 + statWidth * i + statWidth / 2;
|
||
ctx.fillStyle = '#ffffff';
|
||
ctx.font = 'bold 18px sans-serif';
|
||
ctx.fillText(stat.num.toString(), x, statsY);
|
||
ctx.fillStyle = 'rgba(255,255,255,0.5)';
|
||
ctx.font = '11px sans-serif';
|
||
ctx.fillText(stat.label, x, statsY + 16);
|
||
});
|
||
}
|
||
|
||
renderTabs(ctx) {
|
||
const tabY = 245;
|
||
const tabWidth = (this.screenWidth - 30) / 2;
|
||
const tabs = ['我的收藏', '游玩记录'];
|
||
|
||
tabs.forEach((tab, index) => {
|
||
const x = 15 + index * tabWidth;
|
||
const isActive = index === this.currentTab;
|
||
const centerX = x + tabWidth / 2;
|
||
|
||
// Tab背景
|
||
if (isActive) {
|
||
const tabGradient = ctx.createLinearGradient(x, tabY, x + tabWidth, tabY);
|
||
tabGradient.addColorStop(0, '#ff6b6b');
|
||
tabGradient.addColorStop(1, '#ffd700');
|
||
ctx.fillStyle = tabGradient;
|
||
this.roundRect(ctx, x + 10, tabY, tabWidth - 20, 32, 16);
|
||
ctx.fill();
|
||
}
|
||
|
||
// Tab文字
|
||
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, centerX, tabY + 21);
|
||
});
|
||
}
|
||
|
||
renderList(ctx) {
|
||
const list = this.currentTab === 0 ? this.collections : this.progress;
|
||
const startY = 295;
|
||
const cardHeight = 90;
|
||
const cardMargin = 12;
|
||
const padding = 15;
|
||
|
||
// 裁剪区域
|
||
ctx.save();
|
||
ctx.beginPath();
|
||
ctx.rect(0, startY - 10, this.screenWidth, this.screenHeight - startY + 10);
|
||
ctx.clip();
|
||
|
||
if (list.length === 0) {
|
||
ctx.fillStyle = 'rgba(255,255,255,0.4)';
|
||
ctx.font = '14px sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(
|
||
this.currentTab === 0 ? '还没有收藏的故事' : '还没有游玩记录',
|
||
this.screenWidth / 2,
|
||
startY + 50
|
||
);
|
||
ctx.restore();
|
||
return;
|
||
}
|
||
|
||
list.forEach((item, index) => {
|
||
const y = startY + index * (cardHeight + cardMargin) - this.scrollY;
|
||
if (y > -cardHeight && y < this.screenHeight) {
|
||
this.renderListCard(ctx, item, padding, y, this.screenWidth - padding * 2, cardHeight, index);
|
||
}
|
||
});
|
||
|
||
ctx.restore();
|
||
}
|
||
|
||
renderListCard(ctx, item, x, y, width, height, index) {
|
||
// 卡片背景
|
||
ctx.fillStyle = 'rgba(255,255,255,0.06)';
|
||
this.roundRect(ctx, x, y, width, height, 12);
|
||
ctx.fill();
|
||
|
||
// 封面
|
||
const coverSize = 65;
|
||
const coverColors = [
|
||
['#ff758c', '#ff7eb3'],
|
||
['#667eea', '#764ba2'],
|
||
['#4facfe', '#00f2fe'],
|
||
['#43e97b', '#38f9d7'],
|
||
['#fa709a', '#fee140']
|
||
];
|
||
const colors = coverColors[index % coverColors.length];
|
||
const coverGradient = ctx.createLinearGradient(x + 12, y + 12, x + 12 + coverSize, y + 12 + coverSize);
|
||
coverGradient.addColorStop(0, colors[0]);
|
||
coverGradient.addColorStop(1, colors[1]);
|
||
ctx.fillStyle = coverGradient;
|
||
this.roundRect(ctx, x + 12, y + 12, coverSize, coverSize, 10);
|
||
ctx.fill();
|
||
|
||
// 封面文字
|
||
ctx.fillStyle = 'rgba(255,255,255,0.9)';
|
||
ctx.font = 'bold 10px sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(item.category || '故事', x + 12 + coverSize / 2, y + 12 + coverSize / 2 + 4);
|
||
|
||
// 标题
|
||
ctx.fillStyle = '#ffffff';
|
||
ctx.font = 'bold 15px sans-serif';
|
||
ctx.textAlign = 'left';
|
||
const title = item.story_title || item.title || '未知故事';
|
||
ctx.fillText(title.length > 12 ? title.substring(0, 12) + '...' : title, x + 90, y + 35);
|
||
|
||
// 状态
|
||
ctx.font = '12px sans-serif';
|
||
if (this.currentTab === 1 && item.is_completed) {
|
||
ctx.fillStyle = '#4ecca3';
|
||
ctx.fillText('✓ 已完成', x + 90, y + 58);
|
||
} else if (this.currentTab === 1) {
|
||
ctx.fillStyle = 'rgba(255,255,255,0.5)';
|
||
ctx.fillText('进行中...', x + 90, y + 58);
|
||
} else {
|
||
ctx.fillStyle = 'rgba(255,255,255,0.5)';
|
||
ctx.fillText(item.category || '', x + 90, y + 58);
|
||
}
|
||
|
||
// 继续按钮
|
||
const btnGradient = ctx.createLinearGradient(x + width - 65, y + 30, x + width - 10, y + 30);
|
||
btnGradient.addColorStop(0, '#ff6b6b');
|
||
btnGradient.addColorStop(1, '#ffd700');
|
||
ctx.fillStyle = btnGradient;
|
||
this.roundRect(ctx, x + width - 65, y + 30, 52, 28, 14);
|
||
ctx.fill();
|
||
|
||
ctx.fillStyle = '#ffffff';
|
||
ctx.font = 'bold 12px sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText('继续', x + width - 39, y + 49);
|
||
}
|
||
|
||
// 圆角矩形
|
||
roundRect(ctx, x, y, width, height, radius) {
|
||
ctx.beginPath();
|
||
ctx.moveTo(x + radius, y);
|
||
ctx.lineTo(x + width - radius, y);
|
||
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||
ctx.lineTo(x + width, y + height - radius);
|
||
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
||
ctx.lineTo(x + radius, y + height);
|
||
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||
ctx.lineTo(x, y + radius);
|
||
ctx.quadraticCurveTo(x, y, x + radius, y);
|
||
ctx.closePath();
|
||
}
|
||
|
||
onTouchStart(e) {
|
||
const touch = e.touches[0];
|
||
this.lastTouchY = touch.clientY;
|
||
this.touchStartY = touch.clientY;
|
||
this.isDragging = true;
|
||
this.scrollVelocity = 0;
|
||
this.hasMoved = false;
|
||
}
|
||
|
||
onTouchMove(e) {
|
||
if (!this.isDragging) return;
|
||
const touch = e.touches[0];
|
||
const deltaY = this.lastTouchY - touch.clientY;
|
||
|
||
if (Math.abs(touch.clientY - this.touchStartY) > 5) {
|
||
this.hasMoved = true;
|
||
}
|
||
|
||
this.scrollVelocity = deltaY;
|
||
this.scrollY += deltaY;
|
||
this.scrollY = Math.max(0, Math.min(this.scrollY, this.maxScrollY));
|
||
this.lastTouchY = touch.clientY;
|
||
}
|
||
|
||
onTouchEnd(e) {
|
||
this.isDragging = false;
|
||
|
||
if (this.hasMoved) return;
|
||
|
||
const touch = e.changedTouches[0];
|
||
const x = touch.clientX;
|
||
const y = touch.clientY;
|
||
|
||
// 返回按钮
|
||
if (y < 50 && x < 80) {
|
||
this.main.sceneManager.switchScene('home');
|
||
return;
|
||
}
|
||
|
||
// Tab切换
|
||
if (y >= 240 && y <= 285) {
|
||
const tabWidth = (this.screenWidth - 30) / 2;
|
||
const newTab = x < 15 + tabWidth ? 0 : 1;
|
||
if (newTab !== this.currentTab) {
|
||
this.currentTab = newTab;
|
||
this.scrollY = 0;
|
||
this.calculateMaxScroll();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 卡片点击
|
||
this.handleCardClick(x, y);
|
||
}
|
||
|
||
handleCardClick(x, y) {
|
||
const list = this.currentTab === 0 ? this.collections : this.progress;
|
||
const startY = 295;
|
||
const cardHeight = 90;
|
||
const cardMargin = 12;
|
||
const padding = 15;
|
||
|
||
const adjustedY = y + this.scrollY;
|
||
const index = Math.floor((adjustedY - startY) / (cardHeight + cardMargin));
|
||
|
||
if (index >= 0 && index < list.length) {
|
||
const item = list[index];
|
||
const storyId = item.story_id || item.id;
|
||
const cardY = startY + index * (cardHeight + cardMargin) - this.scrollY;
|
||
const buttonX = padding + (this.screenWidth - padding * 2) - 65;
|
||
|
||
if (x >= buttonX && x <= buttonX + 52 && y >= cardY + 30 && y <= cardY + 58) {
|
||
this.main.sceneManager.switchScene('story', { storyId });
|
||
}
|
||
}
|
||
}
|
||
}
|