这是在工作中碰到的第一个问题,我们要进行一个图片上传的需求,但是原有的功能是单图片上传的功能,在改成多图片上传后发现,多图的上传的逻辑是通过一次次调用接口来实现的。
我们要做的效果是调用一次接口完成多张图片的上传。
首先我们创建一个图片上传接口使用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