侧边栏壁纸
博主头像
玄近安

抬猪高手,略懂代码

  • 累计撰写 9 篇文章
  • 累计创建 9 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

基于Taro 3的图片上传组件实现

玄近安
2021-09-03 / 0 评论 / 0 点赞 / 17 阅读 / 0 字

前言

在19年写过一篇关于图片上传组件实现的文章(已删除),但是考虑到Taro的版本变更,及当年技术的不成熟,代码上有许多错漏及让人疑惑的地方,所以对原来的版本进行了一个查漏补缺,并将完整的代码贴出来。

本例只做一个简单的基于Taro的图片上传组件的实现,使用Taro来做跨端处理,这里主要以微信小程序以为主,其他端还没有测试,感兴趣的同学可以自己尝试,做简单兼容即可,更多的功能逻辑也可以在此基础上自行扩展(比如样式优化,扩展组件属性及回调函数等)。

原理

在Taro的基础上实现图片上传及预览功能,这里主要是调用三个基础API

  1. 选择图片API: Taro.chooseImage

  2. 上传文件API: Taro.uploadFile

  3. 图片预览API: Taro.previewImage

实现

注1: 请务必保证自己的Taro已安装,详情教程请见: Taro官方文档
注2: 本例使用Taro版本号为3.3.6(当前最新版本,若非3.x版本库,请审慎考虑,酌情拷贝),理论上本例主体部分代码可以在Taro 2上运行(除了引入taro部分代码需要变动之外)
注3: 本例使用react语法,组件使用class写法(使用其他写法的请自行调整)

废话不多说,直接上代码

目录结构
# src目录
│  app.config.js
│  app.js
│  app.scss
│  index.html
│  tree.txt
│  
├─assets
│  └─images
│          close.png
│          plus.png
│        
├─components
│  └─ImageUpload
│          index.jsx
│          index.scss
│        
└─pages
    └─index
            index.config.js
            index.jsx
            index.scss
组件代码
/* src/components/ImageUpload/index.jsx */
import { PureComponent } from 'react'
import Taro from '@tarojs/taro'
import { View, Image } from '@tarojs/components'
// 关闭及新增的图标(图片请自行网上搜索,或者让UI出图)
import closePng from '../../assets/images/close.png'
import plusPng from '../../assets/images/plus.png'
import './index.scss'

export default class ImageUpload extends PureComponent {
  static defaultProps = {
    imageFiles: [], // 图片列表
    maxLength: 4, // 最大可上传图片数
    onChange() { }, // 图片改变事件回调
    onImageClick() { }, // 图片点击事件回调
  }

  constructor(props) {
    super(props)
    this.picUrls = []
    this.state = {
      uploadUrl: process.env.BASE_URL + '/api/upload', // 图片上传url
      token: '', // 鉴权token
      uploadCurrent: 0 // 当前上传图片的index
    }
  }

  componentDidMount() {
    // 获取服务端token(token主要用来做校验,如果不需要校验或者已经有token,则可以去除)
    this.getToken()
  }

  // 获取上传token
  async getToken() {
    try {
      const res = await Taro.request({
        url: process.env.BASE_URL + '/api/token', // 获取上传token的后台接口
        method: 'GET'
      })
      if (res) {
        this.setState({
          token: res.data?.token // 获取从后台传回的token
        })
      }
    } catch (err) {
      // do something
      console.log(err)
    }
  }

  // 选择图片
  chooseImage() {
    const { imageFiles, maxLength } = this.props
    const limit = maxLength - imageFiles.length
    Taro.chooseImage({
      count: limit,
      success: (res) => {
        this.uploadImage(res.tempFilePaths)
      }
    })
  }

  // 上传图片(这里涉及到多图一起上传的处理)
  uploadImage(tempFilePaths) {
    const { uploadCurrent, uploadUrl, token } = this.state
    const { onChange, imageFiles } = this.props
    Taro.uploadFile({
      url: uploadUrl,
      filePath: tempFilePaths[uploadCurrent],
      name: 'file',
      formData: { 'token': token },
      success: (res) => {
        if (res.errMsg === "uploadFile:ok") {
          const imageUrl = process.env.BASE_URL + res.data.src
          this.picUrls.push(imageUrl)
        } else {
          console.error('图片上传失败')
        }
      },
      fail: () => {
        console.error('图片上传失败')
      },
      complete: () => {
        if (uploadCurrent === tempFilePaths.length - 1) { // 图片全部上传完成
          const totalFiles = imageFiles.concat(this.picUrls)
          onChange(totalFiles)
          this.picUrls = []
          this.setState({
            uploadCurrent: 0
          })
        } else { // 图片没有全部上传完成
          this.setState({
            uploadCurrent: uploadCurrent + 1
          }, () => {
            this.uploadImage(tempFilePaths)
          })
        }
      }
    })
  }

  // 点击上传
  handlePlusClick() {
    this.chooseImage()
  }

  // 删除图片
  handleImageDelete(index) {
    const { imageFiles, onChange } = this.props
    const copyFiles = JSON.parse(JSON.stringify(imageFiles))
    copyFiles.splice(index, 1)
    onChange(copyFiles)
  }

  render() {
    const { onImageClick, imageFiles, maxLength } = this.props
    return (
      <View className='image-upload'>
        {imageFiles.map((item, index) => (
          <View
            key={item + index}
            className='image-upload__item'
          >
            <Image
              className='image-upload__image'
              src={item}
              mode='aspectFill'
              onClick={onImageClick.bind(this, item)}
            ></Image>
            <Image
              onClick={this.handleImageDelete.bind(this, index)}
              className='image-upload__close'
              src={closePng}
            ></Image>
          </View>
        ))}
        {imageFiles.length < maxLength && <View className='image-upload__item'>
          <View
            className='image-upload__plus'
            onClick={this.handlePlusClick.bind(this)}
          >
            <Image
              className='image-upload__plus--image'
              src={plusPng}
            ></Image>
          </View>
        </View>}
      </View>
    )
  }
}

组件基础样式
/* src/components/ImageUpload/index.scss */
.image-upload {
  padding: 20px;
  box-sizing: border-box;
  display: flex;
  &__item {
    position: relative;
    flex: 1;
    max-width: 25%;
  }
  &__image {
    width: 126px;
    height: 126px;
    display: block;
    border-radius: 8px;
  }
  &__close {
    position: absolute;
    right: 18px;
    top: -36px;
    width: 36px;
    height: 36px;
    padding: 20px;
  }
  &__plus {
    display: flex;
    width: 126px;
    height: 126px;
    border: 1px solid #ccc;
    border-radius: 8px;
    box-sizing: border-box;
    align-items: center;
    justify-content: center;
    &--image {
      width: 63px;
      height: 63px;
      display: block;
    }
  }
}

页面内使用组件
/* src/pages/index/index.jsx */
// ... 
constructor(props) {
 super(props)
 this.state = {
   imageFiles: []
 }
}

// 图片改变监听
handleChange(files) {
 this.setState({
   imageFiles: files
 })
}
// 图片点击监听(这里是图片点击之后调起图片预览接口)
handleImageClick(currentUrl) {
 Taro.previewImage({
   current: currentUrl,
   urls: this.state.imageFiles
 })
}

render() {
 const { imageFiles } = this.state
 return (
   <View className='index'>
     <ImageUpload
       imageFiles={imageFiles}
       onChange={this.handleChange.bind(this)}
       onImageClick={this.handleImageClick.bind(this)}
     />
   </View>
 )
}
// ...

附录

  1. process.env.BASE_URL 为api前缀,需要配置在配置文件中

  2. 本例为远程获取token然后携带token进行图片上传(上传七牛云的类型),如果已经有了token则不需要获取

0

评论区