diff --git a/api/appUser.js b/api/appUser.js index 3b60b11..1621d76 100644 --- a/api/appUser.js +++ b/api/appUser.js @@ -6,9 +6,23 @@ export function wechatLogin(code, invite_code) { } // 抖音小程序登录 +/** + * 抖音小程序登录 + * @param {string} code - 抖音登录 code(从 tt.login 获取) + * @param {string} anonymous_code - 匿名登录 code(可选) + * @param {string} invite_code - 邀请码(可选) + */ +export function douyinLogin(code, anonymous_code, invite_code) { + const data = {} + if (code) data.code = code + if (anonymous_code) data.anonymous_code = anonymous_code + if (invite_code) data.invite_code = invite_code + return request({ url: '/api/app/users/douyin/login', method: 'POST', data }) +} + +// 保持向后兼容 export function toutiaoLogin(code, invite_code) { - const data = invite_code ? { code, invite_code } : { code } - return request({ url: '/api/app/users/toutiao/login', method: 'POST', data }) + return douyinLogin(code, null, invite_code) } // ============================================ @@ -44,6 +58,15 @@ export function bindPhone(user_id, code, extraHeader = {}) { return authRequest({ url: `/api/app/users/${user_id}/phone/bind`, method: 'POST', data: { code }, header: extraHeader }) } +/** + * 绑定抖音手机号 + * @param {number} user_id - 用户ID + * @param {string} code - 抖音手机号授权 code + */ +export function bindDouyinPhone(user_id, code) { + return authRequest({ url: `/api/app/users/${user_id}/douyin/phone/bind`, method: 'POST', data: { code } }) +} + export function getUserStats(user_id) { return authRequest({ url: `/api/app/users/${user_id}/stats`, method: 'GET' }) } diff --git a/manifest.json b/manifest.json index c659be6..8868b79 100644 --- a/manifest.json +++ b/manifest.json @@ -71,7 +71,12 @@ }, "mp-toutiao": { "usingComponents": true, - "appid": "ttf031868c6f33d91001" + "appid": "ttf031868c6f33d91001", + "privacy": { + "getPhoneNumber": { + "desc": "用于登录和账号绑定" + } + } }, "uniStatistics": { "enable": false diff --git a/pages-game/game/minesweeper/play.vue b/pages-game/game/minesweeper/play.vue index 44795c4..f663843 100644 --- a/pages-game/game/minesweeper/play.vue +++ b/pages-game/game/minesweeper/play.vue @@ -437,62 +437,132 @@ export default { setupSocketListeners() { nakamaManager.setListeners({ onmatchmakermatched: async (matched) => { + // ========== 控制台日志 - 匹配成功 ========== + console.log('=== 匹配成功事件 ==='); + console.log('[匹配时间]', new Date().toLocaleString()); + console.log('[Match ID]', matched.match_id); + console.log('[Match Token]', matched.token ? '存在' : '不存在'); + console.log('[完整匹配对象]', JSON.stringify(matched, null, 2)); + console.log('=================='); + this.addLog('system', `📡 信号锁定!同步中...`); this.isMatching = false; clearInterval(this.matchInterval); - + // 保存匹配信息用于可能的重连 this.pendingMatchId = matched.match_id; this.pendingMatchToken = matched.token; - + // 带重试的 joinMatch const maxRetries = 3; for (let i = 0; i < maxRetries; i++) { try { + console.log(`[尝试 ${i + 1}/${maxRetries}] 开始加入游戏房间...`); const match = await nakamaManager.joinMatch(matched.match_id, matched.token); + + // ========== 控制台日志 - 加入成功 ========== + console.log('=== 成功加入游戏房间 ==='); + console.log('[房间 ID]', match.match_id); + console.log('[我的用户 ID]', this.myUserId); + console.log('[房间对象]', JSON.stringify(match, null, 2)); + console.log('======================'); + this.matchId = match.match_id; this.addLog('system', `成功接入战局`); setTimeout(() => { + console.log('[发送状态请求] 请求初始游戏状态...'); nakamaManager.sendMatchState(match.match_id, 100, JSON.stringify({ action: 'getState' })); }, 100); return; // 成功,退出重试循环 } catch (e) { + // ========== 控制台日志 - 加入失败 ========== + console.error(`[尝试 ${i + 1}/${maxRetries} 失败]`, e.message); + console.error('[错误详情]', e); + this.addLog('system', `⚠️ 接入尝试 ${i + 1}/${maxRetries} 失败: ${e.message}`); if (i < maxRetries - 1) { + console.log(`等待 1 秒后重试...`); await new Promise(r => setTimeout(r, 1000)); // 等待1秒后重试 } } } + + // ========== 控制台日志 - 最终失败 ========== + console.error('=== 加入游戏房间最终失败 ==='); + console.error('[Match ID]', matched.match_id); + console.error('[重试次数]', maxRetries); + console.error('============================'); + this.addLog('system', `❌ 接入失败,请检查网络后重试`); }, onmatchdata: (matchData) => { const opCode = matchData.op_code; const data = JSON.parse(new TextDecoder().decode(matchData.data)); + + // ========== 控制台日志 - 接收游戏数据 ========== + console.log('=== 接收游戏数据 ==='); + console.log('[OpCode]', opCode, `(${this.getOpCodeName(opCode)})`); + console.log('[数据]', data); + console.log('[时间]', new Date().toLocaleString()); + console.log('===================='); + this.handleGameData(opCode, data); }, ondisconnect: async () => { + // ========== 控制台日志 - 断开连接 ========== + console.warn('=== 连接断开 ==='); + console.warn('[断开时间]', new Date().toLocaleString()); + console.warn('[Match ID]', this.matchId); + console.warn('==============='); + this.addLog('system', `⚠️ 连接断开,尝试重连中...`); this.isConnected = false; - + // 自动重连 try { + console.log('[重连] 开始重新连接...'); await nakamaManager.authenticateWithGameToken(this.gameToken); this.isConnected = true; + + console.log('[重连] 认证成功'); + this.addLog('system', `✅ 重连成功`); - + // 如果有进行中的比赛,尝试重新加入 if (this.matchId) { + console.log('[重连] 尝试重新加入游戏房间:', this.matchId); const match = await nakamaManager.joinMatch(this.matchId); + console.log('[重连] 成功重新加入游戏房间'); + this.addLog('system', `✅ 已重新加入战局`); nakamaManager.sendMatchState(this.matchId, 100, JSON.stringify({ action: 'getState' })); } } catch (e) { + console.error('[重连] 失败:', e); this.addLog('system', `❌ 重连失败: ${e.message}`); } } }); }, handleGameData(opCode, data) { + // 关键游戏状态的控制台日志 + if (opCode === 1) { + // ========== 控制台日志 - 游戏开始 ========== + console.log('🎮 ========== 游戏开始 =========='); + console.log('[玩家数量]', Object.keys(data.players || {}).length); + console.log('[我的位置]', data.turnOrder?.indexOf(this.myUserId)); + console.log('[网格大小]', data.gridSize); + console.log('[游戏状态]', data); + console.log('================================'); + } else if (opCode === 6) { + // ========== 控制台日志 - 游戏结束 ========== + console.log('🏁 ========== 游戏结束 =========='); + console.log('[获胜者]', data.winnerId); + console.log('[是否胜利]', data.winnerId === this.myUserId); + console.log('[最终状态]', data); + console.log('================================'); + } + if (opCode === 1 || opCode === 2) { this.gameState = data; if (opCode === 1) this.addLog('system', '战局开始,准备翻格!'); @@ -588,7 +658,18 @@ export default { if (content === 'empty') return '✅'; return this.getItemIcon(content); }, - getNumColor(n) { return ['','blue','green','red','purple','orange'][n] || 'black'; } + getNumColor(n) { return ['','blue','green','red','purple','orange'][n] || 'black'; }, + getOpCodeName(code) { + const map = { + 1: '游戏开始', + 2: '状态更新', + 3: '玩家点击格子', + 5: '游戏事件', + 6: '游戏结束', + 100: '请求游戏状态' + }; + return map[code] || '未知'; + } } } diff --git a/pages.json b/pages.json index 2508c37..13deaab 100644 --- a/pages.json +++ b/pages.json @@ -174,14 +174,14 @@ { "path": "game/minesweeper/index", "style": { - "navigationStyle": "custom", + "navigationStyle": "default", "navigationBarTitleText": "扫雷 game" } }, { "path": "game/minesweeper/play", "style": { - "navigationStyle": "custom", + "navigationStyle": "default", "navigationBarTitleText": "扫雷对战", "disableScroll": true, "app-plus": { diff --git a/pages/cabinet/index.vue b/pages/cabinet/index.vue index 1503d24..8669c28 100644 --- a/pages/cabinet/index.vue +++ b/pages/cabinet/index.vue @@ -165,6 +165,7 @@ import { ref, computed } from 'vue' import { onShow, onReachBottom, onShareAppMessage, onPullDownRefresh } from '@dcloudio/uni-app' import { getInventory, getProductDetail, redeemInventory, requestShipping, cancelShipping, listAddresses, getShipments, createAddressShare } from '@/api/appUser' import { vibrateShort } from '@/utils/vibrate.js' +import { checkPhoneBound } from '@/utils/checkPhone.js' const currentTab = ref(0) const aggregatedList = ref([]) @@ -214,6 +215,9 @@ async function fetchProductMeta(productId) { } onShow(() => { + // 检查手机号绑定状态 + if (!checkPhoneBound()) return + // Check for external tab switch request try { const targetTab = uni.getStorageSync('cabinet_target_tab') @@ -224,13 +228,10 @@ onShow(() => { } catch (e) {} const token = uni.getStorageSync('token') - const phoneBound = !!uni.getStorageSync('phone_bound') - console.log('cabinet onShow token:', token, 'isLogin:', !!token, 'phoneBound:', phoneBound) - - if (!token || !phoneBound) { + if (!token) { uni.showModal({ title: '提示', - content: '请先登录并绑定手机号', + content: '请先登录', confirmText: '去登录', success: (res) => { if (res.confirm) { diff --git a/pages/index/index.vue b/pages/index/index.vue index f9f3c65..7af5fd2 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -132,6 +132,7 @@