vue登录页作为modal全局使用
背景
vue项目,有一个登录页面作为单独页面来使用。想要将其改造成 一个modal,然后全局可调用。类似于 mint-ui 的 toast组件这样。
要用到的位置主要是:vue页面内、接口请求的响应数据处理方法内(环境是 无法拿到当前作用域 this)
实现原理
将登陆页面modal封装成一个 插件。
主要代码
1、登录页组件:
正常书写,主要是提供一个组件广播事件 this.$emit('on-logged')。在 登录完成后 通知调用者,做一些操作。
<template> <mt-popup v-model="showValue" :modal="showModal" popup-transition="popup-fade" class="mint-popup-modal"> <div class="zyby-container box-sizing-border"> <mt-header> <by-back slot="left" :specialBack="_hide"></by-back> </mt-header> <div class="welcome-block">欢迎登录八音!</div> <div class="input-block"> <mt-field class="none-border" placeholder="手机号" type="tel" v-model="pm.telephone" :attr="{ maxlength: 11 }" ></mt-field> <mt-field placeholder="验证码" type="tel" v-model="pm.code" :attr="{ maxlength: 6 }"> <count-down ref="countDown" class="count-down" :getcodebefore="_getCode" :model="codeData.model" > <span slot="secUnit">s</span> </count-down> </mt-field> </div> <!--<div class="interpretation">完成“八音App”登录注册以便后续奖励发放</div>--> <mt-button class="login-block" :class="classObject" type="primary" size="large" :disabled="!loginFlag" @click="_goLogin" >登录</mt-button> <div class="tip-block">未注册用户登录代表已同意注册</div> </div> </mt-popup> </template> <script> import { CountDown } from "vue-zyby-ui"; import { addMinutes, format} from 'date-fns' import { sendCode, login } from "@/api/index.js"; import { cellPhoneValidate, numberValidate } from "@/libs/stringUtil.js"; import { LOCALDATA } from '@/libs/constans.js' import storage from '@/libs/storage.js' import { commonValidate } from '@/libs/validateUtil.js' import config from '@/config' export default { name: "Login", components: { CountDown }, data() { return { codeData: { showCountDown: false, model: "validateCode" }, pm: { telephone: null, code: null, }, showValue: false, showModal: false }; }, mounted() { document.querySelectorAll("input").forEach(item => { item.addEventListener("blur", function() { window.scroll(0, 0); }); }); }, computed: { classObject: function() { return { "login-block-active": this.pm.telephone && this.pm.code }; }, loginFlag: function() { return this.pm.telephone && this.pm.code; } }, props: { value: { type: Boolean, default: false }, }, watch: { value (val) { this.showValue = val }, showValue (val) { /*this.$emit('input', val) if (val) { if (this.showInput) { this.msg = '' setTimeout(() => { if (this.$refs.input) { this.setInputFocus() } }, 300) } this.$emit('on-show') // emit just after msg is cleared }*/ } }, methods: { _getCode() { if (this._validatePhone()) { this.$refs.countDown.setEnd(format(addMinutes(new Date(), 1), 'YYYY/MM/DD HH:mm:ss')) return sendCode(this.pm.telephone); } }, _validatePhone() { const validateData = [ { '请输入联系方式': !this.pm.telephone }, { '请输入正确的手机号': !cellPhoneValidate(this.pm.telephone) } ] return commonValidate(validateData) }, _goLogin() { if (!this._validatePhone()) { return false } const validateData = [ { '请输入验证码': !this.pm.code }, { '请输入正确的验证码': (this.pm.code.length != 6 || !numberValidate(this.pm.code)) }, ] if (commonValidate(validateData)) { login(this.pm).then(res => { // 设置登录标志 let user = { telephone: this.pm.telephone, token: res.token, uuid: config.uuid, id: res.id } storage.setToken(user) this.showValue = false this.$emit('on-logged') }) } }, _hide () { this.showValue = false } }, }; </script> <style scoped lang="less" rel="stylesheet/less"> @import "../assets/css/function.less"; @no-left-padding: { padding-left: 0; }; @border-bottom-style: { border-bottom: 1px solid #d3dfef; /*no*/ }; .input-text(@opacity: 0.45) { font-family: PingFangSC-Regular; font-size: 28px; color: rgba(75, 84, 97, @opacity); } .interpretation{ width: 100%; background-color: #EEFAF8; height: 52px; line-height: 52px; font-size: 24px; color: #009F8A; text-align: center; margin-top: 30px; margin-bottom: 180px; } .mint-header { @no-left-padding(); } .zyby-container { padding-top: 88px; position: relative; height: 100%; background-color: #fff; } .welcome-block { font-family: PingFangSC-Semibold; font-size: 50px; color: rgba(62, 74, 89, 0.75); letter-spacing: 2px; /*no*/ text-align: left; line-height: 50px; margin-top: 56px; } .input-block { margin-top: 108px; /*margin-bottom: 211px;*/ @border-bottom-style(); /deep/ .mint-cell-wrapper { padding-left: 0; } .none-border /deep/ .mint-cell-wrapper { background: none; } .mint-field:first-child { @border-bottom-style(); } /deep/ input::-webkit-input-placeholder { .input-text(); } .count-down { .input-text(); border-left: 1px solid rgba(75, 84, 97, 0.45); /*no*/ margin-left: 40px; padding-left: 10px; /deep/ .code-text { color: rgba(75, 84, 97, 0.45); } /deep/ .code-count-down { color: rgba(75, 84, 97, 0.8); } } } .tip-block { opacity: 0.75; font-family: PingFangSC-Regular; font-size: 22px; color: rgba(75, 84, 97, 0.45); line-height: 22px; margin-top: 100px; text-align: center; } .login-block { margin-top: 220px; background-color: rgba(0, 0, 0, 0.1); height: 100px; border-radius: 12px; /*no*/ font-family: PingFangSC-Medium; font-size: 32px; color: #ffffff; letter-spacing: 3px; /*no*/ text-align: center; line-height: 32px; } .login-block-active { background-color: #3ad29f; } </style>
2、设置为插件的入口
将组件打包成为一个插件,在此处进行。
关键操作有:
1、暴露install方法:Vue.use用到
2、设置为全局属性,可基于vue实例 直接调用:vue.mixin
3、声明全局 对于登录modal的调用方法:主要有 show、hide、isVisible
4、事件接收:在登录组件里 ,完成登录后我们会发出一个广播事件,也是在这里面 来监听该事件,$vm.$on('on-logged'
// plugins/login/index.js
import LoginComponent from '@/components/Login' import { mergeOptions } from '@/libs/plugin_helper' let $vm const plugin = { install (vue, options = {}) { const Login = vue.extend(LoginComponent) if (!$vm) { $vm = new Login({ el: document.createElement('div') }) document.body.appendChild($vm.$el) } const login = { show (options) { if (typeof options === 'object') { mergeOptions($vm, options) } if (typeof options === 'object' && (options.onShow || options.onHide)) { options.onShow && options.onShow() } this.$watcher && this.$watcher() this.$watcher = $vm.$watch('showValue', (val) => { if (!val && options && options.onHide) { options.onHide() } }) $vm.$off('on-logged') $vm.$on('on-logged', msg => { options && options.onLogged && options.onLogged(msg) }) $vm.showValue = true }, /*setInputValue (val) { vue.nextTick(() => { setTimeout(() => { $vm.setInputValue(val) }, 10) }) },*/ hide () { $vm.showValue = false }, isVisible () { return $vm.showValue } } // all Vux's plugins are included in this.$vux /* if (!vue.$vux) { vue.$vux = { login } } else { vue.$vux.login = login } */ vue.mixin({ created: function () { this.$login = login } }) } } export default plugin export const install = plugin.install
// plugin_helper.js
import objectAssign from 'object-assign' const mergeOptions = function ($vm, options) { const defaults = {} for (let i in $vm.$options.props) { if (i !== 'value') { defaults[i] = $vm.$options.props[i].default } } const _options = objectAssign({}, defaults, options) for (let i in _options) { $vm[i] = _options[i] } } export { mergeOptions }
使用
1、安装登录modal插件.
// main.js
import loginPlugin from '@/plugins/Login' // 注意 由于登录组件里,用到了别的一些组件,所以 use 应该在 最下面,即保证 用到的组件已存在 Vue.use(loginPlugin)
2、使用
普通页面里:
this.$login.show({ onLogged: () => this.reload() // 登录完成后的回调方法 })
其他位置(无法容易拿到 vue this):
window.$vue = new Vue({ // 将vue实例 绑定到 window el: '#app', router, components: { App }, template: '<App/>' }) window.$vue.$login.show(); // 基于window 来使用