自动上传
auto-upload//是否在选取文件后立即进行上传 默认自动上传
自定义上传文件后缀类型与上传文件大小
action --必选参数,上传的地址
headers --设置上传的请求头部
multiple --是否支持多选文件
data --上传时附带的额外参数
name --上传的文件字段名
with-credentials --支持发送 cookie 凭证信息
show-file-list --是否显示已上传文件列表
drag --是否启用拖拽上传
accept --接受上传的文件类型(thumbnail-mode 模式下此参数无效)
on-preview --点击文件列表中已上传的文件时的钩子 function(file)
on-remove --文件列表移除文件时的钩子 function(file, fileList)
on-success --文件上传成功时的钩子 function(response, file, fileList)
on-error --文件上传失败时的钩子 function(err, file, fileList)
on-progress --文件上传时的钩子 function(event, file, fileList)
on-change --文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
before-upload --上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被
reject,则停止上传。 function(file)
before-remove --删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回
Promise 且被 reject,则停止删除。 function(file, fileList) — —
list-type --文件列表的类型 string text/picture/picture-card
auto-upload --是否在选取文件后立即进行上传
file-list --上传的文件列表
http-request --覆盖默认的上传行为,可以自定义上传的实现
disabled --是否禁用
limit --最大允许上传个数
on-exceed --文件超出个数限制时的钩子 function(files, fileList)
组件封装上传
components-FileUploader.vue
components文件夹下的components
如下所示:element-plus+vue3(ts)
文件格式 以txt格式为例:
具体格式可更替
文件上传地址为true 上传wav格式文件会报错显示请求调取服务地址上传错误
可把文件上传地址改为false
如下面的 自动上传(去掉手动触发上传)里面功能是上传地址为false
<template>
<el-upload
ref="upload"
:action="uploadUrl"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-upload="beforeUpload"
:on-success="handleSuccess"
:on-error="handleError"
:file-list="fileList"
:auto-upload="autoUpload"
:limit="limit"
:on-exceed="handleExceed"
:disabled="disabled"
multiple>
<el-button type="primary">上传</el-button>
<template #tip>
<div class="el-upload__tip">{{ tip }}</div>
</template>
</el-upload>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue';
import { ElMessage } from 'element-plus'
export default defineComponent({
name: 'FileUploader',
props: {
uploadUrl: { type: String, required: true },// 文件上传地址
autoUpload: { type: Boolean, default: true }, // 是否自动上传
fileList: { type: Array, default: () => [] },// 文件列表
limit: { type: Number, default: 5 },// 文件数量限制
disabled: { type: Boolean, default: false },// 禁用状态
tip: { type: String, default: '只能上传 TXT 文件,且不超过 5MB' },// 提示信息
},
emits: ['uploaded', 'removed'],//组件同步上传成功/失败的状态
setup(props, { emit }) {
const upload = ref(null);
// 文件预览
const handlePreview = (file) => {
console.log('Preview file:', file);
};
// 文件移除
const handleRemove = (file, fileList) => {
emit('removed', file, fileList);
};
/*文件上传前的钩子 (一般限制格式与大小)*/
const beforeUpload = (file) => {
// ['image/jpeg', 'image/png', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
// 'application/msword', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
// 'application/vnd.ms-excel'].includes(file.type);
const isAcceptableType = ['text/plain'].includes(file.type);
const isLt5M = file.size / 1024 / 1024 < 5;
if (!isAcceptableType) {
ElMessage.error('上传文件只能是 TXT 格式!');
}
if (!isLt5M) {
ElMessage.error('上传文件大小不能超过 5MB!');
}
return isAcceptableType && isLt5M;
};
// 文件上传成功后的钩子
const handleSuccess = (response, uploadFile, fileList) => {
ElMessage.success('文件上传成功');
emit('uploaded', response, uploadFile, fileList);
};
// 文件上传失败后的钩子
const handleError = (error, uploadFile, fileList) => {
ElMessage.error('文件上传失败');
console.error('Upload error:', error);
};
// 文件超出限制时的钩子
const handleExceed = (files, fileList) => {
ElMessage.warning(
`当前限制选择 ${props.limit} 个文件,本次选择了 ${
files.length
} 个文件,共选择了 ${files.length + fileList.length} 个文件`
);
};
// 监听父组件传递的 fileList 变化
watch(() => props.fileList, (newVal) => {
if (upload.value) {
upload.value.clearFiles();
newVal.forEach(file => {
// 假设 newVal 中的每个元素都有 name 和 type 属性
const newFile = new File([], file.name, { type: file.type });
upload.value!.handleAdd(newFile); // 使用 handleAdd 方法添加文件
});
}
}, { deep: true });
return {
upload,
handlePreview,
handleRemove,
beforeUpload,
handleSuccess,
handleError,
handleExceed,
};
},
});
</script>
<style scoped>
.el-icon--upload {
font-size: 67px;
color: #c0c4cc;
margin-bottom: 13px;
}
.el-upload__text em {
color: #409eff;
}
</style>
页面使用
<template>
<el-form :model="fileFrom" ref="formRef" :rules="rules" @submit.prevent>
<el-form-item label="上传文件" prop="fileList" :label-width="formLabelWidth">
<file-uploader
:upload-url="'https://jsonplaceholder.typicode.com/posts'"
:auto-upload="true"
:file-list="fileFrom.fileList"
:limit="2"
:disabled="false"
@uploaded="handleUploaded"
@removed="handleRemoved"
/>
</el-form-item>
</el-form>
</template>
<script lang='ts'>
import { reactive, ref, toRefs } from 'vue';
import { ElForm, ElMessage } from 'element-plus';
import FileUploader from './components/FileUploader.vue';
export default {
name: '',
components: {
FileUploader,
},
setup() {
const formRef = ref<InstanceType<typeof ElForm> | null>(null);
const data = reactive({
formLabelWidth: '140px',
fileFrom: {
fileList: [],
}
});
const handleUploaded = (response, uploadFile, fileList) => {
// 处理上传成功的文件
console.log(response, uploadFile, fileList, 'o');
// 更新文件列表或其他操作
};
const handleRemoved = (file, fileList) => {
// 处理移除的文件
console.log('File removed:', file, fileList);
// 更新文件列表或其他操作
};
return {
...toRefs(data),
handleUploaded,
handleRemoved,
formRef,
};
}
};
</script>
<style scoped>
/* 你的CSS样式 */
</style>
然后就可以处理你需要的逻辑使用
多文件不同格式封装上传
不请求服务 上传的地址作为入参定义
多个上传文件上传
components-FileUploader.vue
components文件夹下的components
如下所示:element-plus+vue3(ts)
<template>
<el-upload
ref="upload"
:action="uploadUrl"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-upload="beforeUpload"
:auto-upload="autoUpload"
:file-list="fileList"
:limit="limit"
:on-exceed="handleExceed"
:disabled="disabled"
:http-request="customUpload"
multiple
>
<el-button type="primary">上传</el-button>
<template #tip>
<div v-if="currentTip === 'txt'" class="el-upload__tip">{{ tip }}</div>
<div v-else-if="currentTip === 'xls'" class="el-upload__tip">{{ tip1 }}</div>
<div v-else-if="currentTip === 'zip'" class="el-upload__tip">{{ tip2 }}</div>
</template>
</el-upload>
</template>
<script lang="ts">
import { defineComponent, ref, watch, computed } from 'vue';
import { ElMessage } from 'element-plus';
export default defineComponent({
name: 'FileUploader',
props: {
uploadUrl: {
type: String,
required: true,
},
autoUpload: {
type: Boolean,
default: true,
},
fileList: {
type: Array,
default: () => [],
},
limit: {
type: Number,
default: 5,
},
disabled: {
type: Boolean,
default: false,
},
// 提示信息
tip: {
type: String,
default: '只能上传 TXT 文件,且不超过 5MB',
},
tip1: {
type: String,
default: "只能上传 XLS/XLSX 文件",
},
tip2: {
type: String,
default: "只能上传 ZIP/RAR/TAR 文件",
},
// 当前使用的提示信息类型
currentTip: {
type: String,
default: 'tip'
}
},
emits: ['uploaded', 'removed'],
setup(props, { emit }) {
const upload = ref(null);
// 计算 accept 属性值
const accept = computed(() => {
if (props.currentTip === "xls") {
return ".xls,.xlsx";
} else if (props.currentTip === "zip") {
return ".zip,.rar,.tar";
} else if (props.currentTip === "txt") {
return ".txt";
}
return "";
});
// 文件预览
const handlePreview = (file) => {
console.log('Preview file:', file);
};
// 文件移除
const handleRemove = (file, fileList) => {
emit('removed', file, fileList);
};
// 自定义上传方法
const customUpload = (options) => {
if (props.uploadUrl === '#' || props.uploadUrl === '') {
// 模拟上传成功的行为
setTimeout(() => {
const file = options.file;
file.status = 'success';
file.message = 'Mock upload success';
emit('uploaded', file); // 直接传递文件对象
ElMessage.success('文件上传成功');
}, 1000); // 模拟延迟
} else {
emit('uploaded', options.file);
// 如果有真实的上传 URL,则允许默认行为
options.onSuccess();
ElMessage.success('文件上传成功');
}
};
// 文件上传前的钩子
const beforeUpload = (file) => {
//如果上传包含中文 禁止上传
const isContainChinese = /[\u4e00-\u9fa5]/.test(file.name);
if (isContainChinese) {
ElMessage.error("文件名不能包含中文字符!");
return false;
}
const fileExt = file.name.split(".").pop()?.toLowerCase();
let acceptableExts: string[] = [];
if (props.currentTip === "xls") {
acceptableExts = ["xls", "xlsx"];
} else if (props.currentTip === "zip") {
acceptableExts = ["zip", "rar", "tar"];
} else if (props.currentTip === "txt") {
acceptableExts = ["txt"];
}
const isAcceptableType = acceptableExts.includes(fileExt as string);
if (!isAcceptableType) {
ElMessage.error(`上传文件只能是 ${props.currentTip} 格式!`);
return false;
}
return true;
};
// 文件超出限制时的钩子
const handleExceed = (files, fileList) => {
ElMessage.warning(
`当前限制选择 ${props.limit} 个文件,本次选择了 ${
files.length
} 个文件,共选择了 ${files.length + fileList.length} 个文件`
);
};
// 监听父组件传递的 fileList 变化
watch(() => props.fileList, (newVal) => {
if (upload.value) {
upload.value.clearFiles();
newVal.forEach(file => {
upload.value.handleStart(file);
});
}
}, { deep: true });
return {
upload,
handlePreview,
handleRemove,
beforeUpload,
customUpload,
handleExceed,
accept,
};
},
});
</script>
<style scoped>
.el-icon--upload {
font-size: 67px;
color: #c0c4cc;
margin-bottom: 13px;
}
.el-upload__text em {
color: #409eff;
}
</style>
页面使用
<template>
<!-- txt文件 -->
<el-form-item label="txt文件" prop="txtFileList" :label-width="formLabelWidth">
<file-uploader
:upload-url="'https://jsonplaceholder.typicode.com/posts'"
:auto-upload="true"
:file-list="fileFrom.txtFileList"
:limit="1"
:disabled="false"
:currentTip="'txt'"
@uploaded="handleTxtUploaded"
@removed="handleRemoved"
/>
</el-form-item>
<!-- XLS/XLSX 文件 -->
<el-form-item label="xls文件" prop="xlsFileList" :label-width="formLabelWidth">
<file-uploader
:upload-url="'#'"
:auto-upload="true"
:file-list="fileFrom.xlsFileList"
:limit="1"
:disabled="false"
:currentTip="'xls'"
accept=".xls,.xlsx"
@uploaded="handleXlsUploaded"
@removed="handleRemoved"
/>
</el-form-item>
<!-- ZIP/RAR/TAR 文件 -->
<el-form-item label="zip压缩文件" prop="zipFileList" :label-width="formLabelWidth">
<file-uploader
:upload-url="'#'"
:auto-upload="true"
:file-list="fileFrom.zipFileList"
:limit="1"
:disabled="false"
:currentTip="'zip'"
accept=".zip,.rar,.tar"
@uploaded="handleZipUploaded"
@removed="handleRemoved"
/>
</el-form-item>
<el-button type="primary" @click="btnClik">点击</el-button>
</template>
<script lang='ts'>
import { reactive, ref, toRefs, watch,computed} from 'vue';
import { ElForm, ElMessage } from 'element-plus';
import FileUploader from '@/components/FileUploader.vue';
export default {
name: '',
components: {
FileUploader,
},
emits: ['update:visible', 'addFrom', 'fileFrom'],
setup(props, { emit }) {
const formRef = ref<InstanceType<typeof ElForm> | null>(null);
const data = reactive({
formLabelWidth: '140px',
fileFrom: {
name: '',
isOnline: false,
txtFileList: [],
xlsFileList: [],
zipFileList: []
}
});
const rules = reactive({
name: [
{ required: true, message: '请输入名称', trigger: 'blur' },
{ min: 1, message: '长度至少为1个字符', trigger: 'blur' },
],
txtFileList: [
{ required: true, message: '请上传txt文件', trigger: 'change' },
],
xlsFileList: [
{ required: true, message: '请上传xls文件', trigger: 'change' },
],
zipFileList: [
{ required: true, message: '请上传zip压缩文件', trigger: 'change' },
]
});
const handleTxtUploaded = (file) => {
// 更新文件列表
data.fileFrom.txtFileList = [file];
};
const handleXlsUploaded = (file) => {
// 更新文件列表
data.fileFrom.xlsFileList = [file];
};
const handleZipUploaded = (file) => {
// 更新文件列表
data.fileFrom.zipFileList = [file];
};
const handleRemoved = (file, fileList) => {
console.log('File removed:', file, fileList);
// 根据文件类型更新对应的文件列表
if (data.fileFrom.txtFileList.includes(file)) {
data.fileFrom.txtFileList = fileList;
} else if (data.fileFrom.xlsFileList.includes(file)) {
data.fileFrom.xlsFileList = fileList;
} else if (data.fileFrom.zipFileList.includes(file)) {
data.fileFrom.zipFileList = fileList;
}
};
const btnClik = (file) => {
console.log(data.fileFrom,'opop');
}
return {
...toRefs(data),
handleTxtUploaded,
handleXlsUploaded,
handleZipUploaded,
handleRemoved,
rules,
formRef,
btnClik,
// safeTxtFileList,
// safeXlsFileList,
// safeZipFileList
};
}
};
</script>
单页面带请求接口上传
渲染已请求文件在页面
element+vue2(js)
action为请求接口地址(必填)
headers如带token需把token单独放在请求头上
对应上传时间填写(可填)
accept//限制上传类型格式
on-preview点击文件对应列表(文件)
on-success文件上传成功
fileList对应上传文件的列表
before-upload上传之前的操作(限制提示上传格式)
:before-remove="()=>false"//禁止删除功能
<el-upload
:before-remove="()=>false"
class="upload-demo"
:action="action"
:before-upload="beforeUpload"
:data="fileData"
:headers="headers"
accept=".doc,.xlsx,.pdf,.jpg,.png"
:on-success="fileSuccessFunc"
:on-preview="handlePreview"
:file-list="fileList"
>
<el-button
size="mini"
>点击上传</el-button>
<div
slot="tip"
class="el-upload__tip"
>只能上传doc/xlsx/pdf/jpg/png文件,且不超过2MB</div>
</el-upload>
data(){
return{
action:"https//XXXXXXX(接口地址)",
headers: {},
fileDatas:[],//接收上传成功的数据
fileList:[],//必填(用来存储上传后的文件列表)
}
}
beforeUpload(file) {//上传之前的限制文件格式
this.headers.后端规定token名称 = 'XXXXXXXXXXXXXX';//token(用来放token)
this.headers.time = new Date().getTime;//可选
const fileSuffix = file.name.substring(file.name.lastIndexOf(".") + 1);
const whiteList = ["doc", "xlsx", "pdf", "jpg", "png"];
if (whiteList.indexOf(fileSuffix) === -1) {
this.$message({
message: '上传文件只能是doc(Doc), xlsx(Xlsx), pdf(Pdf), jpg(Jpg), png(Png)格式',
type: 'warning'
});
return false;
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
this.$message({
message: '上传文件大小不能超过2MB',
type: 'warning'
});
return false;
}
},
// 上传成功
fileSuccessFunc(response, file, fileList) {
//实现多个数据放在数组中
var arr=[]
arr=[...arr,respose.data.url];
this.fileDatas=arr//上传成功的数据
},
handlePreview(file) {
//这个是上传后定位对应单个文件
//单个文件列表值
var that = this;
var a = document.createElement('a');
var event = new MouseEvent('click');
a.download = file.name;
a.href = file.url;
a.dispatchEvent(event);
console.log(file);//单个文件的值
}
PS
one.jpg,two.pdf
把对应的后缀去掉只要前面数值怎么搞呢?
如下所示
var a="a.jpg";
var b=a.split(".")[0];
console.log(b);//a
手动上传
上传文件限制中文(封装使用)
上传文件为手动上传 不去请求服务
如下所示:element-plus+vue3(ts)
首先上传文件不能上传带中文的名称 根据pinyin-pro库转写中文变成字母 达到想要的结果
下面是封装的方法-如下所示:
utils-pinyin.js
需要安装pinyin-pro库
如下链接
pinyin-pro库封装中文转字母格式功能
import { pinyin } from 'pinyin-pro';
/* 提取上传文件带中文名称转成字母格式方法
并且去掉特殊字符
*/
export function convertFileNameToPinyin(file: File): File {
// 提取文件扩展名
const extension = file.name.split('.').pop();
if (!extension) {
console.error('File has no extension:', file.name);
return file; // 如果没有扩展名,直接返回原文件对象
}
// 去掉文件扩展名后的名字部分
const nameWithoutExtension = file.name.replace(new RegExp(`.${extension}$`), '');
console.log('Original name without extension:', nameWithoutExtension);
// 将名字部分转换为拼音
let pinyinName = '';
try {
pinyinName = pinyin(nameWithoutExtension, { toneType: 'none' }).toLowerCase().replace(/\s+/g, '-');
console.log('Converted to pinyin:', pinyinName);
} catch (error) {
console.error('Error converting filename to pinyin:', error);
pinyinName = nameWithoutExtension; // 如果转换失败,则保留原名
}
// 定义要移除的特殊字符列表
const specialCharsToRemove = [
'\\', '/', ':', '*','(', ')', '?', '"', '<', '>', '|', '$', '+', '{', '}', '[', ']', '(', ')'
];
// 移除所有特殊字符,包括括号和其他非法字符
let cleanedPinyinName = pinyinName;
specialCharsToRemove.forEach(char => {
cleanedPinyinName = cleanedPinyinName.split(char).join('');
});
console.log('Cleaned pinyin name:', cleanedPinyinName);
// 创建新的带有拼音文件名的Blob对象
const newFile = new File([file], `${cleanedPinyinName}.${extension}`, { type: file.type });
console.log('New file object:', newFile);
return newFile;
}
1 手动触发上传
封装上传的组件
before-upload设置手动上传 不去请求服务
手动触发上传
需求:
增添的时候只需要带传入文件链接的入参 不去请求服务
components-FileUploader.vue
<template>
<div>
<el-upload
ref="upload"
:action="uploadUrl"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-upload="beforeUpload"
:auto-upload="false"
:limit="limit"
:disabled="disabled"
multiple
:accept="'.wav'"
:file-list="fileList"
:on-exceed="handleExceed"
:http-request="customUpload"
>
<el-button type="primary">选择文件</el-button>
<template #tip>
<div style="display: flex">
<div class="el-upload__tip">{{ tip }}</div>
<el-button size="small" style="margin: 11px 6px;" @click="submitUpload" type="primary">
<el-icon><Check /></el-icon>
</el-button>
</div>
</template>
</el-upload>
<!-- <el-button @click="submitUpload" style="margin-top: 20px;">保存</el-button> -->
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import { convertFileNameToPinyin } from "../utils/pinyin";
import { Edit, Search, Check, CirclePlus } from "@element-plus/icons-vue";
export default defineComponent({
name: "FileUploader",
components: {
Edit,
Search,
Check, // 注册 Plus 图标组件
CirclePlus, // 注册 CirclePlus 图标组件
},
props: {
uploadUrl: { type: String, required: true },
autoUpload: { type: Boolean, default: false },
fileList: { type: Array, default: () => [] },
limit: { type: Number, default: 5 },
disabled: { type: Boolean, default: false },
tip: {
type: String,
default: "只能上传音视频文件(wav格式),且不超过 50MB",
},
},
emits: ["uploaded", "removed", "Preview"],
setup(props, { emit }) {
const upload = ref(null);
// 模拟上传的方法
const customUpload = (options) => {
const file = options.file;
// 在模拟上传前转换文件名
const convertedFile = convertFileNameToPinyin(file);
setTimeout(() => {
const response = { success: true, message: "模拟上传" };
emit("uploaded", response, convertedFile); // 使用转换后的文件对象
ElMessage.success("文件保存成功");
}, 1000);
};
// 手动触发上传
const submitUpload = () => {
if (upload.value) {
upload.value.submit(); // 触发所有选中的文件上传
}
};
// 假设这是你在 FileUploader.vue 中处理预览的方法
const handlePreview = (file) => {
// 如果 file 是一个有效的 File 对象,则发射 Preview 事件
if (file.raw && file.raw instanceof File) {
emit("Preview", file.raw); // 发射原始文件对象
} else {
//证明是编辑的状态(接口返回 发射file)
emit("Preview", file);
console.warn("Invalid file object for preview:", file);
}
};
const handleRemove = (file, fileList) => {
emit("removed", file, fileList);
};
const beforeUpload = (file) => {
//自动上传才会生效(当前手动上传)
const isAcceptableType = [
//限制wav格式
"audio/mpeg",
"audio/wav",
"audio/ogg",
"video/mp4",
"video/webm",
"video/quicktime",
].includes(file.type);
const isLt50M = file.size / 1024 / 1024 < 50;
if (!isAcceptableType) {
ElMessage.error("上传文件只能是音视频格式!");
}
if (!isLt50M) {
ElMessage.error("上传文件大小不能超过 50MB!");
}
return isAcceptableType && isLt50M;
};
const handleExceed = (files, fileList) => {
ElMessage.warning(
`当前限制选择 ${props.limit} 个文件,本次选择了 ${
files.length
} 个文件,共选择了 ${files.length + fileList.length} 个文件`
);
};
watch(
() => props.fileList,
(newVal) => {
if (Array.isArray(newVal)) {
if (upload.value) {
upload.value.clearFiles();
newVal.forEach((file) => {
// el-upload 并没有直接的 handleStart 方法来模拟文件选择。
// 需要预览文件或显示已选文件,需要手动创建 File 对象或使用其他方式。
});
}
} else {
console.warn("fileList prop is not an array:", newVal);
}
},
{ deep: true, immediate: true }
); // 使用 immediate 确保初始化时也触发
return {
upload,
customUpload,
handlePreview,
handleRemove,
beforeUpload,
handleExceed,
submitUpload, // 提交上传函数
};
},
});
</script>
<style lang="scss" scoped>
// :deep(.el-upload) {
// display: flex !important;
// justify-content: space-between !important;
// }
</style>
页面使用
<template>
<el-form :model="fileFrom" ref="formRef" :rules="rules" @submit.prevent>
<el-form-item label="上传文件" prop="fileList" :label-width="formLabelWidth">
<file-uploader
ref="fileUploader"
:upload-url="'#'"
:auto-upload="false"
:file-list="formData.fileList"
:limit="1"
:disabled="false"
@Preview="handPreview"
@uploaded="handleUploaded"
@removed="handleRemoved"
/>
</el-form-item>
</el-form>
</template>
<script lang='ts'>
import { reactive, ref, toRefs } from 'vue';
import { ElForm, ElMessage } from 'element-plus';
import FileUploader from './components/FileUploader.vue';
export default {
name: '',
components: {
FileUploader,
},
setup() {
const formRef = ref<InstanceType<typeof ElForm> | null>(null);
const data = reactive({
formLabelWidth: '140px',
formData: {
fileList: [],
}
});
const handPreview = async (file: File) => {
// 预览
console.log(file);
}
const handleUploaded = (response, uploadFile, fileList) => {
// 处理上传成功的文件
console.log(response, uploadFile, fileList, 'o');
// 更新文件列表或其他操作
};
const handleRemoved = (file, fileList) => {
// 处理移除的文件
console.log('File removed:', file, fileList);
// 更新文件列表或其他操作
};
return {
...toRefs(data),
handleUploaded,
handleRemoved,
formRef,
handPreview,
};
}
};
</script>
<style scoped>
/* 你的CSS样式 */
</style>
2 自动上传(去掉手动触发上传)
封装上传的组件
components-FileUploader.vue
限制wav格式文件格式上传
不请求服务 前端上传 只获取上传文件对应的参数
<template>
<div>
<el-upload
ref="upload"
:auto-upload="true"
:action="uploadUrl"
:on-preview="handlePreview"
:on-remove="handleRemove"
:limit="limit"
:disabled="disabled"
multiple
:accept="'.wav'"
:file-list="fileList"
:before-upload="beforeUpload"
:http-request="customUpload"
:on-exceed="handleExceed"
>
<el-button type="primary">选择文件</el-button>
<template #tip>
<div class="el-upload__tip">{{ tip }}</div>
</template>
</el-upload>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import { Check } from "@element-plus/icons-vue";
export default defineComponent({
name: "FileUploader",
components: {
Check,
},
props: {
uploadUrl: { type: String, required: false }, // 不再需要实际的上传 URL
autoUpload: { type: Boolean, default: true }, // 默认启用自动上传
fileList: { type: Array, default: () => [] },
limit: { type: Number, default: 5 },
disabled: { type: Boolean, default: false },
tip: {
type: String,
default: "只能上传音视频文件(wav格式),且不超过 50MB",
},
},
emits: ["uploaded", "removed", "preview"],
setup(props, { emit }) {
const upload = ref(null);
// 自定义上传方法(纯前端处理)
const customUpload = async (options) => {
const file = options.file;
// 可以在这里对文件进行前端处理,例如读取文件内容
const reader = new FileReader();
reader.onload = function(e) {
console.log("File content:", e.target?.result); // 文件内容
emit("uploaded", { success: true, message: "文件上传成功" }, file);
ElMessage.success("文件上传成功");
};
reader.readAsArrayBuffer(file); // 或者使用 readAsDataURL, readAsText 等
// 如果只是想获取文件的基本信息,则可以直接使用 file 对象
console.log("File info:", file);
};
// 假设这是你在 FileUploader.vue 中处理预览的方法
const handlePreview = (file) => {
if (file.raw && file.raw instanceof File) {
emit("preview", file.raw); // 发射原始文件对象
} else {
emit("preview", file);
console.warn("Invalid file object for preview:", file);
}
};
const handleRemove = (file, fileList) => {
emit("removed", file, fileList);
};
const beforeUpload = (file) => {
// 检查文件名是否包含中文字符
const hasChineseChars = /[\u4e00-\u9fa5]/.test(file.name);
if (hasChineseChars) {
ElMessage.error("文件名不能包含中文字符");
return false;
}
// 限制 wav 格式
const isAcceptableType = ["audio/wav"].includes(file.type);
const isLt50M = file.size / 1024 / 1024 < 50;
if (!isAcceptableType) {
ElMessage.error("上传文件只能是音频格式!");
}
if (!isLt50M) {
ElMessage.error("上传文件大小不能超过 50MB!");
}
return isAcceptableType && isLt50M;
};
const handleExceed = (files, fileList) => {
ElMessage.warning(
`当前限制选择 ${props.limit} 个文件,本次选择了 ${
files.length
} 个文件,共选择了 ${files.length + fileList.length} 个文件`
);
};
watch(
() => props.fileList,
(newVal) => {
if (Array.isArray(newVal)) {
if (upload.value) {
upload.value.clearFiles();
newVal.forEach((file) => {
// el-upload 并没有直接的 handleStart 方法来模拟文件选择。
// 需要预览文件或显示已选文件,需要手动创建 File 对象或使用其他方式。
});
}
} else {
console.warn("fileList prop is not an array:", newVal);
}
},
{ deep: true, immediate: true }
); // 使用 immediate 确保初始化时也触发
return {
upload,
customUpload,
handlePreview,
handleRemove,
beforeUpload,
handleExceed,
};
},
});
</script>
<style lang="scss" scoped>
// 样式保持不变
</style>
页面使用
<template>
<el-form :model="fileFrom" ref="formRef" :rules="rules" @submit.prevent>
<el-form-item label="上传文件" prop="fileList" :label-width="formLabelWidth">
<file-uploader
ref="fileUploader"
:upload-url="'#'"
:auto-upload="false"
:file-list="formData.fileList"
:limit="1"
:disabled="false"
@Preview="handPreview"
@uploaded="handleUploaded"
@removed="handleRemoved"
/>
</el-form-item>
</el-form>
</template>
<script lang='ts'>
import { reactive, ref, toRefs } from 'vue';
import { ElForm, ElMessage } from 'element-plus';
import FileUploader from './components/FileUploader.vue';
export default {
name: '',
components: {
FileUploader,
},
setup() {
const formRef = ref<InstanceType<typeof ElForm> | null>(null);
const data = reactive({
formLabelWidth: '140px',
formData: {
fileList: [],
}
});
const handPreview = async (file: File) => {
// 预览
console.log(file);
}
const handleUploaded = (response, uploadFile, fileList) => {
// 处理上传成功的文件
console.log(response, uploadFile, fileList, 'o');
// 更新文件列表或其他操作
};
const handleRemoved = (file, fileList) => {
// 处理移除的文件
console.log('File removed:', file, fileList);
// 更新文件列表或其他操作
};
return {
...toRefs(data),
handleUploaded,
handleRemoved,
formRef,
handPreview,
};
}
};
</script>
<style scoped>
/* 你的CSS样式 */
</style>
----
编辑模块 回显文件上传/上传文件
上传文件的入参为formData
1 编辑时有对应的文件 如果不上传保存 默认保存的回显文件
(这种情况需要blob把文件下载然后再次作入参 否则会显示[object,object])
2 上传文件
封装prepareFormData 函数
- utils-formDataUtils.ts
// 封装文件处理函数,接收一个对象参数,包含其他字段和文件列表
export const prepareFormData = async (data: { [key: string]: any; fileList?: any[] }) => {
const formData = new FormData();
// 遍历对象,将除 fileList 外的其他字段添加到 FormData 中 (fileLists是上传文件的列表数据)
for (const key in data) {
if (key!== 'fileList') {
formData.append(key, data[key].toString());
}
}
const filePromises: Promise<void>[] = [];
const fileList = data.fileList || [];
if (Array.isArray(fileList)) {
fileList.forEach((fileItem: any) => {
if (fileItem instanceof File) {
formData.append('vpFile', fileItem);
} else if (fileItem.url && typeof fileItem === 'object') {
const fetchFile = async () => {
try {
const response = await fetch(fileItem.url);
if (!response.ok) {
throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`);
}
const blob = await response.blob();
const fileName = fileItem.name || 'unknown';
const file = new File([blob], fileName, { type: blob.type });
console.log('Downloaded and converted to File:', file);
formData.append('vpFile', file);//VpFile是文件的入参(具体根据后端的定义更替)
} catch (error) {
console.error('Error fetching file:', error);
// 如果下载失败,回退到 URL
formData.append('vpFile', fileItem.url);
}
};
filePromises.push(fetchFile());
}
});
}
// 等待所有文件处理完成
await Promise.all(filePromises);
return formData;
};
页面编辑 保存
// 导入封装好的函数
import { prepareFormData } from "@/utils/formDataUtils";
const vprSave = async (e: any) => {//编辑时保存按钮
try {
const formData = await prepareFormData({//对应的入参(根据对应接口入参更替)
name: e.name,
idNumber: e.idNumber,
areaId: e.areaId,
fileList: e.fileList
});
const response = await proAdd(formData);//对应的请求封装地址
await nextTick();
getPrVp();//初始化表格的数据
data.dialog.visible = false; // 弹框关闭
} catch (error) {
console.error('Error creating:', error);
}
};
分片上传
又称为断点续传通常用于上传大文件或在网络条件不稳定的情况下,
确保文件能够完整、可靠地上传到服务器
带上传进度展示百分百效果
封装方法 多页面调用
npm install axios mockjs spark-md5 --save
该结合mock模拟接口
mock.ts
import Mock from 'mockjs';
// 模拟分片上传接口
Mock.mock('/api/upload-chunk', 'post', (options) => {
console.log('接收到分片:', options.body);
return {
success: true,
message: '分片上传成功',
};
});
// 模拟合并分片接口
Mock.mock('/api/merge-chunks', 'post', (options) => {
console.log('开始合并分片:', options.body);
return {
success: true,
message: '分片合并成功',
};
});
uploadService.ts
import axios from 'axios';
import SparkMD5 from 'spark-md5';
class UploadService {
chunkSize: number;
constructor() {
this.chunkSize = 1 * 1024 * 1024; // 每个分片大小为 1MB
}
/**
* 计算文件的 MD5 值
* @param {File} file - 文件对象
* @returns {Promise<string>} - MD5 值
*/
calculateMD5(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const blobSlice =
File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
const chunkSize = 2 * 1024 * 1024; // 每次读取 2MB
const chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
fileReader.onload = (e: ProgressEvent<FileReader>) => {
if (e.target?.result) {
spark.append(e.target.result as ArrayBuffer); // 添加数组缓冲区
}
currentChunk++;
if (currentChunk < chunks) {
this.loadNextChunk(fileReader, file, blobSlice, currentChunk, chunkSize);
} else {
resolve(spark.end());
}
};
fileReader.onerror = () => {
reject(new Error('读取文件时出错'));
};
this.loadNextChunk(fileReader, file, blobSlice, currentChunk, chunkSize);
});
}
/**
* 加载下一个分片
* @param {FileReader} fileReader - 文件读取器
* @param {File} file - 文件对象
* @param {Function} blobSlice - 切片函数
* @param {number} currentChunk - 当前分片索引
* @param {number} chunkSize - 分片大小
*/
loadNextChunk(
fileReader: FileReader,
file: File,
blobSlice: (this: File, start: number, end: number) => Blob,
currentChunk: number,
chunkSize: number
): void {
const start = currentChunk * chunkSize;
const end = Math.min(file.size, start + chunkSize);
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
/**
* 动态上传单个分片
* @param {Blob} chunk - 分片数据
* @param {string} fileName - 文件名
* @param {string} md5 - 文件 MD5 值
* @param {number} index - 分片索引
* @param {number} totalChunks - 总分片数
* @param {string} uploadUrl - 上传地址
*/
async uploadChunk(chunk: Blob, fileName: string, md5: string, index: number, totalChunks: number, uploadUrl: string): Promise<void> {
const formData = new FormData();
formData.append('file', chunk);
formData.append('fileName', fileName);
formData.append('md5', md5);
formData.append('index', index.toString());
formData.append('totalChunks', totalChunks.toString());
await axios.post(uploadUrl, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
}
/**
* 合并分片
* @param {string} fileName - 文件名
* @param {string} md5 - 文件 MD5 值
* @param {number} totalChunks - 总分片数
* @param {string} mergeUrl - 合并地址
*/
async mergeChunks(fileName: string, md5: string, totalChunks: number, mergeUrl: string): Promise<void> {
await axios.post(mergeUrl, { fileName, md5, totalChunks });
}
/**
* 上传多个文件
* @param {File[]} files - 文件列表
* @param {Function} onProgress - 进度回调
* @param {string} uploadUrl - 上传地址
* @param {string} mergeUrl - 合并地址
*/
async uploadFiles(files: File[], onProgress: (fileName: string, progress: number) => void, uploadUrl: string, mergeUrl: string): Promise<void> {
for (const file of files) {
await this.uploadFile(file, onProgress, uploadUrl, mergeUrl);
}
}
/**
* 上传单个文件
* @param {File} file - 文件对象
* @param {Function} onProgress - 进度回调
* @param {string} uploadUrl - 上传地址
* @param {string} mergeUrl - 合并地址
*/
async uploadFile(file: File, onProgress: (fileName: string, progress: number) => void, uploadUrl: string, mergeUrl: string): Promise<void> {
const md5 = await this.calculateMD5(file);
const totalChunks = Math.ceil(file.size / this.chunkSize);
for (let i = 0; i < totalChunks; i++) {
const start = i * this.chunkSize;
const end = Math.min(file.size, start + this.chunkSize);
const chunk = file.slice(start, end);
try {
await this.uploadChunk(chunk, file.name, md5, i, totalChunks, uploadUrl);
const progress = Math.round(((i + 1) / totalChunks) * 100);
onProgress(file.name, progress); // 更新进度
} catch (error) {
throw new Error(`分片上传失败: ${error.message}`);
}
}
// 合并分片
await this.mergeChunks(file.name, md5, totalChunks, mergeUrl);
}
}
export default new UploadService();
页面使用
<template>
<div class="upload-container">
<h3>分片上传示例</h3>
<input type="file" multiple @change="handleFileChange" />
<button @click="startUpload" :disabled="isUploading">开始上传</button>
<div v-for="(progress, index) in progresses" :key="index">
<p>{{ progress.name }}: {{ progress.value }}%</p>
<progress :value="progress.value" max="100"></progress>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import '../src/mock/mock'
import uploadService from '../src/utils/uploadService';
// 文件列表
const files = ref([]);
// 是否正在上传
const isUploading = ref(false);
// 每个文件的上传进度
const progresses = ref([]);
// 监听文件选择事件
const handleFileChange = (event) => {
files.value = Array.from(event.target.files);
};
// 开始上传
const startUpload = async () => {
if (!files.value.length) {
alert('请选择文件');
return;
}
isUploading.value = true;
progresses.value = files.value.map(file => ({ name: file.name, value: 0 }));
try {
await uploadService.uploadFiles(
files.value,
(fileName, progress) => {
const fileProgress = progresses.value.find(p => p.name === fileName);
if (fileProgress) {
fileProgress.value = progress;
}
},
'/api/upload-chunk', // 动态设置上传地址
'/api/merge-chunks' // 动态设置合并地址
);
alert('所有文件上传成功!');
} catch (error) {
console.error('上传失败:', error.message);
alert('上传失败,请稍后重试');
}
isUploading.value = false;
};
</script>
<style scoped>
/* 样式代码保持不变 */
</style>