应用示例:

前端请求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> 顺序的最佳方案:

  1. 使用 LinkedHashMap - 最简单有效

  2. 配置 MyBatis 默认 Map 类型 - 一劳永逸

  3. 在 Mapper XML 中明确指定 - 最可控

  4. 前端配合使用 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);
        }
    }
}

最佳实践建议:

  1. 推荐使用 Map<String, Object> - 最简单直接,适合大多数动态场景

  2. 考虑数据量 - 如果数据量大,建议使用明确的对象结构

  3. 文档化 - 对可能的返回字段进行文档说明

  4. 类型安全 - 在业务层进行必要的类型转换和验证

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>

最佳实践建议:

  1. 使用组合式函数封装数据获取逻辑,提高代码复用性

  2. 添加加载状态和错误处理,提升用户体验

  3. 使用TypeScript增强类型安全

  4. 根据数据类型动态选择渲染组件

  5. 考虑数据量大时的分页或虚拟滚动


Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐