前言
如果想要下载好看的Excel推荐阅读:
详细的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);
}
注意,以上的代码需要注意的点如下:
- data 以及total的赋值,需要看api带来的参数,多少会有点变动,有些是length有些直接为total,注意看位置
- 对应下载的数据,可以通过一个表定义选择对应的数据导出
下面给一个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); // 解锁按钮
}
},