打印
[活动]

用node帮老婆做excel工资表

[复制链接]
797|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
科叼|  楼主 | 2024-7-16 17:33 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
我是天元,立志做1000个有趣的项目的前端。

顺便吆喝一声,技术大厂,内推捞人,前/后端or测试←感兴趣
要求学历:全日制统招本科(非学院派即可):
--加班偶尔较多,但周末加班两倍工资。
--15-35K,工资在一线城市属于一般,但二线城市很可以。

背景
我老婆从事HR的工作,公司有很多连锁店,她需要将所有的门店的工资汇总计算,然后再拆分给各门店请确认,最后再提供给财务发工资。
随着门店数量渐渐增多,渐渐的我老婆已经不堪重负,每天加班都做不完,严重影响夫妻感情生活。
最终花费了2天的时间,完成了整个node程序,她只需要传入工资表,相应的各种表格在10s内自动输出。目前已正式交付,得到了每月零花钱提高100元的重大成果。
整体需求
  • 表格的导入和识别
  • 表格的计算(计算公式要代入),表格样式正确
  • 最终结果按照门店拆分为工资表
需求示例(删减版)


需求为,根据传入的基本工资及补发补扣,生成总工资表,门店工资表,财务工资表发放表。
工资表中字段为门店,姓名,基本工资,补发补扣,最终工资(基本工资+补发补扣)。最后一行为总计
门店工资表按照每个门店,单独一个表格,字段同工资表。最后一行为总计
工资表

基础工资

补发补扣

技术选型
这次的主力库为exceljs,官方文档介绍如下
读取,操作并写入电子表格数据和样式到 XLSX 和 JSON 文件。
一个 Excel 电子表格文件逆向工程项目
选择exceljs是因为它支持完整的excel的样式及公式。
安装及目录结构
优先安装exceljs
npm inityarn add exceljs
创建input,out,src三个文件夹,src放入index.js


package.json增加start脚本
"scripts": {    "start": "node src/index.js"  },代码相关导入
通过new Excel.Workbook();创建一个工作簿,通过workbook.xlsx.readFile来导入文件, 注意这是个promise
const ExcelJS = require("exceljs");const path = require("path");const inputPath = path.resolve(__dirname, "../input");const outputPath = path.resolve(__dirname, "../out");const loadInput =async () => {  const workbook = new ExcelJS.Workbook();  const inputFile = await workbook.xlsx.readFile(inputPath + "/工资表.xlsx")};loadInput()数据拆分
通过getWorksheetApi,我们可以获取到对应的工作表的内容
  const loadInput =async () => {        ...  // 基本工资  const baseSalarySheet = inputFile.getWorksheet("基本工资");  // 补发补扣  const supplementSheet = inputFile.getWorksheet("补发补扣");}
然后我们需要进一步的来进行拆分,因为第一行为每个工作表的头,这部分在我们实际数据处理中不会使用,所以通过getRows来获取实际的内容。
  const baseSalaryContent = baseSalarySheet.getRows(    2,    baseSalarySheet.rowCount  );  baseSalaryContent.map((row) => {    console.log(row.values);  });  /**[ <1 empty item>, '2024-02', '海贼王', '路飞', 12000 ][ <1 empty item>, '2024-02', '海贼王', '山治', 8000 ][ <1 empty item>, '2024-02', '火影忍者', '鸣人', '6000' ][ <1 empty item>, '2024-02', '火影忍者', '佐助', 7000 ][ <1 empty item>, '2024-02', '火影忍者', '雏田', 5000 ][ <1 empty item>, '2024-02', '一拳超人', '琦玉', 4000 ][][]**/
可以看到实际的内容已经拿到了,我们要根据这些内容拼装一下最终便于后续的调用。
我们可以通过 row.getCellApi获取到对应某一列的内容,例如门店是在B列,那么我们就可以使用row.getCell('B')来获取。
因为我们需要拆分门店,所以这里的基本工资,我们以门店为单位,把数据进行拆分
  const baseSalary = {};  baseSalaryContent.forEach((row) => {    const shopName = row.getCell("B").value;    if (!shopName) return; // 过滤空行    const name = row.getCell("C").value;    const salary = row.getCell("D").value;    if (!baseSalary[shopName]) {      baseSalary[shopName] = [];    }    baseSalary[shopName].push({      name,      salary,    });  });
这样我们得到了一个以门店名称为key的对象,value为该门店的员工信息数组。利用相同方法,获取补发补扣。因为每个人已经确定了门店,所以后续只需要根据姓名来做key,拆分成一个object即可
  // 补发补扣  const supplement = {};  supplementSheet.getRows(2, supplementSheet.rowCount).forEach((row) => {    const name = row.getCell("C").value;    const type = row.getCell("H").value;    let count = row.getCell("D").value;    // 如果为补扣,则金额为负数    if (type === "补扣") {      count = -count;     }    if (!supplement[name]) {      supplement[name] = 0;    }    supplement[name] += count;  });数据组合门店工资表
因为每个门店需要独立一张表,所以需要遍历baseSalary
    Object.keys(baseSalary).forEach((shopName) => {    const workbook = new ExcelJS.Workbook();    const worksheet = workbook.addWorksheet("工资表");    // 添加表头    worksheet.addRow([      "序号",      "门店",      "姓名",      "基本工资",      "补发补扣",      "最终工资",    ]);    baseSalary[shopName].forEach((employee, index) => {      worksheet.addRow([        index + 1,        shopName,        employee.name,        +employee.salary,        supplement[employee.name] || 0,        +employee.salary + (supplement[employee.name] || 0),      ]);    });  });
此时你也可以快进到表格输出来查看输出的结果,以便随时调整
这样我们就把基本工资已经写入工作表了,但是这里存在问题,最终工资使用的是一个数值,而没有公式。所以我们需要改动下
worksheet.addRow([        index + 1,        shopName,        employee.name,        employee.salary,        supplement[employee.name] || 0,        {          formula: `D${index + 2}+E${index + 2}`,          result: employee.salary + (supplement[employee.name] || 0),        },      ]);
这里的formula将对应到公式,而result是显示的值,这个值是必须写入的,如果你写入了错误的值,会在表格中显示该值,但是双击后,公式重新计算,会替换为新的值。所以这里必须计算正确
合计
依照上方的逻辑,继续添加一行作为合计,但是之前计算的时候,需要添加一个临时变量,记录下合计的相关内容。
     const count = [0, 0, 0];    baseSalary[shopName].forEach((employee, index) => {      count[0] += +employee.salary;      count[1] += supplement[employee.name] || 0;      count[2] += +employee.salary + (supplement[employee.name] || 0);      worksheet.addRow([        index + 1,        shopName,        employee.name,        +employee.salary,        supplement[employee.name] || 0,        {          formula: `D${index + 2}+E${index + 2}`,          result: +employee.salary + (supplement[employee.name] || 0),        },      ]);    });
然后在尾部添加一行
worksheet.addRow([      "合计",      "",      "",      {        formula: `SUM(D2:D${baseSalary[shopName].length + 1})`,        result: count[0],      },      {        formula: `SUM(E2:E${baseSalary[shopName].length + 1})`,        result: count[1],      },      {        formula: `SUM(F2:F${baseSalary[shopName].length + 1})`,        result: count[2],      },    ]);美化
表格的合并,可以使用mergeCells
   worksheet.mergeCells(      `A${baseSalary[shopName].length + 2}:C${baseSalary[shopName].length + 2}`    );
这样就合并了我们的最后一行的前三列,接下来我们要给表格添加线条。
对于批量的添加,可以直接使用addConditionalFormatting,它将在一个符合条件的单元格范围内添加规则
    worksheet.addConditionalFormatting({      ref: `A1:F${baseSalary[shopName].length + 2}`,      rules: [        {          type: "expression",          formulae: ["true"],          style: {            border: {              top: { style: "thin" },              left: { style: "thin" },              bottom: { style: "thin" },              right: { style: "thin" },            },            alignment: { vertical: "top", horizontal: "left", wrapText: true },          },        },      ],    });表格输出
现在门店工资表已经拆分完成,我们可以直接保存了,使用xlsx.writeFileApi来保存文件
Object.keys(baseSalary).forEach((shopName) => {         ...          workbook.xlsx.writeFile(outputPath + `/${shopName}工资表.xlsx`);})最终效果



相关代码地址
github.com/tinlee/1000…

使用特权

评论回复

相关帖子

沙发
天意无罪| | 2024-7-16 18:43 | 只看该作者
这广告植入无违和感,就是正文内容排版看起来有点头大,可以改进一下。

使用特权

评论回复
板凳
sun2152| | 2024-7-17 08:02 | 只看该作者
有种在CSDN看推文的感觉

使用特权

评论回复
地板
dirtwillfly| | 2024-7-17 08:25 | 只看该作者
代码都乱了,没法看阿。建议楼主再整理整理,这样更好推广你的广告

使用特权

评论回复
5
[鑫森淼焱垚]| | 2024-7-17 09:28 | 只看该作者
发帖子的不像是一个程序员

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

120

主题

129

帖子

1

粉丝