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

13 KiB
Raw Blame History

字典系统使用指南

概述

字典Dictionary系统是一个用于管理应用中常用的枚举值和标签化数据的功能。通过字典系统你可以在后台统一管理这些数据而无需修改代码和重新编译部署。

字典的核心概念

  • 字典类型Dict Type:用来分类管理字典项,例如 user_status(用户状态)、gender(性别)等
  • 字典编码Dict Code:字典类型的唯一标识,用于前端查询,例如 user_statusgender
  • 字典项Dict Item:具体的字典值和标签,包括:
    • dict_value:存储在数据库中的实际值(例如 10
    • dict_label:显示给用户的标签文本(例如 "启用"、"禁用"

数据库表结构

-- 字典类型表
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. 获取字典项(最常用)

根据字典编码获取字典项列表:

GET /api/dict/items/code/:code?include_disabled=0

请求示例:

curl -X GET "http://localhost:8080/api/dict/items/code/user_status?include_disabled=0"

响应示例:

{
  "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. 字典管理接口

获取字典类型列表

GET /api/dict/types?parentId=&status=

添加字典类型

POST /api/dict/types
{
  "dict_code": "user_status",
  "dict_name": "用户状态",
  "status": 1
}

添加字典项

POST /api/dict/items
{
  "dict_type_id": 101,
  "dict_label": "启用",
  "dict_value": "1",
  "status": 1,
  "sort": 1
}

更新/删除字典项

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返回字典项数组

示例:

import { getDictItemsByCode } from '@/api/dict'

// 获取用户状态字典
const statusItems = await getDictItemsByCode('user_status')
console.log(statusItems)
// 输出:
// [
//   { dict_label: '启用', dict_value: '1', ... },
//   { dict_label: '禁用', dict_value: '0', ... }
// ]

其他可用函数

// 获取字典类型列表
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

import { getDictItemsByCode } from '@/api/dict'

2. 定义状态字典数据和加载函数

<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. 定义辅助函数

// 根据状态值获取字典标签
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. 在模板中使用

<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在下拉选择中使用字典

场景:在"添加/编辑用户"对话框中,用字典项填充状态下拉选择。

<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多个字典项的场景

场景:同时加载多个字典(用户状态、性别、部门类型等)。

<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

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 }
})

在组件中使用:

import { useDictStore } from '@/stores/dict'

const dictStore = useDictStore()
const statusDict = await dictStore.fetchDict('user_status')

2. 创建字典枚举文件

建议为常用字典创建枚举文件,便于维护:

文件pc/src/constants/dicts.ts

// 字典编码常量
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]

在组件中使用:

import { DICT_CODES } from '@/constants/dicts'
import { useDictStore } from '@/stores/dict'

const dictStore = useDictStore()
const statusDict = await dictStore.fetchDict(DICT_CODES.USER_STATUS)

3. 处理不同的数据值类型

字典值可能是数字、字符串或其他类型。确保比较时进行正确的类型转换:

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. 错误处理

始终为字典加载添加适当的错误处理和回退机制:

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_typesys_dict_item 表中添加字典数据
  2. 前端导入import { getDictItemsByCode } from '@/api/dict'
  3. 加载字典:在 onMounted 或其他合适位置调用 API 加载
  4. 使用字典:通过 dict_valuedict_label 来显示和存储数据
  5. 最优化:使用 Pinia store 缓存字典,避免重复请求

通过字典系统,你可以灵活地管理应用中的枚举值和标签数据,而无需修改代码。