feat: 首次提交

This commit is contained in:
peerless_hero
2023-08-17 21:28:49 +08:00
parent 36f80fb971
commit ec1e5e16cd
571 changed files with 95322 additions and 0 deletions

View File

@ -0,0 +1,206 @@
<!--本文件由FirstUI授权予新疆天衡创新研究院有限公司手机号 1 8614 07 2 549身份证尾号5A07X5专用请尊重知识产权勿私下传播违者追究法律责任-->
<template>
<!-- #ifndef MP-QQ -->
<canvas :style="{ width: w + 'px', height: h + 'px' }" :canvas-id="canvasId" :id="canvasId"
class="fui-poster__canvas" v-if="canvasId"></canvas>
<!-- #endif -->
<!-- #ifdef MP-QQ -->
<canvas :style="{ width: w + 'px', height: h + 'px' }" canvas-id="canvas_poster"
class="fui-poster__canvas"></canvas>
<!-- #endif -->
</template>
<script>
// #ifdef MP-WEIXIN
const canvasId = `fui_${Math.ceil(Math.random() * 10e5).toString(36)}`
// #endif
//注意h5、app-vue 中单个尺寸过大的 canvas 在 iOS/Safari 无法绘制(具体限制尺寸未公布)
import poster from './index.js';
export default {
name: "fui-poster",
emits: ['ready'],
props: {
//画布宽度单位rpx
width: {
type: [Number, String],
default: 750
},
//画布高度单位rpx
height: {
type: [Number, String],
default: 1024
},
//像素比率,缩放比
pixelRatio: {
type: [Number, String],
// #ifdef MP-ALIPAY
default: 3
// #endif
// #ifndef MP-ALIPAY
default: 2
// #endif
}
},
data() {
//如果小程序端无法识别,则使用固定值
// #ifndef MP-WEIXIN || MP-QQ
const canvasId = `fui_${Math.ceil(Math.random() * 10e5).toString(36)}`
// #endif
//vue3下QQ小程序无法使用动态id
// #ifdef MP-QQ
const canvasId = 'canvas_poster'
// #endif
return {
canvasId,
w: 375,
h: 512
};
},
watch: {
width(val) {
this.w = this._toPx(val)
},
height(val) {
this.h = this._toPx(val)
}
},
created() {
this.w = this._toPx(this.width)
this.h = this._toPx(this.height)
},
mounted() {
this.$nextTick(() => {
setTimeout(() => {
poster.create(Number(this.pixelRatio), this.canvasId, this)
this.$emit('ready')
}, 50)
})
},
methods: {
_toPx(rpx) {
return uni.upx2px(Number(rpx) * Number(this.pixelRatio))
},
_getPosterData(texts, blocks, lines, imgs) {
let queue = [].concat(texts.map((item) => {
item.type = 'text';
item.zIndex = item.zIndex || 0;
return item;
})).concat(blocks.map((item) => {
item.type = 'block';
item.zIndex = item.zIndex || 0;
return item;
})).concat(lines.map((item) => {
item.type = 'line';
item.zIndex = item.zIndex || 0;
return item;
})).concat(imgs.map((item) => {
item.type = 'image';
item.zIndex = item.zIndex || 0;
return item;
}));
// 按照顺序排序
queue.sort((a, b) => a.zIndex - b.zIndex);
return queue;
},
//生成海报
generatePoster(params, callback) {
let {
texts = [], imgs = [], blocks = [], lines = []
} = params;
//需要看平台支持情况,如果对应平台不支持将会绘制失败
//图片处理 type1-无需处理base64、本地路径、网络路径等需在平台支持下2-网络图片,下载 3-base64转本地图片只支持App微信小程序H5
if (imgs.length > 0) {
let funcArr = []
let idxArr = []
imgs.forEach((item, index) => {
if (item.type == 2) {
funcArr.push(poster.getImage(item.imgResource))
idxArr.push(index)
}
// #ifdef APP-PLUS || H5 || MP-WEIXIN
if (item.type == 3) {
funcArr.push(poster.getImagebyBase64(item.imgResource))
idxArr.push(index)
}
// #endif
})
if (funcArr.length > 0) {
Promise.all(funcArr).then(res => {
res.forEach((imgRes, idx) => {
let item = imgs[idxArr[idx]]
item.imgResource = imgRes
// console.log(imgRes)
})
const queue = this._getPosterData(texts, blocks, lines, imgs);
poster.generatePoster(this.width, this.height, queue, callback)
}).catch(err => {
// console.log(err)
uni.showToast({
title: '图片资源处理失败',
icon: 'none'
})
})
} else {
const queue = this._getPosterData(texts, blocks, lines, imgs);
poster.generatePoster(this.width, this.height, queue, callback)
}
} else {
const queue = this._getPosterData(texts, blocks, lines, imgs);
poster.generatePoster(this.width, this.height, queue, callback)
}
},
saveImage(filePath) {
poster.saveImage(filePath)
},
//删除已缓存文件,防止超出存储空间大小限制 【备用】
//App、微信小程序、支付宝小程序、百度小程序、抖音小程序、飞书小程序、QQ小程序、京东小程序
removeSavedFile() {
//使用前请先查看支持平台
let count = 0;
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-JD || MP-QQ || MP-LARK
uni.getSavedFileList({
success(res) {
count = res.fileList.length;
if (count > 0) {
let num = 0;
let list = res.fileList || []
list.forEach(item => {
uni.removeSavedFile({
filePath: item.filePath,
complete(res) {
num++;
if (num === count) {
resolve(true)
}
}
})
})
} else {
resolve(true)
}
},
fail() {
reject(false)
}
})
// #endif
// #ifndef APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-JD || MP-QQ || MP-LARK
resolve(true)
// #endif
})
}
}
}
</script>
<style scoped>
.fui-poster__canvas {
position: fixed;
left: -6666px;
bottom: 0;
}
</style>

View File

@ -0,0 +1,644 @@
// 本文件由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
};