Vue3 + Ant Design 实现 Excel 模板导入表格数据

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. 使用说明

  1. 基本使用:
xml 复制代码
<script setup>
import ExcelImport from './ExcelImport.vue';
import { tempColumns } from './columns'; // 导入您提供的列配置
</script>

<template>
  <ExcelImport :columns="tempColumns" />
</template>
  1. 自定义验证:
xml 复制代码
<script setup>
const customValidate = (data) => {
  const errors = [];
  // 添加自定义验证逻辑
  return errors;
};
</script>

<template>
  <ExcelImport :columns="tempColumns" :validate-fn="customValidate" />
</template>

4. 功能特点

  1. 完整的Excel导入流程:

    • 支持Excel文件选择
    • 数据解析和格式转换
    • 数据验证和错误提示
    • 模板下载功能
  2. 数据验证:

    • 必填字段检查
    • 数字字段验证
    • 是否类字段验证(0或1)
    • 自定义验证支持
  3. 用户界面:

    • 使用Ant Design组件
    • 错误信息清晰展示
    • 表格数据预览
    • 支持单条数据删除
  4. 数据提交:

    • 提交前确认
    • 加载状态指示
    • 提交结果反馈

5. 注意事项

  1. 对于大型Excel文件(超过1000行),建议添加分片处理或进度提示
  2. 后端也需要进行相同的数据验证,前端验证不能替代后端验证
  3. 可以根据实际需求调整模板内容和验证规则
  4. 对于敏感数据,建议在前端加密后再传输到后端

这个实现基于Vue3和Ant Design,完整支持了您提供的表格列配置,并包含了所有必要的导入功能

相关推荐
恋猫de小郭4 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅11 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606112 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了12 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅12 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅12 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅13 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment13 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅13 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊13 小时前
jwt介绍
前端