vue2+xlsx+xlsx-style导出excel、设置样式、合并单元格

1 下载依赖

npm install xlsx
npm install xlsx-style

2 创建公用导出excel方法exportExcel.js

import * as XLSX from 'xlsx'
import * as XLSX_STYLE from 'xlsx-style'

/**
 * 导出数据
 */
export function exportExcel(headers, datasource, options, type, fileName="未命名" ,cell=[])  {
    // 一、准备数据
    const list = getExportDataList(headers, datasource, options)

    // 二、新建一个工作簿
    const workBook = XLSX.utils.book_new()

    // 三、使用二维数组生成一个工作表
    const workSheet = sheet_from_array_of_arrays(list)

    // 五、设置每列的宽度(单位:px)
    const wsCols = headers.map(item=>{
        return {wch:item.width}
    })
    workSheet['!cols'] = wsCols

    // 六、设置每行的高度(单位:px)
    let wsRows = []
    for (let i in list) {
      if (i == 0) {
        wsRows.push({ hpx: 100 }) // 首行高度为 100px
      } else {
        wsRows.push({ hpx: 30 }) // 其他行高度为 30px
      }
    }
    workSheet['!rows'] = wsRows
    var cellData =[]
   
    // 七、设置单元格样式
    for (let key in workSheet) {
      if (key == '!ref' || key == '!merges' || key == '!cols' || key == '!rows') {
        continue
      } else {
        // 匹配表格第一行(注意 A1 单元已合并为一个单元),设置其样式
        if (key == 'A1') {
          workSheet[key] = {
            t: 's', // 设置单元格类型(type: b Boolean, e Error, n Number, d Date, s Text, z Stub)
            v: fileName, // 设置单元格内容(raw value (number, string, Date object, boolean))
            // l: { Target: "https://sheetjs.com", Tooltip: "Find us @ SheetJS.com!" }, // 单元格超链接 cell hyperlink / tooltip (More Info)
            s: { // 设置单元格样式
              fill: { // 设置背景色
                fgColor: { rgb: 'ffffff' },
              },
              font: { // 设置字体
                name: '等线', // 字体名称
                sz: 24, // 字体大小
                bold: true, // 字体是否加粗
                color: { rgb: '5e7ce0' }, // 文字颜色
              },
              alignment: { // 设置居中
                horizontal: 'center', // 水平(向左、向右、居中)
                vertical: 'center', // 上下(向上、向下、居中)
                wrapText: false, // 设置单元格自动换行,目前仅对非合并单元格生效
                indent: 0 // 设置单元格缩进
              },
            },
          }
        }
        // 匹配表格第二行(A2 B2 C2 D2...),设置其样式
        else if (key.match(/\d+/g).join('') == '2') {
          workSheet[key].s = {
            border: {
              top: {
                style: 'thin',
              },
              bottom: {
                style: 'thin',
              },
              left: {
                style: 'thin',
              },
              right: {
                style: 'thin',
              },
            },
            fill: { // 设置背景色
              fgColor: { rgb: 'eeeeee' },
            },
            font: { // 设置字体
              name: '微软雅黑', // 字体名称
              sz: 10, // 字体大小
            },
            alignment: {
              horizontal: 'center', // 水平(向左、向右、居中)
              vertical: 'center', // 上下(向上、向下、居中)
              wrapText: false, // 设置单元格自动换行,目前仅对非合并单元格生效
              indent: 0 // 设置单元格缩进
            }
          }
        }
        // 其它单元格,设置其样式
        else {
          cell.filter(item=>{
            if(key != `${item.label}1` && key != `${item.label}2` && key.indexOf(`${item.label}`) > -1){
              cellData.push({value:workSheet[key].v,key:item.label})
            }
          })
          workSheet[key].s = {
            border: {
              top: {
                style: 'thin',
              },
              bottom: {
                style: 'thin',
              },
              left: {
                style: 'thin',
              },
              right: {
                style: 'thin',
              },
            },
            fill: { // 设置背景色
              fgColor: { rgb: 'ffffff' },
            },
            font: { // 设置字体
              name: '微软雅黑', // 字体名称
              sz: 10, // 字体大小
            },
            alignment: {
              horizontal: 'center', // 水平(向左、向右、居中)
              vertical: 'center', // 上下(向上、向下、居中)
              wrapText: true, // 设置单元格自动换行,目前仅对非合并单元格生效
              indent: 0 // 设置单元格缩进
            }
          }
        }
      }
    }
    //设置需要合并的单元格
  // 四、将 "A1" 到 "M1" 的单元格合并为 "A1"
// s:开始行  e:截至行   c:列  r:行
    let mergeArr = [{
          s: { c: 0, r: 0 },
          e: { c: headers.length-1, r: 0 },
        }]
  //循环设置指定列需要合并的行数据
    cell.filter(item=>{
      let data = []
      cellData.forEach(item1=>{
        if(item1.key===item.label){
          data.push(item1.value)
        } 
      })
      mergeArr = mergeArr.concat(mergeCell(data,item.value))
    })
     
     workSheet['!merges'] = mergeArr
    // 八、在工作簿中添加工作表
    XLSX.utils.book_append_sheet(workBook, workSheet, fileName)

    // 九、使用 xlsx-style 写入文件方式,使得自定义样式生效
    const tmpDown = new Blob([
     s2ab(
        XLSX_STYLE.write(workBook, {
          bookType: 'xlsx',
          bookSST: true,
          type: 'binary',
          cellStyles: true,
        })
      ),
    ])

    // 十、导出 Excel 文件
    const date = new Date()
    const formattedDate =
      `` +
      `${date.getFullYear()}` + // 年
      `${(date.getMonth() + 1).toString().padStart(2, '0')}` + // 月
      `${date.getDate().toString().padStart(2, '0')}` + // 日
      `${date.getHours().toString().padStart(2, '0')}` + // 时
      `${date.getMinutes().toString().padStart(2, '0')}` + // 分
      `${date.getSeconds().toString().padStart(2, '0')}` + // 秒
      ``.trim()
         downloadExcelFile(tmpDown, `${fileName}-${formattedDate}.${type.bookType == 'biff2' ? 'xls' : type.bookType}`)

  }
  /**
   * 合并单元格
   */
  function mergeCell(data,c) {
    let arr = []
    let data1 = [...new Set(data)]
    let  num = 2
    data1.forEach((item,index) =>{
        let numr = num
        let numc = numr+data.filter(item1=>item1==item).length-1
        num =  numr+data.filter(item1=>item1==item).length
        console.log(numr,numc)

      let obj = {
        s: { c: c, r: numr },
        e: { c: c, r: numc },
      }
      arr.push(obj)
    })
    return arr
  }

  /**
   * 准备数据
   */
 function  getExportDataList(headers, datasource, options) {
    const thList = headers.map(item=>item.label)
    const keyList = headers.map(item=>item.prop)
    const targetList = datasource
    const tdList = formatJson(keyList, targetList) // 过滤字段以及转换数据格式,即:表格数据
    tdList.unshift(thList) // 将 thList 数组添加到 tdList 数组开头,即:表格头部
    tdList.unshift(['']) // 将空字符串数组添加到 tdList 数组开头,即:表格首行
    const list = tdList
    return list
  }

  /**
   * 过滤字段以及转换数据格式
   */
  function formatJson(filterVal, jsonData) {
    return jsonData.map(v => filterVal.map(item => {
      if (item === 'name') {
        if (v['name'] != null && v['name'] != '') {
          return v[item].split(';').join('\n')
        } else {
          return '-'
        }
      }
      else {
        return v[item]
      }
    }))
  }
  /**
   * 使用二维数组生成一个工作表
   */
  function  sheet_from_array_of_arrays(data, opts) {
    var ws = {};
    var range = {
      s: {
        c: 10000000,
        r: 10000000
      },
      e: {
        c: 0,
        r: 0
      }
    }

    for (var R = 0; R != data.length; ++R) {
      for (var C = 0; C != data[R].length; ++C) {
        if (range.s.r > R) range.s.r = R;
        if (range.s.c > C) range.s.c = C;
        if (range.e.r < R) range.e.r = R;
        if (range.e.c < C) range.e.c = C;
        var cell = {
          v: data[R][C]
        };
        if (cell.v == null) continue;
        var cell_ref = XLSX.utils.encode_cell({
          c: C,
          r: R
        })

        if (typeof cell.v === 'number') cell.t = 'n';
        else if (typeof cell.v === 'boolean') cell.t = 'b';
        else if (cell.v instanceof Date) {
          cell.t = 'n';
          cell.z = XLSX.SSF._table[14];
          cell.v = date_num(cell.v);
          // cell.z = 'YYYY-MM-DD'
          cell.z = 'YYYY-MM-DD HH:mm:ss'
        } else cell.t = 's'

        ws[cell_ref] = cell
      }
    }

    if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range)
    return ws
  }

  /**
   * 日期转换
   */
  function date_num(v, date1904) {
    if (date1904) {
      v += 1462;
    }
    var epoch = Date.parse(v);
    return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
  }

  /**
   * 文件流转换
   */
  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
    }
  }

  /**
   * 使用 a 标签下载文件
   */
  function downloadExcelFile(obj, fileName) {
    const a_node = document.createElement('a')
    a_node.download = fileName
    if ('msSaveOrOpenBlob' in navigator) {
      window.navigator.msSaveOrOpenBlob(obj, fileName)
    } else {
      a_node.href = URL.createObjectURL(obj)
    }
    a_node.click()
    setTimeout(() => {
      URL.revokeObjectURL(obj)
    }, 2000)
  }

3 使用
(1)引入导出方法

import { exportExcel} from '@/js/exportExcel'

(2) 创建数据

//表头
  tableColumn: [
        { prop: 'index', label: '问题编号' ,width:8 },
        { prop: 'objectType', label: '测评对象分类',width:12 },
        { prop: 'objectName', label: '测评对象',width:12 },
        { prop: 'safeLevel', label: '安全层面' ,width:10},
        { prop: 'safetyControlPoint', label: '安全控制点',width:14 },
        { prop: 'controlItem', label: '测评项' ,width:20},
        { prop: 'originalRiskValue', label: '风险值' ,width:10},
        { prop: 'describe', label: '安全问题描述' ,width:20},
        { prop: 'problemAnalysis', label: '问题分析',width:20 },
        { prop: 'hazardAnalysis', label: '风险评价',width:20},
        { prop: 'recommendation', label: '整改建议' ,width:20},
        { prop: 'relatedThreats', label: '关联威胁',width:20},
      ],
//标题
  const options2 = [
    { title:`${this.project_name}${this.db_level}等级保护测评项目_安全问题汇总(传统)`}
    ]
//文件类型设置
  const type1 = { bookType: 'xlsx', bookSST: true, type: 'binary', cellStyles: true };
//需要合并的单元格
 let cell = [{
            label:"B",
            value:1
          },
          {
            label:"C",
            value:2
          }]
   /**
          *调用数据传值 
           * tableColumn2: 导出内容列以及列宽
           * data:导出内容
           * options:导出内容标题
           * type1:导出文件类型
           * filname:导出文件名
          * cell:需要合并的单元格
           */
 exportExcel(this.tableColumn,data, options1, type1, fileName,cell);

参考资料:
1.https://blog.csdn.net/Cai181191/article/details/131130926

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容