js xlsx自定义样式导出

使用的技术

  1. xlsx 用于解析和编写各种电子表格。比如excel、csv、html文件
  2. xlsx-style 为xlsx库添加样式,比如字体颜色,大小,行宽等。但是只支持xlsx、xlsm、xlsb格式
  3. FileSaver.js 负责下载保存文件。对各种兼容性比较好,对跨域文件也有处理
  4. lodash js常用工具库

需要注意的地方

一、引入xlsx-style报错

This relative module was not found:
./cptable in ./node_modules/xlsx-style@0.8.13@xlsx-style/dist/cpexcel.js

亲测两种解决方案:

  1. 在\node_modules\xlsx-style\dist\cpexcel.js 807行 的 var cpt = require(’./cpt’ + ‘able’); 把这一行改成 var cpt = cptable;
  2. 把xlsx的cpexcel.js文件复制到xlsx-style的dist文件夹,覆盖cpexcel.js。

二、颜色添加不上

  1. xlsx-style 只 支持16进制的ARGB颜色,比如:{ rgb: "FFFFAA00" }。注意这里是没有#号的。
  2. 添加背景色是使用fill的fgColor,而不是bgColor。这里需要驼峰编写。

实现步骤

一、需求描述

将列表数据导出成Excel。所有的导出表格都固定表头样式,但是内容不固定,特殊列需要高亮(添加背景色),其他的数据列按需求设置宽度,可以添加换行等。(比如身份证号固定字符长度,地址列固定宽度、自动换行)

二、实现导出核心代码

使用xlsx的json_to_sheet工具方法,把数组的数据格式转换成xlsx需要worksheet。然后再添加xlsx-style支持的样式代码,使用xlsx-style编写成数据流,最后再使用FileSaver.js把数据流保存成文件

创建工作簿

let wb = XLSX.utils.book_new()

创建显示表头

const ws = XLSX.utils.json_to_sheet([
  colNames
], { header: keys, skipHeader: true })

追加数据到excel中,从第二行开始

  XLSX.utils.sheet_add_json(ws, data, { header: keys, skipHeader: true, origin: 'A2' })

添加表头样式,设置背景色、字体大小、下边框线

for (const key in ws) {
  // 第一行
  if(key.replace(/[^0-9]/ig, '') === '1') {
    ws[key].s = {
      fill: {
        fgColor: { rgb: 'FFA3F4B1' } // 添加背景色
      },
      font: {
        name: '宋体', // 字体
        sz: 12, // 字体大小
        bold: true // 加粗
      },
      border: {
      	// 下划线
        bottom: {
          style: 'thin',
          color: 'FF000000'
        }
      }
    }
  }
}

生成xlsx导出类。这里必须使用xlsx-style才能生成指定样式

wbOut = XLSXStyle.write(wb, { bookType: bookType, bookSST: false, type: 'binary' })

生成并下载文件

saveAs(new Blob([s2ab(wbOut)], { type: '' }), filename)

完整的代码

import XLSX from 'xlsx'
import XLSXStyle from 'xlsx-style'
import { saveAs } from 'file-saver'
import path from 'path'
import _ from 'lodash'

const FILE_NAME = '云途教育表格.xlsx'
const COL_PARAMS = ['hidden', 'wpx', 'width', 'wch', 'MDW']
const STYLE_PARAMS = ['fill', 'font', 'alignment', 'border']

/**
 * 数字转换成excel表头。 (递归处理)
 * @param {Number} num 需要转换的数字
 */
// eslint-disable-next-line no-unused-vars
function numToString(num) {
  let strArray = []
  let numToStringAction = function(o) {
    let temp = o - 1
    let a = parseInt(temp / 26)
    let b = temp % 26
    strArray.push(String.fromCharCode(64 + parseInt(b + 1)))
    if (a > 0) {
      numToStringAction(a)
    }
  }
  numToStringAction(num)
  return strArray.reverse().join('')
}

/**
 * 表头字母转换成数字。(进制转换)
 * @param {string} str 需要装换的字母
 */
function stringToNum(str) {
  let temp = str.toLowerCase().split('')
  let len = temp.length
  let getCharNumber = function(charx) {
    return charx.charCodeAt() - 96
  }
  let numout = 0
  let charnum = 0
  for (let i = 0; i < len; i++) {
    charnum = getCharNumber(temp[i])
    numout += charnum * Math.pow(26, len - i - 1)
  }
  return numout
}

/**
 * worksheet转成ArrayBuffer
 * @param {worksheet} s xlsx库中的worksheet
 */
function s2ab(s) {
  if (typeof ArrayBuffer !== 'undefined') {
    const buf = new ArrayBuffer(s.length)
    const view = new Uint8Array(buf)
    for (let i = 0; i !== s.length; ++i) {
      view[i] = s.charCodeAt(i) & 0xFF
    }
    return buf
  } else {
    const buf = new Array(s.length)
    for (let i = 0; i !== s.length; ++i) {
      buf[i] = s.charCodeAt(i) & 0xFF
    }
    return buf
  }
}

/**
 * 导出成Excel
 * @param {Array} data 数据
 * @param {Object} columns 每列参数说明。可直接写对应的中文名称,也可以写这一列的样式。比如列宽,背景色
 * @param {String} filename 文件名。可根据文件名后缀动态判断文件格式。支持xlsx, xlsm, xlsb(这三个支持自定义样式), html, csv等
 * @param {Object} styleConf 其他xlsx-style的参数
 *
 * columns数据格式:{
 *         name: '姓名',
 *         sex: '性别',
 *         birthday: {
 *           name: '出生日期',
 *           wch: 14
 *         },
 *         address: {
 *           name: '地址',
 *           wpx: 160,
 *           alignment: { wrapText: true }
 *         }
 *       }
 *
 * styleConf数据格式: {
 *          'E4': {
 *             font: {
 *               bold: true,
 *               color: { rgb 'FFFF0000' }
 *             }
 *          },
 *          '!merges': [ // 合并第一行
 *             {
 *               s: { c: 0, r: 0 },
 *               e: { c: 7, r: 0 }
 *             }
 *          ]
 *        }
 */
export function exportExcel(data, columns, filename = FILE_NAME, styleConf) {
  let keys = _.keys(columns)
  let colNames = _.mapValues(columns, o => {
    if (_.isPlainObject(o)) {
      return o.name
    } else {
      return o
    }
  })

  // 创建工作簿
  let wb = XLSX.utils.book_new()

  // 显示表头
  const ws = XLSX.utils.json_to_sheet([
    colNames
  ], { header: keys, skipHeader: true })
  // 过滤数据,只显示表头包含的数据
  for (let i = 0; i < data.length; i++) {
    data[i] = _.pick(data[i], keys)
  }
  // 追加数据到excel中,从第二行开始
  XLSX.utils.sheet_add_json(ws, data, { header: keys, skipHeader: true, origin: 'A2' })

  wb.SheetNames.push('sheet1')
  wb.Sheets['sheet1'] = ws

  // 根据不同的扩展名,导出不同格式的文件
  let bookType = null
  let ext = path.extname(filename)
  if (ext == null) {
    filename += '.xlsx'
    bookType = 'xlsx'
  } else {
    bookType = ext.substr(1).toLowerCase()
  }

  let wbOut
  // 如果是支持样式的格式
  if (['xlsx', 'xlsm', 'xlsb'].includes(bookType)) {
    // ws['!merges'] = [{// 合并第一行数据[B1,C1,D1,E1]
    //   s: {// s为开始
    //     c: 0, // 开始列
    //     r: 0// 开始取值范围
    //   },
    //   e: {// e结束
    //     c: 7, // 结束列
    //     r: 0// 结束范围
    //   }
    // }]

    for (const key in ws) {
      // 第一行,表头
      if (key.replace(/[^0-9]/ig, '') === '1') {
        ws[key].s = {
          fill: {
            fgColor: { rgb: 'FFA3F4B1' }
          },
          font: {
            name: '宋体',
            sz: 12,
            bold: true
          },
          border: {
            bottom: {
              style: 'thin',
              color: 'FF000000'
            }
          }
        }
      } else {
        let str = key.replace(/[^A-Za-z]+$/ig, '')
        let colIndex = stringToNum(str) - 1
        if (keys[colIndex] && _.isPlainObject(columns[keys[colIndex]])) {
          const a = _.pick(columns[keys[colIndex]], STYLE_PARAMS)
          ws[key].s = _.assign(ws[key].s, a)
        }
      }
    }
    // 设置列宽
    const colsP = []
    _.mapValues(columns, o => {
      colsP.push(_.pick(o, COL_PARAMS))
    })
    ws['!cols'] = colsP

    // 合并其他样式参数
    if (styleConf) {
      for (const key in styleConf) {
        if (ws.hasOwnProperty(key)) {
          ws[key].s = styleConf[key]
        }
      }
    }
    wbOut = XLSXStyle.write(wb, { bookType: bookType, bookSST: false, type: 'binary' })
  } else {
    wbOut = XLSX.write(wb, { bookType: bookType, bookSST: false, type: 'binary' })
  }

  saveAs(new Blob([s2ab(wbOut)], { type: '' }), filename)
}

调用实例

import { exportExcel } from '../../utils/xlsxUtils.js'

async exportStudent() {
      const params = {
        pageNum: this.pageNum,
        pageSize: this.pageSize
      }
      params[this.queryForm.searchType] = this.queryForm.searchInput
      this.exportLoading = true
      try {
        const res = await this.$http.get('/v1/ExportOrImport/exportStudentInfo', params)
        res.data.forEach(item => {
          item.courseStr = item.courses.map(o => o.name).join(',')
          item.classStr = item.classes.map(o => o.name).join(',')
        })
        await exportExcel(res.data, {
          name: '姓名',
          sex: '性别',
          idCard: {
            name: '身份证号',
            wch: 18
          },
          birthday: {
            name: '出生日期',
            wch: 14
          },
          primaryContactPhone: {
            name: '联系人号码',
            wch: 11
          },
          primaryContactName: '联系人姓名',
          relation: '联系人关系',
          nickName: '昵称',
          school: '就读学校',
          grade: '年级',
          address: {
            name: '地址',
            wpx: 160,
            alignment: { wrapText: true }
          },
          remark: {
            name: '备注',
            wpx: 240,
            alignment: { wrapText: true }
          },
          courseStr: {
            name: '报读课程',
            wpx: 120,
            font: { color: { rgb: 'ffff0000' } },
            alignment: { wrapText: true }
          },
          classStr: {
            name: '班级',
            wpx: 120,
            font: { color: { rgb: 'ffff0000' } },
            alignment: { wrapText: true }
          }
        }, '学员信息表.xlsx', {
          'E4': {
            fill: { fgColor: { rgb: 'FFFF0000' } }
          }
        })
      } catch (error) {

      }
      this.exportLoading = false
    }

最后导出的样式:
导出示例

  • 7
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值