目录
- 前言
- 1. 基本知识
- 2. Demo
- 3. 实战
前言
🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF
从实战中出发:
1. 基本知识
Vue3 和 Java 通信时如何进行字典数据管理
需要了解字典数据的结构。通常,字典数据包含一个键和一个值,有时候还有一些额外的属性,比如颜色类型或 CSS 类,这些在前端展示时可能会用到
库来发送 HTTP 请求到后端,获取这些字典数据。
需要分析如何在前端存储和管理这些字典数据。Pinia 是一个推荐的 Vue
通过 Pinia,可以定义一个字典 store,用来存储所有获取到的字典数据。这样,所有组件都可以方便地访问这些数据,而不需要每次都通过 API 获取.另外,前端缓存也是一个重要的考虑因素。由于字典数据通常是静态的或变化不大,可以在前端缓存这些数据,减少对后端的请求次数,提高应用的响应速度。Vue 的hooks,比如 useCache
如何在路由和权限控制中使用这些字典数据
在用户登录后,前端需要获取字典数据以完成菜单加载和路由动态生成。这里需要与 Vue Router 结合,根据字典数据生成对应的路由配置,确保用户只能访问其权限范围内的功能。还需要处理字典数据的更新和缓存的失效。如果字典数据有更新,前端需要有一个机制来刷新缓存,确保用户使用的是最新的数据。这可能涉及到设置缓存的有效期,或者在有更新时手动清除缓存并重新获取数据
字典数据管理是一个重要的组成部分,字典数据通常包括各种下拉列表、状态标识等,用于在页面中展示和交互
特别是当使用 Vue3 与 Java 后端进行通信时,字典数据的获取、存储、管理和reload都成为关键点
- 字典数据通常包括以下几个要素:
- 字典类型(dictType):表示字典的分类,如用户状态、订单状态等
- 字典值(dictValue):具体的一个字典项,包含 value 和 label,有时还包括额外属性如颜色或 CSS 类
- 其他属性:根据业务需求,可能还包括颜色类型、排序顺序等
interface DictDataVO {dictType: string;value: string;label: string;colorType?: string;cssClass?: string;
}
后端会提供一个获取字典数据的接口,如:GET /api/system/dict/simple
返回的数据格式如下:
[{"dictType": "user_status","value": "0","label": "正常"},{"dictType": "user_status","value": "1","label": "禁用"},// 其他字典项
]
- 使用 Pinia 进行字典数据的状态管理,Pinia 是 Vue3 推荐的状态管理库,适合管理全局状态,如字典数据
具体定义的Store:
export const useDictStore = defineStore('dict', {state: () => ({dictMap: new Map<string, any>(),isSetDict: false}),getters: {getDictMap: (state) => state.dictMap,getIsSetDict: (state) => state.isSetDict},actions: {async setDictMap() {// 具体逻辑稍后分析},getDictByType(type: string) {return this.dictMap.get(type) || [];}}
});
后续只需要初始化对应的Store即可:
const dictStore = useDictStore();
dictStore.setDictMap();
- 前端缓存可以减少对后端的请求,提高响应速度。可以存储在 sessionStorage 或 localStorage 中
使用自定义缓存钩子:
import { CACHE_KEY } from '@/hooks/web/useCache';const { wsCache } = useCache('sessionStorage');// 存储字典数据
wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 });// 获取字典数据
const cachedDict = wsCache.get(CACHE_KEY.DICT_CACHE);
- 字典数据的获取与初始化
在用户登录后,前端需要获取字典数据并存储到 Pinia 和缓存中
逻辑步骤:
检查缓存:首先尝试从缓存中获取字典数据,如果存在且未过期,直接使用
从后端获取:如果缓存不存在或过期,发送请求到后端获取最新数据
存储到 Pinia 和缓存:将获取到的字典数据存储到 Pinia 和 SessionStorage 中
动态生成路由(可选):根据字典数据动态加载菜单和路由,确保用户只能访问权限内的功能
主要的步骤如下:
async function initDictData() {const dictStore = useDictStore();if (dictStore.isSetDict) return;// 从缓存获取const cachedDict = wsCache.get(CACHE_KEY.DICT_CACHE);if (cachedDict) {dictStore.dictMap = cachedDict;dictStore.isSetDict = true;return;}// 从后端获取try {const response = await getSimpleDictDataList();const dictDataMap = new Map<string, any>();response.forEach((dictData: DictDataVO) => {if (!dictDataMap.has(dictData.dictType)) {dictDataMap.set(dictData.dictType, []);}dictDataMap.get(dictData.dictType)?.push({value: dictData.value,label: dictData.label,colorType: dictData.colorType,cssClass: dictData.cssClass});});// 存储到 Pinia 和缓存dictStore.dictMap = dictDataMap;dictStore.isSetDict = true;wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 });} catch (error) {console.error('Failed to fetch dictionary data:', error);}
}
- 在组件中使用字典数据
在需要使用字典数据的组件中,可以轻松访问 Pinia 存储的字典数据
<template><div><label>用户状态:</label><select v-model="selectedStatus"><option v-for="(item, index) in dictStatus" :key="index" :value="item.value">{{ item.label }}</option></select></div>
</template><script setup lang="ts">
const dictStore = useDictStore();
const dictStatus = dictStore.getDictByType('user_status');
const selectedStatus = ref('0');
</script>
``6. 字典数据的更新与缓存失效
为确保字典数据的一致性,需要处理缓存的更新和失效```js
async function refreshDict() {const dictStore = useDictStore();// 清除缓存wsCache.delete(CACHE_KEY.DICT_CACHE);// 重新加载字典数据await dictStore.setDictMap();
}
2. Demo
以下主要是围绕第三章的实战中的Demo,提供的思路
前端与后端通信:使用 Vue3 和 Axion 发送 HTTP 请求到 Java 后端,获取字典数据
状态管理:使用 Pinia 管理字典数据,确保全局状态的唯一性和一致性
前端缓存:通过自定义缓存钩子,将字典数据存储在 sessionStorage 中,减少对后端的请求
组件化开发:创建可复用的字典选择组件,提升代码的可维护性和扩展性
动态数据加载:在应用初始化时加载字典数据,支持动态生成路由和菜单
项目结构:
src/
├── api/
│ └── dict.ts # 后端 API 接口声明
├── hooks/
│ └── web/
│ └── useCache.ts # 缓存钩子
├── store/
│ └── modules/
│ └── dict.ts # Pinia Store 定义
├── components/
│ └── DictSelect.vue # 字典选择组件
└── main.ts # 应用入口
定义Api接口:
// src/api/dict.ts
export interface DictDataVO {dictType: string;value: string;label: string;colorType?: string;cssClass?: string;
}export async function getSimpleDictDataList(): Promise<DictDataVO[]> {return await request({url: '/api/system/dict/simple',method: 'GET'});
}
- 创建字典状态管理 Store:
// src/store/modules/dict.ts
import { defineStore } from 'pinia';
import { CACHE_KEY, useCache } from '@/hooks/web/useCache';const { wsCache } = useCache('sessionStorage');
import { getSimpleDictDataList } from '@/api/dict';export interface DictState {dictMap: Map<string, any>;isSetDict: boolean;
}export const useDictStore = defineStore('dict', {state: () => ({dictMap: new Map<string, any>(),isSetDict: false}),getters: {getDictMap: state => state.dictMap,getIsSetDict: state => state.isSetDict},actions: {async setDictMap() {if (this.isSetDict) return;const cachedDict = wsCache.get(CACHE_KEY.DICT_CACHE);if (cachedDict) {this.dictMap = cachedDict;this.isSetDict = true;return;}try {const response = await getSimpleDictDataList();const dictDataMap = new Map<string, any>();response.forEach(dictData => {if (!dictDataMap.has(dictData.dictType)) {dictDataMap.set(dictData.dictType, []);}dictDataMap.get(dictData.dictType)?.push({value: dictData.value,label: dictData.label,colorType: dictData.colorType,cssClass: dictData.cssClass});});this.dictMap = dictDataMap;this.isSetDict = true;wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 });} catch (error) {console.error('Failed to fetch dictionary data:', error);}},getDictByType(type: string) {return this.dictMap.get(type) || [];}}
});
- 创建字典选择组件:
<!-- src/components/DictSelect.vue -->
<template><div class="dict-select"><label>{{ label }}</label><select v-model="selectedValue" @change="handleChange"><option v-for="(item, index) in dictOptions" :key="index" :value="item.value">{{ item.label }}</option></select></div>
</template><script setup lang="ts">
import { useDictStore } from '@/store/modules/dict';interface DictSelectProps {dictType: string;label?: string;modelValue?: string;
}const props = defineProps<DictSelectProps>();
const emit = defineEmits(['update:modelValue']);const dictStore = useDictStore();
const dictOptions = dictStore.getDictByType(props.dictType);
const selectedValue = ref(props.modelValue);const handleChange = (e: Event) => {const value = (e.target as HTMLSelectElement).value;selectedValue.value = value;emit('update:modelValue', value);
};
</script><style scoped>
.dict-select {margin: 10px 0;
}select {padding: 5px;border: 1px solid #ddd;border-radius: 4px;
}
</style>
- 在主应用中初始化字典数据:
// src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import pinia from './store';
import { useDictStore } from './store/modules/dict';const app = createApp(App).use(router).use(pinia);router.isReady().then(() => {app.mount('#app');// 初始化字典数据const dictStore = useDictStore();dictStore.setDictMap();
});
- 使用字典选择组件:
<!-- src/App.vue -->
<template><div id="app"><h1>字典数据管理示例</h1><dict-selectdictType="user_status"label="用户状态"v-model:modelValue="selectedStatus"/></div>
</template><script setup lang="ts">
import DictSelect from './components/DictSelect.vue';const selectedStatus = ref('0');
</script>
3. 实战
以下实战来源:https://gitee.com/zhijiantianya/ruoyi-vue-pro
附上基本的代码注释讲解:
权限管理模块:
import router from './router'
import type { RouteRecordRaw } from 'vue-router'
import { isRelogin } from '@/config/axios/service'
import { getAccessToken } from '@/utils/auth'
import { useTitle } from '@/hooks/web/useTitle'
import { useNProgress } from '@/hooks/web/useNProgress'
import { usePageLoading } from '@/hooks/web/usePageLoading'
import { useDictStoreWithOut } from '@/store/modules/dict'
import { useUserStoreWithOut } from '@/store/modules/user'
import { usePermissionStoreWithOut } from '@/store/modules/permission'// 初始化进度条和页面加载状态
const { start, done } = useNProgress()
const { loadStart, loadDone } = usePageLoading()/*** 解析孔(URL)为路径和参数对象* @param url 输入的完整URL* @returns 包含 basePath 和 paramsObject的对象*/
const parseURL = (url: string | null | undefined
): { basePath: string; paramsObject: { [key: string]: string } } => {// 如果输入为 null 或 undefined,返回空字符串和空对象if (url == null) {return { basePath: '', paramsObject: {} }}// 找到问号的位置,分割基础路径和查询参数const questionMarkIndex = url.indexOf('?')let basePath = urlconst paramsObject: { [key: string]: string } = {}// 如果有查询参数,进行解析if (questionMarkIndex !== -1) {basePath = url.substring(0, questionMarkIndex)const queryString = url.substring(questionMarkIndex + 1)const searchParams = new URLSearchParams(queryString)searchParams.forEach((value, key) => {paramsObject[key] = value})}// 返回解析后的结果return { basePath, paramsObject }
}// 不需要重定向的白名单路径
const whiteList = ['/login','/social-login','/auth-redirect','/bind','/register','/oauthLogin/gitee'
]// 路由加载前的钩子函数
router.beforeEach(async (to, from, next) => {start() // 开始进度条loadStart() // 开始页面加载if (getAccessToken()) { // 检查是否有访问令牌if (to.path === '/login') {// 已经登录的情况下,重定向到主页next({ path: '/' })} else {// 获取字典、用户、权限仓库const dictStore = useDictStoreWithOut()const userStore = useUserStoreWithOut()const permissionStore = usePermissionStoreWithOut()// 检查字典是否加载完成if (!dictStore.getIsSetDict) {await dictStore.setDictMap() // 如果没有加载过,加载字典数据}// 检查用户信息是否加载完成if (!userStore.getIsSetUser) {isRelogin.show = true // 显示重新登录提示await userStore.setUserInfoAction() // 加载用户信息isRelogin.show = false// 加载权限路由await permissionStore.generateRoutes()permissionStore.getAddRouters.forEach((route) => {router.addRoute(route as unknown as RouteRecordRaw) // 动态添加路由})// 处理重定向路径const redirectPath = from.query.redirect || to.pathconst redirect = decodeURIComponent(redirectPath as string)const { basePath, paramsObject: query } = parseURL(redirect)// 根据情况决定是否替换当前路由const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect, query }next(nextData)} else {next() // 用户信息已加载,继续导航}}} else {// 没有登录,检查是否在白名单中if (whiteList.indexOf(to.path) !== -1) {next() // 白名单路径,允许访问} else {// 重定向到登录页,并携带当前路径作为 redirect 参数next(`/login?redirect=${to.fullPath}`)}
})// 路由加载完成后的钩子函数
router.afterEach((to) => {useTitle(to?.meta?.title as string) // 更新页面标题done() // 结束进度条loadDone() // 结束页面加载状态
})
字典管理模块:
import { defineStore } from 'pinia'
import { store } from '../index'
import { DictDataVO } from '@/api/system/dict/types'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache('sessionStorage')
import { getSimpleDictDataList } from '@/api/system/dict/dict.data'/*** 字典值的类型定义*/
export interface DictValueType {value: anylabel: stringcolorType?: stringcssClass?: string
}/*** 字典类型的类型定义*/
export interface DictTypeType {dictType: stringdictValue: DictValueType[]
}/*** 字典状态类型定义*/
export interface DictState {dictMap: Map<string, any>isSetDict: boolean
}/*** 定义字典 Pinia Store*/
export const useDictStore = defineStore('dict', {state: (): DictState => ({dictMap: new Map<string, any>(), // 字典映射表isSetDict: false // 表示字典是否已加载}),getters: {/*** 获取字典映射表,从缓存中读取*/getDictMap(): Recordable {const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)if (dictMap) {this.dictMap = dictMap}return this.dictMap},/*** 检查字典是否已加载*/getIsSetDict(): boolean {return this.isSetDict}},actions: {/*** 设置字典映射表,从缓存或API获取*/async setDictMap() {const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)if (dictMap) {this.dictMap = dictMapthis.isSetDict = true} else {const res = await getSimpleDictDataList()// 构建字典数据映射const dictDataMap = new Map<string, any>()res.forEach((dictData: DictDataVO) => {// 按照 dictType 分组if (!dictDataMap.has(dictData.dictType)) {dictDataMap.set(dictData.dictType, [])}// 添加字典值dictDataMap.get(dictData.dictType)?.push({value: dictData.value,label: dictData.label,colorType: dictData.colorType,cssClass: dictData.cssClass})})// 更新状态和缓存this.dictMap = dictDataMapthis.isSetDict = truewsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 缓存60秒}},/*** 根据字典类型获取对应的字典值* @param type 字典类型*/getDictByType(type: string) {if (!this.isSetDict) {this.setDictMap() // 如果未加载,先加载字典}return this.dictMap[type]},/*** 重置字典数据,清空缓存并重新加载*/async resetDict() {wsCache.delete(CACHE_KEY.DICT_CACHE) // 清空缓存const res = await getSimpleDictDataList()const dictDataMap = new Map<string, any>()res.forEach((dictData: DictDataVO) => {// 重新构建字典映射if (!dictDataMap.has(dictData.dictType)) {dictDataMap.set(dictData.dictType, [])}dictDataMap.get(dictData.dictType)?.push({value: dictData.value,label: dictData.label,colorType: dictData.colorType,cssClass: dictData.cssClass})})this.dictMap = dictDataMapthis.isSetDict = truewsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 更新缓存}}
})/*** 提供一个不带 store 的字典 Store 实例*/
export const useDictStoreWithOut = () => {return useDictStore(store)
}