Files
device-voice-h5/pages/bind/bind.vue
2026-09-16 16:32:08 +08:00

238 lines
5.7 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page">
<!-- 自定义导航栏 -->
<view v-if="!fromLogin" class="custom-navbar">
<view class="navbar-back" @tap="goBack">
<image class="back-icon" src="../../static/left.png" mode="aspectFit" />
</view>
<view class="navbar-title">绑定手机号</view>
<view class="navbar-placeholder"></view>
</view>
<view class="container" :style="{ paddingTop: fromLogin ? '0' : '0' }">
<view class="card flex-col-g20">
<view class="flex-row-g20">
<label for="">手机号:</label>
<up-input placeholder="请输入绑定的手机号" border="surround" v-model="bind.phone" />
</view>
<view class="flex-row-g20">
<label for="">验证码:</label>
<up-input placeholder="验证码" v-model="bind.code">
<template #suffix>
<up-button @tap="getCode" :text="codeText" type="success" :disabled="cooldown > 0"></up-button>
</template>
</up-input>
</view>
<view class="btn">
<up-button type="primary" @tap="bindPhone">绑定手机号</up-button>
</view>
<!-- 如果从登录页面跳转过来显示退出按钮 -->
<view class="btn" v-if="fromLogin">
<up-button type="error" @tap="logout">退出</up-button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { authApi, assetApi } from '@/api/index.js';
import { useUserStore } from '@/store/index.js';
const POST_BIND_RELOGIN_NOTICE_KEY = 'postBindReloginNotice';
const userStore = useUserStore();
let bind = reactive({
phone: '',
code: ''
});
let cooldown = ref(0);
let codeText = ref('获取验证码');
let timer = null;
let fromLogin = ref(false);
onMounted(async () => {
// 获取页面参数,判断是否从登录页面跳转过来
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options;
fromLogin.value = options.fromLogin === 'true';
// 契约变化:账号已有主手机号时,主号 + 验证码有效 → 幂等建联。
// 预填账号已有主手机号,避免存量用户「提示要验证却提交任何号码都被拒」的死锁。
const token = uni.getStorageSync('token');
const identifier = userStore.state.identifier || uni.getStorageSync('identifier') || '';
if (!token || !identifier) return;
try {
const data = await assetApi.getInfo(identifier);
const mainPhone = (data && data.bound_phone) || '';
if (mainPhone) {
bind.phone = mainPhone;
}
} catch (e) {
// 预填失败不阻塞绑定流程,用户仍可手动输入手机号
console.error('预填主手机号失败', e);
}
});
const prepareManualRelogin = () => {
const preservedIdentifier = userStore.state.identifier || uni.getStorageSync('identifier') || '';
userStore.clearUser();
if (preservedIdentifier) {
userStore.setIdentifier(preservedIdentifier);
}
uni.setStorageSync(POST_BIND_RELOGIN_NOTICE_KEY, '1');
// #ifdef H5
if (typeof sessionStorage !== 'undefined') {
sessionStorage.removeItem('assetToken');
}
// #endif
};
const getCode = async () => {
if (!bind.phone) {
uni.showToast({ title: '请输入手机号', icon: 'none' });
return;
}
if (!/^1[3-9]\d{9}$/.test(bind.phone)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
return;
}
try {
const data = await authApi.sendCode(bind.phone, 'bind_phone');
cooldown.value = data.cooldown_seconds || 60;
codeText.value = `${cooldown.value}s`;
timer = setInterval(() => {
cooldown.value--;
if (cooldown.value <= 0) {
clearInterval(timer);
codeText.value = '获取验证码';
} else {
codeText.value = `${cooldown.value}s`;
}
}, 1000);
uni.showToast({ title: '验证码已发送', icon: 'success' });
} catch (e) {
console.error('发送验证码失败', e);
}
};
const bindPhone = async () => {
if (!bind.phone || !bind.code) {
uni.showToast({ title: '手机号和验证码都不能为空', icon: 'none' });
return;
}
try {
await authApi.bindPhone(bind.phone, bind.code);
if (fromLogin.value) {
prepareManualRelogin();
uni.reLaunch({ url: '/pages/login/login' });
return;
}
uni.showToast({ title: '绑定成功', icon: 'success' });
setTimeout(() => {
// 从其他页面过来的,返回上一页
uni.navigateBack();
}, 1500);
} catch (e) {
console.error('绑定手机号失败', e);
}
};
const logout = () => {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
uni.clearStorageSync();
uni.reLaunch({ url: '/pages/login/login' });
}
}
});
};
const goBack = () => {
uni.navigateTo({
url: '/pages/index/index'
})
};
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: var(--bg-secondary);
max-width: 750rpx;
margin: 0 auto;
width: 100%;
box-sizing: border-box;
}
.custom-navbar {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
margin-bottom: 24rpx;
background: var(--bg-primary);
border-bottom: 1rpx solid var(--border-light);
padding: 0 24rpx;
position: sticky;
top: 0;
z-index: 100;
.navbar-back {
display: flex;
align-items: center;
gap: 8rpx;
padding: 8rpx;
cursor: pointer;
.back-icon {
width: 40rpx;
height: 40rpx;
}
.back-text {
font-size: 28rpx;
color: var(--text-primary);
}
}
.navbar-title {
font-size: 32rpx;
font-weight: 600;
color: var(--text-primary);
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.navbar-placeholder {
width: 100rpx;
}
}
.container {
padding: var(--space-md);
}
</style>