通用ID转名称组件:表格字段显示与缓存策略
引言 📜
在Vue项目中,我们经常需要将后端返回的ID值转换为对应的名称进行展示。本文介绍一个高效的解决方案:通用ID转名称组件及其缓存机制。
核心思路 🎯
使用Pinia存储管理不同表(如分类表、平台表)的ID到名称的映射,并实现多级缓存策略:
- 内存缓存 - 快速访问
- localStorage缓存 - 持久化存储
- API调用 - 最终数据源
存储设计(useTableInfoStore)📦
数据结构
javascript
interface TableInfo {
[key: number]: string; // ID到名称的映射
}
interface TableInfoState {
[tableName: string]: TableInfo; // 不同表的数据存储
}
表名与API的映射
javascript
const tableApiMap = {
category: {
api: getAllDsCategoryList,
responseType: 'dsCategoryList',
nameField: 'categoryName'
},
platform: {
api: getAllDsPlatformList,
responseType: 'dsPlatformList',
nameField: 'platformName'
}
} as const;
数据获取流程
graph TD
A[调用getTableInfo] --> B{内存中是否有数据?}
B -->|是| C[返回内存数据]
B -->|否| D{localStorage中是否有缓存?}
D -->|是| E[返回缓存数据并存入内存]
D -->|否| F[调用API]
F --> G{API调用成功?}
G -->|是| H[处理数据并存入缓存]
G -->|否| I[返回空对象]
H --> J[返回新数据]
核心方法实现
javascript
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. 调用API
try {
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转换(数组或逗号分隔字符串)
- 自动处理缓存更新
组件实现
vue
<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) {
// 处理多个ID
const 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) {
// 处理单个ID
text.value = result[id] || '';
}
};
// 初始化和监听变化
onMounted(updateText);
watch(() => props, updateText, { deep: true });
</script>
使用示例 🚀
单个ID展示
vue
<el-table-column prop="categoryId" label="分类">
<template #default="scope">
<TableText :props="{ table: 'category', id: scope.row.categoryId }" />
</template>
</el-table-column>

多个ID展示
vue
<el-table-column prop="platformIds" label="平台">
<template #default="scope">
<TableText :props="{ table: 'platform', ids: scope.row.platformIds }" />
</template>
</el-table-column>

数据流转示例 🔄
单个ID场景
TableText组件 TableInfoStore 后端API localStorage 获取分类信息('category') 返回内存中的映射 读取缓存 返回缓存数据 存入内存 返回缓存数据 调用getAllDsCategoryList() 返回分类列表 转换为{id: name}映射 存入缓存 存入内存 返回新数据 alt [localStorage中有缓存] [无缓存] alt [内存中有数据] [内存中无数据] 根据ID查找名称并显示 TableText组件 TableInfoStore 后端API localStorage
多个ID场景
组件接收IDs参数 拆分IDs为数组 获取表映射数据 遍历IDs查找名称 过滤空值 用逗号连接名称 显示结果
方案优势 ✨
- 高性能:三级缓存策略减少API调用
- 通用性:通过配置支持不同表类型
- 易用性:组件化设计简化使用
- 灵活性:支持单个和多个ID转换
- 容错性:自动处理异常情况
这个通用ID转名称解决方案通过精心设计的缓存策略和组件化实现,显著提高了表格数据显示的效率,同时保持了代码的简洁性和可维护性。
附录 🔗
pinia完整代码
web\admin\src\stores\tableInfo.ts
js
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);
// 初始化tableInfo
if (!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);
// 保存到内存和localStorage
if (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
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>