Files
ai_game/client/js/scenes/ProfileScene.js

372 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 个人中心场景
*/
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 });
}
}
}
}