feat: 优化 dict

pull/152/head
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;
if (dictOpts.length === 0) {
return [] as any;
}
case 'number': {
dictValue = Number.parseInt(`${d.value}`);
break;
}
case 'string': {
dictValue = `${d.value}`;
break;
}
// No default
}
dictOptions.push({
value: dictValue,
const converter = valueConverters[valueType];
const dictOptions: DictDataType[] = dictOpts.map((d: DictItem) => ({
value: converter(d.value),
label: d.label,
});
});
}
return dictOptions.length > 0 ? dictOptions : [];
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'),
},
},
{