前端vue3项目的excel文件解析上传(vue3 + tdesign)
【代码】前端vue3项目的excel文件解析上传(vue3 + tdesign)
·
一、安装依赖
xlsxjs库
npm install xlxs -s
二、封装解析函数 + 重复数据校验函数(代码有删减,根据需求自行替换)
1、核心解析函数
import * as XLSX from 'xlsx';
// 核心解析函数
export const parseExcelFile = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
// 解析所有工作表数据
const results = {};
workbook.SheetNames.forEach((sheetName) => {
const worksheet = workbook.Sheets[sheetName];
// 获取表头和数据行
const header = [];
const body = [];
// 获取所有单元格范围
const range = XLSX.utils.decode_range(worksheet['!ref']);
// 遍历所有行 (从第2行开始,根据自己需要选择index)
for (let rowNum = 1; rowNum <= range.e.r; rowNum++) {
const row = [];
// 遍历所有列
for (let colNum = range.s.c; colNum <= range.e.c; colNum++) {
const cellAddress = XLSX.utils.encode_cell({ r: rowNum, c: colNum });
const cell = worksheet[cellAddress];
row.push(cell ? cell.v : '');
}
// 第一行作为表头
if (rowNum === 1) {
header.push(...row);
} else if (rowNum > 2) {
body.push(row);
}
}
// 转换为对象数组
const jsonData = body.map((row) => {
return header.reduce((obj, key, index) => {
obj[key] = row[index];
return obj;
}, {});
});
results[sheetName] = jsonData;
});
resolve(results);
} catch (error) {
reject(error);
}
};
reader.onerror = (error) => reject(error);
reader.readAsArrayBuffer(file);
});
};
2、重复校验工具
// 重复校验工具;
export const checkDuplicates = (data) => {
const keyMap = new Map();
const duplicates = [];
data.forEach((row, index) => {
const compositeKey = [
row.key1,
row.key2,
row.key3,
row.key4,
row.key1,
row.key5,
].join('__');
if (keyMap.has(compositeKey)) {
duplicates.push({
line: index + 3, // Excel 行号从2开始
data: compositeKey.split('__'),
});
} else {
keyMap.set(compositeKey, true);
}
});
return {
uniqueData: data
.map((i, n) => ({ ...i, line: n + 3 }))
.filter((row, index) => !duplicates.some((d) => d.line === index + 3)), // 剔除重复数据
duplicates,
};
};
三、实际应用(实例使用的是vue3 + td)
<script setup>
import { reactive, ref } from 'vue';
import {
MessagePlugin,
Button as TButton,
Table as TTable,
Dialog as TDialog,
Upload as TUpload,
} from 'tdesign-vue-next';
import { parseExcelFile } from '../utils/excelParser';
import { checkDuplicates } from '../utils/checkDuplicates';
const emit = defineEmits(['upload-success']);
const visible = ref(false);
/* 重复数据 */
const duplicates = ref([]);
/* 不重复数据 */
const uniqueData = ref([]);
const transformBooleanField = (value) => {
const mapping = { 是: true, 否: false };
const normalized = String(value).trim();
if (!(normalized in mapping)) {
console.warn(`无效的布尔值字段: ${value}`);
return null; // 或根据需求返回 undefined
}
return mapping[normalized];
};
const processBatchData = (data) => {
return data.map((item) => ({
key1: item['名称1'],
key2: item['名称2'],
key3: item['名称3'],
key4: item['名称4'],
key5: transformBooleanField(item['名称5']),
key6: transformBooleanField(item['名称6']),
}));
};
const AllDataPagination = reactive({
defaultCurrent: 1,
defaultPageSize: 10,
current: 1,
pageSize: 10,
total: uniqueData.value.length,
pageSizeOptions: [10, 20, 50, 100, 200],
});
// 文件选择回调
const beforeUpload = async (files) => {
console.log('files', files);
const file = files.raw;
if (!file) return;
try {
// 解析Excel
const rawData = await parseExcelFile(file);
const mapData = processBatchData(rawData['xxx批量导入模版'] || []);
// 重复数据校验(根据当前需要)
const { uniqueData: filteredData, duplicates: dupList } = checkDuplicates(mapData);
duplicates.value = dupList;
uniqueData.value = filteredData;
AllDataPagination.total = filteredData.length || 0;
if (dupList.length > 0) {
visible.value = true;
} else {
confirmUpload();
}
} catch (error) {
MessagePlugin.error(error.message);
}
};
// 确认上传
const confirmUpload = () => {
emit('upload-success', uniqueData.value);
visible.value = false;
MessagePlugin.success('导入成功');
};
const onPageChange = async (pageInfo) => {
AllDataPagination.current = pageInfo.current;
AllDataPagination.pageSize = pageInfo.pageSize;
};
const sameRowColumns = [
{
colKey: 'line',
title: '行号',
width: 120,
},
{
colKey: 'row',
title: '数据内容',
cell: (h, { row }) => row.data.join('__'),
},
];
const allDataColumns = [
{ colKey: 'serial-number', title: '序号' },
{ colKey: 'line', title: '行号' },
{
colKey: 'key1',
title: '型号',
},
{
colKey: 'key2',
title: '名称',
},
{
colKey: 'key3',
title: '版本号',
},
{
colKey: 'key4',
title: '地址',
// cell: (h, { row }) => row.data.join('|'),
},
{
colKey: 'key5',
title: '是否对修改',
cell: (h, { row }) => (row.key5=== true ? '是' : '否'),
},
{
colKey: 'key6',
title: '是否分发',
cell: (h, { row }) => (row.key6=== true ? '是' : '否'),
},
];
</script>
<template>
<div class="t-upload-container">
<!-- TD上传组件 -->
<t-upload action="" :auto-upload="false" accept=".xlsx,.xls" :before-upload="beforeUpload">
<t-button theme="primary">
<template #icon><t-icon name="upload" /></template>
批量导入开源组件
</t-button>
</t-upload>
<!-- TD对话框 -->
<t-dialog
v-model:visible="visible"
header="重复数据提示"
:on-confirm="confirmUpload"
:on-close="() => (visible = false)"
width="80vw"
placement="center"
>
<div class="duplicate-content">
<p>发现 {{ duplicates.length }} 条重复数据:</p>
<t-table :data="duplicates" :columns="sameRowColumns" row-key="line" :max-height="300"> </t-table>
</div>
<div class="duplicate-content">
<t-table
:data="uniqueData"
:columns="allDataColumns"
row-key="line"
:max-height="600"
:pagination="AllDataPagination"
@page-change="onPageChange"
>
</t-table>
</div>
</t-dialog>
</div>
</template>
<style scoped>
.t-upload-container {
margin: 20px;
}
.duplicate-content {
padding: 10px;
}
.duplicate-content p {
margin-bottom: 15px;
color: var(--td-text-color-secondary);
}
</style>
更多推荐
所有评论(0)