375 lines
8.6 KiB
Markdown
375 lines
8.6 KiB
Markdown
# 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` - 删除(不再需要)
|
||
|