效果图
- 使用了动态组件做组件切换
- 使用vue内置的transition组件实现过渡效果,蒙层 和 弹框 都使用了transition,并嵌套
- vuex + computed计算属性,实现数据 和 方法共享,让其它组件也能够控制到登录弹框
- 蒙层使用了固定定位
- 未实现:鼠标滚轮在蒙层上面滚动时,蒙层下面的页面也会滚动。但是,我不想使用body,overflow:hidden,这种方式,这样右侧的滚动条就会消失掉,还没找到方法。(已解决:参考-蒙层禁止页面滚动)
- 未实现:页面会立即回到顶部(已解决:参考-蒙层禁止页面滚动))
- 与原博客中有差异,我的是弹出来后,下面的页面静止不动了,仍然有滚动条,但滚动条里的滑块没了
- 有其他定位相关的问题,影响了其它页面布局(不要用!!!),只是做个效果
代码
App.vue
- 直接将Login这个组件,放入App.vue中,而Login组件使用固定定位,覆盖掉整个视口,然后,使用flex布局,让弹窗内容在中间。
<template>
<div id="app">
<router-view/>
<Login/>
</div>
</template>
<script>
import Login from '@/components/model/Login.vue';
export default {
name:'App',
data() {
return {
}
},
components: {
Login
}
}
</script>
store/index.js
- loginShow控制最外面的蒙层的v-show(即display:none)
- componentName控制动态组件切换的组件名
- loginShow 和 componentName都存到vuex中,提供方法入口给外界,去修改这两个值
- 这两个值,会被Login组件使用,控制展示蒙层 和 切换的动态组件
import Vue from 'vue'
import Vuex from 'vuex'
import router from '@/router'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
loginShow: false,
componentName: '',
scrollTop1:0
},
getters: {
},
mutations: {
updateComponentName(state, val) {
state.componentName = val
},
updateLoginShow(state, val) {
state.loginShow = val
},
showComponentName(state,componentName){
/* 记录此时的滚动距离 */
let documentTop = document.scrollingElement.scrollTop;
state.scrollTop1 = documentTop
/* 调用其它mutation中的方法 */
this.commit('updateComponentName',componentName)
this.commit('updateLoginShow',true)
/* 保持滚动距离 */
this._vm.$nextTick(()=>{
document.body.style.position = "fixed"
document.body.style.top = -state.scrollTop1 + "px";
})
}
},
actions: {
},
modules: {
}
})
Login.vue
- 给 蒙层(使用了v-show,支持过渡) 和 动态组件(动态组件支持过渡) 添加过渡样式
- 使用 计算属性 配合使用 vuex
- 点击蒙层时,调用vuex提供的方法,修改store中的值,然后,计算属性接收到修改的值,从而更新渲染(动态组件消失,蒙层隐藏)
<style lang="scss">
.body-mask {
position: fixed;
background-color: rgba(0, 0, 0, .3);
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.mask-content {
max-height: 90%;
width: 460px;
overflow-y: auto;
overflow-x: hidden;
background-color: #fff;
border-radius: 6px;
padding: 40px 30px 10px;
}
/* 蒙层的动画 */
.mask-enter,
.mask-leave-to {
opacity: 0;
}
.mask-enter-to,
.mask-leave {
opacity: 1;
}
.mask-enter-active,
.mask-leave-active {
transition: all 0.2s;
}
/* 蒙层中的组件切换的动画 */
.v-enter,
.v-leave-to {
transform: scale(0.8);
opacity: 0;
}
.v-enter-to,
.v-leave {
transform: scale(1);
opacity: 1;
}
.v-enter-active,
.v-leave-active {
transition: all 0.2s;
}
</style>
<template>
<transition name="mask">
<div class="body-mask" v-show="loginShow" ref="bodyMask">
<transition appear mode="out-in">
<component :is="componentName" />
</transition>
</div>
</transition>
</template>
<script>
import LoginBox from '@/components/model/LoginBox'
import RegisterBox from '@/components/model/RegisterBox'
import ForgetPwd from '@/components/model/ForgetPwd'
export default {
name: 'Login',
data() {
return {
}
},
computed: {
componentName: {
get() {
return this.$store.state.componentName
}
},
loginShow: {
get() {
return this.$store.state.loginShow
}
}
},
mounted() {
this.$refs['bodyMask'].onclick = (e) => {
if (e.target === this.$refs['bodyMask']) {
this.$store.commit('updateComponentName', '')
this.$store.commit('updateLoginShow', false)
/* 恢复之前的滚动距离 */
this.$nextTick(()=>{
document.body.style.position = "static";
document.body.style.top = "auto";
/* 始终保留右边的滚动条 */
document.body.style.overflowY = 'scroll'
document.scrollingElement.scrollTop = this.$store.state.scrollTop1;
})
}
}
},
methods: {
},
components: {
LoginBox,
RegisterBox,
ForgetPwd
}
}
</script>
LoginBox.vue
直接调用vuex暴露出来的方法,来修改store中的值,Login组件接收到修改的值,从而更新渲染(动态组件更新)
<style lang="scss" scoped>
.mask-content {
max-height: 90%;
width: 460px;
overflow-y: auto;
overflow-x: hidden;
background-color: #fff;
border-radius: 6px;
// box-sizing: border-box;
padding: 40px 30px 10px;
}
input {
border: none;
outline: none;
border-bottom: 1px solid #9e9e9e;
padding: 5px 0;
width: 100%;
font-size: 16px;
&::placeholder {
color: #9f9f9f;
}
}
.form-item {
margin-bottom: 40px;
&:last-child {
margin-bottom: 15px;
}
}
.form-item-name {
color: #696969;
font-size: 13px;
margin-bottom: 2px;
}
button {
border:none;
cursor: pointer;
background-color: #2196f3;
color: #fff;
width: 100%;
font-size: 16px;
padding: 8px;
border-radius: 4px;
box-shadow: 0px 1px 2px 1px rgb(0 0 0 / 23%);
}
.entrance-tip {
font-size: 15px;
display: flex;
align-items: center;
justify-content: space-between;
}
.entrance-social {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 20px;
&::before {
content: '';
display: block;
position: absolute;
height: 1px;
background-color: #d8d8d8;
width: 60%;
}
span {
position: absolute;
font-size: 13px;
color: #b5b5b5;
z-index: 10;
background-color: #fff;
padding: 0 10px;
}
}
.social-login {
display: flex;
justify-content: center;
}
span.iconfont {
cursor: pointer;
margin: 0 8px;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
color: #fff;
font-size: 18px;
&.icon-weibo {
background-color: #e05244;
}
&.icon-qq {
background-color: #00aaee;
}
}
</style>
<template>
<div class="mask-content ">
<div class="form-item">
<div class="form-item-name">邮箱号</div>
<input placeholder="请输入您的邮箱"/>
</div>
<div class="form-item">
<div class="form-item-name">密码</div>
<input placeholder="请输入您的密码"/>
</div>
<div class="form-item">
<button>登录</button>
</div>
<div class="form-item">
<div class="entrance-tip">
<a href="#" @click="changeComponent('RegisterBox')">立即注册</a>
<a href="#" @click="changeComponent('ForgetPwd')">忘记密码?</a>
</div>
</div>
<div class="form-item">
<div class="entrance-social">
<span>社交账号登录</span>
</div>
</div>
<div class="form-item">
<div class="social-login">
<span class="iconfont icon-weibo"></span>
<span class="iconfont icon-qq"></span>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'LoginBox',
methods: {
changeComponent(name) {
this.$store.commit('updateComponentName',name)
}
},
components: {
}
}
</script>
RegisterBox.vue
直接调用vuex暴露出来的方法,来修改store中的值,Login组件接收到修改的值,从而更新渲染(动态组件更新)
<style lang="scss">
.mask-content {
max-height: 90%;
width: 460px;
overflow-y: auto;
overflow-x: hidden;
background-color: #fff;
border-radius: 6px;
// box-sizing: border-box;
padding: 40px 30px 10px;
}
input {
border: none;
outline: none;
border-bottom: 1px solid #9e9e9e;
padding: 5px 0;
width: 100%;
font-size: 16px;
&::placeholder {
color: #9f9f9f;
}
}
.form-item {
margin-bottom: 40px;
&:last-child {
margin-bottom: 15px;
}
}
.form-item-name {
color: #696969;
font-size: 13px;
margin-bottom: 2px;
}
button {
border: none;
cursor: pointer;
background-color: #f44336;
color: #fff;
width: 100%;
font-size: 16px;
padding: 8px;
border-radius: 4px;
box-shadow: 0px 1px 2px 1px rgb(0 0 0 / 23%);
}
.entrance-tip {
font-size: 15px;
align-items: center;
font-size: 14px;
color: #333333;
span {
margin-right: 5px;
}
}
.entrance-social {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 20px;
&::before {
content: '';
display: block;
position: absolute;
height: 1px;
background-color: #d8d8d8;
width: 60%;
}
span {
position: absolute;
font-size: 13px;
color: #b5b5b5;
z-index: 10;
background-color: #fff;
padding: 0 10px;
}
}
.social-login {
display: flex;
justify-content: center;
}
span.iconfont {
cursor: pointer;
margin: 0 8px;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
color: #fff;
font-size: 18px;
&.icon-weibo {
background-color: #e05244;
}
&.icon-qq {
background-color: #00aaee;
}
}
.check-code {
display: flex;
a {
word-break: keep-all;
padding: 5px 10px;
color: #bdbdbd;
/* padding-left: 10px; */
font-size: 13px;
}
}
</style>
<template>
<div class="mask-content">
<div class="form-item">
<div class="form-item-name">邮箱号</div>
<input placeholder="请输入您的邮箱" />
</div>
<div class="form-item">
<div class="form-item-name">验证码</div>
<div class="check-code">
<input placeholder="请输入位数验证码" />
<a href="#">发送</a>
</div>
</div>
<div class="form-item">
<div class="form-item-name">密码</div>
<input placeholder="请输入您的密码" />
</div>
<div class="form-item">
<button>注册</button>
</div>
<div class="form-item">
<div class="entrance-tip">
<span>已有账号?</span>
<a href="#" @click="changeComponent('LoginBox')">登录</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RegisterBox',
methods: {
changeComponent(name) {
this.$store.commit('updateComponentName',name)
}
},
components: {
}
}
</script>
ForgetPwd.vue
直接调用vuex暴露出来的方法,来修改store中的值,Login组件接收到修改的值,从而更新渲染(动态组件更新)
<style lang="scss">
.mask-content {
max-height: 90%;
width: 460px;
overflow-y: auto;
overflow-x: hidden;
background-color: #fff;
border-radius: 6px;
// box-sizing: border-box;
padding: 40px 30px 10px;
}
input {
border: none;
outline: none;
border-bottom: 1px solid #9e9e9e;
padding: 5px 0;
width: 100%;
font-size: 16px;
&::placeholder {
color: #9f9f9f;
}
}
.form-item {
margin-bottom: 40px;
&:last-child {
margin-bottom: 15px;
}
}
.form-item-name {
color: #696969;
font-size: 13px;
margin-bottom: 2px;
}
button {
border: none;
cursor: pointer;
background-color: #4caf50;
color: #fff;
width: 100%;
font-size: 16px;
padding: 8px;
border-radius: 4px;
box-shadow: 0px 1px 2px 1px rgb(0 0 0 / 23%);
}
.entrance-tip {
font-size: 15px;
align-items: center;
font-size: 14px;
color: #333333;
span {
margin-right: 5px;
}
}
.entrance-social {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 20px;
&::before {
content: '';
display: block;
position: absolute;
height: 1px;
background-color: #d8d8d8;
width: 60%;
}
span {
position: absolute;
font-size: 13px;
color: #b5b5b5;
z-index: 10;
background-color: #fff;
padding: 0 10px;
}
}
.social-login {
display: flex;
justify-content: center;
}
span.iconfont {
cursor: pointer;
margin: 0 8px;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
color: #fff;
font-size: 18px;
&.icon-weibo {
background-color: #e05244;
}
&.icon-qq {
background-color: #00aaee;
}
}
.check-code {
display: flex;
a {
word-break: keep-all;
padding: 5px 10px;
color: #bdbdbd;
/* padding-left: 10px; */
font-size: 13px;
}
}
</style>
<template>
<div class="mask-content ">
<div class="form-item">
<div class="form-item-name">邮箱号</div>
<input placeholder="请输入您的邮箱" />
</div>
<div class="form-item">
<div class="form-item-name">验证码</div>
<div class="check-code">
<input placeholder="请输入位数验证码" />
<a href="#">发送</a>
</div>
</div>
<div class="form-item">
<div class="form-item-name">密码</div>
<input placeholder="请输入您的密码" />
</div>
<div class="form-item">
<button>确定</button>
</div>
<div class="form-item">
<div class="entrance-tip">
<span>已有账号?</span>
<a href="#" @click="changeComponent('LoginBox')">登录</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ForgetPwd',
methods: {
changeComponent(name) {
this.$store.commit('updateComponentName',name)
}
},
components: {
}
}
</script>
NavBar.vue
直接调用vuex暴露出来的方法,来修改store中的值,Login组件接收到修改的值,从而更新渲染(动态组件更新,并展示蒙层)
<li>
<a class="menu-a" href="#">
<i class="iconfont icon-denglu"></i>
<span @click="$store.commit('showComponentName', 'LoginBox')">登录</span>
</a>
</li>