refactor: ♻️ 字典加载调整为登陆后缓存作为数据源

This commit is contained in:
ray
2024-10-18 21:54:49 +08:00
parent 42150877a3
commit f0e045599b
8 changed files with 386 additions and 195 deletions

180
src/api/system/dict.ts Normal file
View File

@@ -0,0 +1,180 @@
import request from "@/utils/request";
const DICT_BASE_URL = "/api/v1/dict";
const DictAPI = {
/**
* 获取字典分页列表
*
* @param queryParams 查询参数
* @returns 字典分页结果
*/
getPage(queryParams: DictPageQuery) {
return request<any, PageResult<DictPageVO[]>>({
url: `${DICT_BASE_URL}/page`,
method: "get",
params: queryParams,
});
},
/**
* 获取字典表单数据
*
* @param id 字典ID
* @returns 字典表单数据
*/
getFormData(id: number) {
return request<any, ResponseData<DictForm>>({
url: `${DICT_BASE_URL}/${id}/form`,
method: "get",
});
},
/**
* 新增字典
*
* @param data 字典表单数据
*/
add(data: DictForm) {
return request({
url: `${DICT_BASE_URL}`,
method: "post",
data: data,
});
},
/**
* 修改字典
*
* @param id 字典ID
* @param data 字典表单数据
*/
update(id: number, data: DictForm) {
return request({
url: `${DICT_BASE_URL}/${id}`,
method: "put",
data: data,
});
},
/**
* 删除字典
*
* @param ids 字典ID多个以英文逗号(,)分隔
*/
deleteByIds(ids: string) {
return request({
url: `${DICT_BASE_URL}/${ids}`,
method: "delete",
});
},
/**
* 获取字典列表
*
* @returns 字典列表
*/
getList() {
return request<any, DictVO[]>({
url: `${DICT_BASE_URL}/list`,
method: "get",
});
},
};
export default DictAPI;
/**
* 字典查询参数
*/
export interface DictPageQuery extends PageQuery {
/**
* 关键字(字典名称/编码)
*/
keywords?: string;
/**
* 字典状态1:启用0:禁用)
*/
status?: number;
}
/**
* 字典分页对象
*/
export interface DictPageVO {
/**
* 字典ID
*/
id: number;
/**
* 字典名称
*/
name: string;
/**
* 字典编码
*/
dictCode: string;
/**
* 字典状态1:启用0:禁用)
*/
status: number;
}
/**
* 字典
*/
export interface DictForm {
/**
* 字典ID
*/
id?: number;
/**
* 字典名称
*/
name?: string;
/**
* 字典编码
*/
dictCode?: string;
/**
* 字典状态1-启用0-禁用)
*/
status?: number;
/**
* 备注
*/
remark?: string;
}
/**
* 字典数据项分页VO
*
* @description 字典数据分页对象
*/
export interface DictVO {
/** 字典名称 */
name: string;
/** 字典编码 */
dictCode: string;
/** 字典数据集合 */
dictDataList: DictData[];
}
/**
* 字典数据
*
* @description 字典数据
*/
export interface DictData {
/** 字典数据值 */
value: string;
/** 字典数据标签 */
label: string;
/** 标签类型 */
tagType: string;
}

View File

@@ -8,9 +8,8 @@
</template>
<script setup lang="ts">
import DictDataAPI from "@/api/dict-data";
import Cache from "@/utils/cache";
import requestCache from "@/utils/requestCache"; //
import { useDictStore } from "@/store";
const dictStore = useDictStore();
const props = defineProps({
code: String,
@@ -28,40 +27,15 @@ const tagType = ref<
const tagSize = ref(props.size as "default" | "large" | "small");
const dictCache = new Cache("dict_");
const getLabelAndTagByValue = async (dictCode: string, value: any) => {
//
let dictData = dictCache.getCache(dictCode);
//
if (!dictData) {
if (!requestCache.has(dictCode)) {
// Promise
const requestPromise = DictDataAPI.getOptions(dictCode)
.then((data) => {
dictCache.setCache(dictCode, data, 3 * 60 * 1000); // 3
requestCache.delete(dictCode); //
return data;
})
.catch((error) => {
requestCache.delete(dictCode); //
throw error;
});
//
requestCache.set(dictCode, requestPromise);
}
//
dictData = await requestCache.get(dictCode);
}
const dictData = dictStore.getDictionary(dictCode);
//
const dictEntry = dictData.find((item: any) => item.value == value);
return {
label: dictEntry ? dictEntry.label : "",
tag: dictEntry ? dictEntry.tag : undefined,
tag: dictEntry ? dictEntry.tagType : undefined,
};
};
@@ -72,7 +46,13 @@ const fetchLabelAndTag = async () => {
props.modelValue
);
label.value = result.label;
tagType.value = result.tag;
tagType.value = result.tag as
| "success"
| "warning"
| "info"
| "primary"
| "danger"
| undefined;
};
//

View File

@@ -0,0 +1,136 @@
<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 { ref, watch, onBeforeMount } from "vue";
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;
}
}
);
// 监听 selectedValue 的变化并触发 update:modelValue
function handleChange(val: any) {
emit("update:modelValue", val);
}
// 获取字典数据
onBeforeMount(async () => {
options.value = await dictStore.getDictionary(props.code);
if (props.modelValue !== undefined) {
if (props.type === "checkbox") {
selectedValue.value = Array.isArray(props.modelValue)
? props.modelValue
: [];
} else {
selectedValue.value = props.modelValue;
}
}
});
</script>

View File

@@ -1,78 +0,0 @@
<template>
<el-select
v-model="selectedValue"
:placeholder="placeholder"
:disabled="disabled"
clearable
@change="handleChange"
>
<el-option
v-for="option in options"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</template>
<script setup lang="ts">
import DictDataAPI from "@/api/dict-data";
const props = defineProps({
code: {
type: String,
required: true,
},
modelValue: {
type: [String, Number],
required: false,
},
placeholder: {
type: String,
default: "请选择",
},
disabled: {
type: Boolean,
default: false,
},
});
// 使用 defineEmits 声明 emits
const emit = defineEmits(["update:modelValue"]);
// 下拉框选项
const options = ref<Array<{ label: string; value: string | number }>>([]);
const selectedValue = ref<string | number | undefined>(props.modelValue);
// 监听 modelValue 变化
watch(
() => props.modelValue,
(newValue) => {
// 类型转换确保 selectedValue 和 option.value 类型一致
if (typeof options.value[0]?.value === "number") {
selectedValue.value = Number(newValue);
} else {
selectedValue.value = String(newValue);
}
}
);
// 监听 selectedValue 的变化并触发 update:modelValue
function handleChange(val?: string | number) {
emit("update:modelValue", val);
}
// 获取字典数据
onBeforeMount(async () => {
const data = await DictDataAPI.getOptions(props.code);
options.value = data;
// 初次加载时处理类型一致性
if (props.modelValue !== undefined) {
if (typeof options.value[0]?.value === "number") {
selectedValue.value = Number(props.modelValue);
} else {
selectedValue.value = String(props.modelValue);
}
}
});
</script>

41
src/store/modules/dict.ts Normal file
View File

@@ -0,0 +1,41 @@
import { store } from "@/store";
import DictionaryAPI, { type DictVO, type DictData } from "@/api/system/dict";
export const useDictStore = defineStore("dict", () => {
const dictionary = useStorage<Record<string, DictData[]>>("dictionary", {});
const setDictionary = (dict: DictVO) => {
dictionary.value[dict.dictCode] = dict.dictDataList;
};
const loadDictionaries = async () => {
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,
};
});
export function useDictStoreHook() {
return useDictStore(store);
}

View File

@@ -1,79 +0,0 @@
const DEFAULT_CACHE_EXPIRY_TIME = 5 * 60 * 1000; // 默认缓存有效期为5分钟
/**
* 通用缓存工具类
*/
class Cache {
private cachePrefix: string;
constructor(prefix: string = "cache_") {
this.cachePrefix = prefix;
}
/**
* 设置缓存
*
* @param key 缓存的键
* @param data 缓存的数据
* @param expiryTime 缓存有效期毫秒默认5分钟
*/
setCache(
key: string,
data: any,
expiryTime: number = DEFAULT_CACHE_EXPIRY_TIME
) {
const expiryTimestamp = new Date().getTime() + expiryTime;
const cacheKey = this.cachePrefix + key;
const cacheData = { data, expiryTimestamp };
localStorage.setItem(cacheKey, JSON.stringify(cacheData));
}
/**
* 获取缓存
*
* @param key 缓存的键
* @returns 如果缓存有效则返回缓存的数据,否则返回 null
*/
getCache(key: string) {
const cacheKey = this.cachePrefix + key;
const cached = localStorage.getItem(cacheKey);
if (cached) {
const { data, expiryTimestamp } = JSON.parse(cached);
const now = new Date().getTime();
// 如果缓存未过期,返回数据
if (now < expiryTimestamp) {
return data;
} else {
// 如果缓存过期,移除缓存
localStorage.removeItem(cacheKey);
}
}
return null;
}
/**
* 移除缓存
*
* @param key 缓存的键
*/
removeCache(key: string) {
const cacheKey = this.cachePrefix + key;
localStorage.removeItem(cacheKey);
}
/**
* 清空当前前缀下的所有缓存
*/
clearCache() {
for (const key in localStorage) {
if (key.startsWith(this.cachePrefix)) {
localStorage.removeItem(key);
}
}
}
}
export default Cache;

View File

@@ -1,4 +0,0 @@
// 创建一个共享的 requestCache
const requestCache = new Map<string, Promise<any>>();
export default requestCache;

View File

@@ -2,12 +2,13 @@
<script setup lang="ts">
const stringValue = ref("1"); // (String)
const numberValue = ref(1); // (Number)
const arraryValue = ref(["1", "2"]); // (Array)
</script>
<template>
<div class="app-container">
<el-link
href="https://gitee.com/youlaiorg/vue3-element-admin/blob/master/src/views/demo/dict.vue"
href="https://gitee.com/youlaiorg/vue3-element-admin/blob/master/src/views/demo/dictionary.vue"
type="primary"
target="_blank"
class="mb-[20px]"
@@ -16,18 +17,32 @@ const numberValue = ref(1); // 性别(值为Number)
</el-link>
<el-form>
<el-form-item label="性别">
<dictionary v-model="stringValue" code="gender" />
<dict v-model="stringValue" code="gender" />
<el-link :underline="false" type="primary" class="ml-5">
值为String: const value = ref("1");
</el-link>
</el-form-item>
<el-form-item label="性别">
<dictionary v-model="numberValue" code="gender" />
<dict v-model="numberValue" code="gender" />
<el-link :underline="false" type="success" class="ml-5">
值为Number: const value = ref(1);
</el-link>
</el-form-item>
<el-form-item label="单字典">
<dict v-model="numberValue" type="radio" code="gender" />
<el-link :underline="false" type="success" class="ml-5">
值为Number: const value = ref(1);
</el-link>
</el-form-item>
<el-form-item label="复选框字典">
<dict v-model="arraryValue" type="checkbox" code="gender" />
<el-link :underline="false" type="success" class="ml-5">
值为Number: const value = ref(["1", "2"]);
</el-link>
</el-form-item>
</el-form>
</div>
</template>