Bootstrap

根据条件查询下载Excel表单(Java+Vue 及 Vue 两种方式)

前言

如果想要下载好看的Excel推荐阅读:

  1. 详细讲解Java使用EasyExcel函数来操作Excel表(附实战)
  2. 详细讲解Java使用HSSFWorkbook函数导出Excel表(附实战)
  3. Python读取Excel的几种工具包(附Demo)

详细的Java知识推荐阅读: java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)

此文通过后端你的查询前端,使用前端的下载参数下载!

1. 基本知识

this.$export.excel 是在 Vue.js 组件中使用的一个方法,通过 Vue.js 插件或 mixin 扩展到组件中的一个方法

主要功能是将给定的数据导出为 Excel 文件

参数包括:

  • 标题:Excel 文件的标题或名称
  • 列信息:导出的数据应该如何在 Excel 表格中排列,包括列名、宽度等
  • 数据:要导出的实际数据,可能是一个数组,每个元素代表一行数据,每行的元素按照列信息进行排列

具体作用如下:

  • 灵活性:支持不同的选项,比如自定义标题、指定列的顺序和格式、处理数据以适应 Excel 的需求等
  • 用户友好:提供一种用户友好的方式来导出数据,使用户能够以熟悉的 Excel 格式保存和处理数据,而无需手动将数据从应用程序复制粘贴到 Excel 中

2. 纯前端导入导出(Vue)

导入:

  • 自定义样式以增强界面外观。

  • 添加了 .xlsx 文件的限制,只允许选择该类型的文件进行上传。

  • 添加了一些虚拟数据,以便在没有上传文件时也可以查看表格的展示效果。

  • <style> 部分使用了 scoped,以确保样式仅在当前组件中生效,避免全局污染。

<template>
  <div class="excel-import-demo">
    <el-upload
      class="upload-excel"
      :before-upload="handleUpload"
      action="default"
      accept=".xlsx"
    >
      <el-button type="primary">选择要导入的 .xlsx 表格</el-button>
    </el-upload>

    <div v-if="table.columns.length > 0" class="table-container">
      <h2>导入的数据</h2>
      <el-table :data="table.data" :columns="table.columns"></el-table>
    </div>
  </div>
</template>

<script>
import Vue from "vue";
import pluginImport from "@d2-projects/vue-table-import";

Vue.use(pluginImport);

export default {
  data() {
    return {
      table: {
        columns: [],
        data: [],
      },
    };
  },
  methods: {
    handleUpload(file) {
      this.$import.xlsx(file).then(({ header, results }) => {
        // 将表头作为列的标签和属性
        this.table.columns = header.map((e) => {
          return {
            label: e,
            prop: e,
          };
        });
        // 演示用的虚拟数据
        this.table.data = [
          { id: 1, name: "John", age: 30, email: "[email protected]" },
          { id: 2, name: "Jane", age: 28, email: "[email protected]" },
          { id: 3, name: "Doe", age: 35, email: "[email protected]" },
        ];
      });
      return false;
    },
  },
};
</script>

<style scoped>
.excel-import-demo {
  max-width: 600px;
  margin: 0 auto;
}

.upload-excel {
  margin-bottom: 20px;
}

.table-container {
  margin-top: 20px;
}
</style>

导出:

<template>
  <div class="excel-export-demo">
    <div class="header">
      <h2>数据导出为 Excel</h2>
    </div>
    <div class="content">
      <el-button @click="exportExcel" type="primary">
        <el-icon name="download" /> 导出为 Excel
      </el-button>
    </div>
    <div class="footer">
      <p>© 2024 码农研究僧</p>
    </div>
  </div>
</template>

<script>
import Vue from 'vue'
import pluginExport from '@d2-projects/vue-table-export'

Vue.use(pluginExport)

export default {
  data() {
    return {
      table: {
        columns: [],
        data: []
      }
    }
  },
  methods: {
    exportExcel() {
      this.$export.excel({
        columns: this.table.columns,
        data: this.table.data
      }).then(() => {
        this.$message('导出表格成功')
      })
    }
  }
}
</script>

<style scoped>
.excel-export-demo {
  max-width: 400px;
  margin: 0 auto;
  text-align: center;
  padding: 20px;
}

.header h2 {
  margin-bottom: 20px;
}

.footer {
  margin-top: 20px;
  font-size: 12px;
}
</style>

3. 前后端(Vue + Java)

根据前面的一个框架,大致设计一个导出Excel

通过点击按钮:

handleExcel() {
  this.loading = true;
  queryAllForExcel( Object.assign(this.getQuery()) ).then(res=>{
    console.log();
    const  column = this.option.column;
    const  data = res.data.data;
    let opt = {
      title: 'EXCEL 下载',
      column: column,
      data: data
    };
    console.log(opt);
    this.$export.excel({
      title: opt.title || new Date().getTime(),
      columns: opt.column,
      data: opt.data
    });

  }).finally(()=>{
    this.loading = false;
  });
},

传输的条件如下:(以下为Demo)

getQuery(){
  var mTime = this.formInline.maintenanceTimePicker;
  var aTime = this.formInline.acceptTimePicker;
  var js;

  // 多机种的多个搜索
  var newmodel = '';
  console.log(this.formInline.model);
  if(this.formInline.model){//添加检查
    for(var i=0;i<this.formInline.model.length;i++){
      if(i==0){
        newmodel=this.formInline.model[i]
      }else{
        newmodel=newmodel+'-'+this.formInline.model[i]
      }
    }
  }

  // 设备编号的多个搜索
  var nos = '';
  if(this.formInline.equipmentNo){ // 不为空的判断
    for(var i=0;i<this.formInline.equipmentNo.length;i++){
      if(i==0){
        nos=this.formInline.equipmentNo[i]
      }else{
        nos=nos+'-'+this.formInline.equipmentNo[i]
      }
    }
  }
  if((mTime!=null&&(mTime.length!=1))&&(aTime!=null&&(aTime.length!=1))){
    js = {
      "equipmentNos": nos,
      "model":newmodel,
      "maintenanceType":this.formInline.maintenanceType,
      "maintenanceStartTime":moment(mTime[0]).format('YYYY-MM-DD HH:mm:ss'),
      "maintenanceEndTime":moment(mTime[1]).format('YYYY-MM-DD HH:mm:ss'),
      "acceptStartTime":moment(aTime[0]).format('YYYY-MM-DD HH:mm:ss'),
      "acceptEndTime":moment(aTime[1]).format('YYYY-MM-DD HH:mm:ss'),
    };
  }else if((mTime!=null&&(mTime.length!=1))&&aTime==null){
    js = {
      "equipmentNos": nos,
      "model":newmodel,
      "maintenanceType":this.formInline.maintenanceType,
      "maintenanceStartTime":moment(mTime[0]).format('YYYY-MM-DD HH:mm:ss'),
      "maintenanceEndTime":moment(mTime[1]).format('YYYY-MM-DD HH:mm:ss'),
    };
  }else if((aTime!=null&&(aTime.length!=1))&&mTime==null){
    js = {
      "equipmentNos": nos,
      "model":newmodel,
      "maintenanceType":this.formInline.maintenanceType,
      "acceptStartTime":moment(aTime[0]).format('YYYY-MM-DD HH:mm:ss'),
      "acceptEndTime":moment(aTime[1]).format('YYYY-MM-DD HH:mm:ss'),
    };
  }else{
    js = {
      "equipmentNos": nos,
      "model":newmodel,
      "maintenanceType":this.formInline.maintenanceType,
    }; 
  }
  console.log(js);
    return js;
},

前端接口如下:

export const queryAllForExcel = ( params) => {
  return request({
    url: '/api/blade-equipment/maintenanceorder/queryAllForExcel',
    method: 'get',
    params: {
      ...params,
    },
    timeout:18000,
  })
}

对应后端传输的接口:

@GetMapping("/queryAllForExcel")
@ApiOperationSupport(order = 2)
@ApiOperation(value = "查全部", notes = "传入maintenanceOrder")
public R<List<MaintenanceOrderVO>> queryAll(MaintenanceOrderVO maintenanceOrdervo) {

	QueryWrapper<MaintenanceOrder> maintenanceOrderQueryWrapper = new QueryWrapper<>();
	
	// 条件查询
	maintenanceOrderQueryWrapper = this.getWrapper(maintenanceOrdervo,maintenanceOrderQueryWrapper);

	List<MaintenanceOrder> maintenanceOrders = maintenanceOrderService.list(maintenanceOrderQueryWrapper);
	List<MaintenanceOrderVO> maintenanceOrderVOS = MaintenanceOrderWrapper.build().listVO(maintenanceOrders);
	return R.data(maintenanceOrderVOS);
}

对于上述条件查询主要是 MybatisPlus自我封装的一个函数,推荐阅读: 【Java项目】实战CRUD的功能整理(持续更新)

4. 拓展版本一

为了加深印象,以Bladex项目中的纯前端进行演示

点击查询的时候改造如下:

onSubmit() {
    var js = this.getQuery();
    this.query =  js;
    this.page.currentPage = 1;
    this.onLoad(this.page, js);
  },
  getQuery() {
    var mTime = this.formInline.createTimePicker;
    var js;
    var nos = '';
    // 多机种的多个搜索
    var newmodel = '';
    var checktypes = '';
    if(this.formInline.model){//添加检查
      for(var i=0;i<this.formInline.model.length;i++){
        if(i==0){
          newmodel=this.formInline.model[i]
        }else{
          newmodel=newmodel+'-'+this.formInline.model[i]
        }
      }
    }

    if(this.formInline.equipmentNo){ // 不为空的判断
      for(var i=0;i<this.formInline.equipmentNo.length;i++){
        if(i==0){
          nos=this.formInline.equipmentNo[i]
        }else{
          nos=nos+'-'+this.formInline.equipmentNo[i]
        }
      }
    }

    if(this.formInline.checkType){ // 不为空的判断
      for(var i=0;i<this.formInline.checkType.length;i++){
        if(i==0){
          checktypes=this.formInline.checkType[i]
        }else{
          checktypes=checktypes+'-'+this.formInline.checkType[i]
        }
      }
    }

    if ((mTime != null && (mTime.length != 1))) {
      js = {
        "model":newmodel,
        "equipmentNos": nos,
        "createStartTime": moment(mTime[0]).format('YYYY-MM-DD HH:mm:ss'),
        "createEndTime": moment(mTime[1]).format('YYYY-MM-DD HH:mm:ss'),
        "checkType":checktypes
      };
    } else {
      js = {
        "equipmentNos": nos,
        "model":newmodel,
        "checkType":checktypes
      };
    }

    return js;
  },

对应的下载通过其相关的搜索 下载对应的Excel

  handleExcel() {
    debugger;
    listAllForView(Object.assign(this.getQuery())).then(res=>{
      const  column = this.option.column;
      const  data = res.data.data;
      let opt = {
        title: '检查记录',
        column: column,
        data: data
      }
      this.$export.excel({
        title: opt.title || new Date().getTime(),
        columns: opt.column,
        data: opt.data
      });
    })
  }

对应的两个按钮如下:

<el-button type="primary" size="small" @click="onSubmit">查询</el-button>
<el-button type="primary"  size="small" @click="handleExcel">下载 excel</el-button>

由于不同页面不一
比如查询的时候是有分页数据的,但是下载的时候是不需要带分页数据,否则下载的数据是此页的分页数据

对此需要单独处理:

同时对应的接口也需要注意下:


// 查询的接口按钮,带分页
export const listViewDeviceChargeHistory =  (current, size, params) => {
  return request({
    url: '/api/blade-equipment/devicechargehist/listView',
    method: 'get',
    params: {
      ...params,
      current,
      size,

    }
  })
}
  
// 下载接口,不带(多了一个这个)
分页参数
export const exportListViewDeviceChargeHistory = (params) => {
  return request({
    url: '/api/blade-equipment/devicechargehist/exportListView',
    method: 'get',
    params: {
      ...params,
    }
  });
}

后端的请求代码注意甄别:

/**
 * 自定义分页 设备运行历史
 */
@GetMapping("/listView")
@ApiOperationSupport(order = 3)
@ApiOperation(value = "分页", notes = "传入deviceChargeHist")
public R<IPage<DeviceChargeHistVO>> listView(DeviceChargeHistVO deviceChargeHistVo, Query query) {
	String deviceName = deviceChargeHistVo.getDeviceName();
	Date startTime = deviceChargeHistVo.getStartTime();
	Date endTime = deviceChargeHistVo.getEndTime();

	if (("".equals(deviceName) || deviceName == null) && startTime == null && endTime == null) {
		return R.data(null);
	}

	IPage<DeviceChargeHistVO> pages = deviceChargeHistService.selectDeviceChargeHistPage(Condition.getPage(query), deviceChargeHistVo);
	return R.data(pages);
}

/**
 * 2024.10.28 导出的时候分页有误,此处不要分页
 */
@GetMapping("/exportListView")
@ApiOperationSupport(order = 3)
@ApiOperation(value = "分页", notes = "传入deviceChargeHist")
public R<List<DeviceChargeHistVO>> exportListView(DeviceChargeHistVO deviceChargeHistVo) {
	String deviceName = deviceChargeHistVo.getDeviceName();
	Date startTime = deviceChargeHistVo.getStartTime();
	Date endTime = deviceChargeHistVo.getEndTime();

	if (("".equals(deviceName) || deviceName == null) && startTime == null && endTime == null) {
		return R.data(null);
	}
	// 各个数据状态在xml进行判断,此处不可使用QueryWrapper,默认会带上is_delete,除非禁止他带上
	List<DeviceChargeHist> deviceChargeHists = deviceChargeHistService.selectAllDeviceChargeHistPage(deviceChargeHistVo);
	List<DeviceChargeHistVO> deviceChargeHistVOS = DeviceChargeHistWrapper.build().listVO(deviceChargeHists);
	return R.data(deviceChargeHistVOS);
}

注意,以上的代码需要注意的点如下:

  1. data 以及total的赋值,需要看api带来的参数,多少会有点变动,有些是length有些直接为total,注意看位置
  2. 对应下载的数据,可以通过一个表定义选择对应的数据导出

下面给一个Demo,代表查询的数据 以及下载的数据,虽然都是用的同一个接口,但是数据显示以及展示都可以不一样的定义!

<template>
  <basic-container>
    <el-form :inline="true" :model="formInline" label-width="80px">
      <el-form-item label="车辆名称">
        <el-input v-model="formInline.deviceName" placeholder="请输入车辆名称"></el-input>
      </el-form-item>
      <el-form-item label="验收时间" prop="acceptTimePicker">
        <el-date-picker
          v-model="formInline.createTimePicker"
          type="datetimerange"
          :picker-options="pickerOptions"
          range-separator=""
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          align="right">
        </el-date-picker>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" size="small" @click="onSubmit">查询</el-button>
        <el-button type="primary"  size="small" @click="handleExcel">下载 excel</el-button>
      </el-form-item>
    </el-form>
    <avue-crud
      ref="chargeHisCrud"
      :option="chargeHisOption"
      :data="chargeHisData"
      :page.sync="page"
      @current-change="currentChange"
      @on-load="onLoad"
      >
    </avue-crud>
  </basic-container>
</template>

<script>
import {getDeviceInfoList,getDeviceRunHistoryList,getDeviceChargeHistoryList, listViewDeviceChargeHistory} from "@/api/equipment/chargingSchedule/deviceinfo";
import {mapGetters} from "vuex";
import moment from "moment";

export default {
  data(){
    return{
      formInline:{
        deviceName:'',
        createTimePicker: null
      },
      query: {},
      page:{
        pageSize: 10,
        currentPage: 1,
        total: 0
      },
      runHisDialog:false,
      chargeHisDialog:false,
      form:{},
      data:[],
      
      runHisData:[],
      runHisOption:{
        height:'auto',
        calcHeight: 30,
        fit:true,
        border: true,
        menu: false,
        addBtn:false,
        highlightCurrentRow: true,
        column: [
          {
            label: '设备IP',
            prop: 'deviceId',
            width: '100'
          },
          {
            label: '车辆名称',
            prop: 'deviceName'
          },
          {
            label: '车辆状态',
            prop: 'vehicleStatus',
            dicData:[
              {
                label: '启动',
                value: '01'
              },
              {
                label: '熄火',
                value: '02'
              },
              {
                label: '充电',
                value: '03'
              },
              {
                label: '离线',
                value: '99'
              }
            ]
          },
          {
            label: '开始电量(%)',
            prop: 'beginCapacity'
          },
          {
            label: '结束电量(%)',
            prop: 'endCapacity'
          },
          {
            label: '开始里程',
            prop: 'beginMileage'
          },
          {
            label: '结束里程',
            prop: 'endMileage'
          },

          // 下方注释的这些事Excel导出的相关数据,但由于Java后端要改造 引出这些数据,此处默认与网站对应,不要此数据
          
          {
            label: '开始充电时间',
            prop: 'chargeStartTime'
          },
          {
            label: '剩余里程(km)',
            prop: 'remainMileage'
          },
          {
            label: '总容量(Ah)',
            prop: 'batteryCapacity'
          },
          {
            label: '创建时间',
            prop: 'infoCreateTime',
            width: '150'
          }
        ]
      },
      pickerOptions: {
        shortcuts: [{
          text: '最近一周',
          onClick(picker) {
            const end = new Date();
            const start = new Date();
            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
            picker.$emit('pick', [start, end]);
          }
        }, {
          text: '最近一个月',
          onClick(picker) {
            const end = new Date();
            const start = new Date();
            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
            picker.$emit('pick', [start, end]);
          }
        }, {
          text: '最近三个月',
          onClick(picker) {
            const end = new Date();
            const start = new Date();
            start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
            picker.$emit('pick', [start, end]);
          }
        }]
      },
      chargeHisData:[],
      chargeHisOption:{
        height:'auto',
        calcHeight: 30,
        fit:true,
        border: true,
        menu: false,
        addBtn:false,
        highlightCurrentRow: true,
        column: [
          {
            label: '设备IP',
            prop: 'deviceId',
            width: '100'
          },
          {
            label: '车辆名称',
            prop: 'deviceName'
          },
          {
            label: '车辆状态',
            prop: 'vehicleStatus',
            dicData:[
              {
                label: '启动',
                value: '01'
              },
              {
                label: '熄火',
                value: '02'
              },
              {
                label: '充电',
                value: '03'
              },
              {
                label: '离线',
                value: '99'
              }
            ]
          },
          {
            label: '开始电量(%)',
            prop: 'beginCapacity'
          },
          {
            label: '结束电量(%)',
            prop: 'endCapacity'
          },
          {
            label: '开始里程',
            prop: 'beginMileage'
          },
          {
            label: '结束里程',
            prop: 'endMileage'
          },
          {
            label: '单次充电电量(kWh)',
            prop: 'chargeCapacity',
            width: '150'
          },
          {
            label: '总容量(Ah)',
            prop: 'batteryCapacity'
          },
          {
            label: '创建时间',
            prop: 'infoCreateTime',
            width: '150'
          }
        ]
      }
    }
  },
  methods:{
    // 原本提交是这个,版本,无法多条件查询,后续改写此方法
    onSearch(){
      let searchInfo = {
        deviceName: this.formInline.deviceName === ''? null : this.formInline.deviceName,
      }
      let time = this.formInline.createTimePicker
      if (time !== null) {
        searchInfo.startTime = moment(time[0]).format('YYYY-MM-DD HH:mm:ss')
        searchInfo.endTime = moment(time[1]).format('YYYY-MM-DD HH:mm:ss')
      }
      this.page.currentPage = 1
      this.query = searchInfo

      this.onLoad(this.page,searchInfo)
    },

    // 保留上述方法,改写为如下方法:
    onSubmit() {
      var js = this.getQuery();
      this.query =  js;
      this.page.currentPage = 1
      this.onLoad(this.page, js);
    },
    // 此为查询的条件(多种查询条件)
    getQuery() {
      var js;
      let time = this.formInline.createTimePicker
      if ((time != null && (time.length != 1))) {
        js = {
          deviceName: this.formInline.deviceName === ''? null : this.formInline.deviceName,
          startTime: moment(time[0]).format('YYYY-MM-DD HH:mm:ss'),
          endTime: moment(time[1]).format('YYYY-MM-DD HH:mm:ss')
        };
      } else {
        js = {
          deviceName: this.formInline.deviceName === ''? null : this.formInline.deviceName
        };
      }
      return js;
    },
    currentChange(currentPage) {
      this.page.currentPage = currentPage;
    },
    onLoad(page,params={}){
      listViewDeviceChargeHistory(Object.assign(params, this.query)).then(res=>{
        const data = res.data.data;
        this.page.total = data.length;
        this.chargeHisData = data;
      })
    },
    // 下载Excel
    handleExcel() {
      // 获取查询条件
      const query = this.getQuery();
      listViewDeviceChargeHistory(query).then(res=>{
        const  column = this.runHisOption.column;
        const data = res.data.data;
        let opt = {
          title: 'xx下载',
          column: column,
          data: data
        }
        this.$export.excel({
          title: opt.title || new Date().getTime(),
          columns: opt.column,
          data: opt.data
        });
      })
    }
  }

}
</script>

5. 拓展版本二

对于不同的后端有些可能是xml有些可能是QueryWrapper文件

对应的差异如下:

假设原先的代码如下:

@GetMapping("/queryErrorByPage")
@ApiOperationSupport(order=9)
@ApiOperation(value = "",notes="")
public R<IPage<DoCheckErrorVO>> queryErrorByPage(DoCheckErrorVO doCheckErrorVO, Query query) {
	QueryWrapper<DoCheckError> doCheckQueryWrapper =
		new QueryWrapper<DoCheckError>();
		
	LocalDateTime checkStartTime = doCheckErrorVO.getCheckStartTime();
	if(checkStartTime!=null){
		doCheckQueryWrapper.ge("check_date",checkStartTime);
	}
	LocalDateTime checkEndTime = doCheckErrorVO.getCheckEndTime();
	if(checkEndTime!=null){
		doCheckQueryWrapper.le("check_date",checkEndTime);
	}
	doCheckQueryWrapper.eq("status", CommonConstant.STATUS_ERROR)
						.orderByDesc("create_time");
	IPage<DoCheckError> pages = docheckErrorService.page(Condition.getPage(query), doCheckQueryWrapper);
	return R.data(DocheckErrorWrapper.build().pageVO(pages));
}

那么后续不要分页的情况,进行改造:

@GetMapping("/queryExportErrorByPage")
@ApiOperationSupport(order=9)
@ApiOperation(value = "",notes="")
public R<List<DoCheckErrorVO>> queryExportErrorByPage(DoCheckErrorVO doCheckErrorVO) {
	QueryWrapper<DoCheckError> doCheckQueryWrapper =
		new QueryWrapper<DoCheckError>();

	LocalDateTime checkStartTime = doCheckErrorVO.getCheckStartTime();
	if(checkStartTime!=null){
		doCheckQueryWrapper.ge("check_date",checkStartTime);
	}
	LocalDateTime checkEndTime = doCheckErrorVO.getCheckEndTime();
	if(checkEndTime!=null){
		doCheckQueryWrapper.le("check_date",checkEndTime);
	}
	doCheckQueryWrapper.eq("status", CommonConstant.STATUS_ERROR)
		.orderByDesc("create_time");
	List<DoCheckError> doCheckErrors = docheckErrorService.list(doCheckQueryWrapper);
	List<DoCheckErrorVO> doCheckErrorVOS = DocheckErrorWrapper.build().listVO(doCheckErrors);
	return R.data(doCheckErrorVOS);
}

再次补充前端的代码如下:(加深印象)

onLoad(page, params = {}) {
  this.loading = true;
  queryErrorByPage(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
    const data = res.data.data;
    this.page.total = data.total;
    this.data = data.records;
    this.loading = false;
    this.selectionClear();
  });
},
// 查询的方法
onSubmit() {
  var js = this.getQuery();
  this.query =  js;
  this.page.currentPage = 1;
  this.onLoad(this.page, js);
},
// 将getQuery的查询条件封装成json
getQuery(){
  var mTime = this.formInline.checkTimePicker;
  var js;
  var nos = '';
  var newmodel = '';
  if(this.formInline.model){//添加检查
    for(var i=0;i<this.formInline.model.length;i++){
      if(i==0){
        newmodel=this.formInline.model[i]
      }else{
        newmodel=newmodel+'-'+this.formInline.model[i]
      }
    }
  }
  if(this.formInline.equipmentNo){ 
    for(var j=0;j<this.formInline.equipmentNo.length;j++){
      if(j==0){
        nos=this.formInline.equipmentNo[j]
      }else{
        nos=nos+'-'+this.formInline.equipmentNo[j]
      }
    }
  }
  if ((mTime != null && (mTime.length != 1))) {
    js = {
      "equipmentNo": nos,
      "model": newmodel,
      "checkStartTime": moment(mTime[0]).format('YYYY-MM-DD HH:mm:ss'),
      "checkEndTime": moment(mTime[1]).format('YYYY-MM-DD HH:mm:ss')
    };
  } else {
    js = {
      "equipmentNo": nos,
      "model": newmodel
    };
  }
  return js;
},

// 下载Excel
handleExcel() {
  queryExportErrorByPage(Object.assign(this.getQuery())).then(res=>{
    const  column = this.option.column;
    const  data = res.data.data;
    let opt = {
      title: 'xxx下载',
      column: column,
      data: data
    }
    this.$export.excel({
      title: opt.title || new Date().getTime(),
      columns: opt.column,
      data: opt.data
    });
  })
},

6. 深造优化

上述这种版本对于大数据量来说,可能不够好,会出现如下情况:

在这里插入图片描述

再者基本的返回数据,需要结合自身:

在这里插入图片描述

6.1 初始版本(错误)

此版本有问题,如果你可以看出来,说明你是
在这里插入图片描述

有些数据量的请求,在后台计算,返回给前端的时,会导致如下问题:

async handleExcel() {
  if (this.isProcessing) return; // 防止重复点击
  this.isProcessing = true;
  // 提示后台处理中
  this.$message({
    message: '数据量较大,后台正在处理中,请勿重复点击!',
    type: 'info',
  });
  try {
    const pageSize = 100; // 每批次处理的条数
    const totalData = await queryExcelByPage(this.getQuery()); // 获取所有数据
    const data = totalData.data.data;
    console.log(data)
    const totalPages = Math.ceil(data.length / pageSize);
    const allData = []; // 用于存储所有批次的数据
    for (let page = 0; page < totalPages; page++) {
      const start = page * pageSize;
      const end = start + pageSize;
      const batchData = data.slice(start, end);
      allData.push(...batchData); // 合并所有批次数据
    }

    const opt = {
      title: 'xxx数据',
      column: this.option.column,
      data: allData,
    };

    this.$export.excel({
      title: opt.title,
      columns: opt.column,
      data: opt.data,
    });

    this.$message({
      message: '数据已成功导出!',
      type: 'success',
    });
  } catch (error) {
    console.error('导出失败:', error);
    this.$message({
      message: '导出失败,请稍后重试。',
      type: 'error',
    });
  } finally {
    // 5秒后解锁按钮
    setTimeout(() => {
        this.isProcessing = false;
      }, 500);
  }

},

后台对应的请求数据修改为:(增加一个延时)

// 钢丝绳跟踪表的导出方法
export const queryExcelByPage = (params) => {
  return request({
    url: '/api/blade-equipment/xx/xx',
    method: 'get',
    params: {
      ...params,
    },
    timeout:10000000,
  })
}

后续发现这类代码有点问题

  • queryExcelByPage 返回的数据量过大
  • queryExcelByPage 方法返回了所有数据,而不是分页数据。
    前端的分页逻辑又试图在本地对所有数据分页,这会导致不必要的重复计算和占用内存

6.2 迭代版本(正确)

使用真正的分页接口
确保后端提供分页接口,前端按需请求,避免一次性加载所有数据

修改分页请求逻辑:

async handleExcel() {
  if (this.isProcessing) return; // 防止重复点击
  this.isProcessing = true;

  try {
    const pageSize = 100; // 每批次处理的条数
    const allData = []; // 用于存储所有数据
    let currentPage = 1;
    let hasMoreData = true;

    while (hasMoreData) {
      const response = await queryExcelByPage({
        ...this.getQuery(),
        page: currentPage,
        size: pageSize, // 向后端传递分页参数
      });

      const batchData = response.data.data;
      allData.push(...batchData); // 合并数据

      if (batchData.length < pageSize) {
        hasMoreData = false; // 最后一页,停止循环
      } else {
        currentPage++; // 请求下一页
      }
    }

    const opt = {
      title: '钢丝绳跟踪表数据',
      column: this.option.column,
      data: allData,
    };

    this.$export.excel({
      title: opt.title,
      columns: opt.column,
      data: opt.data,
    });

    this.$message({ type: 'success', message: '数据已成功导出!' });
  } catch (error) {
    console.error('导出失败:', error);
    this.$message({ type: 'error', message: '导出失败,请稍后重试。' });
  } finally {
    setTimeout(() => {
      this.isProcessing = false;
    }, 500); // 解锁按钮
  }
}

实际代码如下:

// 下载Excel
async handleExcel() {
  if (this.isProcessing) return; // 防止重复点击
  this.isProcessing = true;
  // 提示后台处理中
  this.$message({
    message: '数据量较大,后台正在处理中,请勿重复点击!',
    type: 'info',
  });
  try {
    const pageSize = 100; // 每批次处理的条数
    const allData = []; // 用于存储所有数据
    let hasMoreData = true;
    let currentPage = 1;
    while (hasMoreData) {
      const response = await queryByPage(currentPage, 100, this.getQuery());
      const result = response.data.data;
      const batchData = result.records;
      allData.push(...batchData); // 合并数据
      const totalRecords = result.total; // 总记录数
      const processedRecords = currentPage * pageSize; // 已处理记录数
      // 判断是否还有下一页
      if (processedRecords >= totalRecords) {
        hasMoreData = false; // 没有更多数据
      } else {
        currentPage++; // 请求下一页
      }
    }

    const opt = {
      title: '钢丝绳跟踪表数据',
      column: this.option.column,
      data: allData,
    };

    this.$export.excel({
      title: opt.title,
      columns: opt.column,
      data: opt.data,
    });

    this.$message({ type: 'success', message: '数据已成功导出!' });
  } catch (error) {
    console.error('导出失败:', error);
    this.$message({ type: 'error', message: '导出失败,请稍后重试。' });
  } finally {
    setTimeout(() => {
      this.isProcessing = false;
    }, 500); // 解锁按钮
  }

},
;