数据字典设计文档.md 11 KB

数据字典设计文档

一、设计背景

前端开发中,大量存在"字段值 → 展示值 → 颜色标签"的映射需求。例如表格中状态列的 0/1 需要显示为"禁用/启用",下拉选择器需要选项列表,标签需要根据值显示不同颜色。

在没有字典机制时,这些映射以硬编码形式散落在各个页面的函数、map 对象、模板三元表达式中,带来以下问题:

  • 重复定义:同一个 user_status(0=禁用, 1=启用)在多个页面中重复硬编码
  • 不一致风险:不同页面可能定义不同的展示文本(如"禁用"/"停用"/"无效")
  • 修改成本高:新增一个状态或修改展示文本需要改动多处代码并重新发布
  • 魔法值泛滥:代码中到处是 === 0 ? '禁用' : '启用',可读性和可维护性差

字典功能的设计初衷是:将值转换映射统一收口到数据库,前端组件自动读取并渲染,消除硬编码

二、数据模型

2.1 数据库表结构

表名: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 更新时间

2.2 设计特点

无字典类型表:不设独立的"字典类型"实体。code 字段即类型标识,共享同一 code 的多条记录组成一个字典分组。这种扁平化设计降低了查询复杂度。

code 命名约定:字典 code 采用 类名.属性名 格式(如 Order.statusWashStation.type),与业务实体类属性直接对应,前端开发者可直观地从字段名推断对应的字典 code。

2.3 唯一性约束

同一 code 分组内,namevalue 均不可重复。后端 DataDictServiceImpl.saveOrUpdate 方法在写入前执行校验:

字典[code]取值异常,请核对

三、架构设计

3.1 整体架构

┌─────────────────────────────────────────────────┐
│                    前端                          │
│  ExtDSelect / ExtDLabel / dictUtil              │
│         ↓ 读取                                   │
│  sessionStorage / uni.Storage (key: "dicts")    │
│         ↑ 登录/启动时一次性拉取                   │
├─────────────────────────────────────────────────┤
│                    后端                           │
│  DataDictController                              │
│         ↓                                        │
│  DataDictService                                │
│         ↓                                        │
│  t_data_dict (MySQL)                            │
└─────────────────────────────────────────────────┘

3.2 缓存策略

层级 缓存方式 生命周期 说明
后端 无缓存 每次请求直接查库,字典变更低频,无缓存失效问题
前端 sessionStorage / uni.Storage 登录会话 登录后调用 POST /dataDict/list (pageSize=1024) 全量拉取,按 code 分组存储

为什么后端不缓存:字典数据变更频率极低(设计理念为"定义后不可修改")。前端 sessionStorage 缓存的生命周期恰好是一个登录会话。免去了 Redis 同步/失效的运维复杂度。

为什么全量拉取:字典数据量小(通常 50-200 条),一次请求全量获取后,后续所有字典组件均为纯内存操作,零网络延迟。

3.3 后端 API

方法 路径 认证 说明
POST /dataDict/list 管理端需登录 按 code/name 模糊查询,前端以 pageSize=1024 拉取全量
POST /dataDict/saveOrUpdate dict.add/edit 权限 批量保存/更新,含唯一性校验
POST /dict/list 无需认证 小程序端公开接口

四、前端实现

4.1 admin-web-new (Vue 3)

工具模块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" />

4.2 admin-web (Vue 2)

工具模块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'} 即可自动渲染为对应字典组件。

4.3 car-wash-mp (UniApp 小程序)

工具函数src/utils/common.ts

函数 说明
fmtDictName(code, value) 根据 code 和 value 获取展示名称
getServicePhone() Service.phone 字典获取客服电话

字典数据在 App.vueonLaunch 中通过 POST /dict/list(无需认证)全量拉取,使用 uni.setStorage({key: 'dict'}) 缓存。

五、字典条目清单

5.1 种子数据(init.sql)

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=消费

5.2 业务字典(通过管理界面维护,部分)

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 客服电话

六、设计理念与约束

核心原则

字典以常量形式定义在业务实体类中,业务逻辑以常量替代,取消魔法值

运营约束

  • 字典只提供查询接口,修改和新增由数据库 DML 执行实现
  • 字典定义后不可修改 code,不可删除,可废弃
  • 为保持与前端及关联系统一致,字典条目需全量维护

适用场景

字典机制适合以下场景:

  • 状态/类型等有限枚举值的展示
  • 需要下拉选择的枚举值列表
  • 带颜色标记的状态标签

不适合的场景:

  • 动态变化频繁的数据
  • 数据量巨大的列表(字典全量缓存在前端,条目过多影响性能)

七、最佳实践

前端开发

// 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" />

新增字典步骤

  1. 执行 SQL 插入字典数据到 t_data_dict
  2. 前端更新后会自动加载新字典
  3. 在页面中使用字典工具函数或组件引用新的字典 code