前端使用XLSX导出文件,涉及到合并、字体、边框等

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
      }

导出效果

前端使用JavaScript下载xlsx模板通常涉及两个步骤:首先从服务器获取文件,然后使用相关的库如`xlsx`处理Excel数据以及`js-xlsx`来生成XLSX文件。对于自定义边框样式,我们需要在创建工作表时指定单元格的格式。 以下是基本的步骤: 1. **安装必要的库**: - 安装`axios`用于发送HTTP请求:`npm install axios` - 安装`js-xlsx`处理XLSX:`npm install js-xlsx xlsx-style` 2. **从服务器下载模板**: 使用`axios`发起GET请求,获取服务器上的xlsx文件内容。例如: ```javascript const axios = require('axios'); axios.get('/template.xlsx') .then(response => { const workbook = XLSX.read(response.data, { type: 'binary' }); // workbook现在包含了模板的数据 }) .catch(error => console.error('Error downloading template:', error)); ``` 3. **自定义边框样式**: 使用`xlsx-style`库可以添加自定义样式。假设你想给某个范围的单元格添加边框: ```javascript const styles = XLSX_styles(workbook.SheetNames[0], workbook.Sheets); const borderedCellRange = 'A1:B5'; // 示例范围 const borderSettings = { border: [ { color: 'FF0000', style: 'thin', side: 'all' }, // 红色细边框 { color: '00FF00', style: 'medium', side: ['top', 'bottom'] } // 绿色上、下粗边框 ] }; const cellProperties = XLSX_cellProp({ range: borderedCellRange, properties: borderSettings }); XLSX.utils.applycellStyles(cellProperties, styles); ``` 4. **生成新的XLSX文件**: 将修改后的样式应用到工作簿后,你可以将其转换回二进制字符串并提供给用户下载: ```javascript const newFile = XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' }); const blob = new Blob([newFile], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const url = URL.createObjectURL(blob); // 提供给用户下载链接或创建a标签触发下载 downloadButton.href = url; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值