登陆注册页面

This commit is contained in:
李志鹏
2026-02-04 10:01:50 +08:00
parent 734c0129cd
commit 110b1e2219
21 changed files with 990 additions and 16 deletions

View File

@@ -1,6 +1,7 @@
* { * {
padding: 0; padding: 0;
margin: 0; margin: 0;
box-sizing: border-box;
} }
html, html,
body, body,
@@ -17,3 +18,8 @@ body,
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
.background-pink {
background-color: #f8f7f5;
background-image: url('@/assets/images/home-bg.png');
background-size: 100% 100%;
}

View File

@@ -1,6 +1,7 @@
* { * {
padding: 0; padding: 0;
margin: 0; margin: 0;
box-sizing: border-box;
} }
html, html,
@@ -20,4 +21,10 @@ body,
100% { 100% {
transform: rotate(360deg); transform: rotate(360deg);
} }
}
.background-pink {
background-color: rgba(248, 247, 245, 1);
background-image: url('@/assets/images/home-bg.png');
background-size: 100% 100%;
} }

View File

@@ -0,0 +1,3 @@
<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.5424 6.70765C25.1525 7.31784 25.1525 8.30716 24.5424 8.91735L14.7097 18.75L24.5424 28.5826C25.1525 29.1928 25.1525 30.1822 24.5424 30.7924C23.9322 31.4025 22.9428 31.4025 22.3326 30.7924L11.3951 19.8549C10.785 19.2447 10.785 18.2553 11.3951 17.6451L22.3326 6.70765C22.9428 6.09745 23.9322 6.09745 24.5424 6.70765Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -0,0 +1,103 @@
<template>
<div class="input-code">
<input
ref="inputRef"
type="tel"
maxlength="1"
v-for="(v, i) in props.length"
:key="i"
v-model="code[i]"
@input="handleInput(i)"
@keydown.delete="handleDelete(i)"
@paste="handlePaste"
/>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, computed, watch, nextTick } from 'vue'
const emit = defineEmits(['submit', 'update:modelValue'])
const props = defineProps({
length: {
type: Number,
default: 6
}
})
const inputRef = ref('')
const code = ref([])
const codeStr = computed(() => code.value.join(''))
watch(codeStr, (newVal) => {
emit('update:modelValue', newVal)
})
const resetCode = (size) => {
code.value = []
for (let i = 0; i < size; i++) {
code.value.push('')
}
}
resetCode(props.length)
const handleInput = (index: number) => {
const value = code.value[index]
if (value) {
if (/[0-9]/.test(value)) {
code.value[index] = value
focusLast()
} else {
code.value[index] = ''
}
}
submit()
}
const handleDelete = (index: number) => {
if (code.value[index].length == 0) {
focusLast(-1)
}
}
const handlePaste = (e: ClipboardEvent) => {
const text = e.clipboardData?.getData('text')
if (text) {
const nums = text.match(/[0-9]/g) || []
if (nums.length === code.value.length) {
code.value = [...nums]
focusLast()
nextTick(submit)
}
}
}
// 聚焦最后一个没有输入的
const focusLast = (step = 0) => {
let index = code.value.findIndex((item) => !item) + step
index < 0 && (index = 0)
if (index >= 0 && index < props.length) {
inputRef.value[index]?.focus?.()
}
if (code.value.every((item) => item.length)) {
inputRef.value?.forEach((item) => item.blur?.())
}
}
const submit = () => {
if (codeStr.value.length === props.length) {
emit('submit', codeStr.value)
}
}
onMounted(() => {
focusLast()
})
</script>
<style lang="less" scoped>
.input-code {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
> input {
width: 7rem;
height: 7rem;
border-radius: 1rem;
border: 0.02rem solid #dfdfdf;
text-align: center;
margin: auto;
}
}
</style>

View File

@@ -12,10 +12,10 @@ const router = createRouter({
routes: [ routes: [
{ {
path: '/', path: '/',
redirect: '/home' redirect: '/index'
}, },
{ {
path: '/', path: '/index',
name: 'index', name: 'index',
component: () => import('../views/login/index.vue'), component: () => import('../views/login/index.vue'),
}, },
@@ -24,6 +24,11 @@ const router = createRouter({
name: 'login', name: 'login',
component: () => import('../views/login/login.vue'), component: () => import('../views/login/login.vue'),
}, },
{
path: '/register',
name: 'register',
component: () => import('../views/login/register.vue'),
},
{ {
path: '/home', path: '/home',
name: 'home', name: 'home',

View File

@@ -102,6 +102,18 @@ export function FormatDate(value: Date | number | string, format: string = 'yyyy
return str; return str;
} }
/**
* 倒计时
* @param time 倒计时时间,单位秒
* @returns 倒计时字符串,格式为 mm:ss
*/
export function CountDown(time: number) {
const mm = String(Math.floor(time / 60)).padStart(2, '0');
const ss = String(time % 60).padStart(2, '0');
return `${mm}:${ss}`;
}
/** /**
* 下载图片 * 下载图片

184
src/views/css/style.css Normal file
View File

@@ -0,0 +1,184 @@
.register,
.login {
width: 100%;
height: 100%;
overflow: hidden;
padding: 2.5rem;
display: flex;
}
.register > .left,
.login > .left {
flex: 1;
height: 100%;
position: relative;
overflow: hidden;
}
.register > .left > .bg,
.login > .left > .bg {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 2rem;
}
.register > .left > .logo,
.login > .left > .logo {
position: absolute;
top: 2.4rem;
left: 4.2rem;
}
.register > .left > .logo > img,
.login > .left > .logo > img {
width: 6rem;
height: auto;
}
.register > .left > .logo > span,
.login > .left > .logo > span {
font-weight: 600;
font-size: 3.3rem;
}
.register > .right,
.login > .right {
width: 90rem;
min-width: 600px;
display: flex;
flex-direction: column;
}
.register > .right > .top,
.login > .right > .top {
display: flex;
padding: 0 3rem;
}
.register > .right > .top > .back,
.login > .right > .top > .back {
width: 5rem;
height: 5rem;
border-radius: 1.2rem;
border: 0.25rem solid #dfdfdf;
background-color: transparent;
cursor: pointer;
}
.register > .right > .box,
.login > .right > .box {
min-width: 50rem;
flex: 1;
overflow-y: auto;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
}
.register > .right > .box > img,
.login > .right > .box > img {
width: 11rem;
height: auto;
margin-top: 2rem;
}
.register > .right > .box > .visible-code,
.login > .right > .box > .visible-code {
margin-top: 1.7rem;
margin-bottom: 7.2rem;
}
.register > .right > .box > .title,
.login > .right > .box > .title {
font-weight: 600;
font-size: 7rem;
display: flex;
align-items: center;
justify-content: center;
color: #252727;
margin-top: 1.7rem;
}
.register > .right > .box > .title > img,
.login > .right > .box > .title > img {
width: auto;
height: 9.8rem;
}
.register > .right > .box > .tip,
.login > .right > .box > .tip {
font-weight: 400;
font-family: General Sans Variable;
font-style: Regular;
font-size: 1.8rem;
color: #666;
margin-top: 0.4rem;
}
.register > .right > .box > .el-form,
.login > .right > .box > .el-form {
margin-top: 5rem;
width: 100%;
}
.register > .right > .box > .el-form::v-deep .el-form-item,
.login > .right > .box > .el-form::v-deep .el-form-item {
margin-bottom: 2rem;
}
.register > .right > .box > .el-form::v-deep .el-form-item__label,
.login > .right > .box > .el-form::v-deep .el-form-item__label {
color: #252727;
font-size: 1.8rem;
margin-bottom: 0.8rem;
}
.register > .right > .box > .el-form::v-deep .el-input,
.login > .right > .box > .el-form::v-deep .el-input {
--el-input-height: 5rem;
--el-input-border-radius: 0.8rem;
--el-input-text-color: #252727;
--el-border-color: #dfdfdf;
font-size: 1.4rem;
}
.register > .right > .box > .el-form::v-deep .forgetPassword,
.login > .right > .box > .el-form::v-deep .forgetPassword {
margin-top: -1.2rem;
margin-bottom: 2rem;
font-size: 1.6rem;
text-align: right;
color: #666666;
cursor: pointer;
text-decoration: underline;
}
.register > .right > .box > .el-form::v-deep .privacy,
.login > .right > .box > .el-form::v-deep .privacy {
--el-checkbox-height: auto;
margin-bottom: 4rem;
}
.register > .right > .box > .el-form::v-deep .privacy .el-checkbox__label,
.login > .right > .box > .el-form::v-deep .privacy .el-checkbox__label {
font-size: 1.6rem;
color: #666666;
font-weight: 400;
}
.register > .right > .box > .el-form::v-deep .privacy .el-checkbox__label > span,
.login > .right > .box > .el-form::v-deep .privacy .el-checkbox__label > span {
text-decoration: underline;
cursor: pointer;
}
.register > .right > .box > .el-form::v-deep .el-form-item__error,
.login > .right > .box > .el-form::v-deep .el-form-item__error {
padding-top: 1px;
font-size: 1.4rem;
}
.register > .right > .box > .el-form::v-deep .submit,
.login > .right > .box > .el-form::v-deep .submit {
width: 100%;
height: 6rem;
background: #252727;
font-size: 2rem;
border-radius: 0.8rem;
color: #fff;
font-weight: 600;
}
.register > .right > .box > .tip-2,
.login > .right > .box > .tip-2 {
font-weight: 400;
font-size: 1.6rem;
color: #666;
}
.register > .right > .box > .tip-2 > span,
.login > .right > .box > .tip-2 > span {
text-decoration: underline;
color: #FF7A50;
cursor: pointer;
}
.register > .right > .box > .other-login,
.login > .right > .box > .other-login {
margin-top: 5rem;
}

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="home"> <div class="home background-pink">
<left-nav /> <left-nav />
<div class="right-main"> <div class="right-main">
<top-nav /> <top-nav />
@@ -23,9 +23,6 @@
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
background-color: rgba(248, 247, 245, 1);
background-image: url('@/assets/images/home-bg.png');
background-size: 100% 100%;
user-select: none; user-select: none;
> .right-main { > .right-main {
flex: 1; flex: 1;

View File

@@ -189,7 +189,6 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0 0.8rem; padding: 0 0.8rem;
box-sizing: border-box;
} }
> .title { > .title {
font-weight: 600; font-weight: 600;

View File

@@ -1,13 +1,63 @@
<template> <template>
<div class="index"></div> <div class="index background-pink">
<div class="header">
<p class="split"></p>
<button class="login" @click="onLogin">Log in</button>
<button class="register" @click="onRegister">Sign up</button>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { useGlobalStore } from '@/stores' import { useRouter } from 'vue-router'
const globalStore = useGlobalStore() const router = useRouter()
const loading = computed(() => globalStore.state.loading) const onLogin = () => {
router.push({ name: 'login' })
}
const onRegister = () => {
router.push({ name: 'register' })
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.index {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
> .header {
position: absolute;
top: 3rem;
left: 0;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
> .split {
margin: 0 auto;
}
> button {
margin-right: 3rem;
width: 20rem;
height: 5.2rem;
border-radius: 5rem;
border: none;
outline: none;
font-size: 2.2rem;
font-weight: 600;
&:active {
opacity: 0.8;
}
}
> .login {
background-color: #ff7a51;
color: #fff;
}
> .register {
background-color: #fff;
color: #232323;
}
}
}
</style> </style>

View File

@@ -1,13 +1,108 @@
<template> <template>
<div class="login"></div> <div class="login">
<div class="left">
<img class="bg" src="@/assets/images/login/left-bg.png" />
<div class="logo">
<img src="@/assets/images/logo.png" />
<span>FiDA</span>
</div>
</div>
<div class="right">
<div class="top">
<button class="back" @click="onBack">
<svg-icon name="arrow-left" size="37" />
</button>
</div>
<div class="box">
<img src="@/assets/images/login/elephant.png" />
<template v-if="!isVisible">
<div class="title">
<span>Log on to</span>
<img src="@/assets/images/logo-2.png" />
</div>
<div class="tip">A multi-agent canvas for rapid, trend driven design iteration.</div>
<el-form :model="formData" :rules="ruleForm" label-position="top" ref="formRef">
<el-form-item label="Email" prop="email">
<el-input v-model="formData.email" placeholder="Enter your email" name="email" />
</el-form-item>
<el-form-item label="Password" prop="password">
<el-input
v-model="formData.password"
placeholder="Enter your password"
type="password"
show-password
name="password"
/>
</el-form-item>
<div class="forgetPassword">
<span>forget password?</span>
</div>
<el-form-item prop="privacy" class="privacy">
<el-checkbox v-model="formData.privacy">
I agree to the <span @click.prevent="onClickPrivacy">Terms, Policy</span> and Fees.
</el-checkbox>
</el-form-item>
<el-form-item>
<el-button class="submit" type="primary" @click="onSubmit">Log in</el-button>
</el-form-item>
</el-form>
<div class="tip-2">
Don't have an account? <span @click.prevent="onClickRegister">Sign up</span>
</div>
</template>
<visible-code v-else :email="formData.email" @submit="onVerifyCode" />
<other-login />
</div>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed, reactive, ref } from 'vue'
import { useGlobalStore } from '@/stores' import { useRouter } from 'vue-router'
const globalStore = useGlobalStore() import { validateEmail, validatePass, validatePrivacy } from './tools'
const loading = computed(() => globalStore.state.loading) import OtherLogin from './other-login.vue'
import VisibleCode from './visible-code.vue'
const router = useRouter()
const ruleForm = reactive({
email: [{ validator: validateEmail, trigger: 'blur' }],
password: [{ validator: validatePass, trigger: 'blur' }],
privacy: [{ validator: validatePrivacy, trigger: 'change' }]
})
const isVisible = ref(false)
const formData = reactive({
email: '',
password: '',
privacy: false
})
const formRef = ref(null)
const onBack = () => {
if (isVisible.value) {
isVisible.value = false
} else {
router.back()
}
}
const onSubmit = () => {
formRef.value?.validate?.((valid) => {
if (valid) {
console.log('submit!')
isVisible.value = true
} else {
console.log('error submit!')
}
})
}
const onVerifyCode = (code: string) => {
console.log(code)
router.push({ name: 'home' })
}
const onClickPrivacy = () => {}
const onClickRegister = () => {
router.push({ name: 'register' })
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import './style.less';
</style> </style>

View File

@@ -0,0 +1,76 @@
<template>
<div class="other-login">
<div class="title">or continue with</div>
<div class="btns">
<el-button class="submit" @click="onGoogle">
<img src="@/assets/images/login/google.png" />
Sign in with Google
</el-button>
<el-button class="submit" @click="onWechat">
<img src="@/assets/images/login/wechat.png" />
Sign in with Wechat
</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
const onGoogle = () => {}
const onWechat = () => {}
</script>
<style lang="less" scoped>
.other-login {
width: 100%;
> .title {
width: 100%;
color: #252727;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.6rem;
&::before,
&::after {
content: '';
flex: 1;
border-bottom: 0.05rem solid #333535;
}
&::before {
margin-right: 2.5rem;
}
&::after {
margin-left: 2.5rem;
}
}
> .btns {
margin-top: 3rem;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
> .el-button {
flex: 1;
height: 5rem;
--el-border-radius-base: 0.8rem;
--el-font-size-base: 1.6rem;
--el-border-color: #dfdfdf;
--el-border-text-color: #666;
margin-left: 2.4rem;
&:first-child {
margin-left: 0;
}
img {
width: auto;
height: 3rem;
margin-right: 2.5rem;
}
}
}
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div class="register">
<div class="left">
<img class="bg" src="@/assets/images/login/left-bg.png" />
<div class="logo">
<img src="@/assets/images/logo.png" />
<span>FiDA</span>
</div>
</div>
<div class="right">
<div class="top">
<button class="back" @click="onBack">
<svg-icon name="arrow-left" size="37" />
</button>
</div>
<div class="box">
<img src="@/assets/images/login/elephant.png" />
<template v-if="!isVisible">
<div class="title">
<span>Register for</span>
<img src="@/assets/images/logo-2.png" />
</div>
<div class="tip">A multi-agent canvas for rapid, trend driven design iteration.</div>
<el-form :model="formData" :rules="ruleForm" label-position="top" ref="formRef">
<el-form-item label="Name" prop="name">
<el-input name="name" v-model="formData.name" placeholder="Enter your name" />
</el-form-item>
<el-form-item label="Password" prop="password">
<el-input
name="password"
v-model="formData.password"
placeholder="Enter your password"
type="password"
show-password
/>
</el-form-item>
<el-form-item label="Email" prop="email">
<el-input name="email" v-model="formData.email" placeholder="Enter your email" />
</el-form-item>
<el-form-item prop="privacy" class="privacy">
<el-checkbox v-model="formData.privacy">
I agree to the <span @click.prevent="onClickPrivacy">Terms, Policy</span> and Fees.
</el-checkbox>
</el-form-item>
<el-form-item>
<el-button class="submit" type="primary" @click="onSubmit">Register</el-button>
</el-form-item>
</el-form>
<div class="tip-2">
Already have an account? <span @click.prevent="onClickLogin">Log in</span>
</div>
</template>
<visible-code v-else :email="formData.email" @submit="onVerifyCode" />
<other-login />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { validateName, validateEmail, validatePass, validatePrivacy } from './tools'
import OtherLogin from './other-login.vue'
import VisibleCode from './visible-code.vue'
const router = useRouter()
const ruleForm = reactive({
name: [{ validator: validateName, trigger: 'blur' }],
email: [{ validator: validateEmail, trigger: 'blur' }],
password: [{ validator: validatePass, trigger: 'blur' }],
privacy: [{ validator: validatePrivacy, trigger: 'change' }]
})
const isVisible = ref(false)
const formData = reactive({
name: '',
email: '',
password: '',
privacy: false
})
const formRef = ref(null)
const onBack = () => {
if (isVisible.value) {
isVisible.value = false
} else {
router.back()
}
}
const onSubmit = () => {
formRef.value?.validate?.((valid) => {
if (valid) {
console.log('submit!')
isVisible.value = true
} else {
console.log('error submit!')
}
})
}
const onVerifyCode = (code: string) => {
console.log(code)
router.push({ name: 'home' })
}
const onClickPrivacy = () => {}
const onClickLogin = () => {
router.push({ name: 'login' })
}
</script>
<style lang="less" scoped>
@import './style.less';
</style>

187
src/views/login/style.less Normal file
View File

@@ -0,0 +1,187 @@
.register,
.login {
width: 100%;
height: 100%;
overflow: hidden;
padding: 2.5rem;
display: flex;
>.left {
flex: 1;
height: 100%;
position: relative;
overflow: hidden;
>.bg {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 2rem;
}
>.logo {
position: absolute;
top: 2.4rem;
left: 4.2rem;
>img {
width: 6rem;
height: auto;
}
>span {
font-weight: 600;
font-size: 3.3rem;
}
}
}
>.right {
width: 90rem;
min-width: 600px;
display: flex;
flex-direction: column;
>.top {
display: flex;
padding: 0 3rem;
>.back {
width: 5rem;
height: 5rem;
border-radius: 1.2rem;
border: 0.25rem solid rgba(223, 223, 223, 1);
background-color: transparent;
cursor: pointer;
}
}
>.box {
min-width: 50rem;
flex: 1;
overflow-y: auto;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
>img {
width: 11rem;
height: auto;
margin-top: 2rem;
}
>.visible-code {
margin-top: 1.7rem;
margin-bottom: 7.2rem;
}
>.title {
font-weight: 600;
font-size: 7rem;
display: flex;
align-items: center;
justify-content: center;
color: #252727;
margin-top: 1.7rem;
>img {
width: auto;
height: 9.8rem;
}
}
>.tip {
font-weight: 400;
font-family: General Sans Variable;
font-style: Regular;
font-size: 1.8rem;
color: #666;
margin-top: 0.4rem;
}
>.el-form {
margin-top: 5rem;
width: 100%;
&::v-deep {
.el-form-item {
margin-bottom: 2rem;
}
.el-form-item__label {
color: #252727;
font-size: 1.8rem;
margin-bottom: 0.8rem;
}
.el-input {
--el-input-height: 5rem;
--el-input-border-radius: 0.8rem;
--el-input-text-color: #252727;
--el-border-color: #dfdfdf;
font-size: 1.4rem;
}
.forgetPassword {
margin-top: -1.2rem;
margin-bottom: 2rem;
font-size: 1.6rem;
text-align: right;
color: #666666;
cursor: pointer;
text-decoration: underline;
}
.privacy {
--el-checkbox-height: auto;
margin-bottom: 4rem;
.el-checkbox__label {
font-size: 1.6rem;
color: #666666;
font-weight: 400;
>span {
text-decoration: underline;
cursor: pointer;
}
}
}
.el-form-item__error {
padding-top: 1px;
font-size: 1.4rem;
}
.submit {
width: 100%;
height: 6rem;
background: #252727;
font-size: 2rem;
border-radius: 0.8rem;
color: #fff;
font-weight: 600;
}
}
}
>.tip-2 {
font-weight: 400;
font-size: 1.6rem;
color: #666;
>span {
text-decoration: underline;
color: #FF7A50;
cursor: pointer;
}
}
>.other-login {
margin-top: 5rem;
}
}
}
}

35
src/views/login/tools.js Normal file
View File

@@ -0,0 +1,35 @@
export const validateName = (rule, value, callback) => {
var str = ""
if (!value) {
str = 'Please input the name'
} else if (value.length < 2 || value.length > 20) {
str = 'Name length must be between 2 and 20 characters'
}
callback(str ? new Error(str) : undefined)
}
export const validateEmail = (rule, value, callback) => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?$/
var str = ''
if (!value) {
str = 'Please input the email'
} else if (!emailRegex.test(value)) {
str = 'Please input the email again'
}
callback(str ? new Error(str) : undefined)
}
export const validatePass = (rule, value, callback) => {
var str = ''
if (!value) {
str = 'Please input the password'
} else if (value.length < 6 || value.length > 20) {
str = 'Password length must be between 6 and 20 characters'
}
callback(str ? new Error(str) : undefined)
}
export const validatePrivacy = (rule, value, callback) => {
if (!value) {
callback(new Error('Please agree to the Terms, Policy and Fees'))
} else {
callback()
}
}

View File

@@ -0,0 +1,105 @@
<template>
<div class="visible-code">
<div class="title">Verify your email address</div>
<div class="tip">
A verification code has been sent to <span>{{ email }}</span>
</div>
<input-code @submit="onVerify" v-model="code" />
<el-button class="verify" @click="onVerify">Verify</el-button>
<p class="time" v-if="time > -1">
<span @click="onResend">Resend Code </span> in {{ timeStr }}
</p>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { CountDown } from '@/utils/tools'
import InputCode from '@/components/input-code.vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const emit = defineEmits(['submit'])
const props = defineProps({
email: ''
})
const code = ref('')
const time = ref(60)
const timeStr = computed(() => CountDown(time.value))
const timeout = ref(null)
const setTime = (s = 120) => {
clearTime()
time.value = s
timeout.value = setInterval(() => {
time.value--
if (time.value <= 0) {
clearTime()
time.value = 0
}
}, 1000)
}
const clearTime = () => {
time.value = -1
clearTimeout(timeout.value)
}
onBeforeUnmount(() => {
clearTime()
})
onMounted(() => {
setTime()
})
const onResend = () => {
if (time.value > 0) return
setTime()
}
const onVerify = () => {
if (code.value.length !== 6) return
emit('submit', code.value)
}
</script>
<style lang="less" scoped>
.visible-code {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
> .title {
font-weight: 600;
font-size: 4rem;
color: #252727;
}
> .tip {
margin-top: 2rem;
font-size: 1.8rem;
color: #666;
> span {
color: #252727;
font-weight: 600;
}
}
> .input-code {
margin: 8.6rem 0;
}
> .verify {
width: 100%;
height: 6rem;
background: #252727;
font-size: 2rem;
border-radius: 0.8rem;
color: #fff;
font-weight: 600;
}
> .time {
user-select: none;
margin-top: 2rem;
font-size: 1.6rem;
color: #666;
> span {
color: #ff7a50;
text-decoration: underline;
cursor: pointer;
font-weight: 500;
}
}
}
</style>