vue3+ts 封装上传图片(限制大小、尺寸)和文件到oss

前置条件 Vue3 ali-oss

  1. 自行安装依赖 yarn add ali-oss 自己找个文件夹,
  2. 然后创建 oss.ts 文件
  3. accessKeyId: ‘’,accessKeySecret: ‘’, 去oss管理器找
import OSS from 'ali-oss';
import { buildShortUUID } from '/@/utils/uuid';
import { urlToBase64, dataURLtoBlob } from '/@/utils/file/base64Conver';

// @ts-ignore
const isDev = ['development', 'test', 'dev'].includes(process.env.NODE_ENV);

/**
 * @description 获取oss实例
 * @param region 桶所在区域
 * @param bucket 桶名称
 */
export function getOssClient({}: {
  region?: 'DEFAULT' /* 桶所在区域 */;
  bucket?: 'DEFAULT' /* 桶名称 */;
}) {
  return new OSS({
    region: 'oss-cn-hangzhou' /* Bucket所在地域 */,
    // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
    accessKeyId: '', //oss accessKeyId
    accessKeySecret: '', // oss accessKeySecret
    // 从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: 'ali-dish' /* Bucket名称 */,
  });
}

interface UploadFileResultModule {
  name: string;
  url: string;
  domain: string;
  fastDomain: string;
  dir: string;
  fileName: string;
  fastUrl: string;
  res: {
    status: string;
    statusCode: string;
    headers: {
      'content-length': string;
    };
    size: string;
    aborted: boolean;
    rt: string;
    keepAliveSocket: boolean;
    data: {
      type: string;
      data: any[];
    };
    requestUrls: string[];
    timing: any;
    remoteAddress: string;
    remotePort: string;
  };
}

/**
 * @description 简单上传文件
 * @param data 文件
 * @param fileName 文件名称
 * @param reName 是否重命名
 * @param mineType 文件类型,即文件后缀
 * @param dir 文件存储路径
 * @param region 桶所在区域
 * @param bucket 桶名称
 */
export async function uploadFile({
  data,
  fileName,
  reName = true,
  mineType,
  region = 'DEFAULT',
  bucket = 'DEFAULT',
}: {
  data: Blob /* 文件 */;
  fileName?: string /* 文件名称 */;
  reName?: boolean /* 是否重命名 */;
  mineType?: string /* 文件类型,即文件后缀 */;
  dir?: 'FOOD_MAIN_IMG' /* 文件存储路径 */;
  region?: 'DEFAULT' /* 桶所在区域 */;
  bucket?: 'DEFAULT' /* 桶名称 */;
}): Promise<UploadFileResultModule> {
  function getFileName(): string {
    let name = '';
    if (fileName && fileName.indexOf('.') > -1) {
      const arr = fileName.split('.');
      for (let i = 0; i < arr.length - 1; i++) {
        name += arr[i];
      }
      name =
        (reName ? buildShortUUID(name) : arr[arr.length - 2]) +
        '.' +
        (mineType || arr[arr.length - 1] || 'png');
    } else {
      name =
        (reName ? buildShortUUID(new Date().getTime().toString()) : fileName) +
        '.' +
        (mineType || 'png');
    }
    return name;
  }
  const saveFileName = getFileName();
  const fullPath = `${'index/main/'}${saveFileName}`;
  const res = await getOssClient({ region, bucket }).put(fullPath, data);
  return {
    ...res,
    domain: 'http://XXX.oss-cn-hangzhou.aliyuncs.com',
    fastDomain: 'http://XXX.oss-cn-hangzhou.aliyuncs.com',
    dir: 'index/main/',
    fileName: saveFileName,
    fastUrl: 'http://XXX.oss-cn-hangzhou.aliyuncs.com' + '/' + 'index/main/' + saveFileName,
  };
}

export async function uploadFileByUrl(
  url: string | string[],
  {
    dir = 'DISH_MAIN_IMG',
    fileName,
    region = 'DEFAULT',
    bucket = isDev ? 'DEFAULT_TEST' : 'DEFAULT',
  }: {
    fileName?: string /* 文件名称 */;
    dir?: 'DISH_MAIN_IMG' /* 文件存储路径 */;
    region?: 'DEFAULT' /* 桶所在区域 */;
    bucket?: 'DEFAULT' /* 桶名称 */;
  }
) {
  async function single(item) {
    let arr: string[] = [];
    let blob;
    if (item.indexOf('data:') === 0) {
      arr = ['png'];
      blob = dataURLtoBlob(item);
    } else {
      arr = item.split('.');
      blob = dataURLtoBlob(await urlToBase64(item));
    }
    return await uploadFile({
      data: blob,
      dir,
      fileName,
      region,
      bucket,
      mineType: arr[arr.length - 1],
    });
  }
  if (typeof url === 'string') {
    return single(url);
  } else {
    const arr = new Array(url.length).fill(null);
    for (let index = 0; index < url.length; index++) {
      const item = url[index];
      const result = await single(item);
      arr[index] = result;
      if (arr.indexOf(null) < 0) {
        return Promise.resolve(arr);
      }
    }
  }
}

class FfileReader extends FileReader {
  file;
  proportion /* 图片或视频尺寸比例 */;
  maxSize /* 最大文件尺寸,单位kb */;
  maxWidth /* 最大文件宽度 */;
  maxHeight /* 最大文件高度 */;
  imgWidth /* 最大文件宽度 */;
  imgHeight /* 最大文件高度 */;
  status = 0 /* 文件读取状态 */;
  // loaded = null /* 读取完成后的结果 */;
  message = '';
  ecb = Function;
  constructor(
    file = File,
    proportion = '',
    maxSize = 0,
    maxWidth = 0,
    maxHeight = 0,
    imgWidth = 0,
    imgHeight = 0,
    cb = Function,
    ecb = Function
  ) {
    super();
    this.file = file;
    this.proportion = proportion;
    this.maxSize = maxSize;
    this.maxWidth = maxWidth;
    this.maxHeight = maxHeight;
    this.imgWidth = imgWidth;
    this.imgHeight = imgHeight;
    if (ecb) {
      this.ecb = ecb;
    }
    this.status = super.readyState;
    super.onabort = this.err;
    super.onerror = this.err;
    super.onload = (e) => {
      const size = {
        width: 0,
        height: 0,
      };
      const load = () => {
        if (maxSize && e.total / 1024 > maxSize) {
          this.message = `单个文件大小不能超过${maxSize / 1024}m,文件${file.name}大小为:${(
            e.total /
            1024 /
            1024
          ).toFixed(2)}m`;
        } else if (maxWidth && size.width && size.width > maxWidth) {
          this.message = `单个文件宽度不能超过${maxWidth}像素,文件${file.name}宽度为:${size.width}像素`;
        } else if (maxHeight && size.height > maxHeight) {
          this.message = `单个文件高度不能超过${maxHeight}像素,文件${file.name}高度为:${size.height}像素`;
        } else if (imgWidth && size.width && size.width != imgWidth) {
          this.message = `图片宽度必须为${imgWidth}像素,文件${file.name}宽度为:${size.width}像素`;
        } else if (imgHeight && size.height != imgHeight) {
          this.message = `图片高度必须为${imgHeight}像素,文件${file.name}高度为:${size.height}像素`;
        } else if (proportion && size.width && size.height) {
          const arr = proportion.split(/[,.|:~`!@#$%^&*-/_/=+;/\\?><]/);
          if (
            arr.length == 2 &&
            Number(arr[0]) &&
            Number(arr[1]) &&
            size.width / size.height != Number(arr[0]) / Number(arr[1])
          ) {
            this.message = `单个文件尺寸比例为${arr[0] + ':' + arr[1]}像素,文件${
              file.name
            }尺寸比例为:${'1:' + (size.height / size.width).toFixed(2)}`;
          }
        }
        if (this.message) {
          // Vue.prototype.$message({
          //   message: this.message,
          //   type: "error",
          // });
          ecb && ecb();
          super.abort();
        } else if (e.target && e.target.result) {
          const path = e.target.result;
          cb &&
            cb({
              file,
              path,
            });
        }
      };
      if (this.file.type.indexOf('image') > -1) {
        const imageObj = new Image();
        imageObj.src = String(e.target && e.target.result);
        imageObj.onload = function () {
          size.width = imageObj.width;
          size.height = imageObj.height;
          load();
        };
      } else if (this.file.type.indexOf('video') > -1) {
        const videoUrl = URL.createObjectURL(file);
        const videoObj = document.createElement('video');
        videoObj.onloadedmetadata = () => {
          URL.revokeObjectURL(videoUrl);
          size.width = videoObj.videoWidth;
          size.height = videoObj.videoHeight;
          load();
        };
        videoObj.src = videoUrl;
        videoObj.load();
      } else {
        load();
      }
    };
    super.readAsDataURL(this.file);
  }
  err() {
    this.status = -1;
    this.ecb && this.ecb();
  }
}

//上传文件
export function chooseFile({
  cb,
  max = 1,
  accept = '',
  proportion = '',
  maxSize = 0,
  maxWidth = 0,
  maxHeight = 0,
  imgWidth = 0,
  imgHeight = 0,
  loading = '',
}) {
  const el = document.createElement('input');
  el.setAttribute('type', 'file');
  if (max > 1) {
    el.setAttribute('multiple', 'true');
  }
  if (accept) {
    el.setAttribute('accept', accept);
  }
  el.click();
  let waiting = false; // 是否尚在等待选择文件
  let clicked = false; // 按钮是否被点击
  el.addEventListener('click', () => {
    clicked = true; // 按钮被点击
    waiting = true; // 等待用户选择文件, 此时按钮会失去焦点
  });
  el.addEventListener('blur', () => {
    if (clicked && waiting) {
      clicked = false; // 用户点击容器后, 容器会失去一次焦点, 此时处于waiting状态
      // waiting没有被input的change事件置为false, 却触发了blur的失焦事件
    } else if (waiting) {
      // 容器再次失去焦点, 仍旧处于waiting状态, 断言用户取消了选择
      console.log('blur事件测试到用户取消了选择');
    }
  });
  el.addEventListener('change', () => {
    // if (loading) {
    //   loading = Vue.prototype.$loading({
    //     target: loading,
    //     lock: true,
    //     text: "加载中...",
    //     spinner: "el-icon-loading",
    //     background: "rgba(0, 0, 0, 0.7)",
    //   });
    // }
    waiting = false; // 检测到用户选择了文件
    let files = el.files || [];
    const data = [];
    if (files.length) {
      const status = [];
      if (files.length > max) {
        /* 检测到用户选择文件数量超出限制,取前面符合数量的文件 */
        console.log(files.length, max);
        const fileList = [];
        for (let i = 0; i < files.length; i++) {
          const element = files[i];
          if (i < max) {
            fileList.push(element);
          }
        }
        files = fileList;
        // Vue.prototype.$message({
        //   message: `您选择的文件数量超出限制,已取前${max}个文件。`,
        //   type: "warn",
        // });
      }
      for (const item of files) {
        const fr = new FfileReader(
          item,
          proportion,
          maxSize,
          maxWidth,
          maxHeight,
          imgWidth,
          imgHeight,
          (e) => {
            data.push(e);
            if (data.length == files.length) {
              cb(data, loading);
            }
          },
          () => {
            cb([], loading);
          }
        );
        status.push(fr);
      }
    } else {
      cb([], loading);
    }
  });
}

//上传图片
export function chooseImg({
  cb,
  max = 1,
  proportion = '',
  maxSize = 2048,
  maxWidth = 0,
  maxHeight = 0,
  imgWidth = 0,
  imgHeight = 0,
}) {
  chooseFile({
    max,
    accept: 'image/*',
    proportion,
    maxSize,
    maxWidth,
    maxHeight,
    imgWidth,
    imgHeight,
    cb,
  });
}

使用chooseFile上传文件

 chooseFile({
      cb: (files) => {
        if (files.length) {
          files.forEach(async (i) => {
            const results = await uploadFile({
              data: i.file,
              fileName: 'SERVICE' + new Date().getTime() + i.file.name,
              dir: 'FILE',
            });
            console.log(results.url)
          });
        } else {
          notification['error']({
            message: '上传失败',
          });
        }
      },
      max: 100,
    });

使用 chooseImg 上传照片

 chooseImg({
      cb: async (files) => {
        console.log(files);
        if (files.length) {
          const results = await uploadFile({
            data: files[0].file,
            fileName: 'IMGURL' + new Date().getTime(),
            dir: 'IMG',
          });
          console.log(results);
        } else {
          notification['error']({
            message: '上传失败',
          });
        }
      },
      maxSize: 1024, //图片大小
      imgWidth: 220, //图片宽
      imgHeight: 220, //图片高
    });

buildShortUUID文件

const hexList: string[] = [];
for (let i = 0; i <= 15; i++) {
  hexList[i] = i.toString(16);
}

export function buildUUID(): string {
  let uuid = '';
  for (let i = 1; i <= 36; i++) {
    if (i === 9 || i === 14 || i === 19 || i === 24) {
      uuid += '-';
    } else if (i === 15) {
      uuid += 4;
    } else if (i === 20) {
      uuid += hexList[(Math.random() * 4) | 8];
    } else {
      uuid += hexList[(Math.random() * 16) | 0];
    }
  }
  return uuid.replace(/-/g, '');
}

let unique = 0;
export function buildShortUUID(prefix = ''): string {
  const time = Date.now();
  const random = Math.floor(Math.random() * 1000000000);
  unique++;
  return prefix + '_' + random + unique;
}

文件转base64 base64Conver

/**
 * @description: base64 转 blob
 */
export function dataURLtoBlob(base64Buf: string): Blob {
  const arr = base64Buf.split(',');
  const typeItem = arr[0];
  const mime = typeItem.match(/:(.*?);/)![1];
  const bstr = atob(arr[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], { type: mime });
}

/**
 * img url to base64
 * @param url
 */
export function urlToBase64(url: string, mineType?: string): Promise<string> {
  return new Promise((resolve, reject) => {
    let canvas = document.createElement('CANVAS') as Nullable<HTMLCanvasElement>;
    const ctx = canvas!.getContext('2d');

    const img = new Image();
    img.crossOrigin = '';
    img.onload = function () {
      if (!canvas || !ctx) {
        return reject();
      }
      canvas.height = img.height;
      canvas.width = img.width;
      ctx.drawImage(img, 0, 0);
      const dataURL = canvas.toDataURL(mineType || 'image/png');
      canvas = null;
      resolve(dataURL);
    };
    img.src = url;
  });
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 3是一种流行的JavaScript框架,而TypeScript是一种类型安全的JavaScript的超集。封装axios是在Vue 3项目中使用TypeScript来封装axios库,以便更好地管理和使用网络请求。 在Vue 3 + TypeScript中封装axios,可以按照以下步骤进行: 1. 安装axios和@types/axios: ``` npm install axios @types/axios ``` 2. 创建一个api.ts文件,用于封装axios请求: ```typescript import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; // 创建一个axios实例 const instance = axios.create({ baseURL: 'http://api.example.com', // 设置请求的基础URL timeout: 5000, // 设置请求超时时间 }); // 请求拦截器 instance.interceptors.request.use( (config: AxiosRequestConfig) => { // 在发送请求之前做一些处理,例如添加token等 return config; }, (error: any) => { // 处理请求错误 return Promise.reject(error); } ); // 响应拦截器 instance.interceptors.response.use( (response: AxiosResponse) => { // 对响应数据进行处理,例如统一处理错误码等 return response.data; }, (error: any) => { // 处理响应错误 return Promise.reject(error); } ); // 封装get请求 export function get(url: string, params?: any): Promise<any> { return instance.get(url, { params }); } // 封装post请求 export function post(url: string, data?: any): Promise<any> { return instance.post(url, data); } ``` 3. 在Vue组件中使用封装的axios请求: ```typescript import { defineComponent } from 'vue'; import { get, post } from './api'; export default defineComponent({ methods: { fetchData() { get('/api/data').then((response) => { // 处理响应数据 }).catch((error) => { // 处理请求错误 }); }, postData() { post('/api/data', { name: 'example' }).then((response) => { // 处理响应数据 }).catch((error) => { // 处理请求错误 }); }, }, }); ``` 以上就是在Vue 3 + TypeScript中封装axios的基本步骤。通过封装,可以更好地管理和复用网络请求代码,并且可以在请求拦截器和响应拦截器中进行统一处理。同时,使用TypeScript可以提供类型检查和自动补全的功能,提高代码的可靠性和开发效率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值