上传组件 完善
John0King opened this issue · 2 comments
John0King commented
首先, 请原谅我使用 typescript 来写 (我个人喜欢 typescript , 它可以减少很多常见的错误),
正因为是 typescript , 所以我就不 PR 了。
我这个版本里面, 增加了 outField
用了 从 服务端返回的json 上获取一个字段
关于 @Prop
, @Model
, @Emit
的关系 请查看此文档 https://github.com/kaorun343/vue-property-decorator#Watch , 里面又 对应的代码, 很容易理解
以下是我的代码
<template>
<view class="tui-container">
<view class="tui-upload-box">
<view class="tui-image-item" v-for="(item,index) in imageList" :key="index">
<image
:src="item.image"
class="tui-item-img"
@click.stop="previewImage(index)"
mode="aspectFill"
/>
<view v-if="!forbidDel" class="tui-img-del" @click.stop="delImage(index)"></view>
<view v-if="imageList[index].state!='uploaded'" class="tui-upload-mask">
<view class="tui-upload-loading" v-if="imageList[index].state == 'uploading'"></view>
<text class="tui-tips">{{getStateText(index)}}</text>
<view
class="tui-mask-btn"
v-if="imageList[index].state=='fail'"
@click.stop="reUpload(index)"
hover-class="tui-hover"
:hover-stay-time="150"
>重新上传</view>
</view>
</view>
<view v-if="isShowAdd" class="tui-upload-add" @click="chooseImage">
<view class="tui-upload-icon tui-icon-plus"></view>
</view>
</view>
</view>
</template>
<script lang="ts">
import Vue from "vue";
import { Component, Prop, Model, Emit, Watch } from "vue-property-decorator";
import { httpClient } from "../../core/httpclient";
@Component
export default class MultiImgUpload extends Vue {
/** v-model 的值 */
@Model("change", { type: Array, default: () => [] })
value!: string[];
/**上传地址 */
@Prop({ type: String, default: `/api/upload/image` })
uploadApi!: string;
/**禁止删除 */
@Prop({ type: Boolean, default: false })
forbidDel!: boolean;
/**禁止添加 */
@Prop({ type: Boolean, default: false })
forbidAdd!: boolean;
/** 限制数量 */
@Prop({ type: Number, default: 9 })
limit!: number;
/**上传时表单字段的名字 默认 file */
@Prop({ type: String, default: "file" })
filedName!: string;
/** 服务端api 返回的json种的字段 默认 data */
@Prop({ type: String, default: "data" })
outField!: string;
@Emit("change")
emitChange(val: string[]) {
return val;
}
@Emit("add")
emitAdd(newFile: string) {
this.emitChange(
this.imageList
.filter(x => x.state == "uploaded")
.map(x => x.value as string)
);
return newFile;
}
@Emit("delete")
emitDelete(file: string) {
this.emitChange(
this.imageList
.filter(x => x.state == "uploaded")
.map(x => x.value as string)
);
return file;
}
imageList: ImageItem[] = [];
get isShowAdd() {
let isShow = true;
if (this.forbidAdd || (this.limit && this.imageList.length >= this.limit)) {
isShow = false;
}
return isShow;
}
mounted() {
if (this.imageList.length > this.value.length) {
this.imageList.length = this.value.length; // 切掉多的
}
for (let i = 0; i < this.value.length; i++) {
let img = this.imageList[i] ?? new ImageItem();
img.value = this.value[i];
img.state = "uploaded";
}
}
getStateText(index: number) {
let img = this.imageList[index];
let txt = "";
switch (img.state) {
case "pre":
txt = "准备上传";
break;
case "uploading":
txt = "上传中...";
break;
case "uploaded":
txt = "上传完成";
break;
case "fail":
txt = "上传失败";
break;
}
return txt;
}
chooseImage() {
uni.chooseImage({
count: this.limit - this.imageList.length,
success: e => {
let imageArr: ImageItem[] = [];
for (let i = 0; i < e.tempFilePaths!.length; i++) {
let len = this.imageList.length;
if (len >= this.limit) {
uni.showToast({
title: `最多可上传${this.limit}张图片`,
icon: "none"
});
break;
}
let path = e.tempFilePaths![i] as string;
let item = new ImageItem();
item.preValue = path;
item.state = "pre";
imageArr.push(item);
this.imageList.push(item);
}
//this.change();
for (let item of imageArr) {
this.uploadImage(item); // 不等待,并行上传
}
}
});
}
reUpload(index: number) {
let img = this.imageList[index];
this.uploadImage(img);
}
/**上传单个照片 */
uploadImage(img: ImageItem) {
img.state = "uploading";
if (this.uploadApi == null || this.uploadApi == "") {
img.state = "uploaded";
return;
}
httpClient
.uploadFile<ApiResult<string>>(this.uploadApi, {
filePath: img.preValue,
fileType: "image",
name: this.filedName
})
.then(x => {
if (x.data.successed) {
img.state = "uploaded";
img.value = (x.data as any)[this.OutField] as string;
this.emitAdd(img.value);
} else {
img.state = "fail";
}
})
.catch(err => {
console.log(err);
img.state = "fail";
});
}
delImage(index: number) {
let item = this.imageList[index];
this.imageList.splice(index, 1);
if (item.state == "uploaded") {
this.emitDelete(item.value as string);
}
}
previewImage(index: number) {
let item = this.imageList[index];
uni.previewImage({
current: item.image,
loop: true,
urls: this.imageList.map(x => x.image)
});
}
}
class ImageItem {
value?: string;
preValue?: string;
state: UpStates = "uploaded";
get image() {
return this.value ?? this.preValue;
}
}
type UpStates = "pre" | "uploading" | "uploaded" | "fail";
</script>
<style lang="scss" scoped>
@font-face {
font-family: "tuiUpload";
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAATcAA0AAAAAByQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEwAAAABoAAAAciR52BUdERUYAAASgAAAAHgAAAB4AKQALT1MvMgAAAaAAAABCAAAAVjxvR/tjbWFwAAAB+AAAAEUAAAFK5ibpuGdhc3AAAASYAAAACAAAAAj//wADZ2x5ZgAAAkwAAADXAAABAAmNjcZoZWFkAAABMAAAAC8AAAA2FpiS+WhoZWEAAAFgAAAAHQAAACQH3QOFaG10eAAAAeQAAAARAAAAEgwAACBsb2NhAAACQAAAAAwAAAAMAEoAgG1heHAAAAGAAAAAHwAAACABEgA2bmFtZQAAAyQAAAFJAAACiCnmEVVwb3N0AAAEcAAAACgAAAA6OMUs4HjaY2BkYGAAYo3boY/i+W2+MnCzMIDAzb3qdQj6fwPzf+YGIJeDgQkkCgA/KAtvAHjaY2BkYGBu+N/AEMPCAALM/xkYGVABCwBZ4wNrAAAAeNpjYGRgYGBl0GJgZgABJiDmAkIGhv9gPgMADTABSQB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PGJ9xMjf8b2CIYW5gaAAKM4LkANt9C+UAAHjaY2GAABYIVmBgAAAA+gAtAAAAeNpjYGBgZoBgGQZGBhBwAfIYwXwWBg0gzQakGRmYnjE+4/z/n4EBQksxSf6GqgcCRjYGOIeRCUgwMaACRoZhDwCiLwmoAAAAAAAAAAAAAAAASgCAeNpdjkFKw0AARf/vkIR0BkPayWRKQZtYY90ohJju2kOIbtz0KD1HVm50UfEmWXoAr9ADOHFARHHzeY//Fx8Ci+FJfIgdJFa4AhgiMshbrCuIsLxhFJZVs+Vl1bT1GddtbXTC3OhohN4dg4BJ3zMJAnccyfm468ZzHXddrH9ZKbHzdf9n/vkY/xv9sPQXgGEvBrHHwst5kTbXLE+YpYVPkxepPmW94W16UbdNJd6f3SAzo5W7m1jaKd+8ZZIvk5nlKw9SK6Wle7BLS3f/bTzQLmfAF2T1NsQAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMGiTIxMjMxsKak5qSWpbFmZiRmJ+QAmgAUIAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMABAABAAQAAAACAAAAAHjaY2BgYGQAgqtL1DlA9M296nUwGgA+8QYgAAA=)
format("woff");
font-weight: normal;
font-style: normal;
}
.tui-upload-icon {
font-family: "tuiUpload" !important;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding: 10rpx;
}
.tui-icon-delete:before {
content: "\e601";
}
.tui-icon-plus:before {
content: "\e609";
}
.tui-upload-box {
width: 100%;
display: flex;
flex-wrap: wrap;
}
.tui-upload-add {
width: 220rpx;
height: 220rpx;
font-size: 68rpx;
font-weight: 100;
color: #888;
background-color: #f7f7f7;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.tui-image-item {
width: 220rpx;
height: 220rpx;
position: relative;
margin-right: 20rpx;
margin-bottom: 20rpx;
}
.tui-image-item:nth-of-type(3n) {
margin-right: 0;
}
.tui-item-img {
width: 220rpx;
height: 220rpx;
display: block;
}
.tui-img-del {
width: 36rpx;
height: 36rpx;
position: absolute;
right: -12rpx;
top: -12rpx;
background: #eb0909;
border-radius: 50%;
color: white;
font-size: 34rpx;
z-index: 999;
}
.tui-img-del::before {
content: "";
width: 16rpx;
height: 1px;
position: absolute;
left: 10rpx;
top: 18rpx;
background: #fff;
}
.tui-upload-mask {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
padding: 40rpx 0;
box-sizing: border-box;
background: rgba(0, 0, 0, 0.6);
}
.tui-upload-loading {
width: 28rpx;
height: 28rpx;
border-radius: 50%;
border: 2px solid;
border-color: #b2b2b2 #b2b2b2 #b2b2b2 #fff;
animation: tui-rotate 0.7s linear infinite;
}
@keyframes tui-rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
.tui-tips {
font-size: 26rpx;
color: #fff;
}
.tui-mask-btn {
padding: 6rpx 16rpx;
border-radius: 40rpx;
text-align: center;
font-size: 24rpx;
color: #fff;
border: 1rpx solid #fff;
display: flex;
align-items: center;
justify-content: center;
}
.tui-hover {
opacity: 0.5;
}
</style>
John0King commented
dingyong0214 commented
可以