前端开发中,大量存在"字段值 → 展示值 → 颜色标签"的映射需求。例如表格中状态列的 0/1 需要显示为"禁用/启用",下拉选择器需要选项列表,标签需要根据值显示不同颜色。
在没有字典机制时,这些映射以硬编码形式散落在各个页面的函数、map 对象、模板三元表达式中,带来以下问题:
user_status(0=禁用, 1=启用)在多个页面中重复硬编码=== 0 ? '禁用' : '启用',可读性和可维护性差字典功能的设计初衷是:将值转换映射统一收口到数据库,前端组件自动读取并渲染,消除硬编码。
表名:t_data_dict
| 字段 | 类型 | 说明 |
|---|---|---|
id |
BIGINT | 主键 |
company_id |
BIGINT | 租户ID(多租户隔离) |
code |
VARCHAR(50) | 字典编码,作为分组标识 |
name |
VARCHAR(100) | 展示名称 |
value |
VARCHAR(200) | 字典值 |
weight |
INT | 排序权重,控制前端展示顺序 |
color |
VARCHAR(20) | 颜色标记(如 success/danger/warning/info/primary) |
remark |
VARCHAR(255) | 备注说明 |
create_time |
DATETIME | 创建时间 |
update_time |
DATETIME | 更新时间 |
无字典类型表:不设独立的"字典类型"实体。code 字段即类型标识,共享同一 code 的多条记录组成一个字典分组。这种扁平化设计降低了查询复杂度。
code 命名约定:字典 code 采用 类名.属性名 格式(如 Order.status、WashStation.type),与业务实体类属性直接对应,前端开发者可直观地从字段名推断对应的字典 code。
同一 code 分组内,name 和 value 均不可重复。后端 DataDictServiceImpl.saveOrUpdate 方法在写入前执行校验:
字典[code]取值异常,请核对
┌─────────────────────────────────────────────────┐
│ 前端 │
│ ExtDSelect / ExtDLabel / dictUtil │
│ ↓ 读取 │
│ sessionStorage / uni.Storage (key: "dicts") │
│ ↑ 登录/启动时一次性拉取 │
├─────────────────────────────────────────────────┤
│ 后端 │
│ DataDictController │
│ ↓ │
│ DataDictService │
│ ↓ │
│ t_data_dict (MySQL) │
└─────────────────────────────────────────────────┘
| 层级 | 缓存方式 | 生命周期 | 说明 |
|---|---|---|---|
| 后端 | 无缓存 | — | 每次请求直接查库,字典变更低频,无缓存失效问题 |
| 前端 | sessionStorage / uni.Storage | 登录会话 | 登录后调用 POST /dataDict/list (pageSize=1024) 全量拉取,按 code 分组存储 |
为什么后端不缓存:字典数据变更频率极低(设计理念为"定义后不可修改")。前端 sessionStorage 缓存的生命周期恰好是一个登录会话。免去了 Redis 同步/失效的运维复杂度。
为什么全量拉取:字典数据量小(通常 50-200 条),一次请求全量获取后,后续所有字典组件均为纯内存操作,零网络延迟。
| 方法 | 路径 | 认证 | 说明 |
|---|---|---|---|
| POST | /dataDict/list |
管理端需登录 | 按 code/name 模糊查询,前端以 pageSize=1024 拉取全量 |
| POST | /dataDict/saveOrUpdate |
需 dict.add/edit 权限 |
批量保存/更新,含唯一性校验 |
| POST | /dict/list |
无需认证 | 小程序端公开接口 |
工具模块:src/utils/dict.ts
| 函数 | 说明 |
|---|---|
dictUtil.loadDicts() |
从服务端拉取全量字典并缓存到 sessionStorage |
dictUtil.getDicts() |
获取完整的 { [code]: DictItem[] } 结构 |
dictUtil.getDictList(code) |
获取指定 code 下的字典条目列表 |
dictUtil.getDictLabel(code, value) |
根据 code 和 value 获取展示名称(输出:"--" / label) |
dictUtil.getDictValue(code, name) |
根据 code 和 name 反查值 |
getDictOptions(code) |
返回 [{label, value}] 格式,用于 el-select 的 options |
formatDict(code, value) |
getDictLabel 的便捷导出 |
getDictColor(code, value) |
根据 code 和 value 获取颜色标记 |
字典组件:
| 组件 | 路径 | 用法 |
|---|---|---|
ExtDSelect |
components/ExtForm/ExtDSelect.vue |
<ext-d-select type="Order.status" v-model="..." /> |
ExtDLabel |
components/ExtForm/ExtDLabel.vue |
<ext-d-label type="Order.status" :value="row.status" /> |
工具模块:src/utils/u.ts
| 函数 | 说明 |
|---|---|
u.fmt.fmtDict(value, code) |
根据 code 和 value 获取展示名称(输出:"--" / label) |
u.fmt.fmtDictColor(value, code) |
根据 code 和 value 获取颜色标记 |
原始数据存储在 Session.get("dicts")(与 admin-web-new 相同的结构)。
字典组件:
| 组件 | 路径 | 用法 |
|---|---|---|
ExtDSelect |
components/form/ExtDSelect.vue |
<ext-d-select type="Order.status" v-model="..." /> |
ExtDLabel |
components/form/ExtDLabel.vue |
<ext-d-label type="Order.status" :value="row.status" /> |
ExtDRadio |
components/form/ExtDRadio.vue |
<ext-d-radio type="user_status" v-model="..." /> |
ExtBoolean |
components/form/ExtBoolean.vue |
布尔值选择器,使用 yes_no 字典 |
表格列和查询表单列在配置中指定 type: 'dict' + conf: {dict: 'xxx'} 即可自动渲染为对应字典组件。
工具函数:src/utils/common.ts
| 函数 | 说明 |
|---|---|
fmtDictName(code, value) |
根据 code 和 value 获取展示名称 |
getServicePhone() |
从 Service.phone 字典获取客服电话 |
字典数据在 App.vue 的 onLaunch 中通过 POST /dict/list(无需认证)全量拉取,使用 uni.setStorage({key: 'dict'}) 缓存。
| code | 条目 |
|---|---|
user_status |
0=禁用(danger), 1=启用(success) |
message_type |
1=系统通知(primary), 2=站内信(success), 3=待办事项(warning), 4=公告通知(info) |
message_status |
0=未读(danger), 1=已读(success), 2=已删除(info) |
priority |
0=普通, 1=重要(warning), 2=紧急(danger) |
notice_status |
0=未开始(info), 1=生效中(success), 2=已结束, 3=已取消(danger) |
yes_no |
0=否(info), 1=是(success) |
Settlement.status |
0=待结算(info), 1=已结算(success), 2=异常结算(danger) |
OptLog.operationType |
CREATE=新增(success), UPDATE=修改(warning), DELETE=删除(danger), QUERY=查询(info), LOGIN=登录(primary), LOGOUT=登出, OTHER=其他 |
OptLog.httpMethod |
GET(success), POST(primary), PUT(warning), DELETE(danger) |
Menu.type |
0=菜单(primary), 1=iframe(warning), 2=外链(danger), 3=按钮(info) |
AdminUser.sex |
0=男, 1=女 |
WalletDetail.type |
1=积分, 2=红包, 3=消费 |
| code | 业务含义 |
|---|---|
AdminUser.status |
管理员用户状态 |
Order.status |
订单状态 |
Order.pay |
支付状态 |
Order.openType |
开单方式 |
Order.closeType |
关单方式 |
Order.feeType |
费用类型 |
OrderCard.type |
订单卡类型 |
WashStation.status |
站点运营状态 |
WashStation.type |
站点类型 |
WashDevice.status |
设备状态 |
WashDevice.foam |
泡沫能力 |
WashDevice.water |
水能力 |
Activity.discountType |
活动优惠类型 |
Banner.status |
横幅状态 |
Investor.status |
投资人状态 |
Department.status |
部门状态 |
Invoice.status |
发票状态 |
Feedback.type |
反馈类型 |
SplitRecord.type |
分账类型 |
SplitRecord.status |
分账状态 |
WithdrawnRecord.status |
提现审核状态 |
WithdrawnRecord.paymentStatus |
提现打款状态 |
RefundLog.status |
退款状态 |
RefundLog.fundsAccount |
退款资金账户 |
Faq.status |
常见问题状态 |
Object.type |
通用对象类型 |
Service.phone |
客服电话 |
字典以常量形式定义在业务实体类中,业务逻辑以常量替代,取消魔法值
字典机制适合以下场景:
不适合的场景:
// admin-web-new: 表格列中使用 dict 函数
<el-table-column label="状态">
<template #default="{ row }">
<el-tag :type="getDictColor('Order.status', row.status)" size="small">
{{ formatDict('Order.status', row.status) }}
</el-tag>
</template>
</el-table-column>
// admin-web-new: 查询表单中使用字典下拉
<el-form-item label="状态">
<el-select v-model="query.status">
<el-option v-for="opt in getDictOptions('Order.status')"
:key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
// admin-web (Vue 2): 使用字典组件
<ext-d-select type="Order.status" v-model="query.status" />
<ext-d-label type="Order.status" :value="row.status" />
// admin-web (Vue 2): 使用工具函数
const label = u.fmt.fmtDict(value, 'Order.status');
const color = u.fmt.fmtDictColor(value, 'Order.status');
// 禁止:硬编码 map 对象
const statusMap = { 0: '禁用', 1: '启用' };
// 禁止:模板内三元表达式
{{ row.status === 1 ? '启用' : '禁用' }}
// 禁止:硬编码下拉选项
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
t_data_dict 表