uni小程序使用vant上传组件,封装腾讯COS上传hooks

前言

最近又负责了个uni小程序的开发,拉下代码一看,大为吃惊,vue2+js的老项目,底层几乎什么都没封装处理,连上传文件都没有,可把我烦躁坏了,没办法只能封装处理下,之前也封装过腾讯COS的上传,但是那是后台的,而且我感觉当初写的还是有点不太好,除此之外小程序配合vant的还是有点区别。

代码

/*
 * @Author       : 桔子
 * @Date         : 2024-04-25 17:11:11
 * @LastEditors  : 桔子
 * @LastEditTime : 2024-04-26 09:26:03
 * @Description  : 文件上传
 * @FilePath     : /hooks/web/useUploadFile.js
 */
// 引入cos-wx-sdk-v5
import COS from "cos-wx-sdk-v5";
import dayjs from "dayjs";
import md5 from "@/config/md5.min"; // md5加密

const basic_url = "https://xxxxx.com/xx/xxx"; // 获取oss基本信息

const src = 'xxx'

let cos = null; // 存放 new 出的对象

let oss_token = null; // cos相关的token

export const useUploadFile = () => {
  /**
   * @Description: 初始化对应的cos对象
   * @return {*}
   */
  const initCos = async () => {
    const result = await uni.$originalRequest({
      url: basic_url,
      data: { src },
      method: "POST",
    });

    oss_token = result?.data?.data || null;

    const {
      oss_expired_time,
      oss_session_token,
      oss_start_time,
      oss_tmp_secret_id,
      oss_tmp_secret_key,
    } = oss_token || {};

    // new 一个COS对象
    cos = new COS({
      // 必选参数
      SimpleUploadMethod: "putObject",
      getAuthorization: (options, callback) => {
        callback({
          TmpSecretId: oss_tmp_secret_id,
          TmpSecretKey: oss_tmp_secret_key,
          XCosSecurityToken: oss_session_token,
          StartTime: oss_start_time, // 时间戳,单位秒,如:1580000000
          ExpiredTime: oss_expired_time, // 时间戳,单位秒,如:1580000900
          ScopeLimit: true, // 细粒度控制权限需要设为 true,会限制密钥只在相同请求时重复使用--如果是要单次使用,即每次上传都需要获取一次签名时,那么就需要改为false
          ProgressInterval: 0.001,
        });
      },
    });
  };

  /**
   * @Description: 处理文件名
   * @param {*} file
   * @param {*} now_time
   * @return {*}
   */
  const getFileName = (file, now_time) => {
    const { type, url } = file || {};
    
    const name_arr = url.split("."); // 转为数组
    
    let file_name_type = name_arr[name_arr.length - 1]; // 最后一个是文件类型
    file_name_type =
      type === "video"
        ? `-video.${file_name_type}`
        : `-img.${file_name_type}`;
        
    name_arr.pop(); // 删除最后一项文件类型的
    const deal_file_name = name_arr.join(""); // 转成字符串
    let file_name = `${now_time}-${md5(deal_file_name)}${file_name_type}`;

    return file_name;
  };

  /**
   * @Description: 判断token是否过期
   * @return {*}
   */
  const judgeOssToken = async () => {
    const now_time = +dayjs().unix();
    const now_time_end = now_time + 60 * 5; // 这里之所以加五分钟的秒数,是担心不加的时候,现在可能满足条件,但是后面请求可能恰好到了过期时间,从而导致上传失败
    const oss_expired_time_end = oss_token?.oss_expired_time || 0;

    if (oss_expired_time_end < now_time_end) {
      await initCos();
    }

    return {
      now_time,
    };
  };

  /**
   * @Description: 上传文件
   * @param {*} file
   * @return {*}
   */
  const uploadFile = async (file) => {
    const { now_time } = await judgeOssToken();

    const { type, url } = file || {};
    const { oss_bucket, oss_region, oss_path, oss_cdn_domain } =
      oss_token || {};
    const file_name = getFileName(file, now_time);
    const file_key = `${oss_path}${file_name}`;
    const put_obj = {
      Bucket: oss_bucket,
      Region: oss_region,
      Key: file_key,
      FilePath: url,
      Headers:
        type === "video"
          ? null
          : {
              "Pic-Operations":
                '{"is_pic_info": 1, "rules": [{"fileid": "' +
                file_name +
                '", "rule": "imageMogr2/thumbnail/600x/"}]}',
            },
      onProgress: (info) => {
        console.log(JSON.stringify(info));
      },
    };

    return new Promise((resolve, reject) => {
      cos.putObject(put_obj, (err, data) => {
        if (err) {
          uni.showToast({
            title: "网络错误,请重新上传图片!",
            icon: "none",
          });

          resolve({ code: 0, data: err });
        } else {
          const reesult = `https://${oss_cdn_domain}${
            data.Location.split(".com")[1]
          }`;
          resolve({ code: 1, data: reesult, file_key });
        }
      });
    });
  };

  /**
   * @Description: 删除文件
   * @param {*} file_key
   * @return {*}
   */
  const deleteFile = async (file_key) => {
    await judgeOssToken();

    const { oss_bucket, oss_region } = oss_token || {};
    const delete_obj = {
      Bucket: oss_bucket,
      Region: oss_region,
      Key: file_key,
    };

    return new Promise((resolve, reject) => {
      cos.deleteObject(delete_obj, (err, data) => {
        if (err) {
          resolve({ code: 0, data: err });
        } else {
          resolve({ code: 1, data: data });
        }
      });
    });
  };

  return {
    uploadFile,
    deleteFile,
  };
};

uni.$originalRequest其实就是uni.request哈,我不喜欢里面通过success或者fail返回结果哈,因为我感觉里面写太多逻辑,不易于阅读,就很简单的套了一层promise,如下

/**
 * @Description: 原始的uni.request封一层
 * @param {*} row
 * @return {*}
 */
export const originalRequest = (row) => {
  return new Promise((resolve, reject) => {
    uni.request({
      ...row,
      success: (res) => {
        resolve(res);
      },
      fail: (err) => {
        reject(err);
      },
    });
  });
};

至于上面的基础oss请求地址和那个参数找公司配置的人要哈。

组件使用

<!--
 * @Author       : 桔子
 * @Date         : 2024-04-25 13:41:56
 * @LastEditors  : 桔子
 * @LastEditTime : 2024-04-26 10:10:44
 * @Description  : 上传文件组件
 * @FilePath     : /components/UploadFile/index.vue
-->
<template>
  <view :class="{ basic_upload_wrap: defaultStyle }">
    <van-uploader
      :fileList="file_list"
      :multiple="multiple"
      :maxCount="maxCount"
      :accept="accept"
      :readonly="uploadFlag"
      :deletable="deletable"
      :use-before-read="true"
      @before-read="beforeRead"
      @after-read="afterRead"
      @delete="deleteRead"
    >
      <slot></slot>
    </van-uploader>
  </view>
</template>

<script>
import { useUploadFile } from "@/hooks/web/useUploadFile";
import { dealListData } from "./format";
export default {
  name: "UploadFile",

  props: {
    defaultStyle: {
      type: Boolean,
      default: true,
    },

    /**
     * @Description: 格式['地址', ‘地址’]或者[{url: '地址', status: 'done', file_key: '文件key'}]
     * @return {*}
     */
    initList: {
      type: Array,
      default: () => [],
    },

    // 是否开启多选
    multiple: {
      type: Boolean,
      default: false,
    },

    maxCount: {
      type: Number,
      default: 1,
    },

    accept: {
      type: String,
      default: "all",
    },

    deletable: {
      type: Boolean,
      default: true,
    },
  },

  data() {
    return {
      file_list: [], // 图片列表
      uploadFlag: false, // 是否可上传
    };
  },

  created() {
    this.autoInitList();
  },

  methods: {
    /**
     * @Description: 自动注入数据,但是只会执行一次
     * @return {*}
     */
    autoInitList() {
      const unwatch = this.$watch(
        "initList",
        (val) => {
          this.file_list = dealListData(val);
        },
        {
          immediate: true,
          deep: true,
        }
      );

      unwatch && unwatch();
    },

    /**
     * @Description: 手动注入数据
     * @param {*} val
     * @return {*}
     */
    operationInitList(val) {
      this.file_list = dealListData(val);
    },

    /**
     * @Description: 文件上传前的钩子函数
     * @param {*} file
     * @param {*} detail
     * @return {*}
     */
    beforeRead(event) {
      const { file, callback } = event.detail;
      this.uploadFlag = true;
      callback && callback(true);
    },

    /**
     * @Description: 上传成功回调
     * @param {*} file
     * @param {*} detail
     * @return {*}
     */
    async afterRead(file, detail) {
      this.uploadFlag = false;
      const file_data = file.detail.file;
      const { uploadFile } = useUploadFile();
      const result = await uploadFile(file_data);
      if (result.code === 1) {
        this.file_list.push({
          url: result.data,
          status: "done",
          file_key: result.file_key,
        });
      }

      this.$emit("getUploadData", this.file_list);
    },

    /**
     * @Description: 删除图片
     * @param {*} event
     * @return {*}
     */
    async deleteRead(event) {
      const { file = {} } = event.detail;
      //   const { deleteFile } = useUploadFile();

      if (file.file_key) {
        // 目前删除服务端没有给权限,删除不了
        // await deleteFile(file.file_key);
      }
      this.file_list = this.file_list.filter((item) => item.url !== file.url);
      this.$emit("getUploadData", this.file_list);
    },
  },
};
</script>

<style lang="scss" scoped>
.basic_upload_wrap {
  background: #fff;
}
</style>

format文件

/*
 * @Author       : 桔子
 * @Date         : 2024-04-26 09:49:49
 * @LastEditors  : 桔子
 * @LastEditTime : 2024-04-26 10:04:20
 * @Description  : 头部注释配置模板
 * @FilePath     : /silkworm-business-box-uni/components/UploadFile/format.js
 */
/**
 * @Description: 初始化数据处理
 * @param {*} val
 * @return {*}
 */
export const dealListData = (val) => {
  const arr = [];
  if (Array.isArray(val) && val.length) {
    val.forEach((item) => {
      const data = { status: "done", file_key: "" };

      if (typeof item === "string") {
        data.url = item;
      }

      if (typeof item !== "string" && item?.url) {
        data.url = item.url;
      }

      arr.push(data);
    });
  }

  return arr;
};

以上我用的是"@vant/weapp": "^1.11.5"版本哈,注意点vant和vant/weapp有点区别的,仔细点

说明

autoInitList是用来初始化开始的数据哈,但是它只会执行一遍,开始本来用监听简单点写,但是我发现当触发getUploadData方法后,父组件获取的数据字段和initList如果是同一个,那么监听会再次执行一遍,其实是不需要的哈,initList传进来改变的也是file_list字段,而我上传好了数据,file_list字段就是最新的,没必要监听在执行一次,感觉消耗性能,所以这里我才用了命令式的监听,目的就是初始化一次后,取消监听不在执行了,但是我又担心总有些特例要手动更新下,为此再写了个operationInitList方法,目的就是通过$ref手动再次赋值。

最后

我就不过多讲解了,页面中直接引组件就好,根据业务需求调整即可,之所以重新封装一下,就是一方面我这里的写法不一样了,用hoos写的,更清晰明了,另一方面我这里不会每次去请求获取token了,而是根据过期时间适当请求,最后就是我增加了删除文件的方法,当然这个方法还需要配置是否能删除的权限哈,我这里没有配置,所以删除其实是不成功的,由于老项目,没用ts,需要ts的其实改下就好,加个类型即可

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值