182 lines
4.3 KiB
Vue
182 lines
4.3 KiB
Vue
<template>
|
||
<view class="upload-file-wrap">
|
||
<!-- 多图展示区域 -->
|
||
<view class="upload-file-item" v-for="(img, index) in modelValue" :key="index">
|
||
<image class="img" :src="img" mode="aspectFill" @click="previewImage(index)"></image>
|
||
<view class="close" @click.stop="removeImg(index)">
|
||
<up-icon name="close-circle" color="#333" size="14"></up-icon>
|
||
</view>
|
||
</view>
|
||
<!-- 上传按钮(未达上限时显示) -->
|
||
<view class="upload-file-btn" @click="chooseImage" v-if="modelValue.length < props.maxCount">
|
||
<slot v-if="$slots.default"></slot>
|
||
<view class="icon" v-else>+</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { uploadFile } from '@/http/api/index.js';
|
||
import { ref, watch, defineProps, defineEmits } from 'vue';
|
||
|
||
// 1. 定义Props(扩展配置项)
|
||
const props = defineProps({
|
||
// 最大上传数量(默认9,可外部传参)
|
||
maxCount: {
|
||
type: Number,
|
||
default: 9
|
||
},
|
||
// 是否压缩(默认开启)
|
||
isCompressed: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
// 图片来源(相册/相机,默认都支持)
|
||
sourceType: {
|
||
type: Array,
|
||
default: () => ['album', 'camera']
|
||
}
|
||
});
|
||
|
||
// 2. 双向绑定多图列表(Array类型)
|
||
const modelValue = defineModel({
|
||
type: Array,
|
||
default: () => []
|
||
});
|
||
|
||
// 3. 选择图片(支持多图)
|
||
async function chooseImage() {
|
||
// 计算剩余可上传数量
|
||
const remainCount = props.maxCount - modelValue.value.length;
|
||
if (remainCount <= 0) {
|
||
uni.showToast({ title: `最多只能上传${props.maxCount}张图片`, icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
uni.chooseImage({
|
||
count: remainCount, // 仅能选择剩余数量的图片
|
||
sizeType: props.isCompressed ? ['compressed'] : ['original', 'compressed'],
|
||
sourceType: props.sourceType,
|
||
success: async function (res) {
|
||
uni.showLoading({ title: '上传中' });
|
||
try {
|
||
// 批量上传选中的图片
|
||
const uploadPromises = res.tempFiles.map((file) => uploadFile(file));
|
||
const fileResList = await Promise.all(uploadPromises);
|
||
|
||
// 过滤上传失败的图片,仅保留成功的URL
|
||
const successUrls = fileResList.filter((url) => !!url);
|
||
if (successUrls.length > 0) {
|
||
modelValue.value = [...modelValue.value, ...successUrls]; // 追加到列表
|
||
uni.showToast({ title: `成功上传${successUrls.length}张图片`, icon: 'success' });
|
||
}
|
||
} catch (error) {
|
||
uni.showToast({ title: '部分图片上传失败', icon: 'none' });
|
||
console.error('图片上传失败:', error);
|
||
} finally {
|
||
uni.hideLoading();
|
||
}
|
||
},
|
||
fail: () => {
|
||
uni.showToast({ title: '取消选择图片', icon: 'none' });
|
||
}
|
||
});
|
||
}
|
||
|
||
// 4. 删除单张图片
|
||
function removeImg(index) {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要删除这张图片吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
modelValue.value.splice(index, 1); // 删除对应索引的图片
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 5. 预览图片(支持左右滑动)
|
||
function previewImage(currentIndex) {
|
||
uni.previewImage({
|
||
urls: modelValue.value, // 所有已上传的图片列表
|
||
current: modelValue.value[currentIndex], // 当前预览的图片
|
||
loop: true // 支持循环预览
|
||
});
|
||
}
|
||
|
||
// 6. 扩展:批量清空图片(可暴露给父组件调用)
|
||
const clearAllImg = () => {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要清空所有图片吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
modelValue.value = [];
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
// 暴露方法给父组件
|
||
defineExpose({ clearAllImg });
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.upload-file-wrap {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx; // 图片之间的间距
|
||
}
|
||
|
||
.upload-file-item {
|
||
$size: 128rpx;
|
||
width: $size;
|
||
height: $size;
|
||
border: 1px dashed #d9d9d9;
|
||
border-radius: 8rpx;
|
||
position: relative;
|
||
overflow: hidden;
|
||
|
||
.img {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.close {
|
||
position: absolute;
|
||
right: -10rpx;
|
||
top: -10rpx;
|
||
background: #fff;
|
||
border-radius: 50%;
|
||
padding: 2rpx;
|
||
z-index: 10;
|
||
}
|
||
}
|
||
|
||
.upload-file-btn {
|
||
$size: 128rpx;
|
||
width: $size;
|
||
height: $size;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
border: 1px dashed #d9d9d9;
|
||
border-radius: 8rpx;
|
||
|
||
.icon {
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
border: 4rpx solid #999;
|
||
border-radius: 8rpx;
|
||
font-weight: 700;
|
||
color: #999;
|
||
font-size: 32rpx;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
line-height: 1;
|
||
}
|
||
}
|
||
</style>
|