1. 程式人生 > 其它 >Vue+Element+物件儲存 圖片上傳元件簡單封裝

Vue+Element+物件儲存 圖片上傳元件簡單封裝

環境參考:vue2.6.10, element-ui2.13.2, 阿里雲物件儲存OSS

描述

Element 元件庫中的上傳元件的為基礎,新增上傳進度展示、上傳遮罩、上傳到物件儲存,控制上傳數量,大圖預覽

效果展示

元件程式碼

NPM包準備

# 安裝 Element 元件庫
npm i element-ui -S

# 安裝阿里雲物件儲存OSS
npm i ali-oss -S

結構

<template>
  <div class="image-upload-container">
    <!-- 圖片上傳 -->
    <el-upload
      list-type="picture-card"
      :file-list="fileList"
      :before-upload="beforeUpload"
      :on-progress="handleProgress"
      :on-success="handleSuccess"
      :on-error="handleError"
      action=""
      :http-request="handleUpload"
      :class="{'hide-plus': imgLen}"
    >
      <i class="el-icon-plus" />
      <!-- 利用作用域插槽自定義圖片上傳的展示邏輯 -->
      <template #file="{file}">
        <img class="el-upload-list__item-thumbnail" :src="file.url" alt="">
        <!-- 上傳成功標示 -->
        <label class="el-upload-list__item-status-label"><i class="el-icon-upload-success el-icon-check" /></label>
        <!-- 滑鼠懸浮操作選項 -->
        <span class="el-upload-list__item-actions">
          <span class="el-upload-list__item-preview" @click="handlePreview(file)"> <i class="el-icon-zoom-in" /> </span>
          <span class="el-upload-list__item-delete" @click="handleRemove(file)"> <i class="el-icon-delete" /> </span>
        </span>
        <!-- 遮罩及進度條 -->
        <template v-if="file.status !== 'success' && progressBar.isShow">
          <div class="upload-mask" />
          <el-progress class="upload-progress" type="circle" :percentage="progressBar.percentage" />
        </template>
      </template>
    </el-upload>

    <!-- 圖片大圖預覽彈窗 -->
    <el-dialog width="80%" title="圖片預覽" :visible.sync="previewDialog.isShow">
      <img width="100%" :src="previewDialog.imgUrl" alt="">
    </el-dialog>
  </div>
</template>

樣式

<style scoped lang="scss">
.image-upload-container {
  .hide-plus::v-deep {
    .el-upload.el-upload--picture-card { // 隱藏上傳按鈕
      display: none;
    }
  }
  .el-upload-list__item {
    &.is-success .el-upload-list__item-status-label {
      display: block!important; // 避免右上角上傳標誌在圖片hover時(由於權重不夠)被隱藏
    }
    .upload-mask { // 遮罩
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      background-color: rgba(0, 0, 0, 0.8);
    }
    .upload-progress::v-deep { // 進度條文字與遮罩形成顏色反差
      .el-progress__text {
        color: #fff!important;
      }
    }
  }
}
</style>

行為

<script>
import OSS from 'ali-oss'
export default {
  name: 'ImageUpload',
  props: {
    limit: { // 上傳數量限制
      type: Number,
      default: 1
    },
    defaultImage: { // 外部傳入的需要顯示在列表的圖片的地址(對預設圖片大於上傳限制的情況沒有做處理,全部展示)
      // String: 'http://......'
      // Aarray: ['http://......', 'http://......', ...]
      type: [String, Array],
      default: ''
    }
  },
  data() {
    return {
      previewDialog: { // 圖片預覽彈窗資訊
        isShow: false,
        imgUrl: ''
      },
      progressBar: { // 控制上傳進度條
        isShow: false,
        percentage: 0
      },
      fileList: [], // 存放圖片的列表
      fileFormat: '' // 存放待上傳檔案的格式
    }
  },
  computed: {
    imgLen() { // 設定一個計算屬性 判斷是否已到達上傳限制
      return this.fileList.length >= this.limit
    }
  },
  watch: {
    defaultImage: { // 偵聽是否有預設圖片需要展示
      handler(newVal) {
        this.fileList = []
        this.handleDefaultImageProp(newVal)
      }
    }
  },
  methods: {
    initOSS() { // 阿里雲物件儲存OSS初始化 例項化OSS Client
      return new OSS({
        // yourRegion填寫Bucket所在地域。以華東1(杭州)為例,Region填寫為oss-cn-hangzhou。
        region: 'oss-cn-beijing', // 必填
        // 從STS服務獲取的臨時訪問金鑰(AccessKey ID和AccessKey Secret)。
        accessKeyId: 'LTAI5tH62wHWz1XkPT2wJKXE', // 必填
        accessKeySecret: 'XkhUCoERJmrPHCBmp9kFmyEo76homI', // 必填
        // // 從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: 'classlate1' // 必填
      })
    },
    handlePreview(file) { // 圖片大圖預覽
      this.previewDialog = { imgUrl: file.url, isShow: true }
    },
    handleRemove(file) { // 處理圖片刪除
      this.fileList.some((item, idx) => {
        if (item.uid === file.uid) {
          this.fileList.splice(idx, 1)
          this.$message.success('圖片刪除成功')
          return true
        } else {
          return false
        }
      })
    },
    async handleUpload({ file, onProgress, onSuccess, onError }) { // 覆蓋預設的上傳行為,可以自定義上傳的實現
      this.progressBar.isShow = true // 展示上傳進度條
      onProgress('開始上傳') // 呼叫進度回撥,對ready狀態檔案進行處理
      const that = this // 存放當前vue元件例項物件
      const client = this.initOSS() // 物件儲存例項化
      const fileName = `img/banner${Date.parse(new Date())}.${this.fileFormat}` // 自定義上傳檔案的檔名
      let netUrl = '' // 檔案的線上地址,後面作為響應資料進行返回
      try { // 上傳到遠端 阿里雲 物件儲存OSS // multipartUpload 分片上傳支援上傳進度,簡單上傳put 不支援上傳進度
        // fileName 表示上傳到OSS的檔名稱,支援路徑
        // file 表示瀏覽器中需要上傳的檔案,支援HTML5 file和Blob型別
        const { res: { status, requestUrls }} = await client.multipartUpload(fileName, file, {
          progress(p) { // 上傳進度回撥
            console.log('進度: ', p)
            that.progressBar.percentage = Number.parseInt(p * 100) // 同步上傳進度
          },
          partSize: 1024 * 100 // 分塊大小, 最小為100k
        })
        if (!status === 200) throw new Error() // 上傳不成功丟擲異常
        netUrl = requestUrls[0].split('?')[0] // 處理線上地址,準備傳入檔案上傳(成功)回撥
      } catch {
        onError('上傳失敗') // 異常中呼叫檔案上傳失敗的回撥
      } finally {
        this.progressBar = { // 重置進度條
          isShow: false,
          percentage: 0
        }
      }
      return netUrl // 如果上面沒有上傳失敗的回撥,此處返回的資料會作為響應傳入成功回撥
    },
    handleProgress(event, file, fileList) { // 處理上傳進度,預先放入本地檔案(使用blob地址)
      console.log('進度回撥')
      if (this.limit === 1) { // 圖片數量上限限制為一張時直接替換,否則新增
        this.fileList = [{ ...file }]
      } else {
        this.fileList.push({ ...file })
      }
    },
    handleSuccess(res, file, fileList) { // 處理上傳成功
      console.log('成功回撥')
      // 實際觀察後發現 file.response 中存放的就是上傳(這裡就是函式handleProgress的返回值)的結果
      // 且直接修改file 引數物件會直接影響到 資料變數 fileList 中的對應元素 (或者可以遍歷檔案列表尋找相同uid檔案對之進行操作)
      file.url = file.response
      this.$message.success('圖片上傳成功')
    },
    handleError(err, file, fileList) { // 處理上傳失敗
      console.log('失敗回撥')
      console.log(err)
      this.handleRemove(file) // 刪除圖片
      this.$message.error('圖片上傳失敗')
    },
    beforeUpload(file) { // 上傳前的操作 返回布林值決定是否繼續上傳
      const types = ['image/png', 'image/jpeg', 'image/gif']
      if (!types.includes(file.type)) { // 檢測檔案的型別
        this.$message.error('必須上傳png,jpg,jpeg,gif格式的檔案')
        return false
      }
      if (file.size / 1024 / 1024 > 1) { // 檢測檔案的大小(限制1M以內)
        this.$message.error('圖片不可以超過1M')
        return false
      }
      this.fileFormat = file.name.split('.').at(-1) // 這裡直接使用了陣列新API `at` ,如果需要考慮低版本瀏覽器可以利用陣列長度-1
      return true
    },
    handleDefaultImageProp(data) { // 對外部傳入的圖片進行處理
      if (data && typeof data === 'string') {
        this.fileList.push({ url: data })
      } else if (Array.isArray(data)) {
        this.fileList.push(...data.reduce((acc, val) => {
          acc.push({ url: val })
          return acc
        }, []))
      } else {
        return false
      }
    }
  }
}
</script>

呼叫示例

<image-upload />
<image-upload :limit="3" />
<image-upload :limit="3" :default-image="'http://xxxx.jpg'" />
<image-upload :limit="3" :default-image="['http://xxxx.jpg', 'http://xxxx.jpg']" />