feat: 添加微信授权登录和修改昵称功能

This commit is contained in:
wangwuww111
2026-03-11 12:10:19 +08:00
parent 906b5649f7
commit eac6b2fd1f
20 changed files with 1021 additions and 67 deletions

View File

@@ -9,50 +9,125 @@ export default class UserManager {
this.openid = null;
this.nickname = '';
this.avatarUrl = '';
this.token = '';
this.isLoggedIn = false;
}
/**
* 初始化用户
* 检查是否已登录(只检查本地缓存)
* @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;
}
/**
* 初始化用户(只恢复缓存,不自动登录)
*/
async init() {
try {
// 尝试从本地存储恢复用户信息
const cached = wx.getStorageSync('userInfo');
if (cached) {
this.userId = cached.userId;
this.openid = cached.openid;
this.nickname = cached.nickname;
this.avatarUrl = cached.avatarUrl;
this.isLoggedIn = true;
return;
}
// 只检查本地缓存,不自动登录
this.checkLogin();
}
/**
* 执行登录(供登录按钮调用)
* @param {Object} userInfo - 微信用户信息(头像、昵称等),可选
*/
async doLogin(userInfo = null) {
try {
// 获取登录code
const { code } = await this.wxLogin();
// 调用后端登录接口
const result = await post('/user/login', { code });
// 调用后端登录接口,传入用户信息
const result = await post('/user/login', {
code,
userInfo: userInfo ? {
nickname: userInfo.nickName,
avatarUrl: userInfo.avatarUrl,
gender: userInfo.gender || 0
} : null
});
this.userId = result.userId;
this.openid = result.openid;
this.nickname = result.nickname || '游客';
this.avatarUrl = result.avatarUrl || '';
// 优先使用后端返回的,其次用授权获取的
this.nickname = result.nickname || (userInfo?.nickName) || '游客';
this.avatarUrl = result.avatarUrl || (userInfo?.avatarUrl) || '';
this.token = result.token || '';
this.isLoggedIn = true;
// 缓存用户信息
// 缓存用户信息(包含 token
wx.setStorageSync('userInfo', {
userId: this.userId,
openid: this.openid,
nickname: this.nickname,
avatarUrl: this.avatarUrl
avatarUrl: this.avatarUrl,
token: this.token
});
return {
userId: this.userId,
nickname: this.nickname,
avatarUrl: this.avatarUrl
};
} catch (error) {
console.error('用户初始化失败:', error);
// 使用临时身份
this.userId = 0;
this.nickname = '游客';
this.isLoggedIn = false;
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;
}
}
@@ -63,12 +138,16 @@ export default class UserManager {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('登录超时'));
}, 3000);
}, 5000);
wx.login({
success: (res) => {
clearTimeout(timeout);
resolve(res);
if (res.code) {
resolve(res);
} else {
reject(new Error('获取code失败'));
}
},
fail: (err) => {
clearTimeout(timeout);

View File

@@ -46,13 +46,37 @@ export default class Main {
});
console.log('[Main] 云环境初始化完成');
// 用户初始化(失败不阻塞
console.log('[Main] 初始化用户...');
await this.userManager.init().catch(e => {
console.warn('[Main] 用户初始化失败,使用游客模式:', e);
});
console.log('[Main] 用户初始化完成');
// 检查用户是否已登录(只检查缓存,不自动登录
console.log('[Main] 检查登录状态...');
const isLoggedIn = this.userManager.checkLogin();
console.log('[Main] 登录状态:', isLoggedIn ? '已登录' : '未登录');
// 隐藏加载界面
this.hideLoading();
if (!isLoggedIn) {
// 未登录,显示登录场景
console.log('[Main] 未登录,显示登录页面');
this.sceneManager.switchScene('login');
} else {
// 已登录,加载数据并进入首页
await this.loadAndEnterHome();
}
// 设置分享
this.setupShare();
} catch (error) {
console.error('[Main] 初始化失败:', error);
this.hideLoading();
this.showError('初始化失败,请重试');
}
}
// 加载数据并进入首页
async loadAndEnterHome() {
this.showLoading('正在加载...');
try {
// 加载故事列表
console.log('[Main] 加载故事列表...');
await this.storyManager.loadStoryList();
@@ -65,17 +89,15 @@ export default class Main {
this.sceneManager.switchScene('home');
console.log('[Main] 初始化完成,进入首页');
// 设置分享
this.setupShare();
// 启动草稿检查(仅登录用户)
if (this.userManager.isLoggedIn) {
this.startDraftChecker();
}
} catch (error) {
console.error('[Main] 初始化失败:', error);
this.hideLoading();
this.showError('初始化失败,请重试');
console.error('[Main] 加载失败:', error);
// 加载失败也进入首页,让用户可以重试
this.sceneManager.switchScene('home');
}
}

View File

@@ -0,0 +1,257 @@
/**
* 登录场景 - 微信授权登录
*/
import BaseScene from './BaseScene';
export default class LoginScene extends BaseScene {
constructor(main, params = {}) {
super(main, params);
this.isLoading = false;
this.userInfoButton = null;
}
loadAssets() {
// 不加载外部图片,使用 Canvas 绘制
}
init() {
console.log('[LoginScene] 初始化登录场景');
// 创建微信授权按钮
this.createUserInfoButton();
}
// 创建微信用户信息授权按钮
createUserInfoButton() {
const { screenWidth, screenHeight } = this;
const btnWidth = 280;
const btnHeight = 50;
const btnX = (screenWidth - btnWidth) / 2;
const btnY = screenHeight * 0.55;
// 创建透明的用户信息按钮,覆盖在登录按钮上
this.userInfoButton = wx.createUserInfoButton({
type: 'text',
text: '',
style: {
left: btnX,
top: btnY,
width: btnWidth,
height: btnHeight,
backgroundColor: 'transparent',
borderColor: 'transparent',
borderWidth: 0,
borderRadius: 25,
color: 'transparent',
textAlign: 'center',
fontSize: 18,
lineHeight: 50
}
});
// 监听点击事件
this.userInfoButton.onTap(async (res) => {
console.log('[LoginScene] 用户信息授权回调:', res);
if (res.userInfo) {
// 用户同意授权,获取到了头像昵称
await this.doLoginWithUserInfo(res.userInfo);
} else {
// 未获取到用户信息,静默登录
console.log('[LoginScene] 未获取到用户信息,使用静默登录');
await this.doLoginWithUserInfo(null);
}
});
}
render(ctx) {
const { screenWidth, screenHeight } = this;
// 绘制背景渐变
const gradient = ctx.createLinearGradient(0, 0, 0, screenHeight);
gradient.addColorStop(0, '#1a1a2e');
gradient.addColorStop(0.5, '#16213e');
gradient.addColorStop(1, '#0f0f23');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, screenWidth, screenHeight);
// 绘制装饰性光效
this.renderGlow(ctx);
// 绘制Logo
this.renderLogo(ctx);
// 绘制应用名称
this.renderTitle(ctx);
// 绘制登录按钮
this.renderLoginButton(ctx);
// 绘制底部提示
this.renderFooter(ctx);
}
renderGlow(ctx) {
const { screenWidth, screenHeight } = this;
// 顶部光晕
const topGlow = ctx.createRadialGradient(
screenWidth / 2, -100, 0,
screenWidth / 2, -100, 400
);
topGlow.addColorStop(0, 'rgba(106, 90, 205, 0.3)');
topGlow.addColorStop(1, 'rgba(106, 90, 205, 0)');
ctx.fillStyle = topGlow;
ctx.fillRect(0, 0, screenWidth, 400);
}
renderLogo(ctx) {
const { screenWidth, screenHeight } = this;
const logoSize = 120;
const logoX = (screenWidth - logoSize) / 2;
const logoY = screenHeight * 0.2;
// 绘制Logo背景圆
ctx.beginPath();
ctx.arc(screenWidth / 2, logoY + logoSize / 2, logoSize / 2 + 10, 0, Math.PI * 2);
const logoGradient = ctx.createRadialGradient(
screenWidth / 2, logoY + logoSize / 2, 0,
screenWidth / 2, logoY + logoSize / 2, logoSize / 2 + 10
);
logoGradient.addColorStop(0, 'rgba(106, 90, 205, 0.4)');
logoGradient.addColorStop(1, 'rgba(106, 90, 205, 0.1)');
ctx.fillStyle = logoGradient;
ctx.fill();
// 绘制内圆
ctx.fillStyle = '#6a5acd';
ctx.beginPath();
ctx.arc(screenWidth / 2, logoY + logoSize / 2, logoSize / 2 - 10, 0, Math.PI * 2);
ctx.fill();
// 绘制书本图标
ctx.fillStyle = '#fff';
ctx.font = `${logoSize * 0.5}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('📖', screenWidth / 2, logoY + logoSize / 2);
}
renderTitle(ctx) {
const { screenWidth, screenHeight } = this;
const titleY = screenHeight * 0.2 + 160;
// 应用名称
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 32px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('AI互动故事', screenWidth / 2, titleY);
// 副标题
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
ctx.font = '16px sans-serif';
ctx.fillText('探索无限可能的剧情世界', screenWidth / 2, titleY + 40);
}
renderLoginButton(ctx) {
const { screenWidth, screenHeight } = this;
// 按钮位置和尺寸
const btnWidth = 280;
const btnHeight = 50;
const btnX = (screenWidth - btnWidth) / 2;
const btnY = screenHeight * 0.55;
// 绘制按钮背景
const btnGradient = ctx.createLinearGradient(btnX, btnY, btnX + btnWidth, btnY);
if (this.isLoading) {
btnGradient.addColorStop(0, '#666666');
btnGradient.addColorStop(1, '#888888');
} else {
btnGradient.addColorStop(0, '#07c160');
btnGradient.addColorStop(1, '#06ae56');
}
this.roundRect(ctx, btnX, btnY, btnWidth, btnHeight, 25);
ctx.fillStyle = btnGradient;
ctx.fill();
// 绘制按钮阴影效果
if (!this.isLoading) {
ctx.shadowColor = 'rgba(7, 193, 96, 0.4)';
ctx.shadowBlur = 20;
ctx.shadowOffsetY = 5;
this.roundRect(ctx, btnX, btnY, btnWidth, btnHeight, 25);
ctx.fill();
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetY = 0;
}
// 绘制按钮文字
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 18px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
if (this.isLoading) {
ctx.fillText('登录中...', screenWidth / 2, btnY + btnHeight / 2);
} else {
ctx.fillText('微信一键登录', screenWidth / 2, btnY + btnHeight / 2);
}
}
renderFooter(ctx) {
const { screenWidth, screenHeight } = this;
const footerY = screenHeight - 60;
// 用户协议提示
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('登录即表示同意《用户协议》和《隐私政策》', screenWidth / 2, footerY);
}
// 不再需要手动处理点击事件,由 userInfoButton 处理
onTouchEnd(e) {}
async doLoginWithUserInfo(userInfo) {
if (this.isLoading) return;
this.isLoading = true;
console.log('[LoginScene] 开始登录,用户信息:', userInfo);
try {
// 调用 UserManager 的登录方法,传入用户信息
await this.main.userManager.doLogin(userInfo);
console.log('[LoginScene] 登录成功,加载数据并进入首页');
// 隐藏授权按钮
if (this.userInfoButton) {
this.userInfoButton.hide();
}
// 登录成功,加载数据并进入首页
await this.main.loadAndEnterHome();
} catch (error) {
console.error('[LoginScene] 登录失败:', error);
this.isLoading = false;
wx.showToast({
title: error.message || '登录失败',
icon: 'none'
});
}
}
destroy() {
console.log('[LoginScene] 销毁登录场景');
// 销毁授权按钮
if (this.userInfoButton) {
this.userInfoButton.destroy();
this.userInfoButton = null;
}
}
}

View File

@@ -21,6 +21,10 @@ export default class ProfileScene extends BaseScene {
this.selectedStoryRecords = []; // 选中故事的记录列表
this.selectedStoryInfo = {}; // 选中故事的信息
// 头像相关
this.avatarImage = null;
this.avatarImageLoaded = false;
// 统计
this.stats = {
works: 0,
@@ -40,6 +44,29 @@ export default class ProfileScene extends BaseScene {
async init() {
await this.loadData();
this.loadAvatarImage();
}
// 加载头像图片
loadAvatarImage() {
let avatarUrl = this.main.userManager.avatarUrl;
if (!avatarUrl) return;
// 如果是相对路径,拼接完整 URL
if (avatarUrl.startsWith('/uploads')) {
avatarUrl = 'http://172.20.10.8:8000' + avatarUrl;
}
if (avatarUrl.startsWith('http')) {
this.avatarImage = wx.createImage();
this.avatarImage.onload = () => {
this.avatarImageLoaded = true;
};
this.avatarImage.onerror = () => {
this.avatarImageLoaded = false;
};
this.avatarImage.src = avatarUrl;
}
}
async loadData() {
@@ -155,18 +182,42 @@ export default class ProfileScene extends BaseScene {
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();
// 保存头像区域用于点击检测
this.avatarRect = { x: avatarX, y: avatarY, width: avatarSize, height: avatarSize };
// 如果有头像图片则绘制图片,否则绘制默认渐变头像
if (this.avatarImage && this.avatarImageLoaded) {
ctx.save();
ctx.beginPath();
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
ctx.clip();
ctx.drawImage(this.avatarImage, avatarX, avatarY, avatarSize, avatarSize);
ctx.restore();
} else {
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);
}
// 编辑图标(头像右下角)
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.beginPath();
ctx.arc(avatarX + avatarSize - 8, avatarY + avatarSize - 8, 10, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 20px sans-serif';
ctx.font = '10px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(user.nickname ? user.nickname[0] : '', avatarX + avatarSize / 2, avatarY + avatarSize / 2 + 7);
ctx.fillText('', avatarX + avatarSize - 8, avatarY + avatarSize - 5);
// 昵称和ID
ctx.textAlign = 'left';
@@ -713,6 +764,21 @@ export default class ProfileScene extends BaseScene {
return;
}
// 设置按钮(右上角)
if (y < 50 && x > this.screenWidth - 50) {
this.showSettingsMenu();
return;
}
// 头像点击
if (this.avatarRect) {
const rect = this.avatarRect;
if (x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height) {
this.showAvatarOptions();
return;
}
}
// Tab切换
if (this.tabRects) {
for (const rect of this.tabRects) {
@@ -946,4 +1012,196 @@ export default class ProfileScene extends BaseScene {
}
});
}
// 显示设置菜单
showSettingsMenu() {
wx.showActionSheet({
itemList: ['修改头像', '修改昵称', '退出登录'],
success: (res) => {
switch (res.tapIndex) {
case 0:
this.chooseAndUploadAvatar();
break;
case 1:
this.showEditNicknameDialog();
break;
case 2:
this.confirmLogout();
break;
}
}
});
}
// 显示头像选项
showAvatarOptions() {
wx.showActionSheet({
itemList: ['从相册选择', '取消'],
success: (res) => {
if (res.tapIndex === 0) {
this.chooseAndUploadAvatar();
}
}
});
}
// 选择并上传头像
chooseAndUploadAvatar() {
console.log('[ProfileScene] 开始选择头像');
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
sizeType: ['compressed'],
success: async (res) => {
console.log('[ProfileScene] 选择图片成功:', res);
const tempFilePath = res.tempFiles[0].tempFilePath;
wx.showLoading({ title: '上传中...' });
try {
// 上传图片到服务器
const uploadRes = await this.uploadAvatar(tempFilePath);
console.log('[ProfileScene] 上传结果:', uploadRes);
if (uploadRes && uploadRes.url) {
// 更新用户头像
const success = await this.main.userManager.updateProfile(
this.main.userManager.nickname,
uploadRes.url
);
wx.hideLoading();
if (success) {
// 重新加载头像
this.loadAvatarImage();
wx.showToast({ title: '头像更新成功', icon: 'success' });
} else {
wx.showToast({ title: '更新失败', icon: 'none' });
}
} else {
wx.hideLoading();
wx.showToast({ title: '上传失败', icon: 'none' });
}
} catch (error) {
wx.hideLoading();
console.error('[ProfileScene] 上传头像失败:', error);
wx.showToast({ title: '上传失败', icon: 'none' });
}
},
fail: (err) => {
console.error('[ProfileScene] 选择图片失败:', err);
if (err.errMsg && err.errMsg.indexOf('cancel') === -1) {
wx.showToast({ title: '选择图片失败', icon: 'none' });
}
}
});
}
// 上传头像到服务器
uploadAvatar(filePath) {
return new Promise((resolve, reject) => {
const token = this.main.userManager.token || '';
const baseUrl = 'http://172.20.10.8:8000'; // 与 http.js 保持一致
wx.uploadFile({
url: `${baseUrl}/api/upload/avatar`,
filePath: filePath,
name: 'file',
header: {
'Authorization': token ? `Bearer ${token}` : ''
},
success: (res) => {
try {
const data = JSON.parse(res.data);
if (data.code === 0) {
resolve(data.data);
} else {
reject(new Error(data.message || '上传失败'));
}
} catch (e) {
reject(e);
}
},
fail: reject
});
});
}
// 修改昵称弹窗
showEditNicknameDialog() {
console.log('[ProfileScene] 显示修改昵称弹窗');
const currentNickname = this.main.userManager.nickname || '';
// 微信小游戏使用 wx.showModal 的 editable 参数
wx.showModal({
title: '修改昵称',
editable: true,
placeholderText: '请输入新昵称',
success: async (res) => {
console.log('[ProfileScene] showModal 回调:', res);
if (res.confirm) {
const newNickname = (res.content || '').trim();
console.log('[ProfileScene] 新昵称:', newNickname);
if (!newNickname) {
wx.showToast({ title: '昵称不能为空', icon: 'none' });
return;
}
if (newNickname === currentNickname) {
wx.showToast({ title: '昵称未变更', icon: 'none' });
return;
}
wx.showLoading({ title: '保存中...' });
try {
const success = await this.main.userManager.updateProfile(
newNickname,
this.main.userManager.avatarUrl || ''
);
wx.hideLoading();
if (success) {
wx.showToast({ title: '修改成功', icon: 'success' });
} else {
wx.showToast({ title: '修改失败', icon: 'none' });
}
} catch (e) {
wx.hideLoading();
console.error('[ProfileScene] 修改昵称失败:', e);
wx.showToast({ title: '修改失败', icon: 'none' });
}
}
},
fail: (err) => {
console.error('[ProfileScene] showModal 失败:', err);
}
});
}
// 确认退出登录
confirmLogout() {
wx.showModal({
title: '确认退出',
content: '退出登录后需要重新授权登录,确定退出吗?',
confirmText: '退出',
confirmColor: '#e74c3c',
success: (res) => {
if (res.confirm) {
// 执行退出登录
this.main.userManager.logout();
// 停止草稿检查
this.main.stopDraftChecker();
// 跳转到登录页
this.main.sceneManager.switchScene('login');
wx.showToast({ title: '已退出登录', icon: 'success' });
}
}
});
}
}

View File

@@ -7,6 +7,7 @@ import EndingScene from './EndingScene';
import ProfileScene from './ProfileScene';
import ChapterScene from './ChapterScene';
import AICreateScene from './AICreateScene';
import LoginScene from './LoginScene';
export default class SceneManager {
constructor(main) {
@@ -18,7 +19,8 @@ export default class SceneManager {
ending: EndingScene,
profile: ProfileScene,
chapter: ChapterScene,
aiCreate: AICreateScene
aiCreate: AICreateScene,
login: LoginScene
};
}

View File

@@ -9,7 +9,7 @@ const ENV = 'cloud'; // 'local' = 本地后端, 'cloud' = 微信云托管
const CONFIG = {
local: {
baseUrl: 'http://localhost:8000/api'
baseUrl: 'http://172.20.10.8:8000/api' // 局域网IP真机测试用
},
cloud: {
env: 'prod-6gjx1rd4c40f5884',
@@ -17,6 +17,18 @@ const CONFIG = {
}
};
/**
* 获取存储的 Token
*/
function getToken() {
try {
const userInfo = wx.getStorageSync('userInfo');
return userInfo?.token || '';
} catch (e) {
return '';
}
}
/**
* 发送HTTP请求
*/
@@ -33,16 +45,43 @@ export function request(options) {
*/
function requestLocal(options) {
return new Promise((resolve, reject) => {
const timeoutMs = options.timeout || 30000;
// 自动添加 Token 到请求头
const token = getToken();
const header = {
'Content-Type': 'application/json',
...options.header
};
if (token) {
header['Authorization'] = `Bearer ${token}`;
}
// 处理 URL 查询参数
let url = CONFIG.local.baseUrl + options.url;
if (options.params) {
const queryString = Object.entries(options.params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
url += (url.includes('?') ? '&' : '?') + queryString;
}
wx.request({
url: CONFIG.local.baseUrl + options.url,
url,
method: options.method || 'GET',
data: options.data || {},
timeout: options.timeout || 30000,
header: {
'Content-Type': 'application/json',
...options.header
},
timeout: timeoutMs,
header,
success(res) {
// 处理 401 未授权错误
if (res.statusCode === 401) {
// Token 过期或无效,清除本地存储
wx.removeStorageSync('userInfo');
reject(new Error('登录已过期,请重新登录'));
return;
}
if (res.data && res.data.code === 0) {
resolve(res.data.data);
} else {
@@ -62,19 +101,43 @@ function requestLocal(options) {
*/
function requestCloud(options) {
return new Promise((resolve, reject) => {
// 自动添加 Token 到请求头
const token = getToken();
const header = {
'X-WX-SERVICE': CONFIG.cloud.serviceName,
'Content-Type': 'application/json',
...options.header
};
if (token) {
header['Authorization'] = `Bearer ${token}`;
}
// 处理 URL 查询参数
let path = '/api' + options.url;
if (options.params) {
const queryString = Object.entries(options.params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
path += (path.includes('?') ? '&' : '?') + queryString;
}
wx.cloud.callContainer({
config: {
env: CONFIG.cloud.env
},
path: '/api' + options.url,
path,
method: options.method || 'GET',
data: options.data || {},
header: {
'X-WX-SERVICE': CONFIG.cloud.serviceName,
'Content-Type': 'application/json',
...options.header
},
header,
success(res) {
// 处理 401 未授权错误
if (res.statusCode === 401) {
wx.removeStorageSync('userInfo');
reject(new Error('登录已过期,请重新登录'));
return;
}
if (res.data && res.data.code === 0) {
resolve(res.data.data);
} else if (res.data) {