接说上文、咱们的antv图表已经配置完,接下来看如何把本地的一个表格文件数据读取出来,转换成咱们想要的一种对象格式呢?
首先介绍一下我们的依赖,图形这一部分还是使用上一章中我们的 antv 来实现的, 框架我自己使用的是 vite + vue3 搭的一个 demo, 表格转换需要使用 xlsx 为什么使用 xlsx 因为其适配性 及 通用性香蕉而言都是比较高的,可以看一下下图
- 下载依赖
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>
这里我们拿到的对象应该长下面这个样子:
- 引入 xlsx 包, 进行数据的读取, 数据读取这里我们使用 FileReader 以二进制的方式读取 FileReader提供了如下方法:
- readAsArrayBuffer(file) 按字节读取文件内容,结果用ArrayBuffer对象表示
- readAsBinaryString(file) 按字节读取文件内容,结果为文件的二进制串
- readAsDataURL(file) 读取文件内容,结果用data:url的字符串形式表示
- readAsText(file,encoding) 按字符读取文件内容,结果用字符串形式表示
- 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值
// 标准化 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 对象 用来映射我们每一组数据当中的每一列
- 输出以后
- 在我们使用的数据中,往往还需要进一步处理,因为这些字段中我们只使用其中的一部分,顺序也有可能需要处理, 当然,如果我们不使用表格数据,使用后端接口来搞,也是同样可以完成最后的图形绘制,不过需要和后端定义好我们需要的字段
数据进一步处理以后:
最后还要说一下,在使用我们已经处理好的数据,来绘制图形的时候,我们处理好的数据的 type 是数值类型,这也是我后来出现bug
以后 找到的原因, antv 的 y 轴或者 x 轴使用的 映射值必须是 字符串类型,也就是我可能下面数据改造的时候需要把type
转换成字符串类型
下面的图形部分如果大家没有这方便的需求也可以不用看, 如果有这方便的需求,可以看一下我的上一篇文章,使用 antv 完成区间柱形图
- 那么如果不转换会出现什么样的问题呢?
大家可以看到,上图所示,就是我在使用 数值类型 输出的图形结果,会出现串行的现象,而且当前行渲染的也不是左边我的对应数据,下面很多行也大部分都不是对应行数据,但最终他还是能把这些柱形绘制出来
- 下面这张就是修改好以后的图形,因为我的表格不是按照我需要的编号顺序排列,这里可以使用 sort 在绘制之前进行排列就可以了
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
}