使用xlsx完成js读取表格数据并转换成对象格式

70 阅读5分钟

接说上文、咱们的antv图表已经配置完,接下来看如何把本地的一个表格文件数据读取出来,转换成咱们想要的一种对象格式呢?

首先介绍一下我们的依赖,图形这一部分还是使用上一章中我们的 antv 来实现的, 框架我自己使用的是 vite + vue3 搭的一个 demo, 表格转换需要使用 xlsx 为什么使用 xlsx 因为其适配性 及 通用性香蕉而言都是比较高的,可以看一下下图

iShot_2022-10-31_10.18.54.png

  • 下载依赖
npm install xlsx 
  • 模板

这里我使用的是vue, 展示了两种上传方案,一种是原生的 input, 另一种是使用 el 的组件, 这里我直接在回调中 把我们需要用的 file 对象提取出来穿进去了, 就可以使用同一个函数而不在函数中增加逻辑判断,大家也更能直观的看到, 原生 和 el 封装的对象的区别了 下面的 container 容器,是我们上一篇描述的 antv 的渲染容器

<template>
  <div class="page1">
    <div>
      <input
        type="file"
        :onchange="(event) => beforeUpload(event.target.files[0])"
      />
    </div>
    <el-upload
      ref="excelRef"
      :auto-upload="false"
      :show-file-list="false"
      :on-change="(event) => beforeUpload(event.raw)"
    >
      <el-button type="primary">上传表格</el-button>
    </el-upload>
    <div class="page1_warp" id="container"></div>
  </div>
</template>

这里我们拿到的对象应该长下面这个样子:

iShot_2022-10-31_10.21.13.png

  • 引入 xlsx 包, 进行数据的读取, 数据读取这里我们使用 FileReader 以二进制的方式读取 FileReader提供了如下方法:
  1. readAsArrayBuffer(file) 按字节读取文件内容,结果用ArrayBuffer对象表示
  2. readAsBinaryString(file) 按字节读取文件内容,结果为文件的二进制串
  3. readAsDataURL(file) 读取文件内容,结果用data:url的字符串形式表示
  4. readAsText(file,encoding) 按字符读取文件内容,结果用字符串形式表示
  5. abort() 终止文件读取操作
import * as XLSX from "xlsx";
const excelRef = ref(null);
const renderData = ref([])
// 这个是表格的表头字段映射, 需要在我们的数据处理方法中使用
const excelNameToKey = {
  任务id: "id",
  停站时间: "waitTime",
  充电时间点: "rechargeTime",
  出发站: "start",
  到达时间: "endTime",
  到达站: "end",
  单程点: "runningTime",
  发车时间: "startTime",
  司机: "driver",
  燃料类型: "fuel",
  线路: "line",
  车号: "carNum",
  车辆型号: "carModel",
};
// 读取表格数据
const readerExcel = (file) => {
  const fileReader = new FileReader();

  // 以二进制的方式读取表格内容
  fileReader.readAsBinaryString(file);

  // 表格内容读取完成
  fileReader.onload = (event) => {
    try {
      const fileData = event.target.result;
      const workbook = XLSX.read(fileData, {
        type: "binary",
      });

      // 表格是有序列表,因此可以取多个 Sheet,这里取第一个 Sheet
      const wsname = workbook.SheetNames[0];
      // 将表格内容生成 json 数据
      const sheetJson = XLSX.utils.sheet_to_json(workbook.Sheets[wsname]);

      // 标准化 JSON 数据
      const changeData = excelDataToJson(sheetJson,excelNameToKey);
      renderData.value = handleData(changeData);
    } catch (e) {
      console.log(e);
      return false;
    }
  };
};

const clearFile = () => {
  excelRef.value.value = "";
};

const beforeUpload = (files) => {
  console.log("上传的文件", files);
  // 读取文件内容
  readerExcel(files);
  // 清除数据
  clearFile();
};
  • 上面的数据处理 我提取出来在另一个js文件中

因为表格当中,有可能没有表头,也有可能有表头,这需要根据表格的字段来进行匹配,我这个表格的字段是 algorithm_uuid , 你们的表格可能就不是这样了, 需要大家自己对照一下, 如下图中所示,有表头的话,第一条数据就是表头的字段映射了,和下面每一项的数据对照一下就能看出来区别了

还需要使用 toKey ,也就是传入的第二个参数, 用来遍历我们每一组的key值

iShot_2022-10-31_10.26.55.png

// 标准化 JSON 数据
export const excelDataToJson = (sheetJson,toKey) => {
  const excelData = []
  if (!sheetJson.length) return;
  let result = sheetJson;
  //TODO 根据自己输出表格的字段进行首行判断
  const hasTableHead = !sheetJson[0]?.algorithm_uuid;

  // 拥有表头的数据,需要取正确的值
  if (hasTableHead) {
    const header = sheetJson.shift();
    const data = [];
    Object.keys(header).forEach((key) => {
      sheetJson.forEach((item, index) => {
        const obj = data[index] || {};
        obj[header[key]] = item[key];
        data[index] = obj;
      });
    });

    result = data;
  }

  // 将表格对应的文字转换为 key
  result.forEach((item) => {
    const newItem = {};
    Object.keys(item).forEach((key) => {
      newItem.title = key;
      newItem[toKey[key]] = item[key];
    });
    excelData.push(newItem);
  });
  console.log('excelData',excelData);
  return excelData
};

这里演示一下我们输出的数据格式

  • 输出之前

输出使用的就是我们上面 excelNameToKey 对象 用来映射我们每一组数据当中的每一列 iShot_2022-10-31_11.31.39.png

  • 输出以后

iShot_2022-10-31_11.31.32.png

  • 在我们使用的数据中,往往还需要进一步处理,因为这些字段中我们只使用其中的一部分,顺序也有可能需要处理, 当然,如果我们不使用表格数据,使用后端接口来搞,也是同样可以完成最后的图形绘制,不过需要和后端定义好我们需要的字段

数据进一步处理以后:

iShot_2022-10-31_11.37.18.png

最后还要说一下,在使用我们已经处理好的数据,来绘制图形的时候,我们处理好的数据的 type 是数值类型,这也是我后来出现bug以后 找到的原因, antv 的 y 轴或者 x 轴使用的 映射值必须是 字符串类型,也就是我可能下面数据改造的时候需要把type转换成字符串类型

下面的图形部分如果大家没有这方便的需求也可以不用看, 如果有这方便的需求,可以看一下我的上一篇文章,使用 antv 完成区间柱形图

  • 那么如果不转换会出现什么样的问题呢?

iShot_2022-10-31_10.14.06.png 大家可以看到,上图所示,就是我在使用 数值类型 输出的图形结果,会出现串行的现象,而且当前行渲染的也不是左边我的对应数据,下面很多行也大部分都不是对应行数据,但最终他还是能把这些柱形绘制出来

  • 下面这张就是修改好以后的图形,因为我的表格不是按照我需要的编号顺序排列,这里可以使用 sort 在绘制之前进行排列就可以了 iShot_2022-10-31_10.12.18.png
export function handleData(res) {
  // 数据改造
  function changeData(data) {
    const outPutData = [];
    data.forEach((item)=>{
      outPutData.push({
        type:String(item.carNum),
        module: item.carModel,
        way:item.start,
        values:[
          ChangeStrToMinutes(item.startTime),
          ChangeStrToMinutes(item.endTime)
        ]
      })
    })
    // data.forEach((item) => {
    //   item.wayLine.forEach((every) => {
    //     outPutData.push(
    //       {
    //         type: item.line,
    //         module: item.module,
    //         way: every.way,
    //         values: [
    //           ChangeStrToMinutes(every.time[0]),
    //           ChangeStrToMinutes(every.time[1]),
    //         ],
    //       });
    //   });
    // });
    //TODO 这里是为了解决 颜色 处理部分循环进入两次的问题, 这里写在 上面的数据处理之前和之后 会有分别,分别在于 后面是否使用 reverse 反转数组
    outPutData.push({});
    return outPutData;
  }
  const data = changeData(res)
  console.log('data',data);
  return data
}