# 5.11 字典配置与使用
:::tip 本章概要
数据字典的完整使用流程,从新增字典类型到在代码中使用字典数据的实操指南。字典是系统中管理枚举值的标准方式,替代硬编码,实现"数据与代码分离"。
:::
## 数据字典结构
数据字典由两层组成:
```text
字典类型(think_dict) 字典项(think_dict_item)
├── gender(性别) ├── 1 → 男
│ └── 2 → 女
├── user_status(用户状态) ├── 0 → 禁用
│ └── 1 → 启用
├── article_status(文章状态) ├── 0 → 草稿
│ └── 1 → 已发布
└── ...
```
| 层级 | 表 | 说明 |
|------|-----|------|
| 字典类型 | `think_dict` | 字典分类(如"性别""用户状态") |
| 字典项 | `think_dict_item` | 字典的具体选项(如"1-男""2-女") |
### 表结构
**think_dict(字典主表):**
| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `id` | INT | 主键 | 1 |
| `name` | VARCHAR(50) | 字典名称 | 性别 |
| `code` | VARCHAR(50) | 字典编码(唯一) | gender |
| `remark` | VARCHAR(500) | 备注 | 性别字典 |
| `is_delete` | TINYINT(1) | 软删除 | 0 |
| `create_user` | VARCHAR(50) | 创建人 | admin |
| `create_time` | DATETIME | 创建时间 | |
| `update_user` | VARCHAR(50) | 更新人 | |
| `update_time` | DATETIME | 更新时间 | |
**think_dict_item(字典项表):**
| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `id` | INT | 主键 | 1 |
| `dict_id` | INT | 字典 ID(外键) | 1 |
| `name` | VARCHAR(50) | 项名称(显示标签) | 男 |
| `value` | VARCHAR(50) | 项值(存储值) | 1 |
| `sort` | INT | 排序 | 1 |
| `note` | VARCHAR(200) | 备注 | |
| `is_delete` | TINYINT(1) | 软删除 | 0 |
| `create_user` | VARCHAR(50) | 创建人 | |
| `create_time` | DATETIME | 创建时间 | |
| `update_user` | VARCHAR(50) | 更新人 | |
| `update_time` | DATETIME | 更新时间 | |
## 系统内置字典
| 字典编码 | 字典名称 | 字典项 |
|----------|---------|--------|
| `gender` | 性别 | 0=女, 1=男 |
| `user_status` | 用户状态 | 0=禁用, 1=启用 |
| `article_status` | 文章状态 | 0=草稿, 1=已发布 |
| `tenant_status` | 租户状态 | 0=禁用, 1=启用 |
| `example_type` | 案例类型 | 0=类型一, 1=类型二 |
| `example_status` | 案例状态 | 0=禁用, 1=启用 |
| `data_scope` | 数据权限 | 1=全部, 2=本部门, 3=仅本人 |
## 实操:新增一个字典类型
### 场景
需要为"案例类型"模块添加一个下拉选项:`1-基础 2-进阶 3-高级`。
### 第 1 步:在后台添加字典类型
登录管理后台,进入「数据管理 → 字典管理」:
1. 点击「新增」按钮
2. 填写信息:
| 字段 | 值 |
|------|-----|
| 字典名称 | 案例类型 |
| 字典编码 | example_type |
| 状态 | 启用 |
3. 保存
### 第 2 步:添加字典项
在字典类型列表中点击「案例类型」,进入字典项管理:
1. 添加字典项:
| 字典值 | 字典标签 | 排序 |
|--------|---------|------|
| 1 | 基础 | 1 |
| 2 | 进阶 | 2 |
| 3 | 高级 | 3 |
2. 保存
### 第 3 步:在 Logic 中关联字典
在模块的 Logic 中配置 `serializeMaps`:
```php
class ExampleLogic extends BaseLogic
{
// ... 其他配置 ...
/**
* 枚举显示名映射
*
* @var array
*/
protected array $serializeMaps = [
'type' => 'example_type',
'status' => 'example_status',
];
}
```
配置后,列表和详情接口会自动将 `type=1` 转换为 `typeText='基础'`。
### 第 4 步:在 Validate 中校验枚举值(可选)
```php
class ExampleValidate extends Validate
{
protected $rule = [
'name' => 'require',
'type' => 'in:1,2,3', // 限制为合法枚举值
'status' => 'in:0,1',
];
protected $scene = [
'add' => ['name', 'type', 'status'],
'update' => ['name', 'type', 'status'],
];
}
```
## 在后端代码中使用
### 方式 1:通过 serializeMaps 自动转换(推荐)
```php
// Logic 中配置后,查询数据自动补全
protected array $serializeMaps = [
'status' => 'example_status',
];
// 查询结果自动包含 statusText 字段
// { "id": 1, "name": "测试", "status": 1, "statusText": "启用" }
```
### 方式 2:手动调用 DictService
```php
use app\service\DictService;
// 根据值获取名称
$text = DictService::getText('example_status', 1); // '启用'
// 根据名称反向获取值(导入时用)
$value = DictService::getValue('example_status', '启用'); // '1'
// 获取下拉框选项
$options = DictService::getOptions('example_status');
// [['label'=>'启用','value'=>'1'], ['label'=>'禁用','value'=>'0']]
// 获取完整字典项(含 id、sort、note)
$items = DictService::getFullItems('example_status');
```
### 方式 3:在导入时反向映射
```php
// ExampleLogic::import()
foreach ($data as &$row) {
// Excel 中"基础" → 数据库中 1
if (!empty($row['type']) && !is_numeric($row['type'])) {
$row['type'] = DictService::getValue('example_type', $row['type']);
}
}
```
## 在前端使用
### 获取字典项
```typescript
// ui/src/api/common/index.ts
export function getDictItemList(code: string) {
return http.request({
url: '/dict/item/getDictItemList/' + code,
method: 'GET',
});
}
```
### 下拉框使用
```vue
{{ item.name }}
```
### 多个字典批量获取
```typescript
// 同时获取多个字典选项
const [genderOptions, statusOptions] = await Promise.all([
getDictItemList('gender'),
getDictItemList('user_status'),
]);
```
### 封装为通用 Hook
```typescript
// ui/src/hooks/web/useDict.ts
import { ref, onMounted } from 'vue';
import { getDictItemList } from '@/api/common/index';
export function useDict(code: string) {
const options = ref([]);
const loading = ref(false);
const load = async () => {
loading.value = true;
try {
options.value = await getDictItemList(code);
} finally {
loading.value = false;
}
};
onMounted(() => load());
return { options, loading, reload: load };
}
// 使用
const { options: genderOptions } = useDict('gender');
const { options: statusOptions } = useDict('user_status');
```
### 字典值显示为 Tag
```vue
{{ row.statusText }}
```
## 缓存管理
DictService 采用两级缓存(请求级 + 持久缓存,1小时过期)。修改字典数据后需刷新缓存:
```bash
# 调用刷新接口
GET /api/dict/refreshCache
```
### 缓存失效时机
| 时机 | 操作 | 说明 |
|------|------|------|
| 字典项新增/修改/删除 | 自动清除该字典缓存 | DictLogic 中调用 `DictService::clearCache($code)` |
| 手动刷新 | `GET /dict/refreshCache` | 清除所有字典缓存 |
| 缓存过期 | 自动失效 | 1 小时 TTL |
:::warning 字典数据变更后必须刷新缓存
修改字典项后,如果不刷新缓存,前端显示的可能仍是旧数据。建议在字典管理的增删改接口中自动调用 `clearCache()`。
:::
## 字典 vs 硬编码
| 方式 | 优点 | 缺点 |
|------|------|------|
| **字典(推荐)** | 可在后台动态修改,无需改代码重启 | 需要额外的数据库查询(有缓存) |
| 硬编码 | 简单直接,无额外查询 | 修改需改代码重新部署 |
**何时用字典:** 值可能变化(状态、类型、分类)、需要前端下拉框、需要多处复用
**何时硬编码:** 值几乎不变(性别只有男女)、单次使用、性能极致敏感
## 命名规范
| 类型 | 命名规范 | 示例 |
|------|---------|------|
| 字典编码 | 小写下划线 | `example_type`、`user_status` |
| 字典值 | 数字字符串 | `1`、`2`、`3` |
| 字典标签 | 中文名称 | `基础`、`启用` |
| 字段名 | 小写下划线 | `type`、`status` |
| 显示名字段 | 字段名 + Text | `typeText`、`statusText` |
## 最佳实践
| 实践 | 说明 |
|------|------|
| 编码统一 | 同一字典编码全局唯一,如 `user_status` |
| 值用数字 | 字典值使用数字字符串(`1`、`2`),便于排序和比较 |
| 标签用中文 | 字典标签使用中文(`启用`、`禁用`),便于前端直接显示 |
| 排序有序 | 通过 `sort` 字段控制下拉框选项顺序 |
| 备注说明 | 在 `note` 字段记录特殊值的含义,便于维护 |
| Validate 校验 | 在 Validate 中用 `in:0,1` 限制合法值 |
| 导入导出 | 导入时用 `getValue` 反向映射,导出时用 `getText` 正向翻译 |