字典管理
字典管理
业务用途
字典管理维护系统中常用的下拉选项数据(如性别、状态、流程类型等),避免把这些枚举值硬编码到代码里。平台字典分两层:
- 字典类型(SysDictType):一个分类,如
system_user_sex(用户性别)。 - 字典数据(SysDictData):某类型下的具体选项,如
0-未知、1-男、2-女。
平台扩展了作用域(scopeType)机制:字典类型分为 GLOBAL(全局通用)与 APP(应用专属,需指定 appId)。查询时优先匹配应用级字典,找不到再回退到全局字典,实现「应用可覆盖全局」的继承效果。

后端无 Spring Cache 缓存
SysDictTypeServiceImpl 与 SysDictDataServiceImpl 未使用 @Cacheable/@CacheEvict 注解,每次查询字典数据都直接走数据库(按 status=0 过滤启用项、按 sortOrder 排序)。前端通过 DictSelect.vue 组件和 DICT_TYPE 常量(frontend/src/utils/dict.ts)在页面层做展示,重复请求由浏览器/网络层处理。如需提升性能,可自行在 getByDictType 上加 @Cacheable 并在增删改时 @CacheEvict。
涉及文件
| 层 | 文件路径 |
|---|---|
| 控制器 | backend/src/main/java/com/lowcode/controller/system/SysDictController.java |
| 类型服务 | backend/src/main/java/com/lowcode/service/impl/SysDictTypeServiceImpl.java |
| 数据服务 | backend/src/main/java/com/lowcode/service/impl/SysDictDataServiceImpl.java |
| 实体 | backend/src/main/java/com/lowcode/entity/SysDictType.java、SysDictData.java |
| 前端 API | frontend/src/api/dict.ts |
| 前端组件 | frontend/src/components/DictSelect.vue、frontend/src/utils/dict.ts |
| 前端页面 | frontend/src/views/system/dict/index.vue |
数据库表
sys_dict_type(字典类型表)
| 字段 | 类型 | 说明 |
|---|---|---|
dict_name | String | 字典名称(如「用户性别」) |
dict_type | String | 字典类型编码(如 system_user_sex,唯一键组成部分) |
scope_type | String | 作用域(GLOBAL全局 / APP应用级) |
app_id | Long | 所属应用 ID(仅 APP 作用域有值,GLOBAL 为 null) |
status | String | 状态(0正常 1禁用) |
remark | String | 备注 |
dict_data_list | List | 字典数据列表(@TableField(exist = false),不入库) |
sys_dict_data(字典数据表)
| 字段 | 类型 | 说明 |
|---|---|---|
dict_type_id | Long | 字典类型 ID(外键) |
dict_type | String | 字典类型编码(冗余字段,便于直接按类型查询) |
dict_label | String | 选项标签(如「男」) |
dict_value | String | 选项值(如 1) |
dict_sort | Integer | 排序(Java 字段名 sortOrder,@TableField("dict_sort")) |
css_class | String | CSS 类名(前端样式定制) |
list_class | String | 列表样式(如 primary/success/danger,el-tag 类型) |
is_default | String | 是否默认选中(Y是 N否) |
status | String | 状态(0正常 1禁用) |
remark | String | 备注 |
后端实现
端点一览
SysDictController 基础路径:/api/system/dict
字典类型:
| HTTP 方法 | 路径 | 方法名 | 说明 |
|---|---|---|---|
| GET | /api/system/dict/type/list | typeList | 分页查询字典类型(支持 dictName/dictType/status/scopeType/appId 过滤) |
| GET | /api/system/dict/type/all | typeAll | 获取全部启用的字典类型(GLOBAL + 指定 appId 的 APP) |
| GET | /api/system/dict/type/{id} | typeGetById | 获取字典类型详情 |
| POST | /api/system/dict/type | typeAdd | 新增字典类型 |
| PUT | /api/system/dict/type | typeUpdate | 修改字典类型 |
| DELETE | /api/system/dict/type/{id} | typeDelete | 删除字典类型(级联删除其下所有字典数据) |
字典数据:
| HTTP 方法 | 路径 | 方法名 | 说明 |
|---|---|---|---|
| GET | /api/system/dict/data/byType/{dictType} | dataByType | 根据类型编码查询字典数据(支持 appId 应用覆盖) |
| GET | /api/system/dict/data/list | dataList | 查询字典数据列表(按 dictTypeId 过滤) |
| GET | /api/system/dict/data/{id} | dataGetById | 获取字典数据详情 |
| POST | /api/system/dict/data | dataAdd | 新增字典数据 |
| PUT | /api/system/dict/data | dataUpdate | 修改字典数据 |
| DELETE | /api/system/dict/data/{id} | dataDelete | 删除字典数据 |
关键代码
字典类型的作用域校验与唯一性校验:APP 作用域必须指定 appId,唯一性按 (tenantId, dictType, scopeType, appId) 组合判定:
private boolean isDictTypeUnique(SysDictType dictType) {
LambdaQueryWrapper<SysDictType> wrapper = new LambdaQueryWrapper<SysDictType>()
.eq(SysDictType::getTenantId, currentTenantId())
.eq(SysDictType::getDictType, dictType.getDictType())
.eq(SysDictType::getScopeType, dictType.getScopeType());
if ("APP".equals(dictType.getScopeType())) {
wrapper.eq(SysDictType::getAppId, dictType.getAppId());
} else {
wrapper.isNull(SysDictType::getAppId);
}
if (dictType.getId() != null) wrapper.ne(SysDictType::getId, dictType.getId());
return dictTypeService.count(wrapper) == 0;
}字典数据的「应用覆盖全局」查询逻辑是核心--resolveDictType 先尝试匹配 APP 作用域,找不到再回退 GLOBAL:
@Override
public List<SysDictData> getByDictType(String dictType, Long appId) {
SysDictType type = resolveDictType(dictType, appId);
if (type == null) return List.of();
return getByDictTypeId(type.getId());
}
private SysDictType resolveDictType(String dictType, Long appId) {
Long tenantId = currentTenantId();
// 1. 若传了 appId,优先找应用级字典
if (appId != null) {
SysDictType appType = dictTypeMapper.selectOne(new LambdaQueryWrapper<SysDictType>()
.eq(SysDictType::getTenantId, tenantId)
.eq(SysDictType::getDictType, dictType)
.eq(SysDictType::getScopeType, "APP")
.eq(SysDictType::getAppId, appId)
.eq(SysDictType::getStatus, "0").last("LIMIT 1"));
if (appType != null) return appType;
}
// 2. 回退到全局字典
return dictTypeMapper.selectOne(new LambdaQueryWrapper<SysDictType>()
.eq(SysDictType::getTenantId, tenantId)
.eq(SysDictType::getDictType, dictType)
.eq(SysDictType::getScopeType, "GLOBAL")
.eq(SysDictType::getStatus, "0").last("LIMIT 1"));
}删除字典类型会级联删除其下所有字典数据,避免孤儿数据:
@DeleteMapping("/type/{id}")
public Result<Void> typeDelete(@PathVariable Long id) {
SysDictType type = getCurrentTenantDictType(id);
if (type == null) return Result.error("字典类型不存在");
dictDataService.remove(new LambdaQueryWrapper<SysDictData>()
.eq(SysDictData::getTenantId, currentTenantId())
.eq(SysDictData::getDictTypeId, id));
dictTypeService.remove(new LambdaQueryWrapper<SysDictType>()
.eq(SysDictType::getTenantId, currentTenantId())
.eq(SysDictType::getId, id));
return Result.success("删除成功");
}前端实现
- API 层
frontend/src/api/dict.ts:分两组,字典类型(getDictTypeList/addDictType/updateDictType/deleteDictType等)与字典数据(getDictDataByType/addDictData等)。 - 组件
frontend/src/components/DictSelect.vue:通用字典下拉组件,传入dictType即可渲染选项。 - 常量
frontend/src/utils/dict.ts:DICT_TYPE对象集中维护常用字典类型编码(如COMMON_STATUS、SYSTEM_USER_SEX、BPM_PROCESS_INSTANCE_STATUS)。 - 页面
frontend/src/views/system/dict/index.vue:左右双栏或 Tab 切换,左侧管理字典类型,右侧管理选中类型下的字典数据。

// 根据类型编码取字典数据(可带 appId 实现应用覆盖)
export function getDictDataByType(dictType: string, appId?: number | null) {
return request.get(`/system/dict/data/byType/${dictType}`,
appId ? { params: { appId } } : undefined)
}操作步骤
- 进入「系统管理 -> 字典管理」。
- 新增字典类型:填名称、类型编码(如
order_status)、作用域(GLOBAL 或 APP)、状态。 - 选中该类型,新增字典数据:填标签、值、排序、listClass(标签颜色)、是否默认。
- 前端页面用
<DictSelect dictType="order_status" v-model="form.status" />渲染下拉。

常见问题
GLOBAL 和 APP 字典有什么区别?
GLOBAL 全局字典所有应用共享,app_id 为空;APP 应用字典绑定特定应用。查询时若指定 appId,会优先返回应用级字典,找不到才回退全局,实现「应用覆盖全局」。
字典数据为什么有 dictType 冗余字段?
sys_dict_data 同时存了 dict_type_id(外键)和 dict_type(编码冗余)。这样按类型编码查询时无需先 join 类型表,直接 eq(dictType) 即可,简化查询。
修改字典后会立即生效吗?
会。后端无缓存,每次查询直接读库,改完立即生效。前端若用 DictSelect 组件,刷新页面即可看到新值。
listClass 是什么?
listClass 对应 Element Plus el-tag 的 type 属性(primary/success/warning/danger/info),用于在列表中以彩色标签展示字典值,提升可读性。
