通用ID转名称组件:表格字段显示与缓存策略
引言 📜
在Vue项目中,我们经常需要将后端返回的ID值转换为对应的名称进行展示。本文介绍一个高效的解决方案:通用ID转名称组件及其缓存机制。
核心思路 🎯
使用Pinia存储管理不同表(如分类表、平台表)的ID到名称的映射,并实现多级缓存策略:
- 内存缓存 - 快速访问
- localStorage缓存 - 持久化存储
- API调用 - 最终数据源
存储设计(useTableInfoStore)📦
数据结构
interface TableInfo {[key: number]: string; // ID到名称的映射
}interface TableInfoState {[tableName: string]: TableInfo; // 不同表的数据存储
}
表名与API的映射
const tableApiMap = {category: {api: getAllDsCategoryList,responseType: 'dsCategoryList',nameField: 'categoryName'},platform: {api: getAllDsPlatformList,responseType: 'dsPlatformList',nameField: 'platformName'}
} as const;
数据获取流程
graph TDA[调用getTableInfo] --> B{内存中是否有数据?}B -->|是| C[返回内存数据]B -->|否| D{localStorage中是否有缓存?}D -->|是| E[返回缓存数据并存入内存]D -->|否| F[调用API]F --> G{API调用成功?}G -->|是| H[处理数据并存入缓存]G -->|否| I[返回空对象]H --> J[返回新数据]
核心方法实现
async getTableInfo(tableName: string) {// 1. 检查内存缓存if (this.tableInfo[tableName]?.length) return this.tableInfo[tableName];// 2. 检查localStorage缓存const cachedData = localStorage.getItem(`tableInfo:${tableName}`);if (cachedData) {this.tableInfo[tableName] = JSON.parse(cachedData);return this.tableInfo[tableName];}// 3. 调用APItry {const tableConfig = tableApiMap[tableName];const res = await tableConfig.api();if (res.code === 0) {// 转换数组为ID-名称映射const tableInfo = res.data[tableConfig.responseType].reduce((acc, item) => {acc[item.id] = item[tableConfig.nameField];return acc;}, {});// 更新缓存this.tableInfo[tableName] = tableInfo;localStorage.setItem(`tableInfo:${tableName}`, JSON.stringify(tableInfo));return tableInfo;}} catch (error) {console.error(`获取${tableName}数据失败:`, error);return {};}
}
组件设计(TableText) 🧩
组件功能
- 支持单个ID转换
- 支持多个ID转换(数组或逗号分隔字符串)
- 自动处理缓存更新
组件实现
<template>{{ text }}
</template><script lang="ts" setup>
// 省略导入语句interface ITableText {table: string;id?: number;ids?: string | number[];
}const props = defineProps<{ props: ITableText }>();
const text = ref('');const updateText = async () => {const { table, id, ids } = props.props;// 获取表信息const result = await tableInfoStore.getTableInfo(table);if (ids !== undefined) {// 处理多个IDconst idArray = typeof ids === 'string' ? ids.split(',').map(i => parseInt(i.trim())) : ids;text.value = idArray.map(i => result[i] || '').filter(name => name !== '').join(',');} else if (id !== undefined) {// 处理单个IDtext.value = result[id] || '';}
};// 初始化和监听变化
onMounted(updateText);
watch(() => props, updateText, { deep: true });
</script>
使用示例 🚀
单个ID展示
<el-table-column prop="categoryId" label="分类"><template #default="scope"><TableText :props="{ table: 'category', id: scope.row.categoryId }" /></template>
</el-table-column>
多个ID展示
<el-table-column prop="platformIds" label="平台"><template #default="scope"><TableText :props="{ table: 'platform', ids: scope.row.platformIds }" /></template>
</el-table-column>
数据流转示例 🔄
单个ID场景
多个ID场景
方案优势 ✨
- 高性能:三级缓存策略减少API调用
- 通用性:通过配置支持不同表类型
- 易用性:组件化设计简化使用
- 灵活性:支持单个和多个ID转换
- 容错性:自动处理异常情况
这个通用ID转名称解决方案通过精心设计的缓存策略和组件化实现,显著提高了表格数据显示的效率,同时保持了代码的简洁性和可维护性。
附录 🔗
pinia完整代码
web\admin\src\stores\tableInfo.ts
import { defineStore } from 'pinia';
import { getAllDsCategoryList } from '/@/api/ds/dsCategory';
import { getAllDsPlatformList } from '/@/api/ds/dsPlatform';interface TableInfo {[key: number]: string;
}interface TableInfoState {[tableName: string]: TableInfo;
}// 通用响应类型
interface BaseApiResponse {code: number;data: any;
}// 分类响应类型
interface CategoryApiResponse extends BaseApiResponse {data: {dsCategoryList: Array<{id: number;categoryName: string;}>;};
}// 平台响应类型
interface PlatformApiResponse extends BaseApiResponse {data: {dsPlatformList: Array<{id: number;platformName: string;}>;};
}// 表名与接口的映射
const tableApiMap = {category: {api: getAllDsCategoryList,responseType: 'dsCategoryList',nameField: 'categoryName'},platform: {api: getAllDsPlatformList,responseType: 'dsPlatformList',nameField: 'platformName'}
} as const;export const useTableInfoStore = defineStore('tableInfo', {state: () => ({tableInfo: {} as TableInfoState,}),actions: {async getTableInfo(tableName: string) {let key = 'tableInfo:' + tableName;console.log('开始获取表信息:', tableName);// 初始化tableInfoif (!this.tableInfo) {this.tableInfo = {} as TableInfoState;}// 如果内存中已有数据且不为空,直接返回if (this.tableInfo[tableName] && Object.keys(this.tableInfo[tableName]).length > 0) {console.log('从内存中获取数据:', this.tableInfo[tableName]);return this.tableInfo[tableName];}// 查询localStorage缓存const cachedData = localStorage.getItem(key);if (cachedData) {const parsedData = JSON.parse(cachedData) as TableInfo;// 只有当缓存数据不为空时才使用if (Object.keys(parsedData).length > 0) {this.tableInfo[tableName] = parsedData;console.log('从localStorage获取数据:', parsedData);return parsedData;} else {// 如果缓存是空对象,删除它localStorage.removeItem(key);}}// 缓存不存在或无效,根据表名请求对应接口try {let tableInfo: TableInfo = {};const tableConfig = tableApiMap[tableName as keyof typeof tableApiMap];if (tableConfig) {console.log('开始调用API:', tableName);const res = await tableConfig.api() as unknown as BaseApiResponse;console.log('API响应:', res);if (res.code === 0 && res.data[tableConfig.responseType]) {const list = res.data[tableConfig.responseType];console.log('获取到的列表数据:', list);// 将数组转换为id-name映射tableInfo = list.reduce((acc: TableInfo, item: any) => {acc[item.id] = item[tableConfig.nameField];return acc;}, {});console.log('转换后的映射数据:', tableInfo);// 保存到内存和localStorageif (Object.keys(tableInfo).length > 0) {this.tableInfo[tableName] = tableInfo;localStorage.setItem(key, JSON.stringify(tableInfo));console.log('数据已保存到缓存');}} else {console.warn('API响应格式不正确:', res);}} else {console.warn('未找到表配置:', tableName);}return tableInfo;} catch (error) {console.error(`获取${tableName}数据失败:`, error);return {};}},},
});
组件完整实现
web\admin\src\components\TableText\index.vue
<template>{{ text }}
</template><script lang="ts" setup name="TableText">
import { ref, onMounted, watch } from 'vue';
import { useTableInfoStore } from '/@/stores/tableInfo';const tableInfoStore = useTableInfoStore();interface ITableText {table: string;id?: number;ids?: string | number[];
}const props = defineProps<{ props: ITableText }>();
const text = ref('');const updateText = async () => {const { table, id, ids } = props.props;try {console.log('获取表信息:', table, 'ID:', id, 'IDs:', ids);const result = await tableInfoStore.getTableInfo(table);console.log('获取到的结果:', result);if (ids !== undefined) {// 处理多个ID的情况const idArray = typeof ids === 'string' ? ids.split(',').map(i => parseInt(i.trim())) : ids;const names = idArray.map(i => result[i] || '').filter(name => name !== '');text.value = names.join(',');} else if (id !== undefined) {// 处理单个ID的情况text.value = result[id] || '';}console.log('设置的文本值:', text.value);} catch (error) {console.error('获取文本失败:', error);text.value = '';}
};onMounted(() => {updateText();
});watch(() => props,() => {updateText();},{ deep: true }
);
</script><style scoped></style>