😎优化字典组件
This commit is contained in:
parent
09c5f4cf60
commit
8409fa8fbc
@ -27,7 +27,7 @@
|
||||
//],
|
||||
"DbSettings": {
|
||||
"EnableInitDb": true, // 启用库初始化(若实体没有变化建议关闭)
|
||||
"EnableInitView": true, // 启用视图初始化
|
||||
"EnableInitView": true, // 启用视图初始化(若实体和视图没有变化建议关闭)
|
||||
"EnableDiffLog": false, // 启用库表差异日志
|
||||
"EnableUnderLine": false, // 启用驼峰转下划线
|
||||
"EnableConnEncrypt": false // 启用数据库连接串加密(国密SM2加解密)
|
||||
|
||||
@ -92,8 +92,8 @@
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.33.0",
|
||||
"@typescript-eslint/parser": "^8.33.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.33.1",
|
||||
"@typescript-eslint/parser": "^8.33.1",
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"@vitejs/plugin-vue-jsx": "^4.2.0",
|
||||
"@vue/compiler-sfc": "^3.5.16",
|
||||
|
||||
@ -3,41 +3,94 @@
|
||||
import { reactive, watch, PropType } from 'vue';
|
||||
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 dictList = useUserInfo().dictList;
|
||||
const props = defineProps({
|
||||
/**
|
||||
* 绑定的值,支持多种类型
|
||||
* @example
|
||||
* <g-sys-dict v-model="selectedValue" code="xxxx" />
|
||||
*/
|
||||
modelValue: {
|
||||
type: [String, Number, Boolean, Array, null],
|
||||
type: [String, Number, Boolean, Array, null] as PropType<string | number | boolean | any[] | null>,
|
||||
default: null,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* 字典编码,用于获取字典项
|
||||
* @example 'gender'
|
||||
*/
|
||||
code: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* 是否是常量
|
||||
* @default false
|
||||
*/
|
||||
isConst: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 字典项中用于显示的字段名
|
||||
* @default 'label'
|
||||
*/
|
||||
propLabel: {
|
||||
type: String,
|
||||
default: 'label',
|
||||
},
|
||||
/**
|
||||
* 字典项中用于取值的字段名
|
||||
* @default 'value'
|
||||
*/
|
||||
propValue: {
|
||||
type: String,
|
||||
default: 'value',
|
||||
},
|
||||
/**
|
||||
* 字典项过滤函数
|
||||
* @param dict - 字典项
|
||||
* @returns 是否保留该项
|
||||
* @default (dict) => true
|
||||
*/
|
||||
onItemFilter: {
|
||||
type: Function,
|
||||
default: (dict: any): boolean => dict,
|
||||
type: Function as PropType<(dict: DictItem) => boolean>,
|
||||
default: (dict: DictItem) => true,
|
||||
},
|
||||
/**
|
||||
* 字典项显示内容格式化函数
|
||||
* @param dict - 字典项
|
||||
* @returns 格式化后的显示内容
|
||||
* @default () => undefined
|
||||
*/
|
||||
onItemFormatter: {
|
||||
type: Function as PropType<(dict: any) => string | undefined | null>,
|
||||
type: Function as PropType<(dict: DictItem) => string | undefined | null>,
|
||||
default: () => undefined,
|
||||
},
|
||||
/**
|
||||
* 组件渲染方式
|
||||
* @values 'tag', 'select', 'radio', 'checkbox'
|
||||
* @default 'tag'
|
||||
*/
|
||||
renderAs: {
|
||||
type: String,
|
||||
type: String as PropType<'tag' | 'select' | 'radio' | 'checkbox'>,
|
||||
default: 'tag',
|
||||
validator(value: string) {
|
||||
return ['tag', 'select', 'radio', 'checkbox'].includes(value);
|
||||
},
|
||||
},
|
||||
/**
|
||||
* 是否多选
|
||||
* @default false
|
||||
*/
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@ -45,30 +98,92 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
dict: {} as any | any[] | undefined,
|
||||
dictData: [] as any[],
|
||||
value: null as any,
|
||||
dict: undefined as DictItem | DictItem[] | undefined,
|
||||
dictData: [] as DictItem[],
|
||||
value: undefined as any,
|
||||
});
|
||||
|
||||
const setDictValue = (value: any) => {
|
||||
state.value = value;
|
||||
state.dictData = dictList[props.code]?.filter(props.onItemFilter) ?? [];
|
||||
// 获取数据集
|
||||
const getDataList = () => {
|
||||
if (props.isConst) {
|
||||
const data = userStore.constList?.find((x: any) => x.code === props.code)?.data?.result ?? [];
|
||||
// 与字典的显示文本、值保持一致,方便渲染
|
||||
data?.forEach((item: any) => {
|
||||
item.label = item.name;
|
||||
item.value = item.code;
|
||||
delete item.name;
|
||||
});
|
||||
return data;
|
||||
} else {
|
||||
return userStore.dictList[props.code];
|
||||
}
|
||||
};
|
||||
|
||||
// 设置字典数据
|
||||
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: any) => value.includes(x[props.propValue]));
|
||||
if (state.dict) {
|
||||
state.dict.forEach((item: any) => {
|
||||
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';
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
state.dict = state.dictData.find((x: any) => x[props.propValue] == state.value);
|
||||
if (state.dict && !['success', 'warning', 'info', 'primary', 'danger'].includes(state.dict.tagType ?? '')) {
|
||||
state.dict.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(
|
||||
@ -82,39 +197,41 @@ watch(
|
||||
<!-- 渲染标签 -->
|
||||
<template v-if="props.renderAs === 'tag'">
|
||||
<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">
|
||||
{{ onItemFormatter(item) ?? item[props.propLabel] }}
|
||||
<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">
|
||||
{{ getDisplayText(item) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<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>
|
||||
<span v-else>{{ state.value }}</span>
|
||||
<span v-else>{{ getDisplayText() }}</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 渲染选择器 -->
|
||||
<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-option v-for="(item, index) in state.dictData" :key="index" :label="onItemFormatter(item) ?? item[props.propLabel]" :value="item[props.propValue]" />
|
||||
<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="getDisplayText(item)" :value="item[propValue]" />
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<!-- 渲染复选框(多选) -->
|
||||
<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 v-for="(item, index) in state.dictData" :key="index" :label="item[props.propValue]">
|
||||
{{ onItemFormatter(item) ?? item[props.propLabel] }}
|
||||
</el-checkbox>
|
||||
<el-checkbox-group v-model="state.value" v-bind="$attrs" @change="updateValue">
|
||||
<el-checkbox-button v-for="(item, index) in state.dictData" :key="index" :value="item[propValue]">
|
||||
{{ getDisplayText(item) }}
|
||||
</el-checkbox-button>
|
||||
</el-checkbox-group>
|
||||
</template>
|
||||
|
||||
<!-- 渲染单选框 -->
|
||||
<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 v-for="(item, index) in state.dictData" :key="index" :value="item[props.propValue]">
|
||||
{{ onItemFormatter(item) ?? item[props.propLabel] }}
|
||||
<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[propValue]">
|
||||
{{ getDisplayText(item) }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user