2026-03-03 16:57:49 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 用户数据管理器
|
|
|
|
|
|
*/
|
2026-03-10 12:44:55 +08:00
|
|
|
|
import { get, post, del } from '../utils/http';
|
2026-03-03 16:57:49 +08:00
|
|
|
|
|
|
|
|
|
|
export default class UserManager {
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
this.userId = null;
|
|
|
|
|
|
this.openid = null;
|
|
|
|
|
|
this.nickname = '';
|
|
|
|
|
|
this.avatarUrl = '';
|
2026-03-11 12:10:19 +08:00
|
|
|
|
this.token = '';
|
2026-03-03 16:57:49 +08:00
|
|
|
|
this.isLoggedIn = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-11 12:10:19 +08:00
|
|
|
|
* 检查是否已登录(只检查本地缓存)
|
|
|
|
|
|
* @returns {boolean} 是否已登录
|
|
|
|
|
|
*/
|
|
|
|
|
|
checkLogin() {
|
|
|
|
|
|
const cached = wx.getStorageSync('userInfo');
|
|
|
|
|
|
if (cached && cached.userId && cached.token) {
|
|
|
|
|
|
this.userId = cached.userId;
|
|
|
|
|
|
this.openid = cached.openid;
|
|
|
|
|
|
this.nickname = cached.nickname || '游客';
|
|
|
|
|
|
this.avatarUrl = cached.avatarUrl || '';
|
|
|
|
|
|
this.token = cached.token;
|
|
|
|
|
|
this.isLoggedIn = true;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化用户(只恢复缓存,不自动登录)
|
2026-03-03 16:57:49 +08:00
|
|
|
|
*/
|
|
|
|
|
|
async init() {
|
2026-03-11 12:10:19 +08:00
|
|
|
|
// 只检查本地缓存,不自动登录
|
|
|
|
|
|
this.checkLogin();
|
|
|
|
|
|
}
|
2026-03-03 16:57:49 +08:00
|
|
|
|
|
2026-03-11 12:10:19 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 执行登录(供登录按钮调用)
|
|
|
|
|
|
* @param {Object} userInfo - 微信用户信息(头像、昵称等),可选
|
|
|
|
|
|
*/
|
|
|
|
|
|
async doLogin(userInfo = null) {
|
|
|
|
|
|
try {
|
2026-03-03 16:57:49 +08:00
|
|
|
|
// 获取登录code
|
|
|
|
|
|
const { code } = await this.wxLogin();
|
|
|
|
|
|
|
2026-03-11 12:10:19 +08:00
|
|
|
|
// 调用后端登录接口,传入用户信息
|
|
|
|
|
|
const result = await post('/user/login', {
|
|
|
|
|
|
code,
|
|
|
|
|
|
userInfo: userInfo ? {
|
|
|
|
|
|
nickname: userInfo.nickName,
|
|
|
|
|
|
avatarUrl: userInfo.avatarUrl,
|
|
|
|
|
|
gender: userInfo.gender || 0
|
|
|
|
|
|
} : null
|
|
|
|
|
|
});
|
2026-03-03 16:57:49 +08:00
|
|
|
|
|
|
|
|
|
|
this.userId = result.userId;
|
|
|
|
|
|
this.openid = result.openid;
|
2026-03-11 12:10:19 +08:00
|
|
|
|
// 优先使用后端返回的,其次用授权获取的
|
|
|
|
|
|
this.nickname = result.nickname || (userInfo?.nickName) || '游客';
|
|
|
|
|
|
this.avatarUrl = result.avatarUrl || (userInfo?.avatarUrl) || '';
|
|
|
|
|
|
this.token = result.token || '';
|
2026-03-03 16:57:49 +08:00
|
|
|
|
this.isLoggedIn = true;
|
|
|
|
|
|
|
2026-03-11 12:10:19 +08:00
|
|
|
|
// 缓存用户信息(包含 token)
|
2026-03-03 16:57:49 +08:00
|
|
|
|
wx.setStorageSync('userInfo', {
|
|
|
|
|
|
userId: this.userId,
|
|
|
|
|
|
openid: this.openid,
|
|
|
|
|
|
nickname: this.nickname,
|
2026-03-11 12:10:19 +08:00
|
|
|
|
avatarUrl: this.avatarUrl,
|
|
|
|
|
|
token: this.token
|
2026-03-03 16:57:49 +08:00
|
|
|
|
});
|
2026-03-11 12:10:19 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
userId: this.userId,
|
|
|
|
|
|
nickname: this.nickname,
|
|
|
|
|
|
avatarUrl: this.avatarUrl
|
|
|
|
|
|
};
|
2026-03-03 16:57:49 +08:00
|
|
|
|
} catch (error) {
|
2026-03-11 12:10:19 +08:00
|
|
|
|
console.error('登录失败:', error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 退出登录
|
|
|
|
|
|
*/
|
|
|
|
|
|
logout() {
|
|
|
|
|
|
this.userId = null;
|
|
|
|
|
|
this.openid = null;
|
|
|
|
|
|
this.nickname = '';
|
|
|
|
|
|
this.avatarUrl = '';
|
|
|
|
|
|
this.token = '';
|
|
|
|
|
|
this.isLoggedIn = false;
|
|
|
|
|
|
wx.removeStorageSync('userInfo');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新用户资料
|
|
|
|
|
|
*/
|
|
|
|
|
|
async updateProfile(nickname, avatarUrl) {
|
|
|
|
|
|
if (!this.isLoggedIn) return false;
|
|
|
|
|
|
try {
|
|
|
|
|
|
await post('/user/profile', {
|
|
|
|
|
|
nickname,
|
|
|
|
|
|
avatarUrl,
|
|
|
|
|
|
gender: 0
|
|
|
|
|
|
}, { params: { userId: this.userId } });
|
|
|
|
|
|
|
|
|
|
|
|
// 更新本地数据
|
|
|
|
|
|
this.nickname = nickname;
|
|
|
|
|
|
this.avatarUrl = avatarUrl;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新缓存(保留 token)
|
|
|
|
|
|
wx.setStorageSync('userInfo', {
|
|
|
|
|
|
userId: this.userId,
|
|
|
|
|
|
openid: this.openid,
|
|
|
|
|
|
nickname: this.nickname,
|
|
|
|
|
|
avatarUrl: this.avatarUrl,
|
|
|
|
|
|
token: this.token
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('更新资料失败:', e);
|
|
|
|
|
|
return false;
|
2026-03-03 16:57:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 微信登录(带超时)
|
|
|
|
|
|
*/
|
|
|
|
|
|
wxLogin() {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const timeout = setTimeout(() => {
|
|
|
|
|
|
reject(new Error('登录超时'));
|
2026-03-11 12:10:19 +08:00
|
|
|
|
}, 5000);
|
2026-03-03 16:57:49 +08:00
|
|
|
|
|
|
|
|
|
|
wx.login({
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
clearTimeout(timeout);
|
2026-03-11 12:10:19 +08:00
|
|
|
|
if (res.code) {
|
|
|
|
|
|
resolve(res);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reject(new Error('获取code失败'));
|
|
|
|
|
|
}
|
2026-03-03 16:57:49 +08:00
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
|
reject(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取用户游玩进度
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getProgress(storyId = null) {
|
|
|
|
|
|
if (!this.isLoggedIn) return null;
|
|
|
|
|
|
return await get('/user/progress', { userId: this.userId, storyId });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 保存用户进度
|
|
|
|
|
|
*/
|
|
|
|
|
|
async saveProgress(storyId, currentNodeKey, isCompleted = false, endingReached = '') {
|
|
|
|
|
|
if (!this.isLoggedIn) return;
|
|
|
|
|
|
await post('/user/progress', {
|
|
|
|
|
|
userId: this.userId,
|
|
|
|
|
|
storyId,
|
|
|
|
|
|
currentNodeKey,
|
|
|
|
|
|
isCompleted,
|
|
|
|
|
|
endingReached
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 点赞故事
|
|
|
|
|
|
*/
|
|
|
|
|
|
async likeStory(storyId, isLiked) {
|
|
|
|
|
|
if (!this.isLoggedIn) return;
|
|
|
|
|
|
await post('/user/like', {
|
|
|
|
|
|
userId: this.userId,
|
|
|
|
|
|
storyId,
|
|
|
|
|
|
isLiked
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 收藏故事
|
|
|
|
|
|
*/
|
|
|
|
|
|
async collectStory(storyId, isCollected) {
|
|
|
|
|
|
if (!this.isLoggedIn) return;
|
|
|
|
|
|
await post('/user/collect', {
|
|
|
|
|
|
userId: this.userId,
|
|
|
|
|
|
storyId,
|
|
|
|
|
|
isCollected
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取收藏列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getCollections() {
|
|
|
|
|
|
if (!this.isLoggedIn) return [];
|
|
|
|
|
|
return await get('/user/collections', { userId: this.userId });
|
|
|
|
|
|
}
|
2026-03-03 17:06:08 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取最近游玩的故事
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getRecentPlayed() {
|
|
|
|
|
|
if (!this.isLoggedIn) return [];
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await get('/user/recent-played', { userId: this.userId, limit: 10 });
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取AI创作历史
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getAIHistory() {
|
|
|
|
|
|
if (!this.isLoggedIn) return [];
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await get('/user/ai-history', { userId: this.userId, limit: 20 });
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取AI配额
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getAIQuota() {
|
|
|
|
|
|
if (!this.isLoggedIn) return { daily: 3, used: 0, purchased: 0 };
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await get('/user/ai-quota', { userId: this.userId });
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return { daily: 3, used: 0, purchased: 0 };
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-03 17:21:53 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取我的作品
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getMyWorks() {
|
|
|
|
|
|
if (!this.isLoggedIn) return [];
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await get('/user/my-works', { userId: this.userId });
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取草稿箱
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getDrafts() {
|
|
|
|
|
|
if (!this.isLoggedIn) return [];
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await get('/user/drafts', { userId: this.userId });
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-10 12:44:55 +08:00
|
|
|
|
|
|
|
|
|
|
// ========== 游玩记录相关 ==========
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 保存游玩记录
|
|
|
|
|
|
*/
|
|
|
|
|
|
async savePlayRecord(storyId, endingName, endingType, pathHistory) {
|
|
|
|
|
|
if (!this.isLoggedIn) return null;
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await post('/user/play-record', {
|
|
|
|
|
|
userId: this.userId,
|
|
|
|
|
|
storyId,
|
|
|
|
|
|
endingName,
|
|
|
|
|
|
endingType: endingType || '',
|
|
|
|
|
|
pathHistory: pathHistory || []
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('保存游玩记录失败:', e);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取游玩记录列表
|
|
|
|
|
|
* @param {number} storyId - 可选,指定故事ID获取该故事的所有记录
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getPlayRecords(storyId = null) {
|
|
|
|
|
|
if (!this.isLoggedIn) return [];
|
|
|
|
|
|
try {
|
|
|
|
|
|
const params = { userId: this.userId };
|
|
|
|
|
|
if (storyId) params.storyId = storyId;
|
|
|
|
|
|
return await get('/user/play-records', params);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('获取游玩记录失败:', e);
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取单条记录详情
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getPlayRecordDetail(recordId) {
|
|
|
|
|
|
if (!this.isLoggedIn) return null;
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await get(`/user/play-records/${recordId}`);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('获取记录详情失败:', e);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除游玩记录
|
|
|
|
|
|
async deletePlayRecord(recordId) {
|
|
|
|
|
|
if (!this.isLoggedIn) return false;
|
|
|
|
|
|
try {
|
|
|
|
|
|
await del(`/user/play-records/${recordId}`);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('删除记录失败:', e);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-03 16:57:49 +08:00
|
|
|
|
}
|