数据字典的代理请求

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>

接下来实现代理类,通过代理统一返回的数据格式,提高前端开发效率。

它的优点是:

  1. 统一返回数据格式
  2. 去除瞬时重复请求
  3. 操作方便灵活

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>
Last Updated: 2023/8/21 上午11:14:32