20220413_有赞表单组件van-uploader文件上传前后台学习笔记
1概述
van-uploader用于将本地的图片或文件上传至服务器,并在上传过程中展示预览图和上传进度。目前 Uploader 组件不包含将文件上传至服务器的接口逻辑,该步骤需要自行实现。
目前 Chrome、Safari 等浏览器不支持展示 HEIC/HEIF 格式的图片,因此上传后无法在 Uploader 组件中进行预览。
文件上传的核心点:构建FormData,将文件以表单的形式发送出去。
本文主要基于该组件进行全流程功能实现。
1.1van-uploader
1.1.1API
Props
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
v-model (fileList) | 已上传的文件列表 | FileListItem[] | - |
accept | 允许上传的文件类型,详细说明 | string | image/* |
name | 标识符,可以在回调函数的第二项参数中获取 | number | string | - |
preview-size | 预览图和上传区域的尺寸,默认单位为 px |
number | string | 80px |
preview-image | 是否在上传完成后展示预览图 | boolean | true |
preview-full-image | 是否在点击预览图后展示全屏图片预览 | boolean | true |
preview-options v2.9.3 |
全屏图片预览的配置项,可选值见 ImagePreview | object | - |
multiple | 是否开启图片多选,部分安卓机型不支持 | boolean | false |
disabled | 是否禁用文件上传 | boolean | false |
readonly v2.12.26 |
是否将上传区域设置为只读状态 | boolean | false |
deletable | 是否展示删除按钮 | boolean | true |
show-upload v2.5.6 |
是否展示上传区域 | boolean | true |
lazy-load v2.6.2 |
是否开启图片懒加载,须配合 Lazyload 组件使用 | boolean | false |
capture | 图片选取模式,可选值为 camera (直接调起摄像头) |
string | - |
after-read | 文件读取完成后的回调函数 | Function | - |
before-read | 文件读取前的回调函数,返回 false 可终止文件读取, 支持返回 Promise |
Function | - |
before-delete | 文件删除前的回调函数,返回 false 可终止文件读取, 支持返回 Promise |
Function | - |
max-size v2.12.20 |
文件大小限制,单位为 byte |
number | string | (file: File) => boolean | - |
max-count | 文件上传数量限制 | number | string | - |
result-type | 文件读取结果类型,可选值为 file text |
string | dataUrl |
upload-text | 上传区域文字提示 | string | - |
image-fit | 预览图裁剪模式,可选值见 Image 组件 | string | cover |
upload-icon v2.5.4 |
上传区域图标名称或图片链接 | string | photograph |
注意:accept、capture 和 multiple 为浏览器 input 标签的原生属性,移动端各种机型对这些属性的支持程度有所差异,因此在不同机型和 WebView 下可能出现一些兼容性问题。
1.1.2事件
1.1.3回调参数
before-read、after-read、before-delete 执行时会传递以下回调参数:
参数名 | 说明 | 类型 |
---|---|---|
file | file 对象 | object |
detail | 额外信息,包含 name 和 index 字段 | object |
1.1.3.1file对象格式
file具有的属性如下:
content:文件对应的base64编码
file:文件元信息对象
message:
status:
单个文件格式:
多个文件格式:
1.4桌面端适配(桌面预览鼠标点击无法关闭,好人)
Vant 是一个面向移动端的组件库,因此默认只适配了移动端设备,这意味着组件只监听了移动端的 touch
事件,没有监听桌面端的 mouse
事件。
如果你需要在桌面端使用 Vant,可以引入我们提供的 @vant/touch-emulator,这个库会在桌面端自动将 mouse
事件转换成对应的 touch
事件,使得组件能够在桌面端使
https://youzan.github.io/vant/v2/#/zh-CN/advanced-usage#zhuo-mian-duan-gua-pei
# 安装模块
npm i @vant/touch-emulator -S
// 引入模块后自动生效
import '@vant/touch-emulator';
2代码示例
2.1前端请求
2.1.1注册Vant(main.js)
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
// 支持一次性导入所有组件,引入所有组件会增加代码包体积,但比较爽
// 因此不推荐这种做法
// 1.引入 Vant组件
import Vant from 'vant'
// 2.引入 Vant样式
import 'vant/lib/index.css'
// 3.引入拦截器
import './config/httpinterceptor'
Vue.config.productionTip = false
// 3.注册 Vant
Vue.use(Vant)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: ' '
})
// 通过main.js将App.vue渲染到index.html的指定区域中。
2.1.2导入业务组件(main.js)
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
// 支持一次性导入所有组件,引入所有组件会增加代码包体积,但比较爽
// 因此不推荐这种做法
// 1.引入 Vant组件
import Vant from 'vant'
// 2.引入 Vant样式
import 'vant/lib/index.css'
import './config/httpinterceptor'
Vue.config.productionTip = false
// 3.注册 Vant
Vue.use(Vant)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: ' '
})
// 通过main.js将App.vue渲染到index.html的指定区域中。
2.1.3httpinterceptor拦截器
// 接口请求拦截器
import Vue from 'vue'
import VueResource from 'vue-resource'
Vue.use(VueResource)
// 接口请求拦截器
Vue.http.interceptors.push((request, next) => {
debugger
// 1.credentials
// request.credentials = true;
// 2.Content_Type
// 'multipart/form-data'
// request.headers.get('Content-Type')
// url:/server/paging -->/api/server/paging
// 3.判断方法的类型
// if(/^post$/i.test(request.method))
console.log(request.url)
// 每个接口请求拦截前加前缀: /api,这样可以走同一的后台代理
request.url = '/api' + request.url
next((response) => {
// 在响应之后传给then之前对response进行修改和逻辑判断。
// 对于token时候已过期的判断,就添加在此处,
// 页面中任何一次 http请求都会先调用此处方法
if (response.status === 401) {
console.log('response.body:', response.body)
window.location.reload()
}
return response
})
})
2.1.4编写业务组件
2.1.4.1编写模板
{{ msg }}
Vant
-
2.1.4.2编写行为
2.1.4.2.1afterUploader核心发送函数分析
请特别注意headers设置。
afterUploader (file) {
debugger
// 此时可以自行将文件上传至服务器
console.log(file)
// 1.添加文件类型请求头
let httpOption = {
headers: {
// 添加文件类型请求头
'Content-Type': 'multipart/form-data'
}
}
// 2.构建 FormData
let formData = new FormData()
// 单个文件:
formData.append('file', file.file, file.filename)
// 多个文件:
// for (let singlefile of file) {
// // 分多次向formData中同一个键名下添加一个文件即可
// formData.append('files', singlefile.file, singlefile.filename)
// }
// 3.发送
// vue-resource
this.$http.post('/file/upload', formData, httpOption).then(({body}) => {
debugger
}).catch(() => {
debugger
})
}
2.1.4.3编写样式
2.2后台处理
2.2.1FileController
package com.kikop.controller;
import com.alibaba.fastjson.JSONObject;
import com.kikop.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
/**
* @author kikop
* @version 1.0
* @project myantbackdemo
* @file FileController
* @desc
* @date 2022/04/13
* @time 20:39
* @by IDE: IntelliJ IDEA
*/
@RestController
@RequestMapping("/api/file")
public class FileController {
@Autowired
FileService fileService;
// web容器临时存储目录:
// C:\Users\kikop\AppData\Local\Temp\tomcat.1514260066643344745.8086\work\Tomcat\localhost\ROOT\
@RequestMapping("/upload")
public JSONObject upload(@RequestParam(value = "file", required = true) MultipartFile file, HttpServletRequest request) {
return fileService.upload(file, request);
}
@RequestMapping("/upload")
public JSONObject multipleUpload(@RequestParam(value = "files", required = true) MultipartFile file, HttpServletRequest request) {
return fileService.upload(file, request);
}
}
2.2.2FileServiceImpl
package com.kikop.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.kikop.myconst.CommonResponse;
import com.kikop.myconst.ConstVarManager;
import com.kikop.service.FileService;
import com.kikop.utils.MyFileUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
@Service
public class FileServiceImpl implements FileService {
@Override
public JSONObject upload(MultipartFile multipartFile, HttpServletRequest request) {
// tomcat临时文件
// C:\Users\kikop\AppData\Local\Temp\tomcat-docbase.5125138269731218182.8086\
// String realPath = request.getSession().getServletContext().getRealPath("/");
// 1.生成文件名
String strDestFileName = UUID.randomUUID().toString().replace("-", "");
// 2.文件元信息
String originalFilename = multipartFile.getOriginalFilename();
try {
JSONObject result = new JSONObject();
result.put("success", true);
// 3.文件保存
String fileType = originalFilename.substring(originalFilename.lastIndexOf("."));
if (StringUtils.isEmpty(fileType)) {
result.put("success", false);
result.put("msg", "文件类型不合法!");
return result;
}
// C:\Users\kikop/myuploadfile\
String strDestFilePathName_first = ConstVarManager.MyUploadFile_Name;
// 2ea0b69709e947c48984acf248d077f5.png
String strDestFilePathName_more = strDestFileName + fileType;
System.out.println("文件存储全路径为:" + strDestFilePathName_first + strDestFilePathName_more);
MyFileUtils.copyFile2DestDirectory(multipartFile.getInputStream(), strDestFilePathName_first, strDestFilePathName_more);
// http://localhost:8086/myuploadfile/2ea0b69709e947c48984acf248d077f5.png
// 获取服务上下文
String requestContextPath = request.getScheme() + "://" + request.getServerName() + ":"
+ request.getServerPort() + "/" + request.getContextPath();
// http://localhost:8086/
result.put("destContextPath", requestContextPath);
// myuploadfile/2ea0b69709e947c48984acf248d077f5.png
result.put("destFileName", ConstVarManager.MyUploadFile_VisitName + File.separator + strDestFilePathName_more);
// 4.返回
return result;
} catch (Exception ex) {
ex.printStackTrace();
return CommonResponse.getCommonResponse(ex);
}
}
}
2.2.3ConstVarManager
package com.kikop.myconst;
import java.io.File;
public class ConstVarManager {
public static final String MyUploadFile_Protocol = "file:///";
public static final String MyUploadFile_VisitName = "myuploadfile";
// C:\Users\kikop/myuploadfile/
public static final String MyUploadFile_Name = System.getProperties().getProperty("user.home") + File.separator +
MyUploadFile_VisitName + File.separator;
static {
// 自动创建目录判断
// https://www.cnblogs.com/qbdj/p/10980840.html
File uploadDirectory = new File(MyUploadFile_Name);
if (!uploadDirectory.exists()) {
uploadDirectory.mkdir();
// uploadDirectory.mkdirs(); //多层目录需要调用mkdirs
}
}
}
2.2.4MyFileUtils
package com.kikop.utils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MyFileUtils {
public static void copyFile2DestDirectory(InputStream inputStream, String strDestFilePathName_first, String strDestFilePathName_more) throws IOException {
Path destFilePath = Paths.get(strDestFilePathName_first, strDestFilePathName_more);
Files.copy(inputStream, destFilePath);
}
}
2.2.5WebAppConfig
package com.kikop.config;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.kikop.myconst.ConstVarManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* @author kikop
* @version 1.0
* @project myantbackdemo
* @file
* @desc
* @date 2020/10/31
* @time 21:56
* @by IDE: IntelliJ IDEA
*/
@Configuration
public class WebAppConfig implements WebMvcConfigurer {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"};
@Override
public void configureMessageConverters(List> converters) {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
converters.add(fastJsonHttpMessageConverter);
}
/**
* 静态资源(不需要,用默认的即可)
* 配置请求的解析映射路径
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 1.默认不配置也行
// spring mvc默认的
// http://localhost:8080/myveuback.jpeg
registry.addResourceHandler("/**").addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS);
// 2.自定义
registry.addResourceHandler("/myuploadfile/**")
.addResourceLocations(ConstVarManager.MyUploadFile_Protocol+ConstVarManager.MyUploadFile_Name);
}
}
2.3测试
[图片上传失败...(image-6419a0-1649936781086)]
[图片上传失败...(image-34730d-1649936781087)]
参考
1vant uploader组件,回显文件、文件名
https://blog.csdn.net/weixin_42540974/article/details/121539208
2vue 使用vant Uploader 文件上传(图片压缩)
https://blog.csdn.net/weixin_40918145/article/details/108267163
3vant中uploader上传图片
https://blog.csdn.net/weixin_50651378/article/details/123765330