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

591 lines
19 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);
// Tab: 0我的作品 1草稿箱 2收藏 3游玩记录
this.currentTab = 0;
this.tabs = ['作品', '草稿', '收藏', '记录'];
// 数据
this.myWorks = [];
this.drafts = [];
this.collections = [];
this.progress = [];
// 统计
this.stats = {
works: 0,
totalPlays: 0,
totalLikes: 0,
earnings: 0
};
// 滚动
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) {
try {
this.myWorks = await this.main.userManager.getMyWorks?.() || [];
this.drafts = await this.main.userManager.getDrafts?.() || [];
this.collections = await this.main.userManager.getCollections() || [];
this.progress = await this.main.userManager.getProgress() || [];
// 计算统计
this.stats.works = this.myWorks.length;
this.stats.totalPlays = this.myWorks.reduce((sum, w) => sum + (w.play_count || 0), 0);
this.stats.totalLikes = this.myWorks.reduce((sum, w) => sum + (w.like_count || 0), 0);
this.stats.earnings = this.myWorks.reduce((sum, w) => sum + (w.earnings || 0), 0);
} catch (e) {
console.error('加载数据失败:', e);
}
}
this.calculateMaxScroll();
}
getCurrentList() {
switch (this.currentTab) {
case 0: return this.myWorks;
case 1: return this.drafts;
case 2: return this.collections;
case 3: return this.progress;
default: return [];
}
}
calculateMaxScroll() {
const list = this.getCurrentList();
const cardHeight = this.currentTab <= 1 ? 100 : 85;
const gap = 10;
const headerHeight = 260;
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) {
this.renderBackground(ctx);
this.renderHeader(ctx);
this.renderUserCard(ctx);
this.renderTabs(ctx);
this.renderList(ctx);
}
renderBackground(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);
}
renderHeader(ctx) {
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);
// 设置按钮
ctx.fillStyle = 'rgba(255,255,255,0.6)';
ctx.font = '18px sans-serif';
ctx.textAlign = 'right';
ctx.fillText('⚙', this.screenWidth - 20, 35);
}
renderUserCard(ctx) {
const cardY = 55;
const cardH = 145;
const user = this.main.userManager;
// 卡片背景
ctx.fillStyle = 'rgba(255,255,255,0.06)';
this.roundRect(ctx, 12, cardY, this.screenWidth - 24, cardH, 14);
ctx.fill();
// 头像
const avatarSize = 50;
const avatarX = 30;
const avatarY = cardY + 18;
const avatarGradient = ctx.createLinearGradient(avatarX, avatarY, avatarX + avatarSize, avatarY + avatarSize);
avatarGradient.addColorStop(0, '#ff6b6b');
avatarGradient.addColorStop(1, '#ffd700');
ctx.fillStyle = avatarGradient;
ctx.beginPath();
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 20px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(user.nickname ? user.nickname[0] : '游', avatarX + avatarSize / 2, avatarY + avatarSize / 2 + 7);
// 昵称和ID
ctx.textAlign = 'left';
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 16px sans-serif';
ctx.fillText(user.nickname || '游客用户', avatarX + avatarSize + 15, avatarY + 22);
ctx.fillStyle = 'rgba(255,255,255,0.4)';
ctx.font = '11px sans-serif';
ctx.fillText(`ID: ${user.userId || '未登录'}`, avatarX + avatarSize + 15, avatarY + 42);
// 创作者标签
if (this.myWorks.length > 0) {
const tagX = avatarX + avatarSize + 15 + ctx.measureText(`ID: ${user.userId || '未登录'}`).width + 10;
ctx.fillStyle = '#a855f7';
this.roundRect(ctx, tagX, avatarY + 30, 45, 18, 9);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 9px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('创作者', tagX + 22, avatarY + 42);
}
// 分割线
const lineY = cardY + 80;
ctx.strokeStyle = 'rgba(255,255,255,0.08)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(25, lineY);
ctx.lineTo(this.screenWidth - 25, lineY);
ctx.stroke();
// 统计数据
const statsY = lineY + 30;
const statW = (this.screenWidth - 24) / 4;
const statsData = [
{ num: this.stats.works, label: '作品' },
{ num: this.stats.totalPlays, label: '播放' },
{ num: this.stats.totalLikes, label: '获赞' },
{ num: this.stats.earnings.toFixed(1), label: '收益' }
];
statsData.forEach((stat, i) => {
const x = 12 + statW * i + statW / 2;
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(this.formatNumber(stat.num), x, statsY);
ctx.fillStyle = 'rgba(255,255,255,0.45)';
ctx.font = '10px sans-serif';
ctx.fillText(stat.label, x, statsY + 15);
});
}
renderTabs(ctx) {
const tabY = 210;
const tabW = (this.screenWidth - 24) / this.tabs.length;
this.tabRects = [];
this.tabs.forEach((tab, index) => {
const x = 12 + index * tabW;
const isActive = index === this.currentTab;
const centerX = x + tabW / 2;
ctx.fillStyle = isActive ? '#ffffff' : 'rgba(255,255,255,0.45)';
ctx.font = isActive ? 'bold 13px sans-serif' : '13px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(tab, centerX, tabY + 12);
// 下划线
if (isActive) {
const lineGradient = ctx.createLinearGradient(centerX - 14, tabY + 20, centerX + 14, tabY + 20);
lineGradient.addColorStop(0, '#ff6b6b');
lineGradient.addColorStop(1, '#ffd700');
ctx.fillStyle = lineGradient;
this.roundRect(ctx, centerX - 14, tabY + 18, 28, 3, 1.5);
ctx.fill();
}
this.tabRects.push({ x, y: tabY - 5, width: tabW, height: 30, index });
});
}
renderList(ctx) {
const list = this.getCurrentList();
const startY = 250;
const cardH = this.currentTab <= 1 ? 100 : 85;
const gap = 10;
const padding = 12;
ctx.save();
ctx.beginPath();
ctx.rect(0, startY - 5, this.screenWidth, this.screenHeight - startY + 5);
ctx.clip();
if (list.length === 0) {
ctx.fillStyle = 'rgba(255,255,255,0.35)';
ctx.font = '13px sans-serif';
ctx.textAlign = 'center';
const emptyTexts = ['还没有发布作品,去创作吧', '草稿箱空空如也', '还没有收藏的故事', '还没有游玩记录'];
ctx.fillText(emptyTexts[this.currentTab], this.screenWidth / 2, startY + 50);
// 创作引导按钮
if (this.currentTab === 0) {
const btnY = startY + 80;
const btnGradient = ctx.createLinearGradient(this.screenWidth / 2 - 50, btnY, this.screenWidth / 2 + 50, btnY);
btnGradient.addColorStop(0, '#a855f7');
btnGradient.addColorStop(1, '#ec4899');
ctx.fillStyle = btnGradient;
this.roundRect(ctx, this.screenWidth / 2 - 50, btnY, 100, 36, 18);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 13px sans-serif';
ctx.fillText('✨ 开始创作', this.screenWidth / 2, btnY + 23);
this.createBtnRect = { x: this.screenWidth / 2 - 50, y: btnY, width: 100, height: 36 };
}
ctx.restore();
return;
}
list.forEach((item, index) => {
const y = startY + index * (cardH + gap) - this.scrollY;
if (y > startY - cardH && y < this.screenHeight) {
if (this.currentTab === 0) {
this.renderWorkCard(ctx, item, padding, y, this.screenWidth - padding * 2, cardH, index);
} else if (this.currentTab === 1) {
this.renderDraftCard(ctx, item, padding, y, this.screenWidth - padding * 2, cardH, index);
} else {
this.renderSimpleCard(ctx, item, padding, y, this.screenWidth - padding * 2, cardH, index);
}
}
});
ctx.restore();
}
renderWorkCard(ctx, item, x, y, w, h, index) {
// 卡片背景
ctx.fillStyle = 'rgba(255,255,255,0.05)';
this.roundRect(ctx, x, y, w, h, 12);
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();
ctx.fillStyle = 'rgba(255,255,255,0.8)';
ctx.font = 'bold 9px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(item.category || '故事', x + 8 + coverW / 2, y + 8 + coverH / 2 + 3);
const textX = x + 88;
const maxW = w - 100;
// 标题
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'left';
ctx.fillText(this.truncateText(ctx, item.title || '未命名', maxW - 60), textX, y + 25);
// 审核状态标签
const statusMap = {
0: { text: '草稿', color: '#888888' },
1: { text: '审核中', color: '#f59e0b' },
2: { text: '已发布', color: '#22c55e' },
3: { text: '已下架', color: '#ef4444' },
4: { text: '被拒绝', color: '#ef4444' }
};
const status = statusMap[item.status] || statusMap[0];
const statusW = ctx.measureText(status.text).width + 12;
ctx.fillStyle = status.color + '33';
this.roundRect(ctx, textX + ctx.measureText(this.truncateText(ctx, item.title || '未命名', maxW - 60)).width + 8, y + 12, statusW, 18, 9);
ctx.fill();
ctx.fillStyle = status.color;
ctx.font = 'bold 10px sans-serif';
ctx.fillText(status.text, textX + ctx.measureText(this.truncateText(ctx, item.title || '未命名', maxW - 60)).width + 8 + statusW / 2, y + 24);
// 数据统计
ctx.fillStyle = 'rgba(255,255,255,0.45)';
ctx.font = '11px sans-serif';
ctx.textAlign = 'left';
ctx.fillText(`${this.formatNumber(item.play_count || 0)}`, textX, y + 50);
ctx.fillText(`${this.formatNumber(item.like_count || 0)}`, textX + 55, y + 50);
ctx.fillText(`💰 ${(item.earnings || 0).toFixed(1)}`, textX + 105, y + 50);
// 操作按钮
const btnY = y + 65;
const btns = ['编辑', '数据'];
btns.forEach((btn, i) => {
const btnX = textX + i * 55;
ctx.fillStyle = 'rgba(255,255,255,0.1)';
this.roundRect(ctx, btnX, btnY, 48, 24, 12);
ctx.fill();
ctx.fillStyle = 'rgba(255,255,255,0.7)';
ctx.font = '11px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(btn, btnX + 24, btnY + 16);
});
}
renderDraftCard(ctx, item, x, y, w, h, index) {
ctx.fillStyle = 'rgba(255,255,255,0.05)';
this.roundRect(ctx, x, y, w, h, 12);
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();
// AI标签
if (item.source === 'ai') {
ctx.fillStyle = '#a855f7';
this.roundRect(ctx, x + 8, y + 8, 28, 16, 8);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 9px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('AI', x + 22, y + 19);
}
const textX = x + 88;
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'left';
ctx.fillText(this.truncateText(ctx, item.title || '未命名草稿', w - 180), textX, y + 25);
ctx.fillStyle = 'rgba(255,255,255,0.4)';
ctx.font = '11px sans-serif';
ctx.fillText(`创建于 ${item.created_at || '刚刚'}`, textX, y + 48);
ctx.fillText(`${item.node_count || 0} 个节点`, textX + 100, y + 48);
// 按钮
const btnY = y + 62;
const btns = [{ text: '继续编辑', primary: true }, { text: '删除', primary: false }];
let btnX = textX;
btns.forEach((btn) => {
const btnW = btn.primary ? 65 : 45;
if (btn.primary) {
const btnGradient = ctx.createLinearGradient(btnX, btnY, btnX + btnW, btnY);
btnGradient.addColorStop(0, '#a855f7');
btnGradient.addColorStop(1, '#ec4899');
ctx.fillStyle = btnGradient;
} else {
ctx.fillStyle = 'rgba(255,255,255,0.1)';
}
this.roundRect(ctx, btnX, btnY, btnW, 26, 13);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = btn.primary ? 'bold 11px sans-serif' : '11px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(btn.text, btnX + btnW / 2, btnY + 17);
btnX += btnW + 8;
});
}
renderSimpleCard(ctx, item, x, y, w, h, index) {
ctx.fillStyle = 'rgba(255,255,255,0.05)';
this.roundRect(ctx, x, y, w, h, 12);
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 textX = x + 78;
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'left';
ctx.fillText(this.truncateText(ctx, item.story_title || item.title || '未知', w - 150), textX, y + 28);
ctx.fillStyle = 'rgba(255,255,255,0.45)';
ctx.font = '11px sans-serif';
if (this.currentTab === 3 && item.is_completed) {
ctx.fillStyle = '#4ade80';
ctx.fillText('✓ 已完成', textX, y + 50);
} else if (this.currentTab === 3) {
ctx.fillText('进行中...', textX, y + 50);
} else {
ctx.fillText(item.category || '', textX, y + 50);
}
// 继续按钮
const btnGradient = ctx.createLinearGradient(x + w - 58, y + 28, x + w - 10, y + 28);
btnGradient.addColorStop(0, '#ff6b6b');
btnGradient.addColorStop(1, '#ffd700');
ctx.fillStyle = btnGradient;
this.roundRect(ctx, x + w - 58, y + 28, 48, 26, 13);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 11px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('继续', x + w - 34, y + 45);
}
getGradientColors(index) {
const colors = [
['#ff758c', '#ff7eb3'],
['#667eea', '#764ba2'],
['#4facfe', '#00f2fe'],
['#43e97b', '#38f9d7'],
['#fa709a', '#fee140'],
['#a855f7', '#ec4899']
];
return colors[index % colors.length];
}
formatNumber(num) {
if (!num) return '0';
if (num >= 10000) return (num / 10000).toFixed(1) + 'w';
if (num >= 1000) return (num / 1000).toFixed(1) + 'k';
return num.toString();
}
truncateText(ctx, text, maxW) {
if (!text) return '';
if (ctx.measureText(text).width <= maxW) return text;
let t = text;
while (t.length > 0 && ctx.measureText(t + '...').width > maxW) t = t.slice(0, -1);
return t + '...';
}
roundRect(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
ctx.lineTo(x + w, y + h - r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, 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 (this.tabRects) {
for (const rect of this.tabRects) {
if (x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height) {
if (this.currentTab !== rect.index) {
this.currentTab = rect.index;
this.scrollY = 0;
this.calculateMaxScroll();
}
return;
}
}
}
// 创作按钮
if (this.createBtnRect && this.currentTab === 0) {
const btn = this.createBtnRect;
if (x >= btn.x && x <= btn.x + btn.width && y >= btn.y && y <= btn.y + btn.height) {
this.main.sceneManager.switchScene('aiCreate');
return;
}
}
// 卡片点击
this.handleCardClick(x, y);
}
handleCardClick(x, y) {
const list = this.getCurrentList();
const startY = 250;
const cardH = this.currentTab <= 1 ? 100 : 85;
const gap = 10;
const adjustedY = y + this.scrollY;
const index = Math.floor((adjustedY - startY) / (cardH + gap));
if (index >= 0 && index < list.length) {
const item = list[index];
const storyId = item.story_id || item.id;
if (this.currentTab >= 2) {
// 收藏/记录 - 跳转播放
this.main.sceneManager.switchScene('story', { storyId });
} else if (this.currentTab === 1) {
// 草稿 - 跳转编辑暂用AI创作
this.main.sceneManager.switchScene('aiCreate', { draftId: item.id });
}
// 作品Tab的按钮操作需要更精确判断暂略
}
}
}