后台管理系统开发(系统字典 前端篇)

395 阅读2分钟

实现效果

表单中 菜单类型 和 显示类型 (默认值和附带图标)

image.png

表格中 菜单类型 和 显示类型 (默认值和附带图标)

image.png

数据格式和图标渲染

菜单实体部分属性 JSON

后端 系统字典(枚举),在返回前端时通过自定义序列化处理, 对于表格 渲染时直接 menuType.label,visible.label

"menuId": "1",
"menuName": "系统管理",
"menuType": {
  "code": "0",
  "label": "目录",
  "icon": "gg-folder",
  "is_defaulted": true
},
"visible": {
  "code": "0",
  "label": "显示",
  "icon": "",
  "is_defaulted": false
},

系统字典 JSON

在表单渲染时通过 dictType 字典类型获取字典数据渲染

{
  "code": 200,
  "message": "请求成功",
  "data": [
    {
      "dictId": "",
      "dictName": "",
      "dictType": "sys_visible",
      "system": true,
      "dataList": [
        {
          "type": "sys_visible",
          "sort": "",
          "label": "显示",
          "value": "0",
          "defaulted": false,
          "icon": ""
        },
        {
          "type": "sys_visible",
          "sort": "",
          "label": "隐藏",
          "value": "1",
          "defaulted": true,
          "icon": ""
        }
      ]
    },
    {
      "dictName": "",
      "dictType": "sys_menu_type",
      "system": true,
      "dataList": [
        {
          "type": "sys_menu_type",
          "sort": "",
          "label": "目录",
          "value": "0",
          "defaulted": true,
          "icon": "gg-folder"
        },
        {
          "type": "sys_menu_type",
          "sort": "",
          "label": "菜单",
          "value": "1",
          "defaulted": false,
          "icon": "gg-menu"
        },
        {
          "type": "sys_menu_type",
          "sort": "",
          "label": "按钮",
          "value": "2",
          "defaulted": false,
          "icon": "gg-distribute-vertical"
        }
      ]
    }
  ]
}

图标渲染

请求 api.iconify.design/gg:adidas.s… 获取图标 svg 文件, 通过 传递 --svg css 变量, 赋值 mask-image 渲染

图标名称来自于 icones.js.org/collection/…

<script lang="ts" setup>
  const props = defineProps<{
    name?: string;
  }>();
</script>

<template>
  <i
    v-if="!!name"
    :style="{
      '--svg': `url(/iconify/${props.name}.svg)`,
    }"
    class="icon"
  />
</template>

css

.icon {
  display: inline-block;
  width: 1.1em;
  height: 1.1em;
  --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'");
  background-color: currentColor;
  -webkit-mask-image: var(--svg);
  mask-image: var(--svg);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  vertical-align: sub;
}

nuxt3 跨域处理

nitro: {
  devProxy: {
    '/iconify': {
      target: 'https://api.iconify.design/',
      prependPath: true,
      changeOrigin: true,
    },
  },
},

表单代码实现

封装 hooks

import { useRequest } from '#imports';
import { getSystemDictApi } from '~/api/dict';

export const useSystemDictStore = defineStore('system-dict-store', () => {
  const systemDictList = ref<DictTypeEntity[]>([]);

  const { refresh } = useRequest(getSystemDictApi, {
    manual: false,
    onSuccess: (data) => {
      systemDictList.value = [...data];
    },
  });

  /**
   * 刷新 系统字典
   */
  const refreshSystemDict = () => {
    refresh();
  };

  /**
   * 根据 `dictType` 获取字典数据
   *
   * @param dictType 字典类型
   */
  const getDictType = (dictType: string): DictTypeEntity => {
    return (
      systemDictList.value.find((dict) => dict.dictType === dictType) ||
      ({} as DictTypeEntity)
    );
  };

  /**
   * 根据 `dictType` 获取字典数据
   *
   * @param dictType 字典类型
   */
  const getDictTypeData = (dictType: string) => {
    const dictTypeEntity = getDictType(dictType);
    return dictTypeEntity.dataList || [];
  };

  /**
   * 获取字典数据默认值
   */
  const getDefaultValue = (dictType: string) => {
    const dictDataEntities = getDictTypeData(dictType);
    return dictDataEntities.find((dict) => dict.defaulted)?.value || '';
  };

  return {
    getDictType,
    getDictTypeData,
    refreshSystemDict,
    getDefaultValue,
    systemDictList,
  };
});

创建表单

  • getDictTypeData 获取系统字典数据
  • getDefaultValue 获取系统字典默认值

<script lang="ts" setup>
  // 系统字典
  const systemDictStore = useSystemDictStore();

  const SYS_MENU_TYPE = 'sys_menu_type';
  const SYS_VISIBLE = 'sys_visible';

  // 在创建表单打开时,成功创建时 重置数据
  const onResetForm = () => {
    // formRef.value!.resetFields();
    formData.value = {
      menuType: systemDictStore.getDefaultValue(SYS_MENU_TYPE),
      visible: systemDictStore.getDefaultValue(SYS_VISIBLE),
    };
  };
</script>


<a-form-item field="menuType" label="菜单类型">
  <a-radio-group v-model="formData.menuType">
    <a-radio
      v-for="dictData in systemDictStore.getDictTypeData(SYS_MENU_TYPE)"
      :key="dictData.value"
      :value="dictData.value"
    >
      <a-space>
        // 渲染图标
        <Iconify :name="dictData.icon" />
        <span>{{ dictData.label }}</span>
      </a-space>
    </a-radio>
  </a-radio-group>
</a-form-item>


<a-form-item field="visible" label="显示">
  <a-select
    v-model="formData.visible"
    placeholder="菜单是否显示"
  >
    <a-option
      v-for="dictData in systemDictStore.getDictTypeData(SYS_VISIBLE')"
      :key="dictData.value"
      :value="dictData.value"
    >
      <span>{{ dictData.label }}</span>
      <template #icon>
        <Iconify :name="dictData.icon" />
      </template>
    </a-option>

    <template #prefix>
      <i class="i-gg-eye" />
    </template>
  </a-select>
</a-form-item>

封装通用组件 SysDictSelect.vue [Select方式]

<script lang="ts" setup>
  const props = defineProps<{
    dictType: string;
    modelValue?: string;
    placeholder?: string;
  }>();

  // 系统字典
  const systemDictStore = useSystemDictStore();

  const dictValue = useVModel(props, 'modelValue');
</script>

<template>
  <a-select v-model="dictValue" :placeholder="placeholder">
    <template #label="{ data }">
      <a-space>
        <Iconify :name="systemDictStore.getIcon(dictType, data.value)" />
        <span>{{ data.label }}</span>
      </a-space>
    </template>
    <a-option
      v-for="dictData in systemDictStore.getDictTypeData(dictType)"
      :key="dictData.value"
      :value="dictData.value"
    >
      <a-space class="pl-2" fill>
        <Iconify v-if="dictData.icon" :name="dictData.icon" />
        <span>{{ dictData.label }}</span>
      </a-space>
    </a-option>
  </a-select>
</template>

使用

<template>
  <SysDictSelect
    v-model="searchForm.menuType"
    :dict-type="SYS_MENU_TYPE"
  />

  <SysDictSelect
    v-model="searchForm.visible"
    :dict-type="SYS_VISIBLE"
  />
</template>