vue3 + element-plus 用 image-conversion 插件

安装插件

npm i image-conversion --save 
自动单张上传

利用before-upload钩子函数,在上传之前用image-conversion插件的 compressAccurately 方法对图片进行压缩处理。

手动单张上传

手动上传需将 el-upload 组件的auto-upload属性设置为 false,这样before-upload钩子便失效了。这时可以用on-change钩子函数代替

<!--单图上传组件/按钮-->
<template>
    <el-upload
      :action="uploadUrl"
      name="avatar"
      :multiple="false"
      :show-file-list="false"
      :before-upload="beforeUpload" 
      :on-success="handleUploadSuccess"
      :on-error="handleUploadError"
      with-credentials
    >
    </el-upload>
</template>
<script>
import { compress, compressAccurately } from 'image-conversion'
export default {
  setup(props, { emit }) {
    // ...省略其他
    const beforeUpload = file => {
      const typeList = ['image/jpeg', 'image/png', 'image/gif']
      const isTypeValid = typeList.includes(file.type)
      const isLt2M = file.size / 1024 / 1024 < 2
      if (!isTypeValid) {
        ElMessage.error('图片格式只能是 JPG/PNG/GIF!')
      }
      if (!isLt2M) {
        ElMessage.error('图片大小不能超过 2MB!')
      }

      return new Promise((resolve, reject) => {
        if (file.size / 1024 > 200) { // 大于 200 kb 就压缩
          compressAccurately(file, 200).then(res => {
            // The res in the promise is a compressed Blob type (which can be treated as a File type) file;
            console.log(res)
            resolve(res)
          })
        } else if (isTypeValid && isLt2M) {
          // 无需压缩直接返回原文件
          resolve(file)
        } else {
          reject()
        }
      })
    }
    return {
      beforeUpload
    }
  }
}
</script>
多张上传

封装的压缩方法js:

// `src/utils/compress.js`
export function dataURLToBlob(dataUrl) {
  let arr = dataUrl.split(',')
  let mime = arr[0].match(/:(.*?);/)[1]
  let bytes = window.atob(arr[1]) // 去掉url的头,并转换为byte
  var n = bytes.length
  var u8arr = new Uint8Array(n)
  while (n--) { // 将图像数据逐字节放入Uint8Array中
    u8arr[n] = bytes.charCodeAt(n)
  }
  return new Blob([u8arr], { type: mime })
}

export const compress = (img, option, Orientation) => {
  const options = option || {}
  const { width, height, quality } = options
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')

  let initSize = img.src.length
  let w = width || img.width
  let h = height || img.height
  // 如果图片大于四百万像素,计算压缩比并将大小压至400万以下
  let ratio
  if ((ratio = (w * h) / 4000000) > 1) {
    ratio = Math.sqrt(ratio)
    w /= ratio
    h /= ratio
  } else {
    ratio = 1
  }

  canvas.width = w
  canvas.height = h
  ctx.fillStyle = '#fff'
  ctx.fillRect(0, 0, w, h)

  // 瓦片canvas
  const tCanvas = document.createElement('canvas')
  const tctx = tCanvas.getContext('2d')

  let count
  if ((count = (w * h) / 1000000) > 1) {
    // 如果图片像素大于100万则使用瓦片绘制
    console.log('超过100W像素')
    count = ~~(Math.sqrt(count) + 1) //计算要分成多少块瓦片
    // 计算每块瓦片的宽和高
    let nw = ~~(w / count)
    let nh = ~~(h / count)
    tCanvas.width = nw
    tCanvas.height = nh
    for (let i = 0; i < count; i++) {
      for (let j = 0; j < count; j++) {
        tctx.drawImage(
          img,
          i * nw * ratio,
          j * nh * ratio,
          nw * ratio,
          nh * ratio,
          0,
          0,
          nw,
          nh
        )
        ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh)
      }
    }
  } else {
    ctx.drawImage(img, 0, 0, w, h)
  }
  //修复 ios上传图片的时候 被旋转的问题
  if (Orientation != '' && Orientation != 1) {
    switch (Orientation) {
      case 6: //需要顺时针(向左)90度旋转
        rotateImg(img, 'left', canvas)
        break
      case 8: //需要逆时针(向右)90度旋转
        rotateImg(img, 'right', canvas)
        break
      case 3: //需要180度旋转
        rotateImg(img, 'right', canvas) //转两次
        rotateImg(img, 'right', canvas)
    }
  }
  // 进行最小压缩
  // canvas.toDataURL(type, encoderOptions);
  // type: 图片格式 image/png
  // encoderOptions:在指定图片格式为 image/jpeg 或 image/webp的情况下,可以在 0 到 1 的区间选择图片的质量。
  // 如果超出取值范围,将会使用默认值 0.92。
  let newImg = canvas.toDataURL('image/jpeg', quality || 0.3)
  console.log('压缩前:' + initSize)
  console.log('压缩后:' + newImg.length)
  console.log('ndata:' + newImg)

  console.log(
    '压缩率:' + ~~((100 * (initSize - newImg.length)) / initSize) + '%'
  )
  // return new File([blob], file.name, { type: file.mime }) // 如果是返回 File
  // 默认返回 Blob 格式,兼容性较好
  // newImg = base64UrlToBlob(newImg)
  newImg = dataURLToBlob(newImg)

  tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0
  return newImg
}

/** 图片压缩,默认为同比例压缩
 *  @param {file} 接受文件数组或单个文件(图片对象)
 *  @param {option} 指定压缩的配置参数, width| height| quality
 *  回调函数 callback 参数为 base64 字符串图片数据数组
 */
export function compressFile(file, option, callback) {
  const reader = new FileReader()
  const image = new Image()

  // 异步读取传入的 Blob 或 File 对象,【读取成功】会触发 reader 的 load 事件
  reader.readAsDataURL(file)

  // 读取到图片数据保存在 event.target 的 result属性中
  reader.onload = function (event) {
    // 数据格式是 data:URL字符串(base64编码)
    image.src = event.target.result
  }

   return new Promise((resolve, reject) => {
    image.onload = () => {
      resolve(compress(image, option))
    }
  })
}

批量上传组件:

<!--批量上传组件-->
<template>
    <el-upload
      :action="`${apiUrl}/admin/upload`"
      name="photoSrc"
      with-credentials
      :show-file-list="true"
      :file-list="photoList"
      :on-change="photoListChange"
      :on-preview="handlePreview"
      :on-remove="handleRemove"
      :auto-upload="false"
      :drag="drag"
      multiple
      :class="uploadClass"
    >
      <template v-if="!drag" #trigger>
        <el-button size="small" type="primary">选取文件</el-button>
      </template>
      <template v-if="drag">
        <i class="el-icon-upload" />
        <div class="el-upload__text">
          将图片拖到此处,<br /><em>点击上传</em>
        </div>
      </template>
   </el-upload>
</template>

<script lang="ts">
import { compressFile } from '@/utils/compress'

export default defineComponent({
  name: 'UploadMulti',
  setup(props) {
    const photoList = ref([])
    // 当文件列表有改变时触发,添加文件、上传成功和上传失败时都会被调用。
    // 我们配置了手动上传,因此只有添加图片时触发
    const photoListChange = (file: Object) => {
      const typeList = ['image/jpeg', 'image/png', 'image/gif']
      const isTypeValid = typeList.includes(file.raw.type)
      const isLt2M = file.size / 1024 / 1024 < 2
      if (!isTypeValid) return ElMessage.error('图片格式只能是 JPG/PNG/GIF!')
      if (!isLt2M) return ElMessage.error('图片大小不能超过 2MB!')
      // 压缩处理
      compressFile(file.raw).then(res => {
        const newFile = new File([res], file.name, { type: file.raw.type })
        file.blobUrl = res
        file.raw = Object.assign({}, newFile)
        photoList.value.push(file)
      })
      // photoList.value.push(file)
    }
    // 传入 true 获取【已上传】文件,否则是获取【未上传】文件
    // 用是否拥有 raw 属性 来判断,有 raw property 就是待上传的
    const filterPhotoList = (isUploaded?: Boolean) => {
      return isUploaded
        ? photoList.value.filter(
            item => !Object.prototype.hasOwnProperty.call(item, 'raw')
          )
        : photoList.value.filter(item =>
            Object.prototype.hasOwnProperty.call(item, 'raw')
          )
    }
    // 表单提交时先触发这个方法用来获取文件列表,如有未上传的就在这时上传
    const getUploadedList = async () => {
      // 获取待上传的文件列表,如果没有就直接返回 photoList 的值
      const toUploadList = filterPhotoList()
      if (toUploadList.length === 0) return photoList.value

      // 有则开始手动上传
      const formData = new FormData()
      // 压缩处理也可以考虑放这里
      // for (let file of toUploadList) {
      //   const res = await compressFile(file)
      //  ...
      //   formData.append('photoSrc', file.blobUrl, file.name)
      // }
      toUploadList.forEach(file => {
        // 如果是 Blob
        formData.append('photoSrc', file.blobUrl, file.name)
        // formData.append('photoSrc', file.raw, file.name)
      })
      // 执行上传,uploadMulti 是我的批量上传接口
      const { data } = await uploadMulti(formData)
      // 对返回的图片信息做些处理,再给表单去继续 Post
      return filterPhotoList(true).concat(uploadMultiSuccess(data))
    }
    return {
      photoList,
      photoListChange,
      filterPhotoList,
      getUploadedList,
    }
  }
})
</script>

转载于:https://www.jianshu.com/p/519e8b68c018

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用Vue 3.0+Element-Plus开发后台管理系统的步骤: 1.首先,确保你已经安装了Node.js和npm包管理器。 2.使用以下命令安装Vue CLI 4: ```shell npm install -g @vue/cli ``` 3.使用Vue CLI创建一个新项目: ```shell vue create my-project ``` 4.在创建项目时,选择使用Vue 3.0版本,并启用class-style component语法: ```shell ? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, Router, Vuex, CSS Pre-processors, Linter, Unit ? Choose a version of Vue.js that you want to start the project with 3.x (Preview) ? Use class-style component syntax? Yes ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? No ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass) ? Pick a linter / formatter config: ESLint with error prevention only ? Pick additional lint features: Lint on save ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files ``` 5.安装Element-Plus和Echarts 5.0: ```shell npm install element-plus echarts@5.0 ``` 6.在main.js中引入Element-Plus和Echarts: ```javascript import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import ElementPlus from 'element-plus' import 'element-plus/lib/theme-chalk/index.css' import echarts from 'echarts' const app = createApp(App) app.use(store) app.use(router) app.use(ElementPlus) app.config.globalProperties.$echarts = echarts app.mount('#app') ``` 7.现在你可以开始使用Element-Plus和Echarts来开发你的后台管理系统了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值