Vue - 文件导入组件封装

1 情景一

需求背景 :导入本地表格数据到页面中表格,而页面中表格数据通过后端接口获取。
实现思路:弹窗嵌入 Element UI Upload 上传组件,获取到文件后调后端接口。

action: 上传的地址
file-list: 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]
headers: 设置上传的请求头部(上传的文件可能是有固定格式的(表头),这个是在后端设定好了的,所以如果上传的文件格式不对可能会引起报错。)
before-upload: 上传文件之前的钩子,可作上传之前校验
on-error: 文件上传失败时的钩子
on-exceed: 文件超出个数限制时的钩子
on-success: 文件上传成功时的钩子
on-change: 文件状态改变时的钩子
this.$refs.fileUpload.submit(): 手动上传文件列表(fileUpload是自定义的名字)

  • 组件代码
bash 复制代码
<template>
  <span>
    <el-button
      plain
      icon="el-icon-upload2"
      type="primary"
      size="mini"
      :disabled="disabled"
      @click="importFile"
    >{{ $t('import') }}</el-button>
    <el-dialog
      v-if="open"
      :title="importTitle"
      :visible.sync="open"
      width="500px"
      append-to-body
      :close-on-click-modal="false"
      :show-close="false"
      @close="cancel"
    >
      <div v-if="isShowRecord" class="record-btn">
        <el-button icon="el-icon-time" type="primary" size="mini" @click="recordBtnClick">
          <span>{{ recordText }}</span>
        </el-button>
      </div>
      <div style="text-align: center;">
        <el-upload
          ref="fileUpload"
          multiple
          :auto-upload="false"
          :action="uploadFileUrl"
          :before-upload="handleBeforeUpload"
          :file-list="fileList"
          :limit="limit"
          :on-error="handleUploadError"
          :on-exceed="handleExceed"
          :on-success="handleUploadSuccess"
          :on-change="handleChange"
          :headers="headers"
          class="upload-file-uploader"
        >
          <div style="border:1px dashed #e9e9e9">
            <i class="el-icon-plus avatar-uploader-icon" style="margin:80px;font-size: 25px;" />
          </div>
          <!-- 上传提示 -->
          <div v-if="showTip" slot="tip" class="el-upload__tip">
            {{ $t("pleaseUpload") }}
            <template v-if="fileSize">
              {{ $t("sizeLess") }}
              <b style="color: #f56c6c">{{ fileSize }}MB</b>
            </template>
            <template v-if="fileType">
              {{ $t("formatIs") }}
              <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
            </template>
            {{ $t("file") }}
          </div>
        </el-upload>
      </div>
      <div class="template">
        <!-- <form v-show="false" id="templateForm" :action="templateUrl" method="get" /> -->
        <span @click="downloadFile">{{ $t("downloadThisTemplate") }}</span>
      </div>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">{{ $t('confirm') }}</el-button>
        <el-button @click="cancel">{{ $t('cancel') }}</el-button>
      </div>
    </el-dialog>
  </span>
</template>
<script>
import { getToken } from '@/utils/auth'
import { downloadGet } from '@/utils/request'
import moment from 'moment'
import $ from '@/i18n/index'

export default {
  name: 'FileImport',
  props: {
    // 是否禁用导入按钮
    disabled: {
      type: Boolean,
      default: false
    },
    // 导入弹窗标题
    importTitle: {
      type: String,
      default: () => {
        return $.t('fileImport')
      }
    },
    // 数量限制
    limit: {
      type: Number,
      default: 1
    },
    // 大小限制(MB)
    fileSize: {
      type: Number,
      default: 20
    },
    // 文件类型, 例如['png', 'jpg', 'jpeg']
    fileType: {
      type: Array,
      default: () => ['csv']
    },
    // 是否显示提示
    isShowTip: {
      type: Boolean,
      default: true
    },
    // 下载模板路径
    downLoadUrl: { type: String, default: null },
    // 下载的全拼路径
    fullPathUrl: { type: String, default: null },
    // 模板名称
    fileName: { type: String, default: moment().format('yyyyMMDDHHmmss') },
    // 导入数据路径
    importUrl: { type: String, default: null },
    // 是否展示导入导出记录按钮
    isShowRecord: {
      type: Boolean,
      default: false
    },
    // 导入导出记录文字
    recordText: {
      type: String,
      default: () => {
        return $.t('importAndExportRecord')
      }
    }
  },
  data() {
    return {
      open: false,
      number: 0,
      baseUrl: process.env.VUE_APP_BASE_API,
      // templateUrl:
      //   this.fullPathUrl || process.env.VUE_APP_BASE_API + this.downLoadUrl,
      fileList: []
    }
  },
  computed: {
    headers() {
      return { Authorization: getToken() }
    },

    // 是否显示提示
    showTip() {
      return this.isShowTip && (this.fileType || this.fileSize)
    },

    // 上传文件服务器地址
    uploadFileUrl() {
      return process.env.VUE_APP_BASE_API + this.importUrl
    }

    // templateUrl() {
    //   return this.fullPathUrl || process.env.VUE_APP_BASE_API + this.downLoadUrl
    // }
  },
  methods: {
    importFile() {
      this.open = true
    },
    submitForm() {
      if (this.fileList.length === 0) {
        this.$modal.msgError(this.$t('pleaseImportFile'))
        return
      }
      this.$refs.fileUpload.submit()
    },
    cancel() {
      this.$refs.fileUpload.clearFiles()
      this.open = false
      this.$emit('importSuccess')
    },
    // 上传前校检格式和大小
    handleBeforeUpload(file) {
      // 校检文件类型
      if (this.fileType) {
        const fileName = file.name.split('.')
        const fileExt = fileName[fileName.length - 1]
        const isTypeOk = this.fileType.indexOf(fileExt) >= 0
        if (!isTypeOk) {
          this.$modal.msgError(
            `${this.$t('fileTypeErrorUpload')}${this.fileType.join(
              '/'
            )}${this.$t('file')}`
          )
          return false
        }
      }
      // 校检文件大小
      if (this.fileSize) {
        const isLt = file.size / 1024 / 1024 < this.fileSize
        if (!isLt) {
          this.$modal.msgError(
            `${this.$t('uploadFileSizeLess')} ${this.fileSize} MB!`
          )
          return false
        }
      }
      this.$modal.loading(this.$t('waitForUpload'))
      this.number++
      return true
    },
    // 文件个数超出
    handleExceed() {
      this.$modal.msgError(`${this.$t('uploadFileNumberLess')} ${this.limit}`)
    },
    // 上传失败
    handleUploadError() {
      this.$modal.msgError(this.$t('uploadFail'))
      this.$modal.closeLoading()
    },
    // 上传成功回调
    handleUploadSuccess(res, file) {
      if (res.code === 0) {
        this.$message({ type: 'success', message: this.$t('uploadSuccess') })
        this.cancel()
        this.$emit('handleUploadSuccess')
      } else {
        this.number--
        this.$modal.msgError(res.msg)
        this.$refs.fileUpload.handleRemove(file)
      }
      this.$modal.closeLoading()
    },
    downloadFile() {
      // document.getElementById('templateForm').submit()
      let dowloadUrl = this.downLoadUrl
      if (this.fullPathUrl) {
        dowloadUrl = this.fullPathUrl
      }
      downloadGet(dowloadUrl, `${this.fileName}.${this.fileType}`, {})
    },
    recordBtnClick() {
      this.$emit('recordBtnClick')
    },
    handleChange(file, fileList) {
      this.fileList = fileList
    }
  }
}
</script>
<style lang="scss" scoped>
.template {
  text-align: center;
  color: #1890ff;
  padding: 10px;
  span:hover {
    cursor: pointer;
    border-bottom: 1px solid #1890ff;
  }
}

.record-btn {
  position: absolute;
  right: 18px;
  top: 18px;
}
</style>
bash 复制代码
import Cookies from 'js-cookie'

const TokenKey = 'Admin-Token'

export function getToken() {
  return Cookies.get(TokenKey)
}
  • 使用组件
bash 复制代码
 <FileImport
      down-load-url="/client/template/export_template/sec_type"
      import-url="/client/sec_type/import"
      file-name="导入持仓"
/>

2 情景二

需求背景 :纯前端实现文件导入的情景。例如,一个常规excel文件填写模板,在用户的电脑上,用户上传完后,还可以在预览展示时,在线修改,改完可以下载,也可以将数据给到服务端,但这时,比如这模板数据通常不多,比如是一个团队成员这样的数据,通过文件流的形式传给后端,可能不是很理想,倒不如前端解析传那几行数据就行。
实现思路:importExcel.js 导入excel文件

  1. 使用html支持上传标签从本地获取文件,例如type为file的input,el-upload等。
  2. 利用FileReader将文件读取为二进制字符串。
  3. 使用XLSX插件XLSX.read()方法,将二进制字符串转换成excel文件的工作蒲对象workbook(简写成wb)。
  4. 通过XLSX.utils.sheet_to_json()方法,从wb中获取第一张 Sheets表格数据并将其转换为json数据。
  5. 重组json数据生成数组,即是根据自己的定义的列字段名,重新组成符合自己需求的json数据。因为从excel中提取的数据是没有字段名或字段名不符合要求的,而我们需要渲染在页面表格中又确实需要合适的字段名。
bash 复制代码
/* eslint-disable */
/* 导入excel文件 */
/**
 * @param file 文件流
 * @param tableTemplate 要导入的表格模板,一个数组,如:
 * tableTemplate: ['userCode', 'userName', 'department', 'major', 'position'],其中的值
 * 为表格的字段名,注意字段的顺序应与实际的导入excel一致。
 */
export default function importExcel (file, tableTemplate) {
  return new Promise((resolve, reject) => {
    let f = file.raw // 获取文件内容
    // 通过DOM取文件数据
    let rABS = false // 是否将文件读取为二进制字符串
    let reader = new FileReader()
    FileReader.prototype.readAsBinaryString = function (f) {
      let binary = ''
      let rABS = false // 是否将文件读取为二进制字符串
      let wb // 读取完成的数据
      let outdata
      let reader = new FileReader()
      reader.onload = function (e) {
        let bytes = new Uint8Array(reader.result)
        let length = bytes.byteLength
        for (let i = 0; i < length; i++) {
          binary += String.fromCharCode(bytes[i])
        }
        let XLSX = require('xlsx')
        if (rABS) {
          wb = XLSX.read(btoa(binary), { // 手动转化
            type: 'base64'
          })
        } else {
          wb = XLSX.read(binary, {
            type: 'binary'
          })
        }
        outdata = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]) // outdata就是表格中的值

        let arr = []
        // 下面是数据解析提取逻辑
        if (tableTemplate.length > 0) {
          let tempArr = Object.keys(outdata[0])
          let tempArrNew = []
          for (let i in tempArr) {
            for (let k in tableTemplate) {
              if (i === k) {
                tempArrNew.push({fieldE: tableTemplate[k], fieldC: tempArr[i]})
              }
            }
          }

          tempArr = tempArrNew
          outdata.map(item => {
            let obj = {}
            tempArr.map(temp2 => {
              obj[temp2.fieldE] = item[temp2.fieldC]
            })
            arr.push(obj)
          })
        }

        resolve(arr)
      }
      reader.readAsArrayBuffer(f)
    }

    if (rABS) {
      reader.readAsArrayBuffer(f)
    } else {
      reader.readAsBinaryString(f)
    }
  })
}
相关推荐
一枚小小程序员哈1 小时前
基于Vue + Node能源采购系统的设计与实现/基于express的能源管理系统#node.js
vue.js·node.js·express
一枚小小程序员哈6 小时前
基于Vue的个人博客网站的设计与实现/基于node.js的博客系统的设计与实现#express框架、vscode
vue.js·node.js·express
定栓6 小时前
vue3入门-v-model、ref和reactive讲解
前端·javascript·vue.js
LIUENG7 小时前
Vue3 响应式原理
前端·vue.js
wycode8 小时前
Vue2实践(3)之用component做一个动态表单(二)
前端·javascript·vue.js
wycode9 小时前
Vue2实践(2)之用component做一个动态表单(一)
前端·javascript·vue.js
第七种黄昏9 小时前
Vue3 中的 ref、模板引用和 defineExpose 详解
前端·javascript·vue.js
pepedd86410 小时前
还在开发vue2老项目吗?本文带你梳理vue版本区别
前端·vue.js·trae
前端缘梦10 小时前
深入理解 Vue 中的虚拟 DOM:原理与实战价值
前端·vue.js·面试
HWL567910 小时前
pnpm(Performant npm)的安装
前端·vue.js·npm·node.js