-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
381 additions
and
8 deletions.
There are no files selected for viewing
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.