🍀终于向常量组件下手了,使用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>
相关推荐
时光足迹19 小时前
Tiptap 简单编辑器模版
前端·javascript·react.js
JSLove19 小时前
nginx入门
前端·nginx
时光足迹19 小时前
ThreeJS之GUI控制器
前端·javascript·three.js
时光足迹19 小时前
Tiptap编辑器
前端·javascript·react.js
时光足迹19 小时前
电子书阅读器之笔记高亮(跨段处理)
前端·javascript·react.js
Dabei19 小时前
Android 副屏(Virtual Display)创建与悬浮窗画中画显示实战
前端·架构
RONIN19 小时前
mock模拟后端,生成伪数据接口
vue.js
Hello-Mr.Wang20 小时前
【保姆级教程】MasterGo MCP + Cursor 一键实现 UI 设计稿还原
前端·javascript·vue.js·ai编程
Dabei20 小时前
Android 无障碍服务实现美团/微信自动化:客户端开发实践
前端·设计模式