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