全局的功能组件——自定义弹窗组件
新建src\components\Toast\Toast.vue
:
<template
><div class="toast">{{message}}</div>
>
</template>
<script>
export default {
name: 'Toast',
props: { message: { type: String } }
}
</script>
<style lang="scss" scoped>
.toast {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%); //垂直水平居中
padding: 0.1rem;
background: rgba(0, 0, 0, 0.35);
border-radius: 0.05rem;
color: #fff;
}
</style>
修改src\views\login\Login.vue
:
<template>
<div class="wrapper">
<img class="wrapper__img" src="/i18n/9_16/img/user.png" />
<div class="wrapper__input">
<input
class="wrapper__input__content"
placeholder="请输入手机号码"
v-model="data.formInfo.username"
/>
</div>
<div class="wrapper__input">
<input
type="password"
class="wrapper__input__content"
placeholder="请输入密码"
v-model="data.formInfo.password"
/>
</div>
<div class="wrapper__login-button" @click="handleLogin">登陆</div>
<div class="wrapper__login__item">
<div class="wrapper__login__item__link" @click="handleRegisterClick">
立即注册
</div>
<p class="wrapper__login__item__cut">|</p>
<div class="wrapper__login__item__password">忘记密码</div>
</div>
<Toast v-if="data.toast.show" :message="data.toast.message" />
</div>
</template>
<script>
import { reactive } from 'vue'
// 路由跳转方法
import { useRouter } from 'vue-router'
import { post } from '@/utils/request'
import Toast from '@/components/Toast/Toast'
export default {
name: 'Login',
components: { Toast },
setup () {
const data = reactive({
formInfo: {
username: '',
password: ''
},
toast: {
show: false,
message: ''
}
})
const toastMsg = message => {
data.toast.show = true
data.toast.message = message
setTimeout(() => {
data.toast.show = false
data.toast.message = ''
}, 2000)
}
// 获取路由实例
const router = useRouter()
// 登录按钮
const handleLogin = async () => {
try {
const resultData = await post('/111/api/user/login', {
username: data.formInfo.username,
password: data.formInfo.password
})
// console.log(result)
if (resultData?.code === 200) {
localStorage.isLogin = true
router.push({ name: 'Home' })
} else {
toastMsg('登陆失败!')
}
} catch (e) {
toastMsg('请求失败!')
}
}
const handleRegisterClick = () => {
router.push({ name: 'Register' })
}
return { data, handleLogin, handleRegisterClick }
}
}
</script>
......
代码拆分
从上面的代码来看 ,实际上来看还是比较难阅读的。
改进src\views\login\Login.vue
:
<template>
<div class="wrapper">
<img class="wrapper__img" src="/i18n/9_16/img/user.png" />
<div class="wrapper__input">
<input
class="wrapper__input__content"
placeholder="请输入手机号码"
v-model="data.formInfo.username"
/>
</div>
<div class="wrapper__input">
<input
type="password"
class="wrapper__input__content"
placeholder="请输入密码"
v-model="data.formInfo.password"
/>
</div>
<div class="wrapper__login-button" @click="handleLogin">登陆</div>
<div class="wrapper__login__item">
<div class="wrapper__login__item__link" @click="handleRegisterClick">
立即注册
</div>
<p class="wrapper__login__item__cut">|</p>
<div class="wrapper__login__item__password">忘记密码</div>
</div>
<Toast v-if="toastData.show" :message="toastData.message" />
</div>
</template>
<script>
import { reactive } from 'vue'
// 路由跳转方法
import { useRouter } from 'vue-router'
import { post } from '@/utils/request'
import Toast from '@/components/Toast/Toast'
/**
* toast相关的逻辑
*/
const useToastEffect = () => {
const toastData = reactive({
show: false,
message: ''
})
const toastMsg = message => {
toastData.show = true
toastData.message = message
setTimeout(() => {
toastData.show = false
toastData.message = ''
}, 2000)
}
return { toastData, toastMsg }
}
export default {
name: 'Login',
components: { Toast },
setup () {
// 获取路由实例
const router = useRouter()
const data = reactive({
formInfo: {
username: '',
password: ''
}
})
const { toastData, toastMsg } = useToastEffect()
/** *************** 主流程中需要做的事情 *******************/
// 登录按钮
const handleLogin = async () => {
try {
const resultData = await post('/111/api/user/login', {
username: data.formInfo.username,
password: data.formInfo.password
})
// console.log(result)
if (resultData?.code === 200) {
localStorage.isLogin = true
router.push({ name: 'Home' })
} else {
toastMsg('登陆失败!')
}
} catch (e) {
toastMsg('请求失败!')
}
}
const handleRegisterClick = () => {
router.push({ name: 'Register' })
}
return { toastData, data, handleLogin, handleRegisterClick }
}
}
</script>
现在我们的toast弹窗专门封装出来的代码其实在每个使用弹窗的地方都需要使用,那么我们可以进一步封装到src\components\Toast\Toast.vue
:
<template
><div class="toast">{{ message }}</div>
>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'Toast',
props: { message: { type: String } }
}
/**
* toast相关的逻辑
*/
export const useToastEffect = () => {
const toastData = reactive({
show: false,
message: ''
})
const toastMsg = (message, time) => {
toastData.show = true
toastData.message = message || ''
setTimeout(() => {
toastData.show = false
toastData.message = ''
}, time || 2000)
}
return { toastData, toastMsg }
}
</script>
<style lang="scss" scoped>
.toast {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%); //垂直水平居中
padding: 0.1rem;
background: rgba(0, 0, 0, 0.35);
border-radius: 0.05rem;
color: #fff;
}
</style>
怎么引入,调整src\views\login\Login.vue
:
<template>
<div class="wrapper">
<img class="wrapper__img" src="/i18n/9_16/img/user.png" />
<div class="wrapper__input">
<input
class="wrapper__input__content"
placeholder="请输入手机号码"
v-model="data.formInfo.username"
/>
</div>
<div class="wrapper__input">
<input
type="password"
class="wrapper__input__content"
placeholder="请输入密码"
v-model="data.formInfo.password"
/>
</div>
<div class="wrapper__login-button" @click="handleLogin">登陆</div>
<div class="wrapper__login__item">
<div class="wrapper__login__item__link" @click="handleRegisterClick">
立即注册
</div>
<p class="wrapper__login__item__cut">|</p>
<div class="wrapper__login__item__password">忘记密码</div>
</div>
<Toast v-if="toastData.show" :message="toastData.message" />
</div>
</template>
<script>
import { reactive } from 'vue'
// 路由跳转方法
import { useRouter } from 'vue-router'
import { post } from '@/utils/request'
import Toast, { useToastEffect } from '@/components/Toast/Toast'
export default {
name: 'Login',
components: { Toast },
setup () {
// 获取路由实例
const router = useRouter()
const data = reactive({
formInfo: {
username: '',
password: ''
}
})
const { toastData, toastMsg } = useToastEffect()
/** *************** 主流程中需要做的事情 *******************/
// 登录按钮
const handleLogin = async () => {
try {
const resultData = await post('/111/api/user/login', {
username: data.formInfo.username,
password: data.formInfo.password
})
// console.log(result)
if (resultData?.code === 200) {
localStorage.isLogin = true
router.push({ name: 'Home' })
} else {
toastMsg('登陆失败!')
}
} catch (e) {
toastMsg('请求失败!')
}
}
const handleRegisterClick = () => {
router.push({ name: 'Register' })
}
return { toastData, data, handleLogin, handleRegisterClick }
}
}
</script>
......
继续简化代码:
src\components\Toast\Toast.vue
:
......
<script>
import { reactive, toRefs } from 'vue'
export default {
name: 'Toast',
props: { message: { type: String } }
}
/**
* toast相关的逻辑
*/
export const useToastEffect = () => {
const toastData = reactive({
show: false,
message: ''
})
const toastMsg = (message, time) => {
toastData.show = true
toastData.message = message || ''
setTimeout(() => {
toastData.show = false
toastData.message = ''
}, time || 2000)
}
const { show, message } = toRefs(toastData)
return { show, message, toastMsg }
}
</script>
......
修改src\views\login\Login.vue
:
<template>
<div class="wrapper">
<img class="wrapper__img" src="/i18n/9_16/img/user.png" />
<div class="wrapper__input">
<input
class="wrapper__input__content"
placeholder="请输入手机号码"
v-model="username"
/>
</div>
<div class="wrapper__input">
<input
type="password"
class="wrapper__input__content"
placeholder="请输入密码"
v-model="password"
/>
</div>
<div class="wrapper__login-button" @click="handleLogin">登陆</div>
<div class="wrapper__login__item">
<div class="wrapper__login__item__link" @click="handleRegisterClick">
立即注册
</div>
<p class="wrapper__login__item__cut">|</p>
<div class="wrapper__login__item__password">忘记密码</div>
</div>
<Toast v-if="show" :message="message" />
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
// 路由跳转方法
import { useRouter } from 'vue-router'
import { post } from '@/utils/request'
import Toast, { useToastEffect } from '@/components/Toast/Toast'
export default {
name: 'Login',
components: { Toast },
setup () {
// 获取路由实例
const router = useRouter()
const data = reactive({
formInfo: {
username: '',
password: ''
}
})
const { show, message, toastMsg } = useToastEffect()
/** *************** 主流程中需要做的事情 *******************/
// 登录按钮
const handleLogin = async () => {
try {
const resultData = await post('/111/api/user/login', {
username: data.formInfo.username,
password: data.formInfo.password
})
// console.log(result)
if (resultData?.code === 200) {
localStorage.isLogin = true
router.push({ name: 'Home' })
} else {
toastMsg('登陆失败!')
}
} catch (e) {
toastMsg('请求失败!')
}
}
const handleRegisterClick = () => {
router.push({ name: 'Register' })
}
const { username, password } = toRefs(data.formInfo)
return { show, message, username, password, handleLogin, handleRegisterClick }
}
}
</script>
......
进一步抽象登录的逻辑:
src\views\login\Login.vue
完整代码如下:
<template>
<div class="wrapper">
<img class="wrapper__img" src="/i18n/9_16/img/user.png" />
<div class="wrapper__input">
<input
class="wrapper__input__content"
placeholder="请输入手机号码"
v-model="username"
/>
</div>
<div class="wrapper__input">
<input
type="password"
class="wrapper__input__content"
placeholder="请输入密码"
v-model="password"
/>
</div>
<div class="wrapper__login-button" @click="handleLogin">登陆</div>
<div class="wrapper__login__item">
<div class="wrapper__login__item__link" @click="handleRegisterClick">
立即注册
</div>
<p class="wrapper__login__item__cut">|</p>
<div class="wrapper__login__item__password">忘记密码</div>
</div>
<Toast v-if="show" :message="message" />
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
// 路由跳转方法
import { useRouter } from 'vue-router'
import { post } from '@/utils/request'
import Toast, { useToastEffect } from '@/components/Toast/Toast'
const useLoginEffect = (toastMsg, router) => {
const data = reactive({
username: '',
password: ''
})
// 登录按钮
const handleLogin = async () => {
try {
const resultData = await post('/111/api/user/login', {
username: data.formInfo.username,
password: data.formInfo.password
})
// console.log(result)
if (resultData?.code === 200) {
localStorage.isLogin = true
router.push({ name: 'Home' })
} else {
toastMsg('登陆失败!')
}
} catch (e) {
toastMsg('请求失败!')
}
}
const { username, password } = toRefs(data)
return { username, password, handleLogin }
}
// 跳转注册
const useRegisterEffect = router => {
const handleRegisterClick = () => {
router.push({ name: 'Register' })
}
return { handleRegisterClick }
}
export default {
name: 'Login',
components: { Toast },
// 从这里可见,setup就是告知代码执行的流程
setup () {
// 获取路由实例
const router = useRouter()
const { show, message, toastMsg } = useToastEffect()
/** *************** 主流程中需要做的事情 *******************/
const { username, password, handleLogin } = useLoginEffect(toastMsg, router)
const { handleRegisterClick } = useRegisterEffect(router)
return {
show,
message,
username,
password,
handleLogin,
handleRegisterClick
}
}
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables';
.wrapper {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
&__img {
display: block;
margin: 0 auto 0.4rem auto;
width: 0.66rem;
height: 0.66rem;
}
&__input {
// box-sizing: border-box;//内部间距
height: 0.48rem;
margin: 0 0.4rem 0.16rem 0.4rem;
background: #f9f9f9;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.06rem;
&__content {
line-height: 0.48rem;
background: none;
border: none;
outline: none;
width: 100%;
font-size: 0.16rem;
color: $centent-notice-fontcolor;
&::placeholder {
color: $centent-notice-fontcolor;
}
}
}
&__login-button {
line-height: 0.48rem;
margin: 0.32rem 0.4rem 0.16rem 0.4rem;
background: #0091ff;
color: #fff;
box-shadow: 0 0.04rem 0.08rem 0 rgba(0, 145, 255, 0.32);
border-radius: 0.04rem;
font-size: 0.16rem;
text-align: center;
}
&__login__item {
text-align: center;
&__link {
display: inline-block;
margin: auto 0.05rem auto 0.05rem;
text-align: center;
font-size: 0.14rem;
color: $centent-notice-fontcolor;
}
&__cut {
display: inline-block;
text-align: center;
font-size: 0.14rem;
margin: auto 0.05rem auto 0.05rem;
}
&__password {
display: inline-block;
text-align: center;
font-size: 0.14rem;
margin: auto 0.05rem auto 0.05rem;
color: $centent-notice-fontcolor;
}
}
}
</style>
注册页面修改
如果是电脑浏览器会自动填充密码。
解决代码:
autocomplete="new-password"
注册页面修改如下:
src\views\register\Register.vue
:
<template>
<div class="wrapper">
<img class="wrapper__img" src="/i18n/9_16/img/user.png" />
<div class="wrapper__input">
<input
class="wrapper__input__content"
placeholder="请输入手机号码"
v-model="username"
/>
</div>
<div class="wrapper__input">
<input
type="password"
class="wrapper__input__content"
placeholder="请输入密码"
v-model="password"
autocomplete="new-password"
/>
</div>
<div class="wrapper__input">
<input
type="password"
class="wrapper__input__content"
placeholder="请再次输入密码"
v-model="againPassword"
autocomplete="new-password"
/>
</div>
<div class="wrapper__register-button" @click="handleRegister">注册</div>
<div class="wrapper__register__item">
<div class="wrapper__register__item__link" @click="handleLoginClick">
已有账号去登陆
</div>
</div>
<Toast v-if="show" :message="message" />
</div>
</template>
<script>
// 路由跳转方法
import { useRouter } from 'vue-router'
import { reactive, toRefs } from 'vue'
import { post } from '@/utils/request'
import Toast, { useToastEffect } from '@/components/Toast/Toast'
// 注册功能的业务逻辑
const useRegisterEffect = (toastMsg, router) => {
const data = reactive({
username: '',
password: '',
againPassword: ''
})
// 登录按钮
const handleRegister = async () => {
try {
const resultData = await post('/api/user/register', {
username: data.username,
password: data.password
})
if (resultData?.code === 200) {
localStorage.isRegister = true
toastMsg('注册成功,请到登录页面登录!')
router.push({ name: 'Login' })
} else {
toastMsg('注册失败!')
}
} catch (e) {
toastMsg('注册请求失败!')
}
}
const { username, password, againPassword } = toRefs(data)
return { username, password, againPassword, handleRegister }
}
// 跳转登录
const useLoginEffect = router => {
const handleLoginClick = () => {
router.push({ name: 'Login' })
}
return { handleLoginClick }
}
export default {
name: 'Register',
components: { Toast },
setup () {
// 获取路由实例
const router = useRouter()
const { show, message, toastMsg } = useToastEffect()
/** *************** 主流程中需要做的事情 *******************/
const {
username,
password,
againPassword,
handleRegister
} = useRegisterEffect(toastMsg, router)
const { handleLoginClick } = useLoginEffect(router)
return {
show,
message,
username,
password,
againPassword,
handleLoginClick,
handleRegister
}
}
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables';
.wrapper {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
&__img {
display: block;
margin: 0 auto 0.4rem auto;
width: 0.66rem;
height: 0.66rem;
}
&__input {
// box-sizing: border-box;//内部间距
height: 0.48rem;
margin: 0 0.4rem 0.16rem 0.4rem;
background: #f9f9f9;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.06rem;
&__content {
line-height: 0.48rem;
background: none;
border: none;
outline: none;
width: 100%;
font-size: 0.16rem;
color: $centent-notice-fontcolor;
&::placeholder {
color: $centent-notice-fontcolor;
}
}
}
&__register-button {
line-height: 0.48rem;
margin: 0.32rem 0.4rem 0.16rem 0.4rem;
background: #0091ff;
color: #fff;
box-shadow: 0 0.04rem 0.08rem 0 rgba(0, 145, 255, 0.32);
border-radius: 0.04rem;
font-size: 0.16rem;
text-align: center;
}
&__register__item {
text-align: center;
&__link {
display: inline-block;
margin: auto 0.05rem auto 0.05rem;
text-align: center;
font-size: 0.14rem;
color: $centent-notice-fontcolor;
}
}
}
</style>