Files
medical-mall/pages/user/bind-phone.uvue

549 lines
11 KiB
Plaintext
Raw 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="bind-phone-page">
<view class="header">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
</view>
<text class="header-title">绑定手机</text>
</view>
<view class="content">
<view v-if="userInfo.phone" class="already-bound">
<text class="bound-icon">✓</text>
<text class="bound-title">已绑定手机</text>
<text class="bound-phone">{{ formatPhone(userInfo.phone) }}</text>
<text class="bound-time">绑定时间:{{ formatTime(userInfo.phoneBoundTime) }}</text>
</view>
<form @submit="onSubmit" v-if="!userInfo.phone || isChanging">
<!-- 手机号输入 -->
<view class="form-item">
<text class="label">手机号</text>
<input
class="input"
type="number"
placeholder="请输入手机号"
v-model="form.phone"
:disabled="loading || countdown > 0"
maxlength="11"
required
/>
</view>
<!-- 验证码 -->
<view class="form-item">
<text class="label">验证码</text>
<view class="code-input-wrapper">
<input
class="code-input"
type="number"
placeholder="请输入验证码"
v-model="form.code"
:disabled="loading"
maxlength="6"
required
/>
<button
class="get-code-btn"
@click="getCode"
:disabled="!canGetCode || countdown > 0"
>
{{ countdown > 0 ? `${countdown}s后重新获取` : '获取验证码' }}
</button>
</view>
</view>
<!-- 提交按钮 -->
<button
class="submit-btn"
form-type="submit"
:disabled="loading || !isFormValid"
:loading="loading"
>
{{ loading ? '处理中...' : userInfo.phone ? '更换手机号' : '绑定手机号' }}
</button>
</form>
<view v-if="userInfo.phone && !isChanging" class="action-buttons">
<button class="change-btn" @click="startChange">更换手机号</button>
<button class="unbind-btn" @click="unbindPhone">解绑手机</button>
</view>
</view>
<!-- 成功提示 -->
<view v-if="showSuccess" class="success-modal" @click="hideSuccess">
<view class="success-content" @click.stop>
<text class="success-icon">✓</text>
<text class="success-title">{{ successTitle }}</text>
<text class="success-text">{{ successMessage }}</text>
<button class="success-btn" @click="hideSuccess">确定</button>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, computed, onMounted } from 'vue'
//import supa from '@/components/supadb/aksupainstance.uts'
// 用户信息
const userInfo = ref({
phone: '',
phoneBoundTime: null as string | null
})
const form = ref({
phone: '',
code: ''
})
const loading = ref<boolean>(false)
const countdown = ref<number>(0)
const isChanging = ref<boolean>(false)
const showSuccess = ref<boolean>(false)
const successTitle = ref<string>('')
const successMessage = ref<string>('')
// 表单验证
const isFormValid = computed((): boolean => {
const { phone, code } = form.value
return /^1[3-9]\d{9}$/.test(phone) && /^\d{6}$/.test(code)
})
// 是否可以获取验证码
const canGetCode = computed((): boolean => {
return /^1[3-9]\d{9}$/.test(form.value.phone)
})
// 加载用户信息
const loadUserInfo = () => {
const storedUserInfo = uni.getStorageSync('userInfo')
if (storedUserInfo) {
try {
const info = JSON.parse(storedUserInfo as string)
userInfo.value.phone = info.phone || ''
userInfo.value.phoneBoundTime = info.phoneBoundTime || null
} catch (e) {
console.error('Failed to parse user info', e)
}
}
}
// 获取验证码
const getCode = () => {
if (!canGetCode.value) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none'
})
return
}
// 开始倒计时
countdown.value = 60
const timer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(timer)
}
}, 1000)
// 模拟发送验证码
uni.showToast({
title: '验证码已发送',
icon: 'success'
})
// 实际项目中这里应该调用发送短信的API
// const response = await sendSmsCode(form.value.phone)
}
// 提交表单
const onSubmit = async () => {
if (!isFormValid.value) {
uni.showToast({
title: '请填写完整且正确的信息',
icon: 'none'
})
return
}
loading.value = true
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000))
// 更新本地用户信息
const storedUserInfo = uni.getStorageSync('userInfo')
let userInfoData = storedUserInfo ? JSON.parse(storedUserInfo as string) : {}
userInfoData.phone = form.value.phone
userInfoData.phoneBoundTime = new Date().toISOString()
uni.setStorageSync('userInfo', JSON.stringify(userInfoData))
userInfo.value.phone = form.value.phone
userInfo.value.phoneBoundTime = userInfoData.phoneBoundTime
// 显示成功消息
successTitle.value = isChanging.value ? '更换成功' : '绑定成功'
successMessage.value = `手机号 ${formatPhone(form.value.phone)} 已成功${isChanging.value ? '更换' : '绑定'}`
showSuccess.value = true
// 重置表单
form.value = { phone: '', code: '' }
isChanging.value = false
} catch (error: any) {
console.error('绑定手机失败:', error)
uni.showToast({
title: error.message || '操作失败',
icon: 'none'
})
} finally {
loading.value = false
}
}
// 开始更换手机号
const startChange = () => {
isChanging.value = true
form.value.phone = userInfo.value.phone
}
// 解绑手机
const unbindPhone = () => {
uni.showModal({
title: '解绑手机',
content: '确定要解绑手机吗?解绑后可能影响账号安全',
success: async (res) => {
if (res.confirm) {
loading.value = true
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000))
// 更新本地用户信息
const storedUserInfo = uni.getStorageSync('userInfo')
let userInfoData = storedUserInfo ? JSON.parse(storedUserInfo as string) : {}
userInfoData.phone = ''
userInfoData.phoneBoundTime = null
uni.setStorageSync('userInfo', JSON.stringify(userInfoData))
userInfo.value.phone = ''
userInfo.value.phoneBoundTime = null
uni.showToast({
title: '解绑成功',
icon: 'success'
})
} catch (error: any) {
console.error('解绑失败:', error)
uni.showToast({
title: error.message || '解绑失败',
icon: 'none'
})
} finally {
loading.value = false
}
}
}
})
}
// 格式化手机号显示
const formatPhone = (phone: string): string => {
if (!phone) return ''
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
}
// 格式化时间显示
const formatTime = (time: string | null): string => {
if (!time) return '未知'
const date = new Date(time)
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`
}
// 导航函数
const goBack = () => {
uni.navigateBack()
}
const hideSuccess = () => {
showSuccess.value = false
}
// 生命周期
onMounted(() => {
loadUserInfo()
})
</script>
<style>
.bind-phone-page {
min-height: 100vh;
background-color: #f5f5f5;
}
.header {
background-color: #ffffff;
padding: 15px;
display: flex;
align-items: center;
border-bottom: 1px solid #e5e5e5;
}
.back-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
}
.back-icon {
font-size: 24px;
color: #333;
}
.header-title {
font-size: 18px;
font-weight: bold;
color: #333;
flex: 1;
}
.content {
padding: 20px;
}
.already-bound {
background-color: #ffffff;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
text-align: center;
}
.bound-icon {
display: block;
width: 60px;
height: 60px;
line-height: 60px;
background-color: #4cd964;
color: #ffffff;
font-size: 30px;
border-radius: 50%;
margin: 0 auto 15px;
}
.bound-title {
display: block;
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 5px;
}
.bound-phone {
display: block;
font-size: 24px;
color: #007aff;
margin-bottom: 10px;
font-weight: bold;
}
.bound-time {
display: block;
font-size: 14px;
color: #999;
}
.form-item {
margin-bottom: 20px;
background-color: #ffffff;
border-radius: 10px;
padding: 15px;
}
.label {
display: block;
font-size: 16px;
color: #333;
margin-bottom: 10px;
font-weight: bold;
}
.input {
width: 100%;
height: 44px;
border: 1px solid #ddd;
border-radius: 8px;
padding: 0 15px;
font-size: 16px;
box-sizing: border-box;
}
.input:focus {
border-color: #007aff;
outline: none;
}
.input:disabled {
background-color: #f9f9f9;
color: #999;
}
.code-input-wrapper {
display: flex;
gap: 10px;
}
.code-input {
flex: 1;
height: 44px;
border: 1px solid #ddd;
border-radius: 8px;
padding: 0 15px;
font-size: 16px;
box-sizing: border-box;
}
.get-code-btn {
width: 120px;
height: 44px;
background-color: #007aff;
color: #ffffff;
border-radius: 8px;
font-size: 14px;
border: none;
}
.get-code-btn:disabled {
background-color: #cccccc;
}
.submit-btn {
width: 100%;
height: 50px;
background-color: #007aff;
color: #ffffff;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
border: none;
margin-top: 30px;
}
.submit-btn:disabled {
background-color: #cccccc;
}
.action-buttons {
display: flex;
flex-direction: column;
gap: 15px;
margin-top: 20px;
}
.change-btn {
width: 100%;
height: 50px;
background-color: #ffffff;
color: #007aff;
border: 2px solid #007aff;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
}
.unbind-btn {
width: 100%;
height: 50px;
background-color: #ffffff;
color: #ff3b30;
border: 2px solid #ff3b30;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
}
/* 成功提示模态框 */
.success-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.success-content {
width: 280px;
background-color: #ffffff;
border-radius: 15px;
padding: 30px 20px;
text-align: center;
}
.success-icon {
display: block;
width: 60px;
height: 60px;
line-height: 60px;
background-color: #4cd964;
color: #ffffff;
font-size: 30px;
border-radius: 50%;
margin: 0 auto 20px;
}
.success-title {
display: block;
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.success-text {
display: block;
font-size: 14px;
color: #666;
margin-bottom: 20px;
line-height: 1.4;
}
.success-btn {
width: 100%;
height: 44px;
background-color: #007aff;
color: #ffffff;
border-radius: 22px;
font-size: 16px;
border: none;
}
/* 响应式优化 */
@media screen and (min-width: 768px) {
.bind-phone-page {
max-width: 500px;
margin: 0 auto;
border-left: 1px solid #e5e5e5;
border-right: 1px solid #e5e5e5;
}
.content {
padding: 40px;
}
}
</style>