目的:
上传excel,将每一行解析为json数据,其中包括合并行,js-xlsx插件需要做些微调,主要要解决的是如何解析合并行数据为每行数据。
说明
本例Excel的header为2-3行合并,从第四行开始数据行,从第二列开始数据列
解析合并行参考:
https://github.com/SheetJS/sheetjs/issues/2674
前端:Vue2
js-xlsx:https://github.com/SheetJS/sheetjs
前端上传框:Element-dialog
<el-dialog title="Upload Feature" :visible.sync="uploadVisible">
<!-- excel area -->
<el-upload class="upload-demo" ref="upload" drag :action="combine_uploadurl()" multiple :before-upload="check_ext" :http-request="uploadFeature" :auto-upload="false">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button @click="uploadVisible = false">取 消</el-button>
<el-button type="primary" @click="submitFiles">上传</el-button>
</div>
</el-dialog>
data
uploadVisible: false,
上传方法:
uploadFeature(e) {
let file = e.file;
if (!file) {
return false;
} else {
const fileReader = new FileReader();
fileReader.onload = (ev) => {
try {
const data = ev.target.result;
const workbook = XLSX.read(data, {
type: "binary",
cellDates: true
});
const exname = workbook.SheetNames[0];
var ws = workbook.Sheets[exname];
// get all merge cell object {s: {r: R, c: C}, e: {r: R, c: C}}
const merges = ws['!merges'].map(m => typeof m == "string" ? XLSX.utils.decode_range(m) : m);
// filter row >= 3,change if header row not 3
const filtermerges = merges.filter(m => m.s.r > 2)
const aoa = XLSX.utils.sheet_to_json(ws, {
blankrows: false,
range: 3,
header: this.header,
defval: ""
});
// generate every row data parse merge cell.
var result = [];
var R = 0, range = XLSX.utils.decode_range(ws["!ref"]);
for(R = 0; R < aoa.length; ++R) {
var row = [];
// pull values from topleft cell of merges
for(var C = 0; C < range.e.c; C++) {
var m = filtermerges.find(m => m.s.c <= C && C <= m.e.c && m.s.r -3 <= R && R <= m.e.r -3);
if(m && aoa[m.s.r - 3][this.header[m.s.c]] != null) {
row[C] = aoa[m.s.r - 3][this.header[m.s.c]];
}
else if(!m && aoa[R][this.header[C]] != null) {
row[C] = aoa[R][this.header[C]];
}
}
// construct object with header and push
result.push(Object.fromEntries(this.header.filter((h, i) => row[i] != null).map((h, i) => ([h, row[i]]))));
}
// add custome fields
if (result.length != 0) {
let combine_data = [];
result.forEach((eldata) => {
if (eldata["version"].toLowerCase() === this.version.toLowerCase()) {
eldata["editable"] = false;
eldata["checkin_date"] = new Date(eldata["checkin_date"].getTime() + 480 * 60 * 1000).toISOString().split("T")[0];
combine_data.push(eldata);
}
});
// if no feature match milestone then exit
if (combine_data.length === 0 ) {
triggerMessage("error", "no feature match milestone " + this.version);
return;
}
// send body to backend api
let data = {
content: combine_data
}
ImportFeature(data).then((res) => {
if (res.data.code == 200) {
triggerMessage("success", "import template ok");
this.uploadVisible = false;
this.getFeatureInfo();
} else {
triggerMessage("error", res.data.data);
}
});
}
} catch (e) {
console.log("something error:", e);
return false;
}
};
// console.log(file)
fileReader.readAsBinaryString(file);
}
},
提交方法
submitFiles() {
this.$refs.upload.submit();
},
这里记录解析合并行的方法:
- 获取excel中所有合并行cell对象,生成数组对象merges: [{s: {r: R, c: C}, e: {r: R, c: C}}, ...]
- 筛掉header及上面空行,生成新的数组对象filtermerges
- 遍历excel每行数据,如果这行对象在filtermeges中能找到,说明他是合并行,则取Upper-left即左上角的数据填充
- 将新数据和header对应映射map,生成json数据
const data = ev.target.result;
const workbook = XLSX.read(data, {
type: "binary",
cellDates: true
});
const exname = workbook.SheetNames[0];
var ws = workbook.Sheets[exname];
// 1. get all merge cell object {s: {r: R, c: C}, e: {r: R, c: C}}
const merges = ws['!merges'].map(m => typeof m == "string" ? XLSX.utils.decode_range(m) : m);
// 2. filter row >= 3,change if header row not 3
const filtermerges = merges.filter(m => m.s.r > 2) // 如果不是从第三行开始,修改这个2的值
const aoa = XLSX.utils.sheet_to_json(ws, {
blankrows: false,
range: 3, // 如果不是从第三行开始,修改这个3的值
header: this.header,
defval: ""
});
// 3. generate every row data parse merge cell.
var result = [];
var R = 0, range = XLSX.utils.decode_range(ws["!ref"]);
for(R = 0; R < aoa.length; ++R) {
var row = [];
// pull values from topleft cell of merges
for(var C = 0; C < range.e.c; C++) {
var m = filtermerges.find(m => m.s.c <= C && C <= m.e.c && m.s.r -3 <= R && R <= m.e.r -3);
if(m && aoa[m.s.r - 3][this.header[m.s.c]] != null) { // 如果不是从第三行开始,修改这个-3的值
row[C] = aoa[m.s.r - 3][this.header[m.s.c]]; // 如果不是从第三行开始,修改这个-3的值
}
else if(!m && aoa[R][this.header[C]] != null) {
row[C] = aoa[R][this.header[C]];
}
}
// 4. construct object with header and push
result.push(Object.fromEntries(this.header.filter((h, i) => row[i] != null).map((h, i) => ([h, row[i]]))));
}
生成结果:
[
{ num: 'uno', ord: '1st', 'a': 1, 'b': 2, 'c': 3, 'd': 4 },
{ num: 'uno', ord: '1st', 'a': 2, 'b': 3, 'c': 4, 'd': 5 },
{ num: 'uno', ord: '2nd', 'a': 3, 'b': 4, 'c': 5, 'd': 6 },
{ num: 'uno', ord: '2nd', 'a': 4, 'b': 5, 'c': 6, 'd': 7 },
{ num: 'dos', ord: '1st', 'a': 5, 'b': 6, 'c': 7, 'd': 8 },
{ num: 'dos', ord: '1st', 'a': 6, 'b': 7, 'c': 8, 'd': 9 },
{ num: 'dos', ord: '2nd', 'a': 7, 'b': 8, 'c': 9, 'd': 10 },
{ num: 'dos', ord: '2nd', 'a': 8, 'b': 9, 'c': 10, 'd': 11 }
]