uniapp 18 图片上传 + 预览 组件

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")
}
posted @ 2026-09-07 17:50  超轶绝尘  阅读(7)  评论(0)    收藏  举报