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  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 来使用