目录结构调整

This commit is contained in:
2026-02-04 23:47:45 +08:00
parent 6938c911c3
commit 6b22238c6e
8780 changed files with 15333 additions and 574 deletions

View File

@@ -0,0 +1,679 @@
// profile.ts (Copied from previous index.ts)
import { config } from '../../config';
import { remoteConfig } from '../../utils/remoteConfig';
// 获取应用实例
const app = getApp<IAppOption>()
Component({
data: {
motto: 'Hello World',
loginHint: config.loginHint,
userInfo: {
avatarUrl: config.defaultAvatarUrl,
nickName: '',
gender: 0, // 0:未知, 1:男, 2:女
country: '',
province: '',
city: '',
language: ''
},
openid: '',
unionid: '',
phoneNumber: '',
playerid: '',
verificationCode: '',
hasUserInfo: false,
authMode: config.authMode || 'oa', // 'oa' | 'mp'
isTestEnabled: config.testConfig && config.testConfig.enable,
canIUseGetUserProfile: wx.canIUse('getUserProfile'),
canIUseNicknameComp: wx.canIUse('input.type.nickname'),
},
pageLifetimes: {
show() {
// 检查是否有从公众号授权回来的数据
const oaUserInfo = wx.getStorageSync('oa_user_info');
if (oaUserInfo) {
console.log('检测到公众号授权数据:', oaUserInfo);
this.setData({
"userInfo.nickName": oaUserInfo.nickName,
"userInfo.avatarUrl": oaUserInfo.avatarUrl,
"userInfo.gender": oaUserInfo.gender,
"userInfo.country": oaUserInfo.country,
"userInfo.province": oaUserInfo.province,
"userInfo.city": oaUserInfo.city,
"userInfo.language": oaUserInfo.language,
// 如果公众号也返回了 unionid可以更新
unionid: oaUserInfo.unionid || this.data.unionid
});
// 清除缓存,避免重复读取
wx.removeStorageSync('oa_user_info');
// 如果已经有手机号,说明是“先手机后头像”的流程,直接保存并进入
if (this.data.phoneNumber) {
this.saveUserInfoToBackend();
} else {
// 授权回来后,尝试登录游戏服务器获取信息
this.loginToServer();
}
}
}
},
lifetimes: {
attached() {
// 检查本地登录态
this.checkLocalSession();
}
},
methods: {
// 检查本地 Session
checkLocalSession() {
const session = wx.getStorageSync('USER_SESSION');
const now = Date.now();
// 默认 24 小时过期
const expirationHours = config.loginExpirationHours || 24;
const expirationTime = expirationHours * 60 * 60 * 1000;
if (session && session.timestamp && (now - session.timestamp < expirationTime)) {
console.log('恢复本地登录态');
this.setData({
userInfo: session.userInfo,
openid: session.openid,
unionid: session.unionid,
phoneNumber: session.phoneNumber,
playerid: session.playerid,
// 始终保持 hasUserInfo 为 false以显示用户信息和操作按钮
hasUserInfo: false
});
// 恢复后,尝试静默刷新服务端状态(不阻塞 UI
this.loginToServer();
} else {
console.log('本地登录态不存在或已过期');
wx.removeStorageSync('USER_SESSION');
// 页面加载时自动静默登录获取 OpenID但不自动登录游戏服务器等待用户点击登录按钮授权
this.performSilentLogin(false);
}
},
// 弹出设置菜单
onSettingsTap() {
wx.showActionSheet({
itemList: ['注销账号'],
itemColor: '#FF0000', // 红色显示注销
success: (res) => {
if (res.tapIndex === 0) {
this.handleLogout();
}
}
});
},
// 处理注销
handleLogout() {
wx.showModal({
title: '提示',
content: '确定要注销当前账号吗?',
success: (res) => {
if (res.confirm) {
// 1. 清除本地存储
wx.removeStorageSync('USER_SESSION'); // 清除持久化会话
wx.removeStorageSync('oa_user_info'); // 清除可能的 OA 授权信息
// 2. 重置页面数据
this.setData({
userInfo: {
avatarUrl: config.defaultAvatarUrl,
nickName: '',
gender: 0,
country: '',
province: '',
city: '',
language: ''
},
openid: '',
unionid: '',
phoneNumber: '',
playerid: '',
verificationCode: '',
// 重置其他相关状态...
});
wx.showToast({
title: '已注销',
icon: 'success'
});
// 3. 重新执行静默登录以获取基础 OpenID (保持未登录状态)
this.performSilentLogin(false);
}
}
});
},
// 选择头像 (小程序原生)
onChooseAvatar(e: any) {
const { avatarUrl } = e.detail;
this.setData({
"userInfo.avatarUrl": avatarUrl
});
},
// 填写昵称 (小程序原生)
onNicknameChange(e: any) {
const nickName = e.detail.value;
this.setData({
"userInfo.nickName": nickName
});
},
// 跳转到公众号授权页面
goOfficialAccountAuth() {
// 如果是 mp 模式,或者测试模式下配置了 Mock UnionID则跳过公众号授权直接尝试登录
if (config.authMode === 'mp' || (config.testConfig && config.testConfig.enable && config.testConfig.mockUnionId)) {
console.log('跳过公众号授权 (模式: ' + config.authMode + ', 测试: ' + (config.testConfig && config.testConfig.enable) + ')');
this.loginToServer();
return;
}
wx.navigateTo({
url: '/pages/webview/webview'
});
},
// 事件处理函数
bindViewTap() {
wx.navigateTo({
url: '../logs/logs',
})
},
// 静默登录逻辑
performSilentLogin(autoLoginGameServer: boolean = true) {
wx.login({
success: (loginRes) => {
if (loginRes.code) {
console.log('静默登录 code:', loginRes.code);
// 请求本地后端
wx.request({
url: `${config.baseUrl}/api/login`,
method: 'POST',
data: { code: loginRes.code },
success: (res: any) => {
if (res.data.error) {
console.error('静默登录失败:', res.data.error);
return;
}
this.setData({
openid: res.data.openid,
unionid: res.data.unionid || '未获取到UnionID'
});
console.log('OpenID 静默获取成功',res.data);
// 获取到 openid/unionid 后,尝试登录游戏服务器获取玩家信息
if (autoLoginGameServer) {
this.loginToServer();
}
},
fail: (err) => {
console.error('静默登录连接失败:', err);
}
});
}
}
});
},
// 登录游戏服务器
loginToServer() {
// 检查远程配置是否就绪
if (!remoteConfig.isReady()) {
console.log('RemoteConfig not ready, waiting...');
wx.showLoading({ title: '加载配置中...', mask: true });
let hasHandled = false;
remoteConfig.onUpdate(() => {
if (!hasHandled && remoteConfig.isReady()) {
hasHandled = true;
wx.hideLoading();
this.loginToServer();
}
});
return;
}
const { openid, unionid, userInfo } = this.data;
if (!openid) return;
// --- 测试配置注入 ---
let finalUnionId = unionid;
if (config.testConfig && config.testConfig.enable && config.testConfig.mockUnionId) {
console.log('[Test] Using Mock UnionID in loginToServer:', config.testConfig.mockUnionId);
finalUnionId = config.testConfig.mockUnionId;
}
// -------------------
// 获取版本号
const { agentid, gameid, channelid, marketid } = config.remoteConfig;
const version = remoteConfig.getParaValue("game_version", agentid, gameid, channelid, marketid);
console.log("game_version",version);
wx.request({
url: `${config.baseUrl}/api/playerLogin`,
method: 'POST',
data: {
openid,
unionid: finalUnionId,
nickName: userInfo.nickName,
avatarUrl: userInfo.avatarUrl,
gender: userInfo.gender,
province: userInfo.province,
city: userInfo.city,
version: version || 1 // 默认版本号 1
},
success: (res: any) => {
this.handleLoginSuccess(res);
},
fail: (err) => {
console.error('游戏服务器登录失败:', err);
wx.showToast({ title: '连接服务器失败', icon: 'none' });
}
});
},
// 测试登录(带手机号和验证码)
testLoginWithPhone() {
const { openid, unionid, userInfo, phoneNumber, verificationCode } = this.data;
if (!openid) {
wx.showToast({ title: '未获取OpenID', icon: 'none' });
return;
}
if (!phoneNumber) {
wx.showToast({ title: '未获取手机号', icon: 'none' });
return;
}
if (!verificationCode) {
wx.showToast({ title: '未生成随机数', icon: 'none' });
return;
}
// --- 测试配置注入 ---
let finalUnionId = unionid;
if (config.testConfig && config.testConfig.enable && config.testConfig.mockUnionId) {
finalUnionId = config.testConfig.mockUnionId;
}
// -------------------
const { agentid, gameid, channelid, marketid } = config.remoteConfig;
const version = remoteConfig.getParaValue("game_version", agentid, gameid, channelid, marketid);
wx.showLoading({ title: '测试登录中...' });
wx.request({
url: `${config.baseUrl}/api/playerLogin`,
method: 'POST',
// data: {
// openid,
// unionid: finalUnionId,
// nickName: userInfo.nickName,
// avatarUrl: userInfo.avatarUrl,
// gender: userInfo.gender,
// province: userInfo.province,
// city: userInfo.city,
// version: version || 1,
// phoneNumber: phoneNumber,
// verificationCode: verificationCode
// },
data: {
openid:"onJdG10JeHtS0Dbz8FtdVv7aeVB6",
unionid: "oLVKis6bj3_l8qspMybG60KV2GN6",
nickName: "testname",
avatarUrl: "testavatarurl",
gender: 3,
province: "testprovince",
city: "testcity",
version: version || 1,
phoneNumber: phoneNumber,
verificationCode: 11111,
telphoneAuto:true,
playerid:710873
},
success: (res: any) => {
wx.hideLoading();
console.log('测试登录返回:', res.data);
this.handleLoginSuccess(res);
wx.showToast({ title: '测试登录请求已发送', icon: 'none' });
},
fail: (err) => {
wx.hideLoading();
console.error('测试登录失败:', err);
wx.showToast({ title: '测试登录失败', icon: 'none' });
}
});
},
handleLoginSuccess(res: any) {
console.log('游戏服务器登录返回:', res.data);
// res.data 是 wxserver 返回的 { success, message, data }
// res.data.data 是游戏服务器返回的原始包 { rpc, data }
const packet = res.data;
if (packet) {
// 1. 处理异常 RPC (show_message, kick_server)
if (packet.rpc === 'show_message' || packet.rpc === 'kick_server') {
const msg = (packet.data && packet.data.msg) || '未知错误';
wx.showToast({
title: msg,
icon: 'none',
duration: 3000
});
return;
}
// 2. 处理业务错误
if (packet.data && packet.data.error) {
wx.showToast({
title: packet.data.error,
icon: 'none',
duration: 3000
});
return;
}
// 3. 处理登录成功
const gameData = packet.data;
if (gameData && (gameData.playerid || gameData.state === 0)) {
// 兼容不同的字段名
const serverNickName = gameData.nickname || gameData.nickName;
const serverAvatarUrl = gameData.avatar || gameData.headimg || gameData.headimgurl || gameData.avatarUrl;
const serverGender = gameData.sex || gameData.gender;
const newUserInfo = {
...this.data.userInfo,
nickName: serverNickName || this.data.userInfo.nickName,
avatarUrl: serverAvatarUrl || this.data.userInfo.avatarUrl,
gender: serverGender !== undefined ? serverGender : this.data.userInfo.gender
};
this.setData({
userInfo: newUserInfo,
playerid: gameData.playerid || '',
phoneNumber: gameData.tel || ''
});
// 更新本地缓存
wx.setStorageSync('USER_SESSION', {
timestamp: Date.now(),
userInfo: newUserInfo,
openid: this.data.openid,
unionid: this.data.unionid,
phoneNumber: gameData.tel || '',
playerid: gameData.playerid || ''
});
}
}
},
// 获取验证码
getVerificationCode() {
// if (this.data.isCountingDown) return;
const { phoneNumber } = this.data;
if (!phoneNumber) {
wx.showToast({ title: '请先绑定手机号', icon: 'none' });
return;
}
wx.showLoading({ title: '获取中...' });
wx.request({
url: `${config.baseUrl}/api/getPhoneCode`,
method: 'POST',
data: { phonenum: phoneNumber },
success: (res: any) => {
wx.hideLoading();
console.log('验证码返回:', res.data);
const smmcode = res.data && res.data.data && res.data.data.smmcode;
if (smmcode) {
this.setData({ verificationCode: smmcode });
wx.showToast({ title: '获取成功', icon: 'success' });
} else {
wx.showToast({ title: '获取失败', icon: 'none' });
}
},
fail: (err) => {
wx.hideLoading();
console.error('获取验证码请求失败:', err);
wx.showToast({ title: '网络错误', icon: 'none' });
}
});
},
// 复制验证码
copyVerificationCode() {
const { verificationCode } = this.data;
if (!verificationCode) return;
wx.setClipboardData({
data: String(verificationCode),
success: () => {
wx.showToast({ title: '复制成功', icon: 'success' });
}
});
},
getPhoneNumber(e: any) {
// const { avatarUrl, nickName } = this.data.userInfo;
const { openid } = this.data;
// 1. 检查 OpenID 是否已获取
if (!openid) {
wx.showToast({ title: '正在初始化...', icon: 'none' });
// 尝试重新登录
this.performSilentLogin();
return;
}
// 2. 获取手机号
if (e.detail.errMsg === "getPhoneNumber:ok" || e.detail.errMsg.includes("ok")) {
// --- 测试配置Mock 手机号 ---
// 原因:微信小程序获取手机号接口收费。在测试阶段,如果配置了 mockPhoneNumber
// 则直接使用模拟数据,跳过后端请求,从而避免产生费用。
if (config.testConfig && config.testConfig.enable && config.testConfig.mockPhoneNumber) {
console.log('[Test] Using Mock PhoneNumber directly:', config.testConfig.mockPhoneNumber);
this.handlePhoneNumberSuccess(config.testConfig.mockPhoneNumber);
return;
}
// ---------------------------
const code = e.detail.code; // 动态令牌
wx.showLoading({ title: '获取手机号...' });
// 请求本地后端获取手机号
wx.request({
url: `${config.baseUrl}/api/getPhoneNumber`,
method: 'POST',
data: { code: code },
success: (res: any) => {
wx.hideLoading();
console.log('后端返回:', res.data);
if (res.data.phoneNumber) {
this.handlePhoneNumberSuccess(res.data.phoneNumber);
} else {
wx.showToast({ title: '获取失败: ' + (res.data.error || '未知错误'), icon: 'none' });
}
},
fail: (err) => {
wx.hideLoading();
console.error('请求后端接口失败:', err);
wx.showToast({ title: '连接服务器失败', icon: 'none' });
}
});
} else {
console.error(e.detail);
wx.showModal({
title: '获取失败',
content: '用户拒绝授权或账号无权限。',
showCancel: false
})
}
},
// 抽离处理手机号成功的逻辑
handlePhoneNumberSuccess(phoneNumber: string) {
this.setData({
phoneNumber: phoneNumber
});
// 3. 检查是否需要同步头像或获取真实UnionID
// 如果没有昵称、头像是默认的或者没有有效的UnionID自动跳转去同步
const hasValidUnionId = this.data.unionid && this.data.unionid !== '未获取到UnionID';
// 在 mp 模式下,我们不强制检查 unionid因为 chooseAvatar 不会返回 unionid
// 但如果用户需要 unionid可能需要其他方式。这里主要关注头像昵称。
const isInfoMissing = !this.data.userInfo.nickName ||
this.data.userInfo.avatarUrl === config.defaultAvatarUrl ||
(this.data.authMode === 'oa' && !hasValidUnionId);
if (isInfoMissing) {
if (this.data.authMode === 'oa') {
wx.showToast({ title: '正在同步完善信息...', icon: 'none', duration: 1500 });
setTimeout(() => {
this.goOfficialAccountAuth();
}, 1500);
} else {
// mp 模式,提示用户手动填写
wx.showToast({ title: '请完善头像和昵称', icon: 'none' });
}
} else {
// 已经有头像了,直接保存
this.saveUserInfoToBackend();
wx.showToast({ title: '登录成功' });
}
},
saveUserInfoToBackend() {
const { userInfo, openid, unionid, phoneNumber } = this.data;
// --- 测试配置注入 ---
let finalUnionId = unionid;
let finalPhoneNumber = phoneNumber;
if (config.testConfig && config.testConfig.enable) {
if (config.testConfig.mockUnionId) {
console.log('[Test] Using Mock UnionID:', config.testConfig.mockUnionId);
finalUnionId = config.testConfig.mockUnionId;
}
if (config.testConfig.mockPhoneNumber) {
console.log('[Test] Using Mock PhoneNumber:', config.testConfig.mockPhoneNumber);
finalPhoneNumber = config.testConfig.mockPhoneNumber;
}
}
// -------------------
const save = (finalAvatarUrl: string) => {
wx.request({
url: `${config.baseUrl}/api/saveUserInfo`,
method: 'POST',
data: {
...userInfo, // 包含 nickName, gender, country, province, city, language 等所有字段
avatarUrl: finalAvatarUrl, // 覆盖可能为临时路径的 avatarUrl
openid,
unionid: finalUnionId,
phoneNumber: finalPhoneNumber
},
success: (res: any) => {
console.log('用户信息保存成功:', res.data);
const serverData = res.data;
// 如果游戏服务器明确返回 result: -1 (例如手机号已存在),则阻止跳转
// wxserver 返回结构: { success: boolean, message: string, data: { data: { result: number, msg: string } } }
// 兼容直接返回的情况
const gamePacket = serverData.data;
const gameResult = (gamePacket && gamePacket.data && gamePacket.data.result !== undefined) ? gamePacket.data.result : ((gamePacket && gamePacket.result !== undefined) ? gamePacket.result : serverData.result);
if (serverData.success === false || gameResult === -1) {
// 优先显示 serverData.data.message其次是 msg
const errorMsg = serverData.message || (gamePacket && gamePacket.data && gamePacket.data.msg) || (gamePacket && gamePacket.msg) || '操作失败';
wx.showToast({
title: errorMsg,
icon: 'none',
duration: 4000
});
return;
}
// 优先取游戏服务器返回的 msg (在 data.data.msg 中),其次取 wxserver 的 message
const displayMsg = (gamePacket && gamePacket.data && gamePacket.data.msg) || (gamePacket && gamePacket.msg) || serverData.message;
if (displayMsg) {
wx.showToast({
title: displayMsg,
icon: 'none',
duration: 5000
});
}
// 保存到本地缓存
wx.setStorageSync('USER_SESSION', {
timestamp: Date.now(),
userInfo: this.data.userInfo,
openid: this.data.openid,
unionid: this.data.unionid,
phoneNumber: this.data.phoneNumber,
playerid: this.data.playerid
});
},
fail: (err) => {
console.error('保存用户信息失败:', err);
}
});
};
// 检查是否需要上传头像 (如果是临时路径)
// 只有在开启了上传配置,且头像是临时路径时才上传
const shouldUpload = config.enableAvatarUpload &&
userInfo.avatarUrl &&
(userInfo.avatarUrl.startsWith('http://tmp') || userInfo.avatarUrl.startsWith('wxfile://'));
if (shouldUpload) {
wx.showLoading({ title: '上传头像中...' });
wx.uploadFile({
url: `${config.baseUrl}/api/upload`,
filePath: userInfo.avatarUrl,
name: 'file',
formData: {
unionid: finalUnionId || openid // 优先使用 unionid如果没有则使用 openid
},
success: (res) => {
wx.hideLoading();
try {
const data = JSON.parse(res.data);
if (data.url) {
console.log('头像上传成功:', data.url);
save(data.url);
} else {
console.error('头像上传响应异常:', data);
// 降级处理:上传失败也尝试保存,虽然头像链接可能无效
save(userInfo.avatarUrl);
}
} catch (e) {
console.error('解析上传响应失败:', e);
save(userInfo.avatarUrl);
}
},
fail: (err) => {
wx.hideLoading();
console.error('头像上传失败:', err);
save(userInfo.avatarUrl);
}
});
} else {
// 已经是网络链接或默认头像,直接保存
save(userInfo.avatarUrl);
}
},
},
})