数据字典的代理请求
liz-q 2020/12/28
什么是数据字典呢?
在我们实际开发项目中,都会有下拉选择框,这些下拉选择的数据一般可以认为是数据字典。
通常这些字典数据是从接口获取的,并且,这些数据格式都是一致的,例如是这样:
[{
value: '选项1',
label: '黄金糕'
}, {
value: '选项2',
label: '双皮奶'
}, {
value: '选项3',
label: '蚵仔煎'
}, {
value: '选项4',
label: '龙须面'
}, {
value: '选项5',
label: '北京烤鸭'
}]
然而,随着接口增多,开发人员的不同,规范的不统一,返回的数据格式也是五花八门(懂的都懂~)
所以,为了方便前端的显示,我做了一个字典代理的类,专门代理数据字典接口的请求,返回数据做统一处理。
# 实现
apis.js
该文件中存放接口列表
/* 获取计量单位 */
/* 返回数据格式:[{id: 1, name: 'foo'}, {id: 2, name: 'bar'}...] */
export function fetchUnitList () {
return request.get(...)
}
/* 获取标准名称列表 */
/* 返回数据格式:[{code: 1, label: 'foo'}, {code: 2, label: 'bar'}...] */
export function fetchStandardNames () {
return request.get(...)
}
如果不用代理请求,我们通常在 vue
组件中这样使用:
<template>
<el-select>
<el-option
v-for="item in options1"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
<el-select>
<el-option
v-for="item in options2"
:key="item.code"
:label="item.label"
:value="item.code">
</el-option>
</el-select>
</template>
<script>
import { fetchUnitList, fetchStandardNames } from './apis'
export default {
data () {
return {
options1: [],
options2: []
}
},
methods: {
async getUnitList () {
this.options1 = await fetchUnitList()
},
async getStandardNames () {
this.options2 = await fetchStandardNames()
}
},
created () {
this.getUnitList()
this.getStandardNames()
}
}
</script>
接下来实现代理类,通过代理统一返回的数据格式,提高前端开发效率。
它的优点是:
- 统一返回数据格式
- 去除瞬时重复请求
- 操作方便灵活
dictionary-proxy.js
import { stringify } from 'qs'
/**
* 代理数据字典接口请求
* */
import dictionaryApis from './apis'
class DictionaryProxy {
// 默认label取name字段值,value取id字段值
static PROPS = {
label: 'name',
value: 'id'
}
// 统一数据格式
static formatLabelAndValue (list, _props) {
list.forEach(d => {
d.label = d[_props.label]
d.value = '' + d[_props.value]
})
return list
}
// 单例模式
static getInstance () {
if (!this.instance) {
this.instance = new DictionaryProxy()
}
return this.instance
}
// 用请求函数名和参数生成请求唯一key
static getReqKey (name, payload = '') {
return `${name}@${stringify(payload)}`
}
constructor () {
this.state = new Map()
this.resultState = new Map()
this.expires = 5 * 1000 // 有效时长 在有效时长内重复请求 返回上次请求数据
}
/* 过滤有效的请求 */
filterReq (list) {
return list.map(req => {
const obj = typeof req === 'string' ? { name: req } : req
obj.reqKey = DictionaryProxy.getReqKey(obj.name, obj.payload)
return dictionaryApis[obj.name] ? obj : null
}).filter(o => o)
}
setReq (effectiveList) {
effectiveList.forEach(req => {
const timestamp = new Date().getTime()
if (this.state.has(req.reqKey) && this.validateExpires(req.reqKey)) {
// 重复请求 更新时间戳
this.state.get(req.reqKey).timestamp = timestamp
} else {
// 缓存请求
this.state.set(req.reqKey, {
pm: this._request(req.name, req.payload, {
label: req.label || DictionaryProxy.PROPS.label,
value: req.value || DictionaryProxy.PROPS.value
}, req),
timestamp
})
}
})
}
/* 验证是否是重复请求 返回true是重复请求 */
validateExpires (reqKey) {
return new Date().getTime() - this.state.get(reqKey).timestamp < this.expires
}
// 请求函数,返回promise
async _request (name, payload, _props, req) {
const res = await dictionaryApis[name](payload)
const list = this.formatCommonRes(res, _props)
// 保存结果数据
this.resultState.set(req.reqKey, list)
return res
}
// 处理各种接口返回的数据
formatCommonRes (res = {}, _props) {
let list = []
// res 是数组
if (Array.isArray(res)) {
list = res
}
// res 是对象
if (Object.prototype.toString.call(res) === '[object Object]') {
const { data = [] } = res
if (Array.isArray(data)) {
list = data
} else if (Object.prototype.toString.call(data) === '[object Object]') {
if (data.records) {
list = data.records
} else {
for (const key in data) {
list.push({
[_props.label]: data[key],
[_props.value]: key
})
}
}
}
}
return DictionaryProxy.formatLabelAndValue(list, _props)
}
/* 代理请求列表 */
async proxyReqs (list = []) {
const effectiveList = this.filterReq(list)
this.setReq(effectiveList)
await Promise.all(effectiveList.map(req => this.state.get(req.reqKey).pm))
return effectiveList.reduce((prev, next) => {
prev[next.alias || next.name] = this.resultState.get(next.reqKey)
return prev
}, {})
}
}
const dictionaryProxy = DictionaryProxy.getInstance()
export {
dictionaryProxy,
DictionaryProxy,
DictionaryProxy as default
}
此时,在 vue
组件中这样使用:
<template>
<el-select>
<el-option
v-for="item in fetchUnitList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<el-select>
<el-option
v-for="item in options2"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
<script>
import { dictionaryProxy } from './dictionary-proxy'
export default {
data () {
return {
fetchUnitList: [],
options2: []
}
},
async created () {
// 前提是你要知道请求接口的名称
const { fetchUnitList, options2 } = await dictionaryProxy.proxyReqs([
'fetchUnitList', // 可以直接是接口名称,默认label取name字段值,value取id字段值,返回列表是fetchUnitList
// 你也可以传入一个对象
{
name: 'fetchStandardNames',
alias: 'options2', // 默认返回列表名是fetchStandardNames,你可以起一个别名
label: 'label', // label取的是label字段值
value: 'code' // value取得是code字段值
}
])
this.fetchUnitList = fetchUnitList
this.options2 = options2
}
}
</script>