登陆注册页面
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body,
|
||||
@@ -17,3 +18,8 @@ body,
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.background-pink {
|
||||
background-color: #f8f7f5;
|
||||
background-image: url('@/assets/images/home-bg.png');
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -20,4 +21,10 @@ body,
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.background-pink {
|
||||
background-color: rgba(248, 247, 245, 1);
|
||||
background-image: url('@/assets/images/home-bg.png');
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
3
src/assets/icons/arrow-left.svg
Normal file
3
src/assets/icons/arrow-left.svg
Normal 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 |
BIN
src/assets/images/login/elephant.png
Normal file
BIN
src/assets/images/login/elephant.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
BIN
src/assets/images/login/google.png
Normal file
BIN
src/assets/images/login/google.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
src/assets/images/login/left-bg.png
Normal file
BIN
src/assets/images/login/left-bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/assets/images/login/wechat.png
Normal file
BIN
src/assets/images/login/wechat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
src/assets/images/logo-2.png
Normal file
BIN
src/assets/images/logo-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
103
src/components/input-code.vue
Normal file
103
src/components/input-code.vue
Normal 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>
|
||||
@@ -12,10 +12,10 @@ const router = createRouter({
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/home'
|
||||
redirect: '/index'
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
path: '/index',
|
||||
name: 'index',
|
||||
component: () => import('../views/login/index.vue'),
|
||||
},
|
||||
@@ -24,6 +24,11 @@ const router = createRouter({
|
||||
name: 'login',
|
||||
component: () => import('../views/login/login.vue'),
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'register',
|
||||
component: () => import('../views/login/register.vue'),
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
|
||||
@@ -102,6 +102,18 @@ export function FormatDate(value: Date | number | string, format: string = 'yyyy
|
||||
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
184
src/views/css/style.css
Normal 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;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<div class="home background-pink">
|
||||
<left-nav />
|
||||
<div class="right-main">
|
||||
<top-nav />
|
||||
@@ -23,9 +23,6 @@
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
background-color: rgba(248, 247, 245, 1);
|
||||
background-image: url('@/assets/images/home-bg.png');
|
||||
background-size: 100% 100%;
|
||||
user-select: none;
|
||||
> .right-main {
|
||||
flex: 1;
|
||||
|
||||
@@ -189,7 +189,6 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 0.8rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
> .title {
|
||||
font-weight: 600;
|
||||
|
||||
@@ -1,13 +1,63 @@
|
||||
<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>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useGlobalStore } from '@/stores'
|
||||
const globalStore = useGlobalStore()
|
||||
const loading = computed(() => globalStore.state.loading)
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const onLogin = () => {
|
||||
router.push({ name: 'login' })
|
||||
}
|
||||
const onRegister = () => {
|
||||
router.push({ name: 'register' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -1,13 +1,108 @@
|
||||
<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>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useGlobalStore } from '@/stores'
|
||||
const globalStore = useGlobalStore()
|
||||
const loading = computed(() => globalStore.state.loading)
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { validateEmail, validatePass, validatePrivacy } from './tools'
|
||||
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>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import './style.less';
|
||||
</style>
|
||||
|
||||
76
src/views/login/other-login.vue
Normal file
76
src/views/login/other-login.vue
Normal 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>
|
||||
110
src/views/login/register.vue
Normal file
110
src/views/login/register.vue
Normal 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
187
src/views/login/style.less
Normal 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
35
src/views/login/tools.js
Normal 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()
|
||||
}
|
||||
}
|
||||
105
src/views/login/visible-code.vue
Normal file
105
src/views/login/visible-code.vue
Normal 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>
|
||||
Reference in New Issue
Block a user