背景:涉及表格需要导入/导出表格(.csv、.excel)
实现:
CSV文件的优势
CSV兼容性差异
CSV相关的坑
问题:导出csv出现多余换行
背景:
后端接口获取数据 -> 过滤 + 定义列名称/字段及顺序 -> 借助a标签的download属性进行.csv格式导出
exportCSV(titles, data);



纠正:清除换行符
press_info_name: item.press_info.name.trim() || ' ',
问题: window导出csv一列分割成两列
初步分析: window在对空格(二进制编码为20)解析为分隔符。
尝试:window office开发表格正常

❌ 错误操作步骤
- 下载csv,自动在记事本中打开, 选择保存。纠正:浏览器设置禁止下载后自动打开。计算机中修改csv默认打开应用为office/wps
- office excel中新建数据表, 操作栏:数据 -> 自定义/导入数据 -> 选择csv文件 -> 保存为xlsx文件。

纠正:分割符禁止选择【空格】
附件源码
借助a标签导出.csv文件
// export.js/*** 导出 csv 文件,并直接下载* 多用于导出当前表格的数据* 注:导出的对象的字段以 titleOptions 中的字段为准,即不在 titleOptions 中的字段不会导出,便于直接使用表格绑定的对象* @param titleOptions {Object} 标题行配置,即要输出的属性名显示的标题. e.g { id: 'ID', title: '标题', sku: 'sku' }* @param data {Array} 导出的具体数据,通常为表格绑定的对象。* @param fileName {String} 下载文件的名字,默认使用 js 时间戳*/export function exportCSV(titleOptions = {}, data = [], fileName) {const fields = Object.keys(titleOptions);// 校验是否有空数据if (fields.length === 0 || data.length === 0) {console.error('参数不能为空'); // for debugreturn;}// 校验 titleOptions 对象中是否有空const hasEmptyTitle = !Object.values(titleOptions).every(Boolean);if (hasEmptyTitle) {console.error('titleOptions 中不能有空值'); // for debugreturn;}// 拼接输出内容const csvTitles = `${fields.map((field) => titleOptions[field]).toString()}\n`;let csvData = '';data.forEach((item) => {csvData += `${fields.map((field) => {// 将输出内容中英文逗号的部分替换掉,否则会导致 csv 识别错误return item[field] ? item[field].toString().replace(/,/g, ';') : '';}).toString()}\n`;});const universalBOM = '\uFEFF';const content = universalBOM + csvTitles + csvData;// 下载文件const downloadLink = window.document.createElement('a');downloadLink.setAttribute('href', 'data:text/csv; charset=utf-8,' + encodeURIComponent(content));downloadLink.setAttribute('download', `${fileName || new Date().getTime()}.csv`);window.document.body.appendChild(downloadLink);downloadLink.click();}
上传csv文件 ExcelUpload.vue
依赖 element 、xlsx
原理
实现
<template><!-- 解析 excel 文件的通用组件 --><!-- <input type="file" ref="upload" accept=".xls,.xlsx" class="outputlist_upload"> --><el-uploadaction=""ref="upload":show-file-list="false":http-request="readExcel":auto-upload="true"accept=".xlsx"><el-button v-bind="btnConf" :style="btnStyle" >{{ btnText }}</el-button></el-upload></template><script>import xlsx from 'xlsx';import { setTimeout } from 'timers';export default {components: {},props: {btnText: {required: false,type: String,default: '上传 Excel',},btnConf: {required: false,type: Object,default: () => ({}),},btnStyle:{required: false,type: String,default: '',},setTime:{required: false,type: Number,default: 100,},handleData: {required: false,type: Function,default: () => {},}},watch: {},data() {return {};},methods: {/*** 异步读取 excel 文件的第一个 sheet 内容,并转换成 json 数组传递给回调函数* @param {file} file excel 文件*/readExcel({file}) {const fileType = file.name.split('.').reverse()[0]if(fileType!=='xlsx'){this.$message.error('上传文件格式错误');return ;}this.$emit('uploadStatus');//保证loding出现,延后解析excelsetTimeout(() => {let reader = new FileReader();reader.onload = (e) => {const wb = xlsx.read(e.target.result, { type: 'binary' });const jsonContent = xlsx.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);//传递excel文件名this.$emit('filename',file.name);this.handleData(jsonContent);};reader.readAsBinaryString(file);},this.setTime)},},mounted() {},};</script>
引用
<excel-uploadclass="excel-upload":btn-conf="excelBtnConf":handle-data="uploadExcel":set-time="500"@uploadStatus="uploadStatus"></excel-upload><el-data :data="tableData" v-loading="tableLoading"></el-data>uploadExcel(content) {const fieldsMap = {书名: 'book_name',isbn: 'isbn',原价: 'pricing',};let ans = [];for (let i = 0; i < content.length; i++) {// ans.push()}this.tableLoading = false;if (content.length > 1) {this.toast(`成功匹配 ${matchedCount} 项任务`, 'success');} else {this.toast('匹配失败,请重新确认 excel 中的电子书名');}return ans;}
