解决了一个问题,就是上传的文件,在提交前给改了,再提交就报错。
<template>
<a-card
size="small"
:body-style="{ padding: '40px' }"
style="width: 100%;"
:bordered="false"
:form-container="true"
class="header-filter"
:no-border-radius="true"
>
<a-form-model
:model="pageData.formData"
layout="horizontal"
vertical-space="no"
:rules="pageData.rules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 6 }"
>
<a-form-model-item label="上传文件" prop="file" style="width: 100%;" size="small">
<input ref="iptUpload" type="file" style="display:none" @change="onChange" />
<a-tag
v-if="pageData.name"
color="green"
style="margin-right:5px;"
closable
@close="deleteChoose"
>{{pageData.name}}</a-tag>
<a-button type="primary" icon="plus" size="mini" ghost @click="chooseFile">选择文件</a-button>
</a-form-model-item>
</a-form-model>
<a-row style="padding-top:20px;">
<a-col span="6"></a-col>
<a-space :size="10">
<a-button type="primary" :loading="pageData.loading" @click="butHandle('upload')">上传</a-button>
<a-button @click="butHandle('reset')">重置</a-button>
<a-button @click="fasdfasd">发发大方的说</a-button>
</a-space>
</a-row>
</a-card>
</template>
<script>
import { onMounted, reactive } from '@vue/composition-api';
import { getDispatch } from '@/utils/service-dispatch.js';
import { actionName } from '../../store/actions';
import { async } from '@pa/pawm-portal-sdk';
export default {
name: 'CollateFilter',
props: {
queryButton: {
type: Object,
default: () => {},
},
},
setup(_, { root, refs }) {
const dispatch = getDispatch(root, root.$route.path);
// 系统平台日
//初始化数据
let formInitData = {
file: undefined,
};
let pageData = reactive({
loading: false,
name: '',
rules: {
file: [
{
required: true,
message: '不能为空',
trigger: 'change',
},
],
},
formData: Object.assign({}, formInitData),
});
const clearSelect = () => {
refs.iptUpload.value = '';
pageData.name = '';
pageData.formData.file = undefined;
};
//查询、重置点击
const butHandle = async state => {
if (state === 'upload') {
const { file } = pageData.formData;
if (!file || !file.get('file')) {
root.$message.error('请选择文件');
return;
}
在上传之前,读一下文件,如果发现文件读取错误,就清空之前的上传数据,提示重新上传。
const reader = new FileReader();
reader.readAsText(file.get('file'), 'UTF-8');
reader.onload = async () => {
pageData.loading = true;
let res = await dispatch(actionName.UPDATE_PLAT_DATE, file);
if (res.responseCode === '000000') {
root.$message.success('上传成功!');
clearSelect();
pageData.formData = Object.assign({}, formInitData);
} else {
root.$message.error(res.responseMsg || '系统异常');
}
pageData.loading = false;
};
reader.onerror = () => {
root.$message.error('文件有变更,请重新上传。');
clearSelect();
};
}
if (state === 'reset') {
clearSelect();
pageData.formData = Object.assign({}, formInitData);
}
};
const onChange = arg => {
const file = arg.target.files[0];
const name = file.name;
pageData.name = name;
let formData = new FormData();
formData.append('file', file);
pageData.formData.file = formData;
};
const chooseFile = () => {
refs.iptUpload.click();
};
const deleteChoose = e => {
e.preventDefault();
clearSelect();
};
const fasdfasd = () => {
console.dir(refs.iptUpload);
};
onMounted(() => {});
return {
pageData,
butHandle,
chooseFile,
onChange,
deleteChoose,
fasdfasd,
};
},
};
</script>
<style lang="less" scoped>
</style>
效果图如下:
vue cli 创建项目
vue-sample-loader
npm install vue-simple-uploader --save
import Vue from 'vue'
import uploader from 'vue-simple-uploader'
import App from './App.vue'
Vue.use(uploader)
/* eslint-disable no-new */
new Vue({
render(createElement) {
return createElement(App)
}
}).$mount('#app')
<template>
<uploader :options="options" class="uploader-example">
<uploader-unsupport></uploader-unsupport>
<uploader-drop>
<p>Drop files here to upload or</p>
<uploader-btn>select files</uploader-btn>
<uploader-btn :attrs="attrs">select images</uploader-btn>
<uploader-btn :directory="true">select folder</uploader-btn>
</uploader-drop>
<uploader-list></uploader-list>
</uploader>
</template>
<script>
export default {
data () {
return {
options: {
// https://github.com/simple-uploader/Uploader/tree/develop/samples/Node.js
target: '//localhost:3000/upload',
testChunks: false
},
attrs: {
accept: 'image/*'
}
}
}
}
</script>
<style>
.uploader-example {
width: 880px;
padding: 15px;
margin: 40px auto 0;
font-size: 12px;
box-shadow: 0 0 10px rgba(0, 0, 0, .4);
}
.uploader-example .uploader-btn {
margin-right: 4px;
}
.uploader-example .uploader-list {
max-height: 440px;
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
}
</style>
上传接口链接
把samples/Node.js 文件夹下的内容下载下来,然后执行下面
npm install
node app.js
git clone https://gitcode.net/mirrors/simple-uploader/vue-uploader
cd vue-uploader
cnpm i
uploader.vue 文件下有下面代码,说明本库是基于simple-uploader.js
二次封装的vue插件库
import Uploader from 'simple-uploader.js'
const uploader = new Uploader(this.options)
simple-uploader.js插件代码
npm install simple-uploader.js
var uploader = new Uploader({
target: '/api/photo/redeem-upload-token',
query: { upload_token: 'my_token' }
})
// 如果不支持 需要降级的地方
if (!uploader.support) location.href = '/some-old-crappy-uploader'
如果想要选择文件或者拖拽文件的话,你可以通过如下两个 API 来指定哪些 DOM 节点:
uploader.assignBrowse(document.getElementById('browseButton')) 单击选择文件
uploader.assignDrop(document.getElementById('dropTarget')) 拖拽文件
实例化后你还可以选择监听一些事件:
// 文件添加 单个文件
uploader.on('fileAdded', function (file, event) {
console.log(file, event)
})
// 单个文件上传成功
uploader.on('fileSuccess', function (rootFile, file, message) {
console.log(rootFile, file, message)
})
// 根下的单个文件(文件夹)上传完成
uploader.on('fileComplete', function (rootFile) {
console.log(rootFile)
})
// 某个文件上传失败了
uploader.on('fileError', function (rootFile, file, message) {
console.log(rootFile, file, message)
})
配置:
new Uploader(...)
内部的配置有:
target
目标上传 URL,可以是字符串也可以是函数,如果是函数的话,则会传入 Uploader.File
实例、当前块 Uploader.Chunk
以及是否是测试模式,默认值为 '/'
。singleFile
单文件上传。覆盖式,如果选择了多个会把之前的取消掉。默认 false
。chunkSize
分块时按照该值来分。最后一个上传块的大小是可能是大于等于1倍的这个值但是小于两倍的这个值大小,可见这个 Issue #51,默认 1*1024*1024
。forceChunkSize
是否强制所有的块都是小于等于 chunkSize
的值。默认是 false
。simultaneousUploads
并发上传数,默认 3
。fileParameterName
上传文件时文件的参数名,默认 file
。query
其他额外的参数,这个可以是一个对象或者是一个函数,如果是函数的话,则会传入 Uploader.File
实例、当前块 Uploader.Chunk
以及是否是测试模式,默认为 {}
。headers
额外的一些请求头,如果是函数的话,则会传入 Uploader.File
实例、当前块 Uploader.Chunk
以及是否是测试模式,默认 {}
。withCredentials
标准的 CORS 请求是不会带上 cookie 的,如果想要带的话需要设置 withCredentials
为 true
,默认 false
。method
当上传的时候所使用的是方式,可选 multipart
、octet
,默认 multipart
,参考 multipart vs octet。testMethod
测试的时候使用的 HTTP 方法,可以是字符串或者函数,如果是函数的话,则会传入 Uploader.File
实例、当前块 Uploader.Chunk
,默认 GET
。uploadMethod
真正上传的时候使用的 HTTP 方法,可以是字符串或者函数,如果是函数的话,则会传入 Uploader.File
实例、当前块 Uploader.Chunk
,默认 POST
。allowDuplicateUploads
如果说一个文件已经上传过了是否还允许再次上传。默认的话如果已经上传了,除非你移除了否则是不会再次重新上传的,所以也就是默认值为 false
。prioritizeFirstAndLastChunk
对于文件而言是否高优先级发送第一个和最后一个块。一般用来发送到服务端,然后判断是否是合法文件;例如图片或者视频的 meta 数据一般放在文件第一部分,这样可以根据第一个块就能知道是否支持;默认 false
。testChunks
是否测试每个块是否在服务端已经上传了,主要用来实现秒传、跨浏览器上传等,默认 true
。preprocess
可选的函数,每个块在测试以及上传前会被调用,参数就是当前上传块实例 Uploader.Chunk
,注意在这个函数中你需要调用当前上传块实例的 preprocessFinished
方法,默认 null
。initFileFn
可选函数用于初始化文件对象,传入的参数就是 Uploader.File
实例。readFileFn
可选的函数用于原始文件的读取操作,传入的参数就是 Uploader.File
实例、文件类型、开始字节位置 startByte,结束字节位置 endByte、以及当前块 Uploader.Chunk
实例。并且当完成后应该调用当前块实例的readFinished
方法,且带参数-已读取的 bytes。checkChunkUploadedByResponse
可选的函数用于根据 XHR 响应内容检测每个块是否上传成功了,传入的参数是:Uploader.Chunk
实例以及请求响应信息。这样就没必要上传(测试)所有的块了,具体细节原因参考 Issue #1,使用示例.generateUniqueIdentifier
可覆盖默认的生成文件唯一标示的函数,默认 null
。maxChunkRetries
最大自动失败重试上传次数,值可以是任意正整数,如果是 undefined
则代表无限次,默认 0
。chunkRetryInterval
重试间隔,值可以是任意正整数,如果是 null
则代表立即重试,默认 null
。progressCallbacksInterval
进度回调间隔,默认是 500
。speedSmoothingFactor
主要用于计算平均速度,值就是从 0 到 1,如果是 1 那么上传的平均速度就等于当前上传速度,如果说长时间上传的话,建议设置为 0.02
,这样剩余时间预估会更精确,这个参数是需要和 progressCallbacksInterval
一起调整的,默认是 0.1
。successStatuses
认为响应式成功的响应码,默认 [200, 201, 202]
。permanentErrors
认为是出错的响应码,默认 [404, 415, 500, 501]
。initialPaused
初始文件 paused 状态,默认 false
。processResponse
处理请求结果,默认 function (response, cb) { cb(null, response) }
。 0.5.2版本后,processResponse
会传入更多参数:(response, cb, Uploader.File, Uploader.Chunk)。processParams
处理请求参数,默认 function (params) {return params}
,一般用于修改参数名字或者删除参数。0.5.2版本后,processParams
会有更多参数:(params, Uploader.File, Uploader.Chunk, isTest)。new Uploader().xxx
其中xxx属性有:
.support
当前浏览器是否支持 File API 来上传。.supportDirectory
当前浏览器是否支持文件夹上传。.opts
实例的配置项对象。.files
由 Uploader.File
文件对象组成的数组,纯文件列表。.fileList
由 Uploader.File
文件、文件夹对象组成的数组,文件和文件夹共存。配置项太多,看github文档吧,懒得粘贴
git clone https://github.com/simple-uploader/Uploader
引入axios
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
写入标签内容
<input type="file" name="" id="fileInput">
<button id="uploadBtn">上传</button>
修改文件时,触发fileInput
上传文件时,触发uploadBtn
document.getElementById('fileInput').addEventListener('change', handleFileChange);
document.getElementById('uploadBtn').addEventListener('click', handleUpload);
var file = null;
axios.defaults.baseURL = `http://localhost:3000`;
// 文件修改后,触发函数
function handleFileChange(event) {
const file = event.target.files[0];
if (!file) return;
window.file = file;
}
// 大文件上传
async function handleUpload(event) {
const file = window.file;
// 切片
const createFileChunks = function (file, size = 1024 * 1024 * 100) {// 每个切片 10MB
const fileChunks = [];
let cur = 0;
while (cur < file.size) {
fileChunks.push( file.slice(cur, cur + size) );
cur += size;
}
return fileChunks;
}
// 上传 切片
const uploadFileChunks = async function (fileChunks, filename) {
const formDataList = fileChunks.map((chunk, index) => {
const formData = new FormData();
console.log(`${filename}-${index} is instanceof Blob?`, chunk instanceof Blob);
formData.append('filename', filename);
formData.append('hash', index);
formData.append('chunk', chunk);
return formData
});
const requestList = formDataList.map(( formData ) =>
axios({
method: 'post',
url: `/upload`,
data: formData
})
);
await Promise.all(requestList);
}
// 合并切片
async function mergeFileChunks(filename) {
await axios({
method: 'get',
url: `/merge`,
params: {
filename
}
})
}
await uploadFileChunks(createFileChunks(file), file.name);
await mergeFileChunks(file.name);
}
看下切片的slice方法从何而来:
const file = event.target.files[0];
console.log(file.slice(0 , 1024 * 1024 * 100));// slice方法在下面图中
a
和 b
"express": "^4.17.1"
和 "multiparty": "^4.2.2"
index.js
代码const express = require('express');
const multiparty = require('multiparty');
const path = require('path');
const fs = require('fs');
const EventEmitter = require('events');
const { Buffer } = require('buffer');
const server = express();
const STATIC_TEMPORARY = path.resolve(__dirname, './a');
const STATIC_FILES = path.resolve(__dirname, './b');
function deleteFolder(filepath) {
if (fs.existsSync(filepath)) {
fs.readdirSync(filepath).forEach(filename => {
const fp = `${filepath}/${filename}`;
if (fs.statSync(fp).isDirectory()) deleteFolder(fp);
else fs.unlinkSync(fp);
});
fs.rmdirSync(filepath);
}
}
// 设置响应头处理跨域
server.use((request, response, next) => {
response.header('Access-Control-Allow-Origin', '*');
next();
})
// 上传切片
server.post('/upload', async (req, res) => {
const multipart = new multiparty.Form();
try {
let formData = {
filename: '',
hash: '',
chunk: null,
};
let isFileOk = false,
isFieldOk = false;
const myEmitter = new EventEmitter();
// 获取参数
multipart.parse(req, function (err, fields, files) {
formData.filename = fields['filename'];
formData.hash = fields['hash'];
isFieldOk = true;
myEmitter.emit('start');
});
// 获取文件
multipart.on('file', async function (name, file) {
formData['chunk'] = file;
isFileOk = true;
myEmitter.emit('start');
});
// 保存文件
myEmitter.on('start', function () {
if (isFileOk && isFieldOk) {
const {
filename,
hash,
chunk,
} = formData;
console.log(STATIC_TEMPORARY);
// 如果没有文件夹则新建
const chunkDir = `${STATIC_TEMPORARY}/${filename}`;
if (!fs.existsSync(chunkDir)) fs.mkdirSync(chunkDir);
// 获取 Buffer File
const buffer = fs.readFileSync(chunk.path);
// 创建写入流 写入Buffer
const ws = fs.createWriteStream(`${chunkDir}/${hash}`);
ws.end(buffer);
isFileOk = false;
isFieldOk = false;
res.send(`${chunk} 上传完成!`);
}
});
} catch (error) {
console.error(error);
}
});
// 合并切片
server.get('/merge', async (req, res) => {
const { filename } = req.query;
try {
const wsPath = `${STATIC_FILES}/${filename}`;
const temporaryPath = `${STATIC_TEMPORARY}/${filename}`;
let len = 0;
const bufferList = fs.readdirSync(temporaryPath).map(name => {
const buffer = fs.readFileSync(`${temporaryPath}/${name}`);
len += buffer.length;
return buffer;
});
// 合并写入
const buffer = Buffer.concat(bufferList, len);
const ws = fs.createWriteStream(wsPath);
ws.end(buffer);
// 删除切片缓存
deleteFolder(temporaryPath);
res.send(`${filename} 合并成功`);
} catch (error) {
console.error(error);
}
});
server.listen(3000, _ => {
console.log("http://localhost:3000/");
});
结果:a文件夹下放切片
,b下放合并后的文件