yunzer_go/pc/docs/dictionary-usage.md
2025-11-11 16:34:49 +08:00

549 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 字典系统使用指南
## 概述
字典Dictionary系统是一个用于管理应用中常用的枚举值和标签化数据的功能。通过字典系统你可以在后台统一管理这些数据而无需修改代码和重新编译部署。
### 字典的核心概念
- **字典类型Dict Type**:用来分类管理字典项,例如 `user_status`(用户状态)、`gender`(性别)等
- **字典编码Dict Code**:字典类型的唯一标识,用于前端查询,例如 `user_status`、`gender`
- **字典项Dict Item**:具体的字典值和标签,包括:
- `dict_value`:存储在数据库中的实际值(例如 `1`、`0`
- `dict_label`:显示给用户的标签文本(例如 "启用"、"禁用"
### 数据库表结构
```sql
-- 字典类型表
CREATE TABLE sys_dict_type (
id BIGINT,
dict_code VARCHAR(50) -- 字典编码(唯一)
dict_name VARCHAR(100) -- 字典名称
status TINYINT -- 是否启用
...
)
-- 字典项表
CREATE TABLE sys_dict_item (
id BIGINT,
dict_type_id BIGINT -- 关联的字典类型ID
dict_label VARCHAR(100) -- 显示标签(如 "启用"
dict_value VARCHAR(100) -- 存储值(如 "1"
status TINYINT -- 是否启用
...
)
```
---
## 后端 API 接口
### 1. 获取字典项(最常用)
**根据字典编码获取字典项列表:**
```http
GET /api/dict/items/code/:code?include_disabled=0
```
**请求示例:**
```bash
curl -X GET "http://localhost:8080/api/dict/items/code/user_status?include_disabled=0"
```
**响应示例:**
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": 1,
"dict_type_id": 101,
"dict_label": "启用",
"dict_value": "1",
"status": 1,
"sort": 1,
...
},
{
"id": 2,
"dict_type_id": 101,
"dict_label": "禁用",
"dict_value": "0",
"status": 1,
"sort": 2,
...
}
]
}
```
**参数说明:**
- `code` (string, 必需):字典编码,例如 `user_status`
- `include_disabled` (int, 可选)是否包含禁用项0 = 不包含默认1 = 包含
---
### 2. 字典管理接口
#### 获取字典类型列表
```http
GET /api/dict/types?parentId=&status=
```
#### 添加字典类型
```http
POST /api/dict/types
{
"dict_code": "user_status",
"dict_name": "用户状态",
"status": 1
}
```
#### 添加字典项
```http
POST /api/dict/items
{
"dict_type_id": 101,
"dict_label": "启用",
"dict_value": "1",
"status": 1,
"sort": 1
}
```
#### 更新/删除字典项
```http
PUT /api/dict/items/:id
DELETE /api/dict/items/:id
```
---
## 前端 APIVue/JavaScript
### 前端 API 文件位置
```
pc/src/api/dict.js
```
### 可用函数
#### getDictItemsByCode(code, includeDisabled = false)
**最常用的函数**,根据字典编码获取字典项列表。
**参数:**
- `code` (string):字典编码,例如 `'user_status'`
- `includeDisabled` (boolean):是否包含禁用项,默认 `false`
**返回:** Promise返回字典项数组
**示例:**
```javascript
import { getDictItemsByCode } from '@/api/dict'
// 获取用户状态字典
const statusItems = await getDictItemsByCode('user_status')
console.log(statusItems)
// 输出:
// [
// { dict_label: '启用', dict_value: '1', ... },
// { dict_label: '禁用', dict_value: '0', ... }
// ]
```
---
#### 其他可用函数
```javascript
// 获取字典类型列表
getDictTypes(params)
// 根据ID获取字典类型
getDictTypeById(id)
// 添加字典类型
addDictType(data)
// 更新字典类型
updateDictType(id, data)
// 删除字典类型
deleteDictType(id)
// 获取字典项列表(需传入参数过滤)
getDictItems(params)
// 根据ID获取字典项
getDictItemById(id)
// 添加字典项
addDictItem(data)
// 更新字典项
updateDictItem(id, data)
// 删除字典项
deleteDictItem(id)
// 批量更新字典项排序
batchUpdateDictItemSort(data)
```
---
## 前端组件中的使用示例
### 示例 1在用户管理页面显示用户状态
**场景**:在用户列表中,根据用户的 `status` 字段显示对应的状态标签。
**文件**`pc/src/views/system/users/index.vue`
**实现步骤**
#### 1. 导入字典 API
```javascript
import { getDictItemsByCode } from '@/api/dict'
```
#### 2. 定义状态字典数据和加载函数
```javascript
<script setup lang="ts">
import { ref, onMounted } from 'vue'
// 状态字典
const statusDict = ref<any[]>([])
// 加载字典数据
const fetchStatusDict = async () => {
try {
const res = await getDictItemsByCode('user_status')
let items: any[] = []
// 兼容不同的返回结构
if (res?.data && Array.isArray(res.data)) {
items = res.data
} else if (Array.isArray(res)) {
items = res
}
statusDict.value = items
} catch (err) {
console.error('Failed to fetch status dict:', err)
statusDict.value = []
}
}
// 在组件挂载时加载字典
onMounted(async () => {
await fetchStatusDict()
// ... 其他初始化逻辑
})
</script>
```
#### 3. 定义辅助函数
```javascript
// 根据状态值获取字典标签
const getStatusLabel = (status: any) => {
const sval = status !== undefined && status !== null ? String(status) : ''
// 查找匹配的字典项
const item = statusDict.value.find(
(d: any) => String(d.dict_value) === sval || d.dict_value === status
)
if (item && item.dict_label) {
return item.dict_label
}
// 兼容旧逻辑:如果没有匹配的字典项
if (status === 1 || sval === '1' || sval === 'active') return '启用'
return '禁用'
}
// 根据标签确定 el-tag 的样式类型
const getStatusTagType = (status: any) => {
const label = getStatusLabel(status)
if (!label) return 'info'
const l = label.toString()
if (l.includes('启用') || l.includes('正常') || l.includes('active')) return 'success'
if (l.includes('禁用') || l.includes('停用') || l.includes('inactive')) return 'danger'
return 'info'
}
```
#### 4. 在模板中使用
```vue
<template>
<el-table :data="users">
<!-- 其他列 -->
<!-- 状态列 -->
<el-table-column prop="status" label="状态" width="120">
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.status)">
{{ getStatusLabel(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
</el-table>
</template>
```
---
### 示例 2在下拉选择中使用字典
**场景**:在"添加/编辑用户"对话框中,用字典项填充状态下拉选择。
```vue
<template>
<el-dialog title="编辑用户">
<el-form :model="form">
<!-- 其他字段 -->
<!-- 状态下拉 -->
<el-form-item label="状态">
<el-select v-model="form.status">
<el-option
v-for="item in statusDict"
:key="item.dict_value"
:label="item.dict_label"
:value="item.dict_value"
/>
</el-select>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script setup lang="ts">
const statusDict = ref<any[]>([])
const fetchStatusDict = async () => {
try {
const res = await getDictItemsByCode('user_status')
const items = res?.data || res || []
statusDict.value = Array.isArray(items) ? items : []
} catch (err) {
statusDict.value = []
}
}
onMounted(async () => {
await fetchStatusDict()
})
</script>
```
---
### 示例 3多个字典项的场景
**场景**:同时加载多个字典(用户状态、性别、部门类型等)。
```javascript
<script setup lang="ts">
import { getDictItemsByCode } from '@/api/dict'
import { ref, onMounted } from 'vue'
// 定义多个字典
const statusDict = ref<any[]>([])
const genderDict = ref<any[]>([])
const deptTypeDict = ref<any[]>([])
// 统一加载函数
const fetchAllDicts = async () => {
try {
const [status, gender, deptType] = await Promise.all([
getDictItemsByCode('user_status'),
getDictItemsByCode('gender'),
getDictItemsByCode('dept_type'),
])
statusDict.value = status?.data || status || []
genderDict.value = gender?.data || gender || []
deptTypeDict.value = deptType?.data || deptType || []
} catch (err) {
console.error('Failed to fetch dicts:', err)
}
}
onMounted(async () => {
await fetchAllDicts()
})
</script>
```
---
## 最佳实践
### 1. 缓存字典数据
避免在每个组件中都调用字典 API。建议在全局 store 中缓存字典数据:
**文件**`pc/src/stores/dict.ts`
```typescript
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { getDictItemsByCode } from '@/api/dict'
export const useDictStore = defineStore('dict', () => {
const dicts = ref<Record<string, any[]>>({})
const fetchDict = async (code: string) => {
if (dicts.value[code]) {
return dicts.value[code]
}
try {
const res = await getDictItemsByCode(code)
const items = res?.data || res || []
dicts.value[code] = Array.isArray(items) ? items : []
return dicts.value[code]
} catch (err) {
console.error(`Failed to fetch dict ${code}:`, err)
return []
}
}
return { dicts, fetchDict }
})
```
**在组件中使用:**
```javascript
import { useDictStore } from '@/stores/dict'
const dictStore = useDictStore()
const statusDict = await dictStore.fetchDict('user_status')
```
### 2. 创建字典枚举文件
建议为常用字典创建枚举文件,便于维护:
**文件**`pc/src/constants/dicts.ts`
```typescript
// 字典编码常量
export const DICT_CODES = {
USER_STATUS: 'user_status',
GENDER: 'gender',
DEPT_TYPE: 'dept_type',
POSITION_LEVEL: 'position_level',
} as const
// 用于 TypeScript 类型
export type DictCode = typeof DICT_CODES[keyof typeof DICT_CODES]
```
**在组件中使用:**
```javascript
import { DICT_CODES } from '@/constants/dicts'
import { useDictStore } from '@/stores/dict'
const dictStore = useDictStore()
const statusDict = await dictStore.fetchDict(DICT_CODES.USER_STATUS)
```
### 3. 处理不同的数据值类型
字典值可能是数字、字符串或其他类型。确保比较时进行正确的类型转换:
```javascript
const getStatusLabel = (status: any) => {
// 转换为字符串便于比较
const statusStr = String(status)
const item = statusDict.value.find((d: any) => {
// 支持多种比较方式
return (
String(d.dict_value) === statusStr ||
d.dict_value === status ||
d.dict_value == status // 宽松比较
)
})
return item?.dict_label || '未知'
}
```
### 4. 错误处理
始终为字典加载添加适当的错误处理和回退机制:
```javascript
const fetchStatusDict = async () => {
try {
const res = await getDictItemsByCode('user_status')
statusDict.value = res?.data || []
} catch (err) {
console.error('Failed to fetch status dict:', err)
// 使用默认的回退数据
statusDict.value = [
{ dict_label: '启用', dict_value: '1' },
{ dict_label: '禁用', dict_value: '0' },
]
}
}
```
---
## 常见问题
### Q1: 字典数据在页面刷新后丢失了?
**A:** 这是正常的,字典数据只在组件的生命周期内存在。建议使用全局 storePinia来缓存字典数据或在每个需要的组件中通过 `onMounted` 加载。
### Q2: 后端添加了新的字典项,前端不显示新数据?
**A:** 前端缓存了字典数据。有两种解决方案:
1. 刷新浏览器页面,重新加载字典
2. 在后端修改字典后,调用缓存清除接口(如果有的话),或手动清除前端 store 中的字典缓存
### Q3: 字典编码对应的字典类型不存在?
**A:** 确保:
1. 后端已在 `sys_dict_type` 表中添加了对应的字典类型记录
2. 字典类型的状态(`status`)为启用(通常为 1
3. 至少添加了一项字典项(`sys_dict_item` 表中有数据)
4. 字典编码(`dict_code`)完全匹配(区分大小写)
### Q4: 如何在后台管理系统中管理字典?
**A:** 通常在系统设置或配置模块中有"字典管理"功能,可以:
- 添加/编辑/删除字典类型
- 管理字典项(标签、值、排序、启用/禁用等)
具体路径取决于你的后台管理系统设计。
---
## 总结
使用字典系统的核心步骤:
1. **后端准备**:在 `sys_dict_type``sys_dict_item` 表中添加字典数据
2. **前端导入**`import { getDictItemsByCode } from '@/api/dict'`
3. **加载字典**:在 `onMounted` 或其他合适位置调用 API 加载
4. **使用字典**:通过 `dict_value``dict_label` 来显示和存储数据
5. **最优化**:使用 Pinia store 缓存字典,避免重复请求
通过字典系统,你可以灵活地管理应用中的枚举值和标签数据,而无需修改代码。