背景:

我这里是进行了一个正常的表单多文件上传。

踩坑:

踩坑1:

关于表单校验rules的问题。

我首先要想说一个uniapp这个rules,这个真恶心呀,也是我傻了,它里面每个属性都多了一个rules。

/**
 * 表单校验规则
 */
const rules = {
  name: {
    rules: [{ required: true, errorMessage: '请输入姓名' },]
  },
  avator: {
    rules: [{ required: true, errorMessage: '请上传头像' }]
  }
}

这里有两个rules,我写了一个直接不生效了。写element写多了,都没想过是这里的错,而且我这还是用gpt生成的,没想过是这里的错,我真的会谢,以后要细心一点。

还有,官方文档说这里这里如果是自定义规则,需要onReady调用,可以搜搜其他的情况。

踩坑2:

关于文件上传组件的uni-file-picker这个问题

首先说明一下:

1. 我这个上传是一个跟着表单一起上传的,

2. 而且我这个上传有点设计的不好,因为我后端的实体类中图片这个属性是一个string类型,所以说上传很麻烦。

如果使用 uni-file-picker进行上传的话,通过官方文档uni-app官网 (dcloud.net.cn)这个组件,其实它给你整好了都,但是比较糟心的是,它对于表单上传文件及其不友好,具体请看官方截图:

 这个什么意思呢,就是你上传完之后文件的值不会绑定到你定义的变量中,你必须用其他方法进行传值。

前端:

下面有个博客可以看看他的两个方法,看完可以回来。

uniapp uni-file-picker 上传踩坑 - 掘金 (juejin.cn)icon-default.png?t=N7T8https://juejin.cn/post/7211020974024081467

第一个方法老哥的思想是没有问题的。第二个思想也没有问题,但是你需要在后端多写两个东西,单独的提交图片,单独的删除图片,我比较懒,就想在前端让图片随着表单一起提交,而不是再写俩后端,这样太麻烦了。

老哥也是个老前端了,写的代码质量都不一样,不过它的代码需要修改一下:

/**
 * 选择图片
 */
const selectFileV = (res: any) => {
  let { tempFiles } = res;
  tempFiles.forEach((element: any) => {
    let { name, url, uuid, file } = element;
    volunCertifyFileLists.value.push({
      name, url, uuid, file,
    })
  });
  console.log("志愿者图片的值:", volunCertifyFileLists.value)

}


// 删除图片
const deleteFileV = (res: any) => {
  let { tempFile: { uuid } } = res
  let tar = volunCertifyFileLists.value.findIndex((element: {
    name: '',
    url: '',
    uuid: '',
    file: ''
  }) => element.uuid === uuid)
  if (typeof tar === 'number') {
    volunCertifyFileLists.value.splice(tar, 1)
  }
  console.log("志愿者图片的值:", volunCertifyFileLists.value)
}

改成这个样子,具体改的也就一个删除图片的函数,这里是用findIndex进行查找。测试了没啥bug,其他就没有啥了。

但是不得不说,看老哥的写的代码真好,高质量代码真的会改变人。

后端:

如果你没有我的2上传设计问题其实也就结束了,如果你也是后端实体类设计的图片属性的类型是string,你可以看看我的想法,不是说多好,但是够用。

前端弄完之后就是如何将文件和表单上传到后端了,这里因为后端属性的类型是string所以说正常一次性上传是不行的,正常思路就是点击submit的时候图片上传一次,表单上传一次。但是这样子需要的考虑太多了,我这里思路是:前端新建一个数组接收文件,表单中的图片属性就不要了,后端用实体类和一个文件数组接收,如下:

这样子就可以一起上传了,到service层的时候处理一下就行了。


最新状况,写出来了,但是踩坑2的想法不行了,因为微信小程序不能多文件上传,得遍历上传,这样子就不好了,还不如直接用老哥得方法2,这样还不麻烦

我是参考下面老哥得方法得

uni-app 同时上传多个文件_uni.uploadfile上传多个文件-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/wk198786/article/details/131595601怎么说呢,如果想多文件上传,小程序端只能轮询,uniapp官方说了小程序不支持上传数组,所以说,综合考虑,还是自动上传好,做个总结就是你如果上传得文件多,那就自动上传,不费事,费内存,不过可以写个删除得,就比如如果前端这边修改图片删除了,后端也跟着删除。因为我的项目是需要两个字段都多张上传,虽然可以一件上传,但是太麻烦了,而且进行前端回显得话,自动上传也方便。以前想着省点流量,变成手动上传。还是自动上传好点。

这里我找了几个平台,抖音网页版是自动上传,咸鱼,今日头条,美团,都是自动上传。

不过代码写完了都,得留一下做个备注。

这是又找到得好文章:

uni-app 微信小程序 实现图片视频多文件上传功能_uniapp上传图片或视频-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_36410795/article/details/120552848

具体代码:

前端:

<template>
  <view class="container">
    <view class="header">
      <view class="title">学生信息</view>
    </view>
    <view class="main">
      <uni-forms ref="formRules" :rules="rules" :modelValue="formData" label-position="top">
        <uni-card :is-shadow="false">
          <uni-forms-item class="form-card" label="选择头像" name="avator">
            <button class="avatar-wrapper" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
              <image class="avatar" :src="avatar"></image>
            </button>
          </uni-forms-item>
        <uni-card :is-shadow="false">
          <uni-forms-item class="form-card" label="志愿者图片证明" name="volunCertify">
            <uni-file-picker :auto-upload="false" v-model="volunCertifyFileLists" file-mediatype="image" mode="grid"
              :limit="9" @select="selectFileV" @delete="deleteFileV" />
          </uni-forms-item>
        </uni-card>
        <uni-card :is-shadow="false">

          <uni-forms-item class="form-card" label="专业人才图片证明" name="professCertify">
            <uni-file-picker :auto-upload="false" v-model="professCertifyFileLists" file-mediatype="image" mode="grid"
              :limit="9" @select="selectFileP" @delete="deleteFileP" />
          </uni-forms-item></uni-card>
      </uni-forms>
      <button @click="submit(formRules)">Submit</button>


    </view>
    <view calss="footer"></view>
  </view>
</template>

<script lang="ts" setup>
import { ref, reactive } from 'vue'


interface StudentInfo {
  avator: string; // 头像地址
  volunCertify: string | []; // 志愿者证明图片地址
  professCertify: string | []; // 专业人才证明图片地址
}
const formData = reactive<StudentInfo>({
  avator: '',
  volunCertify: [],
  professCertify: [],
})



// 头像列表
const avatar = ref<string>('https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0');

const onChooseAvatar = (e: any) => {
  // console.log("e.detail的值: ", e)
  const { avatarUrl } = e.detail
  avatar.value = avatarUrl
  // console.log("新的头像值:", avatar.value)
}
/**
 * 两个图片列表
 */

const volunCertifyFileLists = ref<{
  name: '',
  url: '',
  uuid: '',
  file: ''
}[]>([]);
const professCertifyFileLists = ref<{
  name: '',
  url: '',
  uuid: '',
  file: ''
}[]>([]);

/**
 * 选择图片
 */
const selectFileV = (res: any) => {
  let { tempFiles } = res;
  tempFiles.forEach((element: any) => {
    let { name, url, uuid, file } = element;
    volunCertifyFileLists.value.push({
      name, url, uuid, file,
    })
  });
  console.log("志愿者图片的值:", volunCertifyFileLists.value)
}

const selectFileP = (res: any) => {
  let { tempFiles } = res;
  tempFiles.forEach((element: any) => {
    let { name, url, uuid, file } = element;
    professCertifyFileLists.value.push({
      name, url, uuid, file,
    })
  });
  console.log("专业技术人才图片的值:", professCertifyFileLists.value)

}


// 删除图片
const deleteFileV = (res: any) => {
  let { tempFile: { uuid } } = res
  let tar = volunCertifyFileLists.value.findIndex((element: {
    name: '',
    url: '',
    uuid: '',
    file: ''
  }) => element.uuid === uuid)
  if (typeof tar === 'number') {
    volunCertifyFileLists.value.splice(tar, 1)
  }
  console.log("志愿者图片的值:", volunCertifyFileLists.value)
}

const deleteFileP = (res: any) => {
  let { tempFile: { uuid } } = res
  let tar = professCertifyFileLists.value.findIndex((element: {
    name: '',
    url: '',
    uuid: '',
    file: ''
  }) => element.uuid === uuid)
  if (typeof tar === 'number') {
    professCertifyFileLists.value.splice(tar, 1)
  }
  console.log("专业技术人才图片的值:", professCertifyFileLists.value)
}


/**
 * 表单校验规则
 */
const rules = {
}

// 上传的图片数组
const imgFiles = ref<{ name: string, uri: string }[]>([])

/**
 * 拼接函数
 */

const concat = () => {
  imgFiles.value = [];
  // 拼接头像
  if (avatar.value != 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0') {
    formData.avator = avatar.value;
  }

  // 拼接志愿者图片
  if (volunCertifyFileLists.value.length > 0) {
    volunCertifyFileLists.value.forEach((item, index) => {
      imgFiles.value.push({
        name: `volunteer${index + 1}`,
        uri: item.url
      })
    })

  }
  // 拼接专业人才证明图片
  if (professCertifyFileLists.value.length > 0) {
    professCertifyFileLists.value.forEach((item, index) => {
      imgFiles.value.push({
        name: `profess${index + 1}`,
        uri: item.url
      })
    })

  }
}

// 表单校验
const formRules = ref<any>();

/**
 * 进行轮询提交图片
 */

const uploadFile = (imgs: any[]) => {
  const uploadTasks = imgs.map((file: any, index: number) => {
    return new Promise((resolve, reject) => {
      const uploadTask = uni.uploadFile({
        url: '',
        filePath: file.uri,
        name: 'imgFile',
        formData: formData,
        header: {
          'Content-Type': 'multipart/form-data'
        },
        success: function (res) {
          resolve(res.data)
        },
        fail: function (err) {
          reject(err)
        },

      });
      // 这里可以根据需要显示进度条
      uploadTask.onProgressUpdate((res) => {
        console.log('上传进度', res.progress)
        console.log('已经上传的数据长度', res.totalBytesSent)
        console.log('预期需要上传的数据总长度', res.totalBytesExpectedToSend)

      })
    })
  })
  Promise.all(uploadTasks)
    .then((res) => {
      console.log('上传成功', res)
      // 上传成功后的操作
    })
    .catch((err) => {
      console.log('上传失败', err)

      // 上传失败后的操作
    })
}

/**
 * 表单提交
 */
const submit = async (formRules: any) => {
  formRules.validate(async (valid: any, errors: any) => {
    if (!valid) {
      uni.showLoading({
        title: '提交中...',
        mask: true
      })

      // 拼接图片
      concat()
      console.log("imgFiles.value的值:", imgFiles.value);
      console.log("选择之后的数据: ", formData);
      uploadFile(imgFiles.value)
      uni.hideLoading()
    } else { // 当表单验证失败时

      console.log('表单校验失败', errors); // 输出错误详情

    }
  })
}

</script>

<style scoped>
.container {
  height: 100vh;
  background-color: #fafafa;

}

.header {
  font-size: 20px;
  text-align: center;
}

.main {
  text-align: center;
  padding-top: 1px;
}

.avatar-wrapper {
  width: 64px;
  height: 64px;
  border-radius: 50%;
}

.avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  margin-left: -14px;
}
</style>

唯一值得说的是两个代码,一个是轮询上传,一个是选择图片删除图片,写法都挺好得。

后端:

    public Result updateDto(@RequestParam("imgFile") MultipartFile imgFile) throws IOException {
        String originalFilename = imgFile.getOriginalFilename();
        imgFile.transferTo(new File("D:\\images\\"+originalFilename));
        return Result.success("修改成功");
    }

这里如果遇见有文件报错500,估计是文件限制大小得原因,改一下就行了

这里后端删除图片我没有写,大概给一下思路,你提交删除就要先判断一下你的图片字段是否有值,如果有值,那就通过链接先把服务器上的图片删除了,然后再添加新的图片。如果没值,那说明本就没有图片,直接赋值即可,这个比较方便,因为是点击提交一件上传的。

 

更新一下,关于第一个老哥的点击上传就上传,点击删除就删除

如果你选择第一个链接老哥的做法,给你个忠告,uniapp它的文件删除很麻烦,如果你先上传一个图片,然后再删除是没法将服务器图片属性传入删除函数,如果是本身就有值,那就没啥问题可以传的到删除函数。因为他的删除函数的形参是绑定内存中虚拟的图片的属性,就是你提交一个图片后,它组件里面会先在内存中生成一个虚拟的图片,虽然你是提交了服务器,并且用v-model进行绑定了,但是组件内只有虚拟图片属性。你如果点击删除,对于删除函数中的形参他是内存中的图片,而不是v-model所绑定的值。。所以说你如果想删除只能用组件给的uuid进行删除。思路是第一个老哥的做法,传的话带个uuid作为图片的唯一属性即可。后端根据uuid作为图片的名字,然后截取字符串,相同的话直接删除即可,我跟老哥一样感觉这是一个大坑。

还有一件事是如果在表单中用户反悔了,删除完图片之后就退出了,从新进,这个情况下是挺麻烦的,此时图片已经被删除了,或者说,它添加了一张图片之后就退出了。

对于第一个删除图片返回,我的想法是删除图片的时候不要调用后端,就在前端删除,可以拿个数组存着,到时候一并传回后端,然后一下子删完。添加一张图片之后退出的话也是这样,给他加一个按钮,如果点击退出,那就直接把新增的图片给后端,让后端从服务器删了就行。其实我感觉这个情况还是看架构,如果公司架构师好的话,这个情况完全可以避免。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐