Skip to content

数据字典文档 - 字典管理和组件使用

概述

本文档详细介绍了 VJSP Vue3 框架的数据字典管理系统。数据字典提供统一的枚举值管理方案,支持状态码、类型标识等数据的集中管理和动态加载,确保数据一致性和维护性。

字典架构

核心特性

  • 统一管理:所有枚举数据集中管理,避免硬编码
  • 动态加载:支持运行时动态加载字典数据
  • 智能缓存:自动缓存机制,提升性能
  • 类型安全:完整的 TypeScript 类型定义支持
  • 错误处理:完善的错误处理和重试机制

技术栈

  • Pinia 状态管理:字典数据的状态管理和缓存
  • 异步加载:支持字典数据的异步获取和更新
  • 组件集成:与 Element Plus 组件库深度集成

字典管理机制

字典数据存储

字典数据采用集中式存储管理:

  • 内存缓存:字典数据缓存在内存中,提升访问速度
  • 自动过期:缓存数据自动过期,确保数据时效性
  • 强制刷新:支持手动强制刷新字典数据

字典加载流程

  1. 应用启动:检查缓存有效性
  2. 数据加载:从后端 API 获取字典数据
  3. 缓存更新:更新内存缓存和时间戳
  4. 错误处理:处理网络错误和重试逻辑

缓存策略

框架实现智能缓存策略:

  • 缓存时长:5分钟自动过期
  • 重试机制:最多3次重试,指数退避延迟
  • 并发控制:防止重复请求,避免资源浪费

字典工具函数

基础字典访问

框架提供安全的字典访问函数,自动处理加载和错误:

  • getSafeDictLabel:安全获取字典标签
  • getSafeDictValue:安全获取字典值
  • getSafeDictOptions:安全获取字典选项列表
  • batchGetDictLabels:批量获取字典标签
  • hasDictType:检查字典类型是否存在

字典状态管理

通过字典存储模块管理字典数据:

  • getDictData:获取字典数据(自动处理缓存)
  • getDictByType:根据类型获取字典项
  • getDictLabel:获取字典标签
  • getDictValue:获取字典值
  • refreshDictData:刷新字典数据
  • clearDictData:清空字典缓存

字典组件使用

组件集合

框架提供完整的字典组件集合:

  • DictSelect:字典选择器
  • DictTag:字典标签显示
  • DictRadio:字典单选组件
  • DictCheckbox:字典多选组件
  • DictSwitch:字典开关组件
  • DictCascader:字典级联选择器

组件特性

所有字典组件具备以下特性:

  • 自动加载:自动加载对应字典类型的数据
  • 错误处理:处理字典数据加载失败的情况
  • 类型安全:完整的 TypeScript 类型支持
  • 样式统一:与 Element Plus 组件风格一致

业务模块开发指南(Product 模块)

1. 定义字典类型

在字典管理系统中定义产品相关的字典类型:

  • product_status:产品状态字典
  • product_category:产品分类字典
  • product_type:产品类型字典

2. 创建产品管理页面

src/views/modules/product/list.vue

vue
<template>
  <div class="product-list">
    <!-- 搜索表单 -->
    <el-form :model="queryParams" inline>
      <el-form-item label="产品状态">
        <DictSelect
          v-model="queryParams.status"
          dict-type="product_status"
          placeholder="请选择产品状态"
          clearable
        />
      </el-form-item>

      <el-form-item label="产品分类">
        <DictSelect
          v-model="queryParams.category"
          dict-type="product_category"
          placeholder="请选择产品分类"
          clearable
        />
      </el-form-item>

      <el-button type="primary" @click="handleQuery">查询</el-button>
      <el-button @click="resetQuery">重置</el-button>
    </el-form>

    <!-- 数据表格 -->
    <el-table :data="productList" v-loading="loading">
      <el-table-column prop="productName" label="产品名称" />
      <el-table-column prop="productCode" label="产品编码" />
      <el-table-column prop="status" label="产品状态">
        <template #default="{ row }">
          <DictTag :value="row.status" dict-type="product_status" />
        </template>
      </el-table-column>
      <el-table-column prop="category" label="产品分类">
        <template #default="{ row }">
          <DictTag :value="row.category" dict-type="product_category" />
        </template>
      </el-table-column>
      <el-table-column prop="type" label="产品类型">
        <template #default="{ row }">
          <DictTag :value="row.type" dict-type="product_type" />
        </template>
      </el-table-column>
      <el-table-column label="操作" width="200">
        <template #default="{ row }">
          <el-button size="small" @click="handleEdit(row)">编辑</el-button>
          <el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useProductApi } from '@/api/modules/product'

const queryParams = ref({
  status: '',
  category: '',
  productName: '',
})

const productList = ref([])
const loading = ref(false)

const loadProductList = async () => {
  loading.value = true
  try {
    const response = await useProductApi().getList(queryParams.value)
    productList.value = response.data.list
  } finally {
    loading.value = false
  }
}

const handleQuery = () => {
  loadProductList()
}

const resetQuery = () => {
  queryParams.value = {
    status: '',
    category: '',
    productName: '',
  }
  loadProductList()
}

onMounted(() => {
  loadProductList()
})
</script>

3. 创建产品表单页面

src/views/modules/product/form.vue

vue
<template>
  <div class="product-form">
    <el-form :model="form" :rules="rules" label-width="100px">
      <el-form-item label="产品名称" prop="productName">
        <el-input v-model="form.productName" placeholder="请输入产品名称" />
      </el-form-item>

      <el-form-item label="产品编码" prop="productCode">
        <el-input v-model="form.productCode" placeholder="请输入产品编码" />
      </el-form-item>

      <el-form-item label="产品状态" prop="status">
        <DictRadio v-model="form.status" dict-type="product_status" />
      </el-form-item>

      <el-form-item label="产品分类" prop="category">
        <DictSelect
          v-model="form.category"
          dict-type="product_category"
          placeholder="请选择产品分类"
          clearable
        />
      </el-form-item>

      <el-form-item label="产品类型" prop="type">
        <DictCheckbox v-model="form.type" dict-type="product_type" placeholder="请选择产品类型" />
      </el-form-item>

      <el-form-item>
        <el-button type="primary" @click="handleSubmit">保存</el-button>
        <el-button @click="handleCancel">取消</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useProductApi } from '@/api/modules/product'

const route = useRoute()
const router = useRouter()

const form = ref({
  productName: '',
  productCode: '',
  status: '',
  category: '',
  type: [],
})

const rules = {
  productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
  productCode: [{ required: true, message: '请输入产品编码', trigger: 'blur' }],
  status: [{ required: true, message: '请选择产品状态', trigger: 'change' }],
}

const handleSubmit = async () => {
  try {
    if (route.params.id) {
      await useProductApi().update(route.params.id, form.value)
    } else {
      await useProductApi().create(form.value)
    }
    router.push('/product/list')
  } catch (error) {
    console.error('保存失败', error)
  }
}

const handleCancel = () => {
  router.push('/product/list')
}

onMounted(async () => {
  if (route.params.id) {
    const response = await useProductApi().getDetail(route.params.id)
    form.value = response.data
  }
})
</script>

4. 使用字典工具函数

在业务逻辑中使用字典工具函数:

typescript
import { getSafeDictLabel, getSafeDictOptions } from '@/utils/dictUtils'

// 获取产品状态标签
const getProductStatusLabel = async (status: string) => {
  return await getSafeDictLabel('product_status', status, '未知状态')
}

// 获取产品分类选项
const loadProductCategories = async () => {
  return await getSafeDictOptions('product_category')
}

// 批量处理产品数据
const processProductData = async (products: any[]) => {
  return await batchGetDictLabels(products, 'status', 'status')
}

字典组件高级用法

自定义字典组件

基于现有字典组件进行扩展:

vue
<template>
  <DictSelect
    v-model="selectedValue"
    :dict-type="dictType"
    :placeholder="placeholder"
    :clearable="clearable"
    :filterable="filterable"
    @change="handleChange"
  >
    <template #prefix>
      <el-icon><Search /></el-icon>
    </template>
  </DictSelect>
</template>

<script setup lang="ts">
interface Props {
  dictType: string
  modelValue?: string | number
  placeholder?: string
  clearable?: boolean
  filterable?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  placeholder: '请选择',
  clearable: true,
  filterable: true,
})

const emit = defineEmits<{
  'update:modelValue': [value: string | number]
  change: [value: string | number]
}>()

const selectedValue = computed({
  get: () => props.modelValue,
  set: value => emit('update:modelValue', value),
})

const handleChange = (value: string | number) => {
  emit('change', value)
}
</script>

字典组件组合使用

多个字典组件组合使用场景:

vue
<template>
  <div class="product-filter">
    <!-- 级联选择产品分类 -->
    <DictCascader
      v-model="selectedCategory"
      dict-type="product_category"
      placeholder="请选择产品分类"
      show-all-levels
    />

    <!-- 多选产品类型 -->
    <DictCheckbox v-model="selectedTypes" dict-type="product_type" placeholder="请选择产品类型" />

    <!-- 产品状态开关 -->
    <DictSwitch
      v-model="statusFilter"
      dict-type="product_status"
      active-value="1"
      inactive-value="0"
    />
  </div>
</template>

最佳实践

字典命名规范

  • 使用有意义的字典类型名称
  • 保持命名一致性(小写字母+下划线)
  • 避免使用过于宽泛的名称

性能优化建议

  • 合理使用字典缓存,避免频繁刷新
  • 批量处理字典数据,减少请求次数
  • 在组件卸载时清理字典引用

错误处理策略

  • 始终使用安全的字典访问函数
  • 提供合理的默认值和错误提示
  • 记录字典访问错误日志

常见问题

Q: 字典数据加载失败怎么办?

A: 使用安全字典函数会自动处理错误,返回默认值或空数据。

Q: 如何强制刷新字典数据?

A: 使用 refreshDictData() 方法或设置 forceRefresh: true 参数。

Q: 字典组件不显示数据怎么办?

A: 检查字典类型名称是否正确,确认后端字典数据已配置。

Q: 如何自定义字典标签样式?

A: 通过 DictTag 组件的 type 属性或自定义 CSS 类名实现样式定制。