Files
FirstUI-vue/components/firstui/fui-poster/index.js
2023-08-17 21:28:49 +08:00

644 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 本文件由FirstUI授权予新疆天衡创新研究院有限公司手机号 186 14072 5 4 9身份证尾号5A07X5专用请尊重知识产权勿私下传播违者追究法律责任。
/*!
* 生成海报
* poster - v1.8.0 (2023/02/02, 16:52:14 PM)
*
*
* 官网地址https://firstui.cn/
* 文档地址https://doc.firstui.cn/
*/
const poster = {
_pixelRatio: 2,
_ctx: null,
_canvasId: null,
_this: null,
create(pixelRatio, canvasId, _this) {
poster._pixelRatio = pixelRatio;
poster._canvasId = canvasId;
poster._this = _this;
poster._ctx = uni.createCanvasContext(canvasId, _this)
},
_toPx(rpx) {
return uni.upx2px(rpx * poster._pixelRatio)
},
_getTextWidth(context, text, fontSize) {
let width = 0;
// #ifndef MP-ALIPAY || MP-BAIDU
width = context.measureText(text).width
// #endif
//支付宝小程序ios真机测试measureText获取长度有bug
//百度小程序有frontText时误差较大影响绘制效果
// #ifdef MP-ALIPAY || MP-BAIDU
let sum = 0;
for (let i = 0, len = text.length; i < len; i++) {
if (text.charCodeAt(i) >= 0 && text.charCodeAt(i) <= 255)
sum = sum + 1;
else
sum = sum + 2;
}
width = sum / 2 * poster._toPx(fontSize)
// #endif
return width;
},
//canvas文字换行rows=-1则不限制行数
_wrapText(text, fontSize, textWidth, width, context, rows = 2) {
let textArr = [];
if (textWidth > width) {
let fillText = '';
let lines = 1;
let arr = text.split('')
for (let i = 0, len = arr.length; i < len; i++) {
fillText = fillText + arr[i];
if (poster._getTextWidth(context, fillText, fontSize) >= width) {
if (lines === rows && rows !== -1) {
if (i !== arr.length - 1) {
fillText = fillText.substring(0, fillText.length - 1) + '...';
}
textArr.push(fillText);
break;
}
textArr.push(fillText);
fillText = '';
lines++;
} else if (i === arr.length - 1) {
textArr.push(fillText);
}
}
} else {
textArr.push(text)
}
return textArr;
},
_drawText(context, params) {
let {
x,
y,
fontSize,
color,
baseLine = 'normal',
textAlign = 'left',
frontText,
frontSize,
spacing, //单位rpx
text,
opacity = 1,
lineThrough = false,
width = 500, //单位rpx
rows = 1,
lineHeight = 0,
fontWeight = 'normal',
fontStyle = 'normal',
fontFamily = "sans-serif"
} = params;
context.save();
context.beginPath();
context.font = fontStyle + " " + fontWeight + " " + poster._toPx(fontSize) + "px " + fontFamily
context.setGlobalAlpha(opacity);
// #ifdef MP-TOUTIAO
context.setFontSize(poster._toPx(fontSize));
// #endif
context.setFillStyle(color);
context.setTextBaseline(baseLine);
context.setTextAlign(textAlign);
let textWidth = poster._getTextWidth(context, text, fontSize);
width = poster._toPx(width);
let textArr = poster._wrapText(text, fontSize, textWidth, width, context, rows)
//如果文本前面有其他文本内容
if (frontText) {
context.setFontSize(poster._toPx(frontSize));
x = poster._getTextWidth(context, frontText, frontSize) + poster._toPx(x + spacing);
context.setFontSize(poster._toPx(fontSize));
} else {
x = poster._toPx(x)
}
textArr.forEach((item, index) => {
context.fillText(item, x, poster._toPx(y + (lineHeight || fontSize) * index))
})
context.restore();
if (lineThrough) {
let lineY = y;
// 根据baseLine的不同对贯穿线的Y坐标做相应调整
switch (baseLine) {
case 'top':
lineY += fontSize / 2 + 4;
break;
case 'middle':
break;
case 'bottom':
lineY -= fontSize / 2 + 4;
break;
default:
// #ifdef MP-WEIXIN
lineY -= fontSize / 2 - 3;
// #endif
// #ifndef MP-WEIXIN
lineY -= fontSize / 2 - 4;
// #endif
break;
}
context.save();
context.moveTo(x, poster._toPx(lineY));
context.lineTo(x + textWidth + 2, poster._toPx(lineY));
context.setStrokeStyle(color);
context.stroke();
context.restore();
}
},
_drawRadiusRect(context, params) {
let {
x,
y,
width,
height,
borderRadius
} = params;
let r = poster._toPx(borderRadius / 2);
x = poster._toPx(x)
y = poster._toPx(y)
width = poster._toPx(width)
height = poster._toPx(height)
context.beginPath();
context.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
context.moveTo(x + r, y);
context.lineTo(x + width - r, y);
context.lineTo(x + width, y + r);
context.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
context.lineTo(x + width, y + height - r);
context.lineTo(x + width - r, y + height);
context.arc(x + width - r, y + height - r, r, 0, Math.PI * 0.5);
context.lineTo(x + r, y + height);
context.lineTo(x, y + height - r);
context.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI);
context.lineTo(x, y + r);
context.lineTo(x + r, y);
},
_drawImage(context, params) {
let {
imgResource,
x,
y,
width,
height,
sx,
sy,
sw,
sh,
borderRadius = 0,
borderWidth = 0,
borderColor
} = params;
context.save();
if (borderRadius > 0) {
this._drawRadiusRect(context, params);
context.strokeStyle = 'rgba(255,255,255,0)'
//处理百度/头条小程序黑边问题
// #ifndef MP-BAIDU || MP-TOUTIAO
context.stroke();
// #endif
context.clip();
}
if (sw && sh) {
//根据画布的宽高计算出图片绘制的大小,可保证图片绘制不变形
//暂时未使用此api后期如果有需要再进行优化
context.drawImage(imgResource, poster._toPx(sx), poster._toPx(sy), poster._toPx(sw), poster._toPx(sh),
poster
._toPx(x), poster._toPx(y), poster._toPx(width), poster._toPx(height));
} else {
context.drawImage(imgResource, poster._toPx(x), poster._toPx(y), poster._toPx(width), poster._toPx(
height))
}
if (borderWidth && borderWidth > 0) {
context.setStrokeStyle(borderColor);
context.setLineWidth(poster._toPx(borderWidth));
context.stroke();
}
context.restore();
},
_drawBlock(context, params) {
let {
width,
height,
x,
y,
borderWidth,
backgroundColor,
gradientColor,
gradientType = 1,
borderColor,
borderRadius = 0,
opacity = 1,
shadow
} = params;
if (backgroundColor) {
context.save();
context.setGlobalAlpha(opacity);
if (gradientColor) {
// #ifndef MP-KUAISHOU
let grd = null;
if (gradientType == 1) {
//从上到下
grd = context.createLinearGradient(0, 0, poster._toPx(width), poster._toPx(height))
} else {
//从左到右
grd = context.createLinearGradient(0, poster._toPx(width), poster._toPx(height), 0)
}
grd.addColorStop(0, backgroundColor)
grd.addColorStop(1, gradientColor)
// Fill with gradient
context.setFillStyle(grd);
// #endif
// #ifdef MP-KUAISHOU
context.setFillStyle(backgroundColor);
// #endif
} else {
context.setFillStyle(backgroundColor);
}
if (shadow) {
const {
offsetX,
offsetY,
blur,
color
} = shadow;
context.shadowOffsetX = poster._toPx(offsetX)
context.shadowOffsetY = poster._toPx(offsetY)
context.shadowBlur = blur
context.shadowColor = color
// context.setShadow(poster._toPx(offsetX), poster._toPx(offsetY), blur, color);
}
if (borderRadius > 0) {
// 画圆角矩形
poster._drawRadiusRect(context, params);
context.fill();
} else {
context.fillRect(poster._toPx(x), poster._toPx(y), poster._toPx(width), poster._toPx(height));
}
context.restore();
}
if (borderWidth) {
// 画线
context.save();
context.setGlobalAlpha(opacity);
context.setStrokeStyle(borderColor);
context.setLineWidth(poster._toPx(borderWidth));
if (borderRadius > 0) {
// 画圆角矩形边框
poster._drawRadiusRect(context, params);
context.stroke();
} else {
context.strokeRect(poster._toPx(x), poster._toPx(y), poster._toPx(width), poster._toPx(height));
}
context.restore();
}
},
_drawLine(context, params) {
let {
x,
y,
endX,
endY,
color,
width = 1
} = params;
context.save();
context.beginPath();
context.setStrokeStyle(color);
context.setLineWidth(poster._toPx(width));
context.moveTo(poster._toPx(x), poster._toPx(y));
context.lineTo(poster._toPx(endX), poster._toPx(endY));
context.stroke();
context.closePath();
context.restore();
},
//ios用户拒绝相册访问 ,引导用户到设置页面,开启相册访问权限
//-1=未请求 1 = 已允许0 = 拒绝|受限
_judgeIosPermissionPhotoLibrary() {
// #ifdef APP-PLUS
var result = 0;
var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
var authStatus = PHPhotoLibrary.authorizationStatus();
if (authStatus === 0) {
result = -1;
} else if (authStatus == 3) {
result = 1;
console.log("相册权限已经开启");
} else {
result = 0;
console.log("相册权限没有开启");
}
plus.ios.deleteObject(PHPhotoLibrary);
return result;
// #endif
},
// Android权限查询
_requestAndroidPermission(permissionID) {
// #ifdef APP-PLUS
return new Promise((resolve, reject) => {
plus.android.requestPermissions(
// 理论上支持多个权限同时查询,本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
[permissionID],
function(resultObj) {
var result = 0;
for (var i = 0; i < resultObj.granted.length; i++) {
var grantedPermission = resultObj.granted[i];
console.log('已获取的权限:' + grantedPermission);
result = 1
}
for (var i = 0; i < resultObj.deniedPresent.length; i++) {
var deniedPresentPermission = resultObj.deniedPresent[i];
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
result = 0
}
for (var i = 0; i < resultObj.deniedAlways.length; i++) {
var deniedAlwaysPermission = resultObj.deniedAlways[i];
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
result = -1
}
resolve(result);
// 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限
},
function(error) {
console.log('申请权限错误:' + error.code + " = " + error.message);
resolve({
code: error.code,
message: error.message
});
}
);
});
// #endif
},
// 跳转到**应用**的权限页面
_gotoAppPermissionSetting(isAndroid) {
// #ifdef APP-PLUS
if (!isAndroid) {
var UIApplication = plus.ios.import("UIApplication");
var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import("NSURL");
// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
var setting2 = NSURL2.URLWithString("app-settings:");
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
} else {
// console.log(plus.device.vendor);
var Intent = plus.android.importClass("android.content.Intent");
var Settings = plus.android.importClass("android.provider.Settings");
var Uri = plus.android.importClass("android.net.Uri");
var mainActivity = plus.android.runtimeMainActivity();
var intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
intent.setData(uri);
mainActivity.startActivity(intent);
}
// #endif
},
_modal: function(callback, confirmColor, confirmText) {
uni.showModal({
title: '提示',
content: '您还没有开启相册权限,是否立即设置?',
showCancel: true,
cancelColor: '#B2B2B2',
confirmColor: confirmColor || "#181818",
confirmText: confirmText || "确定",
success(res) {
if (res.confirm) {
callback && callback(true)
} else {
callback && callback(false)
}
}
})
},
//相册权限查询,如果没有权限则提示打开设置页面
_judgePermissionPhotoLibrary: async function(callback) {
// #ifdef H5 || MP-ALIPAY|| MP-360
//H5端不支持调用api保存到相册
callback && callback(true)
// #endif
// #ifdef APP-PLUS
const res = uni.getSystemInfoSync();
let result;
let isAndroid = res.platform.toLocaleLowerCase() == "android";
if (isAndroid) {
result = await poster._requestAndroidPermission('android.permission.WRITE_EXTERNAL_STORAGE')
} else {
result = poster._judgeIosPermissionPhotoLibrary()
}
if (result == 1) {
callback && callback(true)
} else {
if (!(!isAndroid && result == -1)) {
poster._modal((res) => {
if (res) {
poster._gotoAppPermissionSetting(isAndroid)
}
})
} else {
callback && callback(true)
}
}
// #endif
// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO
uni.authorize({
scope: 'scope.writePhotosAlbum',
success() {
callback && callback(true)
},
fail() {
poster._modal((res) => {
if (res) {
uni.openSetting({
success(res) {
console.log(res.authSetting)
}
});
}
})
}
})
// #endif
},
//图片转成本地文件
getImage(url) {
return new Promise((resolve, reject) => {
uni.downloadFile({
url: url,
success: res => {
resolve(res.tempFilePath);
},
fail: res => {
reject(false)
}
})
})
},
//当服务器端返回图片base64时,转成本地文件
//微信小程序不支持直接绘制base64其他平台可根据支持情况进行处理
getImagebyBase64(base64) {
//使用前先查看支持平台
const uniqueId = `fui_${Math.ceil(Math.random() * 10e5).toString(36)}`
return new Promise((resolve, reject) => {
// #ifdef MP-WEIXIN
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
let arrayBuffer = wx.base64ToArrayBuffer(bodyData)
//uniqueId注意这里名称需要动态生成名称相同部分机型会出现写入失败显示的是上次生成的图片
const filePath = `${wx.env.USER_DATA_PATH}/${uniqueId}.${format}`;
//此处可能会出现存储空间不足的情况,可清理缓存解决
//fail the maximum size of the file storage limit is exceeded
wx.getFileSystemManager().writeFile({
filePath,
data: arrayBuffer,
encoding: 'binary',
success() {
resolve(filePath);
},
fail() {
reject(false)
}
})
// #endif
// #ifdef APP-PLUS
let bitmap = new plus.nativeObj.Bitmap(uniqueId);
bitmap.loadBase64Data(base64, function() {
//console.log("加载Base64图片数据成功");
bitmap.save(`_doc/${uniqueId}.png`, {}, function(e) {
//console.log('保存图片成功:' + JSON.stringify(i));
// let width = e.width; // 保存后图片的实际宽度单位为px
// let height = e.height; // 保存后图片的实际高度单位为px
let target = e.target; // 保存后的图片url路径以"file://"开头
resolve(target);
}, function(e) {
console.log('保存图片失败:' + JSON.stringify(e));
reject(false)
});
}, function() {
console.log('加载Base64图片数据失败' + JSON.stringify(e));
reject(false)
});
// #endif
// #ifdef H5
// let img = new Image();
// img.src = base64;
resolve(base64);
// #endif
//后面查看文档说明再进行转换
// #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-360
reject(false)
// #endif
})
},
generatePoster(cw, ch, queue, callback) {
const context = poster._ctx;
if (context) {
context.clearRect(0, 0, poster._toPx(cw), poster._toPx(ch))
queue.forEach((params) => {
if (params.type === 'image') {
poster._drawImage(context, params)
} else if (params.type === 'text') {
poster._drawText(context, params)
} else if (params.type === 'block') {
poster._drawBlock(context, params)
} else if (params.type === 'line') {
poster._drawLine(context, params)
}
});
const platform = uni.getSystemInfoSync().platform;
let time = 50;
if (platform === 'android') {
time = 300;
}
setTimeout(() => {
context.draw(false, () => {
setTimeout(() => {
// #ifdef MP-ALIPAY
context.toTempFilePath({
success: res => {
callback && callback(res.apFilePath)
},
fail: err => {
callback && callback(false)
}
});
// #endif
// #ifndef MP-ALIPAY
uni.canvasToTempFilePath({
x: 0,
y: 0,
canvasId: poster._canvasId,
fileType: 'png',
quality: 1,
success: function(res) {
callback && callback(res.tempFilePath)
},
fail() {
callback && callback(false)
}
}, poster._this)
// #endif
}, time)
})
}, 50)
} else {
callback && callback(false)
}
},
// 将海报图片保存到本地H5只可预览然后长按保存
saveImage(file) {
// #ifndef H5
//检查是否授权相册权限
poster._judgePermissionPhotoLibrary((res) => {
//保存图片
if (res) {
uni.saveImageToPhotosAlbum({
filePath: file,
success(res) {
uni.showToast({
title: '图片保存成功',
icon: 'none'
})
},
fail(res) {
uni.showToast({
title: '图片保存失败',
icon: 'none'
})
}
})
}
})
// #endif
// #ifdef H5
uni.previewImage({
urls: [file]
});
// #endif
}
}
export default {
create: poster.create,
generatePoster: poster.generatePoster,
getImage: poster.getImage,
getImagebyBase64: poster.getImagebyBase64,
saveImage: poster.saveImage
};