更新字典功能
This commit is contained in:
parent
65b281fe35
commit
8c5859bad5
@ -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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前端 API(Vue/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
pc/docs/pinia-dict-guide.md
Normal file
374
pc/docs/pinia-dict-guide.md
Normal 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` - 删除(不再需要)
|
||||
|
||||
@ -50,5 +50,5 @@ body {
|
||||
}
|
||||
}
|
||||
.el-form-item__label{
|
||||
min-width: 60px;
|
||||
min-width: 80px !important;
|
||||
}
|
||||
179
pc/src/stores/dict.js
Normal file
179
pc/src/stores/dict.js
Normal file
@ -0,0 +1,179 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { getDictItemsByCode } from '@/api/dict'
|
||||
|
||||
/**
|
||||
* 字典 Store
|
||||
*
|
||||
* 用于全局管理系统字典数据
|
||||
* 缓存字典数据避免重复请求,提高性能
|
||||
*
|
||||
* 使用示例:
|
||||
* const dictStore = useDictStore()
|
||||
* const statusDict = await dictStore.getDictItems('user_status')
|
||||
* const roleDict = dictStore.getDictItemsSync('user_role') // 已加载则同步返回
|
||||
*/
|
||||
export const useDictStore = defineStore('dict', () => {
|
||||
// 字典缓存:{ dictCode: [...items] }
|
||||
const dictCache = ref({})
|
||||
|
||||
// 正在加载的字典代码集合
|
||||
const loadingCodes = ref(new Set())
|
||||
|
||||
/**
|
||||
* 获取字典项(异步)
|
||||
* @param {string} code - 字典编码,如 'user_status'
|
||||
* @returns {Promise<Array>} 字典项数组
|
||||
*/
|
||||
async function getDictItems(code) {
|
||||
// 如果缓存中已有,直接返回
|
||||
if (dictCache.value[code]) {
|
||||
return dictCache.value[code]
|
||||
}
|
||||
|
||||
// 避免重复请求:如果已在加载中,等待
|
||||
if (loadingCodes.value.has(code)) {
|
||||
// 等待加载完成(最多 5 秒)
|
||||
return await new Promise((resolve) => {
|
||||
let count = 0
|
||||
const timer = setInterval(() => {
|
||||
if (dictCache.value[code]) {
|
||||
clearInterval(timer)
|
||||
resolve(dictCache.value[code])
|
||||
}
|
||||
count++
|
||||
if (count > 50) {
|
||||
clearInterval(timer)
|
||||
resolve([])
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
// 标记为正在加载
|
||||
loadingCodes.value.add(code)
|
||||
|
||||
try {
|
||||
const res = await getDictItemsByCode(code)
|
||||
let items = []
|
||||
|
||||
// 兼容不同的 API 响应格式
|
||||
if (res?.data && Array.isArray(res.data)) {
|
||||
items = res.data
|
||||
} else if (Array.isArray(res)) {
|
||||
items = res
|
||||
} else if (res?.data?.data && Array.isArray(res.data.data)) {
|
||||
items = res.data.data
|
||||
}
|
||||
|
||||
// 缓存字典项
|
||||
dictCache.value[code] = items
|
||||
// console.log(`✅ 字典 [${code}] 已加载,共 ${items.length} 项`)
|
||||
|
||||
return items
|
||||
} catch (error) {
|
||||
console.error(`❌ 加载字典 [${code}] 失败:`, error)
|
||||
dictCache.value[code] = []
|
||||
return []
|
||||
} finally {
|
||||
// 移除加载标记
|
||||
loadingCodes.value.delete(code)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步获取字典项(如果已缓存)
|
||||
* @param {string} code - 字典编码
|
||||
* @returns {Array} 字典项数组,未缓存则返回空数组
|
||||
*/
|
||||
function getDictItemsSync(code) {
|
||||
return dictCache.value[code] || []
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载字典(在应用启动时调用)
|
||||
* @param {Array<string>} codes - 字典编码数组
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function preloadDicts(codes) {
|
||||
const promises = codes.map(code => getDictItems(code))
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典编码和值获取标签
|
||||
* @param {string} code - 字典编码
|
||||
* @param {any} value - 字典值
|
||||
* @returns {string} 字典标签
|
||||
*/
|
||||
function getDictLabel(code, value) {
|
||||
const items = getDictItemsSync(code)
|
||||
if (!items.length) {
|
||||
console.warn(`⚠️ 字典 [${code}] 未加载,无法获取标签`)
|
||||
return String(value)
|
||||
}
|
||||
|
||||
const item = items.find(i => String(i.dict_value) === String(value) || i.dict_value === value)
|
||||
return item ? item.dict_label : String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典编码和标签获取值
|
||||
* @param {string} code - 字典编码
|
||||
* @param {string} label - 字典标签
|
||||
* @returns {any} 字典值
|
||||
*/
|
||||
function getDictValue(code, label) {
|
||||
const items = getDictItemsSync(code)
|
||||
if (!items.length) {
|
||||
console.warn(`⚠️ 字典 [${code}] 未加载,无法获取值`)
|
||||
return null
|
||||
}
|
||||
|
||||
const item = items.find(i => i.dict_label === label)
|
||||
return item ? item.dict_value : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
* @param {string} code - 字典编码,不指定则清空所有
|
||||
*/
|
||||
function clearCache(code) {
|
||||
if (code) {
|
||||
delete dictCache.value[code]
|
||||
// console.log(`✅ 已清除字典 [${code}] 缓存`)
|
||||
} else {
|
||||
dictCache.value = {}
|
||||
// console.log(`✅ 已清除所有字典缓存`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新字典
|
||||
* @param {string} code - 字典编码
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
async function refreshDict(code) {
|
||||
clearCache(code)
|
||||
return getDictItems(code)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已缓存的字典
|
||||
* @returns {Object}
|
||||
*/
|
||||
const allDicts = computed(() => dictCache.value)
|
||||
|
||||
return {
|
||||
dictCache,
|
||||
loadingCodes,
|
||||
getDictItems,
|
||||
getDictItemsSync,
|
||||
preloadDicts,
|
||||
getDictLabel,
|
||||
getDictValue,
|
||||
clearCache,
|
||||
refreshDict,
|
||||
allDicts,
|
||||
}
|
||||
})
|
||||
@ -30,8 +30,8 @@
|
||||
{{ tenantData.email || '未设置' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="tenantData.status === 'enabled' ? 'success' : 'info'">
|
||||
{{ tenantData.status === 'enabled' ? '启用' : '禁用' }}
|
||||
<el-tag :type="getTenantStatusType(tenantData.status)">
|
||||
{{ getTenantStatusText(tenantData.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
@ -54,8 +54,13 @@
|
||||
>
|
||||
<el-form-item label="审核结果" prop="result">
|
||||
<el-radio-group v-model="auditForm.result">
|
||||
<el-radio value="approved">通过</el-radio>
|
||||
<el-radio value="rejected">拒绝</el-radio>
|
||||
<el-radio
|
||||
v-for="item in reviewStatusDict"
|
||||
:key="item.dict_value"
|
||||
:value="item.dict_value"
|
||||
>
|
||||
{{ item.dict_label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核意见" prop="comment">
|
||||
@ -79,9 +84,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, computed } from 'vue';
|
||||
import { ref, reactive, watch, computed, onMounted } from 'vue';
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
|
||||
import { auditTenant } from '@/api/tenant';
|
||||
import { useDictStore } from '@/stores/dict';
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
@ -107,6 +113,19 @@ const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const tenantData = ref<any>({});
|
||||
|
||||
// 字典数据
|
||||
const dictStore = useDictStore();
|
||||
const reviewStatusDict = ref<any[]>([]);
|
||||
|
||||
// 获取租户审核状态字典数据
|
||||
async function fetchReviewStatusDict() {
|
||||
try {
|
||||
reviewStatusDict.value = await dictStore.getDictItems('tenants_reviewStatus');
|
||||
} catch (error) {
|
||||
console.error('获取租户审核状态字典数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 审核表单
|
||||
const auditForm = reactive({
|
||||
result: '',
|
||||
@ -134,6 +153,19 @@ function getCurrentUser() {
|
||||
return 'system';
|
||||
}
|
||||
|
||||
// 租户状态相关方法
|
||||
function getTenantStatusType(status: string): 'success' | 'info' | 'warning' | 'danger' {
|
||||
// 注意:audit.vue中租户状态是字符串'enabled'或'disabled',与index.vue/detail.vue中的数字1/0不同
|
||||
// 这里保持原有逻辑作为兜底方案
|
||||
return status === 'enabled' ? 'success' : 'info';
|
||||
}
|
||||
|
||||
function getTenantStatusText(status: string): string {
|
||||
// 注意:audit.vue中租户状态是字符串'enabled'或'disabled',与index.vue/detail.vue中的数字1/0不同
|
||||
// 这里保持原有逻辑作为兜底方案
|
||||
return status === 'enabled' ? '启用' : '禁用';
|
||||
}
|
||||
|
||||
// 提交审核
|
||||
async function handleSubmit() {
|
||||
if (!auditFormRef.value) return;
|
||||
@ -184,6 +216,11 @@ watch(
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
// 在组件挂载时获取字典数据
|
||||
onMounted(() => {
|
||||
fetchReviewStatusDict();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@ -32,8 +32,8 @@
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="tenantData.status === 1 ? 'success' : 'info'">
|
||||
{{ tenantData.status === 1 ? '启用' : '禁用' }}
|
||||
<el-tag :type="getTenantStatusType(tenantData.status)">
|
||||
{{ getTenantStatusText(tenantData.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="存储容量">
|
||||
@ -66,9 +66,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import { ref, watch, computed, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getTenantDetail } from '@/api/tenant';
|
||||
import { useDictStore } from '@/stores/dict';
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
@ -92,8 +93,40 @@ const visible = computed({
|
||||
const loading = ref(false);
|
||||
const tenantData = ref<any>({});
|
||||
|
||||
// 字典数据
|
||||
const dictStore = useDictStore();
|
||||
const reviewStatusDict = ref<any[]>([]);
|
||||
const tenantStatusDict = ref<any[]>([]);
|
||||
|
||||
// 获取租户审核状态字典数据
|
||||
async function fetchReviewStatusDict() {
|
||||
try {
|
||||
reviewStatusDict.value = await dictStore.getDictItems('tenants_reviewStatus');
|
||||
} catch (error) {
|
||||
console.error('获取租户审核状态字典数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取租户状态字典数据
|
||||
async function fetchTenantStatusDict() {
|
||||
try {
|
||||
tenantStatusDict.value = await dictStore.getDictItems('tenants_status');
|
||||
} catch (error) {
|
||||
console.error('获取租户状态字典数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 审核状态相关方法
|
||||
function getAuditStatusType(status: string): 'warning' | 'success' | 'danger' | 'info' {
|
||||
// 使用字典数据
|
||||
const dictItem = reviewStatusDict.value.find(
|
||||
(item: any) => String(item.dict_value) === String(status)
|
||||
);
|
||||
if (dictItem) {
|
||||
return dictItem.dict_tag_type || 'info';
|
||||
}
|
||||
|
||||
// 兜底方案,保持原有逻辑
|
||||
const statusMap: Record<string, 'warning' | 'success' | 'danger' | 'info'> = {
|
||||
pending: 'warning',
|
||||
approved: 'success',
|
||||
@ -103,6 +136,15 @@ function getAuditStatusType(status: string): 'warning' | 'success' | 'danger' |
|
||||
}
|
||||
|
||||
function getAuditStatusText(status: string) {
|
||||
// 使用字典数据
|
||||
const dictItem = reviewStatusDict.value.find(
|
||||
(item: any) => String(item.dict_value) === String(status)
|
||||
);
|
||||
if (dictItem) {
|
||||
return dictItem.dict_label || String(status);
|
||||
}
|
||||
|
||||
// 兜底方案,保持原有逻辑
|
||||
const statusMap: Record<string, string> = {
|
||||
pending: '待审核',
|
||||
approved: '已通过',
|
||||
@ -111,6 +153,33 @@ function getAuditStatusText(status: string) {
|
||||
return statusMap[status] || '未知';
|
||||
}
|
||||
|
||||
// 租户状态相关方法
|
||||
function getTenantStatusType(status: string | number): 'success' | 'info' | 'warning' | 'danger' {
|
||||
// 使用字典数据
|
||||
const dictItem = tenantStatusDict.value.find(
|
||||
(item: any) => String(item.dict_value) === String(status)
|
||||
);
|
||||
if (dictItem) {
|
||||
return dictItem.dict_tag_type || 'info';
|
||||
}
|
||||
|
||||
// 兜底方案,保持原有逻辑
|
||||
return String(status) === '1' ? 'success' : 'info';
|
||||
}
|
||||
|
||||
function getTenantStatusText(status: string | number): string {
|
||||
// 使用字典数据
|
||||
const dictItem = tenantStatusDict.value.find(
|
||||
(item: any) => String(item.dict_value) === String(status)
|
||||
);
|
||||
if (dictItem) {
|
||||
return dictItem.dict_label || String(status);
|
||||
}
|
||||
|
||||
// 兜底方案,保持原有逻辑
|
||||
return String(status) === '1' ? '启用' : '禁用';
|
||||
}
|
||||
|
||||
// 格式化容量显示(后端返回的是 MB,直接格式化)
|
||||
function formatCapacity(mb: number | undefined | null): string {
|
||||
if (mb === null || mb === undefined) {
|
||||
@ -184,6 +253,12 @@ watch(
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 在组件挂载时获取字典数据
|
||||
onMounted(() => {
|
||||
fetchReviewStatusDict();
|
||||
fetchTenantStatusDict();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.tenantId,
|
||||
() => {
|
||||
|
||||
@ -29,8 +29,12 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="tenantForm.status" placeholder="请选择状态" style="width: 100%">
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
<el-option
|
||||
v-for="item in tenantStatusDict"
|
||||
:key="item.dict_value"
|
||||
:label="item.dict_label"
|
||||
:value="item.dict_value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="存储容量" prop="capacity">
|
||||
@ -63,9 +67,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, computed } from 'vue';
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
|
||||
import { ref, reactive, watch, computed, onMounted, type Ref } from 'vue';
|
||||
import {
|
||||
ElMessage,
|
||||
ElMessageBox,
|
||||
type FormInstance,
|
||||
type FormRules,
|
||||
} from 'element-plus';
|
||||
import { createTenant, updateTenant } from '@/api/tenant';
|
||||
import { useDictStore } from '@/stores/dict';
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
@ -90,6 +100,19 @@ const visible = computed({
|
||||
const submitting = ref(false);
|
||||
const tenantFormRef = ref<FormInstance>();
|
||||
|
||||
// 字典数据
|
||||
const dictStore = useDictStore();
|
||||
const tenantStatusDict = ref<any[]>([]);
|
||||
|
||||
// 获取租户状态字典数据
|
||||
async function fetchTenantStatusDict() {
|
||||
try {
|
||||
tenantStatusDict.value = await dictStore.getDictItems('tenants_status');
|
||||
} catch (error) {
|
||||
console.error('获取租户状态字典数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否为编辑模式
|
||||
const isEditing = computed(() => {
|
||||
return !!(props.tenant && props.tenant.id);
|
||||
@ -103,7 +126,7 @@ const tenantForm = reactive({
|
||||
owner: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
status: 1,
|
||||
status: '1', // 改为字符串类型,与字典数据中的dict_value保持一致
|
||||
capacity: 0,
|
||||
remark: '',
|
||||
});
|
||||
@ -131,7 +154,7 @@ function resetForm() {
|
||||
tenantForm.owner = '';
|
||||
tenantForm.phone = '';
|
||||
tenantForm.email = '';
|
||||
tenantForm.status = 1;
|
||||
tenantForm.status = '1'; // 改为字符串类型,与字典数据中的dict_value保持一致
|
||||
tenantForm.capacity = 0;
|
||||
tenantForm.remark = '';
|
||||
tenantFormRef.value?.resetFields();
|
||||
@ -146,7 +169,8 @@ function initFormData() {
|
||||
tenantForm.owner = props.tenant.owner || '';
|
||||
tenantForm.phone = props.tenant.phone || '';
|
||||
tenantForm.email = props.tenant.email || '';
|
||||
tenantForm.status = props.tenant.status != null ? Number(props.tenant.status) : 1;
|
||||
// 确保status是字符串类型,与字典数据中的dict_value保持一致
|
||||
tenantForm.status = props.tenant.status != null ? String(props.tenant.status) : '1';
|
||||
// capacity 后端返回的是 MB,直接使用
|
||||
tenantForm.capacity = props.tenant.capacity != null ? Number(props.tenant.capacity) : 0;
|
||||
tenantForm.remark = props.tenant.remark || '';
|
||||
@ -168,7 +192,7 @@ async function handleSubmit() {
|
||||
owner: tenantForm.owner,
|
||||
phone: tenantForm.phone || '',
|
||||
email: tenantForm.email || '',
|
||||
status: Number(tenantForm.status),
|
||||
status: Number(tenantForm.status), // 提交时转换为数字类型
|
||||
// capacity 前后端都使用 MB
|
||||
capacity: tenantForm.capacity || 0,
|
||||
remark: tenantForm.remark || '',
|
||||
@ -215,6 +239,11 @@ watch(
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 在组件挂载时获取字典数据
|
||||
onMounted(() => {
|
||||
fetchTenantStatusDict();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.tenant,
|
||||
() => {
|
||||
|
||||
@ -70,8 +70,8 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status == 1 ? 'success' : 'info'">
|
||||
{{ row.status == 1 ? '启用' : '禁用' }}
|
||||
<el-tag :type="getTenantStatusType(row.status)">
|
||||
{{ getTenantStatusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@ -151,10 +151,15 @@ import { ref, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Plus, Check, View, Edit, Delete, Refresh } from '@element-plus/icons-vue';
|
||||
import { getAllTenants, deleteTenant } from '@/api/tenant';
|
||||
import { useDictStore } from '@/stores/dict';
|
||||
import TenantDetail from './components/detail.vue';
|
||||
import TenantAudit from './components/audit.vue';
|
||||
import TenantEdit from './components/edit.vue';
|
||||
|
||||
// 字典数据
|
||||
const reviewStatusDict = ref<any[]>([]);
|
||||
const tenantStatusDict = ref<any[]>([]);
|
||||
|
||||
const tenants = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
@ -162,6 +167,26 @@ const page = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const total = ref(0);
|
||||
|
||||
// 获取审核状态字典
|
||||
const fetchReviewStatusDict = async () => {
|
||||
try {
|
||||
const dictStore = useDictStore();
|
||||
reviewStatusDict.value = await dictStore.getDictItems('tenants_reviewStatus');
|
||||
} catch (err) {
|
||||
console.error('获取租户审核状态字典失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取租户状态字典
|
||||
const fetchTenantStatusDict = async () => {
|
||||
try {
|
||||
const dictStore = useDictStore();
|
||||
tenantStatusDict.value = await dictStore.getDictItems('tenants_status');
|
||||
} catch (err) {
|
||||
console.error('获取租户状态字典失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 弹窗状态
|
||||
const showViewDialog = ref(false);
|
||||
const showAuditDialog = ref(false);
|
||||
@ -218,6 +243,15 @@ async function refresh() {
|
||||
function getAuditStatusType(
|
||||
status: string
|
||||
): 'warning' | 'success' | 'danger' | 'info' {
|
||||
// 使用字典数据
|
||||
const dictItem = reviewStatusDict.value.find(
|
||||
(item: any) => String(item.dict_value) === String(status)
|
||||
);
|
||||
if (dictItem) {
|
||||
return dictItem.dict_tag_type || 'info';
|
||||
}
|
||||
|
||||
// 兜底方案,保持原有逻辑
|
||||
const statusMap: Record<string, 'warning' | 'success' | 'danger' | 'info'> =
|
||||
{
|
||||
pending: 'warning',
|
||||
@ -228,6 +262,15 @@ function getAuditStatusType(
|
||||
}
|
||||
|
||||
function getAuditStatusText(status: string) {
|
||||
// 使用字典数据
|
||||
const dictItem = reviewStatusDict.value.find(
|
||||
(item: any) => String(item.dict_value) === String(status)
|
||||
);
|
||||
if (dictItem) {
|
||||
return dictItem.dict_label || String(status);
|
||||
}
|
||||
|
||||
// 兜底方案,保持原有逻辑
|
||||
const statusMap: Record<string, string> = {
|
||||
pending: '待审核',
|
||||
approved: '已通过',
|
||||
@ -236,6 +279,33 @@ function getAuditStatusText(status: string) {
|
||||
return statusMap[status] || '未知';
|
||||
}
|
||||
|
||||
// 租户状态相关方法
|
||||
function getTenantStatusType(status: string | number): 'success' | 'info' | 'warning' | 'danger' {
|
||||
// 使用字典数据
|
||||
const dictItem = tenantStatusDict.value.find(
|
||||
(item: any) => String(item.dict_value) === String(status)
|
||||
);
|
||||
if (dictItem) {
|
||||
return dictItem.dict_tag_type || 'info';
|
||||
}
|
||||
|
||||
// 兜底方案,保持原有逻辑
|
||||
return String(status) === '1' ? 'success' : 'info';
|
||||
}
|
||||
|
||||
function getTenantStatusText(status: string | number): string {
|
||||
// 使用字典数据
|
||||
const dictItem = tenantStatusDict.value.find(
|
||||
(item: any) => String(item.dict_value) === String(status)
|
||||
);
|
||||
if (dictItem) {
|
||||
return dictItem.dict_label || String(status);
|
||||
}
|
||||
|
||||
// 兜底方案,保持原有逻辑
|
||||
return String(status) === '1' ? '启用' : '禁用';
|
||||
}
|
||||
|
||||
// 格式化容量显示(后端返回的是 MB,直接格式化)
|
||||
function formatCapacity(mb: number | string | undefined | null): string {
|
||||
// 处理 null/undefined/空字符串,确保是数字类型
|
||||
@ -335,6 +405,8 @@ async function handleDelete(row: any) {
|
||||
|
||||
onMounted(() => {
|
||||
fetchTenants();
|
||||
fetchReviewStatusDict();
|
||||
fetchTenantStatusDict();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -25,43 +25,6 @@
|
||||
<el-input v-model="form.email" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 部门 -->
|
||||
<el-form-item label="部门">
|
||||
<el-select
|
||||
v-model="form.department_id"
|
||||
placeholder="请选择部门"
|
||||
style="width: 100%"
|
||||
:loading="loadingDepartments"
|
||||
clearable
|
||||
@change="handleDepartmentChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="dept in departmentList"
|
||||
:key="dept.id"
|
||||
:label="dept.name"
|
||||
:value="dept.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 职位 -->
|
||||
<el-form-item label="职位">
|
||||
<el-select
|
||||
v-model="form.position_id"
|
||||
placeholder="请选择职位"
|
||||
style="width: 100%"
|
||||
:loading="loadingPositions"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="pos in positionList"
|
||||
:key="pos.id"
|
||||
:label="pos.name"
|
||||
:value="pos.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 角色 -->
|
||||
<el-form-item label="角色">
|
||||
<el-select
|
||||
@ -86,7 +49,11 @@
|
||||
|
||||
<!-- 状态 -->
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="form.status">
|
||||
<el-select
|
||||
v-model="form.status"
|
||||
placeholder="请选择状态"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in statusDict"
|
||||
:key="item.dict_value"
|
||||
@ -106,17 +73,19 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from "vue";
|
||||
import { ref, computed, watch, onMounted } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import {
|
||||
addUser,
|
||||
editUser,
|
||||
getUserInfo,
|
||||
} from "@/api/user";
|
||||
import { getTenantPositions, getPositionsByDepartment } from "@/api/position";
|
||||
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useDictStore } from "@/stores/dict";
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const dictStore = useDictStore();
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
@ -131,44 +100,23 @@ const props = defineProps({
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
departmentList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
positionList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
loadingRoles: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loadingDepartments: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loadingPositions: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
statusDict: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
tenantId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'submit', 'close', 'fetch-positions']);
|
||||
const emit = defineEmits(['update:modelValue', 'submit', 'close']);
|
||||
|
||||
const visible = ref(false);
|
||||
const formRef = ref(null);
|
||||
const loadingRoles = ref(false);
|
||||
const loadingDepartments = ref(false);
|
||||
const loadingPositions = ref(false);
|
||||
const isAdd = ref(false);
|
||||
const statusDict = ref([]);
|
||||
|
||||
const form = ref<any>({
|
||||
id: null,
|
||||
@ -177,10 +125,8 @@ const form = ref<any>({
|
||||
password: "",
|
||||
email: "",
|
||||
role: null,
|
||||
status: "active",
|
||||
status: "1", // ✅ 改为字典中的值"1"(启用)而不是"active"
|
||||
tenant_id: null,
|
||||
department_id: null,
|
||||
position_id: null,
|
||||
});
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
@ -199,10 +145,31 @@ watch(visible, (newVal) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 监听 statusDict 变化,用于调试
|
||||
watch(() => props.statusDict, (newVal) => {
|
||||
console.log('UserEdit statusDict updated:', newVal, 'Length:', Array.isArray(newVal) ? newVal.length : 'not array');
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
const getCurrentTenantId = () => {
|
||||
return props.tenantId || 0;
|
||||
};
|
||||
|
||||
// 获取状态字典数据
|
||||
const fetchStatusDict = async () => {
|
||||
try {
|
||||
const dictItems = await dictStore.getDictItems('user_status');
|
||||
statusDict.value = dictItems || [];
|
||||
} catch (error) {
|
||||
console.error("获取用户状态字典失败:", error);
|
||||
statusDict.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 组件挂载时获取字典数据
|
||||
onMounted(() => {
|
||||
fetchStatusDict();
|
||||
});
|
||||
|
||||
const loadUserData = async (user: any) => {
|
||||
try {
|
||||
// 处理两种调用方式:传递用户对象或用户 ID
|
||||
@ -220,12 +187,11 @@ const loadUserData = async (user: any) => {
|
||||
const tenantId = getCurrentTenantId();
|
||||
let roleValue = data.role || null;
|
||||
|
||||
let statusStr = "active";
|
||||
if (typeof data.status === 'number') {
|
||||
statusStr = data.status === 1 ? "active" : "inactive";
|
||||
} else if (typeof data.status === 'string') {
|
||||
statusStr = data.status;
|
||||
}
|
||||
// ✅ 直接使用后端返回的状态值,不转换
|
||||
// 字典中的值就是 "1"(启用)、"0"(禁用)等
|
||||
const statusValue = data.status !== undefined && data.status !== null
|
||||
? String(data.status)
|
||||
: "1";
|
||||
|
||||
form.value = {
|
||||
id: data.id,
|
||||
@ -234,16 +200,11 @@ const loadUserData = async (user: any) => {
|
||||
password: "",
|
||||
email: data.email,
|
||||
role: roleValue,
|
||||
department_id: data.department_id || null,
|
||||
position_id: data.position_id || null,
|
||||
status: statusStr,
|
||||
status: statusValue, // ✅ 使用字典中的原始值
|
||||
tenant_id: data.tenant_id || tenantId,
|
||||
};
|
||||
|
||||
// 如果用户有部门,根据部门加载职位
|
||||
if (form.value.department_id) {
|
||||
await loadPositions(form.value.department_id);
|
||||
}
|
||||
|
||||
} catch (e: any) {
|
||||
console.error('Failed to load user data:', e);
|
||||
const errorMsg = e?.response?.data?.message || e?.message || "加载用户失败";
|
||||
@ -252,31 +213,7 @@ const loadUserData = async (user: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
const loadPositions = async (departmentId?: number) => {
|
||||
loadingPositions.value = true;
|
||||
try {
|
||||
const tenantId = getCurrentTenantId();
|
||||
let res;
|
||||
if (departmentId && departmentId > 0) {
|
||||
res = await getPositionsByDepartment(departmentId);
|
||||
} else {
|
||||
res = await getTenantPositions(tenantId);
|
||||
}
|
||||
// 职位列表由父组件通过 prop 传入,这里仅用于加载示例
|
||||
} catch (error: any) {
|
||||
console.error('获取职位列表失败:', error);
|
||||
} finally {
|
||||
loadingPositions.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDepartmentChange = (departmentId: number | null) => {
|
||||
form.value.position_id = null;
|
||||
if (departmentId && departmentId > 0) {
|
||||
// 让父组件加载对应部门的职位
|
||||
emit('fetch-positions', departmentId);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
@ -290,10 +227,8 @@ const handleClose = () => {
|
||||
password: "",
|
||||
email: "",
|
||||
role: null,
|
||||
status: "active",
|
||||
status: "1",
|
||||
tenant_id: null,
|
||||
department_id: null,
|
||||
position_id: null,
|
||||
};
|
||||
isAdd.value = false;
|
||||
emit('close');
|
||||
@ -315,13 +250,6 @@ const handleSubmit = async () => {
|
||||
submitData.role = form.value.role;
|
||||
}
|
||||
|
||||
if (form.value.department_id) {
|
||||
submitData.department_id = form.value.department_id;
|
||||
}
|
||||
if (form.value.position_id) {
|
||||
submitData.position_id = form.value.position_id;
|
||||
}
|
||||
|
||||
if (form.value.tenant_id) {
|
||||
submitData.tenant_id = form.value.tenant_id;
|
||||
}
|
||||
@ -347,13 +275,6 @@ const handleSubmit = async () => {
|
||||
submitData.role = form.value.role;
|
||||
}
|
||||
|
||||
if (form.value.department_id) {
|
||||
submitData.department_id = form.value.department_id;
|
||||
}
|
||||
if (form.value.position_id) {
|
||||
submitData.position_id = form.value.position_id;
|
||||
}
|
||||
|
||||
if (form.value.tenant_id) {
|
||||
submitData.tenant_id = form.value.tenant_id;
|
||||
}
|
||||
@ -382,10 +303,8 @@ defineExpose({
|
||||
password: "",
|
||||
email: "",
|
||||
role: null,
|
||||
status: "active",
|
||||
status: "1",
|
||||
tenant_id: tenantId || getCurrentTenantId(),
|
||||
department_id: null,
|
||||
position_id: null,
|
||||
};
|
||||
visible.value = true;
|
||||
},
|
||||
|
||||
@ -32,16 +32,7 @@
|
||||
align="center"
|
||||
min-width="200"
|
||||
/>
|
||||
<el-table-column prop="department" label="部门" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.departmentName || '未分配' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="position" label="职位" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.positionName || '未分配' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="role" label="角色" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getRoleTagType(scope.row.roleName)">
|
||||
@ -116,8 +107,6 @@ import {
|
||||
getUserInfo,
|
||||
} from "@/api/user";
|
||||
import { getRoleByTenantId, getAllRoles } from "@/api/role";
|
||||
import { getTenantDepartments } from "@/api/department";
|
||||
import { getTenantPositions } from "@/api/position";
|
||||
import { getDictItemsByCode } from '@/api/dict'
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
@ -140,14 +129,6 @@ const props = defineProps({
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
departmentList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
positionList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
statusDict: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
@ -235,22 +216,6 @@ const fetchUsers = async () => {
|
||||
roleName = roleInfo ? roleInfo.roleName : '';
|
||||
}
|
||||
|
||||
// 查找部门名称
|
||||
let departmentName = '';
|
||||
const departmentId = item.department_id || null;
|
||||
if (departmentId) {
|
||||
const deptInfo = (props.departmentList as any[]).find(d => d.id === departmentId);
|
||||
departmentName = deptInfo ? deptInfo.name : '';
|
||||
}
|
||||
|
||||
// 查找职位名称
|
||||
let positionName = '';
|
||||
const positionId = item.position_id || null;
|
||||
if (positionId) {
|
||||
const posInfo = (props.positionList as any[]).find(p => p.id === positionId);
|
||||
positionName = posInfo ? posInfo.name : '';
|
||||
}
|
||||
|
||||
// 处理时间字段
|
||||
const lastLoginTime = item.last_login_time || item.lastLoginTime || null;
|
||||
const lastLoginIp = item.last_login_ip || item.lastLoginIp || null;
|
||||
@ -262,10 +227,6 @@ const fetchUsers = async () => {
|
||||
email: item.email,
|
||||
role: roleValue,
|
||||
roleName: roleName,
|
||||
department_id: departmentId,
|
||||
departmentName: departmentName,
|
||||
position_id: positionId,
|
||||
positionName: positionName,
|
||||
status: item.status,
|
||||
lastLoginTime: lastLoginTime
|
||||
? new Date(lastLoginTime).toLocaleString("zh-CN", {
|
||||
|
||||
@ -36,16 +36,7 @@
|
||||
align="center"
|
||||
min-width="200"
|
||||
/>
|
||||
<el-table-column prop="department" label="部门" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.departmentName || '未分配' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="position" label="职位" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.positionName || '未分配' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="role" label="角色" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getRoleTagType(scope.row.roleName)">
|
||||
@ -114,11 +105,7 @@
|
||||
@update:modelValue="editDialogVisible = $event"
|
||||
:is-edit="isEdit"
|
||||
:role-list="roleList"
|
||||
:department-list="departmentList"
|
||||
:position-list="positionList"
|
||||
:loading-roles="loadingRoles"
|
||||
:loading-departments="loadingDepartments"
|
||||
:loading-positions="loadingPositions"
|
||||
:status-dict="statusDict"
|
||||
:tenant-id="getCurrentTenantId()"
|
||||
@submit="handleEditSuccess"
|
||||
@ -147,10 +134,8 @@ import {
|
||||
getUserInfo,
|
||||
} from "@/api/user";
|
||||
import { getRoleByTenantId, getAllRoles } from "@/api/role";
|
||||
import { getTenantDepartments } from "@/api/department";
|
||||
import { getTenantPositions, getPositionsByDepartment } from "@/api/position";
|
||||
import { getDictItemsByCode } from '@/api/dict'
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useDictStore } from "@/stores/dict";
|
||||
import UserEditDialog from './components/UserEdit.vue'
|
||||
import ChangePasswordDialog from './components/ChangePassword.vue'
|
||||
|
||||
@ -166,6 +151,7 @@ interface User {
|
||||
}
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const dictStore = useDictStore();
|
||||
|
||||
const page = ref(1);
|
||||
const pageSize = ref(10);
|
||||
@ -174,10 +160,6 @@ const total = ref(0);
|
||||
const users = ref<any[]>([]);
|
||||
const roleList = ref<any[]>([]);
|
||||
const loadingRoles = ref(false);
|
||||
const departmentList = ref<any[]>([]);
|
||||
const loadingDepartments = ref(false);
|
||||
const positionList = ref<any[]>([]);
|
||||
const loadingPositions = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 状态字典
|
||||
@ -196,12 +178,10 @@ const changePasswordRef = ref()
|
||||
|
||||
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;
|
||||
const items = await dictStore.getDictItems('user_status');
|
||||
statusDict.value = items;
|
||||
} catch (err) {
|
||||
console.error('Error fetching status dict:', err);
|
||||
statusDict.value = [];
|
||||
}
|
||||
}
|
||||
@ -265,48 +245,7 @@ const fetchRoles = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 获取部门列表
|
||||
const fetchDepartments = async () => {
|
||||
loadingDepartments.value = true;
|
||||
try {
|
||||
const tenantId = getCurrentTenantId();
|
||||
const res = await getTenantDepartments(tenantId);
|
||||
if (res.code === 0 && res.data) {
|
||||
departmentList.value = Array.isArray(res.data) ? res.data : [];
|
||||
} else {
|
||||
departmentList.value = [];
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('获取部门列表失败:', error);
|
||||
departmentList.value = [];
|
||||
} finally {
|
||||
loadingDepartments.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取职位列表
|
||||
const fetchPositions = async (departmentId?: number) => {
|
||||
loadingPositions.value = true;
|
||||
try {
|
||||
const tenantId = getCurrentTenantId();
|
||||
let res;
|
||||
if (departmentId && departmentId > 0) {
|
||||
res = await getPositionsByDepartment(departmentId);
|
||||
} else {
|
||||
res = await getTenantPositions(tenantId);
|
||||
}
|
||||
if (res.code === 0 && res.data) {
|
||||
positionList.value = Array.isArray(res.data) ? res.data : [];
|
||||
} else {
|
||||
positionList.value = [];
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('获取职位列表失败:', error);
|
||||
positionList.value = [];
|
||||
} finally {
|
||||
loadingPositions.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取角色标签类型
|
||||
const getRoleTagType = (roleName: string) => {
|
||||
@ -318,8 +257,10 @@ const getRoleTagType = (roleName: string) => {
|
||||
|
||||
// 根据状态值获取字典显示文本
|
||||
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);
|
||||
// 在字典中查找匹配项
|
||||
const item = statusDict.value.find((d: any) => String(d.dict_value) === sval);
|
||||
if (item && item.dict_label) return item.dict_label;
|
||||
// 兼容旧逻辑
|
||||
if (status === 1 || sval === '1' || sval === 'active') return '启用';
|
||||
@ -361,19 +302,8 @@ const fetchUsers = async () => {
|
||||
roleName = roleInfo ? roleInfo.roleName : '';
|
||||
}
|
||||
|
||||
let departmentName = '';
|
||||
const departmentId = item.department_id || null;
|
||||
if (departmentId) {
|
||||
const deptInfo = departmentList.value.find(d => d.id === departmentId);
|
||||
departmentName = deptInfo ? deptInfo.name : '';
|
||||
}
|
||||
|
||||
let positionName = '';
|
||||
const positionId = item.position_id || null;
|
||||
if (positionId) {
|
||||
const posInfo = positionList.value.find(p => p.id === positionId);
|
||||
positionName = posInfo ? posInfo.name : '';
|
||||
}
|
||||
// 确保状态值正确处理
|
||||
const statusValue = item.status !== undefined && item.status !== null ? item.status : '1';
|
||||
|
||||
const lastLoginTime = item.last_login_time || item.lastLoginTime || null;
|
||||
const lastLoginIp = item.last_login_ip || item.lastLoginIp || null;
|
||||
@ -385,11 +315,7 @@ const fetchUsers = async () => {
|
||||
email: item.email,
|
||||
role: roleValue,
|
||||
roleName: roleName,
|
||||
department_id: departmentId,
|
||||
departmentName: departmentName,
|
||||
position_id: positionId,
|
||||
positionName: positionName,
|
||||
status: item.status,
|
||||
status: statusValue, // 使用处理后的状态值
|
||||
lastLoginTime: lastLoginTime
|
||||
? new Date(lastLoginTime).toLocaleString("zh-CN", {
|
||||
year: "numeric",
|
||||
@ -416,8 +342,6 @@ const fetchUsers = async () => {
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
fetchRoles(),
|
||||
fetchDepartments(),
|
||||
fetchPositions(),
|
||||
fetchStatusDict(),
|
||||
]);
|
||||
fetchUsers();
|
||||
@ -432,8 +356,6 @@ const refresh = async () => {
|
||||
try {
|
||||
await Promise.all([
|
||||
fetchRoles(),
|
||||
fetchDepartments(),
|
||||
fetchPositions(),
|
||||
fetchStatusDict(),
|
||||
]);
|
||||
await fetchUsers();
|
||||
@ -480,14 +402,7 @@ const handleEditSuccess = () => {
|
||||
fetchUsers();
|
||||
};
|
||||
|
||||
// 职位加载回调
|
||||
const handleFetchPositions = (departmentId: number | null) => {
|
||||
if (departmentId && departmentId > 0) {
|
||||
fetchPositions(departmentId);
|
||||
} else {
|
||||
fetchPositions();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 删除用户
|
||||
const handleDelete = async (user: User) => {
|
||||
|
||||
39
server/sql/init_dict_data.sql
Normal file
39
server/sql/init_dict_data.sql
Normal file
@ -0,0 +1,39 @@
|
||||
-- 初始化字典数据
|
||||
|
||||
-- 先删除旧数据
|
||||
DELETE FROM `sys_dict_item` WHERE `id` > 0;
|
||||
DELETE FROM `sys_dict_type` WHERE `id` > 0;
|
||||
ALTER TABLE `sys_dict_type` AUTO_INCREMENT = 1;
|
||||
ALTER TABLE `sys_dict_item` AUTO_INCREMENT = 1;
|
||||
|
||||
-- 插入字典类型
|
||||
INSERT INTO `sys_dict_type` (`tenant_id`, `dict_code`, `dict_name`, `parent_id`, `status`, `sort`, `remark`, `create_by`) VALUES
|
||||
(0, 'user_status', '用户状态', 0, 1, 1, '用户启用禁用状态', 'system'),
|
||||
(0, 'user_gender', '用户性别', 0, 1, 2, '用户性别', 'system'),
|
||||
(0, 'position_status', '职位状态', 0, 1, 3, '职位启用禁用状态', 'system'),
|
||||
(0, 'dept_status', '部门状态', 0, 1, 4, '部门启用禁用状态', 'system');
|
||||
|
||||
-- 插入用户状态字典项
|
||||
-- 假设 user_status 字典类型 ID 为 1
|
||||
INSERT INTO `sys_dict_item` (`dict_type_id`, `dict_label`, `dict_value`, `parent_id`, `status`, `sort`, `color`, `remark`, `create_by`) VALUES
|
||||
(1, '启用', '1', 0, 1, 1, '#67C23A', '用户启用状态', 'system'),
|
||||
(1, '禁用', '0', 0, 1, 2, '#F56C6C', '用户禁用状态', 'system');
|
||||
|
||||
-- 插入用户性别字典项
|
||||
-- 假设 user_gender 字典类型 ID 为 2
|
||||
INSERT INTO `sys_dict_item` (`dict_type_id`, `dict_label`, `dict_value`, `parent_id`, `status`, `sort`, `color`, `remark`, `create_by`) VALUES
|
||||
(2, '男', 'M', 0, 1, 1, '#409EFF', '男性', 'system'),
|
||||
(2, '女', 'F', 0, 1, 2, '#E6A23C', '女性', 'system'),
|
||||
(2, '其他', 'O', 0, 1, 3, '#909399', '其他性别', 'system');
|
||||
|
||||
-- 插入职位状态字典项
|
||||
-- 假设 position_status 字典类型 ID 为 3
|
||||
INSERT INTO `sys_dict_item` (`dict_type_id`, `dict_label`, `dict_value`, `parent_id`, `status`, `sort`, `color`, `remark`, `create_by`) VALUES
|
||||
(3, '启用', '1', 0, 1, 1, '#67C23A', '职位启用状态', 'system'),
|
||||
(3, '禁用', '0', 0, 1, 2, '#F56C6C', '职位禁用状态', 'system');
|
||||
|
||||
-- 插入部门状态字典项
|
||||
-- 假设 dept_status 字典类型 ID 为 4
|
||||
INSERT INTO `sys_dict_item` (`dict_type_id`, `dict_label`, `dict_value`, `parent_id`, `status`, `sort`, `color`, `remark`, `create_by`) VALUES
|
||||
(4, '启用', '1', 0, 1, 1, '#67C23A', '部门启用状态', 'system'),
|
||||
(4, '禁用', '0', 0, 1, 2, '#F56C6C', '部门禁用状态', 'system');
|
||||
Loading…
Reference in New Issue
Block a user