Files
ycc-proxy-webview/src/views/Register.vue
2026-02-28 12:18:33 +08:00

430 lines
12 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, onUnmounted, onMounted, nextTick } from 'vue'
import { showToast } from 'vant'
import { registerByInviteCode, applyForAgent } from '@/api/agent'
import { useAgentStore } from '@/stores/agentStore'
import { useUserStore } from '@/stores/userStore'
import { useRoute, useRouter } from 'vue-router'
import useApiFetch from '@/composables/useApiFetch'
import { useAliyunCaptcha } from '@/composables/useAliyunCaptcha'
const router = useRouter()
const route = useRoute()
const agentStore = useAgentStore()
const userStore = useUserStore()
const { runWithCaptcha } = useAliyunCaptcha()
const phoneNumber = ref('')
const verificationCode = ref('')
const inviteCode = ref('')
const isAgreed = ref(false)
const isCountingDown = ref(false)
const countdown = ref(60)
let timer = null
// 从URL参数中读取邀请码并自动填入如果用户已登录且有手机号则自动填充
onMounted(async () => {
const inviteCodeParam = route.query.invite_code;
if (inviteCodeParam) {
inviteCode.value = inviteCodeParam;
}
// 如果用户已登录且有手机号,自动填充手机号
const token = localStorage.getItem("token");
if (token) {
// 确保用户信息已加载
if (!userStore.mobile) {
await userStore.fetchUserInfo();
}
if (userStore.mobile) {
phoneNumber.value = userStore.mobile;
}
}
});
// 是否是已注册用户(根据注册接口返回判断)
const isRegisteredUser = ref(false)
const isPhoneNumberValid = computed(() => {
return /^1[3-9]\d{9}$/.test(phoneNumber.value)
})
const isInviteCodeValid = computed(() => {
return inviteCode.value.trim().length > 0
})
const canRegister = computed(() => {
return isPhoneNumberValid.value &&
verificationCode.value.length === 6 &&
isInviteCodeValid.value &&
isAgreed.value
})
async function sendVerificationCode() {
if (isCountingDown.value || !isPhoneNumberValid.value) return
if (!isPhoneNumberValid.value) {
showToast({ message: "请输入有效的手机号" });
return
}
if (!isInviteCodeValid.value) {
showToast({ message: "请先输入邀请码" });
return
}
// 使用阿里云滑块验证码保护发送短信接口
runWithCaptcha(
(captchaVerifyParam) => useApiFetch('auth/sendSms')
.post({ mobile: phoneNumber.value, actionType: 'agentApply', captchaVerifyParam })
.json(),
(result) => {
if (result && result.code === 200) {
showToast({ message: "获取成功" });
startCountdown();
nextTick(() => {
const verificationCodeInput = document.getElementById('verificationCode');
if (verificationCodeInput) {
verificationCodeInput.focus();
}
});
} else if (result) {
showToast(result.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 handleRegister() {
if (!isPhoneNumberValid.value) {
showToast({ message: "请输入有效的手机号" });
return
}
if (!isInviteCodeValid.value) {
showToast({ message: "请输入邀请码" });
return
}
if (verificationCode.value.length !== 6) {
showToast({ message: "请输入有效的验证码" });
return
}
if (!isAgreed.value) {
showToast({ message: "请先同意用户协议" });
return
}
// 直接执行注册逻辑
performRegister()
}
// 执行实际的注册逻辑
async function performRegister() {
try {
// 先尝试通过邀请码注册(同时注册用户和代理)
const { data, error } = await registerByInviteCode({
mobile: phoneNumber.value,
code: verificationCode.value,
referrer: inviteCode.value.trim()
})
if (data.value && !error.value) {
if (data.value.code === 200) {
// 保存token
localStorage.setItem('token', data.value.data.accessToken)
localStorage.setItem('refreshAfter', data.value.data.refreshAfter)
localStorage.setItem('accessExpire', data.value.data.accessExpire)
// 更新代理信息到store
if (data.value.data.agent_id) {
agentStore.updateAgentInfo({
isAgent: true,
agentID: data.value.data.agent_id,
level: data.value.data.level || 1,
levelName: data.value.data.level_name || '普通代理'
})
}
showToast({ message: "注册成功!" });
// 跳转到代理主页
setTimeout(() => {
window.location.href = '/'
}, 500)
}
}
} catch (err) {
console.error('注册失败:', err)
showToast({ message: "注册失败,请重试" });
}
}
// 已注册用户申请成为代理
async function applyForAgentAsRegisteredUser() {
try {
const { data, error } = await applyForAgent({
mobile: phoneNumber.value,
code: verificationCode.value,
referrer: inviteCode.value.trim()
})
if (data.value && !error.value) {
if (data.value.code === 200) {
// 保存token
localStorage.setItem('token', data.value.data.accessToken)
localStorage.setItem('refreshAfter', data.value.data.refreshAfter)
localStorage.setItem('accessExpire', data.value.data.accessExpire)
showToast({ message: "申请成功!" });
// 跳转到代理主页
setTimeout(() => {
window.location.href = '/'
}, 500)
} else {
showToast(data.value.msg || "申请失败,请重试")
}
}
} catch (err) {
console.error('申请失败:', err)
showToast({ message: "申请失败,请重试" });
}
}
function toUserAgreement() {
router.push(`/userAgreement`)
}
function toPrivacyPolicy() {
router.push(`/privacyPolicy`)
}
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
const onClickLeft = () => {
router.replace('/')
}
</script>
<template>
<div class="login-layout ">
<van-nav-bar fixed placeholder title="注册成为代理" left-text="" left-arrow @click-left="onClickLeft" />
<div class="login px-4 relative z-10">
<div class="mb-8 pt-20 text-left">
<div class="flex flex-col items-center">
<img class="h-16 w-16 rounded-full shadow" src="@/assets/images/logo.png" alt="Logo" />
<div class="text-3xl mt-4 text-slate-700 font-bold">一查查</div>
</div>
</div>
<!-- 注册表单 -->
<div class="login-form">
<!-- 邀请码输入 -->
<div class="form-item">
<div class="form-label">邀请码</div>
<input v-model="inviteCode" class="form-input" type="text" placeholder="请输入邀请码" />
</div>
<!-- 手机号输入 -->
<div class="form-item">
<div class="form-label">手机号</div>
<input v-model="phoneNumber" class="form-input" type="tel" placeholder="请输入手机号" maxlength="11" />
</div>
<!-- 验证码输入 -->
<div class="form-item">
<div class="form-label">验证码</div>
<div class="verification-input-wrapper">
<input v-model="verificationCode" id="verificationCode" class="form-input verification-input"
placeholder="请输入验证码" maxlength="6" />
<button class="get-code-btn"
:class="{ 'disabled': isCountingDown || !isPhoneNumberValid || !isInviteCodeValid }"
@click="sendVerificationCode"
:disabled="isCountingDown || !isPhoneNumberValid || !isInviteCodeValid">
{{ isCountingDown ? `${countdown}s` : '获取验证码' }}
</button>
</div>
</div>
<!-- 协议同意框 -->
<div class="agreement-wrapper">
<input type="checkbox" v-model="isAgreed" class="agreement-checkbox accent-primary"
id="agreement" />
<label for="agreement" class="agreement-text">
我已阅读并同意
<a class="agreement-link" @click="toUserAgreement">用户协议</a>
<a class="agreement-link" @click="toPrivacyPolicy">隐私政策</a>
</label>
</div>
<!-- 提示文字 -->
<div class="notice-text">
未注册手机号注册后将自动生成账号并成为代理并且代表您已阅读并同意
</div>
<!-- 注册按钮 -->
<button class="login-btn" :class="{ 'disabled': !canRegister }" @click="handleRegister"
:disabled="!canRegister">
注册成为代理
</button>
</div>
</div>
</div>
</template>
<style scoped>
.login-layout {
background-image: url('@/assets/images/login_bg.png');
background-position: center;
background-repeat: no-repeat;
background-size: cover;
height: 100vh;
position: relative;
overflow: hidden;
}
/* 登录表单 */
.login-form {
background-color: var(--color-bg-primary);
padding: 2rem;
margin-top: 0.5rem;
box-shadow: 0px 0px 24px 0px #3F3F3F0F;
border-radius: 8px;
}
/* 表单项 */
.form-item {
margin-bottom: 1.5rem;
display: flex;
align-items: center;
border: none;
border-bottom: 1px solid var(--color-border-primary);
}
.form-label {
font-size: 0.9375rem;
color: var(--color-text-primary);
margin-bottom: 0;
margin-right: 1rem;
font-weight: 500;
min-width: 4rem;
flex-shrink: 0;
}
.form-input {
width: 100%;
padding: 0.875rem 0;
font-size: 0.9375rem;
color: var(--color-text-primary);
outline: none;
background-color: transparent;
}
.form-input::placeholder {
color: var(--color-text-tertiary);
}
.form-input:focus {
border-bottom-color: var(--color-text-primary);
}
/* 验证码输入 */
.verification-input-wrapper {
position: relative;
display: flex;
align-items: center;
flex: 1;
}
.verification-input {
flex: 1;
padding-right: 6rem;
}
.get-code-btn {
position: absolute;
right: 0;
background: none;
border: none;
color: var(--color-primary);
font-size: 0.875rem;
cursor: pointer;
padding: 0.5rem;
font-weight: 500;
}
.get-code-btn.disabled {
color: var(--color-gray-400);
cursor: not-allowed;
}
/* 协议同意 */
.agreement-wrapper {
display: flex;
align-items: center;
margin-top: 1.5rem;
margin-bottom: 1rem;
}
.agreement-checkbox {
flex-shrink: 0;
margin-right: 0.5rem;
}
.agreement-text {
font-size: 0.75rem;
color: var(--color-text-secondary);
line-height: 1.4;
}
.agreement-link {
color: var(--color-primary);
cursor: pointer;
text-decoration: none;
}
/* 提示文字 */
.notice-text {
font-size: 0.6875rem;
color: var(--color-text-tertiary);
line-height: 1.5;
margin-bottom: 2rem;
}
/* 登录按钮 */
.login-btn {
width: 100%;
padding: 0.875rem;
background-color: var(--color-primary);
color: var(--color-text-white);
border: none;
border-radius: 1.5rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: opacity 0.3s;
letter-spacing: 0.25rem;
}
.login-btn:hover {
opacity: 0.9;
}
.login-btn.disabled {
background-color: var(--color-gray-300);
cursor: not-allowed;
}
</style>