注册页面、修改密码页面。

main
ZhouXY108 2022-12-30 17:43:10 +08:00
parent 7841e706f6
commit f467cc9203
11 changed files with 333 additions and 390 deletions

View File

@ -0,0 +1,21 @@
<template>
<div class="page">
<RouterView />
</div>
</template>
<script lang="ts" setup></script>
<style scoped>
.page {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
color: #666666;
}
</style>

View File

@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from "vue-router";
import MainPage from "../layouts/MainPage/MainPage.vue"; import MainPage from "../layouts/MainPage/MainPage.vue";
import Login from "../views/Login/Login.vue"; import Login from "../views/Login/Login.vue";
import Page404 from "../views/404/index.vue"; import Page404 from "../views/404/index.vue";
import UserLayout from "@/layouts/UserLayout/UserLayout.vue";
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -17,11 +18,27 @@ const router = createRouter({
}, },
], ],
}, },
{
path: "",
component: UserLayout,
children: [
{ {
path: "/login", path: "/login",
name: "login", name: "login",
component: Login, component: Login,
}, },
{
path: "/register",
name: "register",
component: () => import("@/views/Register/Register.vue"),
},
{
path: "/changePassword",
name: "changePassword",
component: () => import("@/views/ChangePassword/ChangePassword.vue"),
},
],
},
], ],
}); });

View File

@ -0,0 +1,6 @@
export interface ChangePasswordByOTPCommand {
principal: string;
otp: string;
password: string;
reenteredPassword: string;
}

View File

@ -0,0 +1,7 @@
export interface RegisterCommand {
principal: string;
username: string;
code: string;
password: string;
reenteredPassword: string;
}

View File

@ -1,28 +1,149 @@
<template> <template>
<div class="login-page"> <div class="form-container">
<LoginByPassword v-if="loginByPassword" @toLoginByOTP="loginByPassword = false" /> <div class="title">
<LoginByOTP v-else @toLoginByPassword="loginByPassword = true" /> <span style="color: #6777ef">Plusone&nbsp;</span>
<span style="font-weight: 300">Admin</span>
</div>
<n-form
ref="formRef"
:model="model"
:rules="rules"
:show-feedback="false"
:show-label="false"
size="large"
>
<n-form-item path="principal">
<n-input
placeholder="邮箱地址 / 手机号"
v-model:value="model.principal"
@keydown.enter.prevent
/>
</n-form-item>
<n-grid y-gap="24" :cols="24">
<n-gi :span="15">
<n-form-item path="otp">
<n-input placeholder="验证码" v-model:value="model.otp" />
</n-form-item>
</n-gi>
<n-gi :span="9">
<div style="display: flex; justify-content: flex-end">
<n-button
class="txt-btn"
@click="handleBtnGetOTPClick"
size="large"
style="width 100%"
>
获取验证码
</n-button>
</div>
</n-gi>
</n-grid>
<n-form-item path="password">
<n-input placeholder="密码" v-model:value="model.password" type="password" />
</n-form-item>
<n-form-item path="reenteredPassword">
<n-input placeholder="重复密码" v-model:value="model.reenteredPassword" type="password" />
</n-form-item>
<n-button
type="primary"
size="large"
style="width: 100%; margin-top: 8px; margin-bottom: 8px;"
@click="handleBtnChangePasswordClick"
>
修改密码
</n-button>
</n-form>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import LoginByPassword from "./components/ChangePasswordByPassword.vue"; import type { FormInst, FormItemRule, FormRules } from "naive-ui";
import LoginByOTP from "./components/ChangePasswordByOTP.vue"; import type { ChangePasswordByOTPCommand } from "@/type/commands/ChangePasswordCommand";
const loginByPassword = ref(true); const formRef = ref<FormInst | null>(null);
const model = ref<ChangePasswordByOTPCommand>({
principal: "",
otp: "",
password: "",
reenteredPassword: "",
});
const rules: FormRules = {
principal: [
{
required: true,
validator(rule: FormItemRule, value: string) {
if (!value) {
return new Error("需要年龄");
} else if (!/^\d*$/.test(value)) {
return new Error("年龄应该为整数");
} else if (Number(value) < 18) {
return new Error("年龄应该超过十八岁");
}
return true;
},
trigger: ["input", "blur"],
},
],
otp: [
{
required: true,
message: "请输入密码",
},
],
};
function handleBtnChangePasswordClick(e: MouseEvent) {
e.preventDefault();
formRef.value?.validate((errors) => {
if (!errors) {
window.$message.success("验证成功");
} else {
console.log(errors);
window.$message.error("验证失败");
}
});
}
function handleBtnGetOTPClick() {
console.log("handleBtnGetOTPClick");
}
</script> </script>
<style scoped> <style scoped>
.login-page { .form-container {
position: absolute; width: 320px;
top: 0; background-color: #f3f3f3;
right: 0; padding: 32px 40px;
bottom: 0; border-radius: 6px;
left: 0; border: solid 1px #e4e4e4;
display: flex; box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.2);
align-items: center; border-top: solid 4px #6777ef;
justify-content: center; }
color: #666666;
.title {
font-size: 30px;
text-align: center;
margin-bottom: 16px;
}
.n-form-item {
margin-bottom: 8px;
}
.txt-btn {
line-height: 40px;
font-size: 15px;
color: #6777ef;
}
.txt-btn:hover {
color: rgba(103, 119, 239, 0.8);
} }
</style> </style>

View File

@ -1,179 +0,0 @@
<template>
<div class="login-form-container">
<div class="title">
<span style="color: #6777ef">Plusone&nbsp;</span>
<span style="font-weight: 300">Admin</span>
</div>
<n-form
ref="formRef"
:model="model"
:rules="rules"
:show-feedback="false"
:show-label="false"
size="large"
>
<n-form-item path="principal">
<n-input
placeholder="邮箱地址 / 手机号"
v-model:value="model.principal"
@keydown.enter.prevent
/>
</n-form-item>
<n-grid y-gap="24" :cols="24">
<n-gi :span="15">
<n-form-item path="password">
<n-input
placeholder="验证码"
v-model:value="model.otp"
@input="handlePasswordInput"
@keydown.enter.prevent
/>
</n-form-item>
</n-gi>
<n-gi :span="9">
<div style="display: flex; justify-content: flex-end">
<n-button
class="txt-btn"
@click="handleBtnGetOTPClick"
size="large"
style="width 100%"
>
获取验证码
</n-button>
</div>
</n-gi>
</n-grid>
<n-grid y-gap="24" :cols="2">
<n-gi>
<n-form-item path="rememberMe">
<n-checkbox v-model:checked="model.rememberMe" size="large">
<span style="color: #808080">保持登录状态</span>
</n-checkbox>
</n-form-item>
</n-gi>
<n-gi>
<div style="display: flex; justify-content: flex-end">
<n-button class="txt-btn" text @click="$emit('toLoginByPassword')">
密码登录
</n-button>
</div>
</n-gi>
</n-grid>
<n-button
type="primary"
size="large"
style="width: 100%; margin-top: 8px"
@click="handleBtnLoginClick"
>
登录
</n-button>
<div style="display: flex; justify-content: flex-end; margin-top: 8px">
<n-button class="txt-btn" text style="margin-left: 16px"> 忘记密码 </n-button>
<n-button class="txt-btn" text style="margin-left: 16px"> 免费注册 </n-button>
</div>
</n-form>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import type { FormInst, FormItemInst, FormItemRule, FormRules } from "naive-ui";
interface LoginByPasswordCommand {
principal: string;
otp: string;
rememberMe: boolean;
}
const formRef = ref<FormInst | null>(null);
const rPasswordFormItemRef = ref<FormItemInst | null>(null);
const model = ref<LoginByPasswordCommand>({
principal: "",
otp: "",
rememberMe: false,
});
const rules: FormRules = {
principal: [
{
required: true,
validator(rule: FormItemRule, value: string) {
if (!value) {
return new Error("需要年龄");
} else if (!/^\d*$/.test(value)) {
return new Error("年龄应该为整数");
} else if (Number(value) < 18) {
return new Error("年龄应该超过十八岁");
}
return true;
},
trigger: ["input", "blur"],
},
],
otp: [
{
required: true,
message: "请输入密码",
},
],
};
function handlePasswordInput() {
if (model.value.rememberMe) {
rPasswordFormItemRef.value?.validate({ trigger: "password-input" });
}
}
function handleBtnLoginClick(e: MouseEvent) {
e.preventDefault();
formRef.value?.validate((errors) => {
if (!errors) {
window.$message.success("验证成功");
} else {
console.log(errors);
window.$message.error("验证失败");
}
});
}
function handleBtnGetOTPClick() {
console.log("handleBtnGetOTPClick");
}
</script>
<style scoped>
.login-form-container {
width: 320px;
background-color: #f3f3f3;
padding: 32px 40px;
border-radius: 6px;
border: solid 1px #e4e4e4;
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.2);
border-top: solid 4px #6777ef;
}
.title {
font-size: 30px;
text-align: center;
margin-bottom: 16px;
}
.n-form-item {
margin-bottom: 8px;
}
.txt-btn {
line-height: 40px;
font-size: 15px;
color: #6777ef;
}
.txt-btn:hover {
color: rgba(103, 119, 239, 0.8);
}
</style>

View File

@ -1,159 +0,0 @@
<template>
<div class="login-form-container">
<div class="title">
<span style="color: #6777ef">Plusone&nbsp;</span>
<span style="font-weight: 300">Admin</span>
</div>
<n-form
ref="formRef"
:model="model"
:rules="rules"
:show-feedback="false"
:show-label="false"
size="large"
>
<n-form-item path="principal">
<n-input
placeholder="用户名 / 邮箱地址 / 手机号"
v-model:value="model.principal"
@keydown.enter.prevent
/>
</n-form-item>
<n-form-item path="password">
<n-input
placeholder="密码"
v-model:value="model.password"
type="password"
@input="handlePasswordInput"
@keydown.enter.prevent
/>
</n-form-item>
<n-grid y-gap="24" :cols="2">
<n-gi>
<n-form-item path="rememberMe">
<n-checkbox v-model:checked="model.rememberMe" size="large">
<span style="color: #808080">保持登录状态</span>
</n-checkbox>
</n-form-item>
</n-gi>
<n-gi>
<div style="display: flex; justify-content: flex-end">
<n-button class="txt-btn" text @click="$emit('toLoginByOTP')"> </n-button>
</div>
</n-gi>
</n-grid>
<n-button
type="primary"
size="large"
style="width: 100%; margin-top: 8px"
@click="handleBtnLoginClick"
>
登录
</n-button>
<div style="display: flex; justify-content: flex-end; margin-top: 8px">
<n-button class="txt-btn" text style="margin-left: 16px"> 忘记密码 </n-button>
<n-button class="txt-btn" text style="margin-left: 16px"> 免费注册 </n-button>
</div>
</n-form>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import type { FormInst, FormItemInst, FormItemRule, FormRules } from "naive-ui";
import type { PrincipalType } from "@/type/PrincipalType";
interface LoginByPasswordCommand {
principal: string;
password: string;
rememberMe: boolean;
principalType?: PrincipalType;
}
const formRef = ref<FormInst | null>(null);
const rPasswordFormItemRef = ref<FormItemInst | null>(null);
const model = ref<LoginByPasswordCommand>({
principal: "",
password: "",
rememberMe: false,
});
const rules: FormRules = {
principal: [
{
required: true,
validator(rule: FormItemRule, value: string) {
if (!value) {
return new Error("需要年龄");
} else if (!/^\d*$/.test(value)) {
return new Error("年龄应该为整数");
} else if (Number(value) < 18) {
return new Error("年龄应该超过十八岁");
}
return true;
},
trigger: ["input", "blur"],
},
],
password: [
{
required: true,
message: "请输入密码",
},
],
};
function handlePasswordInput() {
if (model.value.rememberMe) {
rPasswordFormItemRef.value?.validate({ trigger: "password-input" });
}
}
function handleBtnLoginClick(e: MouseEvent) {
e.preventDefault();
formRef.value?.validate((errors) => {
if (!errors) {
window.$message.success("验证成功");
} else {
console.log(errors);
window.$message.error("验证失败");
}
});
}
</script>
<style scoped>
.login-form-container {
width: 320px;
background-color: #f3f3f3;
padding: 32px 40px;
border-radius: 6px;
border: solid 1px #e4e4e4;
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.2);
border-top: solid 4px #6777ef;
}
.title {
font-size: 30px;
text-align: center;
margin-bottom: 16px;
}
.n-form-item {
margin-bottom: 8px;
}
.txt-btn {
line-height: 40px;
font-size: 15px;
color: #6777ef;
}
.txt-btn:hover {
color: rgba(103, 119, 239, 0.8);
}
</style>

View File

@ -1,8 +1,6 @@
<template> <template>
<div class="login-page">
<LoginByPassword v-if="loginByPassword" @toLoginByOTP="loginByPassword = false" /> <LoginByPassword v-if="loginByPassword" @toLoginByOTP="loginByPassword = false" />
<LoginByOTP v-else @toLoginByPassword="loginByPassword = true" /> <LoginByOTP v-else @toLoginByPassword="loginByPassword = true" />
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -12,17 +10,3 @@ import LoginByOTP from "./components/LoginByOTP.vue";
const loginByPassword = ref(true); const loginByPassword = ref(true);
</script> </script>
<style scoped>
.login-page {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
color: #666666;
}
</style>

View File

@ -21,9 +21,9 @@
/> />
</n-form-item> </n-form-item>
<n-grid y-gap="24" :cols="24"> <n-grid x-gap="8" :cols="24">
<n-gi :span="15"> <n-gi :span="15">
<n-form-item path="password"> <n-form-item path="otp">
<n-input <n-input
placeholder="验证码" placeholder="验证码"
v-model:value="model.otp" v-model:value="model.otp"
@ -73,8 +73,9 @@
</n-button> </n-button>
<div style="display: flex; justify-content: flex-end; margin-top: 8px"> <div style="display: flex; justify-content: flex-end; margin-top: 8px">
<n-button class="txt-btn" text style="margin-left: 16px"> 忘记密码 </n-button> <n-button class="txt-btn" text style="margin-left: 16px" @click="$router.push('/register')">
<n-button class="txt-btn" text style="margin-left: 16px"> 免费注册 </n-button> 免费注册
</n-button>
</div> </div>
</n-form> </n-form>
</div> </div>

View File

@ -18,7 +18,6 @@
placeholder="密码" placeholder="密码"
v-model:value="model.password" v-model:value="model.password"
type="password" type="password"
@input="handlePasswordInput"
@keydown.enter.prevent @keydown.enter.prevent
/> />
</n-form-item> </n-form-item>
@ -48,8 +47,17 @@
</n-button> </n-button>
<div style="display: flex; justify-content: flex-end; margin-top: 8px"> <div style="display: flex; justify-content: flex-end; margin-top: 8px">
<n-button class="txt-btn" text style="margin-left: 16px"> 忘记密码 </n-button> <n-button
<n-button class="txt-btn" text style="margin-left: 16px"> 免费注册 </n-button> class="txt-btn"
text
style="margin-left: 16px"
@click="$router.push('/changePassword')"
>
忘记密码
</n-button>
<n-button class="txt-btn" text style="margin-left: 16px" @click="$router.push('/register')">
免费注册
</n-button>
</div> </div>
</n-form> </n-form>
</div> </div>
@ -57,13 +65,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import type { FormInst, FormItemInst } from "naive-ui"; import type { FormInst } from "naive-ui";
import { PrincipalType, regexConsts } from "@/type/PrincipalType"; import { PrincipalType, regexConsts } from "@/type/PrincipalType";
import type { LoginByPasswordCommand } from "@/type/commands/LoginCommands"; import type { LoginByPasswordCommand } from "@/type/commands/LoginCommands";
import { loginByPassword } from "@/api/account"; import { loginByPassword } from "@/api/account";
const formRef = ref<FormInst | null>(null); const formRef = ref<FormInst | null>(null);
const rPasswordFormItemRef = ref<FormItemInst | null>(null);
const model = ref<LoginByPasswordCommand>({ const model = ref<LoginByPasswordCommand>({
principal: "", principal: "",
password: "", password: "",
@ -71,12 +78,6 @@ const model = ref<LoginByPasswordCommand>({
principalType: null, principalType: null,
}); });
function handlePasswordInput() {
if (model.value.rememberMe) {
rPasswordFormItemRef.value?.validate({ trigger: "password-input" });
}
}
function handleBtnLoginClick(e: MouseEvent) { function handleBtnLoginClick(e: MouseEvent) {
e.preventDefault(); e.preventDefault();
const principal = model.value.principal; const principal = model.value.principal;

View File

@ -0,0 +1,123 @@
<template>
<div class="form-container">
<div class="title">
<span style="color: #6777ef">Plusone&nbsp;</span>
<span style="font-weight: 300">Admin</span>
</div>
<n-form ref="formRef" :model="model" :show-feedback="false" :show-label="false" size="large">
<n-form-item path="principal">
<n-input placeholder="邮箱地址 / 手机号" v-model:value="model.principal" />
</n-form-item>
<n-form-item path="username">
<n-input placeholder="用户名" v-model:value="model.username" />
</n-form-item>
<n-grid y-gap="24" :cols="24">
<n-gi :span="15">
<n-form-item path="code">
<n-input placeholder="验证码" v-model:value="model.code" />
</n-form-item>
</n-gi>
<n-gi :span="9">
<div style="display: flex; justify-content: flex-end">
<n-button
class="txt-btn"
@click="handleBtnGetOTPClick"
size="large"
style="width 100%"
>
获取验证码
</n-button>
</div>
</n-gi>
</n-grid>
<n-form-item path="password">
<n-input placeholder="密码" v-model:value="model.password" type="password" />
</n-form-item>
<n-form-item path="reenteredPassword">
<n-input placeholder="重复密码" v-model:value="model.reenteredPassword" type="password" />
</n-form-item>
<n-grid :cols="2" style="margin-top: 16px">
<n-gi>
<n-button type="primary" style="width: 72px" @click="handleBtnChangePasswordClick">
注册
</n-button>
</n-gi>
<n-gi>
<div style="display: flex; justify-content: flex-end">
<span style="line-height: 32px">已有帐号</span>
<n-button
class="txt-btn"
style="line-height: 32px"
text
@click="$router.push('/login')"
>
前往登录
</n-button>
</div>
</n-gi>
</n-grid>
</n-form>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import type { FormInst } from "naive-ui";
import type { RegisterCommand } from "@/type/commands/RegisterCommand";
const formRef = ref<FormInst | null>(null);
const model = ref<RegisterCommand>({
principal: "",
username: "",
code: "",
password: "",
reenteredPassword: "",
});
function handleBtnChangePasswordClick(e: MouseEvent) {
e.preventDefault();
console.log(formRef.value);
}
function handleBtnGetOTPClick() {
console.log("handleBtnGetOTPClick");
}
</script>
<style scoped>
.form-container {
width: 320px;
background-color: #f3f3f3;
padding: 32px 40px;
border-radius: 6px;
border: solid 1px #e4e4e4;
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.2);
border-top: solid 4px #6777ef;
}
.title {
font-size: 30px;
text-align: center;
margin-bottom: 16px;
}
.n-form-item {
margin-bottom: 8px;
}
.txt-btn {
line-height: 40px;
font-size: 15px;
color: #6777ef;
}
.txt-btn:hover {
color: rgba(103, 119, 239, 0.8);
}
</style>