|
@@ -0,0 +1,538 @@
|
|
|
+// Canvas工具类 - 用于处理海报生成
|
|
|
+export class CanvasUtils {
|
|
|
+ constructor(canvasId) {
|
|
|
+ this.canvasId = canvasId;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 下载图片到本地(带重试机制)
|
|
|
+ downloadImage(src, retryCount = 3) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+
|
|
|
+ // 如果是本地路径,直接返回
|
|
|
+ if (src.startsWith('/') || src.startsWith('http://localhost') || src.startsWith('file://')) {
|
|
|
+ resolve(src);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const tryDownload = (attempt = 1) => {
|
|
|
+ uni.downloadFile({
|
|
|
+ url: src,
|
|
|
+ success: (res) => {
|
|
|
+ if (res.statusCode === 200) {
|
|
|
+ resolve(res.tempFilePath);
|
|
|
+ } else {
|
|
|
+ console.error(`图片下载失败,状态码: ${res.statusCode}`);
|
|
|
+ if (attempt < retryCount) {
|
|
|
+ setTimeout(() => {
|
|
|
+ tryDownload(attempt + 1);
|
|
|
+ }, 1000 * attempt);
|
|
|
+ } else {
|
|
|
+ reject(new Error(`下载失败,状态码: ${res.statusCode}`));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error(`图片下载失败 (第${attempt}次): ${src}`, err);
|
|
|
+ if (attempt < retryCount) {
|
|
|
+ setTimeout(() => {
|
|
|
+ tryDownload(attempt + 1);
|
|
|
+ }, 1000 * attempt);
|
|
|
+ } else {
|
|
|
+ console.error(`图片下载最终失败: ${src}`);
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+ tryDownload();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 绘制海报
|
|
|
+ drawPoster(data) {
|
|
|
+ return new Promise(async (resolve, reject) => {
|
|
|
+ try {
|
|
|
+ // 基础参数
|
|
|
+ const w = uni.upx2px(690)
|
|
|
+ const top = uni.upx2px(0)
|
|
|
+ const r = uni.upx2px(24)
|
|
|
+
|
|
|
+ // 计算内容布局参数
|
|
|
+ const watermarkArr = data.treeObj.watermarkArr || [];
|
|
|
+ const lineHeight = uni.upx2px(40); // 水印文字行间距
|
|
|
+ const startY = uni.upx2px(870); // 水印文字起始Y坐标
|
|
|
+ const lastWatermarkY = startY + ((watermarkArr.length - 1) * lineHeight);
|
|
|
+
|
|
|
+ const logoHeight = uni.upx2px(80);
|
|
|
+ const logoSpacing = uni.upx2px(26);
|
|
|
+ const logoY = lastWatermarkY - logoHeight - logoSpacing;
|
|
|
+ const brandTextY = logoY + logoHeight + logoSpacing;
|
|
|
+
|
|
|
+ // 使用固定高度,确保有足够空间显示所有内容
|
|
|
+ // 计算canvas高度(根据内容自适应)
|
|
|
+ const bottomMargin = uni.upx2px(20); // 底部边距
|
|
|
+ const h = brandTextY + uni.upx2px(30) + bottomMargin; // 30rpx是文字高度估算
|
|
|
+
|
|
|
+
|
|
|
+ // 下载所有图片到本地
|
|
|
+ const imagePromises = [];
|
|
|
+ const downloadedImages = {};
|
|
|
+
|
|
|
+ // 下载二维码图片
|
|
|
+ if (data.treeObj.qrCodeUrl) {
|
|
|
+ imagePromises.push(
|
|
|
+ this.downloadImage(data.treeObj.qrCodeUrl).then(localPath => {
|
|
|
+ downloadedImages.qrCodeUrl = localPath;
|
|
|
+ })
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 下载树图片
|
|
|
+ if (data.treeObj.posterUrl) {
|
|
|
+ imagePromises.push(
|
|
|
+ this.downloadImage(data.treeObj.posterUrl).then(localPath => {
|
|
|
+ downloadedImages.posterUrl = localPath;
|
|
|
+ })
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 下载树牌背景图片
|
|
|
+ const treeNameBgUrl = 'https://birdseye-img.sysuimars.com/youwei-uniapp/img/treePage/tag-bg.png';
|
|
|
+ imagePromises.push(
|
|
|
+ this.downloadImage(treeNameBgUrl).then(localPath => {
|
|
|
+ downloadedImages.treeNameBgUrl = localPath;
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ // 下载logo图片
|
|
|
+ const logoUrl = 'https://birdseye-img.sysuimars.com/youwei-uniapp/img/treePage/logo.png';
|
|
|
+ imagePromises.push(
|
|
|
+ this.downloadImage(logoUrl).then(localPath => {
|
|
|
+ downloadedImages.logoUrl = localPath;
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ // 等待所有图片下载完成(带容错处理)
|
|
|
+ try {
|
|
|
+ await Promise.all(imagePromises);
|
|
|
+ } catch (error) {
|
|
|
+ // 继续执行,让canvas尝试绘制可用的图片
|
|
|
+ }
|
|
|
+
|
|
|
+ const ctx = uni.createCanvasContext(this.canvasId);
|
|
|
+
|
|
|
+ if (!ctx) {
|
|
|
+ reject(new Error('canvas context创建失败'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清空canvas
|
|
|
+ ctx.clearRect(0, 0, w, h);
|
|
|
+
|
|
|
+ // 绘制白色背景
|
|
|
+ ctx.setFillStyle('rgba(255, 255, 255, 0)')
|
|
|
+ ctx.fillRect(0, 0, w, h);
|
|
|
+ this.drawRoundedRect(ctx, 0, top, w, h, r, '#ffffff');
|
|
|
+
|
|
|
+ // 绘制年份
|
|
|
+ ctx.setFillStyle('#000000')
|
|
|
+ ctx.setTextAlign('left') // 设置文字对齐方式
|
|
|
+ ctx.setTextBaseline('top') // 设置文字基线
|
|
|
+ ctx.font = `bold ${uni.upx2px(36)}px "SweiSpringCJKtc", Arial, sans-serif` // 设置字体样式:粗体 + 自定义字体
|
|
|
+ ctx.fillText(data.treeObj.year, uni.upx2px(26), uni.upx2px(34))
|
|
|
+
|
|
|
+ // 绘制月份
|
|
|
+ ctx.font = `bold ${uni.upx2px(172)}px "SweiSpringCJKtc", Arial, sans-serif` // 设置字体样式:粗体 + 自定义字体,172rpx大小
|
|
|
+ ctx.fillText(data.treeObj.monthNumber, uni.upx2px(26), uni.upx2px(76))
|
|
|
+
|
|
|
+ // 绘制斜线
|
|
|
+ ctx.setLineWidth(uni.upx2px(4)) // 设置线条宽度
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(uni.upx2px(160), uni.upx2px(180)) // 斜线起点(右上角)
|
|
|
+ ctx.lineTo(uni.upx2px(140), uni.upx2px(220)) // 斜线终点(左下角)
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ // 绘制日
|
|
|
+ ctx.font = `bold ${uni.upx2px(48)}px "SweiSpringCJKtc", Arial, sans-serif` // 设置字体样式:粗体 + 自定义字体,172rpx大小
|
|
|
+ ctx.fillText(data.treeObj.day, uni.upx2px(162), uni.upx2px(180))
|
|
|
+
|
|
|
+ // 绘制二维码图片
|
|
|
+ if (downloadedImages.qrCodeUrl) {
|
|
|
+ try {
|
|
|
+ ctx.drawImage(downloadedImages.qrCodeUrl, uni.upx2px(w - -190), uni.upx2px(34), uni.upx2px(130), uni.upx2px(142));
|
|
|
+ } catch (error) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加品种
|
|
|
+ ctx.setFontSize(uni.upx2px(24))
|
|
|
+ ctx.fillText(data.treeObj.pz, uni.upx2px(w - -218), uni.upx2px(190))
|
|
|
+
|
|
|
+ // 绘制树龄
|
|
|
+ ctx.setFontSize(uni.upx2px(24))
|
|
|
+ ctx.fillText(`${data.treeObj.age}年 ${data.treeObj.age > 9 ? "老树" : "树龄"}`, uni.upx2px(w - -104), uni.upx2px(190))
|
|
|
+
|
|
|
+ // 绘制气候适宜
|
|
|
+ ctx.setFontSize(uni.upx2px(24))
|
|
|
+ ctx.fillText(data.treeObj.phenology, uni.upx2px(w - -32), uni.upx2px(230))
|
|
|
+
|
|
|
+ // 绘制采摘方式
|
|
|
+ ctx.setFontSize(uni.upx2px(24))
|
|
|
+ ctx.fillText(`气候适宜-${data.treeObj.howTxt || "果园采摘"}`, uni.upx2px(w - -114), uni.upx2px(230))
|
|
|
+
|
|
|
+ // 绘制树图片(带圆角)
|
|
|
+ const imgX = uni.upx2px(26);
|
|
|
+ const imgY = uni.upx2px(280);
|
|
|
+ const imgW = uni.upx2px(640);
|
|
|
+ const imgH = uni.upx2px(480);
|
|
|
+ const radius = uni.upx2px(10);
|
|
|
+
|
|
|
+ // 创建圆角路径
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.moveTo(imgX + radius, imgY);
|
|
|
+ ctx.lineTo(imgX + imgW - radius, imgY);
|
|
|
+ ctx.arc(imgX + imgW - radius, imgY + radius, radius, -Math.PI/2, 0);
|
|
|
+ ctx.lineTo(imgX + imgW, imgY + imgH - radius);
|
|
|
+ ctx.arc(imgX + imgW - radius, imgY + imgH - radius, radius, 0, Math.PI/2);
|
|
|
+ ctx.lineTo(imgX + radius, imgY + imgH);
|
|
|
+ ctx.arc(imgX + radius, imgY + imgH - radius, radius, Math.PI/2, Math.PI);
|
|
|
+ ctx.lineTo(imgX, imgY + radius);
|
|
|
+ ctx.arc(imgX + radius, imgY + radius, radius, Math.PI, -Math.PI/2);
|
|
|
+ ctx.closePath();
|
|
|
+
|
|
|
+ // 裁剪为圆角区域
|
|
|
+ ctx.save();
|
|
|
+ ctx.clip();
|
|
|
+
|
|
|
+ // 绘制图片
|
|
|
+ if (downloadedImages.posterUrl) {
|
|
|
+ try {
|
|
|
+ ctx.drawImage(downloadedImages.posterUrl, imgX, imgY, imgW, imgH);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('树图片绘制失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 恢复裁剪
|
|
|
+ ctx.restore();
|
|
|
+
|
|
|
+ // 绘制图片文字背景和边框(圆角)
|
|
|
+ const textX = uni.upx2px(w - -104);
|
|
|
+ const textY = uni.upx2px(316);
|
|
|
+ const textWidth = uni.upx2px(180); // 背景宽度
|
|
|
+ const textHeight = uni.upx2px(18); // 背景高度
|
|
|
+ const padding = uni.upx2px(16); // 内边距
|
|
|
+ const textRadius = uni.upx2px(30); // 圆角半径
|
|
|
+
|
|
|
+ // 绘制黑色半透明背景(圆角)
|
|
|
+ ctx.setFillStyle('rgba(0, 0, 0, 0.6)');
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.moveTo(textX - padding + textRadius, textY - padding);
|
|
|
+ ctx.lineTo(textX - padding + textWidth + padding * 2 - textRadius, textY - padding);
|
|
|
+ ctx.arc(textX - padding + textWidth + padding * 2 - textRadius, textY - padding + textRadius, textRadius, -Math.PI/2, 0);
|
|
|
+ ctx.lineTo(textX - padding + textWidth + padding * 2, textY - padding + textHeight + padding * 2 - textRadius);
|
|
|
+ ctx.arc(textX - padding + textWidth + padding * 2 - textRadius, textY - padding + textHeight + padding * 2 - textRadius, textRadius, 0, Math.PI/2);
|
|
|
+ ctx.lineTo(textX - padding + textRadius, textY - padding + textHeight + padding * 2);
|
|
|
+ ctx.arc(textX - padding + textRadius, textY - padding + textHeight + padding * 2 - textRadius, textRadius, Math.PI/2, Math.PI);
|
|
|
+ ctx.lineTo(textX - padding, textY - padding + textRadius);
|
|
|
+ ctx.arc(textX - padding + textRadius, textY - padding + textRadius, textRadius, Math.PI, -Math.PI/2);
|
|
|
+ ctx.closePath();
|
|
|
+ ctx.fill();
|
|
|
+
|
|
|
+ // 绘制白色边框(圆角)
|
|
|
+ ctx.setStrokeStyle('rgba(255, 255, 255, 0.39)');
|
|
|
+ ctx.setLineWidth(uni.upx2px(2));
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.moveTo(textX - padding + textRadius, textY - padding);
|
|
|
+ ctx.lineTo(textX - padding + textWidth + padding * 2 - textRadius, textY - padding);
|
|
|
+ ctx.arc(textX - padding + textWidth + padding * 2 - textRadius, textY - padding + textRadius, textRadius, -Math.PI/2, 0);
|
|
|
+ ctx.lineTo(textX - padding + textWidth + padding * 2, textY - padding + textHeight + padding * 2 - textRadius);
|
|
|
+ ctx.arc(textX - padding + textWidth + padding * 2 - textRadius, textY - padding + textHeight + padding * 2 - textRadius, textRadius, 0, Math.PI/2);
|
|
|
+ ctx.lineTo(textX - padding + textRadius, textY - padding + textHeight + padding * 2);
|
|
|
+ ctx.arc(textX - padding + textRadius, textY - padding + textHeight + padding * 2 - textRadius, textRadius, Math.PI/2, Math.PI);
|
|
|
+ ctx.lineTo(textX - padding, textY - padding + textRadius);
|
|
|
+ ctx.arc(textX - padding + textRadius, textY - padding + textRadius, textRadius, Math.PI, -Math.PI/2);
|
|
|
+ ctx.closePath();
|
|
|
+ ctx.stroke();
|
|
|
+
|
|
|
+ // 绘制文字
|
|
|
+ ctx.setFillStyle('#ffffff');
|
|
|
+ ctx.setTextAlign('center');
|
|
|
+ ctx.setTextBaseline('middle');
|
|
|
+ // 重置字体为默认字体
|
|
|
+ ctx.font = `bold ${uni.upx2px(20)}px Arial, sans-serif`;
|
|
|
+ ctx.fillText(`${data.treeObj.pz}-${data.treeObj.countyName}`, textX + textWidth / 2, textY + textHeight / 2);
|
|
|
+
|
|
|
+ // 绘制树牌文字背景
|
|
|
+ if (downloadedImages.treeNameBgUrl) {
|
|
|
+ try {
|
|
|
+ ctx.drawImage(downloadedImages.treeNameBgUrl, uni.upx2px(40), uni.upx2px(486), uni.upx2px(276), uni.upx2px(274));
|
|
|
+ } catch (error) {
|
|
|
+ console.error('树牌背景图片绘制失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 绘制树牌文字
|
|
|
+ ctx.setFillStyle('#ffffff');
|
|
|
+ ctx.font = `bold ${uni.upx2px(36)}px "jiangxizhuokai", Arial, sans-serif`
|
|
|
+ ctx.fillText(`【${data.treeName}】`, uni.upx2px(178), uni.upx2px(565))
|
|
|
+
|
|
|
+ ctx.font = `bold ${uni.upx2px(18)}px "jiangxizhuokai", Arial, sans-serif`
|
|
|
+ ctx.fillText(`${data.userInfo.nickname || data.userInfo.name} ${data.treeObj.year}.${data.treeObj.monthNumber >= 10 ? data.treeObj.monthNumber : "0" + data.treeObj.monthNumber}.${data.treeObj.day}`, uni.upx2px(178), uni.upx2px(610))
|
|
|
+
|
|
|
+ // 绘制横线(在水印文字上面)
|
|
|
+ ctx.setStrokeStyle('#000000'); // 设置线条颜色为黑色
|
|
|
+ ctx.setLineWidth(uni.upx2px(2));
|
|
|
+
|
|
|
+ // 计算第一行水印文字的长度
|
|
|
+ const firstWatermarkText = data.treeObj.watermarkArr[0] || '';
|
|
|
+ ctx.font = `bold ${uni.upx2px(24)}px "SweiSpringCJKtc", Arial, sans-serif`;
|
|
|
+ const textMetrics = ctx.measureText(firstWatermarkText);
|
|
|
+ const watermarkTextWidth = textMetrics.width;
|
|
|
+
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.moveTo(uni.upx2px(36), uni.upx2px(820)); // 横线起点,左边距离36rpx
|
|
|
+ ctx.lineTo(uni.upx2px(36) + watermarkTextWidth, uni.upx2px(820)); // 横线终点,根据文字长度
|
|
|
+ ctx.stroke();
|
|
|
+
|
|
|
+ // 绘制水印文字(根据数组长度动态生成)
|
|
|
+ ctx.setFillStyle('#000000');
|
|
|
+ ctx.font = `bold ${uni.upx2px(24)}px "SweiSpringCJKtc", Arial, sans-serif`
|
|
|
+ ctx.setTextAlign('left'); // 设置左对齐,确保第一个字在指定位置
|
|
|
+
|
|
|
+ watermarkArr.forEach((text, index) => {
|
|
|
+ const y = startY + (index * lineHeight);
|
|
|
+ ctx.fillText(text, uni.upx2px(36), y);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 绘制logo(与最后一条水印文字对齐)
|
|
|
+ if (downloadedImages.logoUrl) {
|
|
|
+ try {
|
|
|
+ ctx.drawImage(downloadedImages.logoUrl, uni.upx2px(w - -230), logoY, uni.upx2px(76), logoHeight);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('logo图片绘制失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 绘制飞鸟有味(与logo保持间距)
|
|
|
+ ctx.setFillStyle('#000000');
|
|
|
+ ctx.font = `bold ${uni.upx2px(22)}px "SweiSpringCJKtc", Arial, sans-serif`
|
|
|
+ ctx.fillText(`飞鸟有味`, uni.upx2px(w - -225), brandTextY);
|
|
|
+
|
|
|
+ ctx.draw(false, () => {
|
|
|
+ // 等待canvas渲染完成
|
|
|
+ setTimeout(() => {
|
|
|
+ // 额外等待时间确保图片完全渲染
|
|
|
+ setTimeout(() => {
|
|
|
+ resolve();
|
|
|
+ }, 1000);
|
|
|
+ }, 2000); // 增加等待时间确保图片渲染完成
|
|
|
+ });
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('canvas绘制出错:', error);
|
|
|
+ reject(error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 绘制圆角矩形
|
|
|
+ drawRoundedRect(ctx, x, y, w, h, r, color) {
|
|
|
+ ctx.setFillStyle(color);
|
|
|
+ ctx.beginPath();
|
|
|
+ // 左上角
|
|
|
+ ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)
|
|
|
+ // 右上角
|
|
|
+ ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2)
|
|
|
+ // 右下角
|
|
|
+ ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5)
|
|
|
+ // 左下角
|
|
|
+ ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI)
|
|
|
+ ctx.closePath()
|
|
|
+ ctx.fill()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存到相册
|
|
|
+ saveToAlbum() {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ uni.showLoading({
|
|
|
+ title: '正在生成图片...',
|
|
|
+ mask: true
|
|
|
+ });
|
|
|
+
|
|
|
+ // 先检查canvas是否存在
|
|
|
+ const query = uni.createSelectorQuery();
|
|
|
+ query.select(`#${this.canvasId}`).boundingClientRect((rect) => {
|
|
|
+ if (!rect) {
|
|
|
+ console.error('Canvas元素不存在');
|
|
|
+ uni.hideLoading();
|
|
|
+ uni.showToast({
|
|
|
+ title: 'Canvas元素不存在',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ reject(new Error('Canvas元素不存在'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ uni.canvasToTempFilePath({
|
|
|
+ canvasId: this.canvasId,
|
|
|
+ width: 690,
|
|
|
+ height: 1140, // 固定高度1140rpx
|
|
|
+ destWidth: 690,
|
|
|
+ destHeight: 1140, // 固定高度1140rpx
|
|
|
+ fileType: 'png',
|
|
|
+ success: (res) => {
|
|
|
+ uni.hideLoading();
|
|
|
+
|
|
|
+ uni.showLoading({
|
|
|
+ title: '正在保存...',
|
|
|
+ mask: true
|
|
|
+ });
|
|
|
+
|
|
|
+ uni.saveImageToPhotosAlbum({
|
|
|
+ filePath: res.tempFilePath,
|
|
|
+ success: () => {
|
|
|
+ uni.hideLoading();
|
|
|
+ uni.showToast({
|
|
|
+ title: '保存成功',
|
|
|
+ icon: 'success',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ resolve(true);
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('保存到相册失败:', err);
|
|
|
+ uni.hideLoading();
|
|
|
+
|
|
|
+ if (err.errMsg && err.errMsg.includes('auth deny')) {
|
|
|
+ uni.showModal({
|
|
|
+ title: '权限提示',
|
|
|
+ content: '需要相册权限才能保存图片,请前往设置开启',
|
|
|
+ confirmText: '去设置',
|
|
|
+ success: (modalRes) => {
|
|
|
+ if (modalRes.confirm) {
|
|
|
+ uni.openSetting();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ uni.showToast({
|
|
|
+ title: '保存失败: ' + (err.errMsg || '未知错误'),
|
|
|
+ icon: 'none',
|
|
|
+ duration: 3000
|
|
|
+ });
|
|
|
+ }
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('canvas转图片失败:', err);
|
|
|
+ uni.hideLoading();
|
|
|
+ uni.showToast({
|
|
|
+ title: '图片生成失败: ' + (err.errMsg || '未知错误'),
|
|
|
+ icon: 'none',
|
|
|
+ duration: 3000
|
|
|
+ });
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }).exec();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 完整的保存流程
|
|
|
+ async generateAndSave(data) {
|
|
|
+ try {
|
|
|
+
|
|
|
+ // 检查数据完整性
|
|
|
+ if (!data || !data.treeObj) {
|
|
|
+ throw new Error('数据不完整');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查相册权限
|
|
|
+ const authResult = await this.checkPhotoAlbumAuth();
|
|
|
+ if (!authResult) {
|
|
|
+ throw new Error('没有相册权限');
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.drawPoster(data);
|
|
|
+
|
|
|
+ await this.saveToAlbum();
|
|
|
+
|
|
|
+ return true;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('海报生成和保存失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: error.message || '保存失败',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查相册权限
|
|
|
+ checkPhotoAlbumAuth() {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ uni.getSetting({
|
|
|
+ success: (res) => {
|
|
|
+ if (res.authSetting['scope.writePhotosAlbum'] === false) {
|
|
|
+ // 用户之前拒绝了权限,引导用户开启
|
|
|
+ uni.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: '需要相册权限才能保存图片,是否前往设置?',
|
|
|
+ success: (modalRes) => {
|
|
|
+ if (modalRes.confirm) {
|
|
|
+ uni.openSetting({
|
|
|
+ success: (settingRes) => {
|
|
|
+ if (settingRes.authSetting['scope.writePhotosAlbum']) {
|
|
|
+ resolve(true);
|
|
|
+ } else {
|
|
|
+ resolve(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ resolve(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 请求权限
|
|
|
+ uni.authorize({
|
|
|
+ scope: 'scope.writePhotosAlbum',
|
|
|
+ success: () => {
|
|
|
+ resolve(true);
|
|
|
+ },
|
|
|
+ fail: () => {
|
|
|
+ uni.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: '需要相册权限才能保存图片,是否前往设置?',
|
|
|
+ success: (modalRes) => {
|
|
|
+ if (modalRes.confirm) {
|
|
|
+ uni.openSetting({
|
|
|
+ success: (settingRes) => {
|
|
|
+ if (settingRes.authSetting['scope.writePhotosAlbum']) {
|
|
|
+ resolve(true);
|
|
|
+ } else {
|
|
|
+ resolve(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ resolve(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: () => {
|
|
|
+ resolve(false);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|