vue+el+oss上傳/預覽/下載/刪除/存數據庫(前端操做oss的詳細實現)

注:
1. 通常狀況不會讓前端直接操做oss(增刪查),但是我遇到了這樣的需求
2. 默認已經開通oss,在oss官網完成了配置
3. 須要使用到element-ui的upload組件html

點擊跳轉oss的官方API
點擊跳轉element的官方API

需求描述

  1. 前端直接操做oss,類型能夠是任何類型。本demo以圖片和壓縮包爲例,圖片能夠預覽、刪除;壓縮包能夠下載、刪除。
  2. 在表單填寫時,一個表單可能有多個不一樣類型文件的須要上傳;表單涉及新增/查看/編輯功能,複用性要強。
  3. 新增表單的具體需求:表單新增以後oss便可編輯,一旦新增過文件後要麼刪除已上傳文件,要麼提交表單,提交不成功根據後端提示從新修改表單的錯誤從新提交,或者刪除放棄提交。
  4. 查看錶單的具體需求:查看錶單不可操做oss,可是對於圖片能夠進行預覽,壓縮包能夠進行下載。
  5. 修改表單的具體需求:一旦修改過文件(刪除,新增,修改)必須提交表單,提交不成功根據後端提示從新修改表單的錯誤從新提交,直到成功爲止。
  6. F5強刷也要保證上述5條,私密麻生,我太難了,我沒作

效果展現

新增

新增

查看

查看前端

編輯

編輯

圖片預覽(放大)

圖片預覽(放大)

新增但不提交的提示

新增提示

刪除但不提交的提示

刪除提示

修改但不提交的提示

修改提示

前期準備

  1. npm install ali-oss
  2. 須要運維提供accessKeyIdaccessKeySecretbucket
  3. 其中bucket最好區分開發環境和生產環境
  4. oss官方推薦使用STS臨時受權訪問,我這裏沒有用,有興趣的話能夠移步OSS臨時受權

utils.js(工具類)

import axios from 'axios'
import OSS from 'ali-oss'
import UUID4 from 'uuid/v4'

// 下載oss指定地址
export function downloadURL(url) {
  let link = document.createElement('a');
  link.style.display = 'none';
  link.href = url;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}

// 上傳oss
export function ossUpload(picForProject, fileBuffer) {
  let bucketName = '';
  // 判斷環境 生產環境而且不爲測試狀態
  process.env.NODE_ENV === 'production' ? bucketName = '填你本身申請的' : bucketName = '填你本身申請的';

  let client = new OSS({
    region: 'oss-cn-beijing',
    secure: true, // https
    accessKeyId: '填你本身申請的',
    accessKeySecret: '填你本身申請的',
    bucket: bucketName
  });

  const fileName = UUID4().toUpperCase().replace(/-/g, '')
  const suffix = fileBuffer.name.substr(fileBuffer.name.indexOf('.'))
  const url = `${picForProject}${fileName}${suffix}`

  async function put() {
    try {
      await client.put(url, fileBuffer)
      // await xxx(url) 在這裏也能夠直接把地址傳給後端
      return { 'code': 200, 'url': url }
    } catch (e) {
      return e.message
    }
  }

  return put()
};

// 獲取oss地址
export function showPrivateOss(picName) {
  // 這裏和上傳oss的client是同樣的,應該放在store裏面同一管理的,demo這裏寫在這裏了
  let bucketName = '';
  process.env.NODE_ENV === 'production' ? bucketName = '填你本身申請的' : bucketName = '填你本身申請的';

  let client = new OSS({
    secure: true, // https
    accessKeyId: '填你本身申請的',
    accessKeySecret: '填你本身申請的',
    bucket: bucketName,
    endpoint: 'oss-cn-beijing.aliyuncs.com'
  });

  async function getOssAddr() {
    try {
      let signUrl = client.signatureUrl(picName, { expires: 1800 }); // expires單位爲秒
      return { 'code': 200, 'url': signUrl }
    } catch (e) {
      return e.message
    }
  }

  return getOssAddr()
};

// 刪除oss
export function deletePrivateOss(picName) {
  let bucketName = '';
  process.env.NODE_ENV === 'production' ? bucketName = '填你本身申請的' : bucketName = '填你本身申請的';

  let client = new OSS({
    secure: true, // https
    accessKeyId: '填你本身申請的',
    accessKeySecret: '填你本身申請的',
    bucket: bucketName,
    endpoint: 'oss-cn-beijing.aliyuncs.com'
  });

  async function deleteOssAddr() {
    try {
      return client.delete(picName);
    } catch (e) {
      return e.message
    }
  }

  return deleteOssAddr()
};

oss.vue(oss組件)

<template>
  <div>
    <el-upload
      class="avatar-uploader"
      action="oss地址"
      :show-file-list="false"
      :http-request="uploadToOss"
      :before-upload="beforeAvatarUpload"
      :disabled="disabled"
      :accept="accept"
      :typeName="typeName"
    >
      <div v-loading="loading">
        <template v-if="haveUrl">
          <img :src="imgUrl" class="avatar" />
          <span class="show_icon" v-if="!disabled">
            <i class="el-icon-view" @click.stop="handleView()" v-if="typeName==='圖片'"></i>
            <i class="el-icon-download" @click.stop="handleDownload()" v-else></i>
            <i class="el-icon-delete" @click.stop="handleRemove()"></i>
          </span>
          <span class="show_icon" v-else>
            <i class="el-icon-view" @click.stop="handleView()" v-if="typeName==='圖片'"></i>
            <i class="el-icon-download" @click.stop="handleDownload()" v-else></i>
          </span>
        </template>
        <i v-else class="el-icon-plus avatar-uploader-icon"></i>
      </div>
    </el-upload>
    <!--查看圖片-->
    <el-dialog title="照片詳情" :visible.sync="showBigPic" width="720px" append-to-body>
      <img :src="imgUrl" width="100%" />
    </el-dialog>
  </div>
</template>

<script>
// 引用工具類(下載指定地址的oss文件、上傳oss、展現oss圖片、刪除指定地址的oss文件)
import {
  downloadURL,
  ossUpload,
  showPrivateOss,
  deletePrivateOss,
} from "@/assets/scripts/utils";
// 二次封裝的element的message組件,代碼略
import { errorMsg, successMsg } from "@/components/message";

export default {
  props: {
    //對文件的限制
    limit: {
      type: Object,
      default: function () {
        return {
          size: 5,
          types: ["image/jpeg", "image/png"],
        };
      },
    },
    // 是否有url
    haveUrl: {
      type: Boolean,
      default: false,
    },
    // 是否禁用
    disabled: {
      type: Boolean,
      default: false,
    },
    // url的名稱
    urlName: {
      type: String,
      default: "",
    },
    // 對應的後端字段名,同一頁面可能有多個須要上傳oss的
    name: {
      type: String,
      default: "",
    },
    // 上傳彈窗,過濾文件類型
    accept: {
      type: String,
      default: "image/*",
    },
    // 上傳文件類型(文件大小過大提示語也會用到)
    typeName: {
      type: String,
      default: "圖片",
    },
  },
  data() {
    return {
      showBigPic: false, // 預覽圖片
      imgUrl: null,     // 圖片地址
      ossFileName: "", // oss文件名
      loading: false, // 是否上傳中
    };
  },
  watch: {
    urlName: {
      handler(newValue, oldValue) {
        if (newValue) {
          if (this.typeName !== "圖片" && this.haveUrl) {
            this.imgUrl = require("@/assets/images/uploaded.jpg");
          } else {
            showPrivateOss(newValue).then((res) => {
              if (res && res.code) {
                this.imgUrl = res.url;
              } else {
                errorMsg(res);
              }
            });
          }
        }
      },
      immediate: true,
    },
  },
  methods: {
    handleView() {
      this.showBigPic = true;
    },
    handleDownload() {
      showPrivateOss(this.urlName).then((res) => {
        if (res && res.code) {
          downloadURL(res.url);
        } else {
          errorMsg(res);
        }
      });
    },
    handleRemove() {
      let _this = this;
      deletePrivateOss(this.urlName).then((res) => {
        if (res.res.status === 204) {
          successMsg();
          // ossDone參數:url文件地址, haveUrl是否有地址, name字段名
          this.$emit("callback", null, false, _this.name);
        } else {
          errorMsg(res);
        }
      });
    },
    // 上傳oss
    uploadToOss(elUpload) {
      this.loading = true;
      let _this = this;
      ossUpload("back-image/" + this.ossFileName, elUpload.file).then((res) => {
        if (res && res.code) {
          // ossDone參數:url文件地址, haveUrl是否有地址, name字段名
          this.$emit("callback", res.url, true, _this.name);
        } else {
          errorMsg(res);
        }
        this.loading = false;
      });
    },
    // 圖片上傳前 驗證圖片大小
    beforeAvatarUpload(file) {
      let isSize = false;
      if (this.limit.sizeType === "kb") {
        isSize = file.size / 1024 < this.limit.size;
      } else {
        isSize = file.size / 1024 / 1024 < this.limit.size;
      }
      if (!isSize) {
        errorMsg(
          `${this.typeName}大小不能超過${this.limit.size}${
            this.limit.sizeType ? this.limit.sizeType : "MB"
          }!`
        );
      }
      return isSize;
    },
  },
};
</script>

<style lang="less" scoped>
.show_icon {
  display: inline-block;
  position: absolute;
  top: 0;
  right: 0;
  width: 108px;
  height: 72px;
  i {
    display: none;
  }
  &:hover {
    background-color: rgba(0, 0, 0, 0.5);
    i {
      display: inline;
      line-height: 72px;
      color: #fff;
      font-size: 20px;
      &:not(:last-child) {
        margin-right: 10px;
      }
    }
  }
}
</style>

addOrEdit.vue(表單組件;新增/查看/編輯爲同一個組件)

/*
**這是一個彈窗組件,爲了代碼簡潔,刪除了其餘表單內容
**父組件傳來id,即斷定是查看/編輯,須要請求後端詳情
**:before-close須要帶參數,重要!!
*/
<template>
  <el-dialog
    @open="setData"
    :visible.sync="params.show"
    :before-close="() => cancel(false)"
    :close-on-click-modal="false"
    width="1100px"
    append-to-body
  >
    <div slot="title">
      {{params.title}}
      <span class="title_required">
        <span>*</span>爲必填項
      </span>
    </div>
    <el-form
      :model="addForm"
      :inline="true"
      ref="addForm"
      label-position="left"
      label-width="125px"
    >
      // 其餘item略
      <el-form-item
        label="營業執照照片"
        prop="businessLicenseUrl"
        :rules="[{required: true,message: '不能爲空'}]"
      >
        <oss
          @callback="ossDone"
          :haveUrl="haveUrl_businessLicenseUrl"
          :disabled="isDisabled"
          :urlName="addForm.businessLicenseUrl"
          name="businessLicenseUrl"
        />
      </el-form-item>
      <el-form-item label="附件">
        <oss
          @callback="ossDone"
          :limit="limit"
          :haveUrl="haveUrl_attachment"
          :urlName="addForm.attachment"
          :disabled="isDisabled"
          accept=".zip, .rar"
          name="attachment"
          typeName="壓縮包"
        />
      </el-form-item>
    </el-form>
    <span slot="footer">
      <el-button @click="cancel(false)" class="cancelBtn">取 消</el-button>
      <el-button
        @click="submit"
        type="primary"
        class="confirmBtn"
        v-if="this.params.title !=='查看'"
      >確 定</el-button>
    </span>
  </el-dialog>
</template>

<script>
import oss from "@/components/oss";
import { methodPost } from "@/api";  // 新增/修改用【代碼略】
import { GET_DETAILS } from "@/api/company"; //詳情【代碼略】
import { errorMsg, successMsg } from "@/components/message"; // 二次封裝的message【代碼略】

export default {
  components: { oss },
  data() {
    return {
      // 是否有執照
      haveUrl_businessLicenseUrl: false,
      // 是否有附件
      haveUrl_attachment: false,
      // 編輯時,附件的返回值(做爲flag)
      flag_attachment: null,
      // 編輯時,執照的返回值(做爲flag)
      flag_businessLicenseUrl: null,
      // 附件的大小
      limit: {
        size: 10
      }
    };
  },
  methods: {
    // 操做oss後的$emit
    ossDone(url, haveUrl, name) {
      this.$set(this.addForm, `${name}`, url);
      this.$set(this, `haveUrl_${name}`, haveUrl);
    },
    // 彈窗初始化
    setData() {
      this.addForm = {};
      this.isDisabled = false;
      if (this.params.title !== "新增") {
        this.haveUrl_businessLicenseUrl = true;
        this.getDetails({ id: this.params.id });
      } else {
        this.haveUrl_attachment = false;
        this.haveUrl_businessLicenseUrl = false;
        this.flag_attachment = null;
        this.flag_businessLicenseUrl = null;
      }
    },
    getDetails(id) {
      GET_DETAILS(id).then((res) => {
        if (res.code === 10000) {
          this.addForm = res.result;
          this.flag_attachment = res.result.attachment;
          this.flag_businessLicenseUrl = res.result.businessLicenseUrl;
          if (res.result.attachment) {
            this.haveUrl_attachment = true;
          } else {
            this.haveUrl_attachment = false;
          }
        }
        if (this.params.title === "查看") {
          this.isDisabled = true;
        }
      });
    },
    submit() {
      // 新增/編輯
      this.$refs.addForm.validate((valid) => {
        if (valid) {
          let url = "/company/";
          if (this.params.data) {
            url += "update";
          } else {
            url += "insert";
          }
          methodPost(url, this.addForm, true).then((res) => {
            if (res.code === 10000) {
              successMsg();
              this.cancel(true);
            }
          });
        } else {
          errorMsg();
        }
      });
    },
    cancel(refresh) {
      if (this.params.title !== "新增") {
        if (!refresh) {
          if (this.flag_attachment !== this.addForm.attachment) {
            errorMsg("修改附件後,請點擊‘肯定’按鈕");
            return;
          }
          if (
            this.flag_businessLicenseUrl !== this.addForm.businessLicenseUrl
          ) {
            errorMsg("修改營業執照後,請點擊‘肯定’按鈕");
            return;
          }
        }
      } else {
        if (this.addForm.businessLicenseUrl || this.addForm.attachment) {
          errorMsg("點擊‘肯定’提交新增內容,或刪除已上傳文件");
          return;
        }
      }
      this.$refs.addForm.resetFields();
      this.$emit("callback", refresh); // 通知父組件刷新list頁,按實際取捨,父組件代碼略
    },
  },
};
</script>
相關文章
相關標籤/搜索