实现效果
表单中 菜单类型 和 显示类型 (默认值和附带图标)
表格中 菜单类型 和 显示类型 (默认值和附带图标)
数据格式和图标渲染
菜单实体部分属性 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>