签入版本
This commit is contained in:
@@ -9,24 +9,25 @@
|
||||
数据字典由两层组成:
|
||||
|
||||
```text
|
||||
字典类型(think_dict) 字典项(think_dict_item)
|
||||
字典类型(laravel_dict) 字典项(laravel_dict_item)
|
||||
├── gender(性别) ├── 1 → 男
|
||||
│ └── 2 → 女
|
||||
├── user_status(用户状态) ├── 0 → 禁用
|
||||
│ └── 1 → 启用
|
||||
├── article_status(文章状态) ├── 0 → 草稿
|
||||
│ └── 1 → 已发布
|
||||
│ ├── 2 → 女
|
||||
│ └── 3 → 保密
|
||||
├── user_status(用户状态) ├── 1 → 正常
|
||||
│ └── 2 → 禁用
|
||||
├── example_status(案例状态) ├── 1 → 在用
|
||||
│ └── 2 → 停用
|
||||
└── ...
|
||||
```
|
||||
|
||||
| 层级 | 表 | 说明 |
|
||||
|------|-----|------|
|
||||
| 字典类型 | `think_dict` | 字典分类(如"性别""用户状态") |
|
||||
| 字典项 | `think_dict_item` | 字典的具体选项(如"1-男""2-女") |
|
||||
| 字典类型 | `laravel_dict` | 字典分类(如"性别""用户状态") |
|
||||
| 字典项 | `laravel_dict_item` | 字典的具体选项(如"1-男""2-女") |
|
||||
|
||||
### 表结构
|
||||
|
||||
**think_dict(字典主表):**
|
||||
**laravel_dict(字典主表):**
|
||||
|
||||
| 字段 | 类型 | 说明 | 示例 |
|
||||
|------|------|------|------|
|
||||
@@ -40,7 +41,7 @@
|
||||
| `update_user` | VARCHAR(50) | 更新人 | |
|
||||
| `update_time` | DATETIME | 更新时间 | |
|
||||
|
||||
**think_dict_item(字典项表):**
|
||||
**laravel_dict_item(字典项表):**
|
||||
|
||||
| 字段 | 类型 | 说明 | 示例 |
|
||||
|------|------|------|------|
|
||||
@@ -50,23 +51,19 @@
|
||||
| `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 | 更新时间 | |
|
||||
| `is_delete` + 四个审计字段 | | 公共字段 | |
|
||||
|
||||
## 系统内置字典
|
||||
|
||||
| 字典编码 | 字典名称 | 字典项 |
|
||||
|----------|---------|--------|
|
||||
| `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=仅本人 |
|
||||
| `gender` | 性别 | 1=男, 2=女, 3=保密 |
|
||||
| `user_status` | 用户状态 | 1=正常, 2=禁用 |
|
||||
| `tenant_status` | 租户状态 | 1=启用, 0=禁用 |
|
||||
| `example_type` | 案例类型 | 1=类型1, 2=类型2, 3=类型3, 4=类型4 |
|
||||
| `example_status` | 案例状态 | 1=在用, 2=停用 |
|
||||
|
||||
(以数据库初始化脚本中的实际字典项为准,可在「数据管理 → 字典管理」中查看。)
|
||||
|
||||
## 实操:新增一个字典类型
|
||||
|
||||
@@ -84,7 +81,7 @@
|
||||
| 字段 | 值 |
|
||||
|------|-----|
|
||||
| 字典名称 | 案例类型 |
|
||||
| 字典编码 | example_type |
|
||||
| 字典编码 | example_level |
|
||||
| 状态 | 启用 |
|
||||
|
||||
3. 保存
|
||||
@@ -101,24 +98,26 @@
|
||||
| 2 | 进阶 | 2 |
|
||||
| 3 | 高级 | 3 |
|
||||
|
||||
2. 保存
|
||||
2. 保存(保存即触发 `DictItemObserver`,相关缓存自动失效)
|
||||
|
||||
### 第 3 步:在 Logic 中关联字典
|
||||
### 第 3 步:在 Service 中关联字典
|
||||
|
||||
在模块的 Logic 中配置 `serializeMaps`:
|
||||
在模块的 Service 中配置 `serializeMaps`(需 `use HasSerializeMaps`):
|
||||
|
||||
```php
|
||||
class ExampleLogic extends BaseLogic
|
||||
class ExampleService extends BaseService
|
||||
{
|
||||
use HasCrudOperations, HasQueryScopes, HasSerializeMaps, HasUniqueCheck;
|
||||
|
||||
// ... 其他配置 ...
|
||||
|
||||
/**
|
||||
* 枚举显示名映射
|
||||
* 字典翻译配置:[字段 => 字典编码]
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected array $serializeMaps = [
|
||||
'type' => 'example_type',
|
||||
'type' => 'example_level',
|
||||
'status' => 'example_status',
|
||||
];
|
||||
}
|
||||
@@ -126,20 +125,19 @@ class ExampleLogic extends BaseLogic
|
||||
|
||||
配置后,列表和详情接口会自动将 `type=1` 转换为 `typeText='基础'`。
|
||||
|
||||
### 第 4 步:在 Validate 中校验枚举值(可选)
|
||||
### 第 4 步:在 FormRequest 中校验枚举值(可选)
|
||||
|
||||
```php
|
||||
class ExampleValidate extends Validate
|
||||
class ExampleRequest extends BaseRequest
|
||||
{
|
||||
protected $rule = [
|
||||
'name' => 'require',
|
||||
'type' => 'in:1,2,3', // 限制为合法枚举值
|
||||
'status' => 'in:0,1',
|
||||
];
|
||||
protected $scene = [
|
||||
'add' => ['name', 'type', 'status'],
|
||||
'update' => ['name', 'type', 'status'],
|
||||
];
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
'type' => 'nullable|integer|in:1,2,3', // 限制为合法枚举值
|
||||
'status' => 'nullable|integer|in:1,2',
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -148,44 +146,45 @@ class ExampleValidate extends Validate
|
||||
### 方式 1:通过 serializeMaps 自动转换(推荐)
|
||||
|
||||
```php
|
||||
// Logic 中配置后,查询数据自动补全
|
||||
// Service 中配置后,查询数据自动补全
|
||||
protected array $serializeMaps = [
|
||||
'status' => 'example_status',
|
||||
];
|
||||
|
||||
// 查询结果自动包含 statusText 字段
|
||||
// { "id": 1, "name": "测试", "status": 1, "statusText": "启用" }
|
||||
// { "id": 1, "name": "测试", "status": 1, "statusText": "在用" }
|
||||
```
|
||||
|
||||
### 方式 2:手动调用 DictService
|
||||
### 方式 2:手动调用 DictCacheService
|
||||
|
||||
```php
|
||||
use app\service\DictService;
|
||||
use App\Services\DictCacheService;
|
||||
|
||||
// 根据值获取名称
|
||||
$text = DictService::getText('example_status', 1); // '启用'
|
||||
$text = DictCacheService::getText('example_status', 1); // '在用'
|
||||
|
||||
// 根据名称反向获取值(导入时用)
|
||||
$value = DictService::getValue('example_status', '启用'); // '1'
|
||||
$value = DictCacheService::getValue('example_status', '在用'); // '1'
|
||||
|
||||
// 获取下拉框选项
|
||||
$options = DictService::getOptions('example_status');
|
||||
// [['label'=>'启用','value'=>'1'], ['label'=>'禁用','value'=>'0']]
|
||||
$options = DictCacheService::getOptions('example_status');
|
||||
// [['label'=>'在用','value'=>'1'], ['label'=>'停用','value'=>'2']]
|
||||
|
||||
// 获取完整字典项(含 id、sort、note)
|
||||
$items = DictService::getFullItems('example_status');
|
||||
// 批量获取多个字典映射(列表翻译一次取词)
|
||||
$maps = DictCacheService::getBatchTextMaps(['example_type', 'example_status']);
|
||||
```
|
||||
|
||||
### 方式 3:在导入时反向映射
|
||||
|
||||
```php
|
||||
// ExampleLogic::import()
|
||||
// ExampleService::import()(来自项目实际代码)
|
||||
foreach ($data as &$row) {
|
||||
// Excel 中"基础" → 数据库中 1
|
||||
if (!empty($row['type']) && !is_numeric($row['type'])) {
|
||||
$row['type'] = DictService::getValue('example_type', $row['type']);
|
||||
// Excel 中"在用" → 数据库中 1
|
||||
if (! empty($row['status']) && ! is_numeric($row['status'])) {
|
||||
$row['status'] = DictCacheService::getValue('example_status', $row['status']);
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
```
|
||||
|
||||
## 在前端使用
|
||||
@@ -193,8 +192,13 @@ foreach ($data as &$row) {
|
||||
### 获取字典项
|
||||
|
||||
```typescript
|
||||
// ui/src/api/common/index.ts
|
||||
export function getDictItemList(code: string) {
|
||||
// ui/src/api/common/index.ts(实际代码)
|
||||
/**
|
||||
* 根据字典编码查询字典项数据
|
||||
* @param code 字典编码
|
||||
* @returns 返回结果
|
||||
*/
|
||||
export function getDictItemByCode(code) {
|
||||
return http.request({
|
||||
url: '/dict/item/getDictItemList/' + code,
|
||||
method: 'GET',
|
||||
@@ -218,12 +222,12 @@ export function getDictItemList(code: string) {
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { getDictItemList } from '@/api/common/index';
|
||||
import { getDictItemByCode } from '@/api/common/index';
|
||||
|
||||
const statusOptions = ref([]);
|
||||
|
||||
onMounted(async () => {
|
||||
statusOptions.value = await getDictItemList('example_status');
|
||||
statusOptions.value = await getDictItemByCode('example_status');
|
||||
});
|
||||
</script>
|
||||
```
|
||||
@@ -233,41 +237,11 @@ onMounted(async () => {
|
||||
```typescript
|
||||
// 同时获取多个字典选项
|
||||
const [genderOptions, statusOptions] = await Promise.all([
|
||||
getDictItemList('gender'),
|
||||
getDictItemList('user_status'),
|
||||
getDictItemByCode('gender'),
|
||||
getDictItemByCode('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
|
||||
@@ -280,23 +254,27 @@ const { options: statusOptions } = useDict('user_status');
|
||||
|
||||
## 缓存管理
|
||||
|
||||
DictService 采用两级缓存(请求级 + 持久缓存,1小时过期)。修改字典数据后需刷新缓存:
|
||||
DictCacheService 采用两级缓存(请求级 + 持久缓存)。修改字典数据后的缓存失效:
|
||||
|
||||
```bash
|
||||
# 调用刷新接口
|
||||
# 手动刷新接口(认证组内)
|
||||
GET /api/dict/refreshCache
|
||||
|
||||
# 命令行兜底
|
||||
php artisan dict:clear-cache
|
||||
```
|
||||
|
||||
### 缓存失效时机
|
||||
|
||||
| 时机 | 操作 | 说明 |
|
||||
|------|------|------|
|
||||
| 字典项新增/修改/删除 | 自动清除该字典缓存 | DictLogic 中调用 `DictService::clearCache($code)` |
|
||||
| 手动刷新 | `GET /dict/refreshCache` | 清除所有字典缓存 |
|
||||
| 缓存过期 | 自动失效 | 1 小时 TTL |
|
||||
| 字典项新增/修改/删除 | **自动清除** | `DictItemObserver` 监听模型事件 |
|
||||
| 手动刷新 | `GET /api/dict/refreshCache` | 清除所有字典缓存(兜底) |
|
||||
| 命令行清理 | `php artisan dict:clear-cache` | 直接改库等绕过模型事件时使用 |
|
||||
| 缓存过期 | 自动失效 | TTL 见 `DictCacheService::CACHE_TTL` |
|
||||
|
||||
:::warning 字典数据变更后必须刷新缓存
|
||||
修改字典项后,如果不刷新缓存,前端显示的可能仍是旧数据。建议在字典管理的增删改接口中自动调用 `clearCache()`。
|
||||
:::tip
|
||||
正常走后台接口修改字典会自动失效缓存;只有**绕过模型直接操作数据库**(如手工 SQL 改字典项)才需要手动刷新。
|
||||
:::
|
||||
|
||||
## 字典 vs 硬编码
|
||||
@@ -308,7 +286,7 @@ GET /api/dict/refreshCache
|
||||
|
||||
**何时用字典:** 值可能变化(状态、类型、分类)、需要前端下拉框、需要多处复用
|
||||
|
||||
**何时硬编码:** 值几乎不变(性别只有男女)、单次使用、性能极致敏感
|
||||
**何时硬编码:** 值几乎不变、单次使用、性能极致敏感
|
||||
|
||||
## 命名规范
|
||||
|
||||
@@ -329,5 +307,5 @@ GET /api/dict/refreshCache
|
||||
| 标签用中文 | 字典标签使用中文(`启用`、`禁用`),便于前端直接显示 |
|
||||
| 排序有序 | 通过 `sort` 字段控制下拉框选项顺序 |
|
||||
| 备注说明 | 在 `note` 字段记录特殊值的含义,便于维护 |
|
||||
| Validate 校验 | 在 Validate 中用 `in:0,1` 限制合法值 |
|
||||
| 导入导出 | 导入时用 `getValue` 反向映射,导出时用 `getText` 正向翻译 |
|
||||
| FormRequest 校验 | 在 rules 中用 `in:1,2` 限制合法值 |
|
||||
| 导入导出 | 导入时用 `getValue` 反向映射,导出时用 `getText`/`serializeMaps` 正向翻译 |
|
||||
|
||||
Reference in New Issue
Block a user