vue3 项目实战 excel导入导出

70 阅读2分钟

1 Excel 导入

1.1 插件:

import * as XLSX from 'xlsx';

1.2 Excel模板

java后端生成
模板的列名,后端国际化生成:
比如:orderDetail.skuName
    -- 中文:"商品名称"
    -- 英语:"Product Name"
    -- 西语:"Nombre del Producto"

1.3 读取模板数据

// 1 前端获取国际化环境
import { useLanguageStore} from '@/store/modules/language.js';
const languageStore = useLanguageStore();

const previewData = ref([]);    // 预览数据

// 2 根据国际化环境选择对应的列名:
const ORDER_DETAIL_COLUMN_MAPPING = {
  'en': {
    'Product Name': 'skuName',
    'Product Barcode': 'skuCode',
  },
  'zh': {
    '商品名称': 'skuName',
    '商品条码': 'skuCode',
  },
  'es': {
    'Nombre del Producto': 'skuName',
    'Código de Barras': 'skuCode',
  }
}

const columnMapping = computed(() => {
  return ORDER_DETAIL_COLUMN_MAPPING[languageStore.language] || ORDER_DETAIL_COLUMN_MAPPING['zh']
})

// 3 读取Excel文件
const readExcelFile = (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 firstSheetName = workbook.SheetNames[0]
        const worksheet = workbook.Sheets[firstSheetName]
        
        // 将Excel数据转换为JSON
        const jsonData = XLSX.utils.sheet_to_json(worksheet)
        
        // 数据映射和验证
        const mappedData = mapAndValidateData(jsonData)
        resolve(mappedData)
      } catch (error) {
        reject(error)
      }
    }
    
    reader.onerror = () => reject(new Error('文件读取失败'))
    reader.readAsArrayBuffer(file)
  })
}

// 4 数据映射和验证
const mapAndValidateData = (data) => {
  // 限制一次最多处理100条数据
  if (data.length > 100) {
    ElMessage.error('当前行数:' + data.length + ', 一次最多只能导入100条数据!')
    return []
  }

  return data.map((item, index) => {
    const mappedItem = {}
    const errors = []
    
    // 遍历映射关系,将中文列名映射到英文字段名
    Object.keys(columnMapping.value).forEach(chineseKey => {
      const englishKey = columnMapping.value[chineseKey]
      
      if (item[chineseKey] !== undefined) {
        // 处理数据
        let value = item[chineseKey]
        
        // 去除字符串两端的空格
        if (typeof value === 'string') {
          value = value.trim()
        }
        
        // 特定字段的数据处理
        switch(englishKey) {
          case 'detailQuantity':
          case 'detailPrice':
          case 'detailAmount':
          case 'detailDiscountRate':
          case 'detailTaxRate':
            // 转换为数字
            if (value !== '' && value !== null && value !== undefined) {
              const numValue = parseFloat(value)
              if (!isNaN(numValue)) {
                mappedItem[englishKey] = numValue
              } else {
                errors.push(`${chineseKey}格式错误`)
              }
            } else {
              mappedItem[englishKey] = 0
            }
            break
          default:
            mappedItem[englishKey] = value
        }
      } else {
        // 必填字段验证
        if (['skuName', 'skuCode', 'unitCode', 'detailPrice', 'detailTaxRate'].includes(englishKey)) {
          errors.push(`${chineseKey}不能为空`)
        }
      }
    })
    
    
    // 设置默认值
    if (!mappedItem.detailDiscountRate) mappedItem.detailDiscountRate = 0
    if (!mappedItem.detailTaxRate) mappedItem.detailTaxRate = 21
    
    // 如果有错误,添加错误信息
    if (errors.length > 0) {
      mappedItem.errorMessage = errors.join(',')
      mappedItem._isValid = false
    } else {
      mappedItem._isValid = true
    }
    
    mappedItem.rowIndex = index + 1 // Excel行号(从1开始,标题行是第1行)
    
    return mappedItem
  })
}

// 5 业务使用数 ...



2 Excel导出

2.1 使用插件

2.2 设置导出字段

// 订单项字段 src/config/export/orderItem.js
export const orderItemExportFields = [
  { key: 'skuName', i18nKey: 'order.item.skuName', width: 20, order: 1, format: 'text' },
  { key: 'skuCode', i18nKey: 'order.item.skuCode', width: 20, order: 2, format: 'text' },
  { key: 'unitCode', i18nKey: 'order.item.unitCode', width: 10, order: 3, format: 'text' },
  { key: 'detailPrice', i18nKey: 'order.item.detailPrice', width: 12, order: 4, format: 'currency' },
  { key: 'detailQuantity', i18nKey: 'order.item.detailQuantity', width: 10, order: 5, format: 'number' },
  { key: 'detailDiscountRate', i18nKey: 'order.item.detailDiscountRate', width: 10, order: 6, format: 'number' },
  { key: 'detailSalesAmount', i18nKey: 'order.item.detailSalesAmount', width: 14, order: 7, format: 'currency' },
  { key: 'locationCode', i18nKey: 'order.item.locationCode', width: 14, order: 8, format: 'currency' },
]

2.3 导出字段设置组件

// src/components/ExportColumnSelector
 主要功能:
    1 选择指定导出列;
    2 设置导出列顺序;
    3 缓存设置记录,通过模板名称记录!
 鸣谢插件:
    1. 设置顺序的拖拽插件: "vuedraggable";
    2. 本地存储: "localforage";

2.4 导出业务逻辑封装

// src/hooks/useeExcelExport
鸣谢插件:
    1. 保存文件:"file-saver";
    2. EXcel处理:"exceljs";
使用的功能:
    1. excel内容排版;
    2. 自定义A4打印:
        2.1 分页打印设置;
        2.2 页脚页码设置;

2.5 父组件使用:

<template>
......

    <!-- 导出配置组件 -->
    <ExportColumnSelector  v-model="showSelector" 
    :all-fields="allFields" 
    :default-selected="selectedKeys" 
    @confirmExport="handleConfirmExport" />
    
......
</template>
<script>
import { useExcelExport } from '@/hooks/useExcelExport'

const { exportExcel } = useExcelExport();
......

function handleExportExcel(){
  showSelector.value = true
}

// 导出
const handleConfirmExport = (selectedFields) => {
  exportExcel({
    fileName: form.value.orderInitNo,
    sheetName: '订单明细',
    fields: selectedFields,
    data: form.value.salesOrderDetailList,
    order: {
      orderInitNo: form.value.orderInitNo,
      orderConfirmDate: form.value.orderConfirmDate,
      customerName: form.value.customerName,
      customerNif: form.value.customerNif,
      customerPhone: form.value.customerPhone,
      totalSalesAmount: form.value.totalSalesAmount,
      sellerMessage: form.value.sellerMessage,
      buyerMessage: form.value.buyerMessage,
      customerAddress: customerAddress.value,
    }
  })
}

......
</script>