Spring Boot + MyBatis + Vue3 前后端动态数据处理方案
应用示例:PrintServiceImpl.java返回的数据结构:TestReportPrintDataVO.javaTestReportMapper.javaSampleItemResultMapper.javaTestReportMapper.xmlSampleItemResultMapper.xml直接sql查询的情况:前端浏览器接收到的数据:在Spring Boot + MyBatis中
应用示例:
前端请求api
import { checkType } from "@/utils/pubUtils";
import request from "@/utils/request";
/**
* 打印服务,获取打印数据
* @param printType 打印类型
* @param printTemplate 打印模板
* @param printSelectionDTO 打印选集对象
* @returns 打印数据
*/
export const printService = (printType: string, printTemplate: string, printSelectionDTO: any) => {
switch (printType) {
// 检验报告打印
case "testReport":
return request.post("/print/testReport", printSelectionDTO, {
params: {
printTemplate: printTemplate
}
});
}
};
PrintController.java
/**
* 获取检验报告打印数据
*
* @param testReportList 检验报告列表
* @param printTemplate 打印模板,可以根据不同的模板生成不同的打印数据
* @return 报告打印数据列表 {@link Result}<{@link List}<{@link TestReportPrintDataVO}>>
*/
@PostMapping("testReport")
public Result<List<TestReportPrintDataVO>> printTestReport(@RequestBody List<TestReport> testReportList,
@RequestParam(required = false) String printTemplate) {
log.info("【检验报告编制/复核/签发/打印/查询】,打印/查看检验报告,/print/testReport," +
"testReportList = {}, printTemplate = {}", testReportList, printTemplate);
List<TestReportPrintDataVO> testReportPrintDataVOList = printService.printTestReport(testReportList, printTemplate);
return Result.success(testReportPrintDataVOList);
}
PrintServiceImpl.java
/**
* 获取检验报告打印数据
*/
@Override
public List<TestReportPrintDataVO> printTestReport(List<TestReport> testReportList, String printTemplate) {
List<TestReportPrintDataVO> printDatas = new ArrayList<>();
for (TestReport testReport : testReportList) {
// 因为通过执行存储过程返回数据集(最多只有一行记录),数据集的字段是动态的,所以这里使用 Map<String, Object> 来接收
Map<String, Object> printData = testReportMapper.selectTestReportPrintData(testReport.getId());
// 因为通过执行存储过程返回数据集(有多行记录),数据集的字段是动态的,所以这里使用 List<Map<String, Object>> 来接收
List<Map<String, Object>> resultDatas = sampleItemResultMapper.selectByCheckReportIdWithTable(testReport.getId());
List<SampleItemResult> sampleItemResultList = sampleItemResultMapper.selectTestReportPrintDataByCheckReportId(testReport.getId());
Set<String> ffbzSet = sampleItemResultList.stream()
.map(sampleItemResult -> Stream.of(
sampleItemResult.getFfbzId(),
sampleItemResult.getFfbzName(),
sampleItemResult.getVerifyMethod()
)
.filter(Objects::nonNull) // 过滤掉null值
.collect(Collectors.joining()))
.collect(Collectors.toSet());
printDatas.add(new TestReportPrintDataVO(printData, resultDatas, ffbzSet));
}
return printDatas;
}
返回的数据结构:TestReportPrintDataVO.java
package com.weiyu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 检验报告打印数据
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestReportPrintDataVO {
private Map<String, Object> testReportData; // 检验报告数据
// private List<SampleItemResult> resultDatas; // 检验结果列表
// 因为通过执行存储过程返回数据集(有多行记录),数据集的字段是动态的,所以这里使用 List<Map<String, Object>> 来接收
private List<Map<String, Object>> resultDatas; // 检验结果列表
private Set<String> ffbzs; // 检验依据列表
}
TestReportMapper.java
// 获取检验报告打印数据,因为通过执行存储过程返回数据集(最多只有一行记录),数据集的字段是动态的,所以这里使用 Map<String, Object> 来接收
Map<String, Object> selectTestReportPrintData(Integer id);
SampleItemResultMapper.java
// 通过检验报告 id,通过存储过程proc_LabelResultTable查询样品项目结果
// 因为通过执行存储过程返回数据集(有多行记录),数据集的字段是动态的,所以这里使用 List<Map<String, Object>> 来接收
List<Map<String, Object>> selectByCheckReportIdWithTable(Integer checkReportId);
TestReportMapper.xml
<!--获取检验报告打印数据,因为通过执行存储过程返回数据集(最多只有一行记录),数据集的字段是动态的,所以这里 resultType="java.util.Map" -->
<select id="selectTestReportPrintData" resultType="java.util.Map">
exec proc_GetReportPrintData 1, null, null, #{id}
</select>
SampleItemResultMapper.xml
<!-- 通过检验报告 id,通过存储过程proc_LabelResultTable查询样品项目结果 -->
<!--因为通过执行存储过程返回数据集(有多行记录),数据集的字段是动态的,所以这里 resultType="java.util.Map"】-->
<select id="selectByCheckReportIdWithTable" resultType="java.util.Map">
exec proc_LabelResultTable 1, #{checkReportId}, null, null, null, null, null, null, null, null, null
</select>
直接sql查询的情况:

前端浏览器接收到的数据:

浏览器接收到的数据与直接sql查询的数据顺序不一样!
在使用 Map<String, Object> 时,默认的 HashMap 不保证顺序。以下是确保顺序的几种解决方案:
1. 使用 LinkedHashMap (推荐)
后端修改
java
// Service层 - 明确指定使用LinkedHashMap
public List<LinkedHashMap<String, Object>> getDynamicData() {
return dynamicDataMapper.selectDynamicData();
}
// 或者返回Map但内部使用LinkedHashMap
public List<Map<String, Object>> getDynamicData() {
// Mybatis配置或Mapper中确保返回LinkedHashMap
return dynamicDataMapper.selectDynamicData();
}
MyBatis 配置
xml
<!-- 在MyBatis配置文件中设置默认Map类型 -->
<settings>
<setting name="defaultMapType" value="java.util.LinkedHashMap"/>
</settings>
<!-- 或者在Mapper XML中指定 -->
<select id="selectDynamicData" resultType="java.util.LinkedHashMap">
SELECT a, b, c, d, e, f
FROM your_table
ORDER BY create_time DESC
</select>
2. 自定义结果处理器
java
// 自定义结果处理器保持顺序
public class OrderedMapResultHandler implements ResultHandler<LinkedHashMap<String, Object>> {
private final List<LinkedHashMap<String, Object>> results = new ArrayList<>();
private List<String> columnOrder;
@Override
public void handleResult(ResultContext<? extends LinkedHashMap<String, Object>> resultContext) {
LinkedHashMap<String, Object> result = resultContext.getResultObject();
results.add(result);
}
public List<LinkedHashMap<String, Object>> getResults() {
return results;
}
public List<String> getColumnOrder() {
return columnOrder;
}
}
// 使用方式
public List<LinkedHashMap<String, Object>> getOrderedData() {
OrderedMapResultHandler handler = new OrderedMapResultHandler();
sqlSession.select("selectDynamicData", null, handler);
return handler.getResults();
}
3. 使用注解指定顺序
java
// 使用@MapKey注解(如果适用)
@MapKey("id") // 这主要用于另一种场景,但了解下有好处
public Map<Long, LinkedHashMap<String, Object>> selectDataAsMap();
// 使用@Results注解定义列顺序(适用于固定字段)
@Results({
@Result(column = "a", property = "a"),
@Result(column = "b", property = "b"),
@Result(column = "c", property = "c")
})
public List<LinkedHashMap<String, Object>> selectOrderedData();
4. 手动构建有序Map
java
// Service层手动处理顺序
public List<LinkedHashMap<String, Object>> getOrderedDynamicData() {
List<Map<String, Object>> rawData = dynamicDataMapper.selectDynamicData();
return rawData.stream().map(item -> {
LinkedHashMap<String, Object> orderedMap = new LinkedHashMap<>();
// 按照指定顺序添加字段
orderedMap.put("a", item.get("a"));
orderedMap.put("b", item.get("b"));
orderedMap.put("c", item.get("c"));
// 其他字段按需添加
// 或者按照字母顺序
item.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEachOrdered(entry -> orderedMap.put(entry.getKey(), entry.getValue()));
return orderedMap;
}).collect(Collectors.toList());
}
5. 自定义类型处理器
java
// 自定义类型处理器确保返回LinkedHashMap
@MappedTypes({LinkedHashMap.class})
public class LinkedHashMapTypeHandler extends MapTypeHandler {
@Override
public Map<String, Object> getResult(ResultSet rs, String columnName) throws SQLException {
return createLinkedHashMap(rs, columnName);
}
@Override
public Map<String, Object> getResult(ResultSet rs, int columnIndex) throws SQLException {
return createLinkedHashMap(rs, columnIndex);
}
private LinkedHashMap<String, Object> createLinkedHashMap(ResultSet rs, Object identifier) throws SQLException {
// 实现具体的LinkedHashMap创建逻辑
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
Object value = rs.getObject(i);
map.put(columnName, value);
}
return map;
}
}
6. Vue3 前端接收有序数据
vue
<template>
<div>
<!-- 有序表格 -->
<table v-if="orderedData.length > 0">
<thead>
<tr>
<!-- 有序的表头 -->
<th v-for="key in columnOrder" :key="key">
{{ formatColumnName(key) }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in orderedData" :key="index">
<!-- 有序的数据单元格 -->
<td v-for="key in columnOrder" :key="key">
{{ row[key] }}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import axios from 'axios'
const rawData = ref([])
// 获取数据
const fetchData = async () => {
try {
const response = await axios.get('/api/ordered-data')
rawData.value = response.data
} catch (error) {
console.error('获取数据失败:', error)
}
}
// 计算属性:获取列顺序(使用第一行数据的键顺序)
const columnOrder = computed(() => {
if (rawData.value.length === 0) return []
return Object.keys(rawData.value[0])
})
// 计算属性:确保数据顺序(实际上LinkedHashMap已经有序)
const orderedData = computed(() => {
return rawData.value
})
// 格式化列名显示
const formatColumnName = (key) => {
return key.replace(/_/g, ' ').toUpperCase()
}
onMounted(() => {
fetchData()
})
</script>
7. 完整的最佳实践方案
后端完整实现
java
@Mapper
public interface DynamicDataMapper {
// 方法1:直接返回LinkedHashMap
List<LinkedHashMap<String, Object>> selectOrderedData();
// 方法2:使用@MapKey注解(如果需要按某个字段分组)
@MapKey("id")
Map<Long, LinkedHashMap<String, Object>> selectOrderedDataAsMap();
}
@Service
public class DynamicDataService {
public List<LinkedHashMap<String, Object>> getOrderedData() {
return dynamicDataMapper.selectOrderedData();
}
// 如果需要自定义顺序
public List<LinkedHashMap<String, Object>> getCustomOrderedData() {
List<LinkedHashMap<String, Object>> data = dynamicDataMapper.selectOrderedData();
// 自定义字段顺序
List<String> customOrder = Arrays.asList("id", "name", "createTime", "status");
return data.stream().map(item -> {
LinkedHashMap<String, Object> ordered = new LinkedHashMap<>();
customOrder.forEach(key -> {
if (item.containsKey(key)) {
ordered.put(key, item.get(key));
}
});
// 添加剩余字段
item.entrySet().stream()
.filter(entry -> !customOrder.contains(entry.getKey()))
.forEach(entry -> ordered.put(entry.getKey(), entry.getValue()));
return ordered;
}).collect(Collectors.toList());
}
}
Vue3 完整实现
vue
<template>
<div>
<DynamicTable
:data="tableData"
:column-order="columnOrder"
@sort="handleSort"
/>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
const tableData = ref([])
const sortField = ref('')
const sortOrder = ref('asc')
// 获取有序数据
const fetchOrderedData = async () => {
try {
const response = await fetch('/api/ordered-data')
tableData.value = await response.json()
} catch (error) {
console.error('获取数据失败:', error)
}
}
// 计算列顺序
const columnOrder = computed(() => {
if (tableData.value.length === 0) return []
return Object.keys(tableData.value[0])
})
// 排序处理
const handleSort = (field) => {
if (sortField.value === field) {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
} else {
sortField.value = field
sortOrder.value = 'asc'
}
tableData.value.sort((a, b) => {
const aVal = a[field]
const bVal = b[field]
const modifier = sortOrder.value === 'asc' ? 1 : -1
if (aVal < bVal) return -1 * modifier
if (aVal > bVal) return 1 * modifier
return 0
})
}
onMounted(() => {
fetchOrderedData()
})
</script>
总结
确保 Map<String, Object> 顺序的最佳方案:
-
使用
LinkedHashMap- 最简单有效 -
配置 MyBatis 默认 Map 类型 - 一劳永逸
-
在 Mapper XML 中明确指定 - 最可控
-
前端配合使用 Object.keys() 保持顺序 - 完整解决方案
这样既能保持数据的灵活性,又能确保前后端数据顺序的一致性。
应用示例优化:将 Map 改为 LinkedHashMap 确保数据顺序
PrintServiceImpl.java
/**
* 获取检验报告打印数据
*/
@Override
public List<TestReportPrintDataVO> printTestReport(List<TestReport> testReportList, String printTemplate) {
List<TestReportPrintDataVO> printDatas = new ArrayList<>();
for (TestReport testReport : testReportList) {
// 因为通过执行存储过程返回数据集(最多只有一行记录),数据集的字段是动态的,所以这里使用 LinkedHashMap<String, Object> 来接收
LinkedHashMap <String, Object> printData = testReportMapper.selectTestReportPrintData(testReport.getId());
// 因为通过执行存储过程返回数据集(有多行记录),数据集的字段是动态的,所以这里使用 List<LinkedHashMap<String, Object>> 来接收
List<LinkedHashMap <String, Object>> resultDatas = sampleItemResultMapper.selectByCheckReportIdWithTable(testReport.getId());
List<SampleItemResult> sampleItemResultList = sampleItemResultMapper.selectTestReportPrintDataByCheckReportId(testReport.getId());
Set<String> ffbzSet = sampleItemResultList.stream()
.map(sampleItemResult -> Stream.of(
sampleItemResult.getFfbzId(),
sampleItemResult.getFfbzName(),
sampleItemResult.getVerifyMethod()
)
.filter(Objects::nonNull) // 过滤掉null值
.collect(Collectors.joining()))
.collect(Collectors.toSet());
printDatas.add(new TestReportPrintDataVO(printData, resultDatas, ffbzSet));
}
return printDatas;
}
返回的数据结构:TestReportPrintDataVO.java
package com.weiyu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 检验报告打印数据
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestReportPrintDataVO {
private Map<String, Object> testReportData; // 检验报告数据
// private List<SampleItemResult> resultDatas; // 检验结果列表
// 因为通过执行存储过程返回数据集(有多行记录),数据集的字段是动态的,所以这里使用 List<Map<String, Object>> 来接收
private List<LinkedHashMap<String, Object>> resultDatas; // 检验结果列表
private Set<String> ffbzs; // 检验依据列表
}
TestReportMapper.java
// 获取检验报告打印数据,因为通过执行存储过程返回数据集(最多只有一行记录),数据集的字段是动态的,所以这里使用 LinkedHashMap<String, Object> 来接收
LinkedHashMap<String, Object> selectTestReportPrintData(Integer id);
SampleItemResultMapper.java
// 通过检验报告 id,通过存储过程proc_LabelResultTable查询样品项目结果
// 因为通过执行存储过程返回数据集(有多行记录),数据集的字段是动态的,所以这里使用 List<LinkedHashMap<String, Object>> 来接收
List<LinkedHashMap<String, Object>> selectByCheckReportIdWithTable(Integer checkReportId);
TestReportMapper.xml
<!--获取检验报告打印数据,因为通过执行存储过程返回数据集(最多只有一行记录),数据集的字段是动态的,所以这里 resultType="java.util.LinkedHashMap" -->
<select id="selectTestReportPrintData" resultType="java.util.LinkedHashMap">
exec proc_GetReportPrintData 1, null, null, #{id}
</select>
SampleItemResultMapper.xml
<!-- 通过检验报告 id,通过存储过程proc_LabelResultTable查询样品项目结果 -->
<!--因为通过执行存储过程返回数据集(有多行记录),数据集的字段是动态的,所以这里 resultType="java.util.LinkedHashMap"】-->
<select id="selectByCheckReportIdWithTable" resultType="java.util.LinkedHashMap">
exec proc_LabelResultTable 1, #{checkReportId}, null, null, null, null, null, null, null, null, null
</select>
直接sql查询的情况:

前端浏览器接收到的数据:数据顺序与直接sql查询的一致

vue3模板,使用 Object.keys 构建动态表格(动态表头+动态数据行)
<!-- 动态表格 -->
<table class="detail">
<tbody>
<tr class="detail-row border-top-row border-bottom-row">
<!-- 动态表头 -->
<td v-for="key in Object.keys(printData[pageIndex - 1].resultDatas[0])" :key="key">{{ key }}</td>
</tr>
<!-- 动态数据行 -->
<tr class="detail-row" v-for="rowIndex in printData[pageIndex - 1].resultDatas.length" :key="rowIndex">
<!-- 动态单元格 -->
<td v-for="key in Object.keys(printData[pageIndex - 1].resultDatas[rowIndex - 1])" :key="key">
{{ printData[pageIndex - 1].resultDatas[rowIndex - 1][key] }}
</td>
</tr>
</tbody>
</table>
应用效果:

在Spring Boot + MyBatis中处理属性不固定的动态返回数据,有几种常见的解决方案:
1. 使用 Map<String, Object> (推荐)
java
// Service层
@Service
public class DynamicDataService {
@Autowired
private DynamicDataMapper mapper;
public List<Map<String, Object>> getDynamicData() {
return mapper.selectDynamicData();
}
}
// Mapper接口
public interface DynamicDataMapper {
List<Map<String, Object>> selectDynamicData();
}
// Mapper XML
<select id="selectDynamicData" resultType="java.util.Map">
SELECT
<if test="type == 'type1'">a, b, c</if>
<if test="type == 'type2'">a, d, e, f</if>
FROM table_name
</select>
2. 使用通用的DTO对象
java
// 通用DTO
@Data
public class DynamicDTO {
private Map<String, Object> dynamicFields = new HashMap<>();
public void setField(String key, Object value) {
dynamicFields.put(key, value);
}
public Object getField(String key) {
return dynamicFields.get(key);
}
}
// Service层
public List<DynamicDTO> getDynamicData() {
return mapper.selectDynamicData();
}
3. 使用MyBatis的结果处理器
java
// 自定义结果处理器
public class DynamicResultHandler implements ResultHandler<Map<String, Object>> {
private final List<Map<String, Object>> results = new ArrayList<>();
@Override
public void handleResult(ResultContext<? extends Map<String, Object>> resultContext) {
Map<String, Object> result = resultContext.getResultObject();
// 可以在这里对结果进行动态处理
results.add(result);
}
public List<Map<String, Object>> getResults() {
return results;
}
}
// Service层使用
public List<Map<String, Object>> getDynamicDataWithHandler() {
DynamicResultHandler handler = new DynamicResultHandler();
sqlSession.select("selectDynamicData", handler);
return handler.getResults();
}
4. 使用JSON类型接收
java
// 如果数据库支持JSON类型
@Data
public class JsonDataDTO {
private String jsonData;
public Map<String, Object> getDataAsMap() {
return new ObjectMapper().readValue(jsonData, Map.class);
}
}
// Mapper
List<JsonDataDTO> selectJsonData();
5. 实际使用示例
java
@Service
public class ReportService {
public List<Map<String, Object>> generateReport(String reportType) {
List<Map<String, Object>> result = dynamicDataMapper.getReportData(reportType);
// 动态处理返回数据
result.forEach(row -> {
// 根据不同的属性进行不同的处理逻辑
if (row.containsKey("a") && row.containsKey("b")) {
// 处理包含a,b属性的数据
row.put("calculatedField", calculateAB(row));
} else if (row.containsKey("d") && row.containsKey("e")) {
// 处理包含d,e属性的数据
row.put("calculatedField", calculateDE(row));
}
});
return result;
}
// 前端使用示例
@RestController
public class ReportController {
@Autowired
private ReportService reportService;
@GetMapping("/reports/{type}")
public List<Map<String, Object>> getReport(@PathVariable String type) {
return reportService.generateReport(type);
}
}
}
最佳实践建议:
-
推荐使用
Map<String, Object>- 最简单直接,适合大多数动态场景 -
考虑数据量 - 如果数据量大,建议使用明确的对象结构
-
文档化 - 对可能的返回字段进行文档说明
-
类型安全 - 在业务层进行必要的类型转换和验证
java
// 类型安全的获取方式
public String getStringSafely(Map<String, Object> data, String key) {
Object value = data.get(key);
return value != null ? value.toString() : null;
}
public Integer getIntegerSafely(Map<String, Object> data, String key) {
Object value = data.get(key);
if (value instanceof Number) {
return ((Number) value).intValue();
}
return null;
}
前端 Vue3 接收动态属性数据有多种方式,根据不同的使用场景可以选择合适的方案:
1. 基础接收方式
vue
<template>
<div>
<table v-if="tableData.length > 0">
<thead>
<tr>
<!-- 动态表头 -->
<th v-for="key in Object.keys(tableData[0])" :key="key">
{{ key }}
</th>
</tr>
</thead>
<tbody>
<!-- 动态数据行 -->
<tr v-for="(row, index) in tableData" :key="index">
<td v-for="(value, key) in row" :key="key">
{{ value }}
</td>
</tr>
</tbody>
</table>
<div v-else>暂无数据</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const tableData = ref([])
// 获取数据
const fetchData = async () => {
try {
const response = await axios.get('/api/dynamic-data')
tableData.value = response.data
} catch (error) {
console.error('获取数据失败:', error)
}
}
onMounted(() => {
fetchData()
})
</script>
2. 使用 Composition API 封装
vue
<template>
<div>
<!-- 动态表格 -->
<DynamicTable :data="dynamicData" />
<!-- 或者使用卡片形式展示 -->
<div class="card-container">
<div v-for="(item, index) in dynamicData" :key="index" class="card">
<div v-for="(value, key) in item" :key="key" class="field">
<span class="label">{{ key }}:</span>
<span class="value">{{ value }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useDynamicData } from '@/composables/useDynamicData'
// 使用组合式函数
const { dynamicData, loading, error, fetchData } = useDynamicData()
onMounted(() => {
fetchData()
})
// 计算属性:获取所有可能的字段
const allFields = computed(() => {
const fields = new Set()
dynamicData.value.forEach(item => {
Object.keys(item).forEach(key => fields.add(key))
})
return Array.from(fields)
})
</script>
3. 组合式函数封装
javascript
// composables/useDynamicData.js
import { ref } from 'vue'
import axios from 'axios'
export function useDynamicData() {
const dynamicData = ref([])
const loading = ref(false)
const error = ref(null)
const fetchData = async (url, params = {}) => {
loading.value = true
error.value = null
try {
const response = await axios.get(url, { params })
dynamicData.value = response.data
} catch (err) {
error.value = err.message
console.error('获取动态数据失败:', err)
} finally {
loading.value = false
}
}
return {
dynamicData,
loading,
error,
fetchData
}
}
4. 类型安全的处理
vue
<template>
<div>
<div v-if="loading">加载中...</div>
<div v-else-if="error">错误: {{ error }}</div>
<div v-else>
<!-- 根据数据类型动态渲染 -->
<component
:is="getComponentType(item)"
v-for="(item, index) in processedData"
:key="index"
:data="item"
/>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import TypeACard from '@/components/TypeACard.vue'
import TypeBCard from '@/components/TypeBCard.vue'
import DefaultCard from '@/components/DefaultCard.vue'
const dynamicData = ref([])
const loading = ref(false)
// 处理数据,添加类型标识
const processedData = computed(() => {
return dynamicData.value.map(item => {
const keys = Object.keys(item)
// 根据属性判断数据类型
if (keys.includes('a') && keys.includes('b') && keys.includes('c')) {
return { ...item, __type: 'typeA' }
} else if (keys.includes('a') && keys.includes('d') && keys.includes('e')) {
return { ...item, __type: 'typeB' }
} else {
return { ...item, __type: 'default' }
}
})
})
// 动态选择组件
const getComponentType = (item) => {
switch (item.__type) {
case 'typeA': return TypeACard
case 'typeB': return TypeBCard
default: return DefaultCard
}
}
</script>
5. 使用 TypeScript 增强类型安全
vue
<template>
<div>
<div v-for="(item, index) in dynamicData" :key="index">
<DynamicCard :data="item" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios'
// 定义类型
interface DynamicData {
[key: string]: any
}
interface ApiResponse {
data: DynamicData[]
}
const dynamicData = ref<DynamicData[]>([])
// 类型安全的获取数据
const fetchData = async (): Promise<void> => {
try {
const response = await axios.get<ApiResponse>('/api/dynamic-data')
dynamicData.value = response.data.data
} catch (error) {
console.error('获取数据失败:', error)
}
}
// 安全的获取字段值
const getFieldValue = (item: DynamicData, field: string): string => {
return item[field]?.toString() || ''
}
</script>
6. 动态表单组件
vue
<template>
<div>
<form @submit.prevent="handleSubmit">
<div v-for="field in formFields" :key="field.key">
<label :for="field.key">{{ field.label }}</label>
<input
:id="field.key"
v-model="formData[field.key]"
:type="field.type || 'text'"
:placeholder="field.placeholder"
/>
</div>
<button type="submit">提交</button>
</form>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
const dynamicData = ref([])
const formData = ref({})
// 根据数据动态生成表单字段
const formFields = computed(() => {
if (dynamicData.value.length === 0) return []
const sample = dynamicData.value[0]
return Object.keys(sample).map(key => ({
key,
label: key.toUpperCase(),
type: typeof sample[key] === 'number' ? 'number' : 'text'
}))
})
const handleSubmit = () => {
// 提交动态表单数据
console.log('表单数据:', formData.value)
}
</script>
7. 完整示例 - 动态数据仪表板
vue
<template>
<div class="dashboard">
<div class="controls">
<button @click="fetchData('type1')">类型1数据</button>
<button @click="fetchData('type2')">类型2数据</button>
</div>
<div v-if="loading" class="loading">加载中...</div>
<div v-else class="data-display">
<!-- 动态表格 -->
<DynamicTable :data="dynamicData" />
<!-- 统计数据 -->
<div class="stats">
<div v-for="stat in statistics" :key="stat.label" class="stat-item">
<span class="label">{{ stat.label }}:</span>
<span class="value">{{ stat.value }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useDynamicData } from '@/composables/useDynamicData'
const { dynamicData, loading, fetchData } = useDynamicData()
// 计算统计数据
const statistics = computed(() => {
if (dynamicData.value.length === 0) return []
const sample = dynamicData.value[0]
return Object.keys(sample)
.filter(key => typeof sample[key] === 'number')
.map(key => ({
label: `平均${key}`,
value: dynamicData.value.reduce((sum, item) => sum + (item[key] || 0), 0) / dynamicData.value.length
}))
})
</script>
最佳实践建议:
-
使用组合式函数封装数据获取逻辑,提高代码复用性
-
添加加载状态和错误处理,提升用户体验
-
使用TypeScript增强类型安全
-
根据数据类型动态选择渲染组件
-
考虑数据量大时的分页或虚拟滚动
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)