上周五快下班,前后端又吵起来了。
前端小李:
"哥,能不能别改接口?下拉框又空了!"
后端老王:
"我明明加了审核驳回,你前端没同步!"
我夹在中间,只想回家躺着。 就这么个小小的下拉框状态,前后端来回改了三版,还是错。
1. 问题到底出在哪?
两边各写各的。
- 后端数据库:
0=待审核,1=通过,2=驳回
- 前端下拉框:
0=待审核,1=已通过,2=拒绝
差一个汉字,线上就白屏。
2. 土办法:把字典写死
我一开始也干过:
java
// 后端
if (status == 2) return "驳回";
// 前端
if (value == 2) option.text = "拒绝";
写起来确实快,可要是需求一变,两边都得改,非常的麻烦。
3. 枚举
后来我把字典收进一个枚举里,前后端都吃同一份,世界瞬间清净。
新建一个枚举 StatusEnum.java
java
public enum StatusEnum {
PENDING(0, "待审核"),
PASS(1, "通过"),
REJECT(2, "驳回");
private final int code;
private final String desc;
StatusEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
// 根据 code 拿描述
public static String descOf(Integer code) {
for (StatusEnum e : values()) {
if (e.code == code) {
return e.desc;
}
}
return "未知";
}
// 直接扔给前端当下拉框
public static List<Map<String, Object>> options() {
return Arrays.stream(values())
.map(e -> Map.of("value", e.code, "text", e.desc))
.collect(Collectors.toList());
}
}
后端接口直接复用
java
@GetMapping("/status/options")
public List<Map<String, Object>> statusOptions() {
return StatusEnum.options(); // 一行搞定
}
前端拿到直接用
javascript
axios.get('/status/options').then(res => {
this.statusOptions = res.data; // 再也不用手写
});
这时产品想加"已撤销",枚举里直接加一行CANCEL(3, "已撤销")
就可以了,前后端零改动。 运营要把"驳回"改成"拒绝",只改枚举的desc
,重新部署,完事。
4. 进阶用法
一个系统里,如果很多这种固定选项,比如:
- 订单类型:普通单、秒杀单、预售单
- 支付方式:微信、支付宝、银联
- 审核状态:待提交、审核中、已通过、已驳回
- 会员等级:青铜、白银、黄金、钻石
- 甚至:性别、来源渠道、设备类型......
每个都要下拉框,且都要加接口让前后端对数据,这反而很繁琐。咋整呢?我们往下看。
枚举不止是类型,它也可以是一种数据契约。
我们搞了个EnumRepository
,专门统一管理所有业务枚举。
java
@Component
public class EnumRepository {
// 所有枚举都注册到这里
private final Map<String, List<Map<String, Object>>> enumMap = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
// 注册所有枚举列表
enumMap.put("user_status", UserStatusEnum.list());
enumMap.put("order_type", OrderTypeEnum.list());
enumMap.put("pay_channel", PayChannelEnum.list());
// ... 更多
}
// 提供给前端的统一接口
public List<Map<String, Object>> getEnum(String type) {
return enumMap.getOrDefault(type, Collections.emptyList());
}
public Map<String, List<Map<String, Object>>> getAllEnums() {
return enumMap;
}
}
然后暴露一个接口:
java
@GetMapping("/enums")
public Result<Map<String, List<Map<String, Object>>>> getAllEnums() {
return Result.success(enumRepository.getAllEnums());
}
前端一进来,调一次/enums
,拿到所有下拉框数据:
json
{
"user_status": [
{ "value": 1, "label": "正常" },
{ "value": 2, "label": "禁用" }
],
"order_type": [
{ "value": 1, "label": "普通单" },
{ "value": 2, "label": "秒杀单" }
],
"pay_channel": [
{ "value": 1, "label": "微信" },
{ "value": 2, "label": "支付宝" }
]
}
前端存进Pinia
或Redux
,全局使用。不需要再关心这个字段对应啥选项,直接查enums.user_status
就行。 也封装个Dict
工具,然后调用就可以了。
javascript
// src/utils/dict.js
let dictData = {};
/**
* 初始化字典数据(在登录后或应用启动时调用)
* @param {Object} data 后端返回的全部枚举 { user_status: [...], pay_channel: [...] }
*/
export function initDict(data) {
dictData = { ...data };
}
/**
* 根据类型和值,获取 label
* @param {String} type 枚举类型,如 'user_status'
* @param {Number|String} value 枚举值
* @returns {String} 对应的文本,找不到返回 '-'
*/
export function label(type, value) {
const items = dictData[type];
if (!items) return '-';
const item = items.find(i => i.value == value); // == 避免类型问题
return item ? item.label : '-';
}
/**
* 获取某个枚举的全部选项
* @param {String} type 枚举类型
* @returns {Array} 选项列表 [{value, label}, ...]
*/
export function options(type) {
return dictData[type] || [];
}
/**
* 获取完整项(包含 tagType, disabled 等元信息)
* @param {String} type 枚举类型
* @param {Number|String} value 枚举值
* @returns {Object|null} 完整对象
*/
export function get(type, value) {
const items = dictData[type];
if (!items) return null;
return items.find(i => i.value == value) || null;
}
// 也可以挂到全局,比如 window.Dict
export const Dict = {
label,
options,
get,
init: initDict
};
js
// 根据 type 和 value 查 label
Dict.label('user_status', 1) // 返回 '正常'
// 根据 type 查所有选项
Dict.options('pay_channel')
// 带样式的
Dict.get('order_status', 2) // 返回 { value: 2, label: '已发货', color: 'blue' }
表格列直接用:
js
{
label: '状态',
formatter: row => Dict.label('user_status', row.status)
}
下拉框:
js
<el-select :options="Dict.options('user_status')" />
前端开发再也不写死任何下拉数据。 你可能会说:这不就是个字典表吗? 不一样。 字典表是数据驱动 ,适合运营可配置的场景。 枚举是代码契约,适合"业务规则固定"的场景。 我们用枚举,解决的不是技术问题,而是:
- 沟通成本
- 数据一致性
- 维护复杂度
当前端说:"这个字段的选项在哪?"
你能说:"去/enums
拉一下,或者看UserStatusEnum
。"
那一刻,你就知道:这枚举,值了。 搞定。
我是大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!
📌往期精彩
《Elasticsearch 太重?来看看这个轻量级的替代品 Manticore Search》
《只会写 Mapper 就敢说会 MyBatis?面试官:原理都没懂》
《别学23种了!Java项目中最常用的6个设计模式,附案例》