vue
首先安装依赖
npm install xlsx-js-style
页面引用
import * as XLSX from 'xlsx-js-style'
表格数据dataList,我前端显示时也需要合并,有两个合并条件于是建立了两个属性sfRowSpan、userRowSpan,表示合并的行数,最后两行也有横着的合并项,所以column如下面所示
例如第1、2行需要合并,第一行数据的rowSpan为2,第二行的rowSpan为0...
columns: [
{
title: '省份',
dataIndex: 'province',
align: 'center',
width: 100,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (row.sfRowSpan && row.sfRowSpan !== 0) {
obj.attrs.rowSpan = row.sfRowSpan
} else if (row.sfRowSpan == 0) {
obj.attrs.rowSpan = 0
} else {
obj.attrs.rowSpan = 1
}
if (index >= this.dataList.length - 2) {
obj.attrs.colSpan = row.lastColSpanOne
}
return obj
}
},
{
title: '地区',
dataIndex: 'district',
align: 'center',
width: 100,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (index >= this.dataList.length - 2) {
obj.attrs.colSpan = 0
}
return obj
}
},
{
title: '联系人',
dataIndex: 'personInCharge',
align: 'center',
width: 100,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (index >= this.dataList.length - 2) {
obj.attrs.colSpan = 0
}
return obj
}
},
{
title: '业务员',
dataIndex: 'salesman',
align: 'center',
width: 100,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (row.userRowSpan && row.userRowSpan !== 0) {
obj.attrs.rowSpan = row.userRowSpan
} else if (row.userRowSpan == 0) {
obj.attrs.rowSpan = 0
} else {
obj.attrs.rowSpan = 1
}
if (index >= this.dataList.length - 2) {
obj.attrs.colSpan = 0
}
return obj
}
},
{
title: '单号',
dataIndex: 'salOrderNo',
align: 'center',
width: 150,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (index >= this.dataList.length - 2) {
obj.attrs.colSpan = 0
}
return obj
}
},
{
title: '名称',
dataIndex: 'name',
align: 'center',
width: 150,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (index >= this.dataList.length - 2) {
obj.attrs.colSpan = 0
}
return obj
}
},
{
title: '数量',
dataIndex: 'count',
align: 'center',
width: 80,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (index >= this.dataList.length - 1) {
obj.attrs.colSpan = row.lastColSpanTwo
}
return obj
}
},
{
title: '单位',
dataIndex: 'unit',
align: 'center',
width: 80,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (index >= this.dataList.length - 1) {
obj.attrs.colSpan = 0
}
return obj
}
},
{
title: '退货',
dataIndex: 'returnSales',
align: 'center',
width: 80,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (row.sfRowSpan && row.sfRowSpan !== 0) {
obj.attrs.rowSpan = row.sfRowSpan
} else if (row.sfRowSpan == 0) {
obj.attrs.rowSpan = 0
} else {
obj.attrs.rowSpan = 1
}
if (index >= this.dataList.length - 1) {
obj.attrs.colSpan = 0
}
return obj
}
},
{
title: '销量',
dataIndex: 'total',
align: 'center',
width: 80,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (row.sfRowSpan && row.sfRowSpan !== 0) {
obj.attrs.rowSpan = row.sfRowSpan
} else if (row.sfRowSpan == 0) {
obj.attrs.rowSpan = 0
} else {
obj.attrs.rowSpan = 1
}
if (index >= this.dataList.length - 1) {
obj.attrs.colSpan = 0
}
return obj
}
},
{
title: '区域销量',
dataIndex: 'areaTotal',
align: 'center',
width: 100,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (row.userRowSpan && row.userRowSpan !== 0) {
obj.attrs.rowSpan = row.userRowSpan
} else if (row.userRowSpan == 0) {
obj.attrs.rowSpan = 0
} else {
obj.attrs.rowSpan = 1
}
return obj
}
},
{
title: '计划',
dataIndex: 'areaPlanTotal',
align: 'center',
width: 80,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (row.userRowSpan && row.userRowSpan !== 0) {
obj.attrs.rowSpan = row.userRowSpan
} else if (row.userRowSpan == 0) {
obj.attrs.rowSpan = 0
} else {
obj.attrs.rowSpan = 1
}
return obj
}
},
{
title: '完成率',
dataIndex: 'finishingRate',
align: 'center',
width: 90,
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
}
if (row.userRowSpan && row.userRowSpan !== 0) {
obj.attrs.rowSpan = row.userRowSpan
} else if (row.userRowSpan == 0) {
obj.attrs.rowSpan = 0
} else {
obj.attrs.rowSpan = 1
}
return obj
}
}
]
浏览器显示效果
导出方法
导出excel同浏览器显示效果要一样
所以可以根据我之前数据内的rowSpan、colSpan找到合并的具体位置与数量
可以生成多个sheet
downLoadXlsx () {
if (this.dataList.length == 0) {
this.$notification.info({message: '不能导出空的数据'})
return
}
this.loading = true
// 创建工作簿
const wb = XLSX.utils.book_new()
// 列头 字符串数组
const title = this.getTitle(this.columns)
// 数据 字符串数组 数据不能为null 为null时表格边框样式不生效
const data = this.getData(this.dataList, this.columns)
// 标题 文件名
const point = moment(this.param.endDate).format('YYYY年MM月') + '区域完成情况'
// 创建工作表
const ws = XLSX.utils.aoa_to_sheet([[point], title, ...data])
// 取出当前excel范围 {e:{c: 34,r: 2},s:{c: 0, r: 0}} 表示从0,0开始 34,2结束 34列
const range = XLSX.utils.decode_range(ws['!ref'])
// !merges 合并 !cols 列宽度 [{wpx: 100}, ...]
// 合并第一行
ws['!merges'] = [
{s: {r: range.s.r,c: range.s.c}, e: {r: range.s.r,c: range.e.c}}
]
// 设置表格宽度
ws['!cols'] = []
// 按下标来的第一列第二列...
this.columns.forEach(item => {
ws['!cols'].push({
wpx: item.width
})
})
// 根据数据集内的rowSpan合并
for (let i = 0; i < this.dataList.length; i++) {
const iLine = this.dataList[i]
if (i < this.dataList.length - 2) {
if (iLine.sfRowSpan > 1) {
// 省份合并
// 固定规则 当前合并的初始行为2——下标 所以应+2 当前c(列)为0, r应为i+2 结束应为 c=0,r为i+2+rowspan
ws['!merges'].push({
s: {r: i + 2, c: 0}, //开始合并位置
e: {r: i + 2 + iLine.sfRowSpan - 1,c: 0} // 结束合并位置
})
// 合并退货 退货第8列 (下标)
ws['!merges'].push({
s: {r: i + 2, c: 8}, //开始合并位置
e: {r: i + 2 + iLine.sfRowSpan - 1,c: 8} // 结束合并位置
})
// 合并销量 销量第9列 (下标)
ws['!merges'].push({
s: {r: i + 2, c: 9}, //开始合并位置
e: {r: i + 2 + iLine.sfRowSpan - 1,c: 9} // 结束合并位置
})
}
if (iLine.userRowSpan > 0) {
// 合并业务员 业务员第3列 (下标)
ws['!merges'].push({
s: {r: i + 2, c: 3}, //开始合并位置
e: {r: i + 2 + iLine.userRowSpan - 1,c: 3} // 结束合并位置
})
// 合并区域销量 区域销量第10列 (下标)
ws['!merges'].push({
s: {r: i + 2, c: 10}, //开始合并位置
e: {r: i + 2 + iLine.userRowSpan - 1,c: 10} // 结束合并位置
})
// 合并计划 计划第11列 (下标)
ws['!merges'].push({
s: {r: i + 2, c: 11}, //开始合并位置
e: {r: i + 2 + iLine.userRowSpan - 1,c: 11} // 结束合并位置
})
// 合并完成率 完成率第12列 (下标)
ws['!merges'].push({
s: {r: i + 2, c: 12}, //开始合并位置
e: {r: i + 2 + iLine.userRowSpan - 1,c: 12} // 结束合并位置
})
}
} else {
ws['!merges'].push({
s: {r: i + 2, c: 0}, //开始合并位置
e: {r: i + 2, c: iLine.lastColSpanOne - 1 } // 结束合并位置
})
if (i == this.dataList.length - 1) {
ws['!merges'].push({
s: {r: i + 2, c: 6}, //开始合并位置
e: {r: i + 2, c: 6 + iLine.lastColSpanTwo - 1} // 结束合并位置
})
}
}
}
// 设置边框 字体
for (let r = range.s.r;r <= range.e.r; r++) {
for (let c = range.s.c;c <= range.e.c; c++) {
const ex = XLSX.utils.encode_cell({r: r, c: c})
if (!ws[ex]) {
ws[ex] = {}
}
// 第一二行字体加粗
if (r === 0 || r === 1) {
ws[ex].s = {
border: {
top: { style: 'thin', color: '#000000' },
bottom: { style: 'thin', color: '#000000' },
left: { style: 'thin', color: '#000000' },
right: { style: 'thin', color: '#000000' }
},
alignment: {
vertical: 'center', // 垂直居中
horizontal: 'center' // 水平居中
},
font: {
bold: true
}
}
} else {
ws[ex].s = {
border: {
top: { style: 'thin', color: '#000000' },
bottom: { style: 'thin', color: '#000000' },
left: { style: 'thin', color: '#000000' },
right: { style: 'thin', color: '#000000' }
},
alignment: {
vertical: 'center', // 垂直居中
horizontal: 'center' // 水平居中
}
}
}
}
}
// 添加工作表到工作簿
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')
// 第二个页签数据
// 列头
const titleL = this.getTitle(this.leftColumns)
// 数据
const dataL = this.getData(this.leftData, this.leftColumns)
// 创建工作表
const wsL = XLSX.utils.aoa_to_sheet([titleL, ...dataL])
// 取出当前excel范围
const rangeL = XLSX.utils.decode_range(wsL['!ref'])
// 设置字体及边框
for (let r = rangeL.s.r;r <= rangeL.e.r; r++) {
for (let c = rangeL.s.c;c <= rangeL.e.c; c++) {
const ex = XLSX.utils.encode_cell({r: r, c: c})
if (!wsL[ex]) {
wsL[ex] = {}
}
if (r === 0) {
wsL[ex].s = {
border: {
top: { style: 'thin', color: '#000000' },
bottom: { style: 'thin', color: '#000000' },
left: { style: 'thin', color: '#000000' },
right: { style: 'thin', color: '#000000' }
},
alignment: {
vertical: 'center', // 垂直居中
horizontal: 'center' // 水平居中
},
font: {
bold: true
}
}
} else {
wsL[ex].s = {
border: {
top: { style: 'thin', color: '#000000' },
bottom: { style: 'thin', color: '#000000' },
left: { style: 'thin', color: '#000000' },
right: { style: 'thin', color: '#000000' }
},
alignment: {
vertical: 'center', // 垂直居中
horizontal: 'center' // 水平居中
}
}
}
}
}
// 添加工作表到工作簿
XLSX.utils.book_append_sheet(wb, wsL, 'Sheet2')
// 第三个页签数据
// 列头
const titleR = this.getTitle(this.rightColumns)
// 数据
const dataR = this.getData(this.rightData, this.rightColumns)
// 创建工作表
const wsR = XLSX.utils.aoa_to_sheet([titleR, ...dataR])
// 取出当前excel范围
const rangeR = XLSX.utils.decode_range(wsR['!ref'])
// 设置字体及边框
for (let r = rangeR.s.r;r <= rangeR.e.r; r++) {
for (let c = rangeR.s.c;c <= rangeR.e.c; c++) {
const ex = XLSX.utils.encode_cell({r: r, c: c})
if (!wsR[ex]) {
wsR[ex] = {}
}
if (r === 0) {
wsR[ex].s = {
border: {
top: { style: 'thin', color: '#000000' },
bottom: { style: 'thin', color: '#000000' },
left: { style: 'thin', color: '#000000' },
right: { style: 'thin', color: '#000000' }
},
alignment: {
vertical: 'center', // 垂直居中
horizontal: 'center' // 水平居中
},
font: {
bold: true
}
}
} else {
wsR[ex].s = {
border: {
top: { style: 'thin', color: '#000000' },
bottom: { style: 'thin', color: '#000000' },
left: { style: 'thin', color: '#000000' },
right: { style: 'thin', color: '#000000' }
},
alignment: {
vertical: 'center', // 垂直居中
horizontal: 'center' // 水平居中
}
}
}
}
}
// 添加工作表到工作簿
XLSX.utils.book_append_sheet(wb, wsR, 'Sheet3')
// 生成Excel文件
XLSX.writeFile(wb, point + '.xlsx')
this.loading = false
},
getTitle (columns) {
const title = []
columns.forEach(item => {
title.push(item.title)
})
return title
},
getData (dataList, columns) {
const data = []
for (let i = 0; i < dataList.length; i++) {
if (!data[i]) {
data[i] = []
}
for (const col of columns) {
data[i].push(dataList[i][col.dataIndex])
}
}
return data
}
导出效果