首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Spring Boot3 整合VUE3实现前端自定义字典组件

Spring Boot3 整合VUE3实现前端自定义字典组件

作者头像
Harry技术
发布2025-01-13 17:30:03
发布2025-01-13 17:30:03
3800
举报

Spring Boot3 整合VUE3实现前端自定义字典组件

Harry技术后台管理系统,gitee地址:https://gitee.com/harry-tech/harry,采用SpringBoot2.7、MyBatis-Plus、SpringSecurity安全框架等,开发的一套权限系统,实现前后端分离前端技术栈 Vue3 、Element Plus 、Vite(JS版)。

鉴于TypeScript的流行性和其在类型检查、代码智能提示等方面的优势,对前端代码进行静态类型检查,提升代码质量和可维护性。我决定出一个TypeScript版本。

后端技术栈升级计划

后端技术栈升级至Spring Boot 3,这一升级将带来性能提升、新特性支持以及更好的兼容性。目前,该升级工作仍在开发中,一旦完成,我会开源出来,供大家一起学习和交流。

在网上看到Vue3-Element-Admin-Thin项目(基于 Vue3 + Vite5+ TypeScript5 + Element-Plus + Pinia 等主流技术栈构建的),以此作为基础,学习一下。

image-20241230202651847

在前后端分离的项目中,后端设计通常会包含一些字典值,这些字典值在前端展示时可能只是一些数字代码。为了提升用户体验,我们需要将这些数字代码转换为对应的文字描述。Vue3-Element-Admin提供了一个字典值读取及展示组件,可以方便地实现这一功能。

未使用字典

首页:我们后端先写一个获取所有字典值的一个接口/sys/dict/list,这个接口应该返回一个包含字典编码和对应文字描述的JSON对象。返回内容格式如下:

代码语言:javascript
复制
{
    "code": 200,
    "message": "操作成功",
    "data": [
        {
            "name": "用户性别",
            "type": "sys_user_sex",
            "data": [
                {
                    "value": "0",
                    "label": "男",
                    "tagType": "success"
                },
                {
                    "value": "1",
                    "label": "女",
                    "tagType": "warning"
                },
                {
                    "value": "2",
                    "label": "未知",
                    "tagType": "info"
                }
            ]
        }
    ]
}

我们先看看使用字典组件的最终效果,是不是提高了可读性

使用字典组件

前端代码改写

首先在store中定义useDictStore模块,这里是用的是Pinia 存储库,在index中export

代码语言:javascript
复制
import { store } from"@/store";
import DictionaryAPI, { type DictVO, type DictData } from"@/api/system/dict";

exportconst useDictStore = defineStore("dict", () => {
const dictionary = useStorage<Record<string, DictData[]>>("dictionary", {});

const setDictionary = (dict: DictVO) => {
    dictionary.value[dict.type] = dict.data;
  };

const loadDictionaries = async () => {
      // 获取所有字典值的一个接口`/sys/dict/list`
    const dictList = await DictionaryAPI.getList();
    dictList.forEach(setDictionary);
  };

const getDictionary = (dictCode: string): DictData[] => {
    return dictionary.value[dictCode] || [];
  };

const clearDictionaryCache = () => {
    dictionary.value = {};
  };

const updateDictionaryCache = async () => {
    clearDictionaryCache(); // 先清除旧缓存
    await loadDictionaries(); // 重新加载最新字典数据
  };

return {
    dictionary,
    setDictionary,
    loadDictionaries,
    getDictionary,
    clearDictionaryCache,
    updateDictionaryCache,
  };
});

exportfunction useDictStoreHook() {
return useDictStore(store);
}

image-20241230211116132

在components定义组件

代码语言:javascript
复制
<template>
  <el-select
    v-if="type === 'select'"
    v-model="selectedValue"
    :placeholder="placeholder"
    :disabled="disabled"
    clearable
    :style="style"
    @change="handleChange"
  >
    <el-option
      v-for="option in options"
      :key="option.value"
      :label="option.label"
      :value="option.value"
    />
  </el-select>

  <el-radio-group
    v-else-if="type === 'radio'"
    v-model="selectedValue"
    :disabled="disabled"
    :style="style"
    @change="handleChange"
  >
    <el-radio
      v-for="option in options"
      :key="option.value"
      :label="option.label"
      :value="option.value"
    >
      {{ option.label }}
    </el-radio>
  </el-radio-group>

  <el-checkbox-group
    v-else-if="type === 'checkbox'"
    v-model="selectedValue"
    :disabled="disabled"
    :style="style"
    @change="handleChange"
  >
    <el-checkbox
      v-for="option in options"
      :key="option.value"
      :label="option.label"
      :value="option.value"
    >
      {{ option.label }}
    </el-checkbox>
  </el-checkbox-group>
</template>

<script setup lang="ts">
import { useDictStore } from "@/store";

const dictStore = useDictStore();

const props = defineProps({
  code: {
    type: String,
    required: true,
  },
  modelValue: {
    type: [String, Number, Array],
    required: false,
  },
  type: {
    type: String,
    default: "select",
    validator: (value: string) =>
      ["select", "radio", "checkbox"].includes(value),
  },
  placeholder: {
    type: String,
    default: "请选择",
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  style: {
    type: Object,
    default: () => {
      return {
        width: "300px",
      };
    },
  },
});

const emit = defineEmits(["update:modelValue"]);

const options = ref<Array<{ label: string; value: string | number }>>([]);

const selectedValue = ref<any>(
  typeof props.modelValue === "string" || typeof props.modelValue === "number"
    ? props.modelValue
    : Array.isArray(props.modelValue)
      ? props.modelValue
      : undefined
);

// 监听 modelValue 变化
watch(
  () => props.modelValue,
  (newValue) => {
    if (props.type === "checkbox") {
      selectedValue.value = Array.isArray(newValue) ? newValue : [];
    } else {
      selectedValue.value = newValue?.toString() || "";
    }
  },
  { immediate: true }
);

// 监听 options 变化并重新匹配 selectedValue
watch(
  () => options.value,
  (newOptions) => {
    // options 加载后,确保 selectedValue 可以正确匹配到 options
    if (newOptions.length > 0 && selectedValue.value !== undefined) {
      const matchedOption = newOptions.find(
        (option) => option.value === selectedValue.value
      );
      if (!matchedOption && props.type !== "checkbox") {
        selectedValue.value = ""; // 如果找不到匹配项,清空选中
      }
    }
  }
);

// 监听 selectedValue 的变化并触发 update:modelValue
function handleChange(val: any) {
  emit("update:modelValue", val);
}

// 获取字典数据
onMounted(() => {
  options.value = dictStore.getDictionary(props.code);
});
</script>

在页面中调用:

代码语言:javascript
复制
<el-form-item label="性别" prop="sex">
 <Dict v-model="formData.sex" code="sys_user_sex" />
</el-form-item>

code : 字典类型,对应接口中的type。根据type获取相应的字典数据

这样就加载出来了

image-20241230211345303

定义DictLabel组件

代码语言:javascript
复制
<template>
  <template v-if="tagType">
    <el-tag :type="tagType" :size="tagSize">{{ label }}</el-tag>
  </template>
  <template v-else>
    <span>{{ label }}</span>
  </template>
</template>

<script setup lang="ts">
import { useDictStore } from "@/store";
const dictStore = useDictStore();

const props = defineProps({
  code: String,
  modelValue: [String, Number],
  size: {
    type: String,
    default: "default",
  },
});

const label = ref("");
const tagType = ref<
  "success" | "warning" | "info" | "primary" | "danger" | undefined
>();

const tagSize = ref(props.size as "default" | "large" | "small");

const getLabelAndTagByValue = async (dictCode: string, value: any) => {
  // 先从本地缓存中获取字典数据
  const dictData = dictStore.getDictionary(dictCode);
  // 查找对应的字典项
  const dictEntry = dictData.find((item: any) => item.value == value);
  return {
    label: dictEntry ? dictEntry.label : "",
    tag: dictEntry ? dictEntry.tagType : undefined,
  };
};

// 监听 props 的变化,获取并更新 label 和 tag
const fetchLabelAndTag = async () => {
  const result = await getLabelAndTagByValue(
    props.code as string,
    props.modelValue
  );
  label.value = result.label;
  tagType.value = result.tag as
    | "success"
    | "warning"
    | "info"
    | "primary"
    | "danger"
    | undefined;
};

// 首次挂载时获取字典数据
onMounted(fetchLabelAndTag);

// 当 modelValue 发生变化时重新获取
watch(() => props.modelValue, fetchLabelAndTag);
</script>

在页面中调用:

代码语言:javascript
复制
<el-table-column label="性别" width="100" align="center" prop="sex">
  <template #default="scope">
    <DictLabel
      v-model="scope.row.sex"
      code="sys_user_sex"
      size="small"
    />
  </template>
</el-table-column>

显示效果:

注意 需要在路由跳转前加载字典数据,否则会出现字典数据未加载完成导致页面渲染异常

所以,我们在登录之后获取用户信息时,同时加载字典数据

到这里,字典值读取及展示组件就介绍完了。更多文章推荐,公众号搜“Harry技术”,关注我,带你看不一样的人间烟火!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-12-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Harry技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring Boot3 整合VUE3实现前端自定义字典组件
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档