前言
在日常前端开发中,常量枚举常用于定义一组固定的键值对,如状态码、配置项或类型标识。例如,定义一个API_STATUS
枚举来管理请求状态:LOADING
, SUCCESS
, ERROR
。这能避免魔法字符串、增强代码可读性与可维护性,并通过TypeScript的类型检查在编译时发现错误,提升开发体验与协作效率。
该文主要是基于 Tdesigin 进行二次封装三个在日常中经常使用的常量组件,它们分别是 ConstantSelect ;ConstantRadio ; ConstantCheckbox;并且自动化读取常量配置文件,减少代码量,提高开发效率。
前期准备
一、常量定义
单独定义到一个文件
typescript
// modules/account-status.ts
export default {
VALID: { value: '0', label: '正常' },
EXPIRED: { value: '1', label: '已过期' },
LOCKED: { value: '2', label: '已锁定' },
DISABLED: { value: '3', label: '已停用' },
PENDING: { value: '4', label: '待激活' }
};
二、自动化读取常量文件
生成两种不同数据结构的全局变量
也可以根据实际业务调整不同的数据结构
typescript
// 自动化读取常量文件(关键代码)
const modules = import.meta.glob('./modules/*.ts', { eager: true });
const constantValueMap: { [key: string]: { [key: string]: string } } = {};
const constantLabelMap: { [key: string]: { [key: string]: string } } = {};
Object.keys(modules).forEach((key) => {
// 每个文件对应一个常量
const mod = modules[key].default || {};
// 将文件名当作区分不同常量
const newKey = key
.substring(key.lastIndexOf('/') + 1)
.replace('.ts', '')
.replace(/-./g, (x) => x[1].toUpperCase()); // kebab-case 转 camelCase
// 俩种格式的常量
constantValueMap[newKey] = {};
constantLabelMap[newKey] = {};
Object.keys(mod).forEach((subKey) => {
const subConstant = mod[subKey];
constantValueMap[newKey][subKey] = subConstant.value;
constantLabelMap[newKey][subConstant.value] = subConstant.label;
});
});
export { constantLabelMap, constantValueMap };
三、两种不同数据结构的全局变量
- constantValueMap
json
{
"accountStatus": {
"VALID": "0",
"EXPIRED": "1",
....
}
}
- constantLabelMap
json
{
"accountStatus": {
"0": "正常",
"1": "已过期"
....
}
}
Tips 导入常量入口文件
typescript
import { constantValueMap, constantLabelMap } from '@/constants/index';
四、好处
上述这种方法处理的好处在于:每一种常量类型对应一个文件,可以方便管理,通过自动读取的方式进行导入,无需手动导入。使用时导入对应的数据结构类型即可。
ConstantSelect 下拉框组件
一、封装(干货很多)
js
<template>
<t-select v-bind="attr" v-model="modelValue" :placeholder="placeholder ? placeholder : '请选择'" @change="onChange">
<t-option v-if="showEmpty && !multiple" :value="emptyKey" :label="emptyLabel ? emptyLabel : '全部'"></t-option>
<t-option v-for="(value, key) in data" :key="key" :value="key" :label="value" :disabled="disabled(key)"></t-option>
</t-select>
</template>
<script setup lang="ts">
import type { SelectProps } from 'tdesign-vue-next';
import { computed, defineModel } from 'vue';
import { constantLabelMap } from '@/constants';
// 继承 SelectProps 属性
interface Props extends SelectProps {
type: string; // 常量名称(对应文件名 驼峰命名)
// 主要是区分多选和单选全部
showEmpty?: boolean; // 显示单选全部,前提是不能多选,即 multiple 为 false
emptyLabel?: string; // label 值
emptyKey?: string; // 选择全部对应的 value 值
invalidKeys?: string; // 过滤掉非法的 key
disabledKeys?: Array<string | number>; // 禁止选中
customOption?: { [key: string]: string }; // 自定义 options
}
const props: Props = withDefaults(defineProps<Props>(), {
emptyKey: '',
showEmpty: true,
invalidKeys: '',
disabledKeys: () => {
return [];
},
disabled: false,
clearable: true,
multiple: false,
showArrow: true,
max: 0,
popupVisible: undefined,
defaultPopupVisible: undefined,
});
const emit = defineEmits(['change']);
// 透传属性
const attr = computed(() => {
return JSON.parse(JSON.stringify(props));
});
// 禁止选中
const disabled = computed(() => {
return (key: string | number) => {
return props.disabledKeys.includes(key);
};
});
const data = computed(() => {
const constant = constantLabelMap[props.type];
const retObj: Record<string, string> = {};
if (constant) {
const invalidKeysArray = props.invalidKeys
.split(',')
.map((k) => k.trim())
.filter(Boolean);
Object.keys(constant).forEach((key) => {
// 过滤不显示选项
if (!invalidKeysArray.includes(key)) {
retObj[key] = constant[key];
}
});
}
if (props.customOption) {
return { ...retObj, ...props.customOption };
}
return { ...retObj };
});
const modelValue = defineModel<string | Array<string>>();
const onChange: SelectProps['onChange'] = (value) => {
emit('change', value);
};
</script>
<style lang="less" scoped></style>
二、使用案例
1、基本使用
js
<template>
<constants-select v-model="value" type="accountStatus" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ConstantsSelect from '@/components/ConstantsSelect.vue';
const value = ref('');
</script>
<style lang="less" scoped></style>

2、多选
js
<template>
<constants-select v-model="value" type="accountStatus" multiple />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ConstantsSelect from '@/components/ConstantsSelect.vue';
const value = ref([]);
</script>
<style lang="less" scoped></style>

3、单选,可选择全部,自定义【全部】值
js
<template>
<constants-select v-model="value" type="accountStatus" empty-key="all" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ConstantsSelect from '@/components/ConstantsSelect.vue';
const value = ref('');
</script>
<style lang="less" scoped></style>

4、额外添加选项
js
<template>
<constants-select v-model="value" type="accountStatus" :custom-option="customOption" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ConstantsSelect from '@/components/ConstantsSelect.vue';
const value = ref('');
const customOption = ref({
'7': '自定义选项',
});
</script>
<style lang="less" scoped></style>

5、配置禁止选中项
js
<template>
<constants-select v-model="value" type="accountStatus" :disabled-keys="['1', '2', '3']" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ConstantsSelect from '@/components/ConstantsSelect.vue';
const value = ref('');
</script>
<style lang="less" scoped></style>

6、过滤掉特定值
js
<template>
<constants-select v-model="value" type="accountStatus" invalid-keys="1,2,3" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ConstantsSelect from '@/components/ConstantsSelect.vue';
const value = ref('');
</script>
<style lang="less" scoped></style>

ConstantCheckbox 多选框
参数配置跟 ConstantSelect 下拉框组件差不多,使用上也是。
一、封装(干货很多)
vue
<template>
<t-checkbox-group v-bind="attr" v-model="modelValue" @change="onChange">
<t-checkbox v-if="showAll" :key="allKey" :label="allLabel" :check-all="true" />
<t-checkbox v-for="(value, key) in data" :key="key" :value="key" :label="value" />
</t-checkbox-group>
</template>
<script setup lang="ts">
import type { CheckboxGroupProps } from 'tdesign-vue-next';
import { computed, defineModel } from 'vue';
import { constantLabelMap } from '@/constants';
interface Props extends CheckboxGroupProps {
type: string;
allLabel?: string;
allKey?: string;
showAll?: boolean;
invalidKeys?: string;
customOption?: Record<string, string>;
}
const props = withDefaults(defineProps<Props>(), {
allLabel: '全选',
allKey: 'all',
showAll: true,
invalidKeys: '',
customOption: () => ({}),
});
const attr = computed(() => {
return JSON.parse(JSON.stringify(props));
});
// 处理选项数据
const data = computed(() => {
const constant = constantLabelMap[props.type];
const retObj: Record<string, string> = {};
if (constant) {
const invalidKeysArray = props.invalidKeys
.split(',')
.map((k) => k.trim())
.filter(Boolean);
Object.keys(constant).forEach((key) => {
if (!invalidKeysArray.includes(key)) {
retObj[key] = constant[key];
}
});
}
if (props.customOption) {
return { ...retObj, ...props.customOption };
}
return { ...retObj };
});
const modelValue = defineModel<string[]>({ default: () => [] });
const emit = defineEmits(['change']);
const onChange: SelectProps['onChange'] = (value) => {
emit('change', value);
};
</script>
二、使用案例
1、基本使用
js
<template>
<constants-checkbox v-model="value" type="accountStatus" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ConstantsCheckbox from '@/components/constantsCheckbox.vue';
const value = ref([]);
</script>
<style lang="less" scoped></style>

ConstantRadio 单选框
参数配置跟 ConstantSelect 下拉框组件差不多
一、 封装(干货很多)
js
<template>
<t-radio-group v-model="modelValue" v-bind="attr" @change="onChange">
<t-radio v-for="(value, key) in data" :key="key" :value="key">
<template v-if="!$slots[key]">{{ value }}</template>
<slot v-else :key="key" :name="key" :value="value"></slot>
</t-radio>
</t-radio-group>
</template>
<script setup lang="ts">
import type { RadioGroupProps } from 'tdesign-vue-next';
import { computed, defineModel, ref } from 'vue';
import { constantLabelMap } from '@/constants';
interface Props extends RadioGroupProps {
type: string;
invalidKeys?: string;
customOption?: Record<string, string>;
}
const props: Props = withDefaults(defineProps<Props>(), {
invalidKeys: ''
});
const attr = computed(() => {
return JSON.parse(JSON.stringify(props));
});
// 处理选项数据
const data = computed(() => {
const constant = constantLabelMap[props.type];
const retObj: Record<string, string> = {};
if (constant) {
const invalidKeysArray = props.invalidKeys
.split(',')
.map((k) => k.trim())
.filter(Boolean);
Object.keys(constant).forEach((key) => {
if (!invalidKeysArray.includes(key)) {
retObj[key] = constant[key];
}
});
}
if (props.customOption) {
return { ...retObj, ...props.customOption };
}
return { ...retObj };
});
const modelValue = defineModel<string | number | boolean>();
const emit = defineEmits(['change']);
const onChange = (value: string | number | boolean) => {
emit('change', value);
};
</script>
<style lang="less" scoped></style>
二、 使用案例
1、 基本使用
js
<template>
<constantsRadio v-model="value" type="accountStatus" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import constantsRadio from '@/components/constantsRadio.vue';
const value = ref('');
</script>
<style lang="less" scoped></style>
