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,完整支持了您提供的表格列配置,并包含了所有必要的导入功能

相关推荐
小小小小宇16 分钟前
Vue.nextTick()笔记
前端
小约翰仓鼠2 小时前
vue3子组件获取并修改父组件的值
前端·javascript·vue.js
Lin Hsüeh-ch'in2 小时前
Vue 学习路线图(从零到实战)
前端·vue.js·学习
烛阴2 小时前
bignumber.js深度解析:驾驭任意精度计算的终极武器
前端·javascript·后端
计蒙不吃鱼2 小时前
一篇文章实现Android图片拼接并保存至相册
android·java·前端
全职计算机毕业设计3 小时前
基于Java Web的校园失物招领平台设计与实现
java·开发语言·前端
啊~哈3 小时前
vue3+elementplus表格表头加图标及文字提示
前端·javascript·vue.js
xiaogg36783 小时前
vue+elementui 网站首页顶部菜单上下布局
javascript·vue.js·elementui
小小小小宇3 小时前
前端小tips
前端