Files
cashier_app/components/my-components/my-upload-imgs.vue

182 lines
4.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>