从零开始Vue3+Element Plus后台管理系统(20)——封装一个字典标签组件

1,648 阅读4分钟

在后台管理系统中存在大量的字典数据,比如各种状态、类型。后端有时会返回字典的code,前端需要通过字典表来渲染出对应的文字,有的后端比较体贴直接给我们字典的文字,前端无需处理即可展示。

但是,为了优化用户体验,常常需要对不同的字典code显示不同的UI效果,比如“禁用”使用红色标签,“启用”使用绿色,这样页面可读性就会好很多。 image.png

最简单的写法,直接在表格插槽中写:

<el-table-column label="状态">
    <template #default="scope">
       <el-tag :type="scope.row.status === '0' ? 'danger' : 'success'">
           {{ scope.row.status === '0' ? '禁用' : '启用' }}
       </el-tag>
    </template>
</el-table-column>

如果使用字典的地方很少,字典也不会变,这样写没问题。但是实际系统中往往定义了几十上百个字典类型,每个类型下的字典项也会变化,上面的写法就显得吃力了,这时封装一个字典标签组件非常必要。

约定接口请求和返回

在apiFox中新建一个接口/getDicts,用于mock返回

image.png

建立了2个期望,用于模拟不同字典类型的返回值

image.png

在项目中配置好api并使用后,可以看到如期返回

image.png

在页面中获取字典数据

给字典组件添加字典数据源,在我经历过的代码汇中,见过3种方式:

  1. 在字典组件中请求接口,获取数据源
  2. 把数据源一次性保存到vuex中
  3. 每次进入页面获取数据源

第一种每用一次字典组件就会发送一次请求,太频繁了。 第二种基本不需要几次请求,但是数据变化不及时,如果在使用期间修改字典选项,前端没反应。 我觉得第三种更合理,实现起来也不麻烦 😁。

进入页面,就获取所需字典的数据源,这里采用vue3的组合式函数(hook),让代码更加简洁高效

hooks/useDict.ts

一个页面可能有多个字典类型,此处接口只能查询一个类型,所以需要用Promise.all配合forEach返回数组中所有类型的字典选项。

当然也可以把mock接口改成支持多个类型的(前端更简单),但是想试试promise.all所以没改,大家可以根据实际情况修改。

import { reactive, onMounted } from 'vue'
import system from '~/api/system'
import systemApi from '~/api/system'

export function useDict(dictType: Array<string>) {
  let dicts = reactive<any>({})

  onMounted(() => {
    let ps: Promise<any>[] = []

    dictType.forEach((dt, index) => {
      ps[index] = systemApi.getDicts(dt).then((res) => {
        return { type: dt, values: res }
      })
    })

    Promise.all(ps).then((res) => {
      res.forEach((val) => {
        dicts[val.type] = val.values
      })
    })
  })

  return { dicts }
}

在页面中使用

import { useDict } from '~/hooks/useDict'
...
let { dicts } = useDict(['status', 'clientType'])

基于el-tag,二次封装字典组件

 <el-table-column label="状态">
          <template #default="scope">
            <MoDict :value="scope.row.status" :dicts="dicts.status" />
          </template>
        </el-table-column>
</template>

根据和后端约定好的字典结构,定义一个字典元素接口 DictItem

interface DictItem {
  label: string
  value: string
  type?: string
  class?: string
  effect?: string
}

组件props,使用Dict组件时,只需传入字典选项的的数组和字段值,然后一切交给组件来处理

const props = defineProps<{
  value: string
  dicts: any
}>()

给组件增加对应的参数,选择了一些el-tag的属性,如有需要可以继续扩展。

属性名说明类型默认
typeTag 的类型enum**''
effectTag 的主题enum**light
const item = props.dicts.find((val: DictItem) => val.value === props.value) as unknown as DictItem

const label = item.label || props.value
const tagType = item.type
const tagClass = item.class || ''
const tagEffect = item.effect || 'light'

完工!

image.png

结束语

今天又被自己蠢哭了,在我写完以上useDict的hook后,网络请求始终只有最后一个字典类型,无论如何都不返回2个。改成直接用axios.get请求又是正常的。难道二次封装的axois有问题??? 看了半天也没看出问题。

在不断试错中,终于发现问题,之前封装axios的时候,不是增加了取消请求方法么,这个消失的请求就是被取消了。。。 然而F12并没有看出来“已取消”而是直接不见了。

找到取消请求拼接标识这块,补上get请求参数,一切正常。

function getRequestKey(config: AxiosRequestConfig) {
  return (
    (config.method || '') +
    config.url +
    '?' +
    qs.stringify(config?.data) +
    qs.stringify(config?.params)  // 漏了这一截,get请求的参数没有拼接
  )
}

这里不仅仅是浪费了我1个多小时的时间,还让人非常焦躁,怀疑自己对promise、axios的理解出了问题。实际还是在写代码时不够严谨,测试不完全,基础没打好,后面容易出问题。

项目地址

本项目GIT地址:github.com/lucidity99/…

如果有帮助,给个star ✨ 点个赞👍