【kk-utils】Excel工具——实践篇(一)

前言

kk-utils 是一款我自己基于这几年开发封装出来的前端工具库

excel-js

excel-jskk-utils里的工具之一(1.2.0版本开始支持),其作用是有多个针对导出导出的方法函数,是我在写项目时积累下来的,几乎可以覆盖日常所需,具体文档请查看

今天这篇主要说的是实践应用

实践 ------ 导出【测试导入模版】

实现效果

比如我要做一个导入模版给用户填写数据导入进系统,第一行有说明和必填的红色,第二行是表头,后面的是表体数据,某些单元格还支持下拉选择

实现方法

js 复制代码
import { BiMap } from 'kk-utils-library/bidirectional-mapping';
import { exportExcel } from 'kk-utils-library/excel-js';

// 说明行数据
const instructionData = [
  '必填项\n请选择\n避免手动输入',
  '必填项\n请填写',
  '必填项\n请填写\n1、重复的客户编码将更新该客户的信息\n2、只允许输入英文字母或者阿拉伯数字\n3、编号一旦导入不允许修改',
  '必填项\n请选择\n避免手动输入',
  '必填项\n请填写',
  '必填项\n请填写',
  '必填项\n请填写',
  '非必填项\n请填写'
];

// 表头数据
const tableHeader = [
  '操作类型',
  '客户名称',
  '客户编码',
  '客户类型',
  '省',
  '市',
  '区',
  '备注'
];

// 文本和key的映射关系
const keyMap = new BiMap([
  ['操作类型', 'operationType'],
  ['客户名称', 'customerName'],
  ['客户编码', 'customerCode'],
  ['客户类型', 'customerType'],
  ['省', 'provinceName'],
  ['市', 'cityName'],
  ['区', 'areaName'],
  ['备注', 'remark'],
  ['错误信息', 'errorMessage']
]);

// 必填key
const requireKeys = [
  '操作类型',
  '客户名称',
  '客户编码',
  '客户类型',
  '省',
  '市',
  '区'
];

// 选项数据
const validationData = [
  {
    prop: 'operationType',
    options: [
      {
        label: '新增',
        value: 1
      },
      {
        label: '修改',
        value: 2
      },
      {
        label: '删除',
        value: 3
      },
      {
        label: '不变',
        value: 4
      }
    ],
    transformType: Number,
    label: 'label',
    value: 'value'
  },
  {
    prop: 'customerType',
    options: [
      {
        label: '客户',
        value: 0
      },
      {
        label: '供应商',
        value: 1
      },
      {
        label: '客户与供应商',
        value: 2
      }
    ],
    transformType: Number,
    label: 'label',
    value: 'value'
  }
];

// 表体数据
const bodyData = [
  {
    operationType: 1,
    customerName: '客户名称',
    customerCode: '客户编码',
    customerType: 0,
    provinceName: '省',
    cityName: '市',
    areaName: '区',
    remark: '备注'
  }
];

// 在这里把数组处理成二维数组的格式
function handleGetExportData() {
  return new Promise((resolve) => {
    const result = [
      [...instructionData],
      [...tableHeader],
      ...bodyData.map((item) => {
        const processItem = {
          ...item,
          // 这里要自己把枚举类型的value转成label
          operationType: validationData[0].options.find(
            (option) => option.value === item.operationType
          )?.label,
          // 这里要自己把枚举类型的value转成label
          customerType: validationData[1].options.find(
            (option) => option.value === item.customerType
          )?.label
        };
        return tableHeader.map((label) => {
          const valueKey = keyMap.get(label);
          return processItem[valueKey];
        });
      })
    ];
    // 打印一下result
    // 可以看出最终要的结果就是二维数组,你可以不参照我的自己写方法处理,只要结果是如下就行
    // [
    //   [
    //     '必填项\n请选择\n避免手动输入',
    //     '必填项\n请填写',
    //     '必填项\n请填写\n1、重复的客户编码将更新该客户的信息\n2、只允许输入英文字母或者阿拉伯数字\n3、编号一旦导入不允许修改',
    //     '必填项\n请选择\n避免手动输入',
    //     '必填项\n请填写',
    //     '必填项\n请填写',
    //     '必填项\n请填写',
    //     '非必填项\n请填写'
    //   ],
    //   [
    //     '操作类型',
    //     '客户名称',
    //     '客户编码',
    //     '客户类型',
    //     '省',
    //     '市',
    //     '区',
    //     '备注'
    //   ],
    //   [1, '客户名称', '客户编码', 0, '省', '市', '区', '备注']
    // ];
    resolve(result);
  });
}

// 导出模版
function handleExportTemplate() {
  handleGetExportData().then((data) => {
    exportExcel({
      filename: '测试导入模版',
      sheets: [
        {
          processing: (worksheet) => {
            worksheet.addRows(data);
          },
          extraProcessing: (worksheet) => {
            // 处理单元格的样式
            handleProcessingStyle(worksheet);
            // 处理单元格的额外处理
            handleExtraProcessing(worksheet);
          }
        }
      ]
    });
  });
}

// 处理单元格的样式
function handleProcessingStyle(worksheet) {
  handleProcessingInstructionLineStyle(worksheet);
  handleProcessingTableHeaderLineStyle(worksheet);
}

// 处理说明行的样式
function handleProcessingInstructionLineStyle(worksheet) {
  const compareRowLine = 2; // 对比行 要知道列是否必填就要拿表头行的label去对比
  const rowLine = 1; // 操作行 第一行
  // 无论是否合并单元格 Excel的每一行列数你肯定是知道的 一般都是表头是多少列就是多少列
  for (let i = 1; i <= tableHeader.length; i += 1) {
    const cell = worksheet.getRow(compareRowLine).getCell(i); // 获取表头单元格
    const isRequired = requireKeys.includes(cell.text); // 判断表头是否在必填key里
    if (!isRequired) continue;
    // 填充必填样式
    worksheet.getRow(rowLine).getCell(i).style.fill = {
      type: 'pattern',
      pattern: 'solid',
      fgColor: {
        argb: 'FFFF4949'
      }
    };
  }
}

// 处理表头行的样式
function handleProcessingTableHeaderLineStyle(worksheet) {
  const rowLine = 2; // 操作行 第二行 实际应用中你可以定义变量保存每个模块的行数 这样可以通过相加计算出来而不用写死
  // 无论是否合并单元格 Excel的每一行的列数你肯定是知道的 一般都是表头是多少列就是多少列
  for (let i = 1; i <= tableHeader.length; i += 1) {
    // 填充表头样式
    worksheet.getRow(rowLine).getCell(i).fill = {
      type: 'pattern',
      pattern: 'solid',
      fgColor: {
        argb: 'FF99CCFF'
      }
    };
  }
}

// 处理单元格的额外处理 比如下拉框啊 数字金额格式化等这些
function handleExtraProcessing(worksheet) {
  const instructionLineLength = 1; // 说明行的行数
  const tableHeaderLineLength = 1; // 表头行的行数
  const tableBodyLineLength = bodyData.length; // 表体行的行数
  const tableBodyLineStart =
    1 + instructionLineLength + tableHeaderLineLength; // 表体开始行
  const tableBodyLineEnd = tableBodyLineStart + tableBodyLineLength - 1; // 表体结束行
  // 遍历所有选项数据,给对应单元格设置
  for (let i = 0; i < validationData.length; i += 1) {
    const { prop, options, label } = validationData[i];
    const valueKey = keyMap.get(prop);
    const listLabel = options.map((item) => item.label).join(',');
    // 表体每一行的对应单元格都要设置 所以从表体开始行遍历到结束行
    for (let j = tableBodyLineStart; j <= tableBodyLineEnd; j += 1) {
      worksheet
        .getRow(j)
        .getCell(tableHeader.indexOf(valueKey) + 1).dataValidation = {
        type: 'list',
        allowBlank: true,
        formulae: [`"${listLabel}"`]
      };
    }
  }
}

// 执行导出 就可以看到Excel下载好了 打开就是开头的效果
handleExportTemplate();

实践 ------ 导入【测试导入模版】填写的数据

实现效果

这是使用上面导出的模版填写的数据

这是导入后拿到的数组对象

实现方法

先写一个input拿文件和导入按钮

html 复制代码
<input type="file" id="file" accept=".xlsx" />
<button id="import">导入所选文件</button>
js 复制代码
import { BiMap } from 'kk-utils-library/bidirectional-mapping';
import { importExcel } from 'kk-utils-library/excel-js';

const fileInput = document.getElementById('file');
const importButton = document.getElementById('import');
importButton.addEventListener('click', () => {
  const file = fileInput.files[0];
  if (!file) {
    alert('请选择文件');
    return;
  }
  importExcel(file, {
    headerStartLine: 2, // 因为第一行是说明行,所以表头要设置参数从第二行开始
    headerTotalLine: 1 // 总共一行表头 可以不写 默认是1
  }).then((data) => {
    console.log('data', data);
    // 注意了 因为Excel是多工作表的 所以即使我们只要第一个工作表 返回的data还是嵌套多了一层数组
    // [
    //   [
    //     {
    //       操作类型: '新增',
    //       客户名称: '测试1',
    //       客户编码: 'TEST001',
    //       客户类型: '供应商',
    //       省: '辽宁省',
    //       市: '沈阳市',
    //       区: '辽中县'
    //     },
    //     {
    //       操作类型: '修改',
    //       客户名称: '测试2',
    //       客户编码: 'TEST002',
    //       客户类型: '客户',
    //       省: '山西省',
    //       市: '太原市',
    //       区: '晋源区'
    //     },
    //     {
    //       操作类型: '删除',
    //       客户名称: '测试3',
    //       客户编码: 'TEST003',
    //       客户类型: '客户与供应商',
    //       省: '吉林省',
    //       市: '长春市',
    //       区: '宽城区'
    //     },
    //     {
    //       操作类型: '不变',
    //       客户名称: '测试4',
    //       客户编码: 'TEST004',
    //       客户类型: '供应商',
    //       省: '湖南省',
    //       市: '长沙市',
    //       区: '雨花区'
    //     }
    //   ]
    // ];
  });
});

因为传给后台接收的key不可能是中文,所以我们还要使用前面定义的keyMap把中文key转成后台要的key值才能传给后台写入数据库

js 复制代码
import { BiMapConversion } from 'kk-utils-library/bidirectional-mapping'

function handleGetImportData(data) {
  return new Promise((resolve) => {
    const result = data.map((list) => {
      return list.map((item) => {
        const mapData = BiMapConversion(item, keyMap);
        // 因为选项是中文的,要使用选项数据反向处理回对应的value传给后台
        validationData.forEach((el) => {
          const { prop, options, label, value } = el;
          const targetValue = options.find([label, mapData[prop]])?.[
            value
          ];
          (targetValue || target === 0) && (mapData[prop] = targetValue);
        });
        return mapData;
      });
    });
    resolve(result);
  });
}

把importExcel拿到的data传入,就拿到映射后的数据啦

js 复制代码
handleGetImportData(data).then(res=>{
  console.log('res', res);
});
相关推荐
蓝婷儿5 小时前
前端面试每日三题 - Day 32
前端·面试·职场和发展
星空寻流年6 小时前
CSS3(BFC)
前端·microsoft·css3
九月TTS6 小时前
开源分享:TTS-Web-Vue系列:Vue3实现固定顶部与吸顶模式组件
前端·vue.js·开源
CodeCraft Studio6 小时前
数据透视表控件DHTMLX Pivot v2.1发布,新增HTML 模板、增强样式等多个功能
前端·javascript·ui·甘特图
一把年纪学编程6 小时前
【牛马技巧】word统计每一段的字数接近“字数统计”
前端·数据库·word
llc的足迹6 小时前
el-menu 折叠后小箭头不会消失
前端·javascript·vue.js
九月TTS7 小时前
TTS-Web-Vue系列:移动端侧边栏与响应式布局深度优化
前端·javascript·vue.js
Johnstons7 小时前
AnaTraf:深度解析网络性能分析(NPM)
前端·网络·安全·web安全·npm·网络流量监控·网络流量分析
whatever who cares7 小时前
CSS3 伪元素(Pseudo-elements)大全
前端·css·css3
若愚67927 小时前
前端取经路——性能优化:唐僧的九道心经
前端·性能优化