feat:新增了绑定手机检查,抖音登录等逻辑,并且更改了页面样式以符合抖音要求

This commit is contained in:
tsui110 2026-01-02 16:03:33 +08:00
parent 61df7fca5e
commit 05056c8188
10 changed files with 286 additions and 70 deletions

View File

@ -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' })
} }

View File

@ -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

View File

@ -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>

View File

@ -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": {

View File

@ -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) {

View File

@ -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(() => {

View File

@ -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) {

View File

@ -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 {

View File

@ -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
View 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
}