前言
接上篇文章# UNIAPP封装小程序直传阿里云对象存储使用stsToken,这个组件是与上面方法配合使用的,当然也可以单独使用上篇文中的方法,既然项目中有需要,那么我们就要封装一个覆盖项目日常使用的upload组件。
支持功能
- 三栏布局与两栏布局(both)
- 图片上传前添加文字水印(waterMark)
- 支持详情展示模式(detail)
- 支持添加描述文字与自定义文字颜色(desc、descColor)
- 支持删除图片与自定义删除按钮的颜色(deleteable,delColor)
- 支持限制图片数量、大小与格式(maxCount、maxSize、limitType)
- 支持传入默认图片数组(defaultList)
注意事项
此组件适用于uniapp项目,vue@2.x,组件库uview-ui@1.x。如果组件库不是uview,将代码($u、u-icon、u-line-1)改写即可。upload组件中使用了ts,以及装饰器写法(vue-property-decorator)。
组件全文
<template>
<view class="upload">
<view style="width: 0px; height: 0px; overflow: hidden">
<canvas
id="canone"
:style="{ width: imageWidth + 'px', height: imageHeight + 'px' }"
canvas-id="canone"
/>
</view>
<view class="upload__preview">
<view
v-for="(item, index) in lists"
:key="index"
class="upload__preview--item"
:class="{ 'both-width': both, 'third-width': !both }"
>
<view
class="upload__preview--imagebox"
:class="{ both: both, third: !both }"
>
<image
class="upload__preview--image"
:src="item"
@click="previewImage(index)"
></image>
<u-icon
v-if="!detail"
class="upload__preview--close"
name="close-circle-fill"
color="#666666"
size="40"
@click="deleteImageList(index)"
></u-icon>
</view>
<view
v-if="desc"
:class="{ both: both, third: !both }"
class="upload__up--desc u-line-1"
:style="{ color: descColor }"
>
{{ desc }}
</view>
</view>
<view
v-if="!detail && lists.length < maxCount"
class="upload__up"
:class="{ 'both-width': both, 'third-width': !both }"
>
<view
class="upload__up--btn"
:class="{ 'both-border both': both, third: !both }"
@click="selectFile"
>
<u-icon
name="plus"
size="50"
></u-icon>
</view>
<view
v-if="desc"
:class="{ both: both, third: !both }"
class="upload__up--desc u-line-1"
:style="{ color: descColor }"
>
{{ desc }}
</view>
</view>
</view>
</view>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
import { uploadWaterMarkImage, uploadFile } from '@/utils/upload'
@Component({})
export default class Upload extends Vue {
/**
* 是否加水印
*/
@Prop({ type: Boolean, default: false }) waterMark!: boolean
/**
* 最大上传数量
*/
@Prop({ type: Number, default: 9 }) maxCount!: number
/**
* 是否展示删除按钮
*/
@Prop({ type: Boolean, default: true }) deleteable!: boolean
/**
* 是否是详情
*/
@Prop({ type: Boolean, default: false }) detail!: boolean
/**
* 图片描述
*/
@Prop({ type: String, default: '' }) desc!: boolean
/**
* 图片描述的文字颜色
*/
@Prop({ type: String, default: '#2593F2' }) descColor!: boolean
/**
* 是否展示两栏, 默认三栏展示
*/
@Prop({ type: Boolean, default: false }) both!: boolean
/**
* 文件大小限制,单位Mb
*/
@Prop({ type: Number, default: 10 }) maxSize!: number
/**
* 右上角关闭按钮的叉号图标的颜色
*/
@Prop({ type: String, default: '#666666' }) delColor!: string
/**
* 图片限制类型
*/
@Prop({
type: Array,
default () {
return ['png', 'jpg', 'jpeg', 'gif', 'webp', 'image']
}
})
limitType!: []
/**
* 默认图片
*/
@Prop({
type: Array,
default () {
return []
}
})
defaultList!: []
private imageWidth = 300
private imageHeight = 500
private lists: string[] = []
@Watch('defaultList')
onChangeDefaultList (n: []) {
this.lists = n
}
/**
* 图片预览
*/
previewImage (index: number) {
uni.previewImage({
current: index,
urls: this.lists,
indicator: 'default',
loop: true
})
}
/**
* 删除图片
*/
deleteImageList (index: number) {
let deleteList = this.lists.splice(index, 1)
this.$emit('on-change', this.lists)
this.$emit('on-delete', deleteList, this.lists)
}
/**
* 选择图片,校验图片格式和图片大小
*/
selectFile () {
const { maxSize, maxCount, waterMark } = this
let chooseFile = null
chooseFile = new Promise((resolve, reject) => {
uni.chooseImage({
count: 1, // 为了避免麻烦,偷懒,限制了只能选择一张
success: resolve,
fail: reject
})
})
chooseFile
.then((res: any) => {
// 检查文件后缀是否允许,如果不在this.limitType内,就会返回false
let file = res.tempFiles[0]
if (!this.checkFileType(file)) {
return
}
if (file.size > maxSize * 1024 * 1024) {
this.$u.toast('最大允许上传' + maxSize + 'MB的图片')
return
}
if (this.lists.length >= maxCount) {
this.$u.toast('最多允许上传' + maxCount + '张图片')
return
}
let tempFilePath = res.tempFilePaths[0]
if (waterMark) {
this.uploadWaterMarkImage(tempFilePath)
} else {
this.uploadImage(tempFilePath)
}
})
.catch((error: any) => {
this.$emit('on-choose-fail', error)
})
}
// 图片上传
uploadImage (tempFilePath: string) {
uni.getImageInfo({
src: tempFilePath,
success: (res) => {
let data: { [key: string]: any } = {
filePath: tempFilePath,
extensionName: '.' + res.type
}
uploadFile(data)
.then((image: any) => {
this.lists.push(image.url)
this.$emit('on-success', image, this.lists)
this.$emit('on-change', this.lists)
})
.catch((err) => {
this.uploadError(err)
})
}
})
}
// 图片添加水印后上传
uploadWaterMarkImage (tempFilePath: string) {
uni.getImageInfo({
src: tempFilePath,
success: (res) => {
this.imageWidth = res.width
this.imageHeight = res.height
// 组件调用时读不到canvas,所以需要将this传入进去,同时,上传水印那里也需要把当前的this传入
let ctx = uni.createCanvasContext('canone', this) // 创建画布
let data: { [key: string]: any } = {
that: this,
filePath: tempFilePath,
extensionName: '.' + res.type,
ctx: ctx,
height: res.height,
width: res.width
}
uploadWaterMarkImage(data)
.then((image: any) => {
this.lists.push(image.url)
this.$emit('on-success', image, this.lists)
this.$emit('on-change', this.lists)
})
.catch((err) => {
this.uploadError(err)
})
}
})
}
/**
* 上传失败
*/
uploadError (err: any) {
this.$emit('on-error', err, this.lists)
this.$u.toast('上传失败,请重试')
}
// 判断文件后缀是否允许
checkFileType (file: any) {
// 检查是否在允许的后缀中
let flag = false
// 获取后缀名
let fileType = ''
const reg = /.+\./
fileType = file.path.replace(reg, '').toLowerCase()
// 只要符合limitType中的一个,就返回true
flag = this.limitType.some((ext) => {
// 转为小写
return ext.toLowerCase() === fileType
})
if (!flag) {
this.$u.toast(`不允许选择${fileType}格式的文件`)
}
return flag
}
}
</script>
<style lang="scss">
.upload {
&__preview {
display: flex;
align-items: center;
flex-wrap: wrap;
&--item {
margin-top: 20rpx;
}
&--imagebox {
position: relative;
}
&--image {
width: 100%;
height: 100%;
}
&--close {
position: absolute;
top: -12rpx;
right: -12rpx;
}
}
&__up {
margin-top: 20rpx;
&--btn {
border-radius: 6rpx;
background: #f0f2f4;
display: flex;
align-items: center;
justify-content: center;
}
&--desc {
height: 60rpx !important;
line-height: 60rpx;
font-size: 28rpx;
text-align: center;
}
}
.both {
width: 310rpx;
height: 240rpx;
}
.both-width {
width: 50%;
}
.both-border {
border: 1rpx dashed #2593f2;
}
.third {
width: 190rpx;
height: 190rpx;
}
.third-width {
width: 33.3%;
}
}
</style>
使用方法
- 默认情况下,三栏布局
// template <li-upload :maxCount="6" :limitType="['png', 'jpg', 'jpeg']" :detail="isResult" :defaultList="imageList" @on-change="changeImageList" ></li-upload>// ts import Upload from '@/components/upload/upload.vue' @Component({ components: { 'li-upload': Upload } }) ... // 图片列表 changeImageList (list: any) { this.imageList = list }
- 两栏布局
// template <li-upload :maxCount="6" :limitType="['png', 'jpg', 'jpeg']" :detail="isResult" :both="true" desc="'描述文字描述文字描述文字描述文字'" descColor="#2593F2" :defaultList="imageList" @on-change="changeImageList" ></li-upload> ... ...
- 图片水印
<li-upload :maxCount="6" :limitType="['png', 'jpg', 'jpeg']" :detail="isResult" :waterMark="true" :defaultList="imageList" @on-change="changeImageList" ></li-upload>
最后
以上就是封装图片上传方法,自定义上传组件全部内容。