手把手教你学vue - 15 - 上传

140 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

前端熟知的上传一般多为:上传图片、上传Excel、上传Word...

我们根据是否上传到服务器,是否需要前端解析分为两类

准备工作

  • 使用RAM用户或STS方式访问

    由于阿里云账号AccessKey拥有所有API访问权限,建议遵循阿里云安全最佳实践。如果部署在服务端可以使用RAM用户或STS来进行API访问或日常运维管控操作,如果部署在客户端请使用STS方式来进行API访问。更多信息,请参见访问控制

  • 设置跨域资源共享(CORS)

    具体操作,请参见设置跨域资源共享

    通过浏览器直接访问OSS时,CORS配置规则要求如下:

    • 来源:设置精准域名(例如www.aliyun.com)或带有通配符星号(*)的域名(例如*.aliyun.com)。
    • 允许Methods:请根据实际使用场景,选择不同的Methods。例如分片上传时,设置为PUT;删除文件时,设置为DELETE。
    • 允许Headers:设置为*
    • 暴露Headers:设置为ETagx-oss-request-idx-oss-version-id

image.png

上传到服务器 - OSS上传

我司使用的是阿里云的OSS上传

可以上传图片、Word、Pdf...

浏览器支持

  • IE(>=10)
  • Edge
  • 主流版本的Chrome、Firefox、Safari
  • 主流版本的Android、iOS、Windows Phone系统默认浏览器

配置OSS

1. 安装sdk插件

npm install ali-oss

2. 配置参数变量

const OSS = require('ali-oss');

const client = new OSS({
    // yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
    region: 'yourRegion',
    // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
    accessKeyId: 'yourAccessKeyId',
    accessKeySecret: 'yourAccessKeySecret',
    // 从STS服务获取的安全令牌(SecurityToken)。
    stsToken: 'yourSecurityToken',
    refreshSTSToken: async () => {
    // 向您搭建的STS服务获取临时访问凭证。
      const info = await fetch('your_sts_server');
      return {
        accessKeyId: info.accessKeyId,
        accessKeySecret: info.accessKeySecret,
        stsToken: info.stsToken
      }
    },
    // 刷新临时访问凭证的时间间隔,单位为毫秒。
    refreshSTSTokenInterval: 300000,
    // 填写Bucket名称。
    bucket: 'examplebucket'
});

3. Component组件

<div class="upload-container">
    <el-upload
      class="upload-container"
      :data="dataObj"
      :multiple="false"
      :show-file-list="false"
      drag
      :on-change="getFile"
      :auto-upload="false"
    >
      <i class="el-icon-upload" />
      <div class="el-upload__text">
        将文件拖到此处,或<em>点击上传</em>
      </div>
    </el-upload>
    <div class="image-preview">
      <div v-show="imageUrl.length>1" class="image-preview-wrapper">
        <img :src="imageUrl+'?imageView2/1/w/200/h/200'">
        <div class="image-preview-action">
          <i class="el-icon-delete" @click="rmImage" />
        </div>
      </div>
    </div>
  </div>

4. 上传的关键代码

OSS支持两种使用方式

  • 同步方式:基于ES7规范的async/await,使得异步方式同步化。
  • 异步方式:与Callback方式类似,API接口返回Promise,使用then()处理返回结果,使用catch()处理错误。

这里,使用的时第一种同步方式

无论同步方式还是异步方式,均使用new OSS()创建client。

getFile (file, fileList) {
  // 校检文件类型
  if (this.fileType.length > 0) {
    let filename = file.name.split('.')[1]
    const isTypeOk = this.fileType.some((type) => {
      return type.indexOf(filename) > -1
    })

    if (!isTypeOk) {
      this.$message.error(
        this.$store.getters.lableProp[2342] + this.fileType.join(',')
      )
      return false
    }
  }

  // 校检文件大小
  if (this.fileSize > 0) {
    const isLt = file.size / 1024 / 1024 < this.fileSize
    if (!isLt) {
      this.$message.error(
        this.$store.getters.lableProp[2343] + this.fileSize + 'MB'
      )
      return false
    }
  }

  this.getBase64(file.raw).then((source) => {
    this.source = source

    let data = this.base64ToBlob(this.source)

    this.putObject(file, data)
  })
},
// 转换成base64
getBase64 (file) {
  return new Promise(function (resolve, reject) {
    const reader = new FileReader()
    let imgResult = ''
    reader.readAsDataURL(file)
    reader.onload = function () {
      imgResult = reader.result
    }
    reader.onerror = function (error) {
      reject(error)
    }
    reader.onloadend = function () {
      resolve(imgResult)
    }
  })
},
async putObject (file, data) {
  try {
    const result = await this.ossClient.put(
      parseTime(new Date().getTime(), '{y}{m}{d}') +
        '/' +
        file.uid +
        '.' +
        file.name.split('.')[1],
      data
    )
    this.dialogVisible = false

    this.$emit('onSuccessImg', result.name)

    this.$message({
      message: '上传成功'
      type: 'success'
    })
  } catch (error) {
    console.log('Upload OSS Error')
    console.log(error)

    this.$message.error('上传失败')
  }
}

删除图片

this.ossClient.delete(文件名)

前端解析 - Excel上传

前端解析,就是将文件解析成数组,然后生成一个resultsheader

html

<div>
  <input
    ref="excel-upload-input"
    class="excel-upload-input"
    type="file"
    accept=".xlsx, .xls, .csv"
    @change="handleClick"
  />
  <div
    class="drop center"
    @drop="handleDrop"
    @dragover="handleDragover"
    @dragenter="handleDragover"
  >
    <span class="note">{{ lableProp[1826] }}</span>
    <el-button
      :loading="loading"
      style="margin-left: 16px"
      size="mini"
      type="primary"
      @click="handleUpload"
    >
      {{ lableProp[1827] }}
    </el-button>
  </div>
  <div>
    {{ lableProp[2840] }}
  </div>
</div>

css

.excel-upload-input {
  display: none;
  z-index: -9999;
}
.drop {
  border: 2px dashed #bbb;
  width: 100%;
  height: 160px;
  margin: 0 auto;
  border-radius: 5px;
  text-align: center;
  /* color: #bbb; */
  position: relative;
}

.note {
  font-size: 14px;
}

js

import XLSX from 'xlsx'

export default {
  props: {
    beforeUpload: Function, // eslint-disable-line
    onSuccess: Function// eslint-disable-line
  },
  data() {
    return {
      loading: false,
      excelData: {
        header: null,
        results: null
      }
    }
  },
  methods: {
    generateData({ header, results }) {
      this.excelData.header = header
      this.excelData.results = results
      this.onSuccess && this.onSuccess(this.excelData)
    },
    handleDrop(e) {
      e.stopPropagation()
      e.preventDefault()
      if (this.loading) return
      const files = e.dataTransfer.files
      if (files.length !== 1) {
        this.$message.error('Only support uploading one file!')
        return
      }
      const rawFile = files[0] // only use files[0]

      if (!this.isExcel(rawFile)) {
        this.$message.error('Only supports upload .xlsx, .xls, .csv suffix files')
        return false
      }
      this.upload(rawFile)
      e.stopPropagation()
      e.preventDefault()
    },
    handleDragover(e) {
      e.stopPropagation()
      e.preventDefault()
      e.dataTransfer.dropEffect = 'copy'
    },
    handleUpload() {
      this.$refs['excel-upload-input'].click()
    },
    handleClick(e) {
      const files = e.target.files
      const rawFile = files[0] // only use files[0]
      if (!rawFile) return
      this.upload(rawFile)
    },
    upload(rawFile) {
      this.$refs['excel-upload-input'].value = null // fix can't select the same excel

      if (!this.beforeUpload) {
        this.readerData(rawFile)
        return
      }
      const before = this.beforeUpload(rawFile)
      if (before) {
        this.readerData(rawFile)
      }
    },
    readerData(rawFile) {
      this.loading = true
      return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.onload = e => {
          const data = e.target.result
          const workbook = XLSX.read(data, { type: 'array' })
          const firstSheetName = workbook.SheetNames[0]
          const worksheet = workbook.Sheets[firstSheetName]
          const header = this.getHeaderRow(worksheet)
          const results = XLSX.utils.sheet_to_json(worksheet)
          this.generateData({ header, results })
          this.loading = false
          resolve()
        }
        reader.readAsArrayBuffer(rawFile)
      })
    },
    getHeaderRow(sheet) {
      const headers = []
      const range = XLSX.utils.decode_range(sheet['!ref'])
      let C
      const R = range.s.r
      /* start in the first row */
      for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
        const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
        /* find the cell in the first row */
        let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
        if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
        headers.push(hdr)
      }
      return headers
    },
    isExcel(file) {
      return /\.(xlsx|xls|csv)$/.test(file.name)
    }
  }
}

组件调用

<upload-excel-component :on-success="handleSuccess" :before-upload="beforeUpload" />

beforeUpload(file) {
    const isLt1M = file.size / 1024 / 1024 < 1

    if (isLt1M) {
      return true
    }

    this.$message({
      message: 'Please do not upload files larger than 1m in size.',
      type: 'warning'
    })
    return false
  },
handleSuccess({ results, header }) {
    this.tableData = results
    this.tableHeader = header
  }