使用Vue+xlsx+xlsx-style实现导出自定义样式的Excel文件

2023-10-27

本文就是上一篇《使用Python+openpyxl实现导出自定义样式的Excel文件》文章中提到的“之前项目的导出Excel文件操作都是在前端完成的...”这段话中基于前端实现的导出Excel文件方法。

文档地址:https://docs.sheetjs.com/

一、导入依赖
1.若项目是由Vue2 + Webpack构建的,那所需依赖包,如下所示

npm i xlsx
npm i xlsx-style

可能还要解决 Can't resolve './cptable' in '...' 的问题,在 vue.config.js 文件中加入该配置

module.exports = {
    externals: {
        './cptable': 'var cptable'
     }
}

2.若项目是由Vue3 + Vite构建的,那所需依赖包,如下所示

npm i xlsx
npm i xlsx-style-vite

二、引用依赖

import * as XLSX from 'xlsx'
import * as XLSX_STYLE from 'xlsx-style' // Vue2 + Webpack
import * as XLSX_STYLE from 'xlsx-style-vite' // Vue3 + Vite

三、示例代码

/src/Example/ExportExcel/index.vue

<template>
  <div style="display: flex; width: 100%; height: 100%; align-item: center;">
    <a
      style="margin: auto; padding: 5px 11px; background-color: #5e7ce0; border-radius: 3px; color: #fff; cursor: pointer;"
      @click="handelExportExcelClick"
    >
      <small style="display: inline-flex; align-items: center; line-height: 1px;">导出数据</small>
    </a>
  </div>
</template>
 
<script>
import * as XLSX from 'xlsx'
// import * as XLSX_STYLE from 'xlsx-style' // Vue2 + Webpack
import * as XLSX_STYLE from 'xlsx-style-vite' // Vue3 + Vite
 
export default {
  data: () => ({
    targetList: []
  }),
  methods: {
    /**
     * 导出数据
     */
    handelExportExcelClick() {
      // 一、准备数据
      const list = this.getExportDataList()
      console.log('list =>', list)
 
      // 二、新建一个工作簿
      const workBook = XLSX.utils.book_new()
 
      // 三、使用二维数组生成一个工作表
      const workSheet = this.sheet_from_array_of_arrays(list)
 
      // 四、将 "A1" 到 "M1" 的单元格合并为 "A1"
      workSheet['!merges'] = [
        {
          s: { // s ("start"): c = 0 r = 0 -> "A1"
            r: 0,
            c: 0
          },
          e: { // e ("end"):   c = 0 r = 9 -> "J1"
            r: 0,
            c: 9
          }
        }
      ]
 
      // 五、设置每列的宽度(单位:px)
      const wsCols = [
        { wch: 10 },
        { wch: 30 },
        { wch: 40 },
        { wch: 15 },
        { wch: 15 },
        { wch: 15 },
        { wch: 15 },
        { wch: 15 },
        { wch: 20 },
        { wch: 20 },
      ]
      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
 
      // 七、设置单元格样式
      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: '爆款商品列表', // 设置单元格内容(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: 35, // 字体大小
                  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 // 设置单元格缩进
              }
            }
          }
          // 匹配表格中除了 B1 B2 的 B 列单元格,以及除了 C1 C2 的 C 列单元格,设置其样式
          else if ((key != 'B1' && key != 'B2' && key.indexOf('B') > -1) || (key != 'C1' && key != 'C2' && key.indexOf('C') > -1)) {
            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: 'left', // 水平(向左、向右、居中)
                vertical: 'center', // 上下(向上、向下、居中)
                wrapText: true, // 设置单元格自动换行,目前仅对非合并单元格生效
                indent: 1 // 设置单元格缩进
              }
            }
          }
          // 匹配表格中除了 G1 G2 的 G 列单元格,设置其样式
          else if (key != 'G1' && key != 'G2' && key.indexOf('G') > -1) {
            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: false, // 设置单元格自动换行,目前仅对非合并单元格生效
                indent: 0 // 设置单元格缩进
              }
            }
 
            if (workSheet[key].v == '待审核') {
              workSheet[key].s.font.color = { rgb: '1a44d7' }
            } else if (workSheet[key].v == '审核已通过') {
              workSheet[key].s.font.color = { rgb: '4ab913' }
            } else if (workSheet[key].v == '审核未通过') {
              workSheet[key].s.font.color = { rgb: 'ed143d' }
            } else {
              workSheet[key].s.font.color = { rgb: '000000' }
            }
          }
          // 匹配表格中除了 H1 H2 的 H 列单元格,设置其样式
          else if (key != 'H1' && key != 'H2' && key.indexOf('H') > -1) {
            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: false, // 设置单元格自动换行,目前仅对非合并单元格生效
                indent: 0 // 设置单元格缩进
              }
            }
 
            if (workSheet[key].v == '待上架') {
              workSheet[key].s.font.color = { rgb: '1a44d7' }
              workSheet[key].s.fill.fgColor = { rgb: 'eff2fc' }
            } else if (workSheet[key].v == '正常') {
              workSheet[key].s.font.color = { rgb: '4ab913' }
              workSheet[key].s.fill.fgColor = { rgb: 'f0f9eb' }
            } else if (workSheet[key].v == '已下架') {
              workSheet[key].s.font.color = { rgb: 'ed143d' }
              workSheet[key].s.fill.fgColor = { rgb: 'fef0f0' }
            } else {
              workSheet[key].s.font.color = { rgb: '000000' }
              workSheet[key].s.fill.fgColor = { rgb: 'ffffff' }
            }
          }
          // 其它单元格,设置其样式
          else {
            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: false, // 设置单元格自动换行,目前仅对非合并单元格生效
                indent: 0 // 设置单元格缩进
              }
            }
          }
        }
      }
      console.log('workSheet =>', workSheet)
 
      // 八、在工作簿中添加工作表
      XLSX.utils.book_append_sheet(workBook, workSheet, '第一页')
 
      // 九、使用 xlsx-style 写入文件方式,使得自定义样式生效
      const tmpDown = new Blob([
        this.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()
      this.downloadExcelFile(tmpDown, '年度最新爆款商品列表 - ' + formattedDate + '.xlsx')
    },
 
    /**
     * 准备数据
     */
    getExportDataList() {
      const thList = [
        '商品ID',
        '商品名称',
        '商品描述',
        '规格',
        '库存',
        '责任人',
        '是否审核',
        '商品状态',
        '创建时间',
        '修改时间',
      ]
 
      const keyList = [
        'id',
        'name', 
        'desc',
        'spec', 
        'num', 
        'principal', 
        'isApproved', 
        'status', 
        'createTime',
        'updateTime',
      ]
 
      const targetList = [
        {
          id: 1,
          name: '乐事真脆薯条', 
          desc: '百事旗下产品,好吃又好玩!',
          spec: '单位/包', 
          num: 666, 
          principal: '全哥',
          isApproved: '审核已通过', 
          status: 0, 
          createTime: '2023-06-08 22:13:46',
          updateTime: '2023-06-08 22:15:18',
        },
        {
          id: 2,
          name: '干脆面', 
          desc: '味道好极了 ~',
          spec: '单位/箱', 
          num: 123, 
          principal: '全哥',
          isApproved: '审核已通过', 
          status: 1, 
          createTime: '2023-06-08 22:13:46',
          updateTime: '2023-06-08 22:15:18',
        },
        {
          id: 3,
          name: '双汇火腿肠', 
          desc: 'Good!!!',
          spec: '单位/包', 
          num: 666, 
          principal: '全哥',
          isApproved: '审核已通过', 
          status: 2, 
          createTime: '2023-06-08 22:13:46',
          updateTime: '2023-06-08 22:15:18',
        },
        {
          id: 4,
          name: '曲奇饼', 
          desc: '美味。',
          spec: '单位/盒', 
          num: 666, 
          principal: '全哥',
          isApproved: '待审核', 
          status: 1, 
          createTime: '2023-06-08 22:13:46',
          updateTime: '2023-06-08 22:15:18',
        },
        {
          id: 5,
          name: '香飘飘奶茶', 
          desc: 'Yeah',
          spec: '单位/包', 
          num: 666, 
          principal: '全哥',
          isApproved: '审核未通过', 
          status: 2, 
          createTime: '2023-06-08 22:13:46',
          updateTime: new Date(),
        }
      ]
 
      const tdList = this.formatJson(keyList, targetList) // 过滤字段以及转换数据格式,即:表格数据
      tdList.unshift(thList) // 将 thList 数组添加到 tdList 数组开头,即:表格头部
      tdList.unshift(['']) // 将空字符串数组添加到 tdList 数组开头,即:表格首行
      const list = tdList
      return list
    },
 
    /**
     * 过滤字段以及转换数据格式
     */
    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 if (item === 'status') {
          if (v['status'] != null && v['status'] == 0) {
            return '正常'
          } else if (v['status'] != null && v['status'] == 1) {
            return '待上架'
          } else if (v['status'] != null && v['status'] == 2) {
            return '已下架'
          } else {
            return '-'
          }
        }
        else {
          return v[item]
        }
      }))
    },
 
    /**
     * 使用二维数组生成一个工作表
     */
    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 = this.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
    },

    /**
     * 日期转换
     */
    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);
    },
 
    /**
     * 文件流转换
     */
    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 标签下载文件
     */
    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)
    },
  }
}
</script>

四、效果如下 ~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用Vue+xlsx+xlsx-style实现导出自定义样式的Excel文件 的相关文章

  • vim配置全攻略(2)——vim的简单配置

    这篇文章主要讲的是vim的简单配置 相对于vim内置的一些快捷键和功能 vim的客制化才是vim的灵魂 也是vim存活31年仍被热衷的原因 如果你还不了解vim的基本操作和体系 我建议你看一下我上一篇文章 vim配置全攻略 1 vim的基本
  • 【ARM】rk3399挂载nfs报错

    挂载nfs报错mount mnt bad option for several filesystems e g nfs cifs you might need a sbi 实验1 无效 sudo apt get install cifs u
  • 基于Spring Cloud实现日志管理模块

    简介 无论在什么系统中 日志管理模块都属于十分重要的部分 接下来会通过注解 AOP MQ的方式实现一个简易的日志管理系统 思路 注解 标记需要记录日志的方法 AOP 通过AOP增强代码 利用后置 异常通知的方式获取相关日志信息 最后使用MQ
  • Python+Django+Nginx+Uwsgi(史上最全步骤)

    步骤 Python安装 第一步安装Python 很多购买的服务器linux系统中自带python2和python3 我是直接使用自带的python3 5版本的 如果系统中没有 则自己安装 如下 1 下载 wget https www pyt

随机推荐

  • 手写一个解析器

    作者 jolamjiang 腾讯 WXG 前端开发工程师 前言 最近工作中有一些同学在做一些效能工具的时候遇到需要写一门领域相关语言 DSL 及其解析器的场景 笔者恰好有相关的经验向大家指一下北 首先请问一下大家有没有想过这个功能怎么做 点
  • MNIST2_LGB_XGB训练预测

    针对MNIST数据集进行XGB LGB模型训练和预测 部分脚本如下 完整脚本见笔者github lgb param boosting gbdt num iterations 145 num threads 8 verbosity 0 lea
  • 如何实现区块链技术安全?

    随着人们对区块链技术的研究与应用 区块链系统除了其所属信息系统会面临病毒 木马等恶意程序威胁及大规模DDoS攻击外 还将由于其特性而面临独有的安全挑战 区块链可能是安全的 但所有与之交互的软件都是如此 在许多情况下 没有 那么区块链究竟如何
  • 选择排序分析动图演示

    选择排序 思路总结 1 首先在未排序序列中找到最小 大 元素 存放到排序序列的起始位置 2 再从剩余未排序元素中继续寻找最小 大 元素 然后放到已排序序列的末尾 3 重复第二步 直到所有元素均排序完毕 动图演示 代码示例 public st
  • SpringMvc项目配置根据环境自动读取不同的配置文件

    将原有的配置文件拷贝一份 分别放在不同的文件夹内 2 application context xml中读取配置文件的地方 引入变量
  • 小程序实现弹出输入框

    1 微信自带组件 样式 wxml
  • (Chrome42)Lodop页面总提示“未安装”或“请升级”的可能原因和解决办法

    Chrome42之后版本 支持NP插件默认处于关闭状态 要手工打开 方法如下 在谷歌浏览器地址栏输入 chrome flags enable npapi 然后找到 启用NPAPI 地方看到处于启用状态 另外64位Chome不支持js方式访问
  • C语言典型例题八——阶乘

    用递归方法求n 解题思路 求n 可以用递推方法 即从1开始 乘2 再乘3 一直乘到n 这种方法容易理解 也容易实现 递推法的特点是从一个已知的事实 如1 1 出发 按一定规律推出下一个事实 如2 1 2 再从这个新的已知的事实出发 再向下推
  • Acwing 895. 最长上升子序列

    f i 表示所有以第i个数结尾的上升子序列中的最大个数 f i max f j 1 j 0 1 2 i 1 include
  • Openwrt开发笔记(1)—— 开发环境

    OpenWrt简介 OpenWrt 是一个嵌入式设备的 Linux 发行版 以 GPL 许可协议发行 其主要特点有如下几个 代码里不含第三方开源包 只包含开源包地址链接 在编译的时候下载 编译时自动下载源代码 打补丁来满足指定平台要求 并编
  • Oracle生成不重复字符串 sys_guid()

    在oracle8i以后提供了一个生成不重复的数据的一个函数sys guid 一共32位 生成的依据主要是时间和机器码 具有世界唯一性 类似于java中的UUID 都是世界唯一的 其优点就是生成的字符串是唯一的 但其和UUID有同样的弊端 生
  • 论文笔记:FILLING THE G AP S: MULTIVARIATE TIME SERIES IMPUTATION BY GRAPH NEURAL NETWORKS

    0 abstract introduction 之前的补全方法并不能很好地捕获 利用 不同sensor之间的非线性时间 空间依赖关系 高效的时间序列补全方法 不仅应该考虑过去 或者未来 的数值 还应该同时考虑空间上邻近的点的测量值 这里的空
  • 抖音超火的网页表白代码大全(浪漫的html表白源代码)

    精彩专栏推荐 作者主页 进入主页 获取更多源码 web前端期末大作业 HTML5网页期末作业 1000套 程序员有趣的告白方式 HTML七夕情人节表白网页制作 125套 七夕来袭 是时候展现专属于程序员的浪漫了 你打算怎么给心爱的人表达爱意
  • 姿态分析开源工具箱MMPose使用示例:人体姿势估计

    MMPose的介绍及安装参考 https blog csdn net fengbingchun article details 126676309 这里给出人体姿势估计的测试代码 论文 Deep high resolution repres
  • UI自动化测试-selenium元素定位

    在使用Selenium和WebDriver进行UI自动化测试时 我们首先需要对元素定位 那么如何来定位元素呢 HTML 在进行元素定位之前 我们要对html代码有所了解 div class s form div class s form w
  • 【CUDA学习】__syncthreads的理解

    syncthreads 是cuda的内建函数 用于块内线程通信 syncthreads is you garden variety thread barrier Any thread reaching the barrier waits u
  • BUUCTF学习笔记-Secret File

    BUUCTF学习笔记 Secret File 时间 2020 09 28 考点 文件包含 打开页面没发现特别的内容 右键查看源代码才发现下面隐藏了一个a标签只是字体颜色改成了和背景色一样 标签跳转到另外一个页面Archive room ph
  • C语言实现图的邻接矩阵存储结构及深度优先遍历和广度优先遍历

    DFS的核心思想在于对访问的邻接节点进行递归调用 BFS的核心思想在于建立了一个邻接节点的队列 在Dev C 中调试运行通过 用下图进行了测试 include
  • Python里面的[::-1]和[::2]是什么意思

    Python里面的 1 和 2 是什么意思 来源 https docs python org 2 3 whatsnew section slices html L range 10 L 2 0 2 4 6 8 L 1 9 8 7 6 5 4
  • 使用Vue+xlsx+xlsx-style实现导出自定义样式的Excel文件

    本文就是上一篇 使用Python openpyxl实现导出自定义样式的Excel文件 文章中提到的 之前项目的导出Excel文件操作都是在前端完成的 这段话中基于前端实现的导出Excel文件方法 文档地址 https docs sheetj