心如止水呀 發表於 2024-1-14 20:03:00

uni-app实现生成海报

<h3 id="示例图">示例图:</h3>
<p><img src="https://img2024.cnblogs.com/blog/1077417/202401/1077417-20240114200038184-1068294887.png"><br>
<img src="https://img2024.cnblogs.com/blog/1077417/202401/1077417-20240114200046993-195223719.png"></p>
<h3 id="文献参考">文献参考:</h3>
<p>参考文档:https://zhuanlan.zhihu.com/p/597629702<br>
uni-app官网:uni.createSelectorQuery() | uni-app官网<br>
微信小程序官网:canvas | 微信开放文档</p>
<h3 id="海报内容准备">海报内容准备:</h3>
<p>1、背景图片<br>
2、二维码<br>
3、用户头像,用户名称<br>
4、简短的描述文案</p>
<h3 id="ps">PS:</h3>
<p>1、若使用<code>&lt;canvas type="2d" id="myCanvas"&gt;&lt;/canvas&gt;</code> 则报错<code>canvasToTempFilePath:fail fail canvas is empty。</code><br>
2、若canvas不设置canvas-id,则报错<code>&lt;canvas&gt;: canvas-id attribute is undefined(env: Windows,mp,1.06.2308310; lib: 2.25.3)。</code><br>
3、若只设置canvas-id,则节点信息为null。<br>
<code>const query = uni.createSelectorQuery().in(this);   query.select('#canvas').boundingClientRect(data =&gt; {   console.log("节点信息" + JSON.stringify(data)); // null   }).exec();</code><br>
4、canvas绘制的图片不支持网络图片,因此使用网络需使用<code>uni.getImageInfo</code>获取图片信息。</p>
<h3 id="实现">实现</h3>
<h4 id="html页面">HTML页面</h4>
<pre><code>// 触发按钮
&lt;view data-event-opts="{{[['tap',[['handleClick']]]]}}" bindtap="__e"&gt;生成海报&lt;/view&gt;
// 海报容器
&lt;canvas canvas-id="canvass" id="canvas" style="width:100%; height: 1000rpx;" /&gt;
// 生成的海报图片
&lt;image src="{{imgAddRess}}" style="width:100%; height: 1000rpx;"&gt;&lt;/image&gt;
</code></pre>
<h4 id="js页面">JS页面</h4>
<pre><code>// 单网图
data: {
    return {
      imgAddRess: "",
      shareBg: "https://img2.baidu.com/it/u=3334525604,928778682&amp;fm=253&amp;app=138&amp;size=w931&amp;n=0&amp;f=JPEG&amp;fmt=auto?sec=1703955600&amp;t=c88ebba9f3d8aeee92c6afd219945f9a",
      memberCount: 100,
      userImage:"/static/img/code/kong-img.png",
      userName: "快乐星球",
      shareQrImageUrl: "/static/img/code/kong-img.png"
    }
}
// 生成海报
handleClick() {
    const _this = this;
    // 因shareBg图片为线上图片,需做转换uni.getImageInfo({ src: this.shareBg })
    // 单个图片处理
    uni.getImageInfo({ src: this.shareBg }).then(res =&gt; {
    this.shareBg = res.path; // 获取转换后的图片路径
    this.createdCanvas().then(()=&gt;{
      // 绘制需要延迟,不然第一次加载为空白
      setTimeout(() =&gt; {
      // 将生成的canvas图片,转为真实图片-地址
      uni.canvasToTempFilePath({
      x: 0,
      y: 0,
      canvasId: "canvass",
      success: res =&gt; {
          _this.imgAddRess =res.tempFilePath;
          console.log('真实图片&gt;&gt;&gt;&gt;&gt;&gt;', res);
      },
      fail: (err) =&gt; { console.log('error', err) }
      },this);
      }, 500);
    });
});
},
handleClick2() {
    const _this = this;
    // 多张图片处理
    const Images = ;
    Promise.all(Images.map(img =&gt; uni.getImageInfo({ src: img }))).then(
      imageInfos =&gt; {
      console.log('Images then&gt;&gt;&gt;&gt;&gt;&gt;', imageInfos);
      this.shareBg = imageInfos.path;
      this.shareQrImageUrl = imageInfos.path;
      this.userImage = imageInfos.path;

      //根据参数开始绘图
      this.createdCanvas().then(()=&gt;{
         // 绘制需要延迟,不然第一次加载为空白
      setTimeout(() =&gt; {
      // 将生成的canvas图片,转为真实图片-地址
      uni.canvasToTempFilePath({
      x: 0,
      y: 0,
      canvasId: "canvass",
      success: res =&gt; {
          _this.imgAddRess =res.tempFilePath;
          console.log('真实图片&gt;&gt;&gt;&gt;&gt;&gt;', res);
      },
      fail: (err) =&gt; { console.log('error', err) }
      },this);
      }, 500);
      })
    }
);
}
createdCanvas(){
    const _this = this;
    return new Promise((resolve, reject) =&gt; {
      //绘图上下文
      const ctx = uni.createCanvasContext('canvass', this);
      uni.createSelectorQuery()
            .in(this)
            .select('#canvas')
            .boundingClientRect(rect =&gt; {
                if(!rect) return
                var width = rect.width;
                var height = rect.height;
                // 背景图
                ctx.drawImage( _this.shareBg, 0, 0, width, height);

                // 文案:百位超能大神带你飞
                ctx.font = "bold 31px 'DINAlternate-Bold, DINAlternate'"
                ctx.setFillStyle('#ff9900');
                ctx.setTextAlign('left');
                ctx.fillText(_this.memberCount, Math.ceil(width*0.32), Math.ceil(height*0.103*2)+6);

                ctx.setFillStyle('#ff9900');
                ctx.setTextAlign('left');
                ctx.font = "16px 'PingFangSC-Medium, PingFang SC'"
                ctx.fillText('位超能大神带你飞',Math.ceil(width*0.32)+66, Math.ceil(height*0.103*2));

                // 二维码区背景背景
                ctx.beginPath();
                // r:8 ,
                // 起始点
                ctx.moveTo(12, height - 92 + 8);
                // 左上角
                ctx.arcTo(12, height - 92, 12 + 8, height - 92, 8);
                // 右上角
                ctx.arcTo(width - 12, height - 92, width - 12, height - 92 + 8, 8);
                // 右下角
                ctx.arcTo(width - 12, height - 12, width - 12 - 8,height - 12, 8);
                // 左下角
                ctx.arcTo(12, height - 12, 12, height - 12 - 8, 8);
                ctx.fillStyle = '#ffffff';
                ctx.closePath();
                ctx.fill();

                //用户头像+用户名
                let avatarurl_width = 50; //绘制的头像宽度
                let avatarurl_heigth = 50; //绘制的头像高度
                let avatarurl_x = 22; //绘制的头像在画布上的位置
                let avatarurl_y = height - 77; //绘制的头像在画布上的位置
                ctx.save(); // 保存绘图上下文
                ctx.beginPath(); // 开始创建一个路径
                ctx.arc(
                  avatarurl_width / 2 + avatarurl_x,
                  avatarurl_heigth / 2 + avatarurl_y,
                  avatarurl_width / 2,
                  0,
                  Math.PI * 2,
                  false
                ); // 画一个圆形裁剪区域
                ctx.clip(); // 裁剪
                ctx.drawImage(
                  this.userImage,
                  avatarurl_x,
                  avatarurl_y,
                  avatarurl_width,
                  avatarurl_heigth
                ); // //分享人头像-绘制图片x,y轴
                ctx.restore(); // 恢复之前保存的绘图上下文

                // 分享userName
                ctx.setFillStyle('#141414');
                ctx.setTextAlign('left');
                ctx.font = "bold 16px 'PingFangSC-Medium, PingFang SC'"
                let title = _this.userName;
                if(title.length&gt;14){
                  title =title.substr(0,14)+'...'
                }
                ctx.fillText(title, 80, height - 55)                ;

                // 分享描述
                ctx.setFillStyle('#666666');
                ctx.setTextAlign('center');
                ctx.font = "11px 'PingFangSC-Regular, PingFang SC'"
                ctx.fillText('欢迎来到快乐星球根据地', 140, height - 33);ht - 33);

                // 二维码图片
                ctx.drawImage(_this.shareQrImageUrl, width - 84, height - 84, 64, 64 );
                ctx.draw();
            }).exec()
          //获取节点
          resolve()
   })
}
</code></pre>
<h4 id="多网图">多网图</h4>
<pre><code>data: {
    return {
imgAddRess: "",
shareBg:"https://img0.baidu.com/it/u=2868999810,2001254139&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=500&amp;h=714",
shareQrImageUrl: "https://img1.baidu.com/it/u=206225810,1732683717&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=500&amp;h=500",
avater: "https://img2.baidu.com/it/u=1979438230,3745650916&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=500&amp;h=500"
    }
}
// 生成海报
handleSetImage() {
   const _this = this;
   const Images = ;
   let aa = Images.map(img =&gt; uni.getImageInfo({ src: img }));

   Promise.all(Images.map(img =&gt; uni.getImageInfo({ src: img }))).then(
   imageInfos =&gt; {
       console.log('Images then&gt;&gt;&gt;&gt;&gt;&gt;', imageInfos);
       this.shareBg = imageInfos.path;
       this.shareQrImageUrl = imageInfos.path;
       this.avater = imageInfos.path;

       //根据参数开始绘图
       this.createdCanvas().then(()=&gt;{
         // 绘制需要延迟,不然第一次加载为空白
         setTimeout(() =&gt; {
         wx.canvasToTempFilePath({
             x: 0,
             y: 0,
             canvasId: "mc",
             success: res =&gt; {
               _this.imgAddRess =res.tempFilePath;
               console.log('真实图片&gt;&gt;&gt;&gt;&gt;&gt;', res);
             },
             fail:(error)=&gt;{
               console.log('error',error)
               uni.showToast({
               title: error.errMsg,
               icon:'none',
               duration:2000,
               });
             }
         },this);
         }, 500);
       })
   }
   );
},
// 海报绘图
createdCanvas() {
   const _this = this;
   const query = wx.createSelectorQuery().in(this);
   query.select('#mc').boundingClientRect(data =&gt; {
   console.log("得到布局位置信息" + JSON.stringify(data));
   }).exec();

   return new Promise((resolve, reject) =&gt; {
   //绘图上下文
   const ctx = wx.createCanvasContext('mc', this);
   wx.createSelectorQuery()
   .in(this)
   .select('#mc')
   .boundingClientRect(rect =&gt; {
   console.log('rect111',rect)
   if(!rect) return
   var width = rect.width;
   var height = rect.height;
   let qrw = (width-200) / 2;
   let qrh = ((height - 200) / 2 + 80);
   // 背景图
   ctx.drawImage( _this.shareBg, 0, 0, width, height);

   // 头像
   ctx.drawImage(_this.avater, (width - 80) / 2 , 50 , 80, 80 );

   // 文案
   ctx.setFillStyle('#ffffff');
   ctx.setTextAlign('center');
   ctx.font = "bold 26px 'PingFangSC-Medium, PingFang SC'"
   ctx.fillText('超能小飞侠',180, 170, width);

   ctx.font = "bold 16px 'DINAlternate-Bold, DINAlternate'"
   ctx.setFillStyle('#000000');
   ctx.setTextAlign('center');
   ctx.fillText("诚邀您参与超能封神", 185, 205, width); // (文本,X坐标,Y坐标, 最大宽度)

   // 二维码:(图片,X坐标,Y坐标, 图宽,图高)
   ctx.drawImage(_this.shareQrImageUrl, qrw , qrh , 200, 200 );
   //二维码图片
   ctx.draw();
   }).exec()
   //获取节点
   resolve()
   })
},
</code></pre>
<h3 id="检测版本与自行安装">检测版本与自行安装</h3>
<pre><code>&lt;view @click="handleUpdate"&gt;检测新版本&lt;/view&gt;
const handleUpdate = () =&gt; {
        try {
                // 获取当前应用版本
                const currentVersion = plus.runtime.version;
                console.log("获取当前应用版本: ", currentVersion)
       
                // 请求服务器,获取最新版本信息
                const res = await uni.request({
               url: 'https://localhost:5566/check-update', // 查询最新版本接口
               method: 'GET',
               data: {
                        version: currentVersion
                }
       });
       
        // const serverData = res.data;
        // 假设服务器返回
        const serverData = {
                        update: true,
                        latest: '1.1.0',
                        fileName: "check_"+dayjs().format("YYYYMMDDHHmmss")+".apk",
                        url: 'https://app.liuyingyong.cn/build/download/8bef5430-8d5b-11f0-a322-2597c8f390f2',
                        note: '测试自行安装', force: false }
                if (serverData.update &amp;&amp; serverData.latest &gt; currentVersion) {
                        showUpdateDialog(serverData); // 显示更新弹窗
                }
        } catch (error) {
                console.error('检查更新失败:', error);
        }
}
function showUpdateDialog(updateInfo) {
        uni.showModal({
                title: `发现新版本 ${updateInfo.latest}`,
                content: updateInfo.note || '本次更新带来了全新的功能和体验',
                confirmText: '立即更新',
                cancelText: updateInfo.force ? null : '稍后再说', // 强制更新时可能不显示取消按钮
                success: (res) =&gt; {
                        if (res.confirm) {
                                downloadApk(updateInfo.url); // 开始下载APK
                        } else if (updateInfo.force) {
                                // 如果是强制更新,用户取消则退出应用
                                plus.runtime.quit();
                        }
                }
        });
}
function downloadApk(apkUrl) {
        uni.showLoading({
                title: '准备下载...',
                mask: true
        });

        // 创建下载任务
        const downloadTask = plus.downloader.createDownload(
                apkUrl, {
                        filename: '_doc/download/check_new.apk' // 文件保存路径,_doc 目录可读写
                },
                (download, status) =&gt; {
                        console.log("打印download:", download, status)
                        uni.hideLoading();
                        if (status === 200) {
                                uni.showToast({
                                        title: '下载完成,开始安装',
                                        icon: 'none'
                                });
                                installApk(download.filename); // 下载完成,开始安装
                        } else {
                                uni.showToast({
                                        title: '下载失败,请重试',
                                        icon: 'none'
                                });
                                console.error('Download failed, status:', status);
                        }
                }
        );

        // 监听下载进度变化
        downloadTask.addEventListener('statechanged', (task, status) =&gt; {
                if (task.downloadedSize &amp;&amp; task.totalSize) {
                        const percent = (task.downloadedSize / task.totalSize * 100).toFixed(0);
                        uni.showLoading({
                                title: `下载中 ${percent}%`,
                                mask: true
                        }); // 或更新自定义进度条组件
                }
        });

        downloadTask.start(); // 启动下载任务
}
function installApk(filePath) {
        plus.runtime.install(
                filePath, {
                        force: false // 是否强制安装
                },
                function() {
                        console.log('安装成功,重启应用');
                        plus.runtime.restart(); // 安装成功,重启应用
                },
                function(e) {
                        uni.showToast({
                                title: '安装失败',
                                icon: 'none'
                        });
                        console.error('安装失败:', e);
                }
        );
}
</code></pre><br><br>
来源:https://www.cnblogs.com/min77/p/17964116
頁: [1]
查看完整版本: uni-app实现生成海报