// profile.ts (Copied from previous index.ts) import { config } from '../../config'; import { remoteConfig } from '../../utils/remoteConfig'; // 获取应用实例 const app = getApp() 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: { onLoad() { wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] }); }, onShareAppMessage() { return config.share; }, onShareTimeline() { return config.share; }, // 检查本地 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); } }, // 页面跳转方法 goToHistory() { wx.navigateTo({ url: '/pages/history/history' }); }, goToPrivacy() { wx.navigateTo({ url: '/pages/privacy/privacy' }); }, goToAbout() { wx.navigateTo({ url: '/pages/about/about' }); }, // 弹出设置菜单 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() { // console.log('点击复制验证码'); const { verificationCode } = this.data; // console.log('当前验证码:', verificationCode); if (!verificationCode) { // console.log('无验证码,无法复制'); return; } const dataStr = String(verificationCode); wx.setClipboardData({ data: dataStr, success: () => { // console.log('复制成功回调'); // wx.hideToast(); // 隐藏系统默认的"内容已复制"提示 // wx.showToast({ title: '复制成功', icon: 'success' }); }, fail: (err) => { console.error('复制失败:', err); // 隐私协议未声明或用户拒绝会导致复制失败 (errno 112) if (err.errno === 112 || (err.errMsg && err.errMsg.indexOf('privacy') > -1)) { wx.showModal({ title: '提示', content: '无法自动复制,请长按数字进行手动复制', showCancel: false, confirmText: '知道了' }); } else { wx.showToast({ title: '复制失败', icon: 'none' }); } } }); }, 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); } }, }, })