sheetjs的js-xlsx插件是专门为web前段处理excel表格开发的一款开源插件,插件的github地址是https://github.com/sheetjs/js-xlsx官网地址http://sheetjs.com/,它提供了excel表格的导入导出功能,为前段显示修改excel表格提供了可能,但是毕竟只是一个插件,无法实现excel的全部功能,比如表格的合并拆分这些基础的功能,这些都需要开发者自己来实现,本文将介绍如何用纯js实现excel表格的合并拆分表格以及动态插入删除行列。
js-xlsx导入excel表格的时候会生成一个sheet对象,这个对象包含了一个表格的具体数据,包括范围,合并的单元格,各单元格数据,基本样式等等,如下图:
其中"!ref"表示显示的范围,从A1到J35。"!merges"表示那些合并的单元格项,具体数据格式如下:
其中s表示开始单元格的坐标,c是列坐标,r是行坐标,都是从0开始,e表示结束单元格的坐标,我们通过修改这些数据完成单元格的合并与拆分。
var row_count = 0;
var colums_count = 0;
function refreshFromSheet() {
var html = XLSX.utils.sheet_to_html(worksheet);
document.getElementById('result').innerHTML = html;
var ref = worksheet["!ref"];
var s_e = ref.split(":");
row_count = getRowFromId(s_e[1])+1;
colums_count = getIndexFromId(s_e[1])+1;
$("table tr:first-child td:first-child").css("font-size", 25);
$("table tr td").css("text-align", "center");
}
function readWorkbook(workbook) {
var sheetNames = workbook.SheetNames; // 工作表名称集合
worksheet = workbook.Sheets[sheetNames[0]]; // 这里我们只读取第一张sheet
refreshFromSheet();
}
我们要实现的是鼠标点击两个单元格,就合并两个单元格间的所有表格为一个,我们记录下两次点击选择的表格id,转为坐标,然后加入到shheet对象里,最后重新渲染下界面:
var selected_split_grid_id = "";//选中的要拆分的单元格id
var selected_grid_id = "";
var selected_merge_grid_s = {};//要合并的开始单元格
var selected_merge_grid_e = {};//要合并的结束单元格
var selected_merge_grid_sid = "";//要合并的开始单元格id
var selectedType = -1;// 0是选中工艺状态用于拆分,
// 1是参数行选中状态用于编辑
// 2是合并模式
// 3是插入行
// 4是插入列
// 5是插入列
// 6是插入列+
function mergeGrid() {
if (selectedType == 2) {
resetType()
} else {
selectedType = 2;
$("#label").text("合并模式");
}
clearType();
}
function resetType() {
selectedType = -1;
$("#label").text("编辑模式");
}
function clearType() {
if (selected_merge_grid_sid)
$("#" + selected_merge_grid_sid).css("background", "#EEEEEE");
if (grid_selected) {
$("#" + selected_grid_id).css("background", "#eeeeee");
grid_selected = false
}
selected_merge_grid_s = {};
selected_merge_grid_e = {};
}
var letters = [
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG', 'AH', 'AI', 'AJ', 'AK', 'AL', 'AM', 'AN', 'AO', 'AP', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AV', 'AW', 'AX', 'AY', 'AZ'
];
function getIndexFromId(str) {
var reg = /([A-Z]+)/g;
var result = reg.exec(str);
return letters.indexOf(result[0]);
}
function getRowFromId(str) {
var reg = /(\d+)/g;
var result = reg.exec(str);
return parseInt(result[0]) - 1;
}
$("#result").on("tap", "table tbody tr td", function () {
if (selectedType == 2) {//点击选择合并单元格
var colums = getIndexFromId($(this).attr("id"));
var row = getRowFromId($(this).attr("id"));
if (JSON.stringify(selected_merge_grid_s) == "{}") {
//选择合并的开始
selected_merge_grid_sid = $(this).attr("id");
selected_merge_grid_s.r = row;
selected_merge_grid_s.c = colums;
$(this).css("background", "#007aff");
} else {
//选择合并的结尾然后合并
selected_merge_grid_e.r = row;
selected_merge_grid_e.c = colums;
if (selected_merge_grid_s.r == selected_merge_grid_e.r && selected_merge_grid_s.c == selected_merge_grid_e.c) {
//如果第二次选择的单元格和第一次相同则取消第一次的选择
selected_merge_grid_s = {};
selected_merge_grid_e = {};
$("#" + selected_merge_grid_sid).css("background", "#EEEEEE");
} else
// if (selected_merge_grid_s.r == selected_merge_grid_e.r || selected_merge_grid_s.c == selected_merge_grid_e.c)
{
//如果后点在前点店面则交换两点位置信息
if (selected_merge_grid_s.r >= selected_merge_grid_e.r && selected_merge_grid_s.c >= selected_merge_grid_e.c) {
var tmpc = selected_merge_grid_s.c;
selected_merge_grid_s.c = selected_merge_grid_e.c;
selected_merge_grid_e.c = tmpc;
var tmpr = selected_merge_grid_s.r;
selected_merge_grid_s.r = selected_merge_grid_e.r;
selected_merge_grid_e.r = tmpr;
}
var table = document.getElementsByTagName("table")[0];
var sheet = XLSX.utils.table_to_sheet(table);
if (!sheet["!merges"]) sheet["!merges"] = [];
var merges = sheet["!merges"];
var merged = false;
//考虑到第一次选择的单元格可能是已合并的单元格,遍历已合并列表把新合并数据替换进去
if (merges) {
for (var i = 0; i < merges.length; i++) {
var merge_s = merges[i].s;
if (merge_s.c == selected_merge_grid_s.c && merge_s.r == selected_merge_grid_s.r) {
merges[i].e = selected_merge_grid_e;
merged = true;
}
if (merge_s.c == selected_merge_grid_e.c && merge_s.r == selected_merge_grid_e.r) {
merges[i].s = selected_merge_grid_s;
merged = true;
}
}
} else {
merges = [];
}
//如果不是已合并单元格,就直接push进去
if (!merged) {
sheet["!merges"].push({
"s": selected_merge_grid_s,
"e": selected_merge_grid_e
});
}
worksheet = sheet;
refreshFromSheet();//刷新界面
selected_merge_grid_s = {};
selected_merge_grid_e = {};
}
}
}
})
拆分单元格也是同理,根据点击选择的id,转为坐标然后遍历"!merges"数组,删除匹配的项目就完成已合并表格的删除
function splitGrid() {
if (selectedType == 0) {
resetType()
} else {
selectedType = 0;
clearType();
$("#label").text("拆分模式");
}
}
$("#result").on("tap", "table tbody tr td", function () {
if (selectedType == 0) {//点击选择拆分单元格
if (selected_split_grid_id)
$("#" + selected_split_grid_id).css("background", "#eeeeee");
selected_split_grid_id = "";
var colums = getIndexFromId($(this).attr("id"));
var row = getRowFromId($(this).attr("id"));
var merges = worksheet["!merges"];
if (!merges) {
return;
}
var table = document.getElementsByTagName("table")[0];
var sheet = XLSX.utils.table_to_sheet(table);
var changed = false;
for (var j = 0; j < merges.length; j++) {
var srcmerge = sheet["!merges"][j];
if (srcmerge.s.c == colums && srcmerge.s.r == row) {
sheet["!merges"].splice(j, 1);
changed = true;
break;
}
}
if (changed) {
worksheet = sheet;
refreshFromSheet();
resetType();
}
}
})
首先扩展航和列,直接操作"!ref"字段,然后把所选中行列之后的数据全部后移
function insertRow() {
if (selectedType == 3) {
resetType()
} else {
selectedType = 3;
$("#label").text("插入行模式,点击要插入的行");
}
}
function insertRowReal(row) {
var table = document.getElementsByTagName("table")[0];
var sheet = XLSX.utils.table_to_sheet(table);
sheet["!ref"] = "A1:" + letters[colums_count-1] + String(row_count + 1);
for (var i = row_count; i >= row + 1; i--) {
for (var j = 0; j <= colums_count-1; j++) {
sheet[letters[j] + (i + 1)] = sheet[letters[j] + (i)];
}
}
var merges = sheet["!merges"];
var tomerge = [];
if (merges) {
for (var i = 0; i < merges.length; i++) {
var s = merges[i].s;
var e = merges[i].e;
if (s.r == e.r && s.r == row) {
tomerge.push({s: {c: s.c, r: s.r}, e: {c: e.c, r: e.r}});
e.r = e.r + 1;
s.r = s.r + 1;
} else {
if (e.r >= row) {
e.r = e.r + 1;
}
if (s.r > row) {
s.r = s.r + 1;
}
}
}
for (var i = 0; i < tomerge.length; i++) {
merges.push(tomerge[i]);
}
}
worksheet = sheet;
refreshFromSheet();
resetType();
generatexuhao();
}
function deleteRowReal(row) {
var table = document.getElementsByTagName("table")[0];
var sheet = XLSX.utils.table_to_sheet(table);
sheet["!ref"] = "A1:" + letters[colums_count-1] + String(row_count - 1);
for (var i = row + 1; i <= row_count; i++) {
for (var j = 0; j <= colums_count-1; j++) {
sheet[letters[j] + (i)] = sheet[letters[j] + (i + 1)];
}
}
var merges = sheet["!merges"];
var tomerge = [];
if (merges) {
for (var i = 0; i < merges.length; i++) {
var s = merges[i].s;
var e = merges[i].e;
if (s.r == e.r && s.r == row) {
tomerge.push(i)
} else {
if (e.r >= row) {
e.r = e.r - 1;
}
if (s.r > row) {
s.r = s.r - 1;
}
}
}
for (var i = 0; i < tomerge.length; i++) {
sheet["!merges"].splice(tomerge[i], 1);
}
}
worksheet = sheet;
refreshFromSheet();
resetType();
generatexuhao();
}
function insertColums() {
if (selectedType == 4) {
resetType()
} else {
selectedType = 4;
$("#label").text("插入列模式,点击要插入的列");
}
}
function deleteRow() {
if (selectedType == 5) {
resetType()
} else {
selectedType = 5;
$("#label").text("删除行模式,点击要删除的行");
}
}
function deleteColums() {
if (selectedType == 6) {
resetType()
} else {
selectedType = 6;
$("#label").text("删除列模式,点击要删除的列");
}
}
function insertColumsReal(colums) {
var table = document.getElementsByTagName("table")[0];
var sheet = XLSX.utils.table_to_sheet(table);
sheet["!ref"] = "A1:" + letters[colums_count ] + String(row_count);
for (var i = colums_count ; i >= colums; i--) {
for (var j = 0; j <= row_count; j++) {
sheet[letters[i + 1] + (j)] = sheet[letters[i] + (j)];
}
}
var merges = sheet["!merges"];
var tomerge = [];
if (merges) {
for (var i = 0; i < merges.length; i++) {
var s = merges[i].s;
var e = merges[i].e;
if (s.c == e.c && s.c == colums) {
tomerge.push({s: {c: s.c, r: s.r}, e: {c: e.c, r: e.r}});
e.c = e.c + 1;
s.c = s.c + 1;
} else {
if (e.c >= colums) {
e.c = e.c + 1;
}
if (s.c > colums) {
s.c = s.c + 1;
}
}
}
for (var i = 0; i < tomerge.length; i++) {
merges.push(tomerge[i]);
}
}
worksheet = sheet;
refreshFromSheet();
resetType();
}
function deleteColumsReal(colums) {
var table = document.getElementsByTagName("table")[0];
var sheet = XLSX.utils.table_to_sheet(table);
sheet["!ref"] = "A1:" + letters[colums_count - 2] + String(row_count);
for (var i = colums; i < colums_count-1; i++) {
for (var j = 0; j <= row_count; j++) {
sheet[letters[i] + (j)] = sheet[letters[i + 1] + (j)];
}
}
var merges = sheet["!merges"];
var tomerge = [];
if (merges) {
for (var i = 0; i < merges.length; i++) {
var s = merges[i].s;
var e = merges[i].e;
if (s.c == e.c && s.c == colums) {
tomerge.push(i);
} else {
if (e.c >= colums) {
e.c = e.c - 1;
}
if (s.c > colums) {
s.c = s.c - 1;
}
}
}
for (var i = 0; i < tomerge.length; i++) {
sheet["!merges"].splice(tomerge[i], 1);
}
}
worksheet = sheet;
refreshFromSheet();
resetType();
}
$("#result").on("tap", "table tbody tr td", function () {
if (selectedType == 3) {
var row = getRowFromId($(this).attr("id"));
insertRowReal(row);
} else if (selectedType == 4) {
var colums = getIndexFromId($(this).attr("id"));
insertColumsReal(colums);
} else if (selectedType == 5) {
var row = getRowFromId($(this).attr("id"));
deleteRowReal(row);
} else if (selectedType == 6) {
var colums = getIndexFromId($(this).attr("id"));
deleteColumsReal(colums);
}
})
excel导入到web前端然后导出到后台的话会丢失大量样式,我们可以在后端操作excel(java有相关类库),把前段返回的excel表格里的数据填入原excel文件里,这样即保留原文件的样式又获得了前端返回的数据。