一、目的本文通过 Vue2 ElementUI 实现图片上传时添加自定义水印支持图片水印、文字水印以及水印大小位置等样式的控制。目前查阅网上的一些资料发现各篇文章对这方面内容杂乱零散也不全面。所以结合自己的学习和思考对此进行归纳整理给出相关的解决方案实现效果如下视频所示。二、功能实现要点使用 element 的el-upload组件关键属性有action、http-request、file-list、on-preview等既然是需要对上传的图片进行处理加水印操作所以一定会用到http-request属性来实现自定义上传。读取图片并获取临时的 URL需要new Image()实例然后需要创建 Canvas 画布进行重绘根据图片大小自适应控制水印大小包括文字和图片同时设有相关样式的默认值。可以通过传入参数方式来控制图片水印所在位置以及配置其他相关样式画出最终的 Canvas并转成图片文件最后调用上传接口。上传后的图片可以使用 element 源码的单独image-viewer 组件进行查看图片。三、完整源码开箱即用1、组件源码WatermarkComp.vuetemplate div v-loadinguploading element-loading-text图片处理中... element-loading-spinnerel-icon-loading element-loading-backgroundrgba(0, 0, 0, 0.8) el-upload action# :headersheaders list-typepicture-card :multiplefalse :limitlimit :with-credentialswithCredentials :show-file-listshowFileList :file-listfileListValue :before-uploaduploadFileBeforeUpload :on-exceeduploadFileOnExceed :on-previewuploadFilePreview :on-removeuploadFileOnRemove :http-requesthttpRequest i classel-icon-plus/i /el-upload span v-ifuploadTips{{ uploadTips }}/span ElImageViewer v-ifimgPreviewOpen :on-close() { imgPreviewOpen false } :url-listimgPreviewUrl.split() stylez-index: 99999;/ElImageViewer /div /template script import ElImageViewer from element-ui/packages/image/src/image-viewer // 前提是安装使用 element-ui 的组件库 import { getToken } from /utils/auth // 用户自定义获取 token 方法一般使用 js-cookie import { fileUpload } from /api/common // 用户自定义上传文件接口后端提供 export default { name: WatermarkComp, // 水印上传 components: { ElImageViewer }, data(){ return { headers: { // 用户自定义头部 Authorization: Bearer getToken() }, imgPreviewUrl: , // 图片预览 url imgPreviewOpen: false, // 图片弹窗是否打开 uploading: false // 是否上传中... } }, props: { limit: { // 最大允许上传个数 type: Number, default: 0 }, showFileList: { // 是否显示已上传文件列表 type: Boolean, default: true }, fileListValue: { // 上传的文件列表, 例如: [{name: food.jpg, url: https://xxx.cdn.com/xxx.jpg}] type: Array, default: () [] }, withCredentials: { // 支持发送 cookie 凭证信息 type: Boolean, default: false }, uploadTips: { // 提示语 type: String, default: 点击此处上传文件 }, fileSize: { // 大小限制(MB) type: Number, default: 0 }, params: { // 水印参数 type: Object, default: () {} } }, watch: { fileListValue: { deep: true, handler(val){ console.log(文件列表, val) } } }, methods: { async handleWatermark(file){ // 对原图进行处理加水印操作 const img new Image() // 创建指向内存中 Blob/File 对象的临时 URL允许在浏览器中直接访问本地文件内容无需将文件上传到服务器即可预览 // 与 FileReader 的区别createObjectURL 返回 URL适合直接用于 src/href 属性FileReader 返回 Data URLbase64编码适合小文件 img.src URL.createObjectURL(file.file || file.raw || file) await new Promise((resolve) (img.onload resolve)) const logoImg new Image() logoImg.src this.params.logoImgUrl || require(/assets/logo.png) await new Promise((resolve) (logoImg.onload resolve)) const canvas document.createElement(canvas) const ctx canvas.getContext(2d) canvas.width img.width // 设置 canvas 尺寸就是原始图片尺寸 canvas.height img.height ctx.drawImage(img, 0, 0) // 绘制原始图片 // 添加文字水印 const { fontSize, lineHeight } this.calculateTextStyle(img.width, img.height) ctx.fillStyle this.params.fontColor || #fff // 设置填充颜色 ctx.font 500 ${this.params.fontSize || fontSize}px Microsoft YaHei // 设置字体样式 if (this.params.textList this.params.textList.length) { // 遍历存在多行的文字水印如果有的话 let startTextH 0.98 * img.height - this.params.textList.length * lineHeight this.params.textList.forEach(text { ctx.fillText(text, 0.02 * img.width, startTextH, img.width) startTextH startTextH lineHeight }) } // 默认都会有“上传时间水印” ctx.fillText(上传时间${this.getFormattedTime(YYYY-MM-DD HH:mm:ss)}, 0.02 * img.width, 0.98 * img.height, img.width) // 添加 Logo 图片水印 const elementSize this.calculateWatermarkSize(img, logoImg) const positionCoords this.calculatePosition(img, elementSize, this.params.logoPosition || ) ctx.drawImage(logoImg, positionCoords.x, positionCoords.y, elementSize.width, elementSize.height) file.url canvas.toDataURL(image) return file }, async httpRequest(file) { // 将文件转成上传接口所需格式并请求 try { console.log(原始上传文件, file) this.uploading true const handleFile await this.handleWatermark(file) const uploadFile this.base64ToFile(handleFile.url, handleFile.file.name || handleFile.name) const res await fileUpload(uploadFile) if (res.code 200) { // 根据接口返回内容自行添加到文件列表中 this.fileListValue.push({ ...file, id: res.data.fileId, name: res.data.fileName, url: res.data.viewUrl }) } } catch (error) { console.error(处理并上传报错, error) } finally { this.uploading false } }, uploadFileOnExceed(){ // 文件个数超出 this.$message.error(上传文件数量不能超过 ${this.limit} 个) }, uploadFileBeforeUpload(file){ // 上传文件之前的钩子 if (file.type.indexOf(image) -1) { this.$message.error(上传文件类型不是图片) return false } if (this.fileSize file.size / 1024 / 1024 this.fileSize) { this.$message.error(上传文件大小不能超过 ${this.fileSize} MB) return false } }, uploadFileOnRemove(file, fileList){ // 文件列表删除完成后 this.fileListValue fileList.length ? fileList : [] // console.log(上传文件删除, file, this.fileListValue) }, uploadFilePreview(file){ // 点击已上传的文件 console.log(查看文件, file) let tempFileUrl if (file.response file.response.url) { tempFileUrl file.response.url } else if (file.url) { tempFileUrl file.url } if ((file.raw /image\/[a-zA-z]/.test(file.raw.type)) || (file.file /image\/[a-zA-z]/.test(file.file.type))) { // 图片 this.imgPreviewUrl tempFileUrl this.imgPreviewOpen true } else { // 其他文件 this.$confirm(是否确定b stylecolor: #4c91ff; 下载 ${file.name || file.file.name} /b, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, dangerouslyUseHTMLString: true, type: warning }).then(() { if (tempFileUrl) { window.open(tempFileUrl) } else { this.$message.error(下载失败) } }) } }, calculateTextStyle(imageWidth, imageHeight){ // 计算自适应文字大小和行间距 // 以图片较小边为基准计算基础尺寸 const baseSize Math.min(imageWidth, imageHeight) // 文字大小图片宽度的 3%-5%根据图片大小动态调整 const fontSize Math.max( baseSize * 0.03, // 最小为图片尺寸的 3% Math.min(baseSize * 0.05, 40) // 最大不超过 40px避免过大 ) // 行间距字体大小的 1.2-1.5 倍根据文字大小调整 const lineHeight fontSize * (fontSize 20 ? 1.5 : 1.2) return { fontSize: Math.round(fontSize), lineHeight: Math.round(lineHeight) } }, calculateWatermarkSize(originalImg, watermarkImg, options {}){ // 计算水印图片自适应尺寸 const { scale 0.1, // 水印相对于原图的比例 maxWidthRatio 1, // 最大宽度比例 maxHeightRatio 1, // 最大高度比例 minWidth 20, // 最小宽度 minHeight 20 // 最小高度 } options const originalWidth originalImg.width || originalImg.naturalWidth // 原始图片宽度 const originalHeight originalImg.height || originalImg.naturalHeight // 原始图片高度 const watermarkWidth watermarkImg.width || watermarkImg.naturalWidth // 水印图片宽度 const watermarkHeight watermarkImg.height || watermarkImg.naturalHeight // 水印图片高度 // 计算基于比例的尺寸 let targetWidth originalWidth * scale let targetHeight (targetWidth / watermarkWidth) * watermarkHeight // 如果高度超过限制以高度为基准重新计算 if (targetHeight originalHeight * maxHeightRatio) { targetHeight originalHeight * maxHeightRatio targetWidth (targetHeight / watermarkHeight) * watermarkWidth } // 如果宽度超过限制以宽度为基准重新计算 if (targetWidth originalWidth * maxWidthRatio) { targetWidth originalWidth * maxWidthRatio targetHeight (targetWidth / watermarkWidth) * watermarkHeight } // 确保不小于最小尺寸 targetWidth Math.max(targetWidth, minWidth) targetHeight Math.max(targetHeight, minHeight) // 保持水印图片的宽高比 const aspectRatio watermarkWidth / watermarkHeight if (targetWidth / targetHeight aspectRatio) { targetWidth targetHeight * aspectRatio } else { targetHeight targetWidth / aspectRatio } return { width: Math.round(targetWidth), height: Math.round(targetHeight) } }, calculatePosition(containerSize, elementSize, position, padding 7){ // 位置计算方法的封装 const { width: containerWidth, height: containerHeight } containerSize // 容器大小 const { width: elementWidth, height: elementHeight } elementSize // 操作目标元素大小 const positions { // 中心位置 center: { x: (containerWidth - elementWidth) / 2, y: (containerHeight - elementHeight) / 2 }, // 四角位置 top-left: { x: padding, y: padding }, top-right: { x: containerWidth - elementWidth - padding, y: padding }, bottom-left: { x: padding, y: containerHeight - elementHeight - padding }, bottom-right: { x: containerWidth - elementWidth - padding, y: containerHeight - elementHeight - padding }, // 边缘居中位置 top-center: { x: (containerWidth - elementWidth) / 2, y: padding }, bottom-center: { x: (containerWidth - elementWidth) / 2, y: containerHeight - elementHeight - padding }, left-center: { x: padding, y: (containerHeight - elementHeight) / 2 }, right-center: { x: containerWidth - elementWidth - padding, y: (containerHeight - elementHeight) / 2 }, // 九宫格位置 top-left-inner: { x: containerWidth * 0.1, y: containerHeight * 0.1 }, top-right-inner: { x: containerWidth * 0.7 - elementWidth, y: containerHeight * 0.1 }, bottom-left-inner: { x: containerWidth * 0.1, y: containerHeight * 0.7 - elementHeight }, bottom-right-inner: { x: containerWidth * 0.7 - elementWidth, y: containerHeight * 0.7 - elementHeight } } return positions[position] || positions[center] // 默认中心位置 }, getFormattedTime(format) { // 当前时间格式化也可以借助第三方库 day.js 或者 moment.js const now new Date() const pad (num) String(num).padStart(2, 0) const replacements { YYYY: now.getFullYear(), MM: pad(now.getMonth() 1), // 月份从 0 开始需 1 DD: pad(now.getDate()), HH: pad(now.getHours()), mm: pad(now.getMinutes()), ss: pad(now.getSeconds()), } return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match replacements[match]) // 一次替换所有占位符注意匹配顺序不影响结果 }, base64ToFile(str, fileName){ // base64 转成 file 或者 blob const arr str.split(,) const mime arr[0].match(/:(.*?);/)[1] const bStr atob(arr[1]) let n bStr.length const u8arr new Uint8Array(n) while (n--) { u8arr[n] bStr.charCodeAt(n) } return new File([u8arr], fileName, { type: mime }) // file // return new Blob([u8arr], { type: mime }) // blob } } } /script2、调用方式某 vue 文件比如 index.vue直接使用template div !-- 调用组件 -- WatermarkComp :limit6 :paramswImgParams / /div /template script import WatermarkComp from /components/WatermarkComp/index // 引入组件 export default { components: { // 注册组件 WatermarkComp }, data() { return { wImgParams: { logoImgUrl: require(/assets/icons/bg1.png), // 自定义水印图片从本地导入 logoPosition: top-right, // 图片水印的位置 textList: [用户hxhpg, 操作平台PC, 地址浙江省杭州市] // 文字水印内容 } } } } /script这是我本人在工作学习中做的一些总结同时也分享出来给需要的小伙伴哈 ~ 供参考学习虽然现在 AI 已经很强大了但是 AI 只能帮我节省时间提高开发效率却省不了自身的学习成本和经验加油有什么建议欢迎评论留言转载请注明出处哈感谢支持
Vue + Element 实现图片上传添加自定义水印(图片水印、文字水印都可以)
一、目的本文通过 Vue2 ElementUI 实现图片上传时添加自定义水印支持图片水印、文字水印以及水印大小位置等样式的控制。目前查阅网上的一些资料发现各篇文章对这方面内容杂乱零散也不全面。所以结合自己的学习和思考对此进行归纳整理给出相关的解决方案实现效果如下视频所示。二、功能实现要点使用 element 的el-upload组件关键属性有action、http-request、file-list、on-preview等既然是需要对上传的图片进行处理加水印操作所以一定会用到http-request属性来实现自定义上传。读取图片并获取临时的 URL需要new Image()实例然后需要创建 Canvas 画布进行重绘根据图片大小自适应控制水印大小包括文字和图片同时设有相关样式的默认值。可以通过传入参数方式来控制图片水印所在位置以及配置其他相关样式画出最终的 Canvas并转成图片文件最后调用上传接口。上传后的图片可以使用 element 源码的单独image-viewer 组件进行查看图片。三、完整源码开箱即用1、组件源码WatermarkComp.vuetemplate div v-loadinguploading element-loading-text图片处理中... element-loading-spinnerel-icon-loading element-loading-backgroundrgba(0, 0, 0, 0.8) el-upload action# :headersheaders list-typepicture-card :multiplefalse :limitlimit :with-credentialswithCredentials :show-file-listshowFileList :file-listfileListValue :before-uploaduploadFileBeforeUpload :on-exceeduploadFileOnExceed :on-previewuploadFilePreview :on-removeuploadFileOnRemove :http-requesthttpRequest i classel-icon-plus/i /el-upload span v-ifuploadTips{{ uploadTips }}/span ElImageViewer v-ifimgPreviewOpen :on-close() { imgPreviewOpen false } :url-listimgPreviewUrl.split() stylez-index: 99999;/ElImageViewer /div /template script import ElImageViewer from element-ui/packages/image/src/image-viewer // 前提是安装使用 element-ui 的组件库 import { getToken } from /utils/auth // 用户自定义获取 token 方法一般使用 js-cookie import { fileUpload } from /api/common // 用户自定义上传文件接口后端提供 export default { name: WatermarkComp, // 水印上传 components: { ElImageViewer }, data(){ return { headers: { // 用户自定义头部 Authorization: Bearer getToken() }, imgPreviewUrl: , // 图片预览 url imgPreviewOpen: false, // 图片弹窗是否打开 uploading: false // 是否上传中... } }, props: { limit: { // 最大允许上传个数 type: Number, default: 0 }, showFileList: { // 是否显示已上传文件列表 type: Boolean, default: true }, fileListValue: { // 上传的文件列表, 例如: [{name: food.jpg, url: https://xxx.cdn.com/xxx.jpg}] type: Array, default: () [] }, withCredentials: { // 支持发送 cookie 凭证信息 type: Boolean, default: false }, uploadTips: { // 提示语 type: String, default: 点击此处上传文件 }, fileSize: { // 大小限制(MB) type: Number, default: 0 }, params: { // 水印参数 type: Object, default: () {} } }, watch: { fileListValue: { deep: true, handler(val){ console.log(文件列表, val) } } }, methods: { async handleWatermark(file){ // 对原图进行处理加水印操作 const img new Image() // 创建指向内存中 Blob/File 对象的临时 URL允许在浏览器中直接访问本地文件内容无需将文件上传到服务器即可预览 // 与 FileReader 的区别createObjectURL 返回 URL适合直接用于 src/href 属性FileReader 返回 Data URLbase64编码适合小文件 img.src URL.createObjectURL(file.file || file.raw || file) await new Promise((resolve) (img.onload resolve)) const logoImg new Image() logoImg.src this.params.logoImgUrl || require(/assets/logo.png) await new Promise((resolve) (logoImg.onload resolve)) const canvas document.createElement(canvas) const ctx canvas.getContext(2d) canvas.width img.width // 设置 canvas 尺寸就是原始图片尺寸 canvas.height img.height ctx.drawImage(img, 0, 0) // 绘制原始图片 // 添加文字水印 const { fontSize, lineHeight } this.calculateTextStyle(img.width, img.height) ctx.fillStyle this.params.fontColor || #fff // 设置填充颜色 ctx.font 500 ${this.params.fontSize || fontSize}px Microsoft YaHei // 设置字体样式 if (this.params.textList this.params.textList.length) { // 遍历存在多行的文字水印如果有的话 let startTextH 0.98 * img.height - this.params.textList.length * lineHeight this.params.textList.forEach(text { ctx.fillText(text, 0.02 * img.width, startTextH, img.width) startTextH startTextH lineHeight }) } // 默认都会有“上传时间水印” ctx.fillText(上传时间${this.getFormattedTime(YYYY-MM-DD HH:mm:ss)}, 0.02 * img.width, 0.98 * img.height, img.width) // 添加 Logo 图片水印 const elementSize this.calculateWatermarkSize(img, logoImg) const positionCoords this.calculatePosition(img, elementSize, this.params.logoPosition || ) ctx.drawImage(logoImg, positionCoords.x, positionCoords.y, elementSize.width, elementSize.height) file.url canvas.toDataURL(image) return file }, async httpRequest(file) { // 将文件转成上传接口所需格式并请求 try { console.log(原始上传文件, file) this.uploading true const handleFile await this.handleWatermark(file) const uploadFile this.base64ToFile(handleFile.url, handleFile.file.name || handleFile.name) const res await fileUpload(uploadFile) if (res.code 200) { // 根据接口返回内容自行添加到文件列表中 this.fileListValue.push({ ...file, id: res.data.fileId, name: res.data.fileName, url: res.data.viewUrl }) } } catch (error) { console.error(处理并上传报错, error) } finally { this.uploading false } }, uploadFileOnExceed(){ // 文件个数超出 this.$message.error(上传文件数量不能超过 ${this.limit} 个) }, uploadFileBeforeUpload(file){ // 上传文件之前的钩子 if (file.type.indexOf(image) -1) { this.$message.error(上传文件类型不是图片) return false } if (this.fileSize file.size / 1024 / 1024 this.fileSize) { this.$message.error(上传文件大小不能超过 ${this.fileSize} MB) return false } }, uploadFileOnRemove(file, fileList){ // 文件列表删除完成后 this.fileListValue fileList.length ? fileList : [] // console.log(上传文件删除, file, this.fileListValue) }, uploadFilePreview(file){ // 点击已上传的文件 console.log(查看文件, file) let tempFileUrl if (file.response file.response.url) { tempFileUrl file.response.url } else if (file.url) { tempFileUrl file.url } if ((file.raw /image\/[a-zA-z]/.test(file.raw.type)) || (file.file /image\/[a-zA-z]/.test(file.file.type))) { // 图片 this.imgPreviewUrl tempFileUrl this.imgPreviewOpen true } else { // 其他文件 this.$confirm(是否确定b stylecolor: #4c91ff; 下载 ${file.name || file.file.name} /b, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, dangerouslyUseHTMLString: true, type: warning }).then(() { if (tempFileUrl) { window.open(tempFileUrl) } else { this.$message.error(下载失败) } }) } }, calculateTextStyle(imageWidth, imageHeight){ // 计算自适应文字大小和行间距 // 以图片较小边为基准计算基础尺寸 const baseSize Math.min(imageWidth, imageHeight) // 文字大小图片宽度的 3%-5%根据图片大小动态调整 const fontSize Math.max( baseSize * 0.03, // 最小为图片尺寸的 3% Math.min(baseSize * 0.05, 40) // 最大不超过 40px避免过大 ) // 行间距字体大小的 1.2-1.5 倍根据文字大小调整 const lineHeight fontSize * (fontSize 20 ? 1.5 : 1.2) return { fontSize: Math.round(fontSize), lineHeight: Math.round(lineHeight) } }, calculateWatermarkSize(originalImg, watermarkImg, options {}){ // 计算水印图片自适应尺寸 const { scale 0.1, // 水印相对于原图的比例 maxWidthRatio 1, // 最大宽度比例 maxHeightRatio 1, // 最大高度比例 minWidth 20, // 最小宽度 minHeight 20 // 最小高度 } options const originalWidth originalImg.width || originalImg.naturalWidth // 原始图片宽度 const originalHeight originalImg.height || originalImg.naturalHeight // 原始图片高度 const watermarkWidth watermarkImg.width || watermarkImg.naturalWidth // 水印图片宽度 const watermarkHeight watermarkImg.height || watermarkImg.naturalHeight // 水印图片高度 // 计算基于比例的尺寸 let targetWidth originalWidth * scale let targetHeight (targetWidth / watermarkWidth) * watermarkHeight // 如果高度超过限制以高度为基准重新计算 if (targetHeight originalHeight * maxHeightRatio) { targetHeight originalHeight * maxHeightRatio targetWidth (targetHeight / watermarkHeight) * watermarkWidth } // 如果宽度超过限制以宽度为基准重新计算 if (targetWidth originalWidth * maxWidthRatio) { targetWidth originalWidth * maxWidthRatio targetHeight (targetWidth / watermarkWidth) * watermarkHeight } // 确保不小于最小尺寸 targetWidth Math.max(targetWidth, minWidth) targetHeight Math.max(targetHeight, minHeight) // 保持水印图片的宽高比 const aspectRatio watermarkWidth / watermarkHeight if (targetWidth / targetHeight aspectRatio) { targetWidth targetHeight * aspectRatio } else { targetHeight targetWidth / aspectRatio } return { width: Math.round(targetWidth), height: Math.round(targetHeight) } }, calculatePosition(containerSize, elementSize, position, padding 7){ // 位置计算方法的封装 const { width: containerWidth, height: containerHeight } containerSize // 容器大小 const { width: elementWidth, height: elementHeight } elementSize // 操作目标元素大小 const positions { // 中心位置 center: { x: (containerWidth - elementWidth) / 2, y: (containerHeight - elementHeight) / 2 }, // 四角位置 top-left: { x: padding, y: padding }, top-right: { x: containerWidth - elementWidth - padding, y: padding }, bottom-left: { x: padding, y: containerHeight - elementHeight - padding }, bottom-right: { x: containerWidth - elementWidth - padding, y: containerHeight - elementHeight - padding }, // 边缘居中位置 top-center: { x: (containerWidth - elementWidth) / 2, y: padding }, bottom-center: { x: (containerWidth - elementWidth) / 2, y: containerHeight - elementHeight - padding }, left-center: { x: padding, y: (containerHeight - elementHeight) / 2 }, right-center: { x: containerWidth - elementWidth - padding, y: (containerHeight - elementHeight) / 2 }, // 九宫格位置 top-left-inner: { x: containerWidth * 0.1, y: containerHeight * 0.1 }, top-right-inner: { x: containerWidth * 0.7 - elementWidth, y: containerHeight * 0.1 }, bottom-left-inner: { x: containerWidth * 0.1, y: containerHeight * 0.7 - elementHeight }, bottom-right-inner: { x: containerWidth * 0.7 - elementWidth, y: containerHeight * 0.7 - elementHeight } } return positions[position] || positions[center] // 默认中心位置 }, getFormattedTime(format) { // 当前时间格式化也可以借助第三方库 day.js 或者 moment.js const now new Date() const pad (num) String(num).padStart(2, 0) const replacements { YYYY: now.getFullYear(), MM: pad(now.getMonth() 1), // 月份从 0 开始需 1 DD: pad(now.getDate()), HH: pad(now.getHours()), mm: pad(now.getMinutes()), ss: pad(now.getSeconds()), } return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match replacements[match]) // 一次替换所有占位符注意匹配顺序不影响结果 }, base64ToFile(str, fileName){ // base64 转成 file 或者 blob const arr str.split(,) const mime arr[0].match(/:(.*?);/)[1] const bStr atob(arr[1]) let n bStr.length const u8arr new Uint8Array(n) while (n--) { u8arr[n] bStr.charCodeAt(n) } return new File([u8arr], fileName, { type: mime }) // file // return new Blob([u8arr], { type: mime }) // blob } } } /script2、调用方式某 vue 文件比如 index.vue直接使用template div !-- 调用组件 -- WatermarkComp :limit6 :paramswImgParams / /div /template script import WatermarkComp from /components/WatermarkComp/index // 引入组件 export default { components: { // 注册组件 WatermarkComp }, data() { return { wImgParams: { logoImgUrl: require(/assets/icons/bg1.png), // 自定义水印图片从本地导入 logoPosition: top-right, // 图片水印的位置 textList: [用户hxhpg, 操作平台PC, 地址浙江省杭州市] // 文字水印内容 } } } } /script这是我本人在工作学习中做的一些总结同时也分享出来给需要的小伙伴哈 ~ 供参考学习虽然现在 AI 已经很强大了但是 AI 只能帮我节省时间提高开发效率却省不了自身的学习成本和经验加油有什么建议欢迎评论留言转载请注明出处哈感谢支持