u-upload代码:
<u-upload
:fileList="fileList1"
@afterRead="afterRead"
@delete="deletePic"
name="1"
:multiple="true"
:maxCount="5"
>
</u-upload>
<!-- 给图片添加的标签 -->
<canvas
:style="{
width: watermarkCanvasOption.width + 'px',
height: watermarkCanvasOption.height + 'px',
}"
canvas-id="waterMarkCanvas"
id="waterMarkCanvas"
style="position: absolute; top: -10000000rpx"
/>
定义数据:
fileList1:[],
watermarkCanvasOption: {
width: 0,
height: 0
},
pictureInfo: [], // 最后图片存储到这个数组中
上传图片方法:
async afterRead(event) {
const that = this;
// 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
const imgLists = [].concat(event.file);
for (let i = 0; i < imgLists.length; i++) {
const tPath = imgLists[i].url;
if (tPath) {
// addWatermark我是放到util.js文件里了,
// addWatermark方法是精髓
const res = await util.addWatermark(
{
canvasId: "waterMarkCanvas",
imagePath: tPath,
watermarkList: [
{
fontSize: 40,
color: "#fff",
margin: 32,
position: "bottomLeft",
// 这里是水印信息,自己定义
text: ["2025-5-28 20:52:54", "深圳市"]
}
]
},
that
);
imgLists[i].thumb = res;
imgLists[i].url = res;
// 下载
// res && util.saveImageToPA(res)
}
}
that.prepositionUploadFilePromise(event, imgLists);
},
prepositionUploadFilePromise(event, imgLists) {
this.fileListLen = this[`fileList${event.name}`].length;
imgLists.map((item) => {
this[`fileList${event.name}`].push({
...item,
status: "uploading",
message: "上传中",
});
});
for (let i = 0; i < imgLists.length; i++) {
this.DKUploadFilePromise(imgLists, i, event);
}
},
async DKUploadFilePromise(imgLists, i, event) {
let res = await this.uploadFilePromise(imgLists[i].url);
console.log('图片返回信息:', res)
let item = this[`fileList${event.name}`][this.fileListLen];
this[`fileList${event.name}`].splice(
this.fileListLen,
1,
Object.assign(item, {
status: "success",
message: "",
})
);
},
uploadFilePromise(url) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: "", // 上传图片地址
filePath: url, // 图片URL
name: "file",
success: (res) => {
this.pictureInfo.push(JSON.parse(res.data).url);
setTimeout(() => {
resolve(JSON.parse(res.data).url);
}, 1000);
},
});
});
}
addWatermark方法:
// 添加水印并压缩
function addWatermark(options, that, isCompress = true) {
return new Promise((resolve, reject) => {
const {
errLog,
config
} = dealWatermarkConfig(options)
that.watermarkCanvasOption.width = 0
that.watermarkCanvasOption.height = 0
if (!errLog.length) {
const {
canvasId,
imagePath,
watermarkList,
quality = 0.6
} = config
uni.getImageInfo({ // 获取图片信息,以便获取图片的真实宽高信息
src: imagePath,
success: (info) => {
const {
width: oWidth,
height: oHeight,
type,
orientation
} = info; // 获取图片的原始宽高
const fileTypeObj = {
'jpeg': 'jpg',
'jpg': 'jpg',
'png': 'png',
}
const fileType = fileTypeObj[type] || 'png'
let width = oWidth
let height = oHeight
if (isCompress) {
const {
cWidth,
cHeight
} = calcRatioHeightAndWight({
oWidth,
oHeight,
quality,
orientation
})
// 按对折比例缩小
width = cWidth
height = cHeight
}
that.watermarkCanvasOption.width = width
that.watermarkCanvasOption.height = height
that.$nextTick(() => {
// 获取canvas绘图上下文
const ctx = uni.createCanvasContext(canvasId, that);
// 绘制原始图片到canvas上
ctx.drawImage(imagePath, 0, 0, width, height);
// 绘制水印项
const drawWMItem = (ctx, options) => {
const {
fontSize,
color,
text: cText,
position,
margin
} = options
// 添加水印
ctx.setFontSize(fontSize); // 设置字体大小
ctx.setFillStyle(color); // 设置字体颜色为红色
if (isNotEmptyArr(cText)) {
const text = cText.filter(Boolean)
if (position.startsWith('bottom')) {
text.reverse()
}
text.forEach((str, ind) => {
const textMetrics = ctx.measureText(str);
const {
calcX,
calcY
} = calcPosition({
height,
width,
position,
margin,
ind,
fontSize,
textMetrics
})
ctx.fillText(str, calcX, calcY, width);
})
} else {
const textMetrics = ctx.measureText(cText);
const {
calcX,
calcY
} = calcPosition({
height,
width,
position,
margin,
ind: 0,
fontSize,
textMetrics
})
// 在图片底部添加水印文字
ctx.fillText(text, calcX, calcY, width);
}
}
watermarkList.forEach(ele => {
drawWMItem(ctx, ele)
})
// 绘制完成后执行的操作,这里不等待绘制完成就继续执行后续操作,因为我们要导出为图片
ctx.draw(false, () => {
// #ifndef MP-ALIPAY
uni.canvasToTempFilePath({ // 将画布内容导出为图片
canvasId,
x: 0,
y: 0,
width,
height,
fileType,
quality, // 图片的质量,目前仅对 jpg 有效。取值范围为 (0, 1],不在范围内时当作 1.0 处理。
destWidth: width,
destHeight: height,
success: (res) => {
resolve(res.tempFilePath)
},
fail() {
reject(false)
}
}, that);
// #endif
// #ifdef MP-ALIPAY
ctx.toTempFilePath({ // 将画布内容导出为图片
canvasId,
x: 0,
y: 0,
width: width,
height: height,
destWidth: width,
destHeight: height,
// fileType: 'png',
success: (res) => {
resolve(res.tempFilePath)
},
fail() {
reject(false)
}
}, that);
// #endif
});
})
}
});
} else {
const errStr = errLog.join(';')
showMsg(errStr)
reject(errStr)
}
})
}
// 计算等比缩放的宽高
function calcRatioHeightAndWight(options) {
const {
oWidth,
oHeight,
quality,
orientation
} = options
let cWidth = Math.floor(oWidth * quality)
let cHeight = Math.floor(oHeight * quality)
if (orientation === 'up') {
return {
cWidth,
cHeight
}
} else {
return {
cWidth: cHeight,
cHeight: cWidth
}
}
}
// 计算x,y位置
function calcPosition(options) {
const {
height,
width,
position,
margin: marginVal,
ind,
fontSize,
textMetrics
} = options
let calcX = marginVal
let calcY = height - marginVal
switch (position) {
case 'topLeft': {
calcX = marginVal
calcY = marginVal + (fontSize * (ind + 1))
break;
}
case 'topRight': {
calcX = width - marginVal - textMetrics.width
calcY = marginVal + (fontSize * (ind + 1))
break;
}
case 'bottomLeft': {
calcX = marginVal
calcY = height - marginVal - (fontSize * ind)
break;
}
case 'bottomRight': {
calcX = width - marginVal - textMetrics.width
calcY = height - marginVal - (fontSize * ind)
break;
}
}
return {
calcX,
calcY
}
}
// 处理及校验传参
function dealWatermarkConfig(options) {
const valdiateRulesObj = {
canvasId: '画布id',
imagePath: '本地图片路径',
}
const defaultWatermarkItem = {
fontSize: 20,
color: 'red',
margin: 25,
position: 'bottomLeft', // topLeft / topRight / bottomLeft / bottomRight
}
const errLog = validateObj(options, valdiateRulesObj)
const {
canvasId,
watermarkList,
...restObj
} = options
let nList = []
console.log('watermarkListsdf', watermarkList)
if (isNotEmptyArr(watermarkList)) {
const nErrLog = []
watermarkList.forEach(ele => {
const nEle = {
...defaultWatermarkItem,
...ele,
}
if (convertNumber(nEle.fontSize) <= 16) {
nErrLog.push('水印项字体大小需大于16')
}
if (convertNumber(nEle.margin) <= 10) {
nErrLog.push('水印项边距大小需大于10')
}
if (typeof nEle.text === 'string') {
EMPTY_STR_ARR.includes(nEle.text) && nErrLog.push('水印项文案不能为空')
} else {
!isNotEmptyArr(nEle.text) && nErrLog.push('水印项文案数组不能为空')
}
const positionArr = ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']
if (!positionArr.includes(nEle.position)) {
nErrLog.push(`水印项位置不满足【${positionArr.join('/')}】其中之一`)
}
if (!nErrLog.length) {
nList.push(nEle)
}
})
errLog.push(...nErrLog)
} else {
errLog.push('水印项是必填的且为数组')
}
return {
errLog,
config: {
...restObj,
canvasId,
watermarkList: nList
}
}
}
// 校验对象属性值不能为空
function validateObj(valdiateData, validateRules = {}, text = '') {
if (isNotEmptyObj(validateRules)) {
if (isNotEmptyObj(valdiateData)) {
const errLog = []
Object.entries(validateRules).forEach(item => {
const [key, value] = item
const val = valdiateData[key]
if (typeof value === 'string') {
if (EMPTY_STR_ARR.includes(val)) {
errLog.push(`${text? text + '-' : ''}${validateRules[key]}不能为空`)
}
} else if (isNotEmptyObj(value)) {
const {
name,
rules
} = value
if (isNotEmptyArr(rules) && !rules.includes(val)) {
errLog.push(`${text? text + '-' : ''}${name}不能满足【${rules.join('/')}】其中之一`)
}
} else if (typeof value === 'function') {
const fRes = value(val)
fRes && errLog.push(fRes?.toString())
}
})
return errLog
} else {
return []
}
} else {
return []
}
}
// 信息提示
export function showMsg(text, icon = 'none', duration = 2500) {
uni.showToast({
title: text,
icon: icon,
duration
})
}
/**
* @description: 转换为数字
* @param {unknown} str
* @return {*}
*/
export function convertNumber(str) {
const val = Number(str)
return isNaN(val) ? 0 : val
}
// 判断是否是空对象
export function isNotEmptyObj(obj) {
return typeof obj === 'object' && Object.keys(obj)?.length
}
// 判断是否是空数组
export function isNotEmptyArr(arr) {
return Array.isArray(arr) && arr.length
}
// 保存图片到相册
function saveImageToPA(tPath) {
return new Promise((resolve, reject) => {
if (tPath) {
uni.saveImageToPhotosAlbum({
filePath: tPath,
success: function() {
showMsg('保存成功')
resolve(true)
},
fail() {
reject(false)
}
});
} else {
showMsg('未获取到图片本地路径')
reject(false)
}
})
}