upload上传是前端开发很常用的一个功能,在Vue开发中常用的Element组件库也提供了非常好用的upload组件。本文采用scoped-slot
去设置缩略图模版,并且实现了oss粘贴上传功能。
使用插槽scoped-slot获取当前上传的状态file。通过file.status的状态判断是否上传成功。file.status为uploading代表正在上传中,为success代表上传成功。上传中hover到文件显示上传的文件大小、进度、速度。
- 文件大小:{{ showSize(file.size) }}
- 已上传:{{ showSize(file.size * (file.percentage / 100)) }}
- 上传速度:{{ showSize((file.size * (file.percentage / 100)) / file.speed) }}/s
1.首先声明data参数
data() {
return {
thumbnailMaxSize: 1048576 * 20, // 缩略图(最大20M)
id: `baseUpload_${new Date().getTime()}`,
uploadStartTimeList: [], // 存储开始上传的事件
OSS_URL: defaultSettings.OSS_URL, // oss上传地址
myValue: this.value,
tokens: {},
dialogImageUrl: '',
dialogVisible: false,
headers: {
Authorization: getToken()
}
}
},
2.计算属性设置,这里有一个需要注意的点,oss缩略图限制图片最大为20M。如果上传图片过大,可以直接显示图片,另外判断大于20M显示原图片。
computed: {
// 缩略图地址
getThumbnailUrl(file) {
return (file) => {
return validURL(file.url) && file.size < this.thumbnailMaxSize ? file.url + `?x-oss-process=image/resize,w_${this.width}` : file.url
}
}
},
3.文件上传前钩子。前面说到的oss缩略图最大20M,在这里进行判断。之后通过后台接口设置签名,钩子返回Promise reject(false)则回调结束,
generateTokens(key, file) {
return new Promise((resolve, reject) => {
getOssLiuParamsApi().then((res) => {
file.startTime = new Date().getTime()
this.uploadStartTimeList.push(file)
this.tokens.key = res.dir + key
this.tokens.policy = res.policy
this.tokens.OSSAccessKeyId = res.accessid
this.tokens.success_action_status = 200
this.tokens.callback = res.callback
this.tokens.signature = res.signature
resolve(true)
})
}, () => {
// eslint-disable-next-line no-undef
reject(false)
})
},
// 上传文件之前的钩子 {Object} file 文件信息
beforeUpload(file) {
const isBeyond = file.size < this.maxSize
if (!isBeyond) {
const beyondTip = '文件大小超过' + showSize(this.maxSize) + ',请重新上传。'
this.$message.error(beyondTip)
return isBeyond
} else {
const arr = file.name.split('.')
const un_name = `${file.uid}.${arr[arr.length - 1]}`
return this.generateTokens(un_name, file)
}
},
4.文件上传时钩子。
handleProgress(event, file, fileList) {
this.$store.dispatch('tool/toggleBeUploading', true)
const currFile = this.uploadStartTimeList.filter(v => v.uid === file.uid)
if (currFile && currFile[0]) {
file.speed = (new Date().getTime() - currFile[0].startTime) / 1000
}
this.myValue = fileList
},
5.文件上传成功钩子。设置图片路径,为避免前面设置上传进度的时候数据错乱,这里重新遍历设置,有其他更好的方法可以自行修改。
handleUploadSuccess(res, file, fileList) {
fileList.forEach(fileListItem => {
const obj = this.setImg(file)
const index = this.myValue.findIndex(item => item.uid === file.uid)
this.myValue.splice(index, 1, obj)
})
console.log(this.myValue, 'myValue')
this.$store.dispatch('tool/toggleBeUploading', false)
this.$message({ type: 'success', message: '上传成功' })
},
6.最后来说说截屏上传
原理:借助QQ、微信等截屏工具截屏,同时监听浏览器粘贴和鼠标移动事件, ctrl+v调用后台接口进行上传并且同步到upload组件
核心代码
mixins: [pasteUploadMixin],
onMousemove(e) {
const el = e.target
const elWrappend = el.closest(`div#${this.id}`)
const elCurrent = elWrappend.parentNode.parentNode.parentNode.firstElementChild
if (elCurrent.getAttributeNode('for') && elCurrent.getAttributeNode('for').value) {
this.pasteUploadKey = elCurrent.getAttributeNode('for').value
} else {
this.pasteUploadKey = ''
}
},
// 初始化粘贴
initPaste() {
var handleAction = e => {
var cbd = e.clipboardData
var ua = window.navigator.userAgent
// 如果是 Safari 直接 return
if (!(e.clipboardData && e.clipboardData.items)) {
return
}
// Mac平台下Chrome49版本以下 复制Finder中的文件的Bug Hack掉
if (cbd.items && cbd.items.length === 2 && cbd.items[0].kind === 'string' && cbd.items[1].kind === 'file' && cbd.types && cbd.types.length === 2 && cbd.types[0] === 'text/plain' && cbd.types[1] === 'Files' && ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49) {
return
}
for (var i = 0; i < cbd.items.length; i++) {
var item = cbd.items[i]
if (item.kind === 'file') {
var blob = item.getAsFile()
if (blob.size === 0) {
return
}
var reader = new FileReader()
reader.readAsDataURL(blob)
reader.onload = readerE => {
this.submitUpload(readerE.target.result)
}
}
}
}
this.newHandle = event => {
handleAction(event)
}
document.getElementById(this.id).removeEventListener('paste', this.newHandle, false)
document.getElementById(this.id).addEventListener('paste', this.newHandle, false)
},
代码存放目录
BaseUpload
mixin
paste-upload.js
index.vue
对应的JS方法
import { uploadingPicture } from '@/utils/dict'
// 上传默认图片
export const uploadingPicture = {
zip: 'http://xxxx.aliyuncs.com/1546836641143_4273.png',
xls: 'http://xxxx.aliyuncs.com/1550486132435_7782.png',
doc: 'http://xxxx.aliyuncs.com/1550486135954_2036.png',
txt: 'http://xxxx.aliyuncs.com/1550486134063_8220.png',
mp4: 'http://xxxx.aliyuncs.com/1550653591085_6150.png',
other: 'http://xxxx.aliyuncs.com/1550653593890_4662.png'
}
import { showSize } from '@/utils'
/**
* @description 将字节转化为MB GB T
* @param {Number} size 字节
* @return {string} data 转化后的大小
*/
export const showSize = size => {
let data = ''
if (size < 1048576) {
data = Number((size / 1024).toFixed(2)) + 'KB'
} else if (size === 1048576) {
data = '1MB'
} else if (size > 1048576 && size < 1073741824) {
data = Number((size / (1024 * 1024)).toFixed(2)) + 'MB'
} else if (size > 1048576 && size === 1073741824) {
data = '1GB'
} else if (size > 1073741824 && size < 1099511627776) {
data = Number((size / (1024 * 1024 * 1024)).toFixed(2)) + 'GB'
} else {
data = '文件超过1TB'
}
return data
}
import { validURL } from '@/utils/validate'
/**
* @param {string} url
* @returns {Boolean}
*/
export function validURL(url) {
const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return reg.test(url)
}
import { getToken } from '@/utils/auth'
export function getToken() {
return Cookies.get(TokenKey)
}
import { pasteUploadMixin } from './mixin/paste-upload'
import { getOssLiuParamsApi } from '@/api/utils'
// 前端直传参数接口
export function getOssLiuParamsApi(data) {
return request({
url: 'order/ossLiu',
method: 'post',
data
})
}
import defaultSettings from '@/settings'
module.exports = {
OSS_URL: 'https://xxxx/', // oss下载地址(测试)
BASE_API: 'https://xxxx/' // 测试环境
}
/**
* @param {Array} str
* @returns {Boolean}
*/
export function isEmpty(str) {
if (str === '' || str === null || str === undefined || str.length === 0) {
return true
}
return false
}
// 上传粘贴的图片到oss
export function uploadingUpFile(data) {
return request({
url: 'order/screenshotImg',
method: 'post',
data
})
}
- 文件大小:{{ showSize(file.size) }}
- 已上传:{{ showSize(file.size * (file.percentage / 100)) }}
- 上传速度:{{ showSize((file.size * (file.percentage / 100)) / file.speed) }}/s
// paste-upload.js
import { uploadingUpFile } from '@/api/utils'
import { mapGetters } from 'vuex'
import { isEmpty } from '@/utils/validate'
export const pasteUploadMixin = {
data() {
return {
newHandle: null,
pasteUploadKey: ''
}
},
mounted() {
this.$nextTick(() => {
this.initPaste()
})
},
computed: {
...mapGetters(['pastPanel']),
// 获取激活项的地址(默认粘贴板的地址)
getBase64() {
return function(base64) {
let result = base64
if (!isEmpty(this.pastPanel.active)) {
const newResult = this.pastPanel.list.filter(v => v.id === this.pastPanel.active)
result = newResult[0].src
}
return result
}
},
// 返回粘贴限制key对应的type
getPasteUploadType() {
return function(key) {
const keyMap = {
model_img: 1,
reference_img: 2,
material_img: 3
}
return keyMap[key]
}
}
},
methods: {
onMousemove(e) {
const el = e.target
const elWrappend = el.closest(`div#${this.id}`)
const elCurrent = elWrappend.parentNode.parentNode.parentNode.firstElementChild
if (elCurrent.getAttributeNode('for') && elCurrent.getAttributeNode('for').value) {
this.pasteUploadKey = elCurrent.getAttributeNode('for').value
} else {
this.pasteUploadKey = ''
}
},
// 初始化粘贴
initPaste() {
var handleAction = e => {
var cbd = e.clipboardData
var ua = window.navigator.userAgent
// 如果是 Safari 直接 return
if (!(e.clipboardData && e.clipboardData.items)) {
return
}
// Mac平台下Chrome49版本以下 复制Finder中的文件的Bug Hack掉
if (cbd.items && cbd.items.length === 2 && cbd.items[0].kind === 'string' && cbd.items[1].kind === 'file' && cbd.types && cbd.types.length === 2 && cbd.types[0] === 'text/plain' && cbd.types[1] === 'Files' && ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49) {
return
}
for (var i = 0; i < cbd.items.length; i++) {
var item = cbd.items[i]
if (item.kind === 'file') {
var blob = item.getAsFile()
if (blob.size === 0) {
return
}
var reader = new FileReader()
reader.readAsDataURL(blob)
reader.onload = readerE => {
this.submitUpload(readerE.target.result)
}
}
}
}
this.newHandle = event => {
handleAction(event)
}
document.getElementById(this.id).removeEventListener('paste', this.newHandle, false)
document.getElementById(this.id).addEventListener('paste', this.newHandle, false)
},
submitUpload(base64) {
const params = {
type: this.getPasteUploadType(this.pasteUploadKey),
data: this.getBase64(base64)
}
this.$message.info('正在上传中,勿刷新页面')
this.$store.dispatch('tool/toggleBeUploading', true)
uploadingUpFile(params)
.then(res => {
this.myValue.push({ url: res.data, name: new Date().getTime() + '.jpg' })
this.$message({ type: 'success', message: '上传成功' })
this.$store.dispatch('tool/toggleBeUploading', false)
})
.catch(() => {
this.$message({ type: 'error', message: '上传失败' })
this.$store.dispatch('tool/toggleBeUploading', false)
})
}
},
destroyed() {
document.getElementById(this.id) && document.getElementById(this.id).removeEventListener('paste', this.newHandle, false)
}
}
喜欢的给个哦,欢迎留言