> 文章列表 > 模仿风宇博客登录蒙层弹窗(vuex+computed实现)

模仿风宇博客登录蒙层弹窗(vuex+computed实现)

模仿风宇博客登录蒙层弹窗(vuex+computed实现)

效果图

在这里插入图片描述

  • 使用了动态组件做组件切换
  • 使用vue内置的transition组件实现过渡效果,蒙层 和 弹框 都使用了transition,并嵌套
  • vuex + computed计算属性,实现数据 和 方法共享,让其它组件也能够控制到登录弹框
  • 蒙层使用了固定定位
  • 未实现:鼠标滚轮在蒙层上面滚动时,蒙层下面的页面也会滚动。但是,我不想使用body,overflow:hidden,这种方式,这样右侧的滚动条就会消失掉,还没找到方法。(已解决:参考-蒙层禁止页面滚动)
  • 未实现:页面会立即回到顶部(已解决:参考-蒙层禁止页面滚动))
  • 与原博客中有差异,我的是弹出来后,下面的页面静止不动了,仍然有滚动条,但滚动条里的滑块没了
  • 有其他定位相关的问题,影响了其它页面布局
    • 试了下,把这个问题解决了,只要给body加上width:100%,就解决了全部的问题(1. 有轻微的抖动,2. 其它页面的布局被搞乱了)

    • 最终的效果图如下:

      在这里插入图片描述

代码

App.vue

  • 直接将Login这个组件,放入App.vue中,而Login组件使用固定定位,覆盖掉整个视口,然后,使用flex布局,让弹窗内容在中间。
<template><div id="app"><router-view/><Login/><Search/></div>
</template><script>
import Login from '@/components/model/Login.vue';
import Search from '@/components/model/Search.vue';export default {name:'App',data() {return {}},components: {Login,Search}}
</script><style lang="scss">@import url(//at.alicdn.com/t/c/font_4004562_pw9b9ygx6n.css);body {margin: 0;/* 不管什么情况,都特么给老子出现滚动条 */overflow-y: scroll !important;overflow-x: hidden;/* 这个100%很重要 */width: 100%;/* 背景渐变 */background: linear-gradient(90deg, rgba(247, 149, 51, .1), rgba(243, 112, 85, .1) 15%, rgba(239, 78, 123, .1) 30%, rgba(161, 102, 171, .1) 44%, rgba(80, 115, 184, .1) 58%, rgba(16, 152, 173, .1) 72%, rgba(7, 179, 155, .1) 86%, rgba(109, 186, 130, .1));}.shadow {box-shadow: 0 4px 8px 6px rgba(7, 17, 27, .06);}/* 整个滚动条 */
::-webkit-scrollbar {width: 10px;height: 10px;
}/* 滚动条上的滚动滑块,参考: 滚动条样式修改->https://blog.csdn.net/coder_jxd/article/details/124213962 */
::-webkit-scrollbar-thumb {background-color: #49b1f5;/* 关键代码 */background-image: -webkit-linear-gradient(45deg,rgba(255, 255, 255, 0.4) 25%,transparent 25%,transparent 50%,rgba(255, 255, 255, 0.4) 50%,rgba(255, 255, 255, 0.4) 75%,transparent 75%,transparent);border-radius: 32px;
}/* 滚动条样式,参考: */
/* 滚动条轨道 */
::-webkit-scrollbar-track {background-color: #dbeffd;border-radius: 32px;
}* {box-sizing: border-box;
}a {text-decoration: none;color: inherit;
}
</style>

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>