手动合并多张Excel后,我做了一个合并生成Excel的小工具

年前正准备休假。同事火急火燎的让我帮个忙,我还没反应过来,她已经给我发来了20个Excel文件,打看一看,这是一份统计数据,几张表的数据总量在5万内。同事告诉我,做如下的操作:

  1. 将20张表的数据,汇总到一个表格中。
  2. 为每个表格增加一列,这一列标注来源。
  3. 替换表格内的表头数据,该表头数据根据用户指定的含义设置。

我看了看这20张表,再看看同事紧急的样子,没想太多就搞起来。挨个打开文件,复制粘贴,增加列,修改表头;有的数据列由于信息缺失,在粘贴的时候竟然粘错了位置,于是撤销重来。如此操作,中午的一个半小时就过去了。下午我好奇的问同事,这个工作你做了多久,她回答几乎每月会统计好几次。我说每次都是这样操作吗?她说是的,繁琐的操作让她眼花缭乱,手工操作下来,有时还会丢失数据,她被这种统计工作,折腾的精疲力竭。当即,我就在想能不能做个小的工具来解决这个问题呢?于是我立即罗列了一下涉及到的问题。

  1. 多个文件打开、复制。 -> 读取excel文件。
  2. 对文件增加列 -> 读取文件前的配置操作。
  3. 表头设置 -> 读取文件后,生成文件前的标题配置。
  4. 粘贴、汇总到一张表内 -> 合并数据,并生成excel文件。

整理出来后,其实就是个简单的excel数据读取、合并、生成过程。复工后,用空闲时间把这个小工具搞了下,同事在年后的统计工作中,正式使用了这个小工具,表示很好用,因为将之前的一两小时时间缩短到了一两分钟。那来看下目前的实现效果吧。

手动合并多张Excel后,我做了一个合并生成Excel的小工具

项目实现,基于VUE3框架,插件主要使用了xlsx,VUE3创建项目过程不再赘述,根据官网即可完成,下面主要讲一下文件处理方面的。

一、读取文件

读取文件过程,主要涉及三方面内容,文件选择容器、文件读取插件、文件虚拟滚动。

1.1 文件选择容器

面向用户的操作中,需要给用户提供操作入口,HTML中的input type="file">元素允许用户从当前设备中选择文件。在聊聊web中关于文件的使用,及大文件分片上传的实践一文中对该元素做了详细说明。这里我直接使用Element-Plus的Upload组件给用户提供选择文件的入口。注:该组件本质也是对input type="file">做了封装。

<el-upload
        class="upload-wrapper"
        ref="upload"
        action
        multiple
        accept=".xlsx,.xls"
        :auto-upload="false"
        :on-change="handleFiles"
        :show-file-list="false"
>
    <template #trigger>
        <el-button ref="uploadRef" type="primary">选择文件</el-button>
    </template>
</el-upload>

1.2 xlsx读取文件

当用户选择多个文件后,需要对选择文件依次读取。在讲解具体处理方法前,大家可通过官网来了解excel中的基础概念如何抽象成xlsx的数据模型的。有了前置知识,在使用xlsx的API时,可以有更好的理解。

  1. 读取文件:XLSX.read(data, options)
  2. 读取数据转化为json对象数组:XLSX.utils.sheet_to_json(worksheet, opts)

根据官网API指引,在读取文件操作上,主要需要使用到上面列出的两个API。第一个read接口,可根据数据类型将读取的data数据,解析为对应的工作簿对象。这里的data由FileReader来读取,fileReader提供不同的read方式,不同read接口返回的数据类型不同,如FileReader.readAsArrayBuffer()返回的是读取文件的 ArrayBuffer数据对象,FileReader.readAsDataURL()返回URL格式的 Base64 字符串。XLSX的read接口则根据fileReader的data类型,来选择type。

function readLocalFile(file) {
    const reader = new FileReader()
    reader.readAsArrayBuffer(file.raw) // 按照arraybuffer格式读取,这里的读取方式决定了XLSX read接口的type选择
}

1.3 文件的虚拟滚动

文件在读取过程中,通常是一次性读取整个excel当中的数据,当数据量过大时,会出现页面卡顿、空白的现象,这样会导致页面假死,用户体验差。因此虚拟滚动需要考虑到。虚拟滚动,提供一种优化一次性读取大数据量的方式,通过只展示视窗内的数据,而非全部数据,来提高页面性能。这里选择 vue3-virtual-scroller实现虚拟滚动效果。
注意,该插件在实现虚拟滚动上要求数据提供唯一id,因此在读取数据过程中,会自动生成uuid,来满足该插件的要求。

<div class="table-container">
    <table-head :header="columnDatas" v-if="columnDatas.length" class="header-wrapper"/>
    <RecycleScroller
            class="virtual-table-container"
            :items="tableDatas"
            :item-size="30"
            key-field="EXCEL_READ_UUID" // 唯一UUID
    >
        <template #default="{ item }">
            <table-row :item="item"></table-row>
        </template>
    </RecycleScroller>

二、合并文件

文件读取之后,对文件进行合并。合并相对简单,只需要对文件进行concat即可。

tableDatas.value = tableDatas.value.concat(datas)

三、配置表头

由于在生成文件时,可能会根据用户要求,更改excel表头数据,因此在生成文件前,提供了表头的配置功能。满足个性化的表头配置工作。这里提供的是,根据表头生成的可编辑表格弹窗组件。使用el-form和el-table结合。这里需要做表头数据的转化,稍后把代码都贴上。

<el-form ref="cfgForm">
    <el-table :data="tableResults" border height="560">
        <template v-for="(column, idx) in tableColumns" :key="idx">
            <el-table-column :label="column.name" :prop="column.id">
                <template #header>
                    <span class="required-symbol" v-if="idx === 1">*</span>
                    <span>{{ column.name }}</span>
                </template>
                <template #default="scope">
                    <el-form-item :label="tableResults[scope.$index][column.id]" v-if="!idx"/>
                    <el-form-item :prop="tableResults[scope.$index][column.id]" v-else>
                        <el-input v-model="modelResults[scope.$index][column.id]"></el-input>
                    </el-form-item>
                </template>
            </el-table-column>
        </template>
    </el-table>
</el-form>

四、生成文件

文件读取、表头配置完成后,可以生成文件了,生成文件时,按照excel的文档结构进行构造。

  1. 使用XLSX.utils.json_to_sheet生成工作表。
  2. 使用XLSX.utils.book_new生成工作簿。
  3. 使用XLSX.utils.book_append_sheet将工作表附加到工作簿。
  4. 使用writeFileXLSX将数据导出。

导出的数据会生成配置之后的excel表格。

function exportFile(res) {
    const ws = utils.json_to_sheet(res);
    const wb = utils.book_new();
    utils.book_append_sheet(wb, ws, "Data");
    writeFileXLSX(wb, "导出数据.xlsx");
    tableRes.value = []
}

部分重要代码如下

<!--页面内容-->
<template>
    <el-upload
            class="upload-wrapper"
            ref="upload"
            action
            multiple
            accept=".xlsx,.xls"
            :auto-upload="false"
            :on-change="handleFiles"
            :show-file-list="false">
        <template #trigger>
            <el-button ref="uploadRef" type="primary">选择文件</el-button>
        </template>
        <el-button class="ml-30" type="success" @click="submitUpload" :disabled="!tableDatas.length"> 生成文件</el-button>
        <el-button class="ml-30" type="warning" @click="reset">清空选择</el-button>
        <span class="tip-wrapper ml-30">当前已合并<span>{{ tableDatas.length }}</span>条数据</span>
    </el-upload>
    <div class="table-container">
        <table-head :header="columnDatas" v-if="columnDatas.length" class="header-wrapper"/>
        <RecycleScroller
                class="virtual-table-container"
                :items="tableDatas"
                :item-size="30"
                key-field="EXCEL_READ_UUID">
            <template #default="{ item }">
                <table-row :item="item"></table-row>
            </template>
        </RecycleScroller>
    </div>
    <HeaderCfg :visible="cfg" :tableResults="tableTitleRes" @close="closeCfg"/>
</template>

一次帮同事的小忙,促使我做了这个小工具,从时间成本上来说,帮同事节省了时间。从自身来说,探索了一种新的处理excel文档的方法。有时问题就在那里,就看我们是否想用更有效率的方式去处理。

当然,该小工具还存在不够完善的地方,后续的思路是,完善读取前后的配置工作,比如是否生成特殊列,读取文件后,如何支持数据的过滤操作;针对明显错误的数据,能否高亮识别等。

原文链接:https://juejin.cn/post/7341192656993746955 作者:yinmochunCoder

(0)
上一篇 2024年3月1日 下午5:07
下一篇 2024年3月2日 上午10:00

相关推荐

发表回复

登录后才能评论