feat: 优化 dict

This commit is contained in:
xingyu4j
2025-06-20 18:32:05 +08:00
parent 7c3a2b7aa4
commit 9283d58ce9
11 changed files with 158 additions and 124 deletions

View File

@@ -66,6 +66,7 @@ const dictTag = computed(() => {
return {
label: dict.label || '',
colorType,
cssClass: dict.cssClass,
};
});
</script>

View File

@@ -13,7 +13,7 @@ import {
SelectOption,
} from 'ant-design-vue';
import { getDictObj, getIntDictOptions, getStrDictOptions } from '#/utils';
import { getDictOptions } from '#/utils';
defineOptions({ name: 'DictSelect' });
@@ -25,17 +25,16 @@ const props = withDefaults(defineProps<DictSelectProps>(), {
const attrs = useAttrs();
// 获得字典配置
// TODO @dhb可以使用 getDictOptions 替代么?
const getDictOptions = computed(() => {
const getDictOption = computed(() => {
switch (props.valueType) {
case 'bool': {
return getDictObj(props.dictType, 'bool');
return getDictOptions(props.dictType, 'boolean');
}
case 'int': {
return getIntDictOptions(props.dictType);
return getDictOptions(props.dictType, 'number');
}
case 'str': {
return getStrDictOptions(props.dictType);
return getDictOptions(props.dictType, 'string');
}
default: {
return [];
@@ -47,7 +46,7 @@ const getDictOptions = computed(() => {
<template>
<Select v-if="selectType === 'select'" class="w-full" v-bind="attrs">
<SelectOption
v-for="(dict, index) in getDictOptions"
v-for="(dict, index) in getDictOption"
:key="index"
:value="dict.value"
>
@@ -56,7 +55,7 @@ const getDictOptions = computed(() => {
</Select>
<RadioGroup v-if="selectType === 'radio'" class="w-full" v-bind="attrs">
<Radio
v-for="(dict, index) in getDictOptions"
v-for="(dict, index) in getDictOption"
:key="index"
:value="dict.value"
>
@@ -65,7 +64,7 @@ const getDictOptions = computed(() => {
</RadioGroup>
<CheckboxGroup v-if="selectType === 'checkbox'" class="w-full" v-bind="attrs">
<Checkbox
v-for="(dict, index) in getDictOptions"
v-for="(dict, index) in getDictOption"
:key="index"
:value="dict.value"
>

View File

@@ -1,6 +1,8 @@
// TODO @芋艿:后续再优化
// TODO @芋艿:可以共享么?
import type { DictItem } from '#/store';
import { isObject } from '@vben/utils';
import { useDictStore } from '#/store';
@@ -9,33 +11,103 @@ import { useDictStore } from '#/store';
// 先临时移入到方法中
// const dictStore = useDictStore();
// TODO @dhb: antd 组件的 color 类型
/** AntD 组件的颜色类型 */
type ColorType = 'error' | 'info' | 'success' | 'warning';
/** 字典值类型 */
type DictValueType = 'boolean' | 'number' | 'string';
/** 基础字典数据类型 */
export interface DictDataType {
dictType?: string;
label: string;
value: boolean | number | string;
colorType?: ColorType;
colorType?: string;
cssClass?: string;
}
/** 数字类型字典数据 */
export interface NumberDictDataType extends DictDataType {
value: number;
}
/** 字符串类型字典数据 */
export interface StringDictDataType extends DictDataType {
value: string;
}
/** 布尔类型字典数据 */
export interface BooleanDictDataType extends DictDataType {
value: boolean;
}
/** 字典缓存管理器 */
class DictCacheManager {
private cache = new Map<string, DictDataType[]>();
private maxCacheSize = 100; // 最大缓存数量
/** 清空缓存 */
clear(): void {
this.cache.clear();
}
/** 删除指定字典类型的缓存 */
delete(dictType: string): void {
const keysToDelete = [];
for (const key of this.cache.keys()) {
if (key.startsWith(`${dictType}:`)) {
keysToDelete.push(key);
}
}
keysToDelete.forEach((key) => this.cache.delete(key));
}
/** 获取缓存数据 */
get(dictType: string, valueType: DictValueType): DictDataType[] | undefined {
return this.cache.get(this.getCacheKey(dictType, valueType));
}
/** 设置缓存数据 */
set(dictType: string, valueType: DictValueType, data: DictDataType[]): void {
const key = this.getCacheKey(dictType, valueType);
// 如果缓存数量超过限制,删除最早的缓存
if (this.cache.size >= this.maxCacheSize) {
const firstKey = this.cache.keys().next().value;
if (firstKey) {
this.cache.delete(firstKey);
}
}
this.cache.set(key, data);
}
/** 获取缓存键 */
private getCacheKey(dictType: string, valueType: DictValueType): string {
return `${dictType}:${valueType}`;
}
}
/** 字典缓存实例 */
const dictCache = new DictCacheManager();
/** 值转换器映射 */
const valueConverters: Record<
DictValueType,
(value: any) => boolean | number | string
> = {
boolean: (value: any) => `${value}` === 'true',
number: (value: any) => Number.parseInt(`${value}`, 10),
string: (value: any) => `${value}`,
};
/**
* 获取字典标签
*
* @param dictType 字典类型
* @param value 字典值
* @returns 字典标签
*/
function getDictLabel(dictType: string, value: any) {
function getDictLabel(dictType: string, value: any): string {
const dictStore = useDictStore();
const dictObj = dictStore.getDictData(dictType, value);
return isObject(dictObj) ? dictObj.label : '';
@@ -43,103 +115,73 @@ function getDictLabel(dictType: string, value: any) {
/**
* 获取字典对象
*
* @param dictType 字典类型
* @param value 字典值
* @returns 字典对象
*/
function getDictObj(dictType: string, value: any) {
function getDictObj(dictType: string, value: any): DictItem | null {
const dictStore = useDictStore();
const dictObj = dictStore.getDictData(dictType, value);
return isObject(dictObj) ? dictObj : null;
}
/**
* 获取字典数组 用于select radio 等
*
* 获取字典数组 - 优化版本,支持缓存和泛型
* @param dictType 字典类型
* @param valueType 字典值类型,默认 string 类型
* @returns 字典数组
*/
function getDictOptions(
function getDictOptions<T extends DictValueType = 'string'>(
dictType: string,
valueType: 'boolean' | 'number' | 'string' = 'string',
): DictDataType[] {
valueType: T = 'string' as T,
): T extends 'number'
? NumberDictDataType[]
: T extends 'boolean'
? BooleanDictDataType[]
: StringDictDataType[] {
// 检查缓存
const cachedData = dictCache.get(dictType, valueType);
if (cachedData) {
return cachedData as any;
}
const dictStore = useDictStore();
const dictOpts = dictStore.getDictOptions(dictType);
const dictOptions: DictDataType[] = [];
if (dictOpts.length > 0) {
let dictValue: boolean | number | string = '';
dictOpts.forEach((d) => {
switch (valueType) {
case 'boolean': {
dictValue = `${d.value}` === 'true';
break;
}
case 'number': {
dictValue = Number.parseInt(`${d.value}`);
break;
}
case 'string': {
dictValue = `${d.value}`;
break;
}
// No default
}
dictOptions.push({
value: dictValue,
label: d.label,
});
});
if (dictOpts.length === 0) {
return [] as any;
}
return dictOptions.length > 0 ? dictOptions : [];
const converter = valueConverters[valueType];
const dictOptions: DictDataType[] = dictOpts.map((d: DictItem) => ({
value: converter(d.value),
label: d.label,
colorType: d.colorType,
cssClass: d.cssClass,
}));
// 缓存结果
dictCache.set(dictType, valueType, dictOptions);
return dictOptions as any;
}
// TODO @dhb52下面的一系列方法看看能不能复用 getDictOptions 方法
export const getIntDictOptions = (dictType: string): NumberDictDataType[] => {
// 获得通用的 DictDataType 列表
const dictOptions = getDictOptions(dictType) as DictDataType[];
// 转换成 number 类型的 NumberDictDataType 类型
// why 需要特殊转换:避免 IDEA 在 v-for="dict in getIntDictOptions(...)" 时el-option 的 key 会告警
const dictOption: NumberDictDataType[] = [];
dictOptions.forEach((dict: DictDataType) => {
dictOption.push({
...dict,
value: Number.parseInt(`${dict.value}`),
});
});
return dictOption;
/**
* 清空字典缓存
*/
export const clearDictCache = (): void => {
dictCache.clear();
};
// TODO @dhb52下面的一系列方法看看能不能复用 getDictOptions 方法
export const getStrDictOptions = (dictType: string) => {
// 获得通用的 DictDataType 列表
const dictOptions = getDictOptions(dictType) as DictDataType[];
// 转换成 string 类型的 StringDictDataType 类型
// why 需要特殊转换:避免 IDEA 在 v-for="dict in getStrDictOptions(...)" 时el-option 的 key 会告警
const dictOption: StringDictDataType[] = [];
dictOptions.forEach((dict: DictDataType) => {
dictOption.push({
...dict,
value: `${dict.value}`,
});
});
return dictOption;
};
// TODO @dhb52下面的一系列方法看看能不能复用 getDictOptions 方法
export const getBoolDictOptions = (dictType: string) => {
const dictOption: DictDataType[] = [];
const dictOptions = getDictOptions(dictType) as DictDataType[];
dictOptions.forEach((dict: DictDataType) => {
dictOption.push({
...dict,
value: `${dict.value}` === 'true',
});
});
return dictOption;
/**
* 删除指定字典类型的缓存
* @param dictType 字典类型
*/
export const deleteDictCache = (dictType: string): void => {
dictCache.delete(dictType);
};
/** 字典类型枚举 - 按模块分组和排序 */
enum DICT_TYPE {
AI_GENERATE_MODE = 'ai_generate_mode', // AI 生成模式
AI_IMAGE_STATUS = 'ai_image_status', // AI 图片状态
@@ -274,4 +316,12 @@ enum DICT_TYPE {
TRADE_ORDER_TYPE = 'trade_order_type', // 订单 - 类型
USER_TYPE = 'user_type',
}
export { DICT_TYPE, getDictLabel, getDictObj, getDictOptions };
export {
type ColorType,
DICT_TYPE,
type DictValueType,
getDictLabel,
getDictObj,
getDictOptions,
};

View File

@@ -5,7 +5,7 @@ import { ref } from 'vue';
import { Form, Input, Select } from 'ant-design-vue';
import { DICT_TYPE, getIntDictOptions } from '#/utils';
import { DICT_TYPE, getDictOptions } from '#/utils';
// 创建本地数据副本
const modelData = defineModel<any>();
@@ -57,7 +57,7 @@ defineExpose({ validate });
placeholder="请选择状态"
>
<Select.Option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS, 'number')"
:key="dict.value"
:value="dict.value"
>

View File

@@ -11,7 +11,7 @@ import { Button, message, Textarea } from 'ant-design-vue';
import {
AiWriteTypeEnum,
DICT_TYPE,
getIntDictOptions,
getDictOptions,
WriteExample,
} from '#/utils';
@@ -211,22 +211,22 @@ function submit() {
<ReuseLabel label="长度" />
<Tag
v-model="formData.length"
:tags="getIntDictOptions(DICT_TYPE.AI_WRITE_LENGTH)"
:tags="getDictOptions(DICT_TYPE.AI_WRITE_LENGTH, 'number')"
/>
<ReuseLabel label="格式" />
<Tag
v-model="formData.format"
:tags="getIntDictOptions(DICT_TYPE.AI_WRITE_FORMAT)"
:tags="getDictOptions(DICT_TYPE.AI_WRITE_FORMAT, 'number')"
/>
<ReuseLabel label="语气" />
<Tag
v-model="formData.tone"
:tags="getIntDictOptions(DICT_TYPE.AI_WRITE_TONE)"
:tags="getDictOptions(DICT_TYPE.AI_WRITE_TONE, 'number')"
/>
<ReuseLabel label="语言" />
<Tag
v-model="formData.language"
:tags="getIntDictOptions(DICT_TYPE.AI_WRITE_LANGUAGE)"
:tags="getDictOptions(DICT_TYPE.AI_WRITE_LANGUAGE, 'number')"
/>
<div class="mt-3 flex items-center justify-center">

View File

@@ -25,7 +25,7 @@ import {
import { DeptSelectModal, UserSelectModal } from '#/components/select-modal';
import { ImageUpload } from '#/components/upload';
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '#/utils';
import { DICT_TYPE, getDictOptions } from '#/utils';
const props = defineProps({
categoryList: {
@@ -295,7 +295,7 @@ defineExpose({ validate });
<Radio.Group v-model:value="modelData.type">
<!-- TODO BPMN 流程类型需要整合暂时禁用 -->
<Radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
v-for="dict in getDictOptions(DICT_TYPE.BPM_MODEL_TYPE, 'number')"
:key="dict.value"
:value="dict.value"
:disabled="dict.value === 10"
@@ -307,10 +307,11 @@ defineExpose({ validate });
<Form.Item label="是否可见" name="visible" class="mb-5">
<Radio.Group v-model:value="modelData.visible">
<Radio
v-for="(dict, index) in getBoolDictOptions(
v-for="dict in getDictOptions(
DICT_TYPE.INFRA_BOOLEAN_STRING,
'boolean',
)"
:key="index"
:key="dict.label"
:value="dict.value"
>
{{ dict.label }}

View File

@@ -139,8 +139,6 @@ const quickNavItems: WorkbenchQuickNavItem[] = [
];
const router = useRouter();
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
// This is a sample method, adjust according to the actual project requirements
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
if (nav.url?.startsWith('http')) {
openWindow(nav.url);

View File

@@ -1,12 +1,7 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeGridPropTypes } from '#/adapter/vxe-table';
import {
DICT_TYPE,
getDictOptions,
getIntDictOptions,
getRangePickerDefaultProps,
} from '#/utils';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@@ -72,7 +67,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
component: 'Select',
componentProps: {
placeholder: '请选择状态',
options: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
},
},
{

View File

@@ -1,12 +1,7 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeGridPropTypes } from '#/adapter/vxe-table';
import {
DICT_TYPE,
getDictOptions,
getIntDictOptions,
getRangePickerDefaultProps,
} from '#/utils';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@@ -99,7 +94,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
component: 'Select',
componentProps: {
placeholder: '请选择状态',
options: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
},
},
{

View File

@@ -2,7 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MallSeckillConfigApi } from '#/api/mall/promotion/seckill/seckillConfig';
import { DICT_TYPE, getDictOptions, getIntDictOptions } from '#/utils';
import { DICT_TYPE, getDictOptions } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@@ -83,7 +83,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
component: 'Select',
componentProps: {
placeholder: '请选择状态',
options: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
},
},
];

View File

@@ -2,12 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { getAppList } from '#/api/pay/app';
import {
DICT_TYPE,
getIntDictOptions,
getRangePickerDefaultProps,
getStrDictOptions,
} from '#/utils';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
@@ -34,7 +29,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
component: 'Select',
componentProps: {
allowClear: true,
options: getStrDictOptions(DICT_TYPE.PAY_CHANNEL_CODE),
options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE, 'string'),
},
},
{
@@ -63,7 +58,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
component: 'Select',
componentProps: {
allowClear: true,
options: getIntDictOptions(DICT_TYPE.PAY_REFUND_STATUS),
options: getDictOptions(DICT_TYPE.PAY_REFUND_STATUS, 'number'),
},
},
{