Skip to content

Commit

Permalink
feat: add login pages
Browse files Browse the repository at this point in the history
  • Loading branch information
kirklin committed Oct 12, 2023
1 parent 9ade818 commit 28ef9c8
Show file tree
Hide file tree
Showing 15 changed files with 381 additions and 8 deletions.
Binary file removed src/assets/avatar.jpg
Binary file not shown.
3 changes: 3 additions & 0 deletions src/assets/icons/login/eye-off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/icons/login/eye-on.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions src/composables/useMobileCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
type FormInstance,
showToast,
} from "vant";
import type { Ref } from "vue";
import { onUnmounted, ref } from "vue";

/**
* Custom Vue Composition API for sending a mobile verification code.
*
* @param mobileRef - Ref for the mobile number to which the code will be sent.
* @param codeType - The type of code (e.g., 'login', 'register', etc.).
* @param sendCodeFn - The function responsible for sending the mobile code.
* @returns An object containing functions and data related to sending mobile codes.
*/
export const useMobileCode = (mobileRef: Ref<string>, codeType = "login", sendCodeFn: (mobile: string, codeType: string) => Promise<void>) => {
// Initialize the countdown timer value and the form reference.
const countdownValue = ref(0);
const formRef = ref<FormInstance>();
let countdownTimer: number;

/**
* Sends a mobile verification code and starts a countdown timer.
*/
const sendMobileCode = async () => {
// Check if the countdown timer is already running.
if (countdownValue.value > 0) {
return;
}

// Validate the mobile number using the form instance (if available).
await formRef.value?.validate("mobile");

// Send the mobile code using the provided function.
await sendCodeFn(toValue(mobileRef), codeType);

// Show a success message.
showToast("验证码发送成功");

// Initialize the countdown timer.
countdownValue.value = 60;

// Start the countdown timer.
if (countdownTimer) {
clearInterval(countdownTimer);
}
countdownTimer = window.setInterval(() => {
countdownValue.value--;
if (countdownValue.value <= 0) {
clearInterval(countdownTimer);
}
}, 1000);
};

// Cleanup the countdown timer when the component is unmounted.
onUnmounted(() => {
clearInterval(countdownTimer);
});

// Return the functions and data.
return { sendMobileCode, countdownValue, formRef };
};
49 changes: 48 additions & 1 deletion src/layouts/Navbar/index.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,56 @@
<script setup lang="ts">
import { useRouter } from "vue-router";
defineOptions({
name: "Navbar",
});
// 1. 通过props来实现标题和右侧文字的设置
const props = defineProps<{
title?: string;
rightText?: string;
back?: () => void;
}>();
const emit = defineEmits<{
(e: "clickRight"): void;
}>();
// 2. 使用emit函数来触发自定义事件(点击右侧文字按钮)
const onClickRight = () => {
emit("clickRight");
};
const router = useRouter();
// 3. 回退,了解 history.state 信息,监听箭头的点击事件按条件进行跳转
const onClickLeft = () => {
if (props.back) {
return props.back();
}
if (history.state?.back) {
router.back();
} else {
router.push("/");
}
};
</script>

<template>
<VanNavBar title="导航栏" fixed />
<!-- 固定定位,左侧箭头,标题,右侧文字 -->
<VanNavBar
fixed
left-arrow
:title="title"
:right-text="rightText"
@click-left="onClickLeft"
@click-right="onClickRight"
/>
</template>

<style lang="scss" scoped>
.van-nav-bar {
&__arrow {
color: var(--bv-text1);
font-size: 18px;
}
&__title {
font-size: 15px;
}
}
</style>
5 changes: 1 addition & 4 deletions src/layouts/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script setup lang="ts">
import Navbar from "./Navbar/index.vue";
import Container from "./Container/index.vue";
defineOptions({
name: "Layout",
Expand All @@ -10,9 +9,7 @@ defineOptions({
<template>
<div class="font-chinese antialiased">
<Navbar />
<Container>
<RouterView />
</Container>
<RouterView />
</div>
</template>

Expand Down
75 changes: 75 additions & 0 deletions src/pages/login/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* 登录模块 */
.login-module {
/* 登录页块 */
&-page {
padding-top: 46px;
}

/* 登录头部块 */
&-header {
display: flex;
padding: 15px 18px;
justify-content: space-between;
align-items: flex-end;
line-height: 1;

.title {
font-weight: normal;
font-size: 24px;
}

.link {
font-size: 15px;
}
}

/* 其他登录选项块 */
&-options {
margin-top: 40px;
padding: 0 30px;

.icon-container {
display: flex;
justify-content: center;

.icon {
width: 36px;
height: 36px;
padding: 4px;
}
}
}
}

/* Van 表单样式 */
.van-form {
padding: 0 14px;

/* 自定义单元格样式 */
.custom-cell {
height: 52px;
line-height: 24px;
padding: 14px 16px;
box-sizing: border-box;
display: flex;
align-items: center;

/* Van 复选框样式 */
.van-checkbox {
a {
color: var(--bv-primary);
padding: 0 5px;
}
}
}

/* 发送按钮样式 */
.send-button {
color: var(--bv-primary);

/* 激活状态样式 */
&.active {
color: var(--bv-primary-active);
}
}
}
109 changes: 109 additions & 0 deletions src/pages/login/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script setup lang="ts">
import { showSuccessToast, showToast } from "vant";
import { ref, shallowReactive, toRef } from "vue";
import { useRouter } from "vue-router";
import LoginEyeOffSvg from "virtual:icons/login/eye-off";
import LoginEyeOnSvg from "virtual:icons/login/eye-on";
import { codeRules, mobileRules, passwordRules } from "~/utils";
import NavBar from "~/layouts/Navbar/index.vue";
const router = useRouter();
// 数据和状态
const loginFormData = shallowReactive({
mobile: "15911112222",
password: "abc123456",
agree: false,
showPassword: false,
});
// 切换密码登录和短信验证码登录
const isPasswordLogin = ref(true);
const smsCode = ref("");
const handleLogin = async () => {
if (!loginFormData.agree) {
return showToast("请勾选协议");
}
showSuccessToast("登录成功");
await router.replace("/home");
};
// 发送短信验证码
const { sendMobileCode, countdownValue, formRef } = useMobileCode(
toRef(loginFormData.mobile),
"login",
async () => {},
);
</script>

<template>
<div class="login-module-page">
<NavBar right-text="注册" @click-right="$router.push('/register')" />
<!-- 头部 -->
<div class="login-module-header">
<h3 class="login-module-header title">
{{ isPasswordLogin ? '密码登录' : '短信验证码登录' }}
</h3>
<a class="login-module-header link" href="javascript:">
<span @click="isPasswordLogin = !isPasswordLogin">
{{ isPasswordLogin ? '短信验证码登录' : '密码登录' }}
</span>
<VanIcon name="arrow" />
</a>
</div>
<!-- 表单 -->
<VanForm ref="formRef" autocomplete="off" @submit="handleLogin">
<VanField
v-model="loginFormData.mobile"
name="mobile"
:rules="mobileRules"
placeholder="请输入手机号"
type="tel"
/>
<VanField
v-if="isPasswordLogin"
v-model="loginFormData.password"
:rules="passwordRules"
placeholder="请输入密码"
:type="loginFormData.showPassword ? 'text' : 'password'"
>
<template #button>
<LoginEyeOnSvg v-if="loginFormData.showPassword" @click="loginFormData.showPassword = !loginFormData.showPassword" />
<LoginEyeOffSvg v-else @click="loginFormData.showPassword = !loginFormData.showPassword" />
</template>
</VanField>
<VanField
v-else
v-model="smsCode"
:rules="codeRules"
placeholder="短信验证码"
>
<template #button>
<span class="send-button" :class="{ active: countdownValue > 0 }" @click="sendMobileCode">
{{ countdownValue > 0 ? `${countdownValue}s后再次发送` : '发送验证码' }}
</span>
</template>
</VanField>
<div class="custom-cell">
<VanCheckbox v-model="loginFormData.agree">
<span>我已同意</span>
<a href="javascript:">用户协议</a>
<span>及</span>
<a href="javascript:">隐私条款</a>
</VanCheckbox>
</div>
<div class="custom-cell">
<VanButton native-type="submit" block round type="primary">
登 录
</VanButton>
</div>
<div class="custom-cell">
<a href="javascript:">忘记密码?</a>
</div>
</VanForm>
</div>
</template>

<style lang="scss" scoped>
@import './index.scss';
</style>
11 changes: 11 additions & 0 deletions src/router/routes/modules/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const login = [{
path: "/login",
name: "Login",
component: () => import("~/pages/login/index.vue"),
meta: {
title: "登录",
},
},
];

export default login;
1 change: 1 addition & 0 deletions src/styles/variables.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
:root {
--bv-primary: #3491FA;
--bv-primary-active: #3491FA50;
--bv-plain: #EAF8F6;
--bv-orange: #FCA21C;
--bv-text1: #121826;
Expand Down
1 change: 1 addition & 0 deletions src/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from "./domUtils";
export * from "./mitt";
export * from "./moduleHelper";
export * from "./router";
export * from "./rules";
export * from "./typeChecks";
export * from "./treeHelper";
export * from "./util";
Expand Down
Loading

0 comments on commit 28ef9c8

Please sign in to comment.