背景:涉及表格需要导入/导出表格(.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 debug
return;
}
// 校验 titleOptions 对象中是否有空
const hasEmptyTitle = !Object.values(titleOptions).every(Boolean);
if (hasEmptyTitle) {
console.error('titleOptions 中不能有空值'); // for debug
return;
}
// 拼接输出内容
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-upload
action=""
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出现,延后解析excel
setTimeout(() => {
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-upload
class="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;
}