php上传图片到阿里oss加水印,element-ui 自定义上传图片添加水印并上传至oss

本文介绍了一种用于图片水印处理的技术实现方案,并详细描述了如何在前端利用JavaScript进行图片加载、水印添加及最终图片转换为Blob对象的过程。此外,还涉及了图片上传组件的设计与实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

随手笔记,这里我仅仅只是满足需求而封装的,如需使用还需要改进,这里只记一下方法。

在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;

}

上传组件封装

在我的项目中用到了

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"

>

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 });

},

}

};

/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;

}

组件中使用,需要注意点的是

将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();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值