Vue3 + Ant Design 实现 Excel 模板导入表格数据
下面是一个完整的 Vue3 + Ant Design 实现 Excel 模板导入的组件,基于您提供的表格列配置。
1. 安装依赖
bash
npm install xlsx file-saver @ant-design/icons-vue
# 或
yarn add xlsx file-saver @ant-design/icons-vue
2. 完整组件实现
ini
<template>
<div class="excel-import-container">
<a-space direction="vertical" style="width: 100%">
<a-space>
<a-upload
:before-upload="beforeUpload"
:show-upload-list="false"
accept=".xlsx,.xls"
>
<a-button type="primary">
<template #icon><UploadOutlined /></template>
导入Excel
</a-button>
</a-upload>
<a-button @click="downloadTemplate">
<template #icon><DownloadOutlined /></template>
下载模板
</a-button>
</a-space>
<a-alert
v-if="importErrors.length > 0"
type="error"
message="导入数据存在以下问题:"
:description="importErrors.join('\n')"
show-icon
closable
@close="() => importErrors = []"
/>
<a-table
:columns="columns"
:data-source="importedData"
:row-key="(record, index) => index"
:scroll="{ x: 2000 }"
bordered
size="middle"
>
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'index'">
{{ record.index + 1 }}
</template>
<template v-else-if="['isImports', 'coreProduct', 'efficient', 'waterSaving', 'environment', 'infoInnovationProduct', 'govService'].includes(column.dataIndex)">
{{ text == 1 ? '是' : '否' }}
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-button type="link" danger @click="removeItem(record.index)">
删除
</a-button>
</template>
</template>
</a-table>
<div class="action-buttons" v-if="importedData.length > 0">
<a-button @click="clearData">清空数据</a-button>
<a-button
type="primary"
@click="submitData"
:loading="isSubmitting"
>
提交数据
</a-button>
</div>
</a-space>
</div>
</template>
<script setup>
import { ref } from 'vue';
import * as XLSX from 'xlsx';
import { saveAs } from 'file-saver';
import { UploadOutlined, DownloadOutlined } from '@ant-design/icons-vue';
import { message, Modal } from 'ant-design-vue';
// 导入表格列配置
const columns = tempColumns;
// 响应式数据
const importedData = ref([]);
const importErrors = ref([]);
const isSubmitting = ref(false);
// 处理文件上传
const beforeUpload = (file) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = e.target.result;
const workbook = XLSX.read(data, { type: 'array' });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
const jsonData = XLSX.utils.sheet_to_json(worksheet);
// 处理导入数据
processImportedData(jsonData);
} catch (error) {
message.error('文件解析失败: ' + error.message);
}
};
reader.readAsArrayBuffer(file);
return false; // 阻止自动上传
};
// 处理导入的数据
const processImportedData = (data) => {
const errors = [];
data.forEach((row, index) => {
// 检查必填字段
if (!row['商品/服务名称']) {
errors.push(`第 ${index + 2} 行: 商品/服务名称为必填项`);
}
if (!row['采购品目名称']) {
errors.push(`第 ${index + 2} 行: 采购品目名称为必填项`);
}
// 检查数字字段
const numberFields = ['总金额/首购费用(元)', '采购数量', '单价(元)'];
numberFields.forEach(field => {
if (row[field] && isNaN(Number(row[field]))) {
errors.push(`第 ${index + 2} 行: ${field} 必须为数字`);
}
});
// 检查是否类字段(应为0或1)
const booleanFields = [
'是否采购进口产品', '是否核心产品', '是否强制采购节能产品',
'是否强制采购节水产品', '是否优先采购环保产品',
'是否属于政府采购需求标准(2023年版)规范产品', '是否属于政府购买服务'
];
booleanFields.forEach(field => {
if (row[field] !== undefined && row[field] !== null && row[field] !== '' && row[field] != 0 && row[field] != 1) {
errors.push(`第 ${index + 2} 行: ${field} 必须为"是"(1)或"否"(0)`);
}
});
});
if (errors.length > 0) {
importErrors.value = errors;
message.error(`发现 ${errors.length} 处错误,请修正后重新导入`);
return;
}
// 转换数据格式
const formattedData = data.map((row, index) => ({
index,
goodsName: row['商品/服务名称'],
purCatalogName: row['采购品目名称'],
totalPrice: row['总金额/首购费用(元)'] ? Number(row['总金额/首购费用(元)']) : null,
num: row['采购数量'] ? Number(row['采购数量']) : null,
unit: row['计量单位'],
price: row['单价(元)'] ? Number(row['单价(元)']) : null,
isImports: row['是否采购进口产品'] == 1 ? 1 : 0,
coreProduct: row['是否核心产品'] == 1 ? 1 : 0,
efficient: row['是否强制采购节能产品'] == 1 ? 1 : 0,
waterSaving: row['是否强制采购节水产品'] == 1 ? 1 : 0,
environment: row['是否优先采购环保产品'] == 1 ? 1 : 0,
infoInnovationProduct: row['是否属于政府采购需求标准(2023年版)规范产品'] == 1 ? 1 : 0,
industrialClass: row['采购标的所属国民经济分类'],
govService: row['是否属于政府购买服务'] == 1 ? 1 : 0,
govServiceCatalogName: row['政府购买服务指导性目录名称'],
spec: row['规格参数/服务要求']
}));
importedData.value = formattedData;
message.success(`成功导入 ${formattedData.length} 条数据`);
};
// 下载模板
const downloadTemplate = () => {
// 准备表头
const headers = columns
.filter(col => col.dataIndex !== 'index' && col.dataIndex !== 'operation')
.map(col => col.title);
// 示例数据
const sampleData = [
[
'示例商品', // 商品/服务名称
'办公设备', // 采购品目名称
1000, // 总金额/首购费用(元)
10, // 采购数量
'台', // 计量单位
100, // 单价(元)
0, // 是否采购进口产品
1, // 是否核心产品
0, // 是否强制采购节能产品
0, // 是否强制采购节水产品
1, // 是否优先采购环保产品
0, // 是否属于政府采购需求标准(2023年版)规范产品
'C3910', // 采购标的所属国民经济分类
0, // 是否属于政府购买服务
'', // 政府购买服务指导性目录名称
'规格参数示例' // 规格参数/服务要求
]
];
// 创建工作表
const ws = XLSX.utils.aoa_to_sheet([headers, ...sampleData]);
// 创建工作簿
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, '采购数据');
// 设置列宽
if (!ws['!cols']) ws['!cols'] = [];
headers.forEach((_, index) => {
ws['!cols'][index] = { wch: 20 }; // 设置每列宽度
});
// 生成Excel文件
const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
saveAs(
new Blob([wbout], { type: 'application/octet-stream' }),
'采购数据模板.xlsx'
);
};
// 删除单条数据
const removeItem = (index) => {
importedData.value = importedData.value.filter((_, i) => i !== index);
// 重新生成index
importedData.value = importedData.value.map((item, i) => ({ ...item, index: i }));
};
// 清空数据
const clearData = () => {
Modal.confirm({
title: '确认清空数据吗?',
content: '这将清除所有已导入的数据',
onOk() {
importedData.value = [];
importErrors.value = [];
}
});
};
// 提交数据
const submitData = async () => {
isSubmitting.value = true;
try {
// 这里替换为实际的API调用
// const response = await api.submitImportData(importedData.value);
message.success('数据提交成功');
importedData.value = [];
} catch (error) {
message.error('提交失败: ' + error.message);
} finally {
isSubmitting.value = false;
}
};
</script>
<style scoped>
.excel-import-container {
padding: 20px;
background: #fff;
border-radius: 4px;
}
.action-buttons {
margin-top: 16px;
text-align: right;
}
</style>
3. 使用说明
- 基本使用:
xml
<script setup>
import ExcelImport from './ExcelImport.vue';
import { tempColumns } from './columns'; // 导入您提供的列配置
</script>
<template>
<ExcelImport :columns="tempColumns" />
</template>
- 自定义验证:
xml
<script setup>
const customValidate = (data) => {
const errors = [];
// 添加自定义验证逻辑
return errors;
};
</script>
<template>
<ExcelImport :columns="tempColumns" :validate-fn="customValidate" />
</template>
4. 功能特点
-
完整的Excel导入流程:
- 支持Excel文件选择
- 数据解析和格式转换
- 数据验证和错误提示
- 模板下载功能
-
数据验证:
- 必填字段检查
- 数字字段验证
- 是否类字段验证(0或1)
- 自定义验证支持
-
用户界面:
- 使用Ant Design组件
- 错误信息清晰展示
- 表格数据预览
- 支持单条数据删除
-
数据提交:
- 提交前确认
- 加载状态指示
- 提交结果反馈
5. 注意事项
- 对于大型Excel文件(超过1000行),建议添加分片处理或进度提示
- 后端也需要进行相同的数据验证,前端验证不能替代后端验证
- 可以根据实际需求调整模板内容和验证规则
- 对于敏感数据,建议在前端加密后再传输到后端
这个实现基于Vue3和Ant Design,完整支持了您提供的表格列配置,并包含了所有必要的导入功能