npm install ali-oss --save
新建utils/ali-oss-upload.js文件(代码如下)
const OSS = require('ali-oss')
// 文档链接:https://help.aliyun.com/document_detail/32068.html?spm=a2c4g.11186623.6.1291.86b3c107IHjkTR
/**
* 阿里 OSS 服务 OSS 对象
* [accessKeyId] {String}:通过阿里云控制台创建的AccessKey。
* [accessKeySecret] {String}:通过阿里云控制台创建的AccessSecret。
* [stsToken] {String}:使用临时授权方式,详情请参见使用 STS 进行临时授权。
* [bucket] {String}:通过控制台或PutBucket创建的bucket。
* [endpoint] {String}:OSS域名。
* [region] {String}:bucket所在的区域, 默认oss-cn-hangzhou。
* [internal] {Boolean}:是否使用阿里云内网访问,默认false。比如通过ECS访问OSS,则设置为true,采用internal的endpoint可节约费用。
* [cname] {Boolean}:是否支持上传自定义域名,默认false。如果cname为true,endpoint传入自定义域名时,自定义域名需要先同bucket进行绑定。
* [isRequestPay] {Boolean}:bucket是否开启请求者付费模式,默认false。具体可查看请求者付费模式。
* [secure] {Boolean}:(secure: true)则使用HTTPS,(secure: false)则使用HTTP,详情请查看常见问题。
* [timeout] {String|Number}:超时时间,默认60s。
*/
const client = new OSS({
region: '',
accessKeyId: '',
accessKeySecret: '',
bucket: ''
})
/**
* OSS 服务文件上传单个文件
* 同步方式:基于 async 和 await 方式,异步编程同步化。
* @param {String} objectName 可以自定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至当前Bucket或Bucket下的指定目录。
* @param {file} file 本地文件或者文件路径
*/
export async function ossPut(objectName, file) {
try {
const result = await client.put(objectName, file)
// console.log(result)
return result
} catch (error) {
// console.log(error)
return error
}
}
/**
* OSS 服务文件上传单个文件
* 异步方式:API 接口返回 Promise
* @param {String} objectName 可以自定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至当前Bucket或Bucket下的指定目录。
* @param {file} file 本地文件或者文件路径
*/
export async function ossAsyncPut(objectName, file) {
return new Promise((resolve, reject) => {
client.put(objectName, file)
.then(res => {
resolve(res)
})
.catch(error => {
resolve(error)
})
})
}
/**
* OSS 服务文件下载单个文件
* 同步方式:基于 async 和 await 方式,异步编程同步化。
* @param {String} objectName
*/
export async function ossGet(objectName) {
try {
const result = await client.get(objectName)
// console.log(result)
return result
} catch (error) {
// console.log(error)
return error
}
}
/**
* OSS 服务文件下载单个文件
* 异步方式:API 接口返回 Promise
* @param {String} objectName
*/
export async function ossAsyncGet(objectName) {
return new Promise((resolve, reject) => {
client.get(objectName)
.then(res => {
resolve(res)
})
.catch(error => {
reject(error)
})
})
}
/**
* OSS 服务文件删除单个文件
* @param {String} objectName
*/
export async function ossDelete(objectName) {
try {
const result = await client.delete(objectName)
return result
} catch (error) {
// console.log(error)
return error
}
}
/**
* base64ToFileObject base64 码转 blob 二进制,再转 file 对象
* @param {[type]} base64 [base64码]
* @param {string} fileName [转码后 file 对象的名称]
* @return {[type]} newFile[返回新的file对象]
*/
export function base64ToFileObject(base64, fileName = 'file') {
const mime = base64.split(',')[0].match(/:(.*?);/)[1]
base64 = base64.split(',')[1]
base64 = window.atob(base64)
const u8arr = new Uint8Array(base64.length)
for (let i = 0; i < base64.length; i++) {
u8arr[i] = base64.charCodeAt(i)
}
const blob = new Blob([u8arr], { type: mime })
const suffix = mime.split('/')[1]
return new File([blob], `${fileName}.${suffix}`, { type: mime })
}
/**
* imgToBase64 压缩图片
* @param {[type]} file [file对象 event.target.files[0]]
* @param {[type]} maxWidth [最大宽度]
* @param {[type]} maxHeight [最大高度]
* @return {[type]} [description]
*/
export function imgToBase64(file, maxWidth, maxHeight) {
return new Promise((resolve, reject) => {
// 压缩图片需要的一些元素和对象
const reader = new FileReader()
const img = new Image()
img.onload = function() {
// 图片原始尺寸
const originWidth = this.width
const originHeight = this.height
// 目标尺寸
let targetWidth = originWidth
let targetHeight = originHeight
// 图片尺寸超过400x400的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
// 缩放图片需要的canvas
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
// canvas对图片进行缩放
canvas.width = targetWidth
canvas.height = targetHeight
// 清除画布
context.clearRect(0, 0, targetWidth, targetHeight)
// 图片压缩
context.drawImage(img, 0, 0, targetWidth, targetHeight)
const base64 = canvas.toDataURL('image/jpeg', 0.8) // 压缩后质量
// const base64 = canvas.toDataURL()
// console.log(canvas.toDataURL());
resolve(base64)
}
reader.onload = function(e) {
img.src = e.target.result
}
reader.readAsDataURL(file)
})
}
2)组件封装,新建uploadImg/index.vue文件,代码如下
<!--
* @Description:图片上传
* @Author: duyan
* @Date: 2021-10-27 19:44:02
* @LastEditTime: 2021-11-19 16:05:57
* @LastEditors: duyan
-->
<template>
<div class="uploadImg">
<el-upload
:class="(maxCount>imgLength)?'uploadImgContent':'uploadImgContentNone'"
:file-list="value"
:limit="maxCount"
:drag="isDrag"
:http-request="onUploadFile"
:before-upload="handleBeforeUpload"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:on-error="handleError"
v-bind="$attrs"
action=""
accept="image/png,image/jpg,image/jpeg"
list-type="picture-card"
v-on="$listeners"
>
<i slot="default" class="el-icon-plus"/>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过2M</div>
</el-upload>
<el-dialog :visible.sync="dialogVisible" width="100%" append-to-body>
<img :src="dialogImageUrl" height="100%" alt="">
</el-dialog>
</div>
</template>
<script>
import { imgToBase64, base64ToFileObject } from '@/utils'
import { ossAsyncPut, ossDelete } from '@/utils/ali-oss-upload.js'
import { v4 as uuidv4 } from 'uuid'
export default {
// 名字
name: 'UploadImg',
// 部件
components: {
},
model: {
prop: 'fileList'
},
// 静态
props: {
fileList: {
type: Array,
default: () => []
},
// 是否直接上传到 OSS 服务
isPutOss: {
type: Boolean,
default: false
},
// 图片上传到 OSS 服务上的存储目录
ossPathPrefix: {
type: String,
default: ''
},
// 图片上传数
maxCount: {
type: Number,
default: 1
},
isDrag: {
type: Boolean,
default: false
},
// eslint-disable-next-line vue/require-default-prop
uploadSuccess: Function,
// eslint-disable-next-line vue/require-default-prop
afterRead: Function
},
// 数据
data() {
return {
dialogImageUrl: '',
dialogVisible: false,
imgLength: 0
}
},
// 属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;
computed: {
value: {
get(val) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.imgLength = this.fileList.length
return this.fileList
},
set(val) {
this.imgLength = val.length
this.$emit('input', val)
}
}
},
// 对象内部的属性监听,也叫深度监听
watch: {
},
// 请求数据
created() {
},
mounted() {
},
// 方法表示一个具体的操作,主要书写业务逻辑;
methods: {
// 图片上传
onUploadFile(file) {
// 上传完成
this.afterRead && this.afterRead(file)
// 上传oss
this.isPutOss && this.onUpload(file)
},
onUpload(file) {
file.status = 'uploading'
file.message = '上传中...'
this.uploaded(file.file)
.then(response => {
const { url } = response
file.url = url
file.status = 'done'
file.message = '上传成功'
this.value.push(file)
this.$emit('input', this.value)
// this.uploadSuccess(file)
this.$forceUpdate()// 强制渲染
})
.catch(() => {
file.status = 'failed'
file.message = '上传失败'
file.url = null
this.$forceUpdate()
})
},
/**
* 文件上传至阿里 OSS 服务
*/
uploaded(file) {
return new Promise((resolve, reject) => {
const suffix = file.type.split('/')[1]
const fileName = `${this.ossPathPrefix}/${uuidv4()}.${suffix}`
ossAsyncPut(fileName, file)
.then(({ res, url }) => {
// console.log(res)
// console.log(url)
if (res && res.status === 200) {
// console.log('上传成功')
resolve({ res, url })
} else {
// console.log('上传失败')
reject(new Error('图片上传 OSS 服务失败!'))
}
})
})
},
// 图片从oss上删除
_delete(file) {
console.log(file)
if (file.url) {
const startIndex = file.url.indexOf(this.ossPathPrefix)
const objectName = file.url.substr(startIndex)
ossDelete(objectName).then(res => {
})
}
},
handleBeforeUpload(file) {
return new Promise((resolve, reject) => {
const fileType = ['image/png', 'image/jpg', 'image/jpeg']
if (!fileType.includes(file.type)) {
this.$notify.warning({
title: '警告',
message: '请上传格式为image/png, image/jpg, image/jpeg的图片'
})
reject()
}
// 图片压缩后校验图片大小
imgToBase64(file, 800, 800).then(base64 => {
const resultFile = base64ToFileObject(base64)
const isNeed = resultFile.size / 1024 < 200
// console.log(resultFile)
// console.log(resultFile.type, isNeed)
if (!isNeed) {
this.$notify.warning({
title: '警告',
message: '图片过大'
})
reject()
}
resolve()
})
})
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url
this.dialogVisible = true
},
handleRemove(file, fileList) {
this.value = fileList
this._delete(file)
this.$forceUpdate()
},
handleError(err, file, fileList) {
console.log('error:', err, file, fileList)
}
}
}
</script>
<style scoped lang="scss">
.uploadImg{
.uploadImgContent{
::v-deep .el-upload{
display: inline-block !important ;
}
}
.uploadImgContentNone{
::v-deep .el-upload{
display: none !important;
}
}
}
::v-deep .el-dialog{
margin: 5vh 0 !important;
background: none;
.el-dialog__headerbtn .el-dialog__close {
color: #dddddd;
border: 3px solid #dddddd;
border-radius: 50%;
font-size: 28px;
}
.el-dialog__body{
height: calc( 100vh - 20vh );
overflow: hidden;
padding:0px 10px 10px 10px;
text-align: center;
}
}
</style>
<!--
* @Description:文件上传
* @Author: duyan
* @Date: 2021-10-27 19:44:02
* @LastEditTime: 2021-12-21 11:29:30
* @LastEditors: duyan
-->
<template>
<div class="uploadImg">
<el-upload
:class="(maxCount>imgLength)?'uploadImgContent':'uploadImgContentNone'"
:file-list="value"
:limit="maxCount"
:drag="isDrag"
:http-request="onUploadFile"
:before-upload="handleBeforeUpload"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:on-error="handleError"
v-bind="$attrs"
:accept="fileType"
:show-file-list="isShowFileList"
action=""
v-on="$listeners"
>
<div v-show="value.length>
<!-- 模板下载 -->
<div v-if="templateDownload">
<el-dropdown split-button type="success">
<i class="el-icon-upload2"/>
{{ btnLabel }}
<el-dropdown-menu slot="dropdown">
<el-dropdown-item icon="el-icon-download">
<!-- <div style="display: inline-block; height:32px;width:100px;background-color:#11b95c;border:transparent;text-align:center;line-height:32px;border-radius:3px;"> -->
<a :href="downloadUrl">下载导入模板</a>
<!-- </div> -->
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<div v-else>
<el-button size="small" icon="el-icon-plus" type="primary">{{ btnLabel }}</el-button>
<div slot="tip" class="el-upload__tip">只能上传{{ fileType }}文件</div>
</div>
</div>
</el-upload>
</div>
</template>
<script>
import { ossAsyncPut, ossDelete } from '@/utils/ali-oss-upload.js'
import { v4 as uuidv4 } from 'uuid'
export default {
// 名字
name: 'UploadImg',
// 部件
components: {
},
model: {
prop: 'fileList'
},
// 静态
props: {
// 文件list
fileList: {
type: Array,
default: () => []
},
// 是否直接上传到 OSS 服务
isPutOss: {
type: Boolean,
default: false
},
// 文件上传到 OSS 服务上的存储目录
ossPathPrefix: {
type: String,
default: ''
},
// 文件上传数
maxCount: {
type: Number,
default: 1
},
isDrag: {
type: Boolean,
default: false
},
// 文件类型
fileType: {
type: String,
default: () => '.pdf'
},
fileTypeList: {
type: Array,
default: () => {
return ['application/pdf', 'application/doc', 'application/docx']
}
},
// 按钮文字
btnLabel: {
type: String,
default: '点击上传'
},
// 是否显示已上传文件列表
isShowFileList: {
type: Boolean,
default: true
},
// 是否进行模板下载
templateDownload: {
type: Boolean,
default: false
},
// 模板下载链接
downloadUrl: {
type: String,
default: 'https://yzwy1-app.oss-cn-shenzhen.aliyuncs.com/communityModel.xlsx'
},
// eslint-disable-next-line vue/require-default-prop
uploadSuccess: Function,
// eslint-disable-next-line vue/require-default-prop
afterRead: Function
},
// 数据
data() {
return {
dialogImageUrl: '',
dialogVisible: false,
imgLength: 0
}
},
// 属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;
computed: {
value: {
get(val) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.imgLength = this.fileList.length
return this.fileList
},
set(val) {
this.imgLength = val.length
this.$emit('input', val)
}
}
},
// 对象内部的属性监听,也叫深度监听
watch: {
},
// 请求数据
created() {
},
mounted() {
},
// 方法表示一个具体的操作,主要书写业务逻辑;
methods: {
// 文件上传
onUploadFile(file) {
// 上传完成
this.afterRead && this.afterRead(file)
// 上传oss
this.isPutOss && this.onUpload(file)
},
onUpload(file) {
file.status = 'uploading'
file.message = '上传中...'
this.uploaded(file.file)
.then(response => {
const { url } = response
file.url = url
file.status = 'done'
file.message = '上传成功'
file.name = file.file.name
this.value.push(file)
this.$emit('input', this.value)
// this.uploadSuccess(file)
this.$forceUpdate()// 强制渲染
})
.catch(() => {
file.status = 'failed'
file.message = '上传失败'
file.url = null
this.$forceUpdate()
})
},
/**
* 文件上传至阿里 OSS 服务
*/
uploaded(file) {
return new Promise((resolve, reject) => {
const suffix = file.type.split('/')[1]
const fileName = `${this.ossPathPrefix}/${uuidv4()}.${suffix}`
ossAsyncPut(fileName, file)
.then(({ res, url }) => {
// console.log(res)
// console.log(url)
if (res && res.status === 200) {
// console.log('上传成功')
resolve({ res, url })
} else {
// console.log('上传失败')
reject(new Error('文件上传 OSS 服务失败!'))
}
})
})
},
// 文件从oss上删除
_delete(file) {
console.log(file)
if (file.url) {
const startIndex = file.url.indexOf(this.ossPathPrefix)
const objectName = file.url.substr(startIndex)
ossDelete(objectName).then(res => {
})
}
},
handleBeforeUpload(file) {
return new Promise((resolve, reject) => {
const fileType = this.fileTypeList
// console.log('fileType----', fileType)
// console.log('file----', file)
if (!fileType.includes(file.type)) {
this.$notify.warning({
title: '警告',
message: '请上传格式为' + this.fileType + '的文件'
})
reject()
}
resolve()
})
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url
this.dialogVisible = true
},
handleRemove(file, fileList) {
this.value = fileList
this._delete(file)
this.$forceUpdate()
},
handleError(err, file, fileList) {
console.log('error:', err, file, fileList)
}
}
}
</script>
<style scoped lang="scss">
.uploadImg{
.uploadImgContent{
::v-deep .el-upload{
display: inline-block !important ;
}
}
.uploadImgContentNone{
::v-deep .el-upload{
display: none !important;
}
}
}
::v-deep .el-dialog{
margin: 5vh 0 !important;
background: none;
.el-dialog__headerbtn .el-dialog__close {
color: #dddddd;
border: 3px solid #dddddd;
border-radius: 50%;
font-size: 28px;
}
.el-dialog__body{
height: calc( 100vh - 20vh );
overflow: hidden;
padding:0px 10px 10px 10px;
text-align: center;
}
}
.el-dropdown {
vertical-align: top;
}
.el-dropdown + .el-dropdown {
margin-left: 15px;
}
.el-icon-arrow-down {
font-size: 12px;
}
</style>
代码如下:
<!--
* @Description:组件使用
* @Author: duyan
* @Date: 2021-12-20 10:37:07
* @LastEditTime: 2021-12-31 15:30:11
* @LastEditors: duyan
-->
<template>
<div class="community-administrative">
<!-- 上传本地返回 -->
<div style="line-height:35px;">上传表格文件本地返回:</div>
<upload-file
:file-type="'.xls,.xlsx'"
:file-type-list="['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','application/vnd.ms-excel']"
v-model="fileListFile"
:is-put-oss="false"
:btn-label="downloadLabel"
:max-count="1"
:template-download="true"
:is-show-file-list="false"
:download-url="'模板下载链接'"
:after-read="afterRead"/>
<!-- 上传至oss返回 -->
<div style="line-height:35px;">上传pdf文件至oss:</div>
<upload-file
:file-type="'.pdf'"
:file-type-list="['application/pdf']"
v-model="fileListPdf"
:is-put-oss="true"
:max-count="1"
@input="uploadSuccess"/>
<div style="line-height:35px;">上传图片文件至oss:</div>
<upload-img v-model="fileList" :is-put-oss="true" :max-count="1" :oss-path-prefix="'yzsurvey_file/PC'" @input="uploadSuccess"/>
</div>
</template>
<script>
import uploadFile from '@/views/components/uploadFile/index'
import uploadImg from '@/views/components/uploadImg/index'
export default {
// 名字
name: 'UploadFile',
// 部件
components: {
uploadFile,
uploadImg
},
// 静态
props: {
},
// 数据
data() {
return {
// word文件
fileListFile: [],
// word文件文本
downloadLabel: '导入至本地文件及模板下载',
// Pdf文件
fileListPdf: [],
// 图片文件文件
fileList: []
}
},
// 属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;
computed: {
},
// 对象内部的属性监听,也叫深度监听
watch: {
},
// 请求数据
created() {
},
mounted() {
},
// 方法表示一个具体的操作,主要书写业务逻辑;
methods: {
/**
* @description:文件上传成功返回
* @param {*}
* @return {*}
* @param {*} file 本地文件信息
*/
afterRead(file) {
console.log('file-----', file)
},
/**
* @description:图片/文件上传线上成功返回
* @param {*}
* @return {*}
* @param {*} data 文件信息及线上链接
*/
uploadSuccess(data) {
console.log('imageData-----', data)
}
}
}
</script>
<style scoped lang="scss">
.community-administrative{
padding:10px 10px 0 10px;
}
</style>
文见传入格式点此处查看
使用以上组件
存在问题:上传会存在上下闪动问题
出现原因:fileList双向绑定会导致value赋值两次
点此处进行办法解决>>