| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- <template>
- <div class="client-register">
- <div class="client-register__panel">
- <div class="client-register__header">
- <div>
- <h2>注册账户</h2>
- <p>用邮箱完成注册即可开始使用</p>
- </div>
- <div class="client-register__header-link">
- 已有账号?<router-link to="/user/login">立即登录</router-link>
- </div>
- </div>
- <el-form ref="registerFormRef" :model="registerForm" :rules="registerRules" label-position="top"
- class="client-register__form">
- <el-form-item label="联系人姓名" prop="name">
- <el-input v-model.trim="registerForm.name" placeholder="请输入联系人姓名" clearable />
- </el-form-item>
- <el-form-item label="邮箱" prop="email">
- <el-input v-model.trim="registerForm.email" placeholder="用于接收验证码及通知" clearable />
- </el-form-item>
- <el-form-item label="邮箱验证码" prop="emailCode">
- <div class="client-register__code-row">
- <el-input class="client-register__code-input" v-model.trim="registerForm.emailCode" placeholder="请输入6位验证码"
- maxlength="6" />
- <el-button class="client-register__code-btn" type="primary" :loading="sendingCode" :disabled="codeDisabled"
- @click="handleSendEmailCode">
- {{ codeButtonText }}
- </el-button>
- </div>
- </el-form-item>
- <el-form-item label="登录密码" prop="password">
- <el-input v-model="registerForm.password" type="password" placeholder="至少设置6位密码,包含字母或数字" show-password />
- </el-form-item>
- <el-form-item label="确认密码" prop="confirmPassword">
- <el-input v-model="registerForm.confirmPassword" type="password" placeholder="再次输入密码" show-password />
- </el-form-item>
- <el-form-item label="邀请码(选填)" prop="inviteCode">
- <el-input :disabled="userRegister" v-model.trim="registerForm.inviteCode" placeholder="没有可留空" clearable />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" class="client-register__submit" :loading="submitting" @click="handleRegister">
- 注册并登录
- </el-button>
- </el-form-item>
- <p class="client-register__agreement">
- @2019-2026 成都聚德鼎盛文化传播有限公司 蜀ICP备19030658号-1
- </p>
- </el-form>
- </div>
- </div>
- </template>
- <script setup>
- import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
- import { useRouter, useRoute } from 'vue-router'
- import { ElMessage } from 'element-plus'
- import { checkIn } from '/@/api/frontend/user/index'
- import { sendEms } from '/@/api/common'
- import { uuid } from '/@/utils/random'
- import clickCaptcha from '/@/components/clickCaptcha'
- import { useUserInfo } from '/@/stores/userInfo'
- defineOptions({ name: 'ClientRegister' })
- const router = useRouter()
- const route = useRoute()
- const userInfo = useUserInfo()
- // 邀请码开关
- const userRegister = ref(false)
- const registerFormRef = ref(null)
- const registerForm = reactive({
- name: '',
- email: '',
- emailCode: '',
- password: '',
- confirmPassword: '',
- inviteCode: '',
- captchaId: uuid(),
- captchaInfo: ''
- })
- const validateConfirmPassword = (rule, value, callback) => {
- if (!value) {
- callback(new Error('请再次输入密码'))
- } else if (value !== registerForm.password) {
- callback(new Error('两次输入的密码不一致'))
- } else {
- callback()
- }
- }
- const registerRules = reactive({
- name: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }],
- email: [
- { required: true, message: '请输入企业邮箱', trigger: 'blur' },
- { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
- ],
- emailCode: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
- password: [
- { required: true, message: '请输入密码', trigger: 'blur' },
- { min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
- ],
- confirmPassword: [{ required: true, validator: validateConfirmPassword, trigger: 'blur' }],
- inviteCode: []
- })
- const submitting = ref(false)
- const sendingCode = ref(false)
- const countdown = ref(0)
- const countdownTimer = ref(null)
- const codeDisabled = computed(() => sendingCode.value || countdown.value > 0)
- const codeButtonText = computed(() => {
- if (sendingCode.value) return '发送中...'
- return countdown.value > 0 ? `${countdown.value}s 后重试` : '发送验证码'
- })
- const clearCountdown = () => {
- if (countdownTimer.value) {
- clearInterval(countdownTimer.value)
- countdownTimer.value = null
- }
- }
- const startCountdown = seconds => {
- countdown.value = seconds
- clearCountdown()
- countdownTimer.value = setInterval(() => {
- if (countdown.value <= 1) {
- clearCountdown()
- countdown.value = 0
- } else {
- countdown.value -= 1
- }
- }, 1000)
- }
- const handleRegister = () => {
- if (!registerFormRef.value) return
- registerFormRef.value.validate(async valid => {
- if (!valid) return
- submitting.value = true
- try {
- const payload = {
- tab: 'register',
- registerType: 'email',
- name: registerForm.name.trim(),
- email: registerForm.email.trim(),
- username: registerForm.name.trim(),
- password: registerForm.password,
- captcha: registerForm.emailCode.trim(),
- captchaId: registerForm.captchaId,
- captchaInfo: registerForm.captchaInfo,
- inviteCode: registerForm.inviteCode.trim() || undefined
- }
- const res = await checkIn('post', payload)
- // 注册成功后直接填充用户信息并跳转
- if (res?.data?.userInfo) {
- userInfo.dataFill(res.data.userInfo, false)
- }
- if (res?.data?.routePath) {
- router.push({ path: res.data.routePath })
- } else {
- router.push({ path: '/user/home' })
- }
- } catch (error) {
- ElMessage.error(error?.message || '注册失败,请稍后重试')
- // 注册失败后重置验证码ID以防止重复验证
- registerForm.captchaId = uuid()
- } finally {
- submitting.value = false
- }
- })
- }
- const handleSendEmailCode = () => {
- if (!registerFormRef.value) return
- if (codeDisabled.value) return
- if (!registerForm.email.trim()) {
- ElMessage.warning('请输入邮箱地址')
- return
- }
- registerFormRef.value.validateField('email').then((valid) => {
- if (!valid) {
- // 验证失败,Element Plus 会自动显示错误信息
- return
- }
- // 邮箱验证通过,发送验证码前先进行点选验证码验证
- clickCaptcha(registerForm.captchaId, async (captchaInfo) => {
- sendingCode.value = true
- registerForm.captchaInfo = captchaInfo
- try {
- await sendEms(registerForm.email.trim(), 'user_register', {
- captchaInfo,
- captchaId: registerForm.captchaId
- })
- ElMessage.success('验证码发送成功,请查收邮箱')
- startCountdown(60)
- } catch (err) {
- ElMessage.error(err?.message || '验证码发送失败')
- // 失败时刷新点击验证码ID
- registerForm.captchaId = uuid()
- } finally {
- sendingCode.value = false
- }
- })
- })
- }
- // 组件挂载时检查 URL 中的邀请码参数
- onMounted(() => {
- const inviteCode = route.query.invite
- if (inviteCode && typeof inviteCode === 'string') {
- registerForm.inviteCode = inviteCode.trim()
- userRegister.value = true
- }
- })
- onBeforeUnmount(() => {
- clearCountdown()
- })
- </script>
- <style scoped>
- .client-register {
- min-height: 100vh;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 48px 16px;
- background: url('/@/assets/images/login_bg.jpg') center center / cover no-repeat;
- }
- .client-register__panel {
- width: 520px;
- max-width: 100%;
- padding: 28px 36px 24px;
- background: #fff;
- border-radius: 16px;
- box-shadow: 0 15px 35px rgba(0, 0, 0, 0.08);
- }
- .client-register__header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- }
- .client-register__header h2 {
- margin: 0;
- font-size: 22px;
- color: #303133;
- }
- .client-register__header p {
- margin: 6px 0 0;
- color: #909399;
- }
- .client-register__header-link {
- font-size: 14px;
- color: #606266;
- }
- .client-register__header-link a {
- color: #409eff;
- margin-left: 4px;
- }
- .client-register__form ::v-deep .el-form-item__label {
- font-weight: 500;
- color: #303133;
- }
- .client-register__form :deep(.el-form-item) {
- margin-bottom: 18px;
- }
- .client-register__form :deep(.el-input) {
- width: 100%;
- }
- .client-register__code-row {
- display: flex;
- align-items: stretch;
- width: 100%;
- }
- .client-register__code-input {
- flex: 1;
- }
- .client-register__code-btn {
- white-space: nowrap;
- margin-left: -1px;
- min-width: 128px;
- border-radius: 0 8px 8px 0;
- }
- .client-register__code-input :deep(.el-input__wrapper) {
- border-radius: 8px 0 0 8px;
- }
- .client-register__submit {
- width: 100%;
- height: 44px;
- font-size: 15px;
- }
- .client-register__agreement {
- margin: -8px 0 0;
- font-size: 12px;
- color: #909399;
- text-align: center;
- }
- .client-register__agreement a {
- color: #409eff;
- text-decoration: none;
- }
- @media (max-width: 600px) {
- .client-register {
- padding: 24px 12px;
- }
- .client-register__panel {
- padding: 24px 20px;
- }
- .client-register__header {
- flex-direction: column;
- align-items: flex-start;
- gap: 6px;
- }
- }
- </style>
|