前言
在19年写过一篇关于图片上传组件实现的文章(已删除),但是考虑到Taro的版本变更,及当年技术的不成熟,代码上有许多错漏及让人疑惑的地方,所以对原来的版本进行了一个查漏补缺,并将完整的代码贴出来。
本例只做一个简单的基于Taro
的图片上传组件的实现,使用Taro来做跨端处理,这里主要以微信小程序以为主,其他端还没有测试,感兴趣的同学可以自己尝试,做简单兼容即可,更多的功能逻辑也可以在此基础上自行扩展(比如样式优化,扩展组件属性及回调函数等)。
原理
在Taro的基础上实现图片上传及预览功能,这里主要是调用三个基础API
选择图片API: Taro.chooseImage
上传文件API: Taro.uploadFile
图片预览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>
)
}
// ...
附录
process.env.BASE_URL
为api前缀,需要配置在配置文件中本例为远程获取
token
然后携带token
进行图片上传(上传七牛云的类型),如果已经有了token
则不需要获取
评论区