更新字典功能

This commit is contained in:
2025-11-11 18:03:16 +08:00
parent 65b281fe35
commit 8c5859bad5
12 changed files with 880 additions and 827 deletions
+1 -548
View File
@@ -1,548 +1 @@
# 字典系统使用指南
## 概述
字典(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:** 这是正常的,字典数据只在组件的生命周期内存在。建议使用全局 store(Pinia)来缓存字典数据,或在每个需要的组件中通过 `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 缓存字典,避免重复请求
通过字典系统,你可以灵活地管理应用中的枚举值和标签数据,而无需修改代码。
# 字典使用示例
+374
View File
@@ -0,0 +1,374 @@
# Pinia 字典管理系统使用指南
## 系统架构
```
┌─────────────────────────────────────┐
│ API 接口 (getDictItemsByCode) │
│ /api/dict/items/code/{code} │
└──────────────┬──────────────────────┘
┌─────────────────────────────────────┐
│ Pinia Store (useDictStore) │
│ ✅ 自动缓存字典数据 │
│ ✅ 避免重复请求 │
│ ✅ 支持同步/异步访问 │
└──────────────┬──────────────────────┘
┌──────┴──────┐
↓ ↓
┌────────┐ ┌──────────────┐
│组件 │ │Composable │
│直接用 │ │useDict Hook │
└────────┘ └──────────────┘
```
---
## 核心文件说明
### 1. **Store**: `src/stores/dict.js`
字典数据的全局管理器
**主要方法**
```javascript
import { useDictStore } from '@/stores/dict'
const dictStore = useDictStore()
// ✅ 异步获取字典(推荐)
const items = await dictStore.getDictItems('user_status')
// ✅ 同步获取字典(已缓存时)
const items = dictStore.getDictItemsSync('user_status')
// ✅ 获取字典值对应的标签
const label = dictStore.getDictLabel('user_status', 1)
// ✅ 预加载多个字典
await dictStore.preloadDicts(['user_status', 'user_role'])
// ✅ 清空缓存
dictStore.clearCache('user_status')
```
---
---
---
## 使用场景
### 场景1:在列表页加载字典
**文件**: `src/views/system/users/index.vue`
```vue
<script setup>
import { useDictStore } from '@/stores/dict'
import { ref, onMounted } from 'vue'
const dictStore = useDictStore()
const statusDict = ref([])
const fetchStatusDict = async () => {
statusDict.value = await dictStore.getDictItems('user_status')
}
onMounted(async () => {
await fetchStatusDict()
})
</script>
<template>
<!-- 传给子组件 -->
<UserEditDialog :status-dict="statusDict" />
</template>
```
---
### 场景2:在编辑对话框使用字典
**文件**: `src/views/system/users/components/UserEdit.vue`
```vue
<template>
<!-- 状态选择 -->
<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>
</template>
<script setup>
const props = defineProps({
statusDict: {
type: Array,
default: () => [],
},
})
</script>
```
---
### 场景3:直接使用字典Store
直接使用字典Store获取数据:
```vue
<template>
<div>
<p v-if="loading">加载中...</p>
<el-select v-else v-model="status">
<el-option
v-for="item in statusDict"
:key="item.dict_value"
:label="item.dict_label"
:value="item.dict_value"
/>
</el-select>
</div>
</template>
<script setup>
import { useDictStore } from '@/stores/dict'
import { ref, onMounted } from 'vue'
const dictStore = useDictStore()
const status = ref('1')
const statusDict = ref([])
const loading = ref(false)
const fetchStatusDict = async () => {
loading.value = true
try {
const items = await dictStore.getDictItems('user_status')
statusDict.value = items
} catch (error) {
console.error('获取用户状态字典失败:', error)
statusDict.value = []
} finally {
loading.value = false
}
}
onMounted(() => {
fetchStatusDict()
})
</script>
```
---
### 场景4:预加载应用启动时需要的字典
**文件**: `src/main.js`
```javascript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { useDictStore } from '@/stores/dict'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
// 在应用启动后预加载常用字典
const dictStore = useDictStore()
await dictStore.preloadDicts([
'user_status',
'common_status',
'yes_no',
])
app.mount('#app')
```
---
## 字典数据结构
后端返回的字典数据结构:
```json
[
{
"dict_id": 1,
"dict_value": "1",
"dict_label": "启用",
"dict_type": "user_status",
"remarks": "用户启用状态",
"remark": "用户启用状态"
},
{
"dict_id": 2,
"dict_value": "0",
"dict_label": "禁用",
"dict_type": "user_status",
"remarks": "用户禁用状态",
"remark": "用户禁用状态"
}
]
```
**关键字段**
- `dict_value`: 字典值(存储在数据库中)
- `dict_label`: 字典标签(显示给用户)
- `dict_type`: 字典类型编码(如 'user_status'
---
## 最佳实践
### ✅ DO
1. **使用字符串而不是硬编码数字**
```javascript
// ✅ 好
dictStore.getDictItems('user_status')
// ❌ 差
// 避免直接使用数字,应该使用字符串
```
2. **在父组件加载,通过 props 传给子组件**
```javascript
// ✅ 父组件负责数据,子组件负责展示
// index.vue
const statusDict = await dictStore.getDictItems('user_status')
// UserEdit.vue
const props = defineProps({ statusDict: Array })
```
3. **直接使用字典Store**
```javascript
// ✅ 直接使用Store获取数据
const statusDict = await dictStore.getDictItems('user_status')
```
4. **预加载常用字典**
```javascript
// ✅ 应用启动时预加载,避免页面初始化时加载
await dictStore.preloadDicts([...])
```
### ❌ DON'T
1. **不要在多个地方重复加载同一个字典**
```javascript
// ❌ 糟糕:重复加载
// 页面A
const dict1 = await dictStore.getDictItems('user_status')
// 页面B(Store 会自动缓存,但代码看起来重复)
const dict2 = await dictStore.getDictItems('user_status')
```
2. **不要忘记处理加载状态**
```javascript
// ❌ 可能展示空白
const { statusDict } = useUserStatusDict()
// ✅ 处理加载状态
const { statusDict, loading } = useUserStatusDict()
if (loading) { /* 显示加载中 */ }
```
3. **不要混用不同的字典访问方式**
```javascript
// ❌ 混乱
const dict1 = await dictStore.getDictItems('user_status')
const dict2 = dictStore.getDictItemsSync('user_role')
// ✅ 统一使用
const dict1 = await dictStore.getDictItems('user_status')
const dict2 = await dictStore.getDictItems('user_role')
```
---
## 性能优化建议
| 优化项 | 说明 |
|------|------|
| **缓存** | Store 自动缓存,同一个字典只请求一次 |
| **预加载** | 在路由切换前预加载需要的字典 |
| **同步访问** | 已加载的字典可用 `getDictItemsSync` 同步获取 |
| **避免重复** | 不要在多个组件重复请求同一个字典 |
---
## 故障排查
### 问题1:状态选项为空
**原因**:字典未加载
**解决**
```javascript
// ❌ 错误:字典还未加载
const statusDict = dictStore.getDictItemsSync('user_status') // 返回 []
// ✅ 正确:等待异步加载完成
const statusDict = await dictStore.getDictItems('user_status')
```
### 问题2:重复加载字典
**原因**:没有使用 Store 的缓存
**解决**
```javascript
// 所有调用都会自动使用缓存,只请求一次
await dictStore.getDictItems('user_status') // 首次:发送请求
await dictStore.getDictItems('user_status') // 第二次:返回缓存
```
### 问题3:字典显示不对
**原因**value 类型不匹配(如 1 vs "1"
**解决**
```javascript
// Store 会自动处理类型匹配
const item = items.find(i =>
String(i.dict_value) === String(value) || i.dict_value === value
)
```
---
## 集成检清表
- [x] 创建 `src/stores/dict.js` - Store
- [x] 在 `index.vue` 中导入 `useDictStore`
- [x] 在 `UserEdit.vue` 中使用 `useDictStore` 获取字典数据
- [x] 测试字典加载和显示
- [x] 验证缓存功能(打开浏览器 DevTools 检查 Network
- [x] 预加载常用字典(可选)
---
## 相关文件修改
已修改的文件:
- ✅ `src/stores/dict.js` - 新建
- ✅ `src/views/system/users/index.vue` - 使用 `useDictStore`
- ✅ `src/views/system/users/components/UserEdit.vue` - 使用字典功能
- ✅ `src/constants/dictCodes.js` - 删除(不再需要)
- ✅ `src/composables/useDict.js` - 删除(不再需要)