前端实现解析【导入】数据后调用批量处理接口

1. 导入数据不想调用后端接口,可以前端直接解析上传的Excel文件,解析出文本内容后,再直接调用批量插入数据的接口插入数据么?

当然可以。这是一个很好的优化,可以减轻服务器的压力。我们可以在前端使用一个库来解析 Excel 文件,然后将解析出的数据直接发送到批量添加的接口。

要实现这个功能,我们需要一个用于解析 Excel 文件的库。xlsx(也称为 SheetJS)是一个非常流行的选择。

2. 代码实现,基于 React + Antd

TypeScript 复制代码
import * as XLSX from 'xlsx'
import { message, Upload, type UploadProps } from 'antd'

<Upload {...uploadProps}>
  <Button loading={uploading} icon={<UploadOutlined />}>
    导入邮箱
  </Button>
</Upload>

const uploadProps: UploadProps = {
  name: 'file',
  showUploadList: false,
  beforeUpload: (file: any) => {
    const isExcel = file.type === 'application/vnd.ms-excel' || file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    if (!isExcel) {
      message.error('只能上传 Excel 文件!')
      return Upload.LIST_IGNORE
    }

    setUploading(true)
    const reader = new FileReader()
    reader.onload = async (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 json = XLSX.utils.sheet_to_json(worksheet, { header: 1 })

        // 假设第一行是表头
        const headers = json[0] as string[]
        const rows = (json.slice(1) as any[][]).filter(
          (row) => row.length > 0 && row.some((cell) => cell)
        )

        // 映射表头到API字段
        const headerMap: Record<string, string> = {
          名称: 'name',
          类型: 'type',
          说明: 'remark',
        }

        // 校验名称列
        const nameColumnIndex = headers.indexOf('名称')
        if (nameColumnIndex === -1) {
          message.error('模板中未找到"名称"列,请检查文件')
          return
        }

        const names = rows.map((row) => (row as any[])[nameColumnIndex])

        // 检查空数据
        if (names.some((name) => !name)) {
          message.error('名称列存在空数据,请检查后重新上传')
          return
        }

        // 检查重复数据
        const nameSet = new Set()
        for (const name of names) {
          if (nameSet.has(name)) {
            message.error(`名称 "${name}" 重复,请检查后重新上传`)
            return
          }
          nameSet.add(name)
        }

        const dataToSubmit = rows.map((row) => {
          const rowData: Record<string, any> = {}
          headers.forEach((header, index) => {
            const apiKey = headerMap[header]
            if (apiKey) {
              rowData[apiKey] = (row as any[])[index]
            }
          })

          return rowData
        })

        // 调用批量添加接口的方法
        handleBatchAdd(dataToSubmit)
      } catch (error) {
        message.error('文件解析或导入失败')
      } finally {
        setUploading(false)
      }
    }
    reader.readAsArrayBuffer(file)

    return Upload.LIST_IGNORE // 阻止自动上传
  },
}

3. 我不太明白reader.onload和reader.readAsArrayBuffer(file)的执行顺序,以及最后为什么返回这个return Upload.LIST_IGNORE?

这三部分共同实现了一个自定义的、在前端处理文件的上传逻辑。

  1. reader.readAsArrayBuffer(file) - 开始读文件这行代码是整个文件读取过程的起点。
  • 它的作用是告诉浏览器:"现在开始读取用户选择的这个 file 文件,并把它的内容转换成一个 ArrayBuffer(一种内存中的二进制数据格式)。"
  • 这是一个异步操作。也就是说,它像一个经理分配任务,把"读文件"这个耗时的任务交给浏览器底层去处理,它自己则不会等待任务完成,而是立刻返回,让 beforeUpload 函数继续执行下面的代码。
  1. reader.onload - 文件读完后做什么

这个 onload 是一个事件处理函数,你可以理解为"任务完成后的回调"。

  • 在调用 readAsArrayBuffer 之前,我们先定义好 onload 函数。这相当于提前告诉浏览器:"等你成功把文件读完之后,就来执行 onload 里面的这些代码。"
  • onload里的代码(解析Excel、处理数据、调用API)只有在文件被完全、成功地读取到内存后才会被触发执行。
  1. return Upload.LIST_IGNORE - 告诉 antd 组件"交给我处理"这是 antd Upload 组件提供的一个特殊返回值。
  • beforeUpload 函数的返回值可以控制 Upload 组件的行为。
  • 默认情况下,Upload 组件会自己尝试将文件上传到一个服务器地址(通过 action 属性指定)。
  • 但是,在这里我们并不想让 Upload 组件自己上传文件。我们的目标是在前端浏览器里直接解析文件内容,然后把解析出来的数据(而不是文件本身)发送到我们的API。
  • 所以,return Upload.LIST_IGNORE 的作用就是明确地告诉 Upload 组件:
  1. "请停止你默认的上传动作,我自己来处理这个文件。"
  2. "同时,也不用在上传列表里显示这个文件了。"

总结执行顺序

  1. beforeUpload 函数被触发。
  2. 代码定义了 reader.onload 事件处理函数(此时不执行)。
  3. reader.readAsArrayBuffer(file) 被调用,开始在后台异步读取文件。
  4. beforeUpload 函数立即执行到最后一行,return Upload.LIST_IGNORE,阻止了 antd 的默认上传行为。
  5. 过了一会儿(取决于文件大小),文件在后台被读取完毕。
  6. 浏览器触发 load 事件,此时才开始执行 reader.onload 函数里的代码,进行文件解析和API调用。

希望这个解释能帮助您理解!

相关推荐
带着梦想扬帆启航16 小时前
UniApp 多个异步开关控制教程
前端·javascript·uni-app
小高00716 小时前
JavaScript 内存管理是如何工作的?
前端·javascript
是大林的林吖16 小时前
解决 elementui el-cascader组件懒加载时存在选中状态丢失的问题?
前端·javascript·elementui
鹏仔工作室16 小时前
elemetui中el-date-picker限制开始结束日期只能选择当月
前端·vue.js·elementui
一 乐16 小时前
个人博客|博客app|基于Springboot+微信小程序的个人博客app系统设计与实现(源码+数据库+文档)
java·前端·数据库·spring boot·后端·小程序·论文
sTone8737516 小时前
Android Room部件协同使用
android·前端
晴殇i16 小时前
前端代码规范体系建设与团队落地实践
前端·javascript·面试
用户740546399430916 小时前
Vite 库模式输出 ESM 格式时的依赖处理方案
前端·vite
开发者小天16 小时前
React中使用useParams
前端·javascript·react.js
lichong95116 小时前
Android studio release 包打包配置 build.gradle
android·前端·ide·flutter·android studio·大前端·大前端++