Files
zacfrontuser_v2/src/components/RealNameAuthDialog.vue
2026-01-15 18:03:13 +08:00

380 lines
13 KiB
Vue
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.

<script setup>
import { ref, computed } from "vue";
import { useRouter } from "vue-router";
import { useDialogStore } from "@/stores/dialogStore";
import { useAgentStore } from "@/stores/agentStore";
import { useUserStore } from "@/stores/userStore";
import { showToast } from "vant";
import { realNameAuth } from "@/api/agent";
const router = useRouter();
const dialogStore = useDialogStore();
const agentStore = useAgentStore();
const userStore = useUserStore();
// 表单数据
const realName = ref("");
const idCard = ref("");
const phoneNumber = ref("");
const verificationCode = ref("");
const isAgreed = ref(false);
// 倒计时相关
const isCountingDown = ref(false);
const countdown = ref(60);
let timer = null;
// 聚焦状态变量
const nameFocused = ref(false);
const idCardFocused = ref(false);
const phoneFocused = ref(false);
const codeFocused = ref(false);
// 表单验证
const isPhoneNumberValid = computed(() => {
return /^1[3-9]\d{9}$/.test(phoneNumber.value);
});
const isIdCardValid = computed(() => {
return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(idCard.value);
});
const isRealNameValid = computed(() => {
return /^[\u4e00-\u9fa5]{2,}$/.test(realName.value);
});
const canSubmit = computed(() => {
return (
isPhoneNumberValid.value &&
isIdCardValid.value &&
isRealNameValid.value &&
verificationCode.value.length === 6 &&
isAgreed.value
);
});
// 发送验证码
async function sendVerificationCode() {
if (isCountingDown.value || !isPhoneNumberValid.value) return;
if (!isPhoneNumberValid.value) {
showToast({ message: "请输入有效的手机号" });
return;
}
const { data, error } = await useApiFetch("auth/sendSms")
.post({ mobile: phoneNumber.value, actionType: "realName" })
.json();
if (data.value && !error.value) {
if (data.value.code === 200) {
showToast({ message: "获取成功" });
startCountdown();
} else {
showToast(data.value.msg);
}
}
}
function startCountdown() {
isCountingDown.value = true;
countdown.value = 60;
timer = setInterval(() => {
if (countdown.value > 0) {
countdown.value--;
} else {
clearInterval(timer);
isCountingDown.value = false;
}
}, 1000);
}
// 提交实名认证
async function handleSubmit() {
if (!isRealNameValid.value) {
showToast({ message: "请输入有效的姓名" });
return;
}
if (!isIdCardValid.value) {
showToast({ message: "请输入有效的身份证号" });
return;
}
if (!isPhoneNumberValid.value) {
showToast({ message: "请输入有效的手机号" });
return;
}
if (verificationCode.value.length !== 6) {
showToast({ message: "请输入有效的验证码" });
return;
}
if (!isAgreed.value) {
showToast({ message: "请先同意用户协议" });
return;
}
const { data, error } = await realNameAuth({
name: realName.value,
id_card: idCard.value,
mobile: phoneNumber.value,
code: verificationCode.value,
});
if (data.value && !error.value) {
if (data.value.code === 200) {
showToast({ message: "认证成功" });
// 更新实名状态
agentStore.isRealName = true;
// 刷新代理状态信息
await agentStore.fetchAgentStatus();
// 刷新用户信息
await userStore.fetchUserInfo();
closeDialog();
} else {
showToast(data.value.msg);
}
}
}
function closeDialog() {
dialogStore.closeRealNameAuth();
// 重置表单
realName.value = "";
idCard.value = "";
phoneNumber.value = "";
verificationCode.value = "";
isAgreed.value = false;
if (timer) {
clearInterval(timer);
}
}
function toUserAgreement() {
closeDialog();
router.push(`/userAgreement`);
}
function toPrivacyPolicy() {
closeDialog();
router.push(`/privacyPolicy`);
}
</script>
<template>
<div v-if="dialogStore.showRealNameAuth" class="real-name-auth-dialog-box">
<van-popup v-model:show="dialogStore.showRealNameAuth" round position="bottom" @close="closeDialog"
:style="{ maxHeight: '90vh' }">
<div class="real-name-auth-dialog"
style="background: linear-gradient(135deg, var(--van-theme-primary-light), rgba(255,255,255,0.9));">
<div class="title-bar">
<div class="text-base sm:text-lg font-bold" style="color: var(--van-text-color);">实名认证</div>
<van-icon name="cross" class="close-icon" style="color: var(--van-text-color-2);"
@click="closeDialog" />
</div>
<div class="dialog-content">
<div class="px-4 sm:px-6 md:px-8 py-3 sm:py-4">
<div class="auth-notice p-3 sm:p-4 rounded-lg mb-4 sm:mb-6"
style="background-color: var(--van-theme-primary-light); border: 1px solid rgba(162, 37, 37, 0.2);">
<div class="text-xs sm:text-sm space-y-1.5 sm:space-y-2"
style="color: var(--van-text-color);">
<p class="font-medium" style="color: var(--van-theme-primary);">
实名认证说明
</p>
<p>1. 实名认证是提现的必要条件</p>
<p>2. 提现金额将转入您实名认证的银行卡账户</p>
<p>3. 请确保填写的信息真实有效否则将影响提现功能的使用</p>
<p>4. 认证信息提交后将无法修改请仔细核对</p>
</div>
</div>
<div class="space-y-3 sm:space-y-4">
<!-- 姓名输入 -->
<div :class="[
'input-container',
nameFocused ? 'focused' : '',
]"
style="background-color: var(--van-theme-primary-light); border: 2px solid rgba(162, 37, 37, 0);">
<input v-model="realName" class="input-field" type="text" placeholder="请输入真实姓名"
style="color: var(--van-text-color);" @focus="nameFocused = true"
@blur="nameFocused = false" />
</div>
<!-- 身份证号输入 -->
<div :class="[
'input-container',
idCardFocused ? 'focused' : '',
]"
style="background-color: var(--van-theme-primary-light); border: 2px solid rgba(162, 37, 37, 0);">
<input v-model="idCard" class="input-field" type="text" placeholder="请输入身份证号"
maxlength="18" style="color: var(--van-text-color);" @focus="idCardFocused = true"
@blur="idCardFocused = false" />
</div>
<!-- 手机号输入 -->
<div :class="[
'input-container',
phoneFocused ? 'focused' : '',
]"
style="background-color: var(--van-theme-primary-light); border: 2px solid rgba(162, 37, 37, 0);">
<input v-model="phoneNumber" class="input-field" type="tel" placeholder="请输入手机号"
maxlength="11" style="color: var(--van-text-color);" @focus="phoneFocused = true"
@blur="phoneFocused = false" />
</div>
<!-- 验证码输入 -->
<div class="flex items-center gap-2">
<div :class="[
'input-container flex-1',
codeFocused ? 'focused' : '',
]"
style="background-color: var(--van-theme-primary-light); border: 2px solid rgba(162, 37, 37, 0);">
<input v-model="verificationCode" class="input-field" placeholder="请输入验证码"
maxlength="6" style="color: var(--van-text-color);" @focus="codeFocused = true"
@blur="codeFocused = false" />
</div>
<button
class="verify-code-btn px-3 sm:px-4 py-2.5 sm:py-3 text-xs sm:text-sm font-bold flex-shrink-0 rounded-lg transition duration-300 whitespace-nowrap"
:class="isCountingDown || !isPhoneNumberValid
? 'cursor-not-allowed bg-gray-300 text-gray-500'
: 'text-white hover:opacity-90'
" :style="isCountingDown || !isPhoneNumberValid
? ''
: 'background-color: var(--van-theme-primary);'"
@click="sendVerificationCode">
{{
isCountingDown
? `${countdown}s`
: "获取验证码"
}}
</button>
</div>
<!-- 协议同意框 -->
<div class="flex items-start gap-2">
<input type="checkbox" v-model="isAgreed"
class="mt-0.5 sm:mt-1 flex-shrink-0 accent-primary" />
<span class="text-xs sm:text-sm leading-relaxed"
style="color: var(--van-text-color-2);">
我已阅读并同意
<a class="cursor-pointer hover:underline" style="color: var(--van-theme-primary);"
@click="toUserAgreement">用户协议</a>
<a class="cursor-pointer hover:underline" style="color: var(--van-theme-primary);"
@click="toPrivacyPolicy">隐私政策</a>
并确认以上信息真实有效将用于提现等资金操作
</span>
</div>
</div>
<button
class="mb-6 sm:mb-8 mt-6 sm:mt-8 w-full py-2.5 sm:py-3 text-base sm:text-lg font-bold text-white rounded-full transition duration-300"
:class="{ 'opacity-50 cursor-not-allowed': !canSubmit }"
:style="canSubmit ? 'background-color: var(--van-theme-primary);' : 'background-color: var(--van-text-color-3);'"
@click="handleSubmit">
确认认证
</button>
</div>
</div>
</div>
</van-popup>
</div>
</template>
<style scoped>
.real-name-auth-dialog {
max-height: 90vh;
display: flex;
flex-direction: column;
}
.dialog-content {
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.title-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--van-border-color);
flex-shrink: 0;
}
.close-icon {
font-size: 18px;
cursor: pointer;
}
@media (min-width: 640px) {
.close-icon {
font-size: 20px;
}
}
.input-container {
border-radius: 0.75rem;
transition: all 0.2s ease;
}
@media (min-width: 640px) {
.input-container {
border-radius: 1rem;
}
}
.input-container.focused {
border: 2px solid var(--van-theme-primary) !important;
}
.input-field {
width: 100%;
padding: 0.75rem;
background: transparent;
border: none;
outline: none;
font-size: 14px;
transition: border-color 0.3s ease;
}
@media (min-width: 640px) {
.input-field {
padding: 0.875rem 1rem;
font-size: 15px;
}
}
@media (min-width: 768px) {
.input-field {
padding: 1rem;
font-size: 16px;
}
}
.verify-code-btn {
min-width: 85px;
}
@media (min-width: 640px) {
.verify-code-btn {
min-width: 100px;
}
}
/* 优化小屏幕上的间距 */
@media (max-width: 375px) {
.input-field {
padding: 0.625rem;
font-size: 13px;
}
.verify-code-btn {
min-width: 75px;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
}
/* 确保弹窗在键盘弹出时可以滚动 */
.real-name-auth-dialog-box {
position: relative;
}
</style>