😎优化字典组件

This commit is contained in:
zuohuaijun 2025-06-03 09:48:59 +08:00
parent 09c5f4cf60
commit 8409fa8fbc
3 changed files with 160 additions and 43 deletions

View File

@ -27,7 +27,7 @@
//], //],
"DbSettings": { "DbSettings": {
"EnableInitDb": true, // "EnableInitDb": true, //
"EnableInitView": true, // "EnableInitView": true, //
"EnableDiffLog": false, // "EnableDiffLog": false, //
"EnableUnderLine": false, // 线 "EnableUnderLine": false, // 线
"EnableConnEncrypt": false // SM2 "EnableConnEncrypt": false // SM2

View File

@ -92,8 +92,8 @@
"@types/node": "^22.15.29", "@types/node": "^22.15.29",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.33.0", "@typescript-eslint/eslint-plugin": "^8.33.1",
"@typescript-eslint/parser": "^8.33.0", "@typescript-eslint/parser": "^8.33.1",
"@vitejs/plugin-vue": "^5.2.4", "@vitejs/plugin-vue": "^5.2.4",
"@vitejs/plugin-vue-jsx": "^4.2.0", "@vitejs/plugin-vue-jsx": "^4.2.0",
"@vue/compiler-sfc": "^3.5.16", "@vue/compiler-sfc": "^3.5.16",

View File

@ -3,41 +3,94 @@
import { reactive, watch, PropType } from 'vue'; import { reactive, watch, PropType } from 'vue';
import { useUserInfo } from '/@/stores/userInfo'; import { useUserInfo } from '/@/stores/userInfo';
type DictItem = {
[key: string]: any;
tagType?: string;
styleSetting?: string;
classSetting?: string;
};
const userStore = useUserInfo();
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const dictList = useUserInfo().dictList;
const props = defineProps({ const props = defineProps({
/**
* 绑定的值支持多种类型
* @example
* <g-sys-dict v-model="selectedValue" code="xxxx" />
*/
modelValue: { modelValue: {
type: [String, Number, Boolean, Array, null], type: [String, Number, Boolean, Array, null] as PropType<string | number | boolean | any[] | null>,
default: null, default: null,
required: true, required: true,
}, },
/**
* 字典编码用于获取字典项
* @example 'gender'
*/
code: { code: {
type: String, type: String,
required: true, required: true,
}, },
/**
* 是否是常量
* @default false
*/
isConst: {
type: Boolean,
default: false,
},
/**
* 字典项中用于显示的字段名
* @default 'label'
*/
propLabel: { propLabel: {
type: String, type: String,
default: 'label', default: 'label',
}, },
/**
* 字典项中用于取值的字段名
* @default 'value'
*/
propValue: { propValue: {
type: String, type: String,
default: 'value', default: 'value',
}, },
/**
* 字典项过滤函数
* @param dict - 字典项
* @returns 是否保留该项
* @default (dict) => true
*/
onItemFilter: { onItemFilter: {
type: Function, type: Function as PropType<(dict: DictItem) => boolean>,
default: (dict: any): boolean => dict, default: (dict: DictItem) => true,
}, },
/**
* 字典项显示内容格式化函数
* @param dict - 字典项
* @returns 格式化后的显示内容
* @default () => undefined
*/
onItemFormatter: { onItemFormatter: {
type: Function as PropType<(dict: any) => string | undefined | null>, type: Function as PropType<(dict: DictItem) => string | undefined | null>,
default: () => undefined, default: () => undefined,
}, },
/**
* 组件渲染方式
* @values 'tag', 'select', 'radio', 'checkbox'
* @default 'tag'
*/
renderAs: { renderAs: {
type: String, type: String as PropType<'tag' | 'select' | 'radio' | 'checkbox'>,
default: 'tag', default: 'tag',
validator(value: string) { validator(value: string) {
return ['tag', 'select', 'radio', 'checkbox'].includes(value); return ['tag', 'select', 'radio', 'checkbox'].includes(value);
}, },
}, },
/**
* 是否多选
* @default false
*/
multiple: { multiple: {
type: Boolean, type: Boolean,
default: false, default: false,
@ -45,32 +98,94 @@ const props = defineProps({
}); });
const state = reactive({ const state = reactive({
dict: {} as any | any[] | undefined, dict: undefined as DictItem | DictItem[] | undefined,
dictData: [] as any[], dictData: [] as DictItem[],
value: null as any, value: undefined as any,
}); });
const setDictValue = (value: any) => { //
state.value = value; const getDataList = () => {
state.dictData = dictList[props.code]?.filter(props.onItemFilter) ?? []; if (props.isConst) {
const data = userStore.constList?.find((x: any) => x.code === props.code)?.data?.result ?? [];
if (Array.isArray(value)) { // 便
state.dict = state.dictData.filter((x: any) => value.includes(x[props.propValue])); data?.forEach((item: any) => {
if (state.dict) { item.label = item.name;
state.dict.forEach((item: any) => { item.value = item.code;
if (!['success', 'warning', 'info', 'primary', 'danger'].includes(item.tagType ?? '')) { delete item.name;
item.tagType = 'primary'; });
} return data;
});
}
} else { } else {
state.dict = state.dictData.find((x: any) => x[props.propValue] == state.value); return userStore.dictList[props.code];
if (state.dict && !['success', 'warning', 'info', 'primary', 'danger'].includes(state.dict.tagType ?? '')) {
state.dict.tagType = 'primary';
}
} }
}; };
//
const setDictData = () => {
state.dictData = getDataList()?.filter(props.onItemFilter) ?? [];
processNumericValues(props.modelValue);
};
//
const processNumericValues = (value: any) => {
if (typeof value === 'number' || (Array.isArray(value) && typeof value[0] === 'number')) {
state.dictData.forEach((item) => {
item[props.propValue] = Number(item[props.propValue]);
});
}
};
//
const trySetMultipleValue = (value: any) => {
let newValue = value;
if (typeof value === 'string') {
const trimmedValue = value.trim();
if (trimmedValue.startsWith('[') && trimmedValue.endsWith(']')) {
try {
newValue = JSON.parse(trimmedValue);
} catch (error) {
console.warn('[g-sys-dict]解析多选值失败, 异常信息:', error);
}
}
} else if (props.multiple && !value) {
newValue = [];
}
if (newValue != value) updateValue(newValue);
setDictData();
return newValue;
};
//
const setDictValue = (value: any) => {
value = trySetMultipleValue(value);
if (Array.isArray(value)) {
state.dict = state.dictData?.filter((x) => value.find((y) => y == x[props.propValue]));
state.dict?.forEach(ensureTagType);
} else {
state.dict = state.dictData?.find((x) => x[props.propValue] == value);
if (state.dict) ensureTagType(state.dict);
}
state.value = value;
};
//
const ensureTagType = (item: DictItem) => {
if (!['success', 'warning', 'info', 'primary', 'danger'].includes(item.tagType ?? '')) {
item.tagType = 'primary';
}
};
//
const updateValue = (newValue: any) => {
emit('update:modelValue', newValue);
};
//
const getDisplayText = (dict: DictItem | undefined = undefined) => {
if (dict) return props.onItemFormatter?.(dict) ?? dict[props.propLabel];
return state.value;
};
watch( watch(
() => props.modelValue, () => props.modelValue,
(newValue) => setDictValue(newValue), (newValue) => setDictValue(newValue),
@ -82,39 +197,41 @@ watch(
<!-- 渲染标签 --> <!-- 渲染标签 -->
<template v-if="props.renderAs === 'tag'"> <template v-if="props.renderAs === 'tag'">
<template v-if="Array.isArray(state.dict)"> <template v-if="Array.isArray(state.dict)">
<el-tag v-for="(item, index) in state.dict" :key="index" v-bind="$attrs" :type="item.tagType" :style="item.styleSetting" :class="item.classSetting" class="mr-1"> <el-tag v-for="(item, index) in state.dict" :key="index" v-bind="$attrs" :type="item.tagType" :style="item.styleSetting" :class="item.classSetting" class="mr2">
{{ onItemFormatter(item) ?? item[props.propLabel] }} {{ getDisplayText(item) }}
</el-tag> </el-tag>
</template> </template>
<template v-else> <template v-else>
<el-tag v-if="state.dict" v-bind="$attrs" :type="state.dict.tagType" :style="state.dict.styleSetting" :class="state.dict.classSetting"> <el-tag v-if="state.dict" v-bind="$attrs" :type="state.dict.tagType" :style="state.dict.styleSetting" :class="state.dict.classSetting">
{{ onItemFormatter(state.dict) ?? state.dict[props.propLabel] }} {{ getDisplayText(state.dict) }}
</el-tag> </el-tag>
<span v-else>{{ state.value }}</span> <span v-else>{{ getDisplayText() }}</span>
</template> </template>
</template> </template>
<!-- 渲染选择器 --> <!-- 渲染选择器 -->
<template v-if="props.renderAs === 'select'"> <template v-if="props.renderAs === 'select'">
<el-select v-model="state.value" v-bind="$attrs" :multiple="props.multiple" @change="(newValue: any) => emit('update:modelValue', newValue)" clearable> <el-select v-model="state.value" v-bind="$attrs" :multiple="props.multiple" @change="updateValue" clearable>
<el-option v-for="(item, index) in state.dictData" :key="index" :label="onItemFormatter(item) ?? item[props.propLabel]" :value="item[props.propValue]" /> <el-option v-for="(item, index) in state.dictData" :key="index" :label="getDisplayText(item)" :value="item[propValue]" />
</el-select> </el-select>
</template> </template>
<!-- 渲染复选框多选 --> <!-- 渲染复选框多选 -->
<template v-if="props.renderAs === 'checkbox'"> <template v-if="props.renderAs === 'checkbox'">
<el-checkbox-group v-model="state.value" v-bind="$attrs" @change="(newValue: any) => emit('update:modelValue', newValue)"> <el-checkbox-group v-model="state.value" v-bind="$attrs" @change="updateValue">
<el-checkbox v-for="(item, index) in state.dictData" :key="index" :label="item[props.propValue]"> <el-checkbox-button v-for="(item, index) in state.dictData" :key="index" :value="item[propValue]">
{{ onItemFormatter(item) ?? item[props.propLabel] }} {{ getDisplayText(item) }}
</el-checkbox> </el-checkbox-button>
</el-checkbox-group> </el-checkbox-group>
</template> </template>
<!-- 渲染单选框 --> <!-- 渲染单选框 -->
<template v-if="props.renderAs === 'radio'"> <template v-if="props.renderAs === 'radio'">
<el-radio-group v-model="state.value" v-bind="$attrs" @change="(newValue: any) => emit('update:modelValue', newValue)"> <el-radio-group v-model="state.value" v-bind="$attrs" @change="updateValue">
<el-radio v-for="(item, index) in state.dictData" :key="index" :value="item[props.propValue]"> <el-radio v-for="(item, index) in state.dictData" :key="index" :value="item[propValue]">
{{ onItemFormatter(item) ?? item[props.propLabel] }} {{ getDisplayText(item) }}
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</template> </template>
</template> </template>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>