随手笔记,这里我仅仅只是满足需求而封装的,如需使用还需要改进,这里只记一下方法。
- 在utils.js里封装打文字水印的方法
/**
* 图片路径转成canvas
* @param {图片url} url
*/
async function imgToCanvas(url) {
// 创建img元素
const img = document.createElement('img');
img.src = url;
img.setAttribute('crossOrigin', 'anonymous'); // 防止跨域引起的 Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
await new Promise(resolve => {
img.onload = resolve;
});
// 创建canvas DOM元素,并设置其宽高和图片一样
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
// 坐标(0,0) 表示从此处开始绘制,相当于偏移。
canvas.getContext('2d').drawImage(img, 0, 0);
return canvas;
}
/**
* canvas添加水印
* @param {canvas对象} canvas
* @param {水印文字} text
*/
function addWatermark(canvas, texts) {
const [text1, text2, text3] = texts;
const ctx = canvas.getContext('2d');
const x = canvas.width - 20;
const y = canvas.height - 20;
ctx.fillStyle = 'red';
ctx.textBaseline = 'middle';
ctx.font = '30px';
ctx.textAlign = 'end';
ctx.fillText(text1, x, y);
ctx.fillText(text2, x, y - 20);
ctx.fillText(text3, x, y - 40);
return canvas;
}
/**
* canvas转成img
* @param {canvas对象} canvas
*/
function convasToImg(canvas) {
// 新建Image对象,可以理解为DOM
const image = new Image();
// canvas.toDataURL 返回的是一串Base64编码的URL
// 指定格式 PNG
image.src = canvas.toDataURL('image/png');
return image;
}
// 封装方法
export async function watermark(imgUrl, texts) {
// 1.图片路径转成canvas
const tempCanvas = await imgToCanvas(imgUrl);
// 2.canvas添加水印
const canvas = addWatermark(tempCanvas, texts);
// 3.canvas转成img
const img = convasToImg(canvas);
return img;
}
<template>
<div>
<el-upload
ref="upload"
action
list-type="picture-card"
accept="image/jpeg, image/png"
:limit="limit"
:file-list="fileList"
:http-request="httpRequest"
:before-upload="beforeUpload"
:before-remove="beforeRemove"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:on-success="handleSuccess"
:on-error="handlEerror"
:on-exceed="handlExceed"
:disabled="disabled"
:on-change="change"
:auto-upload="true"
>
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog width="80%" :close-on-click-modal="false" append-to-body :visible.sync="dialogVisible">
<img class="bigImage" :src="dialogImageUrl" alt />
</el-dialog>
</div>
</template>
<script>
import { mapState } from 'vuex';
import { client } from '@/utils/oss';
import { watermark, amapLoader } from '@/utils/utils';
import hrApi from '@/api/hr';
export default {
name: 'UploadPicture',
components: {},
props: {
name: {
type: String,
required: true
},
extra: {
type: Object,
default: () => {}
},
fileList: {
type: Array,
required: true
},
limit: {
type: Number,
default: 10
},
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
dialogImageUrl: '',
dialogVisible: false,
fileUrl: '',
};
},
computed: {
...mapState('app', ['taskId', 'busiId', 'enterpriseId', 'staffCode'])
},
watch: {},
created() {},
methods: {
amapLoader,
watermark,
async beforeUpload(file) {
const { type, size } = file;
const accept = ['image/jpeg', 'image/png'];
const isLt2M = size / 1024 / 1024 < 20;
if (!accept.includes(type)) {
this.$message.warning('只支持jpg、png格式的图片上传');
return false;
}
if (!isLt2M) {
this.$message.warning('上传图片大小不能超过 20MB');
return false;
}
return true;
},
httpRequest(option) {
const { file } = option;
const { name } = file;
const fileObj = {};
const reader = new FileReader();
reader.readAsDataURL(file); // 得到base64编码格式
reader.onload = async e => { // 转成blob格式
const url = URL.createObjectURL(this.dataURLtoBlob(e.currentTarget.result));
fileObj.url = url;
this.beforeUploadHandleImg(fileObj)
.then(() => {
// catalog_name/201909/29/filename_taskId_busiId_enterpriseId_毫秒级时间戳.type
const root = this.name || 'unknown';
const t = new Date();
const y = t.getFullYear();
const m = `${t.getMonth() + 1}`.padStart(2, '0');
const d = `${t.getDate()}`.padStart(2, '0');
const fileName = name.substring(0, name.indexOf('.'));
const type = name.substring(name.lastIndexOf('.') + 1);
const { taskId, busiId, enterpriseId } = this;
const time = Date.now();
const extra = `_${taskId}_${busiId}_${enterpriseId}_${time}`;
const uuid = `${fileName}${extra}.${type}`;
const path = `${root}/${y}${m}/${d}/${uuid}`;
option.onProgress({
percent: 0
});
const loading = this.$loading({
lock: true,
text: '图片上传中,请稍后',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
client()
.multipartUpload(path, this.fileUrl, {
progress: async p => {
const e = {
percent: p * 100
};
option.onProgress(e);
}
})
.then(result => {
const res = result;
res.url = `${process.env.VUE_APP_BUCKET}:${res.name}`;
option.onSuccess(res);
})
.catch(err => {
option.onError(err);
})
.finally(() => {
loading.close();
});
})
.catch(() => {
option.onError();
});
};
},
beforeRemove(file) {
const { status } = file;
if (status === 'success') {
const { name } = file;
const arr = name.split('/');
const filename = arr[arr.length - 1];
return this.$confirm(`确定移除 ${filename}吗?`, '提示', {
type: 'warning'
});
}
return true;
},
handleRemove(file) {
const { status } = file;
if (status === 'success') {
let name = '';
if (file.response) {
name = file.response.name;
} else {
name = file.name;
}
this.$emit('remove', name, this.extra);
}
},
handleSuccess(result) {
const { url } = result;
this.$emit('success', url, this.extra);
},
handlEerror() {
this.$message.error('上传失败');
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
handlExceed() {
const { limit } = this;
this.$message.warning(`最多可上传${limit}张图片,请先删除。`);
},
beforeUploadHandleImg(e) {
const dom = document.createElement('div');
dom.setAttribute('id', 'container');
document.body.appendChild(dom);
return new Promise((resolve, reject) => {
this.$loading({
lock: true,
text: '图片处理中,请稍后',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
amapLoader().load()
.then(({ AMap }) => {
// 实例化容器
const mapObj = new AMap.Map('container');
// 使用地图插件-获取当前位置
mapObj.plugin('AMap.Geolocation', () => {
const geolocation = new AMap.Geolocation();
// mapObj.addControl(geolocation);
geolocation.getCurrentPosition((status, result) => {
if (status === 'complete') {
const { lat, lng } = result.position;
// 通过经纬度逆解析地址
mapObj.plugin('AMap.Geocoder', () => {
const geocoder = new AMap.Geocoder();
geocoder.getAddress([lng, lat], (cstatus, cresult) => {
if (cstatus === 'complete' && cresult.regeocode) {
const { province, city, district, township, street, streetNumber } = cresult.regeocode.addressComponent;
const address = `${province}${city}${district}${township}${street}${streetNumber}`;
// 获取员工信息
hrApi({ staffId: this.staffCode })
.then(res => {
// 添加水印
this.watermark(e.url, [address, `${res.name}(${this.staffCode})`, this.$getSystemTime()]).then(cres => {
// 转为blob对象
this.fileUrl = this.dataURLtoBlob(cres.src);
// 手动上传
// this.$refs.upload.submit();
resolve();
});
})
.catch(() => {
reject();
})
.finally(() => {
mapObj.destroy();
document.body.removeChild(dom);
});
} else {
this.$message.error('根据经纬度查询地址失败');
mapObj.destroy();
document.body.removeChild(dom);
reject();
}
});
});
} else {
this.$message.error(result.message);
mapObj.destroy();
document.body.removeChild(dom);
reject();
}
});
});
})
.catch(e => {
console.log(e);
document.body.removeChild(dom);
reject();
});
});
},
change(e) {
console.log(e);
},
// 将base64转换为blob对象
dataURLtoBlob(dataurl) {
const arr = dataurl.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = window.atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
// eslint-disable-next-line no-plusplus
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
},
}
};
</script>
<style lang="scss" scoped>
/deep/.el-upload-list--picture-card .el-upload-list__item-actions {
opacity: 1;
background-color: transparent;
}
/deep/.el-upload-list--picture-card .el-upload-list__item-actions span {
display: inline-block;
}
/deep/.el-upload-list__item-preview {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
.el-icon-zoom-in {
display: none;
}
}
/deep/.el-upload-list__item-delete {
position: absolute;
top: auto;
right: 0;
display: inline-block;
bottom: 0px;
width: 20px;
background: rgba(0, 0, 0, 0.6);
}
/deep/.el-upload-list--picture-card .el-upload-list__item-actions .el-upload-list__item-delete {
position: absolute;
font-size: 14px;
}
/deep/.el-upload--picture-card {
width: 80px;
height: 80px;
line-height: 90px;
}
/deep/.el-upload-list--picture-card .el-upload-list__item {
width: 80px;
height: 80px;
}
/deep/.el-upload-list__item .el-icon-close-tip {
display: none !important;
}
/deep/.el-upload-list__item {
transition: none !important;
}
.bigImage {
max-width: 100%;
display: block;
margin: auto;
}
</style>
组件中使用,需要注意点的是
- 将upload设为不自动上传 :auto-upload="false"
- 自定义上传 :http-request="httpRequest"
- 在change事件回调中处理图片信息,此时去调用封装的打水印方法
- 水印打完之后返回一个base64的图片,需要转成blob或者file对象
- 我这里转为blob,因为转为file对象之后图片有问题,所有我就转成了blob
// 将base64转换为blob对象
dataURLtoBlob(dataurl) {
const arr = dataurl.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = window.atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
},
- 最后再调用手动上传
// 转为blob对象
this.fileUrl = this.dataURLtoBlob(cres.src);
// 手动上传
this.$refs.upload.submit();