vue导出Excel(vue2,插件:file-saver、xlsx、)

1.插件的安装和用途

安装

npm i file-saver (版本2.0.5)
npm i xlsx (版本0.16.0)
npm i xlsx-style (版本0.8.13)

file-saver

用途:

这个插件主要用于在浏览器中保存文件。它允许你将数据保存为文件,并提示用户下载这些文件。

功能:

  • 生成 Blob(大型对象)并保存为文件。
  • 支持多种文件类型,如文本文件、图像文件、PDF 文件等。
  • 浏览器兼容性好,适用于几乎所有现代浏览器。

xlsx

用途:

这个插件是一个流行的 JavaScript 库,用于解析、操作和生成 Excel 文件.xlsx.xls 等)。它可以将 JSON、数组等数据转换为 Excel 文件,也可以解析现有的 Excel 文件并提取数据。

功能:

  • 读取和写入 Excel 文件。
  • 支持多种数据格式(如 CSV、ODS)。
  • 提供丰富的功能来操作工作表、单元格、样式等。

xlsx-style

用途:

xlsx-stylexlsx 插件的一个扩展,用于增强 Excel 文件中样式的支持。虽然 xlsx 可以生成 Excel 文件,但它对单元格样式(如字体、颜色、对齐方式等)的支持较弱,而 xlsx-style 通过扩展 xlsx 的功能,使得你可以在生成 Excel 文件时应用更复杂的样式。

功能:

  • 支持更多样式选项,如单元格背景颜色、字体颜色、加粗、对齐方式等。
  • 在生成 Excel 文件时,可以控制每个单元格的外观。

2.需求

页面有一个表格,需要导出EXCEl表(用XLSX和XLSXS制作了样式之类的表格),格式如下:

1.颜色三种

2.下面表格的字体是粗体:附、A-Z、一二三四五六七八九十,其余内容为正常字体

3.一些问题

问题1:因为使用element,而且页面是有滚动的,所以在表格的右侧还会出现几个多余的单元格,导致后面导出表格出错:

在生成sheet时输出这个div:发现多出的单元格都带属性:flutter

方法一:可以先把这些单元格删除掉

let gutters = div.querySelectorAll('.gutter');
    gutters.forEach(gutter => {
        gutter.remove();
    });

方法二:修改他们的样式 边框设置为默认色

if (word > 'D') {
                    data[key].s.border = {
                        top: {
                            style: 'thin',
                            color: { rgb: 'e6e6e6' }
                        },
                        bottom: {
                            style: 'thin',
                            color: { rgb: 'e6e6e6' }
                        },
                        left: {
                            style: 'thin',
                            color: { rgb: '000000' }
                        },
                        right: {
                            style: 'thin',
                            color: { rgb: 'e6e6e6' }
                        }
                    }
                }

问题2:表格因为使用element制作,合并的表头转为sheet后,缺失格子,需要补齐:

1.创建新的工作簿(工作簿是一个 Excel 文件,可以包含一个或多个工作表(Sheet))

const book = XLSX.utils.book_new()

2.将 HTML 表格转换为工作表(Worksheet,Excel 工作表对象 sheet

const sheet = XLSX.utils.table_to_sheet(div)

3.将工作表添加到工作簿中,命名为 'Sheet1'

XLSX.utils.book_append_sheet(book, sheet, 'Sheet1')

4.因为表头合并转为sheet工作表时确实格子,要补齐格子

sheet.B1 = sheet.B2 = sheet.C1 = sheet.C2 = sheet.D1 = sheet.D2 = {}

const book = XLSX.utils.book_new()
const sheet = XLSX.utils.table_to_sheet(div)
XLSX.utils.book_append_sheet(book, sheet, 'Sheet1')
sheet.B1 = sheet.B2 = sheet.C1 = sheet.C2 = sheet.D1 = sheet.D2 = {}

 转化为sheet 除了表格,还有几个属性

1. !cols: []
2. !fullref: "A1:E36"
3. !merges: (2) [{…}, {…}]
4. !ref: "A1:E36"
5. !rows: []

以下是解析:

1. !cols
定义: 这是一个数组,用于定义每一列的宽度。
作用: 你可以通过在数组中设置每一列的宽度(以字符数为单位)来控制生成的 Excel 文件中每列的显示宽度。
例子: 如果 !cols 为 [{wch: 10}, {wch: 20}],则第一列宽度为10字符,第二列宽度为20字符。
2. !fullref
定义: 这个属性描述了整个工作表的引用范围(即工作表中所有使用的单元格区域)。
作用: 它表示工作表中实际使用的单元格区域,从左上角到右下角。
例子: "A1:E36" 意味着工作表中有效数据的区域从 A1 到 E36。
3. !merges
定义: 这是一个数组,包含多个合并单元格的描述。
作用: 每个元素是一个对象,描述了一个合并单元格的区域。包括起始单元格(s)和结束单元格(e)。
例子: [{s: {r: 0, c: 0}, e: {r: 0, c: 3}}, {s: {r: 1, c: 0}, e: {r: 1, c: 3}}] 表示 A1:D1 和 A2:D2 被合并。
4. !ref
定义: 表示当前表格实际使用的单元格范围。
作用: 与 !fullref 类似,但它专注于已定义的单元格范围。通常,这与 !fullref 一致,或者是 !fullref 的子集。
例子: "A1:E36" 表示表格的有效数据区域为 A1 到 E36。
5. !rows
定义: 这是一个数组,用于定义每一行的高度或其他样式属性。
作用: 可以通过在数组中设置每一行的高度(以点为单位)来控制生成的 Excel 文件中每行的显示高度。
例子: 如果 !rows 为 [{hpt: 20}, {hpt: 30}],则第一行的高度为20点,第二行的高度为30点。

我使用了 !merges 合并单元格:

data['!merges'] = [{ s: { r: 0, c: 0 }, e: { r: 0, c: 3 } }, { s: { r: 1, c: 0 }, e: { r: 1, c: 3 } }];合并了几个单元格

  • s: { r: 0, c: 0 }: 起始单元格位于第 0 行第 0 列,即 Excel 中的 A1 单元格。
  • e: { r: 0, c: 3 }: 结束单元格位于第 0 行第 3 列,即 Excel 中的 D1 单元格。
  • s: { r: 0, c: 0 }e: { r: 0, c: 3 } 说明将 A1D1 单元格合并为一个单元格。

4).使用 FileSaver.saveAs(插件:file-saver) ,将已经生成的 Excel 文件保存到用户的本地计算机上

let file_name = 'xxx报表.xlsx'
    if (name) {
        file_name = name + '.xlsx'
    }

    //尝试将当前table内容保存为excel文件
    try {
        FileSaver.saveAs(
            //被导出的blob二进制对象 [wbout]
            new Blob([s2ab(wbout)], { type: "application/octet-stream" }),
            //导出文件的名称+后缀名
            file_name
        );
    } catch (e) {
        if (typeof console != "undefined") console.log(e, wbout);
    }
function s2ab(s) {
    var buf = new ArrayBuffer(s.length);
    var view = new Uint8Array(buf);
    for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
    return buf;
}

s2ab 函数的作用是将一个字符串转换为二进制格式,以便进一步处理或保存为文件。

注:new Blob([s2ab(wbout)], { type: "application/octet-stream" }) 解析:
1. Blob:Blob (Binary Large Object) 是一个表示二进制数据的大对象,通常用于存储或传输文件。这里的 Blob 对象将存储你想要导出的 Excel 文件内容。
2. s2ab(wbout):s2ab 是一个将字符串转换为 ArrayBuffer 的辅助函数,它将工作簿数据 wbout 转换为一个 ArrayBuffer,以便能够创建 Blob 对象。wbout 是之前生成的 Excel 工作簿的二进制内容。
3. { type: "application/octet-stream" }:这是一个 MIME 类型,表示要保存的文件是一个二进制流(未加工的二进制数据),通常用于下载文件。

s2ab 函数的作用是将一个字符串转换为二进制格式,以便进一步处理或保存为文件。

5.总体代码

父组件:获取id为pdfDom的HTML,点击导出excel

注:这里使用了element的table,而且表格是嵌套的,有一级二级三级(树形数据)

<div id="pdfDom" ref="printSection">
      <el-table
        max-height="550px"
        empty-text="暂无报告"
        v-loading="loading"
        :data="staffJieyuList"
        @selection-change="handleSelectionChange"
        default-expand-all
        row-key="row"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
      >
        <el-table-column align="center" :label="subTitle">
          <el-table-column :label="searchInformation.lysuoName">
            <el-table-column prop="id" align="center" label="序号">
            </el-table-column>
            <el-table-column prop="name" align="center" label="项目">
            </el-table-column>
            <el-table-column prop="amount" align="center" label="金额">
            </el-table-column>
            <el-table-column prop="contractNum" align="center" label="备注">
            </el-table-column>
          </el-table-column>
        </el-table-column>
      </el-table>
    </div>
<el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="el-icon-download"
          size="mini"
          @click="btnEXCELClick"
          v-hasPermi="['lawerfin:staffJieyu:export']"
          >导出EXCEL
        </el-button>
      </el-col>
btnEXCELClick() {
      exportTableAsXLSX("pdfDom", "律师结算报表", [
        { wch: 5 },
        { wch: 70 },
        { wch: 10 },
        { wch: 40 },
      ]);
    },

子组件:处理HTML的结构、给excel添加样式、 导出excel

//导入依赖项
import FileSaver from "file-saver";
import XLSX from "xlsx";
import XLSXS from 'xlsx-style'

/**
 * elemet-ui el-table数据导出为xlsx表格
 * @param {*} _targetId Element-UI el-table组件的id值
 * @param {*} name 导出文件名
 */
export const exportTableAsXLSX = function (_targetId, name, wscols = []) {
    let table = document.getElementById(_targetId)
    if (!table) return
    let div = document.createElement('div');
    div.innerHTML = table.innerHTML;
    // 移除掉左右的fixed
    let fixedLeft = div.querySelector('.el-table__fixed');
    if (fixedLeft) fixedLeft.remove();
    let fixedRight = div.querySelector('.el-table__fixed-right');
    if (fixedRight) fixedRight.remove();

    //表格在页面滚动,导出表格右边多出了几个多余的单元格,要删掉 或者使用下方的 if (word > 'D'){}处理
    // let gutters = div.querySelectorAll('.gutter');
    // gutters.forEach(gutter => {
    //     gutter.remove();
    // });
    console.log('this--table--',div);

    const book = XLSX.utils.book_new()
    const sheet = XLSX.utils.table_to_sheet(div)
    XLSX.utils.book_append_sheet(book, sheet, 'Sheet1')
    sheet.B1 = sheet.B2 = sheet.C1 = sheet.C2 = sheet.D1 = sheet.D2 = {}
    setExcelStyle(sheet, wscols)
    let wbout = XLSXS.write(book, {
        bookType: 'xlsx',
        bookSST: false,
        type: 'binary'
    })

    let file_name = 'xx结算报表.xlsx'
    if (name) {
        file_name = name + '.xlsx'
    }

    //尝试将当前table内容保存为excel文件
    try {
        FileSaver.saveAs(
            //被导出的blob二进制对象 [wbout]
            new Blob([s2ab(wbout)], { type: "application/octet-stream" }),
            //导出文件的名称+后缀名
            file_name
        );
    } catch (e) {
        if (typeof console != "undefined") console.log(e, wbout);
    }
};

// 设置导出Excel样式(统一样式)
function setExcelStyle(data, wscols = [], wpx = 80) {
    data["!cols"] = wscols
    // data['!merges'] =  // 合并前三行   前6列 标题
    data['!merges'] = [{ s: { r: 0, c: 0 }, e: { r: 0, c: 3 } },
    { s: { r: 1, c: 0 }, e: { r: 1, c: 3 } }];
    const excludes = ['!cols', '!fullref', '!merges', '!ref', '!rows']
    let fillColorCell = []
    let amassMonthYear = ['本月合计', '本年累计', '本年合计']
    let fontBoldCell = [] //字体加粗的格子
    let fontLeftCell =[] //字体靠左的项目内容
    // 匹配大写字母、大写阿拉伯数字或者特定的中文字符的正则表达式
    const pattern = /^(附|[A-Z]|[一二三四五六七八九十])+$/;
    //除了1-3行,还有下面匹配的这些 文字居中,项目的内容要往左
    const pattern2 = /^([一二三四五六七八九十])+$/;
    // 匹配需要增加背景色的格子
    for (let item in data) {
        if (item.startsWith("A")) {
            // console.log('item----data',item,data[item]);
            if (pattern.test(data[item].v)) {
                // 符合要求的逻辑处理
                // console.log("数据符合要求--", data[item].v, item);
                fillColorCell.push(Number(item.slice(1))) // [4, 9, 15, 16, 22, 35, 40, 43, 44, 47, 55, 60, 61, 62, 63, 64]
            }
        }
    }
    //匹配需要加粗字体的格子
    for (let item in data) {
        if (pattern.test(data[item].v) || amassMonthYear.includes(data[item].v)) {
            // 符合要求的逻辑处理
            fontBoldCell.push(Number(item.slice(1))) 
        }
    }
    //字体靠左的项目内容
    for (let item in data) {
        if (pattern2.test(data[item].v)) {
            fontLeftCell.push(Number(item.slice(1)))
        }
    }
    for (let key in data) {
        if (data.hasOwnProperty(key)) {
            if (!excludes.includes(key)) {
                let num = Number(key.slice(1))
                let word = key.slice(0, 1)
                data[key].s = {
                    alignment: {
                        horizontal: "center", //水平居中对齐
                        vertical: "center", // 垂直居中
                        wrapText: true,
                    },
                    border: {
                        top: {
                            style: 'thin',
                            color: { rgb: '000000' }
                        },
                        bottom: {
                            style: 'thin',
                            color: { rgb: '000000' }
                        },
                        left: {
                            style: 'thin',
                            color: { rgb: '000000' }
                        },
                        right: {
                            style: 'thin',
                            color: { rgb: '000000' }
                        }
                    },
                    fill: {
                        fgColor: { rgb: "00a2ff" },
                    },
                    font: {
                        sz: 12,
                    },
                    bold: true,
                    numFmt: 0
                }
                //E开始的单元格,多出了几个表格,设置边框消失
                // if (word > 'D') {
                //     data[key].s.border = {
                //         top: {
                //             style: 'thin',
                //             color: { rgb: 'e6e6e6' }
                //         },
                //         bottom: {
                //             style: 'thin',
                //             color: { rgb: 'e6e6e6' }
                //         },
                //         left: {
                //             style: 'thin',
                //             color: { rgb: '000000' }
                //         },
                //         right: {
                //             style: 'thin',
                //             color: { rgb: 'e6e6e6' }
                //         }
                //     }
                // }

                // 根据不同行添加单元格背景颜色
                let color = ''
                if ((fillColorCell.includes(num) || num == 1 || num == 2 || num == 3) && word < 'E') {
                    color = 'd9e1f4'
                    data[key].s.alignment = {
                        horizontal: "center", //居中对其
                        vertical: "center", // 垂直居中
                        wrapText: true,
                    }
                    data[key].s.font = {
                        sz: 12,
                        // bold: true,
                    }
                } else {
                    color = 'ffffff'
                }
                // 最三面三行标题设置不同的颜色
                if (num < 4 && word < 'E') {
                    color = '91aadf'
                }
                //字体加粗
                if((fontBoldCell.includes(num) || num == 1 || num == 2 || num == 3) && word < 'E'){
                    data[key].s.font = {
                        sz: 12,
                        bold: true,
                    }
                }
                if(((!fontLeftCell.includes(num) && num !== 1 && num !== 3) && word == 'B') || num == 2){
                    data[key].s.alignment = {
                        horizontal: "left", //左对其
                        vertical: "center", // 垂直居中
                        wrapText: true,
                    }
                }
                //设置千分位,调用excel格式,0还是0
                if(!isNaN(parseFloat(data[key].v)) && Math.abs(parseFloat(data[key].v)) > 0){
                    // data[key].v = parseFloat(data[key].v).toLocaleString() + ''
                    data[key].s.numFmt = '#,##0.00_-'
                  }
                data[key].s.fill = { fgColor: { rgb: color, patternType: 'solid' } }
                // data[key].s.border = { bottom: {style: 'thin', color: { rgb: '000000' }} }
            }
        }
    }
}
function s2ab(s) {
    var buf = new ArrayBuffer(s.length)
    var view = new Uint8Array(buf)
    for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
    return buf
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值