element-ui-plus 进行一个接口上传多张图片的问题

我爱海鲸 2024-09-07 15:58:56 暂无标签

简介go、vite、vue3、element-ui-plus

这是在工作中碰到的第一个问题,我们要进行一个图片上传的需求,但是原有的功能是单图片上传的功能,在改成多图片上传后发现,多图的上传的逻辑是通过一次次调用接口来实现的。

我们要做的效果是调用一次接口完成多张图片的上传。

首先我们创建一个图片上传接口使用go语言来实现:

1、安装go项目:

安装go环境:我爱海鲸 (haijin.xyz)

安装gin框架:go get -u github.com/gin-gonic/gin

创建一个main.go就好。go.mod不用管,uploads是上传的文件夹会自己创建

main.go:

package main

import (
	"fmt"
	"log"
	"net/http"
	"path/filepath"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	// 设置路由,处理多文件上传
	router.POST("/upload", func(c *gin.Context) {
		// 获取表单中的多文件
		form, err := c.MultipartForm()
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"code": 400,
				"msg":  "获取文件失败",
				"data": nil,
			})
			return
		}

		files := form.File["files"]

		var filePaths []string

		// 保存每个文件到本地,并记录保存路径
		for _, file := range files {
			filename := filepath.Base(file.Filename)
			filePath := "./uploads/" + filename

			if err := c.SaveUploadedFile(file, filePath); err != nil {
				log.Println("保存文件失败:", err)
				c.JSON(http.StatusInternalServerError, gin.H{
					"code": 500,
					"msg":  "文件保存失败",
					"data": nil,
				})
				return
			}

			// 将文件路径添加到 filePaths 列表中
			filePaths = append(filePaths, filePath)
		}

		// 返回 JSON 响应
		c.JSON(http.StatusOK, gin.H{
			"code": 200,
			"msg":  fmt.Sprintf("%d 个文件上传成功", len(files)),
			"data": filePaths,
		})
	})

	// 启动服务器
	router.Run(":8080")
}

go run ./main.go 启动后,使用postman进行调用:

2、创建前端项目:

公司使用的是vue+element-ui写,但我认为已经过时了,我们使用vue3+element-plus来进行创建,思想是一样。

创建项目:我爱海鲸 (haijin.xyz)

项目结构:

几个要改的地方

vite.config.ts:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080/', // 实际请求地址
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

package.json:

{
  "name": "upload",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.7.7",
    "element-plus": "^2.8.2",
    "vue": "^3.4.37"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.1.2",
    "typescript": "^5.5.3",
    "vite": "^5.4.1",
    "vue-tsc": "^2.0.29"
  }
}

main.ts:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

HelloWorld.vue:

<template>
  <div>
    <!-- 上传按钮 -->
    <el-upload
      action="/api/upload"                
      name="files"                        
      list-type="text"
      :multiple="true"
      :limit="5"
      :on-preview="handlePreview"
      :on-remove="handleRemove"
      :on-success="handleSuccess"
      :on-error="handleError"
      :file-list="fileList"
    >
      <el-button size="small" type="primary">点击上传</el-button>
      <el-button
        v-if="fileList.length"
        size="small"
        type="danger"
        @click="clearFiles"
      >
        清空所有文件
      </el-button>
    </el-upload>

    <!-- 上传失败提示 -->
    <el-alert
      v-if="uploadError"
      title="上传失败"
      type="error"
      show-icon
      :closable="false"
      style="margin-top: 20px;"
    >
      请检查网络连接或服务器状态。
    </el-alert>
  </div>
</template>

<script>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'

export default {
  setup() {
    const fileList = ref([])
    const uploadError = ref(false)

    const handlePreview = (file) => {
      console.log('预览文件:', file.name)
    }

    const handleRemove = (file, fileList) => {
      console.log('文件删除:', file, fileList)
    }

    const handleSuccess = (response, file, fileListRef) => {
      console.log('上传成功:', response, file, fileListRef)
      uploadError.value = false
      // 上传成功后弹出提示
      ElMessage({
        message: '文件上传成功!',
        type: 'success',
        duration: 3000,
      })
    }

    const handleError = (err, file, fileListRef) => {
      console.log('上传失败:', err, file, fileListRef)
      uploadError.value = true
      // 上传失败时也可以提示错误消息
      ElMessage({
        message: '文件上传失败!',
        type: 'error',
        duration: 3000,
      })
    }

    const clearFiles = () => {
      fileList.value = []
      uploadError.value = false
    }

    return {
      fileList,
      uploadError,
      handlePreview,
      handleRemove,
      handleSuccess,
      handleError,
      clearFiles
    }
  }
}
</script>

<style scoped>
.el-upload__tip {
  color: #67C23A;
}
</style>

使用命令:npm run dev 启动后按ctrl+选择多张图片,上传图片:

然后发现,这个图片的分多次进行上传,我们的需求是只调用一次接口完成多张图片的上传

现在我们来改一下这个问题:

在HelloWorld.vue:

<template>
  <div>
    <!-- 上传按钮 -->
    <el-upload
      ref="uploadRef"
      :auto-upload="false"
      :on-change="changeUpload"
      :before-upload="beforeUpload"
      multiple
      :on-error="errorUploadHandle"
      :on-success="successUploadHandle"
      name="file"
      :show-file-list="true"
      :on-remove="removeUploadHandle"
      :on-exceed="HandleExceed10"
      :limit="10"
      :file-list="uploadPathFileList"
    >
      <el-button :loading="officialShotsPathLoading" type="primary" plain>
        {{
          uploadPathFileList.length > 0
            ? (officialShotsPathMessage = '点击继续上传图片')
            : (officialShotsPathMessage = '单个文件不能大于5M')
        }}
      </el-button>
    </el-upload>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'

const uploadData  = ref({})

const uploadRef = ref()

const officialShotsPathFileUrl = ref("api/upload")

const officialSuccessHandle = (response, file) =>{

}

const officialShotsPathMessage = ref('文件不能大于5M')

const time = ref(null)

const officialShotsPathLoading = ref(false)

const uploadPathFileList = ref([])

const upOfficialShotsCheckFiles = ref([])
const upuploadPathFileList = ref([])

const changeUpload = (file, fileList) => {
      if (
        file.name
				&& !file.name.includes('.png')
				&& !file.name.includes('.jpg')
      ) {
        ElMessage({
          message: '仅支持PNG、JPG文件',
          type: 'error',
          duration: 3000,
        })
        return;
      }
      if ( file.size / 1024 / 1024 > 5) {
        ElMessage({
          message: '文件大小不能超过5MB',
          type: 'error',
          duration: 1200,
        })
        return;
      }
      upOfficialShotsCheckFiles.value.push(file)
      // 定时等待图片都上传
      clearTimeout(time.value)
      time.value = setTimeout(() => {
        time.value = null
        officialShotsRequest(upOfficialShotsCheckFiles.value)
      }, 0)
    }

    const beforeUpload=(file)=> {
      console.log("不触发了")
    }

    const officialShotsRequest = (fileArr) => {
      officialShotsPathLoading.value = true
      console.log(upOfficialShotsCheckFiles.value.length)
      if (upOfficialShotsCheckFiles.value.length > 0) {
        let data = new FormData()
        upOfficialShotsCheckFiles.value.forEach((ele)=>{
          data.append('files', ele.raw)
        })
        axios({
          url: "api/upload",
          method: 'POST',
          headers: {
            'Content-Type': 'multipart/form-data'
          },
          data,
        }).then(res => {
          ElMessage({
            message: `上传成功`,
            type: 'success'
          })
          successUploadHandle(res, fileArr)
        }).catch(error => {
          ElMessage({
            message: `上传失败,请重新上传`,
            type: 'error',
            duration: 3000,
          })
          officialShotsPathLoading.value = false
        }).finally(()=>{
          upOfficialShotsCheckFiles.value = []
        })
      }
      officialShotsPathLoading.value = false
    }

    const errorUploadHandle = ()=> {
      officialShotsPathLoading.value = false
      ElMessage({
        message: '上传失败,请重新上传',
        type: 'error',
        duration: 3000,
      })
    }

    const successUploadHandle=(res, fileArr)=> {
      console.log("返回结果")
      console.log(res)
      if (res && res.data.code === 200) {
        const dataArr = res.data.data || []
        if (uploadData.value.officialShotsPath === "") {
          dataArr.forEach((ele, index)=> {
            if (index === 0) {
              uploadData.value.officialShotsPath = ele
            } else {
              uploadData.value.officialShotsPath += "," + ele
            }
            const fileName = fileArr[index].name
            uploadPathFileList.value.push({name:`${fileName}`, url: ele })
          })
        } else {
          dataArr.forEach((ele, index)=>{
            uploadData.value.officialShotsPath += "," + ele
            const fileName = fileArr[index].name
            uploadPathFileList.value.push({name:`${fileName}`, url: ele })
          })
        }
      }
    }

    const  removeUploadHandle = (file, fileList)=> {
      removeImageByName(file.name)
    }

    const removeImageByName = (name)=> {
    // 从 uploadPathFileList 中删除对应的对象
    const indexToRemove = uploadPathFileList.value.findIndex(item => item.name === name);
    if (indexToRemove !== -1) {
      const urlToRemove = uploadPathFileList.value[indexToRemove].url;
      uploadPathFileList.value.splice(indexToRemove, 1);

      // 从 officialShotsPath 中删除对应的 URL
      let urls = uploadData.value.officialShotsPath.split(',');
      urls = urls.filter(url => url !== urlToRemove);
      uploadData.value.officialShotsPath = urls.join(',');
    }
  }

const beforeApiUpload=(file)=> {
      if (file.name && !file.name.includes('.jpg') && !file.name.includes('.pdf')) {
          ElMessage({
          message: '只支持JPG、PDF文件',
          type: 'error',
          duration: 3000,
        })
        return false
      } else if (
        file.size / 1024 / 1024 > 5
      ) {
        ElMessage({
          message: '文件大小不能超过5MB',
          type: 'error',
          duration: 3000,
        })
        return false
      }
      else {
        btnLoading.value = true
      }
    }

    const officialHandleRemove=()=> {
      officialFileList.value = []
      uploadData.value.officialDocumentPath = ''
    }

    const HandleExceed10 = () => {
      ElMessage({
          message: '当前限制选择 10 个文件,如上传的文件有误,请删除已上传文件后再次上传!',
          type: 'error',
          duration: 3000,
        })
    }

</script>

<style scoped>
.el-upload__tip {
  color: #67C23A;
}
</style>

上面的关键代码在于:

      // 定时等待图片都上传
      clearTimeout(time.value)
      time.value = setTimeout(() => {
        time.value = null
        officialShotsRequest(upOfficialShotsCheckFiles.value)
      }, 0)

防抖,最终获取

参考文章:

https://www.jb51.net/article/279609.htm

你好:我的2025