若依字典的封装

847 阅读3分钟

一、字典的定义及工具函数的封装

  1. 定义对象拷贝函数(mergeRecursive.js文件)
    export default function mergeRecursive(source, target) {
      for (var p in target) {
          try {
            if (target[p].constructor === Object) {
              source[p] = mergeRecursive(source[p], target[p])
            } else {
              source[p] = target[p]
            }
          } catch (e) {
            source[p] = target[p]
          }
      }
      return source
    }
    
  2. 定义基本字典数据类(DictData.js文件)
    /**
    * @classdesc 字典数据
    * @property {String} label 标签
    * @property {*} value 标签
    * @property {Object} raw 原始数据
    */
    export default class DictData {
      constructor(label, value, raw) {
        this.label = label
        this.value = value
        this.raw = raw
      }
    }
    

二、对字典的参数的处理

  1. 处理字典的参数(DictOptions.js文件),dictConverter函数即下文中 DictConverter.js 导出的函数
    • 判断字典数据是否正确,正确执行转换操作(字典响应数据转换器),返回包含label, value, raw的DictData实例数组
      /**
       * 映射字典
       * @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 []
      	}
      	// dictConverter 返回包含label, value, raw的DictData实例
      	return dicts.map((d) => dictConverter(d, dictMeta))
      }
      
    • 封装字典字典数据参数
      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']
      }
      
    • 将传入的参数中属性更新或克隆到options上。
      export function mergeOptions(src) {
      	mergeRecursive(options, src)
      }
      
  2. 加工每条DictData实例(DictConverter.js文件)
    • 确定字典指定字段
      /**
      * 确定字典字段
      * @param {DictData} dict
      * @param  {...String} fields
      */
      function determineDictField(dict, ...fields) {
      	return fields.find((f) => Object.prototype.hasOwnProperty.call(dict, f))
      }
      
    • 生成包含label, value, raw的DictData实例,DictOptionsDictOptions.js 导出的对象
      export default function(dict, dictMeta) {
      	// 确定label字段
      	const label = determineDictField(
      		dict,
      		dictMeta.labelField,
      		...DictOptions.DEFAULT_LABEL_FIELDS
      	)
      	// 确定value字段
      	const value = determineDictField(
      		dict,
      		dictMeta.valueField,
      		...DictOptions.DEFAULT_VALUE_FIELDS
      	)
      	// 创建一个包含label, value, raw的DictData实例
      	return new DictData(dict[label], dict[value], dict)
      }
      
  3. 处理字典元数据(DictMeta.js文件)
    • 声明字典元数据类
      /**
      * @classdesc 字典元数据
      * @property {String} type 类型
      * @property {Function} request 请求
      * @property {String} label 标签字段
      * @property {String} value 值字段
      */
      export default class DictMeta {
      	constructor(options) {
      		this.type = options.type
      		this.request = options.request
      		this.responseConverter = options.responseConverter
      		this.labelField = options.labelField
      		this.valueField = options.valueField
      		this.lazy = options.lazy === true
      	}
      }
      
    • 给DictMeta添加返回DictMeta的静态方法解析字典元数据
      /**
      * 解析字典元数据
      * @param {Object} options
      * @returns {DictMeta}
      */
      DictMeta.parse = function(options) {
      	let opts = null
      	if (typeof options === 'string') {
      		opts = DictOptions.metas[options] || {}
      		opts.type = options
      	} else if (typeof options === 'object') {
      		opts = options
      	}
      	opts = Object.assign({}, DictOptions.metas['*'], opts)
      	opts = mergeRecursive(DictOptions.metas['*'], opts)
      	return new DictMeta(opts)
      }
      

三、字典类定义及封装成Vue插件

  1. 字典的完整处理(Dict.js文件)
    • 加载字典
      /**
      * 加载字典
      * @param {Dict} dict 字典
      * @param {DictMeta} dictMeta 字典元数据
      * @returns {Promise}
      */
      function loadDict(dict, dictMeta) {
      	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
      	})
      }
      
    • 定义默认的字典参数
      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 = {}
      	}
      
      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)
      }
      
  2. 定义字典插件(index.js文件)
    export default function(Vue, options) {
    mergeOptions(options)
    Vue.mixin({
    	data() {
    	  if (
    	    this.$options === undefined ||
    	    this.$options.dicts === undefined ||
    	    this.$options.dicts === null
    	  ) {
    	    return {}
    	  }
    	  const dict = new Dict()
    	  dict.owner = this
    	  return {
    	    dict
    	  }
    	},
    	created() {
    	  if (!(this.dict instanceof Dict)) {
    	    return
    	  }
    	  options.onCreated && options.onCreated(this.dict)
    	  this.dict.init(this.$options.dicts).then(() => {
    	    options.onReady && options.onReady(this.dict)
    	    this.$nextTick(() => {
    	      this.$emit('dictReady', this.dict)
    	      if (
    	        this.$options.methods &&
    	        this.$options.methods.onDictReady instanceof Function
    	      ) {
    	        this.$options.methods.onDictReady.call(this, this.dict)
    	      }
    	    })
    	  })
    	}
     })
    }
    

四、使用字典组件(基于element-ui)

  1. 注册字典插件(register.js文件)
    • 查询查询字典中是否有对应的值
      function searchDictByKey(dict, key) {
        if (key == null && key == '') {
          return null
        }
        try {
          for (let i = 0; i < dict.length; i++) {
            if (dict[i].key == key) {
              return dict[i].value
            }
          }
        } catch (e) {
          return null
        }
      }
      
    • 导出封装函数(直接在main.js中调用即可)
      function install() {
        Vue.use(DataDict, {
          metas: {
            '*': {
              labelField: 'dictLabel',
              valueField: 'dictValue',
              request(dictMeta) {
                const storeDict = searchDictByKey(store.getters.dict, dictMeta.type)
                if (storeDict) {
                  return new Promise((resolve) => {
                    resolve(storeDict)
                  })
                } else {
                  return new Promise((resolve, reject) => {
                    API.default
                      .getDicts(dictMeta.type)
                      .then((res) => {
                        store.dispatch('dict/setDict', {
                          key: dictMeta.type,
                          value: res.data
                        })
                        resolve(res.data)
                      })
                      .catch((error) => {
                        reject(error)
                      })
                  })
                }
              }
            }
          }
        })
      }
      
      export default {
        install
      }
      
      附:_getDicts_接口(接口封装详见若依后端)
      getDicts: (dictType) =>
      request({
      url: '/admin/omhsysdictdata/type',
      method: 'get',
      data: { dictType }
      }),
      
  2. 字典组件封装(封装形式多样,这里只是举例说明)
    <template>
      <div>
        <template v-for="(item, index) in options">
          <template v-if="values.includes(item.value)">
            <span
              v-if="type==='text'"
              :key="item.value"
              :index="index"
              :class="item.raw.cssClass">
              {{ item.label }}
            </span>
            <el-tag
              v-else
              :text="item.label"
              mode="dark"
              color="#fff"
              :key="item.value"
              :index="index"
              :type="item.raw.listClass == 'primary' ? '' : item.raw.listClass"
              :class="item.raw.cssClass"
              :bg-color="item.raw.cssClass"
              :border-color="item.raw.cssClass">
            </el-tag>
          </template>
        </template>
      </div>
    </template>
    
    <script>
    export default {
      name: 'DictTag',
      props: {
        options: {
          type: Array,
          default: null
        },
        value: [Number, String, Array],
        type: {
          type: String,
          default: null
        }
      },
      computed: {
        values() {
          if (this.value !== null && typeof this.value !== 'undefined') {
            return Array.isArray(this.value) ? this.value : [String(this.value)]
          } else {
            return []
          }
        }
      }
    }
    </script>
    <style scoped>
    .el-tag + .el-tag {
      margin-left: 10px;
    }
    </style>
    
  3. 在页面中使用
    • 在页面中使用
    /**先在对应页面添加对应的字典类型
     *与data,components同级
    */
    dicts: ['in_bill_type', 'bill_approve_status'],
    /**在html中直接渲染,
    *直接调用组件
    */
    <dict-tag
      :options="dict.type.in_bill_type"
      :value="scope.row.billType"
    />
    /**输入字典值
    *通过el-select选择
    */
    <el-select v-model="value">
        <el-option
          v-for="dict in dict.type.in_bill_type"
          :key="dict.value"
          :label="dict.label"
          :value="dict.value"
         />
    </el-select>