一、字典的定义及工具函数的封装
- 定义对象拷贝函数(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 } - 定义基本字典数据类(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 } }
二、对字典的参数的处理
- 处理字典的参数(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) }
- 判断字典数据是否正确,正确执行转换操作(字典响应数据转换器),返回包含label, value, raw的DictData实例数组
- 加工每条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实例,DictOptions是 DictOptions.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) }
- 确定字典指定字段
- 处理字典元数据(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插件
- 字典的完整处理(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) }
- 加载字典
- 定义字典插件(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)
- 注册字典插件(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中调用即可)
附:_getDicts_接口(接口封装详见若依后端)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: (dictType) => request({ url: '/admin/omhsysdictdata/type', method: 'get', data: { dictType } }),
- 查询查询字典中是否有对应的值
- 字典组件封装(封装形式多样,这里只是举例说明)
<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> - 在页面中使用
- 在页面中使用
/**先在对应页面添加对应的字典类型 *与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>