vue3上传图片到七牛云cdn

vue yekong

vue3上传图片到七牛云cdn

在后台项目开发过程中,客户反应图片加载太慢,限于服务器带宽只有1M,大的图片加载会比较慢,于是将图片上传到七牛云,这样记在速度就会明显提升。

后台开发

后端使用的是lin-cms-koa框架

方法封装,我们安装qiniu插件,通过key和密钥换取token返回给前端,用于文件上传

// src/extension/file/qiniu-uploader.js

import qiniu from 'qiniu';
import { config } from 'lin-mizar';

class QiniuUploader {
    constructor() {
        this.accessKey = config.getItem('qiniu.accessKey');
        this.secretKey = config.getItem('qiniu.secretKey');
        this.bucket = config.getItem('qiniu.bucket');
        this.mac = new qiniu.auth.digest.Mac(this.accessKey, this.secretKey);
    }

    generateUploadToken() {
        const options = {
            scope: this.bucket,
            expires: 7200 // token 有效期,单位:秒
        };
        const putPolicy = new qiniu.rs.PutPolicy(options);
        return putPolicy.uploadToken(this.mac);
    }
}

export { QiniuUploader };

接口封装

import {LinRouter, ParametersException} from 'lin-mizar';

import {loginRequired} from '../../middleware/jwt';
import {QiniuUploader} from '../../extension/file/qiniu-uploader';

const file = new LinRouter({
    prefix: '/cms/file'
});

file.get('getQiniuToken', '/get-qiniu-token', loginRequired, async ctx => {
    const qiniuUploader = new QiniuUploader();
    const token = qiniuUploader.generateUploadToken();
    ctx.json({
        token: token
    });
});
export {file};

前端开发

插件版本

这里使用的qiniu-js是3.x,升级4x后api发生了变化,并且没有明确说明文件如何上传到指定目录,所以这里我们使用3.x的版本。

 "qiniu-js": "3.4.2",

接口封装

// 获取七牛云token
export function getQiniuToken(data) {
    return request({
        url: '/cms/file/get-qiniu-token',
        method: 'get',
        data
    })
}

上传组件代码

<template>
  <div class="uploadImgList">
    <div v-for="(image, index) in computedImages" :key="index" class="avatar">
      <img :src="image" class="avatar"/>
      <el-icon class="delete-icon" @click.stop="removeImage(index)">
        <Delete/>
      </el-icon>
    </div>
    <div class="avatar-uploader" @click="handleUpload">
      <el-icon v-if="images.length < maxImages" class="avatar-uploader-icon">
        <Plus/>
      </el-icon>
      <input
          ref="fileInput"
          type="file"
          class="file-input"
          @change="handleFileChange"
          style="display: none;"
          multiple
      />
    </div>
  </div>
</template>

<script setup>
import {defineProps, defineEmits, computed, ref} from 'vue'
import {ElMessage} from 'element-plus'
import {Plus, Delete} from '@element-plus/icons-vue'
import * as qiniu from 'qiniu-js'
import {qiniuConfig} from '@/api/ipConfig.js'
import {getQiniuToken} from '@/api/api/cms.js'
import {v4 as uuidv4} from 'uuid'

const props = defineProps({
  avatar: {
    type: String,
    default: ''
  },
  maxImages: {
    type: Number,
    default: 3
  }
})

const emit = defineEmits(['update:avatar'])

const fileInput = ref(null)
const images = ref(props.avatar ? props.avatar.split(',') : [])

const computedImages = computed(() => {
  return images.value.map(img => `${qiniuConfig.qiuniuUploadUrl}/${img}`)
})

const handleUpload = () => {
  fileInput.value.value = null;
  fileInput.value.click()
}

const getFileExtension = (filename) => {
  return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2);
}

const handleFileChange = async (e) => {
  const files = e.target.files
  if (!files.length) return;

  for (const file of files) {
    const isJPG = file.type === 'image/jpeg'
    const isPNG = file.type === 'image/png'
    const isLt2M = file.size / 1024 / 1024 < 2

    if (!isJPG && !isPNG) {
      ElMessage.error('图片只能是 JPG 或 PNG 格式!')
      continue
    }
    if (!isLt2M) {
      ElMessage.error('图片大小不能超过 2MB!')
      continue
    }

    try {
      const fileExtension = getFileExtension(file.name);
      const key = `rencai/``{uuidv4()}.``{fileExtension}`;

      const tokenResponse = await getQiniuToken();
      if (typeof tokenResponse.token !== 'string') {
        throw new Error('Invalid upload token');
      }
      const token = tokenResponse.token;

      const putExtra = {
        fname: file.name,
        params: {},
        mimeType: null
      };

      const config = {
        useCdnDomain: true,
        region: 'z2',  // 修改这里
        domain: 'up-z2.qiniup.com'  // 添加这行
      };

      const observable = qiniu.upload(file, key, token, putExtra, config);

      observable.subscribe({
        next: (res) => {
          console.log('上传进度', res.total.percent + '%');
        },
        error: (err) => {
          ElMessage.error('上传失败,请重试');
          console.error(err);
        },
        complete: (res) => {
          images.value.push(res.key);
          emit('update:avatar', images.value.join(','));
          ElMessage.success('图片上传成功');
        }
      });

    } catch (error) {
      ElMessage.error('上传失败,请重试');
      console.error(error);
    }
  }
}

const removeImage = (index) => {
  images.value.splice(index, 1);
  emit('update:avatar', images.value.join(','));
}
</script>

<style scoped>
.uploadImgList {
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  flex-wrap: wrap;
  flex-direction: row;
  align-content: flex-start;
}

.avatar-uploader {
  text-align: center;
  width: 150px;
  height: 150px;
  align-items: center;
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: nowrap;
  flex-direction: row;
  align-content: flex-start;
}

.avatar {
  width: 150px;
  position: relative;
  height: 150px;
  display: block;
  margin-right: 10px;
  border: 1px solid #ddd;
}

.delete-icon {
  position: absolute;
  top: 0;
  right: 0;
  cursor: pointer;
  color: red;
}
</style>

喜欢