56 lines
5.3 KiB
Vue
56 lines
5.3 KiB
Vue
<!-- 页面编号:M-05;用途:验证并更换绑定手机号。 -->
|
||
<template>
|
||
<view class="phone-page" :class="{ 'phone-state--ready': phoneState === 'ready', 'phone-state--saving': phoneState === 'saving' }">
|
||
<ModulePageBackground module="profile" />
|
||
<view class="page-layer"><PageHeader title="修改手机号" /></view>
|
||
<view class="page-content page-layer">
|
||
<view class="current-phone"><text>当前绑定手机号</text><text>{{ phoneForm.currentPhone }}</text><text>更换后,新手机号将用于登录与安全验证。</text></view>
|
||
<view class="form-panel">
|
||
<view class="form-row"><text>新手机号</text><input v-model.trim="phoneForm.newPhone" type="number" maxlength="11" aria-label="新手机号" placeholder="请输入新手机号" @input="errors.newPhone = ''" /></view>
|
||
<text v-if="errors.newPhone" class="field-error">{{ errors.newPhone }}</text>
|
||
<view class="form-row code-row"><text>验证码</text><input v-model.trim="phoneForm.code" type="number" maxlength="6" aria-label="短信验证码" placeholder="6 位验证码" @input="errors.code = ''" /><view class="code-action" role="button" :aria-label="codeCountdown ? `${codeCountdown}秒后可重新发送` : '发送验证码'" @click="sendCode">{{ codeCountdown ? `${codeCountdown}s` : "发送验证码" }}</view></view>
|
||
<text v-if="errors.code" class="field-error">{{ errors.code }}</text>
|
||
</view>
|
||
<AppButton block :disabled="phoneState === 'saving'" :label="phoneState === 'saving' ? '正在更换' : '确认更换'" @click="savePhone" />
|
||
</view>
|
||
<AppToast :visible="toastVisible" :message="toastMessage" />
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { onUnmounted, reactive, ref } from "vue";
|
||
import AppButton from "@/components/AppButton.vue";
|
||
import AppToast from "@/components/AppToast.vue";
|
||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||
import PageHeader from "@/components/PageHeader.vue";
|
||
|
||
const phoneForm = reactive({ currentPhone: "139****6421", newPhone: "", code: "" });
|
||
const errors = reactive({ newPhone: "", code: "" });
|
||
const phoneState = ref("ready");
|
||
const codeCountdown = ref(0);
|
||
const toastVisible = ref(false); const toastMessage = ref("");
|
||
let countdownTimer = null; let stateTimer = null; let toastTimer = null;
|
||
const showToast = (message) => { toastMessage.value = message; toastVisible.value = true; clearTimeout(toastTimer); toastTimer = setTimeout(() => (toastVisible.value = false), 1800); };
|
||
const sendCode = () => {
|
||
if (codeCountdown.value) return;
|
||
if (!/^1\d{10}$/.test(phoneForm.newPhone)) { errors.newPhone = "请输入正确的新手机号"; return; }
|
||
errors.newPhone = ""; codeCountdown.value = 60; showToast("演示验证码已发送,请输入任意 6 位数字");
|
||
countdownTimer = setInterval(() => { codeCountdown.value -= 1; if (!codeCountdown.value) { clearInterval(countdownTimer); countdownTimer = null; } }, 1000);
|
||
};
|
||
const validatePhone = () => {
|
||
errors.newPhone = /^1\d{10}$/.test(phoneForm.newPhone) ? "" : "请输入正确的新手机号";
|
||
errors.code = /^\d{6}$/.test(phoneForm.code) ? "" : "请输入 6 位验证码";
|
||
return !errors.newPhone && !errors.code;
|
||
};
|
||
const savePhone = () => {
|
||
if (!validatePhone() || phoneState.value === "saving") return;
|
||
phoneState.value = "saving";
|
||
stateTimer = setTimeout(() => { phoneState.value = "ready"; phoneForm.currentPhone = `${phoneForm.newPhone.slice(0,3)}****${phoneForm.newPhone.slice(-4)}`; phoneForm.newPhone = ""; phoneForm.code = ""; showToast("绑定手机号已更新"); }, 500);
|
||
};
|
||
onUnmounted(() => { clearInterval(countdownTimer); clearTimeout(stateTimer); clearTimeout(toastTimer); });
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.phone-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:28rpx 30rpx 72rpx}.current-phone,.form-panel{background:url("/static/assets/modules/profile/transparent/module-content-frame.png") center/100% 100% no-repeat}.current-phone{min-height:210rpx;padding:40rpx 48rpx;box-sizing:border-box;text-align:center}.current-phone text{display:block}.current-phone text:first-child{color:$ink-muted;font-size:22rpx}.current-phone text:nth-child(2){margin-top:8rpx;color:$ink;font-size:38rpx;font-weight:700;letter-spacing:3rpx}.current-phone text:last-child{margin-top:12rpx;color:$ink-muted;font-size:21rpx;line-height:1.5}.form-panel{margin-top:22rpx;padding:24rpx 34rpx 32rpx;box-sizing:border-box}.form-row{display:grid;grid-template-columns:130rpx minmax(0,1fr);min-height:94rpx;align-items:center;gap:16rpx;border-bottom:1px solid rgba(181,137,63,.42)}.code-row{grid-template-columns:130rpx minmax(0,1fr) auto}.form-row>text{color:$ink;font-size:23rpx;font-weight:700}.form-row input{width:auto;min-width:0;min-height:70rpx;color:$ink;font-size:23rpx}.code-action{display:flex;min-width:126rpx;min-height:70rpx;align-items:center;justify-content:flex-end;color:$brand-red;font-size:21rpx}.field-error{display:block;padding-top:7rpx;color:#b42318;font-size:20rpx;text-align:right}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:22rpx;padding-left:22rpx}.form-panel{padding-right:26rpx;padding-left:26rpx}.form-row{grid-template-columns:112rpx minmax(0,1fr)}.code-row{grid-template-columns:112rpx minmax(0,1fr);}.code-action{grid-column:2;justify-content:flex-start}}
|
||
</style>
|