Используйте Vue для реализации нескольких способов загрузки изображений

задняя часть сервер JavaScript Vue.js
Используйте Vue для реализации нескольких способов загрузки изображений

Необходимость загрузки картинок в проект можно охарактеризовать как часто встречающееся требование.Эта статья познакомит4Существуют различные способы загрузки изображений, которые перечислены и записаны здесь.

Функции без бизнес-сценариев — все хулиганы, поэтому давайте сначала смоделируем бизнес-сценарий, который нужно реализовать. Предположим, мы хотим сделать страницу для добавления товаров в фоновую систему, есть некоторые поля, такие как название товара и информация, и есть необходимость загрузить карусельную карту товаров.

Давайте возьмем Vue, Element-ui и инкапсулированные компоненты в качестве примеров, чтобы поговорить о том, как реализовать эту функцию. Идеи других фреймворков или реализации без фреймворков аналогичны, в этой статье в основном рассказывается об идеях реализации.

облачное хранилище

Common Qiniu Cloud, OSS (Alibaba Cloud) и т. д. Эти облачные платформы предоставляют интерфейсы API, вызывают соответствующий интерфейс и возвращают путь, по которому изображение хранится на сервере после загрузки файла. сохраняет его и отправляет в серверную часть. . Этот процесс относительно прост в обращении.

Основные шаги

  1. Отправить запрос на серверную часть для получения данных конфигурации OSS
  2. Загрузка файла, вызов OSS для предоставления интерфейса
  3. После завершения загрузки файла путь, по которому файл хранится на сервере
  4. Сохранить возвращенный путь к объекту формы

Образец кода

Мы реализуем его с помощью службы Ali OSS и пытаемся инкапсулировать компонент загрузки образа OSS.

Через компонент upLoad element-uihttp-requestПараметры для настройки загрузки нашего файла, используйте только стиль других компонентов и другие связанные крючки перед загрузкой (управление размером изображения, ограничение на загрузку и т. д.).

<template>
  <el-upload
    list-type="picture-card"
    action="''"
    :http-request="upload"
    :before-upload="beforeAvatarUpload">
    <i class="el-icon-plus"></i>
  </el-upload>
</template>

<script>
  import {getAliOSSCreds} from '@/api/common' // 向后端获取 OSS秘钥信息
  import {createId} from '@/utils' // 一个生产唯一的id的方法
  import OSS from 'ali-oss'

  export default {
    name: 'imgUpload',
    data () {
      return {}
    },
    methods: {
      // 图片上传前验证
      beforeAvatarUpload (file) {
        const isLt2M = file.size / 1024 / 1024 < 2
        if (!isLt2M) {
          this.$message.error('上传头像图片大小不能超过 2MB!')
        }
        return isLt2M
      },
      // 上传图片到OSS 同时派发一个事件给父组件监听
      upload (item) {
        getAliOSSCreds().then(res => { // 向后台发请求 拉取OSS相关配置
          let creds = res.body.data
          let client = new OSS.Wrapper({
            region: 'oss-cn-beijing', // 服务器集群地区
            accessKeyId: creds.accessKeyId, // OSS帐号
            accessKeySecret: creds.accessKeySecret, // OSS 密码
            stsToken: creds.securityToken, // 签名token
            bucket: 'imgXXXX' // 阿里云上存储的 Bucket
          })
          let key = 'resource/' + localStorage.userId + '/images/' + createId() + '.jpg'  // 存储路径,并且给图片改成唯一名字
          return client.put(key, item.file) // OSS上传
        }).then(res => {
          console.log(res.url)
          this.$emit('on-success', res.url) // 返回图片的存储路径
        }).catch(err => {
          console.log(err)
        })
      }
    }
  }
</script>

Традиционный файловый сервер загружает изображения

Этот метод заключается в загрузке на жесткий диск вашего собственного файлового сервера или на жесткий диск облачного хоста путем отправкиformdataспособ загрузки файлов. Конкретная идея аналогична облачному файловому серверу.

Основные шаги

  1. Установите путь загрузки на сервер, имя поля загружаемого файла, заголовок, параметры данных и т. д.
  2. После успешной загрузки верните путь, сохраненный сервером.
  3. Возвращенный путь к изображению хранится в объекте отправки формы.

пример кода

Этот тип загрузки изображения может быть реализован в соответствии с компонентом upLoad элемента-ui, если переданы соответствующие поля, согласованные с серверной частью. Если используется элемент js, также создается объект formdata, и загрузка через Ajax аналогично.

Это всего лишь простой пример, подробности см. в документации компонента el-upload.

<template>
  <el-upload
    ref="imgUpload"
    :on-success="imgSuccess"
    :on-remove="imgRemove"
    accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
    :headers="headerMsg"
    :action="upLoadUrl"
    multiple>
    <el-button type="primary">上传图片</el-button>
  </el-upload>
</template>

<script>
  import {getAliOSSCreds} from '@/api/common' // 向后端获取 OSS秘钥信息
  import {createId} from '@/utils' // 一个生产唯一的id的方法
  import OSS from 'ali-oss'

  export default {
    name: 'imgUpload',
    data () {
      return {
          headerMsg:{Token:'XXXXXX'},
          upLoadUrl:'xxxxxxxxxx'
      }
    },
    methods: {
      // 上传图片成功
      imgSuccess (res, file, fileList) {
        console.log(res)
        console.log(file)
        console.log(fileList)   // 这里可以获得上传成功的相关信息
      }
    }
  }
</script>

Загрузите изображение после преобразования в base64

Иногда выполнение некоторых частных проектов или загрузка небольших изображений может перевести интерфейс в base64, а затем превратиться в загрузку строки. Когда у нас есть это требование, есть несколько изображений карусели продуктов, после преобразования в кодировку base64 удаляем data:image/jpeg;base64, объединяя строки в виде запятых и передавая их бэкенду. Как мы делаем это.

1. Как конвертировать локальные файлы в base64

Мы можем преобразовать файл в формат base64 с помощью readAsDataURL, новой функции H5. Есть несколько карусельных изображений, которые можно преобразовать в base64 сразу после нажатия. Я отправляю всю форму для перекодирования за один раз.

Конкретные шаги

  1. Метод создания нового файла для асинхронной инкапсуляции в base64
  2. При добавлении продукта выберите локальный файл и выберите Сохранить весь файловый объект с объектом.
  3. Наконец, закодируйте всю форму продукта перед отправкой.

Здесь следует отметить, что, поскольку операция readAsDataURL является асинхронной, как мы можем закодировать несколько файловых объектов в массиве и сохранить закодированную строку base64 бэкэнд-изображения в новом массиве в порядке загрузки?Первое, что приходит на ум, это цепной вызов обещания, но его нельзя перекодировать одновременно, что является пустой тратой времени. Мы можем реализовать параллелизм, зациклив асинхронные функции и упорядочив их. пожалуйста, посмотриmethodsизsubmitDataметод

utils.js

export function uploadImgToBase64 (file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.readAsDataURL(file)
    reader.onload = function () {  // 图片转base64完成后返回reader对象
      resolve(reader)
    }
    reader.onerror = reject
  })
}

Добавить код раздела страницы продукта

<template>
  <div>
      <el-upload
        ref="imgBroadcastUpload"
        :auto-upload="false" multiple
        :file-list="diaLogForm.imgBroadcastList"
        list-type="picture-card"
        :on-change="imgBroadcastChange"
        :on-remove="imgBroadcastRemove"
        accept="image/jpg,image/png,image/jpeg"
        action="">
        <i class="el-icon-plus"></i>
        <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过2M</div>
      </el-upload>
      <el-button>submitData</el-button> 
  </div>
</template>

<script>
  import { uploadImgToBase64 } from '@/utils' // 导入本地图片转base64的方法

  export default {
    name: 'imgUpload',
    data () {
      return {
        diaLogForm: {
            goodsName:'',  // 商品名称字段
            imgBroadcastList:[],  // 储存选中的图片列表
            imgsStr:''     // 后端需要的多张图base64字符串 , 分割
        }
      }
    },
    methods: {
      // 图片选择后 保存在 diaLogForm.imgBroadcastList  对象中
      imgBroadcastChange (file, fileList) {
        const isLt2M = file.size / 1024 / 1024 < 2  // 上传头像图片大小不能超过 2MB
        if (!isLt2M) {
          this.diaLogForm.imgBroadcastList = fileList.filter(v => v.uid !== file.uid)
          this.$message.error('图片选择失败,每张图片大小不能超过 2MB,请重新选择!')
        } else {
          this.diaLogForm.imgBroadcastList.push(file)
        }
      },
      // 有图片移除后 触发
      imgBroadcastRemove (file, fileList) {
        this.diaLogForm.imgBroadcastList = fileList
      },
      // 提交弹窗数据
      async submitDialogData () {
        const imgBroadcastListBase64 = []
        console.log('图片转base64开始...')
        // 并发 转码轮播图片list => base64
        const filePromises = this.diaLogForm.imgBroadcastList.map(async file => {
          const response = await uploadImgToBase64(file.raw)
          return response.result.replace(/.*;base64,/, '')  // 去掉data:image/jpeg;base64,
        })
        // 按次序输出 base64图片
        for (const textPromise of filePromises) {
          imgBroadcastListBase64.push(await textPromise)
        }
        console.log('图片转base64结束..., ', imgBroadcastListBase64)
        this.diaLogForm.imgsStr = imgBroadcastListBase64.join()
        console.log(this.diaLogForm)
        const res = await addCommodity(this.diaLogForm)              // 发请求提交表单
        if (res.status) {
          this.$message.success('添加商品成功')
          // 一般提交成功后后端会处理,在需要展示商品地方会返回一个图片路径 
        }
      },
    }
  }
</script>

Таким образом, при загрузке локального образа загрузка base64 завершается. Но карусель можно редактировать, как с этим быть? Вообще говоря, страница добавления продукта и страница модификации могут иметь общий компонент, тогда мы продолжаем модифицировать его на этой странице.

При редактировании мы сначала извлечем исходные данные продукта, отобразим их и изменим.В это время изображение, возвращаемое сервером, представляет собой путь.http://xxx.xxx.xxx/abc.jpgТаким образом, когда мы добавляем новое изображение, его можно перекодировать так же, как и в предыдущем методе. Но бэкенд сказал, что немодифицированные картинки тоже надо перевести в base64, так что давайте. Это изображение онлайн-ссылки, а не локальное изображение, как это сделать?

2. Конвертируйте онлайн-изображение в base64

Конкретные шаги

  1. Файл utils.js добавляет метод преобразования онлайн-изображений в base64 с использованием холста.
  2. Чтобы отредактировать продукт, сначала извлеките исходную информацию о продукте и отобразите ее на странице.
  3. Перед отправкой формы различайте онлайн-изображения и локальные изображения для перекодирования.

utils.js

export function uploadImgToBase64 (img) {
  return new Promise((resolve, reject) => {
    function getBase64Image (img) {
      const canvas = document.createElement('canvas')
      canvas.width = img.width
      canvas.height = img.height
      const ctx = canvas.getContext('2d')
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
      var dataURL = canvas.toDataURL()
      return dataURL
    }

    const image = new Image()
    image.crossOrigin = '*'  // 允许跨域图片
    image.src = img + '?v=' + Math.random()  // 清除图片缓存
    image.onload = function () {
      resolve(getBase64Image(image))
    }
    image.onerror = reject
  })
}

Добавить код раздела страницы продукта

<template>
  <div>
      <el-upload
        ref="imgBroadcastUpload"
        :auto-upload="false" multiple
        :file-list="diaLogForm.imgBroadcastList"
        list-type="picture-card"
        :on-change="imgBroadcastChange"
        :on-remove="imgBroadcastRemove"
        accept="image/jpg,image/png,image/jpeg"
        action="">
        <i class="el-icon-plus"></i>
        <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过2M</div>
      </el-upload>
      <el-button>submitData</el-button> 
  </div>
</template>

<script>
    import { uploadImgToBase64, URLImgToBase64 } from '@/utils'
    
  export default {
    name: 'imgUpload',
    data () {
      return {
        diaLogForm: {
            goodsName:'',  // 商品名称字段
            imgBroadcastList:[],  // 储存选中的图片列表
            imgsStr:''     // 后端需要的多张图base64字符串 , 分割
        }
      }
    },
    created(){
        this.getGoodsData()
    },
    methods: {
      // 图片选择后 保存在 diaLogForm.imgBroadcastList  对象中
      imgBroadcastChange (file, fileList) {
        const isLt2M = file.size / 1024 / 1024 < 2  // 上传头像图片大小不能超过 2MB
        if (!isLt2M) {
          this.diaLogForm.imgBroadcastList = fileList.filter(v => v.uid !== file.uid)
          this.$message.error('图片选择失败,每张图片大小不能超过 2MB,请重新选择!')
        } else {
          this.diaLogForm.imgBroadcastList.push(file)
        }
      },
      // 有图片移除后 触发
      imgBroadcastRemove (file, fileList) {
        this.diaLogForm.imgBroadcastList = fileList
      },
      // 获取商品原有信息
      getGoodsData () {
        getCommodityById({ cid: this.diaLogForm.id }).then(res => {
          if (res.status) {
            Object.assign(this.diaLogForm, res.data)
            // 把 '1.jpg,2.jpg,3.jpg' 转成[{url:'http://xxx.xxx.xx/j.jpg',...}] 这种格式在upload组件内展示。 imgBroadcastList 展示原有的图片
            this.diaLogForm.imgBroadcastList = this.diaLogForm.imgsStr.split(',').map(v => ({ url: this.BASE_URL + '/' + v })) 
          }
        }).catch(err => {
          console.log(err.data)
        })
      },
      // 提交弹窗数据
      async submitDialogData () {
        const imgBroadcastListBase64 = []
        console.log('图片转base64开始...')
        this.dialogFormLoading = true
        // 并发 转码轮播图片list => base64
        const filePromises = this.diaLogForm.imgBroadcastList.map(async file => {
          if (file.raw) {  // 如果是本地文件
            const response = await uploadImgToBase64(file.raw)
            return response.result.replace(/.*;base64,/, '')
          } else { // 如果是在线文件
            const response = await URLImgToBase64(file.url)
            return response.replace(/.*;base64,/, '')
          }
        })
        // 按次序输出 base64图片
        for (const textPromise of filePromises) {
          imgBroadcastListBase64.push(await textPromise)
        }
        console.log('图片转base64结束...')
        this.diaLogForm.imgs = imgBroadcastListBase64.join()
        console.log(this.diaLogForm)
        if (!this.isEdit) {  //  新增编辑 公用一个组件。区分接口调用
          const res = await addCommodity(this.diaLogForm)              // 提交表单
          if (res.status) {
            this.$message.success('添加成功')
          }
        } else {
          const res = await modifyCommodity(this.diaLogForm)            // 提交表单
          if (res.status) {
            this.$router.push('/goods/goods-list')
            this.$message.success('编辑成功')
          }
        }
      }
    }
  }
</script>

Загрузка фрагмента изображения

Ввиду времени приготовить загрузку, загрузить, если целый график, может заставить пользователю загружать фоновые изображения слишком медленно, мы можем рассмотреть ломтик, а затем загрузить изображение.

Процесс операции выглядит следующим образом:

  1. мониторinputсобытие получитьfileобъект
  2. пройти черезnew FileReader()положить файл转成base64`
  3. пройти черезnew Image()локальная загрузкаbase64картина
  4. После загрузки изображения перейдитеnaturalWidth naturalHeightРассчитать количество отрезанных листов, ширину и высоту каждого отреза
  5. начатьcanvasСоздание изображений в цикле
    • В цикле => рисуйте картинку каждый раз, когда вы перемещаете позицию
    • canvas.toDataURLпередача изображенияbase64
    • base64Переменаblob
    • blobПеременаfile
    • fileИнтерфейс запроса Загрузка файла
    • Сохранить и вернуться после загрузкиurl
  6. Получить массив изображений в конце среза

код

<template>
  <el-upload
    action="''"
    accept="jpg,jpeg,png,gif"
    :show-file-list="false"
    :auto-upload="false"
    :on-change="handleFileChange"
  >
    <el-button :loading="loading" size="mini" icon="el-icon-upload">上传图片 </el-button>
    <div slot="tip" class="el-upload__tip">只能上传jpg/png文件</div>
  </el-upload>
</template>

<script>
import Apis from '@/apis/common'

export default {
  data() {
    return {
      imgList: [],
      loading: false,
    }
  },
  methods: {
    async handleFileChange(file) {
      this.loading = true
      const fileObj = file.raw
      try {
        // file 转 base64
        const source = await this._fileToBase64(fileObj)
        // 加载 img 获取 img 对象
        const img = new Image()
        img.onload = async () => {
          // 图片加载完成后开始切割图片
          const imgList = await this._createPiece(img)
          this.imgList = imgList
          this.loading = false
        }
        img.src = source
      } catch (e) {
        this.loading = false
      }
    },
    /** 图片切割 上传 到服务器
     * @params img 对象
     * */
    async _createPiece(img) {
      const SPLIT_SIZE = 600 // 分割大小 600px
      const row = Math.ceil(img.naturalHeight / SPLIT_SIZE)

      let canvas = document.createElement('canvas')
      let ctx = canvas.getContext('2d')

      let wpiece = Math.floor(img.naturalWidth)
      let hpiece = Math.floor(img.naturalHeight / row)
      let src = ''
      const imgList = []

      canvas.width = wpiece
      canvas.height = hpiece

      for (let i = 0; i < row; i++) {
        // 绘图
        ctx.drawImage(img, 0, i * hpiece, wpiece, hpiece, 0, 0, wpiece, hpiece)
        // canvas 转 base64
        src = canvas.toDataURL()
        // base64 转 blob
        const res = await fetch(src)
        const blob = await res.blob()
        // base 64转 file
        const file = new File([blob], 'File name', { type: 'image/png' })
        // file 上传到 文件服务器
        const uploadRes = await Apis.UPLOAD_IMAGE(file)
        // 返回 url 图片存储的 地址
        const url = uploadRes.data.url
        // 按切片顺序 保存url
        imgList.push(url)
      }
      return imgList
    },
    // 本地file 转base64
    _fileToBase64(file) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.onload = function (event) {
          let source = event.target.result
          resolve(source)
        }
        reader.onerror = reject
        reader.readAsDataURL(file)
      })
    },
  },
}
</script>

<style lang="scss">
.image-src {
  .el-form-item__content {
    position: static;
  }
}
</style>

Эпилог

На данный момент представлены четыре наиболее часто используемых метода загрузки изображений.Метод преобразования base64 обычно используется в небольших проектах, а традиционные данные формы или облачный сервис больше подходят для загрузки больших файлов. Однако, преобразовав в base64, можно редактировать изображения на внешнем интерфейсе и просматривать их без загрузки на сервер. Основное преимущество — обработка асинхронных операций. Вы можете предложить лучшие способы или предложения.