mirror of
https://gitee.com/fantastic-admin/basic.git
synced 2024-12-01 19:48:15 +08:00
refactor!: 重构登录页面
This commit is contained in:
parent
6f59ddf6b8
commit
cb230f77bd
172
src/components/LoginForm/index.vue
Normal file
172
src/components/LoginForm/index.vue
Normal file
@ -0,0 +1,172 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'LoginForm',
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
account?: string
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
onLogin: [account: string]
|
||||
onRegister: [account: string]
|
||||
onResetPassword: [account: string]
|
||||
}>()
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const title = import.meta.env.VITE_APP_TITLE
|
||||
const loading = ref(false)
|
||||
|
||||
// 登录方式,default 账号密码登录,qrcode 扫码登录
|
||||
const type = ref('default')
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = ref({
|
||||
account: props.account ?? localStorage.login_account ?? '',
|
||||
password: '',
|
||||
remember: !!localStorage.login_account,
|
||||
})
|
||||
const rules = ref<FormRules>({
|
||||
account: [
|
||||
{ required: true, trigger: 'blur', message: '请输入用户名' },
|
||||
],
|
||||
password: [
|
||||
{ required: true, trigger: 'blur', message: '请输入密码' },
|
||||
{ min: 6, max: 18, trigger: 'blur', message: '密码长度为6到18位' },
|
||||
],
|
||||
})
|
||||
function handleLogin() {
|
||||
formRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
userStore.login(form.value).then(() => {
|
||||
if (form.value.remember) {
|
||||
localStorage.setItem('login_account', form.value.account)
|
||||
}
|
||||
else {
|
||||
localStorage.removeItem('login_account')
|
||||
}
|
||||
emits('onLogin', form.value.account)
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function testAccount(account: string) {
|
||||
form.value.account = account
|
||||
form.value.password = '123456'
|
||||
handleLogin()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" class="min-h-500px w-full flex-col-stretch-center p-12">
|
||||
<div class="mb-6">
|
||||
<HTabList
|
||||
v-model="type" :options="[
|
||||
{ label: '账号密码登录', value: 'default' },
|
||||
{ label: '扫码登录', value: 'qrcode' },
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="type === 'default'">
|
||||
<h3 class="mb-8 text-xl color-[var(--el-text-color-primary)] font-bold">
|
||||
欢迎使用 {{ title }} ! 👋🏻
|
||||
</h3>
|
||||
<div>
|
||||
<ElFormItem prop="account">
|
||||
<ElInput v-model="form.account" placeholder="用户名" type="text" tabindex="1">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:user-3-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="password">
|
||||
<ElInput v-model="form.password" type="password" placeholder="密码" tabindex="2" show-password @keyup.enter="handleLogin">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:lock-2-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
<div class="mb-4 flex-center-between">
|
||||
<ElCheckbox v-model="form.remember">
|
||||
记住我
|
||||
</ElCheckbox>
|
||||
<ElLink type="primary" :underline="false" @click="emits('onResetPassword', form.account)">
|
||||
忘记密码了?
|
||||
</ElLink>
|
||||
</div>
|
||||
<ElButton :loading="loading" type="primary" size="large" style="width: 100%;" @click.prevent="handleLogin">
|
||||
登录
|
||||
</ElButton>
|
||||
<div class="mt-4 flex-center gap-2 text-sm color-[var(--el-text-color-secondary)]">
|
||||
还没有帐号?
|
||||
<ElLink type="primary" :underline="false" @click="emits('onRegister', form.account)">
|
||||
创建新帐号
|
||||
</ElLink>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="type === 'qrcode'">
|
||||
<div class="flex-col-center">
|
||||
<el-image src="https://s2.loli.net/2024/04/26/GsahtuIZ9XOg5jr.png" class="h-[250px] w-[250px]" />
|
||||
<div class="mt-2 op-50">
|
||||
请使用微信扫码登录
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="mt-4 text-center -mb-4">
|
||||
<ElDivider>演示账号一键登录</ElDivider>
|
||||
<ElButton type="primary" size="small" plain @click="testAccount('admin')">
|
||||
admin
|
||||
</ElButton>
|
||||
<ElButton size="small" plain @click="testAccount('test')">
|
||||
test
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElForm>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(input[type="password"]::-ms-reveal) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 24px;
|
||||
|
||||
:deep(.el-input) {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
line-height: inherit;
|
||||
|
||||
input {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.el-input__prefix,
|
||||
.el-input__suffix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.el-input__prefix {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.el-input__suffix {
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-divider__text) {
|
||||
white-space: nowrap;
|
||||
background-color: var(--g-container-bg);
|
||||
}
|
||||
</style>
|
148
src/components/RegisterForm/index.vue
Normal file
148
src/components/RegisterForm/index.vue
Normal file
@ -0,0 +1,148 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
defineOptions({
|
||||
name: 'RegisterForm',
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
account?: string
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
onLogin: [account: string]
|
||||
onRegister: [account: string]
|
||||
}>()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = ref({
|
||||
account: props.account ?? '',
|
||||
captcha: '',
|
||||
password: '',
|
||||
checkPassword: '',
|
||||
})
|
||||
const rules = ref<FormRules>({
|
||||
account: [
|
||||
{ required: true, trigger: 'blur', message: '请输入用户名' },
|
||||
],
|
||||
captcha: [
|
||||
{ required: true, trigger: 'blur', message: () => '请输入验证码' },
|
||||
],
|
||||
password: [
|
||||
{ required: true, trigger: 'blur', message: '请输入密码' },
|
||||
{ min: 6, max: 18, trigger: 'blur', message: '密码长度为6到18位' },
|
||||
],
|
||||
checkPassword: [
|
||||
{ required: true, trigger: 'blur', message: '请再次输入密码' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value !== form.value.password) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
}
|
||||
else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
function handleRegister() {
|
||||
ElMessage({
|
||||
message: '注册模块仅提供界面演示,无实际功能,需开发者自行扩展',
|
||||
type: 'warning',
|
||||
})
|
||||
formRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
// 这里编写业务代码
|
||||
emits('onRegister', form.value.account)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" class="min-h-500px w-full flex-col-stretch-center p-12">
|
||||
<h3 class="mb-8 text-xl color-[var(--el-text-color-primary)] font-bold">
|
||||
探索从这里开始! 🚀
|
||||
</h3>
|
||||
<div>
|
||||
<ElFormItem prop="account">
|
||||
<ElInput v-model="form.account" placeholder="用户名" tabindex="1">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:user-3-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="captcha">
|
||||
<ElInput v-model="form.captcha" placeholder="验证码" tabindex="2">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ic:baseline-verified-user" />
|
||||
</template>
|
||||
<template #append>
|
||||
<ElButton>发送验证码</ElButton>
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="password">
|
||||
<ElInput v-model="form.password" type="password" placeholder="密码" tabindex="3" show-password>
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:lock-2-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="checkPassword">
|
||||
<ElInput v-model="form.checkPassword" type="password" placeholder="确认密码" tabindex="4" show-password>
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:lock-2-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
<ElButton :loading="loading" type="primary" size="large" style="width: 100%; margin-top: 20px;" @click.prevent="handleRegister">
|
||||
注册
|
||||
</ElButton>
|
||||
<div class="mt-4 flex-center gap-2 text-sm color-[var(--el-text-color-secondary)]">
|
||||
已经有帐号?
|
||||
<ElLink type="primary" :underline="false" @click="emits('onLogin', form.account)">
|
||||
去登录
|
||||
</ElLink>
|
||||
</div>
|
||||
</ElForm>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(input[type="password"]::-ms-reveal) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 24px;
|
||||
|
||||
:deep(.el-input) {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
line-height: inherit;
|
||||
|
||||
input {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.el-input__prefix,
|
||||
.el-input__suffix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.el-input__prefix {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.el-input__suffix {
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
126
src/components/ResetPasswordForm/index.vue
Normal file
126
src/components/ResetPasswordForm/index.vue
Normal file
@ -0,0 +1,126 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
defineOptions({
|
||||
name: 'ResetPasswordForm',
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
account?: string
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
onLogin: [account: string]
|
||||
onResetPassword: [account: string]
|
||||
}>()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = ref({
|
||||
account: props.account ?? localStorage.login_account ?? '',
|
||||
captcha: '',
|
||||
newPassword: '',
|
||||
})
|
||||
const rules = ref<FormRules>({
|
||||
account: [
|
||||
{ required: true, trigger: 'blur', message: '请输入用户名' },
|
||||
],
|
||||
captcha: [
|
||||
{ required: true, trigger: 'blur', message: '请输入验证码' },
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, trigger: 'blur', message: '请输入新密码' },
|
||||
{ min: 6, max: 18, trigger: 'blur', message: '密码长度为6到18位' },
|
||||
],
|
||||
})
|
||||
function handleReset() {
|
||||
ElMessage({
|
||||
message: '重置密码仅提供界面演示,无实际功能,需开发者自行扩展',
|
||||
type: 'info',
|
||||
})
|
||||
formRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
// 这里编写业务代码
|
||||
emits('onResetPassword', form.value.account)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" class="min-h-500px w-full flex-col-stretch-center p-12">
|
||||
<h3 class="mb-8 text-xl color-[var(--el-text-color-primary)] font-bold">
|
||||
忘记密码了? 🔒
|
||||
</h3>
|
||||
<div>
|
||||
<ElFormItem prop="account">
|
||||
<ElInput v-model="form.account" placeholder="用户名" type="text" tabindex="1">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:user-3-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="captcha">
|
||||
<ElInput v-model="form.captcha" placeholder="验证码" type="text" tabindex="2">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ic:baseline-verified-user" />
|
||||
</template>
|
||||
<template #append>
|
||||
<ElButton>发送验证码</ElButton>
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="newPassword">
|
||||
<ElInput v-model="form.newPassword" type="password" placeholder="新密码" tabindex="3" show-password>
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:lock-2-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
<ElButton :loading="loading" type="primary" size="large" style="width: 100%; margin-top: 20px;" @click.prevent="handleReset">
|
||||
确认
|
||||
</ElButton>
|
||||
<div class="mt-4 flex-center gap-2 text-sm color-[var(--el-text-color-secondary)]">
|
||||
<ElLink type="primary" :underline="false" @click="emits('onLogin', form.account)">
|
||||
去登录
|
||||
</ElLink>
|
||||
</div>
|
||||
</ElForm>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(input[type="password"]::-ms-reveal) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 24px;
|
||||
|
||||
:deep(.el-input) {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
line-height: inherit;
|
||||
|
||||
input {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.el-input__prefix,
|
||||
.el-input__suffix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.el-input__prefix {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.el-input__suffix {
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -38,7 +38,7 @@ function handleChange(index: number) {
|
||||
<TabList class="inline-flex select-none items-center justify-center rounded-md bg-stone-1 p-1 ring-1 ring-stone-2 dark-bg-stone-9 dark-ring-stone-8">
|
||||
<Tab v-for="(option, index) in options" :key="index" v-slot="{ selected }" as="template">
|
||||
<button
|
||||
class="w-full inline-flex items-center justify-center gap-1 break-keep border-size-0 rounded-md bg-inherit px-2 py-1.5 text-sm text-dark ring-stone-2 ring-inset dark-text-white focus-outline-none focus-ring-2 dark-ring-stone-8" :class="{
|
||||
class="w-full inline-flex items-center justify-center gap-1 whitespace-nowrap border-size-0 rounded-md bg-inherit px-2 py-1.5 text-sm text-dark ring-stone-2 ring-inset dark-text-white focus-outline-none focus-ring-2 dark-ring-stone-8" :class="{
|
||||
'cursor-default bg-white dark-bg-dark-9': selected,
|
||||
'cursor-pointer opacity-50 hover-(opacity-100)': !selected,
|
||||
}"
|
||||
|
3
src/types/components.d.ts
vendored
3
src/types/components.d.ts
vendored
@ -26,10 +26,13 @@ declare module 'vue' {
|
||||
ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
|
||||
ImagesUpload: typeof import('./../components/ImagesUpload/index.vue')['default']
|
||||
ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']
|
||||
LoginForm: typeof import('./../components/LoginForm/index.vue')['default']
|
||||
NotAllowed: typeof import('./../components/NotAllowed/index.vue')['default']
|
||||
PageHeader: typeof import('./../components/PageHeader/index.vue')['default']
|
||||
PageMain: typeof import('./../components/PageMain/index.vue')['default']
|
||||
PcasCascader: typeof import('./../components/PcasCascader/index.vue')['default']
|
||||
RegisterForm: typeof import('./../components/RegisterForm/index.vue')['default']
|
||||
ResetPasswordForm: typeof import('./../components/ResetPasswordForm/index.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SearchBar: typeof import('./../components/SearchBar/index.vue')['default']
|
||||
|
@ -6,11 +6,8 @@ meta:
|
||||
</route>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import Copyright from '@/layouts/components/Copyright/index.vue'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'Login',
|
||||
@ -18,324 +15,67 @@ defineOptions({
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const banner = new URL('../assets/images/login-banner.png', import.meta.url).href
|
||||
const logo = new URL('../assets/images/logo.png', import.meta.url).href
|
||||
const title = import.meta.env.VITE_APP_TITLE
|
||||
|
||||
// 登录方式,default 账号密码登录,qrcode 扫码登录
|
||||
const loginType = ref('default')
|
||||
|
||||
// 表单类型,login 登录,register 注册,reset 重置密码
|
||||
const formType = ref('login')
|
||||
const loading = ref(false)
|
||||
const redirect = ref(route.query.redirect?.toString() ?? settingsStore.settings.home.fullPath)
|
||||
|
||||
// 登录
|
||||
const loginFormRef = ref<FormInstance>()
|
||||
const loginForm = ref({
|
||||
account: localStorage.login_account || '',
|
||||
password: '',
|
||||
remember: !!localStorage.login_account,
|
||||
})
|
||||
const loginRules = ref<FormRules>({
|
||||
account: [
|
||||
{ required: true, trigger: 'blur', message: '请输入用户名' },
|
||||
],
|
||||
password: [
|
||||
{ required: true, trigger: 'blur', message: '请输入密码' },
|
||||
{ min: 6, max: 18, trigger: 'blur', message: '密码长度为6到18位' },
|
||||
],
|
||||
})
|
||||
function handleLogin() {
|
||||
loginFormRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
userStore.login(loginForm.value).then(() => {
|
||||
loading.value = false
|
||||
if (loginForm.value.remember) {
|
||||
localStorage.setItem('login_account', loginForm.value.account)
|
||||
}
|
||||
else {
|
||||
localStorage.removeItem('login_account')
|
||||
}
|
||||
router.push(redirect.value)
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 注册
|
||||
const registerFormRef = ref<FormInstance>()
|
||||
const registerForm = ref({
|
||||
account: '',
|
||||
captcha: '',
|
||||
password: '',
|
||||
checkPassword: '',
|
||||
})
|
||||
const registerRules = ref<FormRules>({
|
||||
account: [
|
||||
{ required: true, trigger: 'blur', message: '请输入用户名' },
|
||||
],
|
||||
captcha: [
|
||||
{ required: true, trigger: 'blur', message: '请输入验证码' },
|
||||
],
|
||||
password: [
|
||||
{ required: true, trigger: 'blur', message: '请输入密码' },
|
||||
{ min: 6, max: 18, trigger: 'blur', message: '密码长度为6到18位' },
|
||||
],
|
||||
checkPassword: [
|
||||
{ required: true, trigger: 'blur', message: '请再次输入密码' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value !== registerForm.value.password) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
}
|
||||
else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
function handleRegister() {
|
||||
ElMessage({
|
||||
message: '注册模块仅提供界面演示,无实际功能,需开发者自行扩展',
|
||||
type: 'warning',
|
||||
})
|
||||
registerFormRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
// 这里编写业务代码
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重置密码
|
||||
const resetFormRef = ref<FormInstance>()
|
||||
const resetForm = ref({
|
||||
account: localStorage.login_account,
|
||||
captcha: '',
|
||||
newPassword: '',
|
||||
})
|
||||
const resetRules = ref<FormRules>({
|
||||
account: [
|
||||
{ required: true, trigger: 'blur', message: '请输入用户名' },
|
||||
],
|
||||
captcha: [
|
||||
{ required: true, trigger: 'blur', message: '请输入验证码' },
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, trigger: 'blur', message: '请输入新密码' },
|
||||
{ min: 6, max: 18, trigger: 'blur', message: '密码长度为6到18位' },
|
||||
],
|
||||
})
|
||||
function handleReset() {
|
||||
ElMessage({
|
||||
message: '重置密码仅提供界面演示,无实际功能,需开发者自行扩展',
|
||||
type: 'info',
|
||||
})
|
||||
resetFormRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
// 这里编写业务代码
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function testAccount(account: string) {
|
||||
loginForm.value.account = account
|
||||
loginForm.value.password = '123456'
|
||||
handleLogin()
|
||||
}
|
||||
const account = ref<string>()
|
||||
// 表单类型
|
||||
const formType = ref<'login' | 'register' | 'resetPassword'>('login')
|
||||
const formRef = ref()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="bg-banner" />
|
||||
<div id="login-box">
|
||||
<div class="login-banner">
|
||||
<img :src="logo" class="logo">
|
||||
<img :src="banner" class="banner">
|
||||
</div>
|
||||
<ElForm v-show="formType === 'login'" ref="loginFormRef" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<div class="mb-6">
|
||||
<HTabList
|
||||
v-model="loginType" :options="[
|
||||
{ label: '账号密码登录', value: 'default' },
|
||||
{ label: '扫码登录', value: 'qrcode' },
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="loginType === 'default'">
|
||||
<div class="title-container">
|
||||
<h3 class="title">
|
||||
欢迎来到 {{ title }} ! 👋🏻
|
||||
</h3>
|
||||
</div>
|
||||
<div>
|
||||
<ElFormItem prop="account">
|
||||
<ElInput v-model="loginForm.account" placeholder="用户名" type="text" tabindex="1">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:user-3-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="password">
|
||||
<ElInput v-model="loginForm.password" type="password" placeholder="密码" tabindex="2" autocomplete="new-password" show-password @keyup.enter="handleLogin">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:lock-2-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
<div class="flex-bar">
|
||||
<ElCheckbox v-model="loginForm.remember">
|
||||
记住我
|
||||
</ElCheckbox>
|
||||
<ElLink type="primary" :underline="false" @click="formType = 'reset'">
|
||||
忘记密码了?
|
||||
</ElLink>
|
||||
</div>
|
||||
<ElButton :loading="loading" type="primary" size="large" style="width: 100%;" @click.prevent="handleLogin">
|
||||
登录
|
||||
</ElButton>
|
||||
<div class="sub-link">
|
||||
<span class="text">还没有帐号?</span>
|
||||
<ElLink type="primary" :underline="false" @click="formType = 'register'">
|
||||
创建新帐号
|
||||
</ElLink>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="loginType === 'qrcode'">
|
||||
<div class="flex flex-col items-center">
|
||||
<el-image src="https://s2.loli.net/2024/04/26/GsahtuIZ9XOg5jr.png" class="h-[250px] w-[250px]" />
|
||||
<div class="mt-2 op-50">
|
||||
请使用微信扫码登录
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div style="margin-top: 20px; margin-bottom: -20px; text-align: center;">
|
||||
<ElDivider>演示账号一键登录</ElDivider>
|
||||
<ElButton type="primary" size="small" plain @click="testAccount('admin')">
|
||||
admin
|
||||
</ElButton>
|
||||
<ElButton size="small" plain @click="testAccount('test')">
|
||||
test
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElForm>
|
||||
<ElForm v-show="formType === 'register'" ref="registerFormRef" :model="registerForm" :rules="registerRules" class="login-form" auto-complete="on">
|
||||
<div class="title-container">
|
||||
<h3 class="title">
|
||||
探索从这里开始! 🚀
|
||||
</h3>
|
||||
</div>
|
||||
<div>
|
||||
<ElFormItem prop="account">
|
||||
<ElInput v-model="registerForm.account" placeholder="用户名" tabindex="1">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:user-3-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="captcha">
|
||||
<ElInput v-model="registerForm.captcha" placeholder="验证码" tabindex="2">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ic:baseline-verified-user" />
|
||||
</template>
|
||||
<template #append>
|
||||
<ElButton>发送验证码</ElButton>
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="password">
|
||||
<ElInput v-model="registerForm.password" type="password" placeholder="密码" tabindex="3" show-password>
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:lock-2-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="checkPassword">
|
||||
<ElInput v-model="registerForm.checkPassword" type="password" placeholder="确认密码" tabindex="4" show-password>
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:lock-2-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
<ElButton :loading="loading" type="primary" size="large" style="width: 100%; margin-top: 20px;" @click.prevent="handleRegister">
|
||||
注册
|
||||
</ElButton>
|
||||
<div class="sub-link">
|
||||
<span class="text">已经有帐号?</span>
|
||||
<ElLink type="primary" :underline="false" @click="formType = 'login'">
|
||||
去登录
|
||||
</ElLink>
|
||||
</div>
|
||||
</ElForm>
|
||||
<ElForm v-show="formType === 'reset'" ref="resetFormRef" :model="resetForm" :rules="resetRules" class="login-form">
|
||||
<div class="title-container">
|
||||
<h3 class="title">
|
||||
忘记密码了? 🔒
|
||||
</h3>
|
||||
</div>
|
||||
<div>
|
||||
<ElFormItem prop="account">
|
||||
<ElInput v-model="resetForm.account" placeholder="用户名" type="text" tabindex="1">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:user-3-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="captcha">
|
||||
<ElInput v-model="resetForm.captcha" placeholder="验证码" type="text" tabindex="2">
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ic:baseline-verified-user" />
|
||||
</template>
|
||||
<template #append>
|
||||
<ElButton>发送验证码</ElButton>
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="newPassword">
|
||||
<ElInput v-model="resetForm.newPassword" type="password" placeholder="新密码" tabindex="3" show-password>
|
||||
<template #prefix>
|
||||
<SvgIcon name="i-ri:lock-2-fill" />
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
<ElButton :loading="loading" type="primary" size="large" style="width: 100%; margin-top: 20px;" @click.prevent="handleReset">
|
||||
确认
|
||||
</ElButton>
|
||||
<div class="sub-link">
|
||||
<ElLink type="primary" :underline="false" @click="formType = 'login'">
|
||||
去登录
|
||||
</ElLink>
|
||||
</div>
|
||||
</ElForm>
|
||||
<div class="bg-banner" />
|
||||
<div class="login-box">
|
||||
<div class="login-banner">
|
||||
<img src="@/assets/images/logo.png" class="absolute left-4 top-4 h-30px rounded ring ring-stone-2 dark-ring-stone-8">
|
||||
<img src="@/assets/images/login-banner.png" class="banner">
|
||||
</div>
|
||||
<div class="login-form flex-col-center">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<LoginForm
|
||||
v-if="formType === 'login'"
|
||||
ref="formRef"
|
||||
:account
|
||||
@on-login="router.push(redirect)"
|
||||
@on-register="(account) => { formType = 'register'; account = account }"
|
||||
@on-reset-password="(account) => { formType = 'resetPassword'; account = account }"
|
||||
/>
|
||||
<RegisterForm
|
||||
v-else-if="formType === 'register'"
|
||||
ref="formRef"
|
||||
:account
|
||||
@on-register="(account) => { formType = 'login'; account = account }"
|
||||
@on-login="formType = 'login'"
|
||||
/>
|
||||
<ResetPasswordForm
|
||||
v-else-if="formType === 'resetPassword'"
|
||||
ref="formRef"
|
||||
:account
|
||||
@on-reset-password="(account) => { formType = 'login'; account = account }"
|
||||
@on-login="formType = 'login'"
|
||||
/>
|
||||
</Transition>
|
||||
</div>
|
||||
<Copyright />
|
||||
</div>
|
||||
<Copyright />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bg-banner {
|
||||
position: fixed;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(circle at center, var(--g-container-bg), var(--g-bg));
|
||||
}
|
||||
|
||||
[data-mode="mobile"] {
|
||||
#login-box {
|
||||
.login-box {
|
||||
position: relative;
|
||||
top: inherit;
|
||||
left: inherit;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
transform: translateX(0) translateY(0);
|
||||
|
||||
.login-banner {
|
||||
width: 100%;
|
||||
@ -355,8 +95,6 @@ function testAccount(account: string) {
|
||||
|
||||
.login-form {
|
||||
width: 100%;
|
||||
min-height: auto;
|
||||
padding: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,29 +103,19 @@ function testAccount(account: string) {
|
||||
}
|
||||
}
|
||||
|
||||
:deep(input[type="password"]::-ms-reveal) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bg-banner {
|
||||
position: fixed;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(circle at center, var(--g-container-bg), var(--g-bg));
|
||||
}
|
||||
|
||||
#login-box {
|
||||
.login-box {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
background-color: var(--g-container-bg);
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--el-box-shadow);
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
|
||||
[data-mode="pc"] & {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--el-box-shadow);
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
|
||||
.login-banner {
|
||||
position: relative;
|
||||
@ -400,88 +128,11 @@ function testAccount(account: string) {
|
||||
|
||||
@include position-center(y);
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
height: 30px;
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 500px;
|
||||
min-height: 500px;
|
||||
padding: 50px;
|
||||
overflow: hidden;
|
||||
|
||||
.title-container {
|
||||
position: relative;
|
||||
|
||||
.title {
|
||||
margin: 0 auto 30px;
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 24px;
|
||||
|
||||
:deep(.el-input) {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
line-height: inherit;
|
||||
|
||||
input {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.el-input__prefix,
|
||||
.el-input__suffix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.el-input__prefix {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.el-input__suffix {
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-divider__text) {
|
||||
background-color: var(--g-container-bg);
|
||||
}
|
||||
|
||||
.flex-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sub-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-secondary);
|
||||
|
||||
.text {
|
||||
margin-right: 10px;
|
||||
}
|
||||
transition: height 0.15s ease;
|
||||
}
|
||||
}
|
||||
|
||||
@ -492,4 +143,14 @@ function testAccount(account: string) {
|
||||
padding: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user