✨ qr login page /reset password page
This commit is contained in:
8
components.d.ts
vendored
8
components.d.ts
vendored
@@ -19,6 +19,7 @@ declare module 'vue' {
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
AQrcode: typeof import('ant-design-vue/es')['QRCode']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
ATabPane: typeof import('ant-design-vue/es')['TabPane']
|
||||
@@ -27,7 +28,14 @@ declare module 'vue' {
|
||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
BoxDog: typeof import('./src/components/BoxDog/BoxDog.vue')['default']
|
||||
DynamicTitle: typeof import('./src/components/DynamicTitle/DynamicTitle.vue')['default']
|
||||
FoegetPage: typeof import('./src/views/Forget/FoegetPage.vue')['default']
|
||||
ForgetPage: typeof import('./src/views/Forget/ForgetPage.vue')['default']
|
||||
LockOutlined: typeof import('@ant-design/icons-vue')['LockOutlined']
|
||||
LoginFooter: typeof import('./src/views/Login/LoginFooter.vue')['default']
|
||||
LoginPage: typeof import('./src/views/Login/LoginPage.vue')['default']
|
||||
QRLogin: typeof import('./src/views/QRLogin/QRLogin.vue')['default']
|
||||
QRLoginFooter: typeof import('./src/views/QRLogin/QRLoginFooter.vue')['default']
|
||||
RegisterFooter: typeof import('./src/views/Register/RegisterFooter.vue')['default']
|
||||
RegisterPage: typeof import('./src/views/Register/RegisterPage.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
@@ -21,7 +21,6 @@ body {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.container {
|
||||
|
@@ -20,7 +20,15 @@ export default {
|
||||
login: "login",
|
||||
register: "register",
|
||||
reSendCaptcha: " re-send",
|
||||
passwordRule: "The password must be 6~18 characters and must contain letters, numbers and special symbols!"
|
||||
passwordRule: "The password must be 6~18 characters and must contain letters, numbers and special symbols!",
|
||||
phoneLoginAndRegister: "phone login/register",
|
||||
open: "Please open",
|
||||
scan: "scan the code to login",
|
||||
wechat: "wechat",
|
||||
repassword: "confirm password",
|
||||
repasswordValidate: "please enter the confirmation password",
|
||||
forgetPassword: "forget password",
|
||||
resetPassword: "reset password",
|
||||
},
|
||||
error: {
|
||||
networkError: 'Network error, please try again later',
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// zh.ts
|
||||
export default {
|
||||
login: {
|
||||
title: '五味子云存储',
|
||||
title: '五味子云相册',
|
||||
accountLogin: '账号登录',
|
||||
account: '账号',
|
||||
accountValidate: '请输入手机号/账号/邮箱',
|
||||
@@ -9,7 +9,7 @@ export default {
|
||||
passwordValidate: "请输入密码",
|
||||
autoLogin: "自动登录",
|
||||
forgotPassword: "忘记密码",
|
||||
loginAndRegister: "登录",
|
||||
loginAndRegister: "登录/注册",
|
||||
qrLogin: "扫码登录",
|
||||
phoneLogin: "短信登录",
|
||||
phone: "手机号",
|
||||
@@ -20,7 +20,15 @@ export default {
|
||||
login: "登录",
|
||||
register: "注册",
|
||||
reSendCaptcha: "重新发送",
|
||||
passwordRule: "密码要6~18位字符,且必须包含字母、数字和特殊符号!"
|
||||
passwordRule: "密码要6~18位字符,且必须包含字母、数字和特殊符号!",
|
||||
phoneLoginAndRegister: "短信注册/登录",
|
||||
open: "请打开",
|
||||
scan: "扫码登录",
|
||||
wechat: "微信",
|
||||
repassword: "确认密码",
|
||||
repasswordValidate: "请输入确认密码",
|
||||
forgetPassword: "忘记密码",
|
||||
resetPassword: "重置密码",
|
||||
},
|
||||
error: {
|
||||
networkError: '网络连接失败!',
|
||||
|
11
src/router/modules/qrlogin.ts
Normal file
11
src/router/modules/qrlogin.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export default [
|
||||
{
|
||||
path: '/qrlogin',
|
||||
name: 'qrlogin',
|
||||
component: () => import('@/views/QRLogin/QRLogin.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
title: '扫码登录'
|
||||
}
|
||||
}
|
||||
];
|
@@ -1,11 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
path: '/register',
|
||||
name: 'register',
|
||||
component: () => import('@/views/Register/RegisterPage.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
title: '注册页'
|
||||
}
|
||||
}
|
||||
];
|
11
src/router/modules/resetpass.ts
Normal file
11
src/router/modules/resetpass.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export default [
|
||||
{
|
||||
path: '/resetpass',
|
||||
name: 'resetpass',
|
||||
component: () => import('@/views/Forget/ForgetPage.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
title: '重置密码'
|
||||
}
|
||||
}
|
||||
];
|
@@ -7,12 +7,14 @@ import test2 from "@/router/modules/testI18n.ts";
|
||||
import useStore from "@/store";
|
||||
import {message} from "ant-design-vue";
|
||||
import {close, start} from '@/utils/nprogress/nprogress.ts';
|
||||
import register from "@/router/modules/register.ts";
|
||||
import qrlogin from "@/router/modules/qrlogin.ts";
|
||||
import resetpass from "@/router/modules/resetpass.ts";
|
||||
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
...login,
|
||||
...register,
|
||||
...qrlogin,
|
||||
...resetpass,
|
||||
...test,
|
||||
...test2,
|
||||
];
|
||||
|
8
src/types/user.d.ts
vendored
8
src/types/user.d.ts
vendored
@@ -8,7 +8,15 @@ export interface AccountLogin {
|
||||
account?: string
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface PhoneLogin {
|
||||
phone?: string
|
||||
captcha?: string;
|
||||
}
|
||||
|
||||
export interface ResetPassword {
|
||||
phone?: string
|
||||
captcha?: string;
|
||||
password?: string;
|
||||
repassword?: string;
|
||||
}
|
||||
|
209
src/views/Forget/ForgetPage.vue
Normal file
209
src/views/Forget/ForgetPage.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<div class="forget-main">
|
||||
<div class="forget-left">
|
||||
<BoxDog/>
|
||||
</div>
|
||||
<div class="forget-right">
|
||||
<span class="forget-right-title">{{ t("login.title") }}</span>
|
||||
<ACard class="forget-card" bordered :hoverable="false">
|
||||
<AFlex :vertical="true">
|
||||
<span class="forget-card-title">{{ t("login.forgetPassword") }}</span>
|
||||
<AForm
|
||||
ref="resetPasswordRef"
|
||||
:model="ResetPasswordForm"
|
||||
:rules="rules"
|
||||
>
|
||||
<AFormItem name="phone">
|
||||
<span class="forget-card-span">{{ t("login.phone") }}</span>
|
||||
<AInput v-model:value="ResetPasswordForm.phone" :placeholder=phoneValidate size="large" allow-clear>
|
||||
<template #prefix>
|
||||
<TabletOutlined/>
|
||||
</template>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
<AFormItem name="captcha">
|
||||
<AFlex :vertical="true">
|
||||
<span class="forget-card-span">{{ t("login.phoneCaptcha") }}</span>
|
||||
<AFlex :vertical="false" align="center" justify="center">
|
||||
<AInput v-model:value="ResetPasswordForm.captcha" :placeholder=captchaValidate size="large" allow-clear>
|
||||
<template #prefix>
|
||||
<SafetyOutlined/>
|
||||
</template>
|
||||
</AInput>
|
||||
<AButton v-if="!state.showCountDown" @click="sendCaptcha" size="large" style="margin-left: 10px">{{
|
||||
t("login.sendCaptcha")
|
||||
}}
|
||||
</AButton>
|
||||
<AButton v-if="state.showCountDown" disabled size="large" style="margin-left: 10px">{{
|
||||
state.countDownTime
|
||||
}}s{{ t("login.reSendCaptcha") }}
|
||||
</AButton>
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
</AFormItem>
|
||||
<AFormItem name="password">
|
||||
<span class="forget-card-span">{{ t("login.password") }}</span>
|
||||
<AInput v-model:value="ResetPasswordForm.password" :placeholder=passwordValidate size="large" allow-clear>
|
||||
<template #prefix>
|
||||
<LockOutlined/>
|
||||
</template>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
<AFormItem name="repassword">
|
||||
<span class="forget-card-span">{{ t("login.repassword") }}</span>
|
||||
<AInput v-model:value="ResetPasswordForm.repassword" :placeholder=repasswordValidate size="large" allow-clear>
|
||||
<template #prefix>
|
||||
<LockOutlined/>
|
||||
</template>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
<AFormItem>
|
||||
<AButton @click="resetPasswordSubmit" style="width: 100%;" type="primary" size="large">{{
|
||||
t("login.resetPassword")
|
||||
}}</AButton>
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
</AFlex>
|
||||
<ATooltip placement="left">
|
||||
<template #title>
|
||||
<span>{{ t("login.phoneLogin") }}</span>
|
||||
</template>
|
||||
<div @click="()=>{
|
||||
router.push('/login');
|
||||
}" class="forget-right-qrcode"/>
|
||||
</ATooltip>
|
||||
</ACard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {useI18n} from "vue-i18n";
|
||||
import BoxDog from "@/components/BoxDog/BoxDog.vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import {onMounted, reactive, ref, UnwrapRef} from "vue";
|
||||
import {ResetPassword} from "@/types/user";
|
||||
import {Rule} from "ant-design-vue/lib/form";
|
||||
|
||||
|
||||
const router = useRouter();
|
||||
const {t} = useI18n();
|
||||
const resetPasswordRef = ref()
|
||||
const ResetPasswordForm: UnwrapRef<ResetPassword> = reactive({
|
||||
phone: '',
|
||||
captcha: '',
|
||||
password: '',
|
||||
repassword: '',
|
||||
});
|
||||
// 表单验证提示
|
||||
const passwordValidate = ref<string>(t('login.passwordValidate'));
|
||||
const phoneValidate = ref<string>(t('login.phoneValidate'));
|
||||
const captchaValidate = ref<string>(t('login.captchaValidate'));
|
||||
const repasswordValidate = ref<string>(t('login.repasswordValidate'));
|
||||
|
||||
// 表单验证规则
|
||||
const rules: Record<string, Rule[]> = {
|
||||
password: [
|
||||
{
|
||||
required: true, message: t('login.passwordValidate'), trigger: 'change'
|
||||
},
|
||||
{
|
||||
pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{6,18}$/,
|
||||
message: t('login.passwordRule'),
|
||||
},
|
||||
],
|
||||
repassword: [
|
||||
{
|
||||
required: true, message: t('login.repasswordValidate'), trigger: 'change'
|
||||
},
|
||||
{
|
||||
pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{6,18}$/,
|
||||
message: t('login.passwordRule'),
|
||||
},
|
||||
],
|
||||
phone: [
|
||||
{required: true, message: t('login.phoneValidate'), trigger: 'change'},
|
||||
{pattern: /^1[2-9]\d{9}$/, message: t('login.phoneValidate')}
|
||||
],
|
||||
captcha: [
|
||||
{
|
||||
required: true, message: t('login.captchaValidate'), trigger: 'change'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const state = reactive({
|
||||
countDownTime: 60,
|
||||
showCountDown: false,
|
||||
});
|
||||
/**
|
||||
* 验证码发送倒计时
|
||||
*/
|
||||
const countDown = () => {
|
||||
const startTime = localStorage.getItem('startTimeSendCaptcha');
|
||||
const nowTime = new Date().getTime();
|
||||
let surplus: number = 60;
|
||||
let timer: any;
|
||||
if (startTime) {
|
||||
surplus = 60 - Math.floor((nowTime - Number(startTime)) / 1000);
|
||||
surplus = surplus <= 0 ? 0 : surplus;
|
||||
} else {
|
||||
localStorage.setItem('startTimeSendCaptcha', String(nowTime));
|
||||
}
|
||||
|
||||
state.countDownTime = surplus;
|
||||
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
timer = setInterval(() => {
|
||||
if (state.countDownTime <= 0) {
|
||||
localStorage.removeItem('startTimeSendCaptcha');
|
||||
clearInterval(timer);
|
||||
state.countDownTime = 60;
|
||||
state.showCountDown = false;
|
||||
} else {
|
||||
state.countDownTime--;
|
||||
state.showCountDown = true;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
onMounted(() => {
|
||||
const sendEndTime = localStorage.getItem('startTimeSendCaptcha');
|
||||
if (sendEndTime) {
|
||||
state.showCountDown = true;
|
||||
countDown();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
*/
|
||||
async function sendCaptcha() {
|
||||
resetPasswordRef.value
|
||||
.validateFields("phone")
|
||||
.then(() => {
|
||||
countDown();
|
||||
console.log('values');
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log('error', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置密码表单提交
|
||||
*/
|
||||
async function resetPasswordSubmit() {
|
||||
resetPasswordRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
console.log('values', ResetPasswordForm);
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log('error', error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style src="./index.scss" scoped>
|
||||
@import "@/assets/styles/global.scss";
|
||||
</style>
|
90
src/views/Forget/index.scss
Normal file
90
src/views/Forget/index.scss
Normal file
@@ -0,0 +1,90 @@
|
||||
.forget-main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
//background-color: rgb(238, 255, 238);
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
/* 加载背景图 */
|
||||
background-image: url("@/assets/images/background.png");
|
||||
/* 背景图垂直、水平均居中 */
|
||||
background-position: center center;
|
||||
/* 背景图不平铺 */
|
||||
background-repeat: no-repeat;
|
||||
/* 当内容高度大于图片高度时,背景图像的位置相对于viewport固定 */
|
||||
background-attachment: fixed;
|
||||
/* 让背景图基于容器大小伸缩 */
|
||||
background-size: cover;
|
||||
|
||||
.forget-left {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.forget-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
|
||||
.forget-right-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.forget-card-span {
|
||||
color: #999ba1;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.forget-card-return {
|
||||
color: #999ba1;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.forget-card-return:hover {
|
||||
color: #08a327;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.forget-card {
|
||||
width: 440px;
|
||||
height: 490px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .08);
|
||||
|
||||
.forget-card-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
line-height: 26px;
|
||||
margin-bottom: 24px
|
||||
}
|
||||
|
||||
.forget-right-qrcode {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background-position: 50%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
background-image: url(data:image/png;base64,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);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.forget-right-qrcode:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.forget-form-bottom-button {
|
||||
color: #999ba1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
25
src/views/Login/LoginFooter.vue
Normal file
25
src/views/Login/LoginFooter.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div>
|
||||
<ADivider/>
|
||||
<AFlex :vertical="false" align="center" justify="space-around">
|
||||
<AButton @click="()=>{
|
||||
router.push('/qrlogin')
|
||||
}" class="login-form-bottom-button" :icon="h(QrcodeOutlined)">{{ t("login.qrLogin") }}
|
||||
</AButton>
|
||||
<AButton class="login-form-bottom-button" :icon="h(QqOutlined)"></AButton>
|
||||
<AButton class="login-form-bottom-button" :icon="h(GithubOutlined)"></AButton>
|
||||
</AFlex>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {GithubOutlined, QqOutlined, QrcodeOutlined} from "@ant-design/icons-vue";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {h} from "vue";
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
const router = useRouter();
|
||||
const {t} = useI18n();
|
||||
</script>
|
||||
<style src="./index.scss" scoped>
|
||||
|
||||
</style>
|
@@ -20,7 +20,7 @@
|
||||
name="phone">
|
||||
<span class="login-card-span">{{ t("login.phone") }}</span>
|
||||
<AInput v-model:value="phoneLoginForm.phone" class="login-form-input" size="large"
|
||||
:placeholder=phoneValidate
|
||||
:placeholder=phoneValidate allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
<TabletOutlined/>
|
||||
@@ -33,7 +33,7 @@
|
||||
<AFlex :vertical="true">
|
||||
<span class="login-card-span">{{ t("login.phoneCaptcha") }}</span>
|
||||
<AFlex :vertical="false" align="center" justify="center">
|
||||
<AInput v-model:value="phoneLoginForm.captcha" size="large" :placeholder=captchaValidate>
|
||||
<AInput v-model:value="phoneLoginForm.captcha" size="large" :placeholder=captchaValidate allow-clear>
|
||||
<template #prefix>
|
||||
<SafetyOutlined/>
|
||||
</template>
|
||||
@@ -53,22 +53,16 @@
|
||||
<AFormItem>
|
||||
<AFlex :vertical="false" justify="space-between">
|
||||
<ACheckbox>{{ t("login.autoLogin") }}</ACheckbox>
|
||||
<a>{{ t("login.forgotPassword") }}</a>
|
||||
</AFlex>
|
||||
|
||||
</AFormItem>
|
||||
<AFormItem>
|
||||
<AButton @click="phoneLoginSubmit" type="primary" size="large" class="login-form-button">
|
||||
{{ t("login.login") }}
|
||||
{{ t("login.loginAndRegister") }}
|
||||
</AButton>
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
<ADivider/>
|
||||
<AFlex :vertical="false" align="center" justify="space-around">
|
||||
<AButton class="login-form-bottom-button" :icon="h(QrcodeOutlined)">{{ t("login.qrLogin") }}</AButton>
|
||||
<AButton class="login-form-bottom-button" :icon="h(QqOutlined)"></AButton>
|
||||
<AButton class="login-form-bottom-button" :icon="h(GithubOutlined)"></AButton>
|
||||
</AFlex>
|
||||
<LoginFooter/>
|
||||
</ATabPane>
|
||||
<!-- 账号登录 -->
|
||||
<ATabPane key="accountLogin">
|
||||
@@ -83,7 +77,7 @@
|
||||
name="account">
|
||||
<span class="login-card-span">{{ t("login.account") }}</span>
|
||||
<AInput v-model:value="accountLoginForm.account" class="login-form-input" size="large"
|
||||
:placeholder=accountValidate>
|
||||
:placeholder=accountValidate allow-clear>
|
||||
<template #prefix>
|
||||
<user-outlined/>
|
||||
</template>
|
||||
@@ -95,7 +89,7 @@
|
||||
<AFlex :vertical="true">
|
||||
<span class="login-card-span">{{ t("login.password") }}</span>
|
||||
<AInputPassword v-model:value="accountLoginForm.password" class="login-form-input" size="large"
|
||||
:placeholder=passwordValidate>
|
||||
:placeholder=passwordValidate allow-clear>
|
||||
<template #prefix>
|
||||
<SafetyOutlined/>
|
||||
</template>
|
||||
@@ -105,32 +99,30 @@
|
||||
<AFormItem>
|
||||
<AFlex :vertical="false" justify="space-between">
|
||||
<ACheckbox>{{ t("login.autoLogin") }}</ACheckbox>
|
||||
<a>{{ t("login.forgotPassword") }}</a>
|
||||
<a @click="()=>{
|
||||
router.push('/resetpass')
|
||||
}">{{ t("login.forgotPassword") }}</a>
|
||||
</AFlex>
|
||||
|
||||
</AFormItem>
|
||||
<AFormItem>
|
||||
<AButton @click="accountLoginSubmit" type="primary" size="large" class="login-form-button">
|
||||
{{
|
||||
t("login.loginAndRegister")
|
||||
t("login.login")
|
||||
}}
|
||||
</AButton>
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
<ADivider/>
|
||||
<AFlex :vertical="false" align="center" justify="space-around">
|
||||
<AButton class="login-form-bottom-button" :icon="h(QrcodeOutlined)">{{ t("login.qrLogin") }}</AButton>
|
||||
<AButton class="login-form-bottom-button" :icon="h(QqOutlined)"></AButton>
|
||||
<AButton class="login-form-bottom-button" :icon="h(GithubOutlined)"></AButton>
|
||||
</AFlex>
|
||||
<LoginFooter/>
|
||||
</ATabPane>
|
||||
|
||||
</ATabs>
|
||||
<ATooltip placement="left">
|
||||
<template #title>
|
||||
<span>{{ t("login.qrLogin") }}</span>
|
||||
</template>
|
||||
<div class="login-right-qrcode"/>
|
||||
<div @click="()=>{
|
||||
router.push('/qrlogin');
|
||||
}" class="login-right-qrcode"/>
|
||||
</ATooltip>
|
||||
</ACard>
|
||||
</div>
|
||||
@@ -138,14 +130,16 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {Rule} from "ant-design-vue/lib/form";
|
||||
import {h, onMounted, reactive, ref, UnwrapRef} from "vue";
|
||||
import {onMounted, reactive, ref, UnwrapRef} from "vue";
|
||||
import {AccountLogin, PhoneLogin} from "@/types/user";
|
||||
import {GithubOutlined, QqOutlined, QrcodeOutlined} from "@ant-design/icons-vue";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import BoxDog from "@/components/BoxDog/BoxDog.vue";
|
||||
import LoginFooter from "@/views/Login/LoginFooter.vue";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
const {t} = useI18n();
|
||||
const accountLoginFormRef = ref();
|
||||
const accountLoginFormRef = ref<any>();
|
||||
const phoneLoginFormRef = ref<any>();
|
||||
// 账号登录表单数据
|
||||
const accountLoginForm: UnwrapRef<AccountLogin> = reactive({
|
||||
|
@@ -5,7 +5,7 @@
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
/* 加载背景图 */
|
||||
background-image: url("@/assets/images/login-back.png");
|
||||
background-image: url("@/assets/images/background.png");
|
||||
/* 背景图垂直、水平均居中 */
|
||||
background-position: center center;
|
||||
/* 背景图不平铺 */
|
||||
@@ -82,10 +82,6 @@
|
||||
.login-form-bottom-button {
|
||||
color: #999ba1;
|
||||
}
|
||||
|
||||
.login-form-bottom-button:hover {
|
||||
color: rgba(15, 15, 16, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
51
src/views/QRLogin/QRLogin.vue
Normal file
51
src/views/QRLogin/QRLogin.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="qrlogin-main">
|
||||
<div class="qrlogin-left">
|
||||
<BoxDog/>
|
||||
</div>
|
||||
<div class="qrlogin-right">
|
||||
<span class="qrlogin-right-title">{{ t("login.title") }}</span>
|
||||
<ACard class="qrlogin-card" bordered :hoverable="false">
|
||||
<AFlex :vertical="true" align="center">
|
||||
<span class="qrlogin-card-item-span">{{ t("login.qrLogin") }}</span>
|
||||
<span class="qrlogin-card-item-info">
|
||||
{{ t("login.open") }}
|
||||
<span class="qrlogin-card-wechat">{{ t("login.wechat") }}</span>
|
||||
{{ t("login.scan") }}
|
||||
</span>
|
||||
<AQrcode
|
||||
class="qrlogin-card-qr"
|
||||
:size="230"
|
||||
:error-level="'H'"
|
||||
value="https://www.antdv.com"
|
||||
icon="https://www.antdv.com/assets/logo.1ef800a8.svg"
|
||||
/>
|
||||
<ACheckbox class="qrlogin-card-auto-login">{{ t("login.autoLogin") }}</ACheckbox>
|
||||
</AFlex>
|
||||
<QRLoginFooter/>
|
||||
<ATooltip placement="left">
|
||||
<template #title>
|
||||
<span>{{ t("login.phoneLogin") }}</span>
|
||||
</template>
|
||||
<div @click="()=>{
|
||||
router.push('/login')
|
||||
}" class="qrlogin-right-qrcode"/>
|
||||
</ATooltip>
|
||||
</ACard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {useI18n} from "vue-i18n";
|
||||
import BoxDog from "@/components/BoxDog/BoxDog.vue";
|
||||
import QRLoginFooter from "@/views/QRLogin/QRLoginFooter.vue";
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
const {t} = useI18n();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
</script>
|
||||
<style src="./index.scss" scoped>
|
||||
@import "@/assets/styles/global.scss";
|
||||
</style>
|
25
src/views/QRLogin/QRLoginFooter.vue
Normal file
25
src/views/QRLogin/QRLoginFooter.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div>
|
||||
<ADivider/>
|
||||
<AFlex :vertical="false" align="center" justify="space-around">
|
||||
<AButton @click="()=>{
|
||||
router.push('/login')
|
||||
}" class="qrlogin-form-bottom-button" :icon="h(TabletOutlined)">{{ t("login.phoneLoginAndRegister") }}
|
||||
</AButton>
|
||||
<AButton class="qrlogin-form-bottom-button" :icon="h(QqOutlined)"></AButton>
|
||||
<AButton class="qrlogin-form-bottom-button" :icon="h(GithubOutlined)"></AButton>
|
||||
</AFlex>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {GithubOutlined, QqOutlined, TabletOutlined} from "@ant-design/icons-vue";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {h} from "vue";
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
const router = useRouter();
|
||||
const {t} = useI18n();
|
||||
</script>
|
||||
<style src="./index.scss" scoped>
|
||||
|
||||
</style>
|
98
src/views/QRLogin/index.scss
Normal file
98
src/views/QRLogin/index.scss
Normal file
@@ -0,0 +1,98 @@
|
||||
.qrlogin-main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
//background-color: rgb(238, 255, 238);
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
/* 加载背景图 */
|
||||
background-image: url("@/assets/images/background.png");
|
||||
/* 背景图垂直、水平均居中 */
|
||||
background-position: center center;
|
||||
/* 背景图不平铺 */
|
||||
background-repeat: no-repeat;
|
||||
/* 当内容高度大于图片高度时,背景图像的位置相对于viewport固定 */
|
||||
background-attachment: fixed;
|
||||
/* 让背景图基于容器大小伸缩 */
|
||||
background-size: cover;
|
||||
|
||||
.qrlogin-left {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.qrlogin-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
|
||||
.qrlogin-right-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.qrlogin-card {
|
||||
width: 440px;
|
||||
height: 490px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .08);
|
||||
|
||||
.qrlogin-card-item-span {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.qrlogin-card-item-info {
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #999;
|
||||
line-height: 18px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.qrlogin-card-wechat {
|
||||
color: #7acc35;
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.qrlogin-card-qr {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.qrlogin-card-auto-login {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
|
||||
.qrlogin-right-qrcode {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background-position: 50%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
background-image: url(data:image/png;base64,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);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.qrlogin-right-qrcode:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.qrlogin-form-bottom-button {
|
||||
color: #999ba1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
<script lang="ts">
|
||||
import {defineComponent} from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: "RegisterPage"
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style src="./index.scss" scoped>
|
||||
|
||||
</style>
|
Reference in New Issue
Block a user