图片上传 + 展示公共组件,可通过组件传递属性动态设置是否允许添加图片功能,是否允许删除图片功能,是否允许图片预览。组件样式可按自己样式调整CSS,最主要的是核心功能的实现
1. 图片上传组件源码
<template>
<view class="uploadCardContainer leftSideLine">
<view class="uploadTitleContainer" v-if="title">
<view class="circle">{{ orderNum }}</view>
<view class="title">{{ title }}</view>
</view>
<view class="uploadTitleContainer" v-if="desc">
<view class="circle" style="background-color: #F4F5F9;"></view>
<view class="title" style="color: #929395; font-size: $base-lable-font-size; font-weight: 400;">{{ desc }}</view>
</view>
<!-- 未上传任何照片时展示空白卡片 -->
<view class="firstRowArea" v-if="tempImgUrls.length == 0">
<view class="leftSideLine" v-if="userType == 2"></view>
<view class="uploadImg" @click="chooseImg2(props.maxImgNum)">
<view class="bg-item" v-for="item in 4" :key="item"></view>
<view class="clickImg">
<image mode="aspectFit" :src="fileBaseUrl + 'static/images/svg/camera_blue.svg'" alt="" />
<view class="tilte">{{ uploadIconTitle }}</view>
<view class="desc">支持拍照/相册选择</view>
</view>
</view>
</view>
<!-- 已上传照片时展示图片 可删除, 可添加 -->
<view class="firstRowArea" v-else v-for="(url, index) in tempImgUrls" :key="index">
<view class="leftSideLine" v-if="userType == 2"></view>
<view class="uploadImg">
<image mode="aspectFill" v-if='url.startsWith("http") || url.startsWith("wxfile")' :src="url" alt="" @click="reviewImages(index)" />
<image mode="aspectFill" v-else :src="fileBaseUrl + url" alt="" @click="reviewImages(index)" />
</view>
<view class="deleteImg" v-if="canDel">
<uni-icons type="trash" size="20" @click="deleteImg(index)"></uni-icons>
</view>
</view>
<view class="addImg" v-if="canAdd && tempImgUrls.length > 0 && tempImgUrls.length < maxImgNum">
<uni-icons type="plusempty" size="25" @click="chooseImg2()"></uni-icons>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { chooseImgUtil } from '@/utils/imageUtil.js'
import { onLoad } from '@dcloudio/uni-app'
const emit = defineEmits(['confirmImgUrls']);
const app = getApp()
const fileBaseUrl = app.globalData.fileBaseUrl
onLoad((e) => {
console.log("upload-img component onLoad", e)
})
const userType = ref(2)
const tempImgUrls = ref([])
const props = defineProps({
//上传索引
uploadIndex: Number,
//最大图片数量
maxImgNum: Number,
//编号
orderNum: String,
//标题
title: String,
//描述
desc: String,
//上传按钮title
uploadIconTitle : {
type: String,
default: "点击上传"
},
//上传按钮title
uploadIconDesc: {
type: String,
default: "支持拍照/相册选择"
},
imgUrls: {
type: Array,
default: null
},
//是否展示添加按钮
canAdd: {
type: Boolean,
default: true
},
//是否展示删除按钮
canDel: {
type: Boolean,
default: true
},
//是否可预览
canPreview: {
type: Boolean,
default: true
},
});
if (props.imgUrls && props.imgUrls.length > 0) {
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~imgUrls = ", props.imgUrls)
tempImgUrls.value = [...props.imgUrls]
}
console.log("upload img props urls: ", tempImgUrls.value)
async function chooseImg2(count = 1) {
const res = await chooseImgUtil(count)
console.log("上图片结果:", res)
// tempImgUrls.value = [...tempImgUrls.value, ...res.tempFilePaths]
tempImgUrls.value = [...tempImgUrls.value, ...res]
//同步到父组件
emit('confirmImgUrls', tempImgUrls.value, props.uploadIndex)
}
/** 预览图片,默认预览第一张 */
function reviewImages(imageIndex = 0) {
if (tempImgUrls.value.length == 0) {
return
}
const tempImgShowUrls = []
for (let i = 0; i < tempImgUrls.value.length; i++) {
if (tempImgUrls.value[i].startsWith("http") || tempImgUrls.value[i].startsWith("wxfile") ) {
tempImgShowUrls.push(tempImgUrls.value[i])
} else {
tempImgShowUrls.push(fileBaseUrl + tempImgUrls.value[i])
}
}
// 预览图片
uni.previewImage({
current: imageIndex,
urls: tempImgShowUrls,
longPressActions: {
itemList: ['发送给朋友', '保存图片', '收藏'],
success: function(data) {
// console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
},
fail: function(err) {
// console.log(err.errMsg);
}
}
});
}
/**
* 删除某张图片
*/
function deleteImg(index = 0) {
tempImgUrls.value.splice(index, 1)
//同步到父组件
emit('confirmImgUrls', tempImgUrls.value, props.uploadIndex)
}
/**
* 清空所有图片
*/
function clearImgs() {
if (tempImgUrls.value.length > 0) {
tempImgUrls.value.splice(0)
}
}
defineExpose({clearImgs})
</script>
<style lang="scss" scoped>
/*上传卡片样式*/
.uploadCardContainer {
// border: 1px solid red;
width: 90%;
display: flex;
flex-direction: column;
align-items: center;
.uploadTitleContainer {
// border: 1px solid red;
width: 100%;
display: flex;
.circle {
// border: 1px solid red;
color: white;
background-color: $base-upload-image-color;
width: 50rpx;
height: 50rpx;
border-radius: 50%;
display: grid;
place-items: center;
}
.title {
width: 80%;
font-weight: bold;
color: $base-upload-image-color;
margin-left: 20rpx;
height: 100%;
line-height: 50rpx;
}
}
.firstRowArea {
// border: 1px solid blue;
width: 100%;
display: flex;
// justify-content: center;
.leftSideLine {
border-left: 2rpx solid #DCDCDC;
// height: 310rpx;
margin: 20rpx 0;
margin-left: 24rpx;
}
.uploadImg {
// border: 1rpx solid red;
width: 550rpx;
height: 300rpx;
background-color: white;
border-radius: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
margin: 20rpx 10rpx 20rpx 45rpx;
.bg-item {
width: 90%;
height: 40rpx;
background-color: #F9F9F9;
border-radius: 20rpx;
}
.clickImg {
// border: 1px solid red;
display: grid;
place-items: center;
position: absolute;
image {
width: 50rpx;
height: 50rpx;
}
.title {
}
.desc {
font-size: $base-lable-font-size;
color: #929395;
letter-spacing: 3rpx;
}
}
}
}
.deleteImg {
// border: 1px solid red;
margin: auto 0;
// margin-right: 24rpx;
}
}
</style>
2. 组件调用
<template>
<view class="pageBg">
<view class="container">
<upload-img :uploadIndex="0" ref="imgUploadRef1" orderNum="1" title="上传医疗发票" :maxImgNum="10" :desc="uploadDesc1" @confirmImgUrls="confirmImgUrls"
v-if="eventDetail.imgJson?.medicalInvoiceImages" :imgUrls="eventDetail.imgJson?.medicalInvoiceImages"/>
<upload-img :uploadIndex="0" ref="imgUploadRef1" orderNum="1" title="上传医疗发票" :maxImgNum="10" :desc="uploadDesc1" @confirmImgUrls="confirmImgUrls"
v-else/>
<upload-img :uploadIndex="1" ref="imgUploadRef2" orderNum="2" title="上传病例" :maxImgNum="100" :desc="uploadDesc2" @confirmImgUrls="confirmImgUrls"
v-if="eventDetail.imgJson?.medicalRecordImages" :imgUrls="eventDetail.imgJson?.medicalRecordImages"/>
<upload-img :uploadIndex="1" ref="imgUploadRef2" orderNum="2" title="上传病例" :maxImgNum="100" :desc="uploadDesc2" @confirmImgUrls="confirmImgUrls"
v-else/>
<upload-img :uploadIndex="2" ref="imgUploadRef3" orderNum="3" title="上传就诊照片" :maxImgNum="100" :desc="uploadDesc3" @confirmImgUrls="confirmImgUrls"
v-if="eventDetail.imgJson?.medicalAttentionImages" :imgUrls="eventDetail.imgJson?.medicalAttentionImages"/>
<upload-img :uploadIndex="2" ref="imgUploadRef3" orderNum="3" title="上传就诊照片" :maxImgNum="100" :desc="uploadDesc3" @confirmImgUrls="confirmImgUrls"
v-else/>
<upload-img :uploadIndex="3" ref="imgUploadRef4" orderNum="4" title="上传保险报销凭证" :maxImgNum="100" :desc="uploadDesc4" @confirmImgUrls="confirmImgUrls"
v-if="eventDetail.imgJson?.medicalInsuranceImages" :imgUrls="eventDetail.imgJson?.medicalInsuranceImages"/>
<upload-img :uploadIndex="3" ref="imgUploadRef4" orderNum="4" title="上传保险报销凭证" :maxImgNum="100" :desc="uploadDesc4" @confirmImgUrls="confirmImgUrls"
v-else/>
<view v-show="canUpload && hasTreatFinished == 1" class="uploadButton" @click="submitImages()">立即上传</view>
</view>
</template>
<script setup>
import config from '@/config.js'
import { onLoad } from '@dcloudio/uni-app'
import { ref, getCurrentInstance } from "vue"
import { uploadTempImgs } from "@/utils/upload.js"
import { usePetUserStore } from '@/store/modules/petUser'
const petUserStore = usePetUserStore()
const { proxy } = getCurrentInstance()
//非新建,修改
let eventDetail = ref({
id: null,
eventContent: '',
insureReimburseAmount: 0
})
let date = new Date();
let year = date.getFullYear();
let month = String(date.getMonth() + 1).padStart(2, '0');
let day = String(date.getDate()).padStart(2, '0');
const app = getApp()
const fileBaseUrl = app.globalData.fileBaseUrl
//上传图片子组件引用
const imgUploadRef1 = ref(null)
const imgUploadRef2 = ref(null)
const imgUploadRef3 = ref(null)
const imgUploadRef4 = ref(null)
//上传图片临时url
const tempImgUrls_00 = ref([])
const tempImgUrls_01 = ref([])
const tempImgUrls_02 = ref([])
const tempImgUrls_03 = ref([])
//以数组的形式集中管理,并通过组件中下标索引值属性定位子组件
const tempImgUrlsArr = [tempImgUrls_00, tempImgUrls_01, tempImgUrls_02, tempImgUrls_03]
//上传详情
const uploadDesc1 = ref(null)
const uploadDesc2 = ref(null)
const uploadDesc3 = ref(null)
const uploadDesc4 = ref(null)
onLoad(async(e) => {
//初始化当前事件内容详情
await initEventById(e.id)
})
/**
* 初始化互援事件
*
* @param {*} id 互援事件id
*/
async function initEventById(id) {
if (!id) {
return
}
//从后端接口查询数据
const res = await queryMutualAidEventById(id)
eventDetail.value = res.data
//将后端查到的图片数据放置到图片上传组件临时图片数组中,以备上传使用
const imgJson = res.data.imgJson
if (imgJson) {
if (imgJson.medicalInvoiceImages && imgJson.medicalInvoiceImages.length > 0) { //发票
tempImgUrls_00.value = [...imgJson.medicalInvoiceImages]
//等同于: confirmImgUrls(imgJson.medicalInvoiceImages, 0)
}
if (imgJson.medicalRecordImages && imgJson.medicalRecordImages.length > 0) { //病例
tempImgUrls_01.value = [...imgJson.medicalRecordImages]
}
if (imgJson.medicalAttentionImages && imgJson.medicalAttentionImages.length > 0) { //就诊
tempImgUrls_02.value = [...imgJson.medicalAttentionImages]
}
if (imgJson.medicalInsuranceImages && imgJson.medicalInsuranceImages.length > 0) { //保险
tempImgUrls_03.value = [...imgJson.medicalInsuranceImages]
}
}
}
/**
* 确认子组件中上传的临时图片,子组件新增、删除时调用
*
* @param {Object} imgUrls 图片上传组件中新增删除操作后的吐图片url集合
* @param {Object} index 本次图片变动发生于本页面图片上传组件的下标
*/
function confirmImgUrls(imgUrls, index) {
console.log("confirmImgUrls imgUrls from Son emit: ", imgUrls, index)
tempImgUrlsArr[index].value = [...imgUrls]
}
/**
* 提交申请医疗互援
*/
async function submitImage() {
//发票
let imgUrls_00 = await uploadTempImgs(tempImgUrls_00.value, "medicalInvoice", userId)
//病例
let imgUrls_01 = await uploadTempImgs(tempImgUrls_01.value, "medicalCase", userId)
//就诊
let imgUrls_02 = await uploadTempImgs(tempImgUrls_02.value, "medicalAttention", userId)
//
let imgUrls_03 = await uploadTempImgs(tempImgUrls_03.value, "medicalInsurance", userId)
let data = {
id: eventDetail.value.id,
imgJson: {
medicalInvoiceImages: [...imgUrls_00],
medicalRecordImages: [...imgUrls_01],
medicalAttentionImages: [...imgUrls_02],
signSignatureImage: signSignatureUrl
}
}
}
</script>
<style lang="scss" scoped>
</style>
3. 图片上传公共方法 upload.js
import config from '@/config'
const baseUrl = config.baseUrl
/**
*
* @param {Object} filePaths上传文件路径
*/
export function uploadFile(filePath, fileType, userId) {
console.log("uploadFile param: ", filePath, fileType, userId)
return new Promise((resolve, reject) => {
uni.uploadFile({
url: baseUrl + '/file/tencent/uploadCos',
filePath,
name: 'file',
formData: {
fileType,
userId
},
success: (uploadFileRes) => {
const uploadRes = JSON.parse(uploadFileRes.data).data;
console.log("uploadFile success parse result:...", uploadRes)
resolve(uploadRes)
},
fail(res) {
reject("上传失败", res);
}
})
});
}
/**
* <h3>上传多张图片,并判断是否是服务器中已有的URL </h3>
* <ol>
* <li> 若是服务器中已有的图片,则不再上传</li>
* <li> 若是新上传的图片,则上传至服务器</li>
* </ol>
* @param {Object} tempImgArr
*/
export async function uploadTempImgs(tempImgArr, fileType, userId) {
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(prefix) {
return this.slice(0, prefix.length) === prefix;
};
}
const arr = []
const uploadTasks = []
for (let i = 0; i < tempImgArr.length; i++) {
if (tempImgArr[i] && isTempFile(tempImgArr[i])) {
const uploadTask = uploadFile(tempImgArr[i], fileType, userId)
uploadTasks.push(uploadTask)
} else {
const urlObj = {url: tempImgArr[i]}
uploadTasks.push(Promise.resolve(urlObj))
}
}
const results = await Promise.all(uploadTasks)
console.log('所有图片上传结果:', results)
for (let i = 0; i < results.length; i++) {
arr.push(results[i].url)
}
return arr
}
/**
* 判断是否是临时文件
* @param {Object} url
*/
function isTempFile(url) {
return url.startsWith("wxfile://tmp_") || url.startsWith("http://tmp") || url.startsWith("https://tmp")
}