后台管理项目中经常使用文件导入导出,故封装了一个通用table的导出组件的实现
思路 使用 Dropdown 控件选择导出类型 触发导出
导出的table 数据类型
1 tableColumns: [ 2 { 3 title: \'序号\', 4 key: \'Ordinal\', 5 align: \'center\' 6 }, 7 { 8 title: \'产品编号\', 9 key: \'ProductNo\', 10 align: \'left\' 11 } 12 ] 13 tableData: [{Ordinal:1,ProductNo:\'1234\',ProductDesc:\'1232222\'}]
导出文件大部分情况下是后端处理,有时候我们只需要js处理 该怎么做呢?
1.导出CSV
首先实现导出CSV格式
拼接 csv格式其实就是个纯文本文件,中间使用逗号或者换行符进行拼接
这里使用 json2cvs这个包 需要npm 安装 npm install json2csv -s
下载方式
IE浏览器 不支持a标签进行下载,会打开url 故
对于微软系浏览器(IE和Edge)和非微软系列浏览器采用两种不同的方式进行下载
IE和Edge 采用了 navigator.msSaveBlob 方法 此方法为IE10及以上特有,IE10以下勿采用
非微软浏览器 使用a标签的click事件进行下载
关键代码
1 try { 2 const result = json2csv.parse(rows, { 3 fields: fields, 4 excelStrings: true 5 }); 6 if (this.MyBrowserIsIE()) { 7 // IE10以及Edge浏览器 8 var BOM = "\uFEFF"; 9 // 文件转Blob格式 10 var csvData = new Blob([BOM + result], { type: "text/csv" }); 11 navigator.msSaveBlob(csvData, `${fileName}.csv`); 12 } else { 13 let csvContent = "data:text/csv;charset=utf-8,\uFEFF" + result; 14 // 非ie 浏览器 15 this.createDownLoadClick(csvContent, `${fileName}.csv`); 16 } 17 } catch (err) { 18 alert(err); 19 }
1 //创建a标签下载 2 createDownLoadClick(content, fileName) { 3 const link = document.createElement("a"); 4 link.href = encodeURI(content); 5 link.download = fileName; 6 document.body.appendChild(link); 7 link.click(); 8 document.body.removeChild(link); 9 },
1 // 判断是否IE浏览器 2 MyBrowserIsIE() { 3 let isIE = false; 4 if ( 5 navigator.userAgent.indexOf("compatible") > -1 && 6 navigator.userAgent.indexOf("MSIE") > -1 7 ) { 8 // ie浏览器 9 isIE = true; 10 } 11 if (navigator.userAgent.indexOf("Trident") > -1) { 12 // edge 浏览器 13 isIE = true; 14 } 15 return isIE; 16 },
2.导出Excel类型文件
导出excel借鉴了iview-admin 自带的excel操作js(需要npm安装 xlsx)npm install xlsx -s
需要导出的地方调用excel.export_array_to_excel函数即可
1 const param = { 2 title: titles, 3 key: keys, 4 data: this.exportData, 5 autoWidth: true, 6 filename: this.exportFileName 7 }; 8 excel.export_array_to_excel(param);
完整excel操作js代码如下 excel.js
1 /* eslint-disable */ 2 import XLSX from \'xlsx\'; 3 4 function auto_width(ws, data) { 5 /*set worksheet max width per col*/ 6 const colWidth = data.map(row => row.map(val => { 7 /*if null/undefined*/ 8 if (val == null) { 9 return { 10 \'wch\': 10 11 }; 12 } 13 /*if chinese*/ 14 else if (val.toString().charCodeAt(0) > 255) { 15 return { 16 \'wch\': val.toString().length * 2 17 }; 18 } else { 19 return { 20 \'wch\': val.toString().length 21 }; 22 } 23 })) 24 /*start in the first row*/ 25 let result = colWidth[0]; 26 for (let i = 1; i < colWidth.length; i++) { 27 for (let j = 0; j < colWidth[i].length; j++) { 28 if (result[j][\'wch\'] < colWidth[i][j][\'wch\']) { 29 result[j][\'wch\'] = colWidth[i][j][\'wch\']; 30 } 31 } 32 } 33 ws[\'!cols\'] = result; 34 } 35 36 function json_to_array(key, jsonData) { 37 return jsonData.map(v => key.map(j => { 38 return v[j] 39 })); 40 } 41 42 // fix data,return string 43 function fixdata(data) { 44 let o = \'\' 45 let l = 0 46 const w = 10240 47 for (; l < data.byteLength / w; ++l) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w))) 48 o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w))) 49 return o 50 } 51 52 // get head from excel file,return array 53 function get_header_row(sheet) { 54 const headers = [] 55 const range = XLSX.utils.decode_range(sheet[\'!ref\']) 56 let C 57 const R = range.s.r /* start in the first row */ 58 for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */ 59 var cell = sheet[XLSX.utils.encode_cell({ 60 c: C, 61 r: R 62 })] /* find the cell in the first row */ 63 var hdr = \'UNKNOWN \' + C // <-- replace with your desired default 64 if (cell && cell.t) hdr = XLSX.utils.format_cell(cell) 65 headers.push(hdr) 66 } 67 return headers 68 } 69 70 export const export_table_to_excel = (id, filename) => { 71 const table = document.getElementById(id); 72 const wb = XLSX.utils.table_to_book(table); 73 XLSX.writeFile(wb, filename); 74 75 /* the second way */ 76 // const table = document.getElementById(id); 77 // const wb = XLSX.utils.book_new(); 78 // const ws = XLSX.utils.table_to_sheet(table); 79 // XLSX.utils.book_append_sheet(wb, ws, filename); 80 // XLSX.writeFile(wb, filename); 81 } 82 83 export const export_json_to_excel = ({ 84 data, 85 key, 86 title, 87 filename, 88 autoWidth 89 }) => { 90 const wb = XLSX.utils.book_new(); 91 data.unshift(title); 92 const ws = XLSX.utils.json_to_sheet(data, { 93 header: key, 94 skipHeader: true 95 }); 96 if (autoWidth) { 97 const arr = json_to_array(key, data); 98 auto_width(ws, arr); 99 } 100 XLSX.utils.book_append_sheet(wb, ws, filename); 101 XLSX.writeFile(wb, filename + \'.xlsx\'); 102 } 103 104 // 导出不带有汉字标题的execel内容 105 export const export_array_to_excel = ({ 106 key, 107 data, 108 title, 109 filename, 110 autoWidth 111 }) => { 112 const wb = XLSX.utils.book_new(); 113 const arr = json_to_array(key, data); 114 arr.unshift(title) 115 const ws = XLSX.utils.aoa_to_sheet(arr); 116 if (autoWidth) { 117 auto_width(ws, arr); 118 } 119 XLSX.utils.book_append_sheet(wb, ws, filename); 120 XLSX.writeFile(wb, filename + \'.xlsx\'); 121 } 122 123 // 导出带有汉字标题的execel内容 124 export const export_array_to_excel2 = ({ 125 key, 126 data, 127 title, 128 filename, 129 autoWidth 130 }) => { 131 const wb = XLSX.utils.book_new(); 132 const arr = json_to_array(key, data); 133 arr.unshift(key) 134 arr.unshift(title) 135 const ws = XLSX.utils.aoa_to_sheet(arr); 136 if (autoWidth) { 137 auto_width(ws, arr); 138 } 139 XLSX.utils.book_append_sheet(wb, ws, filename); 140 XLSX.writeFile(wb, filename + \'.xlsx\'); 141 } 142 143 export const read = (data, type) => { 144 /* if type == \'base64\' must fix data first */ 145 // const fixedData = fixdata(data) 146 // const workbook = XLSX.read(btoa(fixedData), { type: \'base64\' }) 147 const workbook = XLSX.read(data, { 148 type: type 149 }); 150 const firstSheetName = workbook.SheetNames[0]; 151 const worksheet = workbook.Sheets[firstSheetName]; 152 const header = get_header_row(worksheet); 153 const results = XLSX.utils.sheet_to_json(worksheet); 154 return { 155 header, 156 results 157 }; 158 } 159 160 export const readesxle = async (file, header, jsointitle) => { 161 return new Promise(function (resolve, reject) { 162 const resultdata = { 163 ErrCode: "9", 164 ErrText: \'导入文件格式不正确。\', 165 Rows: [] 166 } 167 const fileExt = file.name.split(\'.\').pop().toLocaleLowerCase() 168 if (fileExt === \'xlsx\' || fileExt === \'xls\') { 169 const reader = new FileReader(); 170 171 const thisXLSX = XLSX; 172 const thisheader = header; 173 const thisjsointitle = jsointitle; 174 reader.readAsArrayBuffer(file) 175 reader.onloadstart = e => {} 176 // reader.onprogress = e => { 177 // this.progressPercent = Math.round(e.loaded / e.total * 100) 178 // } 179 reader.onerror = e => { 180 resultdata.ErrText = \'文件读取出错\'; 181 resultdata.ErrCode = "1"; 182 resolve(resultdata); 183 } 184 reader.onload = e => { 185 const data = e.target.result 186 const 187 workbook = thisXLSX.read(data, { 188 type: "array" 189 }); 190 let tempFlag = true; 191 192 const firstSheetName = workbook.SheetNames[0]; 193 const worksheet = workbook.Sheets[firstSheetName]; 194 const sheetsheader = get_header_row(worksheet); 195 const sheetarray = thisXLSX.utils.sheet_to_json(worksheet); 196 197 thisheader.forEach((item, index) => { 198 if (sheetsheader.findIndex(x => x == item) == -1) { 199 tempFlag = false 200 } 201 }); 202 if (tempFlag) { 203 let sheetresult = []; 204 for (let i = 0; i < sheetarray.length; i++) { 205 sheetresult.push({}); 206 for (let j = 0; j < thisheader.length; j++) { 207 if (sheetarray[i][thisheader[j]] == undefined || sheetarray[i][thisheader[j]] == null) 208 sheetresult[i][thisjsointitle[j]] = ""; 209 else 210 sheetresult[i][thisjsointitle[j]] = sheetarray[i][thisheader[j]]; 211 } 212 } 213 resultdata.ErrCode = "0"; 214 resultdata.EErrText = "文件导入成功"; 215 resultdata.Rows = sheetresult; 216 } else { 217 resultdata.ErrCode = "1"; 218 resultdata.EErrText = "导入文件格式不正确。"; 219 resultdata.Rows = []; 220 } 221 resolve(resultdata); 222 } 223 } else { 224 resultdata.ErrCode = "1"; 225 resultdata.ErrText = \'文件:\' + file.name + \'不是EXCEL文件,请选择后缀为.xlsx或者.xls的EXCEL文件。\'; 226 resolve(resultdata); 227 } 228 }) 229 } 230 231 export default { 232 export_table_to_excel, 233 export_array_to_excel, 234 export_json_to_excel, 235 export_array_to_excel2, 236 read, 237 readesxle 238 }
3.导出pdf
最开始使用jspdf 包 把 需要导出的table使用 canvas生成图片,然后把图片插入pdf内,但是这种方式不容易控制,并且生成的pdf清晰度不高,如果直接写pdf又会产生对中文支持的不友好,后采用前后端配合生成pdf文件并导出
使用blob的方式 后端返回文件流前端 接收并下载
主要代码如下
1 //思路 webapi返回二进制的文件流 js 通过Blob接收并转换成pdf文件下载 2 this.$axios({ 3 method: "post", 4 Prefix: "", 5 data: { 6 ExCode: "IRAP_RPT_DownLoadFile", 7 fileName: this.exportFileName, 8 access_token: this.$cookies.get("access_token"), 9 valueKeys: valueKeys, //"Product,Version,Description", 10 labeNames: labeNames, // "产品,版本,描述", 11 tableData: tableData 12 } 13 // responseType:\'blob\' 14 }) 15 .then(response => { 16 // base64字符串转 byte[] 17 var bstr = atob(response.data.FileInfo), 18 n = bstr.length, 19 u8arr = new Uint8Array(n); 20 while (n--) { 21 u8arr[n] = bstr.charCodeAt(n); 22 } 23 // 转blob 24 var blob = new Blob([u8arr], { 25 type: `application/pdf;charset-UTF-8` 26 }); 27 28 if (this.MyBrowserIsIE()) { 29 // IE10以及Edge浏览器 30 var BOM = "\uFEFF"; 31 // 传入 Blob 对象 32 navigator.msSaveBlob(blob, `${this.exportFileName}.pdf`); 33 } else { 34 // 非ie 浏览器 35 let content = window.URL.createObjectURL(blob); 36 this.createDownLoadClick(content, `${this.exportFileName}.pdf`); 37 } 38 }) 39 .catch(err => { 40 console.log(err); 41 });
因为公司接口通用规范我这里返回的是文件的base64字符串
如果后台直接返回了二进制的文件流 就不用再次进行转换 并且需要加上responseType:\'blob\'这句
后台接口采用C# webapi 的方式主要代码如下
vue通用导出组件
public string DownLoadFile(string clientID, string msgFormat, string inParam) { dynamic res = new System.Dynamic.ExpandoObject(); try { dynamic dn = inParam.GetSimpleObjectFromJson(); string token = dn.access_token.ToString(); // 解析Json 字符串中的数组然后 转实体对象 string fileName = dn.fileName; string lbelObj = dn.labeNames; string valueKeyObj = dn.valueKeys; object[] tableObj = dn.tableData; string tableJson = JsonConvert.SerializeObject(tableObj); string[] valueKeys = valueKeyObj.Split(\',\'); string[] labeNames = lbelObj.Split(\',\'); //string[] valueKeys = new string[] { "Product", "Version", "Description" }; ; //string[] labeNames = new string[] { "产品", "版本", "描述" }; ; DataTable tblDatas = new DataTable("Datas"); //string jsonStr = "[{\"Product\":\"1\",\"Version\":\"1\",\"Description\":\"1\"}]"; tblDatas = ToDataTableTwo(tableJson); PDFHelper pdfHelper = new PDFHelper(); byte[] array = pdfHelper.ExportPDF(fileName, labeNames, tblDatas, valueKeys); // 文件byte数组转base64字符串 string str = Convert.ToBase64String(array);; byte[] bt = array.ToArray(); res.ErrCode = 0; res.ErrText = "文件生成成功"; res.FileInfo = str; return JsonConvert.SerializeObject(res); } catch (Exception ex) { //throw new Exception(ex.Message); res.ErrCode = 9999; res.ErrText = "Excel导入异常:" + ex.Message; return JsonConvert.SerializeObject(res); } }
C# pdf生成 使用itextsharp
完整js前台代码
1 <template> 2 <div style="float:right;padding-left:5px;"> 3 <Dropdown @on-click="btnExport" placement="bottom-start"> 4 <Button type="primary">导出 5 <Icon type="ios-arrow-down"></Icon> 6 </Button> 7 <DropdownMenu slot="list"> 8 <template v-for="(item,index) in exportList" > 9 <DropdownItem :key="`${_uid}_${index}`" :name="item.ExportID">{{ item.ExportName }}</DropdownItem> 10 </template> 11 </DropdownMenu> 12 </Dropdown> 13 </div> 14 </template> 15 <script> 16 import json2csv from "json2csv"; 17 import excel from "../../Common/libs/excel"; 18 export default { 19 data() { 20 return { 21 exportList: [ 22 { 23 ExportName: "1-导出Excel", 24 ExportID: "Excel" 25 }, 26 { 27 ExportName: "2-导出Csv", 28 ExportID: "Csv" 29 }, 30 { 31 ExportName: "3-导出Pdf", 32 ExportID: "Pdf" 33 } 34 ] 35 }; 36 }, 37 props: { 38 exportData: { 39 type: Array, 40 default: [] 41 }, 42 isPagination: { 43 type: Boolean, 44 default: false 45 }, 46 exportColumns: { 47 type: Array, 48 default: [] 49 }, 50 exportFileName: { 51 type: String, 52 default: "" 53 } 54 }, 55 watch: {}, 56 computed: {}, 57 mounted() { 58 // this.getRPTExportType() 初始化导出类型 59 }, 60 methods: { 61 // 下拉菜单改变时事件 62 handleExportTypeChange(param) { 63 // change 事件改变的时候提交给父组建 参数 value 64 this.$emit("myHandleExportTypeChange", param); 65 }, 66 // 判断是否IE浏览器 67 MyBrowserIsIE() { 68 let isIE = false; 69 if ( 70 navigator.userAgent.indexOf("compatible") > -1 && 71 navigator.userAgent.indexOf("MSIE") > -1 72 ) { 73 // ie浏览器 74 isIE = true; 75 } 76 if (navigator.userAgent.indexOf("Trident") > -1) { 77 // edge 浏览器 78 isIE = true; 79 } 80 return isIE; 81 }, 82 //创建a标签下载 83 createDownLoadClick(content, fileName) { 84 const link = document.createElement("a"); 85 link.href = encodeURI(content); 86 link.download = fileName; 87 document.body.appendChild(link); 88 link.click(); 89 document.body.removeChild(link); 90 }, 91 btnExport(fileType) { 92 debugger; 93 // 如果是分页的 94 if (this.isPagination == true) { 95 // 向父组件提交一个需要重新传数据的方法 类型name 96 this.$emit("myHandleRepeatExprot", fileType); 97 } else { 98 // 不分页直接导出 99 this.exportFile(fileType); 100 } 101 }, 102 exportFile(fileType) { 103 if (this.exportData.length == 0) { 104 this.$Message.error("不允许导出空表格"); 105 return false; 106 } 107 // 循环数组 108 this.exportData.forEach((item, index) => { 109 let temp = {}; 110 // 循环数组中的对象 当传入数据为undefined的时候 赋空 111 Object.keys(item).forEach(function(key) { 112 if (item[key] == undefined) { 113 item[key] == " "; 114 temp[key] = ""; 115 } else { 116 temp[key] = item[key]; 117 } 118 }); 119 this.exportData[index] = temp; 120 }); 121 let titles = []; // 导出内容的中文标题 122 let keys = []; // 导出内容的英文标题 123 this.exportColumns.forEach((item, index) => { 124 if (item.key != "handle") { 125 // 操作列定义为 handle 126 titles.push(item.title); 127 keys.push(item.key); 128 } 129 }); 130 if (fileType == "Excel") { 131 const param = { 132 title: titles, 133 key: keys, 134 data: this.exportData, 135 autoWidth: true, 136 filename: this.exportFileName 137 }; 138 excel.export_array_to_excel(param); 139 } else if (fileType == "Csv") { 140 this.exportCsv( 141 this.exportData, 142 this.exportColumns, 143 keys, 144 this.exportFileName 145 ); 146 } else { 147 // 导出pdf 148 let tempArray = []; 149 let labels = titles.join(","); // title拼接成一个字符串 150 let keys2 = keys.join(","); // 拼接字符串 151 let row0 = this.exportData[0]; //导出数组内容的第一行 152 var rowKeys = []; 153 for (var p1 in row0) { 154 // 数组 155 if (row0.hasOwnProperty(p1)) { 156 rowKeys.push(p1); // table内容的 key 157 } 158 } 159 // keys 和 table的第一列的key比较 取table中不存在的列 为了赋空值 160 var diffArray = keys.filter(key => !rowKeys.includes(key)); 161 162 let tableData = []; 163 // 循环数组 164 this.exportData.forEach((rowItem, index) => { 165 let temp = rowItem; 166 diffArray.forEach((keyItem, index) => { 167 temp[keyItem] = " "; 168 }); 169 tableData.push(temp); 170 }); 171 this.exportPdfFile(keys2, labels, tableData); 172 } 173 }, 174 // row data里面的每一个对象 keys 传入的需要导出列数组 175 // 筛选 需要导出的数据内容 176 /// 例如 keys ["Ordinal","Code"] row 有{Ordinal:1,Code:2,Name:3} 返回新对象{Ordinal:1,Code:2} 177 getRow(row, keys) { 178 let obj = {}; 179 keys.forEach(col => { 180 debugger; 181 let val = row[col]; 182 obj[col] = val; 183 }); 184 return obj; 185 }, 186 exportCsv(data, columns, keys, fileName) { 187 //导出的数据行集合 188 const rows = data.map(t => this.getRow(t, keys)); 189 //导出的数据列标题 190 var fields = []; 191 columns.forEach(t => { 192 if (t.key != "handle") { 193 // 操作列定义为handle 194 let temp = { 195 value: t.key, 196 label: t.title 197 }; 198 fields.push(temp); 199 } 200 }); 201 try { 202 const result = json2csv.parse(rows, { 203 fields: fields, 204 excelStrings: true 205 }); 206 if (this.MyBrowserIsIE()) { 207 // IE10以及Edge浏览器 208 var BOM = "\uFEFF"; 209 var csvData = new Blob([BOM + result], { type: "text/csv" }); 210 navigator.msSaveBlob(csvData, `${fileName}.csv`); 211 } else { 212 let csvContent = "data:text/csv;charset=utf-8,\uFEFF" + result; 213 // 非ie 浏览器 214 this.createDownLoadClick(csvContent, `${fileName}.csv`); 215 } 216 } catch (err) { 217 alert(err); 218 } 219 }, 220 exportPdfFile(valueKeys, labeNames, tableData) { 221 let rexportPdfFile = { 222 ExCode: "DownLoadFile", 223 fileName: this.exportFileName, 224 access_token: this.$cookies.get("access_token"), 225 valueKeys: valueKeys, // 例如 "Product,Version,Description", 226 labeNames: labeNames, // 例如 "产品,版本,描述", 227 tableData: tableData 228 }; 229 //思路 webapi返回二进制的文件流 js 通过Blob接收并转换成pdf文件下载 230 this.$axios({ 231 method: "post", 232 Prefix: "", 233 data: { 234 ExCode: "IRAP_RPT_DownLoadFile", 235 fileName: this.exportFileName, 236 access_token: this.$cookies.get("access_token"), 237 valueKeys: valueKeys, //"Product,Version,Description", 238 labeNames: labeNames, // "产品,版本,描述", 239 tableData: tableData 240 } 241 // responseType:\'blob\' 242 }) 243 .then(response => { 244 // base64字符串转 byte[] 245 var bstr = atob(response.data.FileInfo), 246 n = bstr.length, 247 u8arr = new Uint8Array(n); 248 while (n--) { 249 u8arr[n] = bstr.charCodeAt(n); 250 } 251 // 转blob 252 var blob = new Blob([u8arr], { 253 type: `application/pdf;charset-UTF-8` 254 }); 255 256 if (this.MyBrowserIsIE()) { 257 // IE10以及Edge浏览器 258 var BOM = "\uFEFF"; 259 // 传入 Blob 对象 260 navigator.msSaveBlob(blob, `${this.exportFileName}.pdf`); 261 } else { 262 // 非ie 浏览器 263 let content = window.URL.createObjectURL(blob); 264 this.createDownLoadClick(content, `${this.exportFileName}.pdf`); 265 } 266 }) 267 .catch(err => { 268 console.log(err); 269 }); 270 }, 271 getRPTExportType() { 272 this.$axios({ 273 method: "post", 274 Prefix: "", 275 data: { 276 ExCode: "IRAP_RPT_ExportType", 277 access_token: this.$cookies.get("access_token") 278 } 279 }).then(response => { 280 if (response.data.ErrCode == 0) { 281 this.exportList = response.data.Rows 282 } else { 283 this.$Message.error(response.data.ErrText) 284 } 285 }) 286 .catch(err => { 287 console.log(err); 288 }); 289 } 290 } 291 }; 292 </script>
父组件调用代码
1 <MyExportType :exportFileName=\'`test`\' v-on:myHandleRepeatExprot="myRepeatExprot" 2 :isPagination="isPagination" :exportData="exportData" :exportColumns="exportColumns" ref="MyExportType"></MyExportType>
如果父组件分页的需要导出所有未分页的数据 需要再次调用查询table数据的接口并且给exportData赋值
async myRepeatExprot(name) { // 查询所有 await this.geBTtResult(1) // 调用子组件的导出事件 await this.$refs.MyExportType.exportFile(name) },
否则 未分页或者导出当前页 直接导出即可 不需要通过父组件调用子组件事件
第一次写博文,有点紧张和言语不清忘见量,如果此文章对您有些许帮助,请给作者一些鼓励,或者留下您的意见
wexin alipay