🍀终于向常量组件下手了,使用TypeScript 基于 TDesign二次封装常量组件 🚀🚀

前言

在日常前端开发中,常量枚举常用于定义一组固定的键值对,如状态码、配置项或类型标识。例如,定义一个API_STATUS枚举来管理请求状态:LOADING, SUCCESS, ERROR。这能避免魔法字符串、增强代码可读性与可维护性,并通过TypeScript的类型检查在编译时发现错误,提升开发体验与协作效率。

该文主要是基于 Tdesigin 进行二次封装三个在日常中经常使用的常量组件,它们分别是 ConstantSelectConstantRadioConstantCheckbox;并且自动化读取常量配置文件,减少代码量,提高开发效率。

前期准备

一、常量定义

单独定义到一个文件

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>
相关推荐
玲小珑2 小时前
LangChain.js 完全开发手册(十三)AI Agent 生态系统与工具集成
前端·langchain·ai编程
布列瑟农的星空2 小时前
什么?sessionStorage可以跨页签?
前端
苏打水com2 小时前
网易前端业务:内容生态与游戏场景下的「沉浸式体验」与「性能优化」实践
前端·游戏·性能优化
恋猫de小郭2 小时前
React 和 React Native 不再直接归属 Meta,React 基金会成立
android·前端·ios
掘金安东尼2 小时前
前端周刊434期(2025年9月29日–10月5日)
前端·javascript·面试
brzhang3 小时前
当我第一次看到 snapDOM,我想:这玩意儿终于能解决网页「截图」这破事了?
前端·后端·架构
掘金安东尼3 小时前
前端周刊433期(2025年9月22日–9月28日)
前端·javascript·github
万少3 小时前
我的HarmonyOS百宝箱
前端
江城开朗的豌豆3 小时前
uni-app弹层遮罩难题?看我如何见招拆招!
前端·javascript·微信小程序