大纲
- 方法1:通过常量和名称对象维护字典项
- 方法2:通过选项维护字典项
- 方法3:通过配置维护字典,推荐✅
前言
在项目中,经常会遇到多处使用相同列表项的情况,并且有时需要将后端返回的选项值转换为名称。为了解决这个问题,我们可以通过在统一的配置文件中定义字典项,然后在各个页面中使用。
我们先来看看在代码中维护字典的两种方法:
方案1:用代码维护字典常量和名称map
// 1️⃣ 维护字典 src/consts/dicts
/** 性别常量值 */
export const GENDER = {
male: 1, // 男
female: 2, // 女
other: 3, // 其他
}
/** 根据值获取名称map */
export const genderNameMap = {
[GENDER.male]: '男',
[GENDER.female]: '女',
[GENDER.other]: '其他'
}
/**
* 将map转为列表选项
* @param {string} valueType 值的类型
*/
export function mapToOptions(map, valueType) {
// 值是否为数字
const isNum = valueType === 'number'
return Object.keys(map).map(value => {
return {
label: map[value],
value: isNum ? Number(value) : value
}
})
}
// 2️⃣ 页面中使用字典项
import { GENDER, genderNameMap, mapToOptions } = '@/consts/dicts'
// 生成字典选项
const genderOptions = mapToOptions(genderNameMap);
// 伪代码,请求获取用户信息
const user = await fetchUser();
// 获取性别对应名称,用于回显
const genderName = genderNameMap[user.gender]
// 判断是否男性
const isMale = user.gender == GENDER.male
方案2:用代码维护字典选项
// 1️⃣ 维护字典 src/consts/dicts
/** 根据选项生成字典方法 */
export function genDict(options) {
const map = {}
options.forEach(item => {
map[item.value] = item
})
return {
options, // 字典选项
getName(value) {
// 获取名称
return map[value]?.label
},
is(key, value) {
// 判断
return map[value]?.key === key
}
}
}
}
/** 性别选项 */
export const genderDictOptions = [
{ label: '男', value: 1, key: 'MALE' },
{ label: '女', value: 2, key: 'FEMALE' },
{ label: '其他', value: 3, key: 'OTHER' }
];
/** 性别字典 */
export const genderDict = genDict(genderDictOptions);
// 2️⃣ 页面中使用字典项
import { genderDict } = '@/consts/dicts'
// 生成字典选项
const genderOptions = genderDict.options;
// 伪代码,请求获取用户信息
const user = await fetchUser();
// 获取性别对应名称,用于回显
const genderName = genderDict.getName(user.gender)
// 判断是否男性
const isMale = genderDict.is('MALE', user.gender)
以上两种方法都大差不差,看个人选择:
- 第一种用常量 GENDER.male 有代码提示,方便开发,第二种用'MALE'容易写错;
- 第二种只需要维护一个选项数组即可,第一种要同时维护一个GENDER常量和一个map对象。
方案3:通过配置维护字典
❗️然而,这两种方法都存在一个缺点,即如果需要修改字典项,就需要修改代码,维护起来不够方便。因此,我们可以通过后台配置字典项并生成列表项,然后前端通过请求接口获取字典项。
通过配置生成字典项,使其更加灵活,但在使用上仍存在一些问题:
- 什么时机获取字典项?
- ❌ 刷新页面时获取所有字典项: 这种方式会导致请求慢,因为字典项可能很多,并且很多字典项并不是每个页面都会用到,这样会浪费请求资源。
- ❌ 页面按需请求获取字典项: 尽管页面按需请求字典项可以减少不必要的请求,但不同页面或组件可能会重复请求相同的字典项,导致重复请求,增加了服务器负担和网络流量。
- ✅ 页面按需请求获取字典项,并将获取到的进行缓存: 最佳实践是在页面首次需要使用字典项时,向后端请求获取字典项,并将其缓存起来。如果已经存在缓存,则不进行重复请求。
- 如何更高效的将字典值转为名称?
下面以vue项目为例的解决方案:
- 统一文件维护字典:src/utils/dict.js
import { fetchDictList } from '@/api' // 字典项请求方法
// 此处用全局变量维护字典项,可以在store里面维护,这样页面中可以直接通过store访问
let _dictMap = {}
/** 初始化项目字典,注册Vue全局对象和mixin */
export function initDict(Vue) {
// 全局混入字典初始化逻辑,如果组件有dicts属性,先请求获取,无需在页面再手动请求字典项
Vue.mixin({
data() {
const dicts = this.$options?.dicts
// 如果没有配置dicts属性,不往下执行
if (!dicts || !dicts.length) return {}
const nameMap = {}
const optionsMap = {}
dicts.forEach(alias => {
// 默认空对象/数组,减少读取属性时的非空判断
nameMap[alias] = {}
optionsMap[alias] = []
})
return {
// 通过字典值获取字典名称
// 用法 mixinDictName[字典类型][字典值],如 this.mixinDictName.gender.high
// 用mixin开头为了提醒变量是从mixin中来
mixinDictName: nameMap,
// 字典选项,格式 { 字典类型: 字典选项[] }
mixinDictOptions: optionsMap,
}
},
async created() {
// 如果组件的 export default 中有 dicts 选项,初始化时获取字典
const dicts = this.$options?.dicts
if (!dicts || !dicts.length) return
const dictMap = await getDict(this.$options.dicts)
if (dictMap) {
// 赋值根据字典类型获取字典选项的对象
this.mixinDictOptions = dictMap
// 赋值根据字典值获取字典名称的对象
const dictNameMap = {}
Object.keys(dictMap).forEach(key => {
const nameMap = dictMap[key]?.nameMap
if (nameMap) {
dictNameMap[key] = nameMap
}
})
this.mixinDictName = dictNameMap
}
}
})
}
// 存储正在进行的请求的 Promise 对象,避免重复请求
const pendingRequests = {}
/** 请求获取字典项,并缓存已请求过的 */
export async function getDict(originAlias) {
try {
let alias = originAlias
if (typeof alias === 'string') alias = [alias]
else if (!Array.isArray(alias)) {
throw new Error('alias 必须为字符串或数组')
}
const needFetchDictAlias = [] // 需要请求的字典别名列表
// 过滤已经请求过的字典
for (const aItem of alias) {
// 如果缓存中没有该字典,请求获取
if (!_dictMap[aItem]) {
if (pendingRequests[aItem]) {
// 该字典已在请求中
await pendingRequests[aItem]
} else {
// 该字典未请求
needFetchDictAlias.push(aItem)
}
}
}
// 需要请求获取的字典项
if (needFetchDictAlias.length) {
const request = fetchAndCacheDict(needFetchDictAlias)
// 标识字典项正在请求
needFetchDictAlias.forEach(aItem => {
pendingRequests[aItem] = request
})
const newDictMap = await request
// 去除正在请求标识
needFetchDictAlias.forEach(aItem => {
delete pendingRequests[aItem]
})
Object.assign(_dictMap, newDictMap)
}
if (typeof originAlias === 'string') return _dictMap[originAlias]
const result = {}
for (const aItem of alias) {
result[aItem] = _dictMap[aItem]
}
return result
} catch (error) {
console.log('🚀 ~ getDict ~ error:', error)
}
}
/** 请求并缓存字典项 */
export async function fetchAndCacheDict(alias) {
const dictMap = {}
try {
// 请求获取字典项
const res = await fetchDictList({
alias,
includeValues: true
})
res.list?.forEach((item) => {
// 将请求结果保存到全局字典项
dictMap[item.alias] = item.values // 需要返回的字典项
const dictNameMap = item.values?.reduce((map, dItem) => {
// 保存字典选项别名对应的名称
map[dItem.alias] = dItem.title
return map
}, {})
// 给数组加上nameMap属性,用于根据 alias 获取对应名称 title
dictMap[item.alias].nameMap = dictNameMap
})
} catch (error) {
console.log("🚀 ~ fetchAndCacheDict ~ error:", error)
}
return dictMap
}
/** 异步获取字典项对应的名称 */
export async function asyncGetDictName(alias, value) {
if (!_dictMap[alias]) {
await getDict(alias)
}
return getDictName(alias, value)
}
/** 获取字典项对应的名称,需要请求对应字典后才能使用 */
export function getDictName(alias, value) {
const nameMap = _dictMap[alias]?.nameMap
return nameMap[value] || '未知'
}
/** 清除已缓存的字典项,可在合适的时机清空缓存 */
export function clearDict(alias) {
if (alias) delete _dictMap[alias]
else _dictMap = {}
}
- 在vue页面中使用字典
只需要在页面中配置 dicts 属性,就可以直接使用字典选项,并获取名称,简单快速
<template>
<!-- 字典值名称回显:读取全局注入的 mixinDictName -->
<div>{{ mixinDictName['dict_type'][dictValue] }}</div>
<!-- 字典选项 -->
<el-select v-model="dictValue">
<el-option
v-for="item in mixinDictOptions['dict_type']"
:key="item.alais"
:label="item.label"
:value="item.alias" />
</el-select>
</template>
<script>
export default {
dicts: ['dict_type'], // 需要使用到的字典类型/别名
data() {
return {
dictValue: 1 // 字典项的值
}
},
// ...
}
如果页面有请求先后的需求或其他特殊需求,也可以直接调用getDict方法
import { getDict } from '@/utils/dict'
const options = await getDict('dict_type')
// ...执行其他操作
全文完,望批评指正。