跳至主要內容

若依vue中字典Dict插件的研究

程序员诚哥大约 5 分钟若依若依前端

涉及的知识点

插件与组件的区别

Vue组件(component)用来构成你的App的业务模块,它的目标是App.vue。
Vue插件(plugin) 用来增强你的技术栈的功能模块, 它的目标是Vue本身。(插件是对Vue的功能的增强和补充)

插件的作用

插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
添加全局方法或者属性。如: vue-custom-element
添加全局资源:指令/过滤器/过渡等。如 vue-touch
通过全局混入来添加一些组件选项。如 vue-router
添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router

this.$options

上图中dicts的值可以通过this.$options.dicts拿到

options的五类属性
数据: data, props, propsData, computed, Watch;

DOM: el, template, render, renderError;

声明周期钩子: beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、activated、deactivated、beforeDestroy、destroyed、errorCaptured;

资源: directives、filters、components;

组合: parent、mixins、extends、provide、inject;

minx

具体自己百度

开发插件

Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或 property
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }
  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })
  // 3. 注入组件选项
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })
  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

若依vue dict插件

插件安装

导入并使用插件DataDict,插件内部会调用的到 request(dictMeta)方法用请求管理后台

import Vue from 'vue'
import DataDict from '@/utils/dict'
import { getDicts as getDicts } from '@/api/system/dict/data'

function install() {
  Vue.use(DataDict, {
    metas: {
      '*': {
        labelField: 'dictLabel',
        valueField: 'dictValue',
        // 插件内部会调用的到 request(dictMeta)方法用请求管理后台
        request(dictMeta) {
        // 请求后端接口:根据字典类型查询字典数据信息
          return getDicts(dictMeta.type).then(res => res.data)
        },
      },
    },
  })
}

export default {
  install,
}

在main.js里面会执行:

DictData.install()

插件定义

this.dict.init(this.$options.dicts)

是比较关键的步骤:this.dict.init(this.$options.dicts) 将vue页面上定义的dicts数组传进去,组装数据,请求后端,获取对应字典数据

options和this.$options的差别

options

定义插件的时候传入的值


this.$options

上图中dicts的值可以通过this.$options.dicts拿到。

具体代码,加了一些注释,方便理解

dict/index.js

import Dict from './Dict'
import { mergeOptions } from './DictOptions'

/**
 * 
 * @param {*} Vue 
 * @param {*} options , 插件安装时传入的对象,我们项目里面是:
 * metas: {
     '*': {
       labelField: 'dictLabel',
       valueField: 'dictValue',
       request(dictMeta) {
       //调用后端接口获取数据
         return getDicts(dictMeta.type).then(res => res.data)
       },
     },
   },
 */
export default function(Vue, options) {
  //mergeOptions(options)会将DictOptions的options合并
  mergeOptions(options)
  Vue.mixin({
    data() {
      //如果vue没有定义dixts即this.$options.dicts为空,则返回{}相当于不混入 
      if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) {
        return {}
      }
      //DataDict初始化字典数据
      const dict = new Dict()
      dict.owner = this
      return {
        //混入dict,使得页面内能用dict对象
        dict
      }
    },
    created() {
      if (!(this.dict instanceof Dict)) {
        return
      }
      options.onCreated && options.onCreated(this.dict)
      //执行Dict的init方法,将vue页面上定义的dicts数组传进去,组装数据,请求后端,获取对应字典数据
      this.dict.init(this.$options.dicts).then(() => {
        options.onReady && options.onReady(this.dict)
        this.$nextTick(() => {
        //定义了一些回调,如果有需要,可以在页面上加onDictReady 方法或者dictReady事件,可以对dict进行一些后续处理
          this.$emit('dictReady', this.dict)
          if (this.$options.methods && this.$options.methods.onDictReady instanceof Function) {
            this.$options.methods.onDictReady.call(this, this.dict)
          }
        })
      })
    },
  })
}

dict/DictOptions.js

import { mergeRecursive } from "@/utils/ruoyi";
import dictConverter from './DictConverter'

export const options = {
  metas: {
    '*': {
      /**
       * 字典请求,方法签名为function(dictMeta: DictMeta): Promise
       */
      request: (dictMeta) => {
        console.log(`load dict ${dictMeta.type}`)
        return Promise.resolve([])
      },
      /**
       * 字典响应数据转换器,方法签名为function(response: Object, dictMeta: DictMeta): DictData
       */
      responseConverter,
      labelField: 'label',
      valueField: 'value',
    },
  },
  /**
   * 默认标签字段
   */
  DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'],
  /**
   * 默认值字段
   */
  DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'],
}

/**
 * 映射字典
 * @param {Object} response 字典数据
 * @param {DictMeta} dictMeta 字典元数据
 * @returns {DictData}
 */
function responseConverter(response, dictMeta) {
  const dicts = response.content instanceof Array ? response.content : response
  if (dicts === undefined) {
    console.warn(`no dict data of "${dictMeta.type}" found in the response`)
    return []
  }
  return dicts.map(d => dictConverter(d, dictMeta))
}

export function mergeOptions(src) {
  mergeRecursive(options, src)
}

export default options

Dict.js

比较关键的是loadDict(dict, dictMeta)来查询后端,dictMeta.request(dictMeta)用的比较灵活,dictMeta是插件安装的时候写的option

import Vue from 'vue'
import { mergeRecursive } from "@/utils/ruoyi";
import DictMeta from './DictMeta'
import DictData from './DictData'

const DEFAULT_DICT_OPTIONS = {
  types: [],
}

/**
 * @classdesc 字典
 * @property {Object} label 标签对象,内部属性名为字典类型名称
 * @property {Object} dict 字段数组,内部属性名为字典类型名称
 * @property {Array.<DictMeta>} _dictMetas 字典元数据数组
 */
export default class Dict {
  constructor() {
    this.owner = null
    this.label = {}
    this.type = {}
  }
//这里接收的是:this.$options.dicts
  init(options) {
    if (options instanceof Array) {
      options = { types: options }
    }
    const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options)
    if (opts.types === undefined) {
      throw new Error('need dict types')
    }
    const ps = []
    this._dictMetas = opts.types.map(t => DictMeta.parse(t))
    this._dictMetas.forEach(dictMeta => {
      const type = dictMeta.type
      Vue.set(this.label, type, {})
      Vue.set(this.type, type, [])
      if (dictMeta.lazy) {
        return
      }
      ps.push(loadDict(this, dictMeta))
    })
    return Promise.all(ps)
  }

  /**
   * 重新加载字典
   * @param {String} type 字典类型
   */
  reloadDict(type) {
    const dictMeta = this._dictMetas.find(e => e.type === type)
    if (dictMeta === undefined) {
      return Promise.reject(`the dict meta of ${type} was not found`)
    }
    return loadDict(this, dictMeta)
  }
}

/**
 * 加载字典
 * @param {Dict} dict 字典
 * @param {DictMeta} dictMeta 字典元数据
 * @returns {Promise}
 */
function loadDict(dict, dictMeta) {
//比较灵活,dictMeta是插件定义的时候传入的option
  return dictMeta.request(dictMeta)
    .then(response => {
      const type = dictMeta.type
      let dicts = dictMeta.responseConverter(response, dictMeta)
      if (!(dicts instanceof Array)) {
        console.error('the return of responseConverter must be Array.<DictData>')
        dicts = []
      } else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) {
        console.error('the type of elements in dicts must be DictData')
        dicts = []
      }
      dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts)
      dicts.forEach(d => {
        Vue.set(dict.label[type], d.value, d.label)
      })
      return dicts
    })
}

后续

如果有需要,可以在页面上加onDictReady 方法或者dictReady事件,可以对dict进行一些后续处理

上次编辑于:
贡献者: zccbbg