feat:新增了绑定手机检查,抖音登录等逻辑,并且更改了页面样式以符合抖音要求
This commit is contained in:
parent
61df7fca5e
commit
05056c8188
@ -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) {
|
export function toutiaoLogin(code, invite_code) {
|
||||||
const data = invite_code ? { code, invite_code } : { code }
|
return douyinLogin(code, null, invite_code)
|
||||||
return request({ url: '/api/app/users/toutiao/login', method: 'POST', data })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@ -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 })
|
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) {
|
export function getUserStats(user_id) {
|
||||||
return authRequest({ url: `/api/app/users/${user_id}/stats`, method: 'GET' })
|
return authRequest({ url: `/api/app/users/${user_id}/stats`, method: 'GET' })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,7 +71,12 @@
|
|||||||
},
|
},
|
||||||
"mp-toutiao": {
|
"mp-toutiao": {
|
||||||
"usingComponents": true,
|
"usingComponents": true,
|
||||||
"appid": "ttf031868c6f33d91001"
|
"appid": "ttf031868c6f33d91001",
|
||||||
|
"privacy": {
|
||||||
|
"getPhoneNumber": {
|
||||||
|
"desc": "用于登录和账号绑定"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"uniStatistics": {
|
"uniStatistics": {
|
||||||
"enable": false
|
"enable": false
|
||||||
|
|||||||
@ -437,6 +437,14 @@ export default {
|
|||||||
setupSocketListeners() {
|
setupSocketListeners() {
|
||||||
nakamaManager.setListeners({
|
nakamaManager.setListeners({
|
||||||
onmatchmakermatched: async (matched) => {
|
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.addLog('system', `📡 信号锁定!同步中...`);
|
||||||
this.isMatching = false;
|
this.isMatching = false;
|
||||||
clearInterval(this.matchInterval);
|
clearInterval(this.matchInterval);
|
||||||
@ -449,50 +457,112 @@ export default {
|
|||||||
const maxRetries = 3;
|
const maxRetries = 3;
|
||||||
for (let i = 0; i < maxRetries; i++) {
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
try {
|
try {
|
||||||
|
console.log(`[尝试 ${i + 1}/${maxRetries}] 开始加入游戏房间...`);
|
||||||
const match = await nakamaManager.joinMatch(matched.match_id, matched.token);
|
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.matchId = match.match_id;
|
||||||
this.addLog('system', `成功接入战局`);
|
this.addLog('system', `成功接入战局`);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
console.log('[发送状态请求] 请求初始游戏状态...');
|
||||||
nakamaManager.sendMatchState(match.match_id, 100, JSON.stringify({ action: 'getState' }));
|
nakamaManager.sendMatchState(match.match_id, 100, JSON.stringify({ action: 'getState' }));
|
||||||
}, 100);
|
}, 100);
|
||||||
return; // 成功,退出重试循环
|
return; // 成功,退出重试循环
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// ========== 控制台日志 - 加入失败 ==========
|
||||||
|
console.error(`[尝试 ${i + 1}/${maxRetries} 失败]`, e.message);
|
||||||
|
console.error('[错误详情]', e);
|
||||||
|
|
||||||
this.addLog('system', `⚠️ 接入尝试 ${i + 1}/${maxRetries} 失败: ${e.message}`);
|
this.addLog('system', `⚠️ 接入尝试 ${i + 1}/${maxRetries} 失败: ${e.message}`);
|
||||||
if (i < maxRetries - 1) {
|
if (i < maxRetries - 1) {
|
||||||
|
console.log(`等待 1 秒后重试...`);
|
||||||
await new Promise(r => setTimeout(r, 1000)); // 等待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', `❌ 接入失败,请检查网络后重试`);
|
this.addLog('system', `❌ 接入失败,请检查网络后重试`);
|
||||||
},
|
},
|
||||||
onmatchdata: (matchData) => {
|
onmatchdata: (matchData) => {
|
||||||
const opCode = matchData.op_code;
|
const opCode = matchData.op_code;
|
||||||
const data = JSON.parse(new TextDecoder().decode(matchData.data));
|
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);
|
this.handleGameData(opCode, data);
|
||||||
},
|
},
|
||||||
ondisconnect: async () => {
|
ondisconnect: async () => {
|
||||||
|
// ========== 控制台日志 - 断开连接 ==========
|
||||||
|
console.warn('=== 连接断开 ===');
|
||||||
|
console.warn('[断开时间]', new Date().toLocaleString());
|
||||||
|
console.warn('[Match ID]', this.matchId);
|
||||||
|
console.warn('===============');
|
||||||
|
|
||||||
this.addLog('system', `⚠️ 连接断开,尝试重连中...`);
|
this.addLog('system', `⚠️ 连接断开,尝试重连中...`);
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
|
|
||||||
// 自动重连
|
// 自动重连
|
||||||
try {
|
try {
|
||||||
|
console.log('[重连] 开始重新连接...');
|
||||||
await nakamaManager.authenticateWithGameToken(this.gameToken);
|
await nakamaManager.authenticateWithGameToken(this.gameToken);
|
||||||
this.isConnected = true;
|
this.isConnected = true;
|
||||||
|
|
||||||
|
console.log('[重连] 认证成功');
|
||||||
|
|
||||||
this.addLog('system', `✅ 重连成功`);
|
this.addLog('system', `✅ 重连成功`);
|
||||||
|
|
||||||
// 如果有进行中的比赛,尝试重新加入
|
// 如果有进行中的比赛,尝试重新加入
|
||||||
if (this.matchId) {
|
if (this.matchId) {
|
||||||
|
console.log('[重连] 尝试重新加入游戏房间:', this.matchId);
|
||||||
const match = await nakamaManager.joinMatch(this.matchId);
|
const match = await nakamaManager.joinMatch(this.matchId);
|
||||||
|
console.log('[重连] 成功重新加入游戏房间');
|
||||||
|
|
||||||
this.addLog('system', `✅ 已重新加入战局`);
|
this.addLog('system', `✅ 已重新加入战局`);
|
||||||
nakamaManager.sendMatchState(this.matchId, 100, JSON.stringify({ action: 'getState' }));
|
nakamaManager.sendMatchState(this.matchId, 100, JSON.stringify({ action: 'getState' }));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error('[重连] 失败:', e);
|
||||||
this.addLog('system', `❌ 重连失败: ${e.message}`);
|
this.addLog('system', `❌ 重连失败: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleGameData(opCode, data) {
|
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) {
|
if (opCode === 1 || opCode === 2) {
|
||||||
this.gameState = data;
|
this.gameState = data;
|
||||||
if (opCode === 1) this.addLog('system', '战局开始,准备翻格!');
|
if (opCode === 1) this.addLog('system', '战局开始,准备翻格!');
|
||||||
@ -588,7 +658,18 @@ export default {
|
|||||||
if (content === 'empty') return '✅';
|
if (content === 'empty') return '✅';
|
||||||
return this.getItemIcon(content);
|
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] || '未知';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -174,14 +174,14 @@
|
|||||||
{
|
{
|
||||||
"path": "game/minesweeper/index",
|
"path": "game/minesweeper/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationStyle": "custom",
|
"navigationStyle": "default",
|
||||||
"navigationBarTitleText": "扫雷 game"
|
"navigationBarTitleText": "扫雷 game"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "game/minesweeper/play",
|
"path": "game/minesweeper/play",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationStyle": "custom",
|
"navigationStyle": "default",
|
||||||
"navigationBarTitleText": "扫雷对战",
|
"navigationBarTitleText": "扫雷对战",
|
||||||
"disableScroll": true,
|
"disableScroll": true,
|
||||||
"app-plus": {
|
"app-plus": {
|
||||||
|
|||||||
@ -165,6 +165,7 @@ import { ref, computed } from 'vue'
|
|||||||
import { onShow, onReachBottom, onShareAppMessage, onPullDownRefresh } from '@dcloudio/uni-app'
|
import { onShow, onReachBottom, onShareAppMessage, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||||
import { getInventory, getProductDetail, redeemInventory, requestShipping, cancelShipping, listAddresses, getShipments, createAddressShare } from '@/api/appUser'
|
import { getInventory, getProductDetail, redeemInventory, requestShipping, cancelShipping, listAddresses, getShipments, createAddressShare } from '@/api/appUser'
|
||||||
import { vibrateShort } from '@/utils/vibrate.js'
|
import { vibrateShort } from '@/utils/vibrate.js'
|
||||||
|
import { checkPhoneBound } from '@/utils/checkPhone.js'
|
||||||
|
|
||||||
const currentTab = ref(0)
|
const currentTab = ref(0)
|
||||||
const aggregatedList = ref([])
|
const aggregatedList = ref([])
|
||||||
@ -214,6 +215,9 @@ async function fetchProductMeta(productId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
|
// 检查手机号绑定状态
|
||||||
|
if (!checkPhoneBound()) return
|
||||||
|
|
||||||
// Check for external tab switch request
|
// Check for external tab switch request
|
||||||
try {
|
try {
|
||||||
const targetTab = uni.getStorageSync('cabinet_target_tab')
|
const targetTab = uni.getStorageSync('cabinet_target_tab')
|
||||||
@ -224,13 +228,10 @@ onShow(() => {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
const token = uni.getStorageSync('token')
|
const token = uni.getStorageSync('token')
|
||||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
if (!token) {
|
||||||
console.log('cabinet onShow token:', token, 'isLogin:', !!token, 'phoneBound:', phoneBound)
|
|
||||||
|
|
||||||
if (!token || !phoneBound) {
|
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '请先登录并绑定手机号',
|
content: '请先登录',
|
||||||
confirmText: '去登录',
|
confirmText: '去登录',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
|
|||||||
@ -132,6 +132,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { authRequest, request } from '../../utils/request.js'
|
import { authRequest, request } from '../../utils/request.js'
|
||||||
import SplashScreen from '@/components/SplashScreen.vue'
|
import SplashScreen from '@/components/SplashScreen.vue'
|
||||||
|
import { checkPhoneBound } from '../../utils/checkPhone.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -179,6 +180,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
|
// 检查手机号绑定状态
|
||||||
|
if (!checkPhoneBound()) return
|
||||||
|
|
||||||
// 延迟 200ms 首次加载,让 Token/Session 有机会就绪
|
// 延迟 200ms 首次加载,让 Token/Session 有机会就绪
|
||||||
// 同时避免页面动画卡顿
|
// 同时避免页面动画卡顿
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@ -77,16 +77,15 @@
|
|||||||
<text class="icon-emoji">🎵</text>
|
<text class="icon-emoji">🎵</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text class="panel-title">一键获取手机号</text>
|
<text class="panel-title">抖音快捷登录</text>
|
||||||
<text class="panel-desc">授权获取本机手机号,安全快速登录</text>
|
<text class="panel-desc">使用抖音账号快速登录</text>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn-primary btn-login"
|
class="btn-primary btn-login"
|
||||||
open-type="getPhoneNumber"
|
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
@getphonenumber="onToutiaoGetPhoneNumber"
|
@tap="handleDouyinLogin"
|
||||||
>
|
>
|
||||||
{{ loading ? '获取中...' : '一键获取手机号' }}
|
{{ loading ? '登录中...' : '抖音登录' }}
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
@ -175,7 +174,7 @@
|
|||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { request } from '../../utils/request'
|
import { request } from '../../utils/request'
|
||||||
import { wechatLogin, toutiaoLogin, bindPhone, getUserStats, getPointsBalance, sendSmsCode, smsLogin } from '../../api/appUser'
|
import { wechatLogin, douyinLogin, bindPhone, bindDouyinPhone, getUserStats, getPointsBalance, sendSmsCode, smsLogin } from '../../api/appUser'
|
||||||
import { vibrateShort } from '@/utils/vibrate.js'
|
import { vibrateShort } from '@/utils/vibrate.js'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@ -221,14 +220,19 @@ async function ensureOpenID() {
|
|||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifdef MP-TOUTIAO
|
// #ifdef MP-TOUTIAO
|
||||||
uni.login({
|
// 抖音小程序使用 tt.login
|
||||||
provider: 'toutiao',
|
tt.login({
|
||||||
success: async (loginRes) => {
|
success: async (loginRes) => {
|
||||||
try {
|
try {
|
||||||
|
console.log('[DEBUG] 抖音登录成功,code:', loginRes.code)
|
||||||
|
// 保存 code 用于后续登录
|
||||||
|
uni.setStorageSync('douyin_login_code', loginRes.code)
|
||||||
|
|
||||||
|
// 尝试获取 openid
|
||||||
const res = await request({
|
const res = await request({
|
||||||
url: '/api/app/common/openid',
|
url: '/api/app/common/openid',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: { code: loginRes.code, platform: 'toutiao' }
|
data: { code: loginRes.code, platform: 'douyin' }
|
||||||
})
|
})
|
||||||
if (res && res.openid) {
|
if (res && res.openid) {
|
||||||
console.log('[DEBUG] 静默获取 openid 成功:', res.openid)
|
console.log('[DEBUG] 静默获取 openid 成功:', res.openid)
|
||||||
@ -237,6 +241,9 @@ async function ensureOpenID() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[DEBUG] 静默获取 openid 失败:', err)
|
console.error('[DEBUG] 静默获取 openid 失败:', err)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('[DEBUG] 抖音登录失败:', err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
@ -421,54 +428,58 @@ function onGetPhoneNumber(e) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 抖音登录
|
// 抖音登录 - 不需要手机号授权
|
||||||
function onToutiaoGetPhoneNumber(e) {
|
async function handleDouyinLogin() {
|
||||||
|
console.log('[DEBUG] 抖音登录按钮点击')
|
||||||
|
|
||||||
if (!agreementChecked.value) {
|
if (!agreementChecked.value) {
|
||||||
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
|
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
|
||||||
vibrateShort()
|
vibrateShort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const phoneCode = e.detail.code
|
|
||||||
if (!phoneCode) {
|
|
||||||
uni.showToast({ title: '需要授权手机号', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
uni.login({
|
|
||||||
provider: 'toutiao',
|
|
||||||
success: async (res) => {
|
|
||||||
try {
|
try {
|
||||||
const inviterCode = uni.getStorageSync('inviter_code')
|
// 使用保存的登录 code 或重新登录获取
|
||||||
const data = await toutiaoLogin(res.code, inviterCode)
|
const loginCode = uni.getStorageSync('douyin_login_code')
|
||||||
|
|
||||||
saveUserData(data)
|
let loginData
|
||||||
|
if (!loginCode) {
|
||||||
// 绑定手机号 (仅当后端反馈未绑定时调用)
|
// 如果没有保存的 code,重新登录获取
|
||||||
const isBound = data.phone || data.phone_number || data.mobile
|
console.log('[DEBUG] 未保存登录 code,开始重新登录...')
|
||||||
if (!isBound) {
|
const loginRes = await new Promise((resolve, reject) => {
|
||||||
try {
|
tt.login({
|
||||||
await bindPhone(data.user_id, phoneCode)
|
success: resolve,
|
||||||
} catch (e) {}
|
fail: reject
|
||||||
|
})
|
||||||
|
})
|
||||||
|
console.log('[DEBUG] 重新获取抖音登录 code:', loginRes.code)
|
||||||
|
loginData = await douyinLogin(loginRes.code, null, uni.getStorageSync('inviter_code'))
|
||||||
|
} else {
|
||||||
|
console.log('[DEBUG] 使用保存的登录 code:', loginCode)
|
||||||
|
loginData = await douyinLogin(loginCode, null, uni.getStorageSync('inviter_code'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 后台获取数据
|
console.log('[DEBUG] 抖音登录成功:', loginData)
|
||||||
fetchExtraData(data.user_id)
|
|
||||||
|
|
||||||
|
// 保存登录数据(saveUserData 会自动检查手机号绑定状态)
|
||||||
|
saveUserData(loginData)
|
||||||
|
|
||||||
|
// 后台获取数据(仅在已绑定手机号时)
|
||||||
|
const hasPhone = loginData.mobile || loginData.phone || loginData.phone_number
|
||||||
|
if (hasPhone) {
|
||||||
|
fetchExtraData(loginData.user_id)
|
||||||
uni.showToast({ title: '✨ 登录成功!', icon: 'none', duration: 1200 })
|
uni.showToast({ title: '✨ 登录成功!', icon: 'none', duration: 1200 })
|
||||||
setTimeout(() => uni.reLaunch({ url: '/pages/mine/index' }), 600)
|
setTimeout(() => uni.reLaunch({ url: '/pages/mine/index' }), 600)
|
||||||
|
}
|
||||||
|
// 如果未绑定手机号,saveUserData 会自动切换到短信登录tab
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
uni.showToast({ title: err.message || '登录失败', icon: 'none' })
|
console.error('[DEBUG] 抖音登录失败:', err)
|
||||||
|
uni.showToast({ title: err.message || '登录失败,请重试', icon: 'none', duration: 2000 })
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveUserData(data) {
|
function saveUserData(data) {
|
||||||
@ -482,8 +493,16 @@ function saveUserData(data) {
|
|||||||
if (data.invite_code) uni.setStorageSync('invite_code', data.invite_code)
|
if (data.invite_code) uni.setStorageSync('invite_code', data.invite_code)
|
||||||
if (data.mobile) uni.setStorageSync('last_login_mobile', data.mobile)
|
if (data.mobile) uni.setStorageSync('last_login_mobile', data.mobile)
|
||||||
|
|
||||||
// 核心修复:无论哪种登录方式,登录成功后都标记手机号已绑定
|
// 检查是否已绑定手机号
|
||||||
|
const hasPhone = data.mobile || data.phone || data.phone_number
|
||||||
|
console.log('[DEBUG] 检查手机号绑定状态:', hasPhone ? '已绑定' : '未绑定')
|
||||||
|
|
||||||
|
// 根据实际是否有手机号来设置 phone_bound 状态
|
||||||
|
if (hasPhone) {
|
||||||
uni.setStorageSync('phone_bound', true)
|
uni.setStorageSync('phone_bound', true)
|
||||||
|
} else {
|
||||||
|
uni.setStorageSync('phone_bound', false)
|
||||||
|
}
|
||||||
|
|
||||||
// 如果返回了 openid,则存储 (短信登录现在也会返回已关联的 openid)
|
// 如果返回了 openid,则存储 (短信登录现在也会返回已关联的 openid)
|
||||||
const openid = data.openid || data.open_id
|
const openid = data.openid || data.open_id
|
||||||
@ -493,6 +512,21 @@ function saveUserData(data) {
|
|||||||
} else {
|
} else {
|
||||||
console.warn('[DEBUG] 登录接口未返回 openid, 请检查后端或联系管理员')
|
console.warn('[DEBUG] 登录接口未返回 openid, 请检查后端或联系管理员')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasPhone) {
|
||||||
|
// 未绑定手机号,切换到短信登录tab进行绑定
|
||||||
|
console.log('[DEBUG] 未检测到手机号,切换到短信登录tab')
|
||||||
|
uni.showModal({
|
||||||
|
title: '绑定手机号',
|
||||||
|
content: '登录成功!为了账号安全,请绑定手机号',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '去绑定',
|
||||||
|
success: () => {
|
||||||
|
// 切换到短信登录tab
|
||||||
|
loginMode.value = 'sms'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchExtraData(userId) {
|
function fetchExtraData(userId) {
|
||||||
|
|||||||
@ -470,6 +470,7 @@ import {
|
|||||||
getUserInfo, getUserStats, getPointsBalance, getUserPoints, getUserCoupons, getItemCards,
|
getUserInfo, getUserStats, getPointsBalance, getUserPoints, getUserCoupons, getItemCards,
|
||||||
getUserTasks, getTaskProgress, getInviteRecords, modifyUser
|
getUserTasks, getTaskProgress, getInviteRecords, modifyUser
|
||||||
} from '../../api/appUser.js'
|
} from '../../api/appUser.js'
|
||||||
|
import { checkPhoneBound } from '../../utils/checkPhone.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@ -479,6 +480,7 @@ export default {
|
|||||||
avatar: '',
|
avatar: '',
|
||||||
title: '', // 用户头衔
|
title: '', // 用户头衔
|
||||||
inviteCode: '',
|
inviteCode: '',
|
||||||
|
mobile: '', // 手机号
|
||||||
pointsBalance: 0,
|
pointsBalance: 0,
|
||||||
|
|
||||||
stats: {
|
stats: {
|
||||||
@ -535,6 +537,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
|
// 检查手机号绑定状态
|
||||||
|
if (!checkPhoneBound()) return
|
||||||
|
|
||||||
this.loadUserInfo()
|
this.loadUserInfo()
|
||||||
},
|
},
|
||||||
onShareAppMessage() {
|
onShareAppMessage() {
|
||||||
@ -554,6 +559,23 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 检查是否已绑定手机号
|
||||||
|
checkPhoneBound() {
|
||||||
|
if (!this.mobile) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '需要绑定手机号',
|
||||||
|
content: '为了账号安全,请先绑定手机号',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '去绑定',
|
||||||
|
success: () => {
|
||||||
|
uni.navigateTo({ url: '/pages/login/index?mode=sms' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
// 修改头像
|
// 修改头像
|
||||||
handleEditAvatar() {
|
handleEditAvatar() {
|
||||||
if (!this.userId) {
|
if (!this.userId) {
|
||||||
@ -561,6 +583,8 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
|
|
||||||
uni.chooseImage({
|
uni.chooseImage({
|
||||||
count: 1,
|
count: 1,
|
||||||
sizeType: ['compressed'], // 选择压缩图
|
sizeType: ['compressed'], // 选择压缩图
|
||||||
@ -726,6 +750,7 @@ export default {
|
|||||||
this.inviteCode = cachedUser.invite_code
|
this.inviteCode = cachedUser.invite_code
|
||||||
this.title = cachedUser.title || ''
|
this.title = cachedUser.title || ''
|
||||||
this.pointsBalance = this.normalizePointsBalance(cachedUser.points_balance)
|
this.pointsBalance = this.normalizePointsBalance(cachedUser.points_balance)
|
||||||
|
this.mobile = cachedUser.mobile || cachedUser.phone || cachedUser.phone_number || ''
|
||||||
} else if (cachedUserId) {
|
} else if (cachedUserId) {
|
||||||
this.userId = cachedUserId
|
this.userId = cachedUserId
|
||||||
}
|
}
|
||||||
@ -754,6 +779,7 @@ export default {
|
|||||||
this.title = res.title || res.level_name || ''
|
this.title = res.title || res.level_name || ''
|
||||||
this.inviteCode = res.invite_code
|
this.inviteCode = res.invite_code
|
||||||
this.pointsBalance = this.normalizePointsBalance(res.points_balance)
|
this.pointsBalance = this.normalizePointsBalance(res.points_balance)
|
||||||
|
this.mobile = res.mobile || res.phone || res.phone_number || ''
|
||||||
uni.setStorageSync('user_info', res)
|
uni.setStorageSync('user_info', res)
|
||||||
uni.setStorageSync('user_id', res.id)
|
uni.setStorageSync('user_id', res.id)
|
||||||
|
|
||||||
@ -779,28 +805,36 @@ export default {
|
|||||||
uni.navigateTo({ url: '/pages/login/index' })
|
uni.navigateTo({ url: '/pages/login/index' })
|
||||||
},
|
},
|
||||||
toOrders(status) {
|
toOrders(status) {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
uni.navigateTo({ url: `/pages-user/orders/index?status=${status}` })
|
uni.navigateTo({ url: `/pages-user/orders/index?status=${status}` })
|
||||||
},
|
},
|
||||||
toCabinetTab(tabIndex) {
|
toCabinetTab(tabIndex) {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
uni.setStorageSync('cabinet_target_tab', tabIndex)
|
uni.setStorageSync('cabinet_target_tab', tabIndex)
|
||||||
uni.switchTab({ url: '/pages/cabinet/index' })
|
uni.switchTab({ url: '/pages/cabinet/index' })
|
||||||
},
|
},
|
||||||
toAddresses() {
|
toAddresses() {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
uni.navigateTo({ url: '/pages-user/address/index' })
|
uni.navigateTo({ url: '/pages-user/address/index' })
|
||||||
},
|
},
|
||||||
toPointsPage() {
|
toPointsPage() {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
uni.navigateTo({ url: '/pages-user/points/index' })
|
uni.navigateTo({ url: '/pages-user/points/index' })
|
||||||
},
|
},
|
||||||
toCouponsPage() {
|
toCouponsPage() {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
uni.navigateTo({ url: '/pages-user/coupons/index' })
|
uni.navigateTo({ url: '/pages-user/coupons/index' })
|
||||||
},
|
},
|
||||||
toItemCardsPage() {
|
toItemCardsPage() {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
uni.navigateTo({ url: '/pages-user/item-cards/index' })
|
uni.navigateTo({ url: '/pages-user/item-cards/index' })
|
||||||
},
|
},
|
||||||
toInvitesPage() {
|
toInvitesPage() {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
uni.navigateTo({ url: '/pages-user/invites/index' })
|
uni.navigateTo({ url: '/pages-user/invites/index' })
|
||||||
},
|
},
|
||||||
toTasksPage() {
|
toTasksPage() {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
uni.navigateTo({ url: '/pages-user/tasks/index' })
|
uni.navigateTo({ url: '/pages-user/tasks/index' })
|
||||||
},
|
},
|
||||||
toHelp() {
|
toHelp() {
|
||||||
@ -882,6 +916,7 @@ export default {
|
|||||||
|
|
||||||
// --- Points Logic ---
|
// --- Points Logic ---
|
||||||
async showPointsPopup() {
|
async showPointsPopup() {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
this.pointsVisible = true
|
this.pointsVisible = true
|
||||||
this.pointsList = []
|
this.pointsList = []
|
||||||
this.pointsPage = 1
|
this.pointsPage = 1
|
||||||
@ -918,6 +953,7 @@ export default {
|
|||||||
|
|
||||||
// --- Coupons Logic ---
|
// --- Coupons Logic ---
|
||||||
async showCouponsPopup() {
|
async showCouponsPopup() {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
this.couponsVisible = true
|
this.couponsVisible = true
|
||||||
this.couponsTab = 1
|
this.couponsTab = 1
|
||||||
this.couponsList = []
|
this.couponsList = []
|
||||||
@ -976,6 +1012,7 @@ export default {
|
|||||||
|
|
||||||
// --- Item Cards Logic ---
|
// --- Item Cards Logic ---
|
||||||
async showItemCardsPopup() {
|
async showItemCardsPopup() {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
this.itemCardsVisible = true
|
this.itemCardsVisible = true
|
||||||
this.itemCardsTab = 0
|
this.itemCardsTab = 0
|
||||||
this.itemCardsList = []
|
this.itemCardsList = []
|
||||||
@ -1026,6 +1063,7 @@ export default {
|
|||||||
|
|
||||||
// --- Tasks Logic ---
|
// --- Tasks Logic ---
|
||||||
async showTasksPopup() {
|
async showTasksPopup() {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
this.tasksVisible = true
|
this.tasksVisible = true
|
||||||
this.tasksLoading = true
|
this.tasksLoading = true
|
||||||
try {
|
try {
|
||||||
@ -1162,6 +1200,7 @@ export default {
|
|||||||
|
|
||||||
// --- Invites Logic ---
|
// --- Invites Logic ---
|
||||||
async showInvitesPopup() {
|
async showInvitesPopup() {
|
||||||
|
if (!this.checkPhoneBound()) return
|
||||||
this.invitesVisible = true
|
this.invitesVisible = true
|
||||||
this.invitesLoading = true
|
this.invitesLoading = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -157,6 +157,7 @@
|
|||||||
import { onShow, onReachBottom } from '@dcloudio/uni-app'
|
import { onShow, onReachBottom } from '@dcloudio/uni-app'
|
||||||
import { ref, watch, onUnmounted } from 'vue'
|
import { ref, watch, onUnmounted } from 'vue'
|
||||||
import { getStoreItems, redeemProductByPoints, redeemCouponByPoints, redeemItemCardByPoints } from '../../api/appUser'
|
import { getStoreItems, redeemProductByPoints, redeemCouponByPoints, redeemItemCardByPoints } from '../../api/appUser'
|
||||||
|
import { checkPhoneBound } from '../../utils/checkPhone.js'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const keyword = ref('')
|
const keyword = ref('')
|
||||||
@ -413,15 +414,15 @@ async function onRedeemTap(item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
|
// 检查手机号绑定状态
|
||||||
|
if (!checkPhoneBound()) return
|
||||||
|
|
||||||
const token = uni.getStorageSync('token')
|
const token = uni.getStorageSync('token')
|
||||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
if (token) {
|
||||||
if (token && phoneBound) {
|
|
||||||
page.value = 1
|
page.value = 1
|
||||||
hasMore.value = true
|
hasMore.value = true
|
||||||
allItems.value = []
|
allItems.value = []
|
||||||
loadItems()
|
loadItems()
|
||||||
} else {
|
|
||||||
// Redirect logic if needed
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
28
utils/checkPhone.js
Normal file
28
utils/checkPhone.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 检查手机号绑定状态
|
||||||
|
* 如果未绑定手机号,则跳转到登录页面进行绑定
|
||||||
|
* @returns {boolean} 是否已绑定手机号
|
||||||
|
*/
|
||||||
|
export function checkPhoneBound() {
|
||||||
|
// 获取用户信息
|
||||||
|
const userInfo = uni.getStorageSync('user_info') || {}
|
||||||
|
const mobile = userInfo.mobile || userInfo.phone || userInfo.phone_number || ''
|
||||||
|
|
||||||
|
// 如果已绑定手机号,直接返回
|
||||||
|
if (mobile) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未绑定手机号,显示提示并跳转
|
||||||
|
uni.showModal({
|
||||||
|
title: '需要绑定手机号',
|
||||||
|
content: '为了账号安全,请先绑定手机号',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '去绑定',
|
||||||
|
success: () => {
|
||||||
|
uni.navigateTo({ url: '/pages/login/index?mode=sms' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user