需求背景
一张总域名表包含上千个不同的域名,一张新域名表包含若干个域名,新表中的域名可能有部分与总表重复,需要将两张表数据对比分析,并将新表的非重复部分导出为Excel文件。
实现效果如下,上传总表和新表之后,立即显示出新表的域名冗余状态。
读取总表
代码
<input type="file" @change="domainUpload" accept=".xlsx, .xls" ref="domainInput" />
import * as XLSX from 'xlsx';
type FilteredRow = [string, string]; // 表示过滤后的行
type ExcelData = [string][]; // 表示所有行的数组
const excelData = ref<ExcelData>([]);
type FilteredData = [string, string][]; // 表示所有行的数组
const filteredData = ref<FilteredData>([]);
const domainInput = ref<HTMLInputElement | null>(null); //总表输入框
const newFileInput = ref<HTMLInputElement | null>(null); //新表输入框
const loading = ref<boolean>(false);
const allDomains = ref<Array<string>>([]); //存放所有域名值
const domainUpload = (event: Event) => {
const input = event.target as HTMLInputElement;
const file = input.files ? input.files[0] : null;
if (!file) return;
if (!file.name.endsWith('.xlsx') && !file.name.endsWith('.xls')) {
ElMessage.error('请上传Excel文件');
if (domainInput.value) {
domainInput.value.value = ''; // 清空文件输入框
}
return;
}
const reader = new FileReader();
reader.onload = (e) => {
// e.target.result --> FileReader 完成读取操作后的结果
const arrayBuffer = e.target ? (e.target.result as ArrayBuffer) : null;
if (!arrayBuffer) return;
// 将 ArrayBuffer(原始二进制数据) 转换为 Uint8Array
// XLSX 库期望接收 Uint8Array 格式以解析 Excel
const data = new Uint8Array(arrayBuffer);
const workbook = XLSX.read(data, { type: 'array' }); //开始解析
const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName];
const jsonData: [string][] = XLSX.utils.sheet_to_json(worksheet, {
header: 1
}); //第一列的所有数据
allDomains.value = jsonData.slice(1).map((item) => item[0]); // 去除域名后的第一列所有数据
};
reader.readAsArrayBuffer(file); //result as ArrayBuffer的原因
};
FileReader介绍
FileReader是一个web api(浏览器提供),用于异步读取文件内容,可以将文件内容读取为不同的格式,包括
- 文本(readAsText)
- ArrayBuffer(readAsArrayBuffer)
- Data URL(readAsDataURL)
- 二进制字符串(readAsBinaryString)
reader.readAsArrayBuffer
Excel 本质是二进制数据。FileReader 的 readAsArrayBuffer 方法提供了 ArrayBuffer,这是一种表示原始二进制数据的有效方式。ArrayBuffer 可以轻松转换为其他类型的数据视图,如 Uint8Array(Excel 文件的二进制数据本质上是以字节(8位)为单位的),后者是 XLSX 库处理 Excel 文件时推荐的输入格式之一。
onload
onload表读取成功完成时的事件,需要自定义,reader.readAsArrayBuffer时会触发
sheet_to_json
XLSX.utils.sheet_to_json将 Excel 工作表转换为 JS 对象或数组。
读取新表
<input type="file" @change="newFileUpload" accept=".xlsx, .xls" ref="newFileInput" />
const newFileUpload = (event: Event) => {
const input = event.target as HTMLInputElement;
const file = input.files ? input.files[0] : null;
if (!file) return;
if (!file.name.endsWith('.xlsx') && !file.name.endsWith('.xls')) {
ElMessage.error('请上传Excel文件');
if (newFileInput.value) {
newFileInput.value.value = ''; // 清空文件输入框
}
return;
}
const reader = new FileReader();
reader.onload = async (e) => {
const arrayBuffer = e.target ? (e.target.result as ArrayBuffer) : null;
if (!arrayBuffer) return;
const data = new Uint8Array(arrayBuffer);
const workbook = XLSX.read(data, { type: 'array' });
const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName];
const jsonData: [string][] = XLSX.utils.sheet_to_json(worksheet, {
header: 1
}); //第一列的所有数据
excelData.value = jsonData.slice(1); // 去除域名后的第一列所有数据
await filterData(false); //过滤数据,将冗余信息可视化
};
reader.readAsArrayBuffer(file);
};
对比&导出
<div class="excel-container">
<Box v-show="filteredData.length">
<el-table border :data="filteredData" style="width: 100%">
<el-table-column type="index" label="序号" width="80" />
<el-table-column prop="0" label="域名" width="280">
<template #default="scope">
<div :class="duplicateClass(scope.row)">{{ scope.row[0] }}</div>
</template>
</el-table-column>
<el-table-column prop="1" label="状态" width="100">
<template #default="scope">
<div :class="duplicateClass(scope.row)">
{{ scope.row[1] === '1' ? '冗余' : '新值' }}
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
</Box>
</div>
const filterData = async (closed: boolean) => {
while (!allDomains.value.length) {
loading.value = true;
}
loading.value = false;
const needFilter = ref<ExcelData | FilteredData>([]);
// 将数据添加新属性,1表示冗余,0表示新值
if (closed) {
//关闭编辑框时过滤filteredData
needFilter.value = filteredData.value;
} else {
//点击过滤按钮时过滤excelData
needFilter.value = excelData.value;
}
filteredData.value = needFilter.value.map((row) => {
const domain = row[0];
const isDuplicate = allDomains.value.includes(domain);
return [domain, isDuplicate ? '1' : '0'];
});
};
const exportExcel = () => {
// 将保留的数据添加上表头
const outputData = [
['新值域名'],
...filteredData.value.filter((row) => row[1] === '0').map((row) => [row[0]])
];
// 创建一个新的工作簿
const worksheet = XLSX.utils.aoa_to_sheet(outputData);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
// 导出 Excel 文件,以 年月日时分秒 时间命名
let newFileName = new Date()
.toLocaleString()
.replace(/\//g, '-')
.replace(' ', '_');
XLSX.writeFile(workbook, `${newFileName}.xlsx`);
};
const duplicateClass = (row: [string, string]) => {
return row[1] === '1' ? 'duplicateItem' : '';
};
XLSX.utils.aoa_to_sheet
将数组的数组(Array of Arrays,简称 AoA)转换为 Excel 工作表对象
XLSX.utils.book_new
创建一个新的空 Excel 工作簿对象
XLSX.utils.book_append_sheet
将工作表添加到工作簿中,参数依次为:工作簿对象、工作表对象、工作表名称
XLSX.writeFile
将工作簿写入文件,用于保存 Excel 文件到本地文件系统或触发文件下载
// 创建数据
const data = [["Name", "Age"], ["John", 30], ["Jane", 25]];
// 创建工作簿
const wb = XLSX.utils.book_new();
// 创建工作表
const ws = XLSX.utils.aoa_to_sheet(data);
// 将工作表添加到工作簿
XLSX.utils.book_append_sheet(wb, ws, "People");
// 保存文件
XLSX.writeFile(wb, "people.xlsx");
工作簿WorkBook是整个 Excel 文件,工作表Worksheet是工作簿中的一个单独的"页面"或数据网格