利用web-view在vue中合成海报,在小程序展示

90 阅读8分钟
     当时我们得需求是想把我们整个小程序里面得活动页面动态得绘制在canvas上合成海报,
   
   然后把个图既可以下载在相册里面,又可以通过按钮去分享。

1.逻辑分析

当时拿到这样的需求,我和我们后端一起想了个方法,然后交互的形式和产品沟通了之后实现了最后得效果。

这个效果可以在 微信小程序,搜索 -- 享买到家,下拉负一屏,点击分享可以看到。

     我们构思得思路是:在点击按钮分享----准备数据存到接口----跳转一个完整得页面这个页面
     
是利用web-view嵌套得vue页面-----这个页面写一个一摸一样得页面并且渲染出来----利用这个

页面直接做到页面转canvas----把画好的图片通过file下载下来保存到后端数据库----跳转小程

序页面----拿出来之前得图片渲染并且分享



这里要说得是,后端的需要把页面部署到服务器上,而且小程序里面是需要配置得,不然通不了。还有接口也要注意

!!还有最后很重要得一点,恶心了我们很久,接口拿到得渲染得图片记得同源,不然会污染画布,就会画不出来

难点分析

  • 小程序那边得配置,因为要用的接口还有跳转页面得许可

  • vue页面html2canvas那边得转化,小心canvas污染,图片得问题要注意。还有就是清晰比例不要调太高了

  • 这个调试起来也很麻烦,看不到效果会。

  • 画完的canvas一定要转成file传给接口才可以用

  • 跳回小程序一定要安装npm wx

2.小程序代码

a.点击按钮分享

页面

<view class="one-btn-box" src="https://thirdpartimg.51ishare.com/%E7%BB%84%202011@2x.png"
 bindtap="shareActivity">
    <image src="https://thirdpartimg.51ishare.com/2022551438.png" class="btn-icon"></image>
      <text class="vtb-text">分享活动</text>
 </view>

逻辑

b.准备接口数据,然后跳转一个新的页面

// 分享活动商品
        shareActivity(e) {
            console.log('合成')
            wx.showLoading({
                title: '跳转合成中...',
            })
            let id = new Date().getTime() + wx.getStorageSync('storeId') + Math.floor(Math.random() * (9 - 0))
            wx.setStorageSync('htmlId', id)
            request.authRequest({
                url: config.api.host + '/test2222/saveTemp/' + id,
                method: "POST",
                data: {
                    userAuth: wx.getStorageSync('userAuth'),
                    shopInfo: wx.getStorageSync('storeInfo'),
                    bannerInfo: this.data.showList ? this.data.showList[0] : null,
                    activityGoods: this.data.specialOffers ? this.data.specialOffers[0] : null,
                    activetyListTwo: this.data.activetyList ? this.data.activetyList : null,
                    qrImage: this.data.qrImage ? this.data.qrImage : null
                }
            }).then(res => {
                if (res) {
                    wx.hideLoading()
                    wx.navigateTo({
                        url: '/package/pages/new/new?id=' + id,
                    })
                }
            })

        },

c.跳转得新页面用web-view去嵌套页面

  这里要注意一下web-view得使用注意事项,个人类型的小程序暂不支持使用,并且webview 指向网页
 的链接。可打开关联的公众号的文章,其它网页需登录[小程序管理后台]
 (https://mp.weixin.qq.com/)配置业务域名。

页面


<web-view src="{{src}}" bindmessage="message"> 
</web-view>

逻辑

this.setData({
            src: config.api.host+"/test2222/app?id=" + options.id
          
        })

3.vue页面代码

a.vue新页面得部署

这里要值得注意得是,要写一个一摸一样在小程序适配得页面,这样跳转过去不会奇怪,然后添加一个在动,用npm下载一个html2canvas工具来转换成canvas变成图片

html2canvas.hertzen.com/features/ 官网地址

不会写的可以去百度一下怎么用。

页面

 
<template>
  <div>
    <div id="target" class="container">
      <!--    头部店铺信息-->
      <div class="imageBack">
        <div class="box">
          <div class="head-img">
            <img :src="shopInfo.logo"/>
          </div>
          <div class="title" >
            <div class="title-text">{{shopInfo.name}}</div>
            <img src="https://thirdpartimg.51ishare.com/bq20225311405.png" class="img1" />
          </div>
          <div class="box1">
            <img src="https://thirdpartimg.51ishare.com/%E8%B7%AF%E5%BE%84%201530@2x(1).png" class="img2" />
            <span class="text1">距离您很近</span>
          </div>
<!--          <div class="juzhong marginl" >-->
<!--            <img src="https://thirdpartimg.51ishare.com/icon-1.png" class="img3"/>-->
<!--            <div class="text2" >收藏</div>-->
<!--          </div>-->
<!--          <div class="box3 juzhong">-->
<!--            <img src="https://thirdpartimg.51ishare.com/fx2022571453.png" class="img4"/>-->
<!--            <div class="text2" >分享</div>-->
<!--          </div>-->
        </div>
      </div>
      <!--    banner轮播图-->
      <div class="box4 juzhong" v-if="bannerInfo&&bannerInfo.length!==0" >
        <div class="box5">
          <div class="box6" >
            <div class="text3" >{{bannerInfo.name}}</div>
            <div class="text4" >到店特价</div>
            <div class="text5">
              <span> <span style="font-size: 24px" >{{bannerInfo.payPrice}}</span></span>
              <span>原价 {{bannerInfo.marketPrice}}元</span>
            </div>
          </div>
          <div class="box19" >
            <img :src="bannerInfo.coverImage" class="img5" />
          </div>
        </div>
      </div>
      <!--    到店优惠-->
      <div class="box77 juzhong" v-if="activityGoods&&activityGoods!==null"  >
        <div class="box7">
          <div class="box8" >
            <div >品牌商到店优惠活动</div>
            <div>
              <img src="https://thirdpartimg.51ishare.com/asdasdsad%E9%98%BF%E6%96%AF%E9%A1%BF.png" />
              <span>使用规则</span>
            </div>
          </div>
          <div class="box9 juzhong">
            <div>
              <div class="text6" >{{activityGoods.type=='TWO_WAY_SUBSIDY'?activityGoods.goodsName:activityGoods.name}}</div>
              <div class="text7">{{activityGoods.type=='SINGLE_PRODUCT_SUBSIDY'?'立减'+activityGoods.singleProductSubsidy.subsidy/100+'元':
                  activityGoods.type=='FULL_N_DEDUCTION'?'满'+activityGoods.fullDeduction.n+'件减'+activityGoods.fullDeduction.x/100+'元活动':
                      activityGoods.type=='TWO_WAY_SUBSIDY'?activityGoods.name:'第二份半价'}}</div>
              <div class="text8">距离活动结束还有 {{activityGoods.remainDays?activityGoods.remainDays:'计算中'}}天</div>
              <div class="text9">立即购买</div>
            </div>
            <img :src="activityGoods.img" class="img6"/>
<!--            <div class="box10" >-->
<!--              <img src="https://thirdpartimg.51ishare.com/jhsdjfbvsjkasjdbbxcbn.png"  />-->
<!--              <span>分享</span>-->
<!--            </div>-->
          </div>
        </div>
      </div>
      <!--    下面四方的活动-->
      <div :class="activityGoods&&activityGoods.length!==0?'box16 juzhong':'box16 juzhong duo'"  v-if="activetyListTwo&&activetyListTwo.length!==0">
        <!--      黄色title盒子-->
        <div class="box11" >
          <img src="https://thirdpartimg.51ishare.com/20224284657.png" />
          <span>品牌精选优惠</span>
          <img src="https://thirdpartimg.51ishare.com/20224281656.png"/>
        </div>
        <!--      盒子内容-->
        <div class="box12">
          <div class="box13" v-for="item in activetyListTwo" :key="item.index" >
            <div>
              <img :src="item.showImage?item.showImage:'https://thirdpartimg.51ishare.com/87ca098e0a2eea91a948f180bca4483.png'"/>
            </div>
            <div>{{item.name}}</div>
          </div>
        </div>
      </div>
      <!--    更多查看-->
      <div class="juzhong" :style="activetyListTwo&&activetyListTwo.length==0&&activityGoods&&activityGoods.length==0&&bannerInfo&&bannerInfo.length==0?'margin-top:-180px':activetyListTwo.length==0&&activityGoods.length==0?'margin-top:-180px':activetyListTwo&&activetyListTwo.length==0?'margin-top:16px':''" >
        <div class="box14">更多活动...  请扫码查看</div>
      </div>

      <!--    扫码-->
      <div class="box15 juzhong">
        <div class="box17">
          <img :src="qrImage"/>
          <span>长按图片识别二维码</span>
        </div>
      </div>
    </div>
    <div v-if="loadPrompt" class="mask" >
      <div class="box20" >
        <div class="box21" >
          <img src="https://thirdpartimg.51ishare.com/%E5%BF%83%E5%BF%83%E7%9B%B8%E5%8D%B0(1)20225271621.png"  class="img8"/>
          <img src="https://thirdpartimg.51ishare.com/%E9%B8%A1%E5%B0%BE%E9%85%92(1)20225271621.png"  class="img9"/>
          <img src="https://thirdpartimg.51ishare.com/%E5%8C%96%E5%A6%86(1)20225271621.png"  class="img10"/>
          <img src="https://thirdpartimg.51ishare.com/%E8%9D%B4%E8%9D%B6%E7%BB%93(1)20225271621.png"  class="img11"/>
          <img src="https://thirdpartimg.51ishare.com/%E8%9B%8B%E7%B3%95%E7%94%9C%E5%93%81(1)20225271621.png"  class="img12"/>
        </div>
        <div class="text10" >努力合成中...</div>
      </div>
    </div>
  </div>
</template>
 

逻辑

import html2canvas from 'html2canvas'
import axios from 'axios'
import wx from 'weixin-js-sdk'
export default {
  name: "HelloWorld",
  data() {
    return{
      showList:[],
      img:null,
      userAuth:null,
      shopInfo:[],
      bannerInfo:[],
      activityGoods:[],
      activetyListTwo:[],
      qrImage:null,
      loadPrompt:false
    }
  },
  methods: {
    base64ToFile(urlData, fileName){
      let arr = urlData.split(',');
      let mime = arr[0].match(/:(.*?);/)[1];
      let bytes = atob(arr[1]);
      let n = bytes.length
      let ia = new Uint8Array(n);
      while (n--) {
        ia[n] = bytes.charCodeAt(n);
      }
      return new File([ia], fileName, { type: mime });
    }
  },
  mounted() {
    this.loadPrompt=true
    let id = location.href.split('?')[1].split('=')[1]
    axios({
      method: 'GET',
      url: '/test2222/getTemp/' + id
    }).then(res => {
      let data =res.data.data
      this.userAuth=data.userAuth
      this.shopInfo=data.shopInfo
      this.bannerInfo=data.bannerInfo
      this.activityGoods =data.activityGoods
      this.activetyListTwo= data.activetyListTwo
      this.qrImage=data.qrImage
      const target = document.querySelector('#target')
      setTimeout(() => {
        html2canvas(target, {
          scale: 2.2,
          useCORS: true
        }).then(res => {
          document.body.appendChild(res);
          let img = res.toDataURL('image/png', 1)
          let file = this.base64ToFile(img, id + '.png')
          axios({
            method: 'post',
            url: '/customer/uploadImage',
            headers: { // 设置请求头
              Authorization : 'Bearer ' + this.userAuth,
              'Content-type': 'multipart/form-data'
            },
            data: {
              image: file
            }
          }).then(res => {
            const url = res.data.data
            axios({
              method: 'POST',
              url: '/test2222/saveTemp/' + id,
              data: {
                url: url
              }
            })
            .then(res => {
              if (res.data.data) {
                wx.miniProgram.navigateBack({
                  delta: 1
                })
              }
            })
          })
        })
      }, 1000)
    })
  }
}
 

样式

html body{
    width: 100%;
    height: 100%;
    padding: 0;
    margin: 0;
    background: #F7F7F7;
}
div{
    box-sizing: border-box;
}
.container{
    width: 100%;
    display: flex;
    flex: 1;
    overflow: hidden;
    flex-direction: column;
}
.imageBack{
    height: 294px;
    background-image: url("https://thirdpartimg.51ishare.com/jhsajhejnhdjk.png");
    width: 100%;
    background-position: 0 0;
    background-repeat: no-repeat;
    background-size: 100%;
    border-top: 1px solid #FF8304;
}
.box{
    display: flex;
    flex-direction: row;
    justify-items: center;
    margin-top: 37px;
    margin-left: calc(32px / 2);
    margin-right: calc(32px / 2);
}
.head-img{
    width: calc(98px / 2);
    height: calc(98px / 2);
    border-radius: 50%;
    margin-right: 12px;
    border: 1px solid #fff;
    overflow: hidden;
}
.head-img>img{
    width: 100%;
    height: 100%;
}
.title .title-text{
    font-size: 16px;
    font-weight: bold;
    color: #19223C;
}
.box1{
    margin-left: 12px;
    margin-right: 12px;
    margin-top: 12px;
}
.title .img1{
    display: inline-block;
    width: 70px;
    height: 19.5px;
    margin-top: 8px;
}
.img2{
    width: 9px;
    height: 11.5px;
}
.text1{
    font-size: 12px;
    color: #FFFFFF;
    margin-left: 4px;
}
.img3{
    width: 18px;
    height: 18px;
    margin-bottom: 4px;
}
.img4{
    width: 16px;
    height: 16px;
    margin-bottom: 6px;
}
.text2{
    font-size: 12px;
    color: #19223C;
}
.box3{
    margin-left: 10px;
}
.marginl{
    margin-left: 12px;
}
.juzhong{
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
.box4{
    height: calc(276px / 2);
    width: 100%;
}
.box5{
    height: 100%;
    width: 94%;
    border-radius: 8px;
    margin-top:-386px;
    background-image:url("https://thirdpartimg.51ishare.com/bg2022551419.png");
    background-size: 100% 100%;
    background-repeat: no-repeat;
    background-position: 0 0;
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
}
.box6{
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    margin-right: 53px;
}
.text3{
    font-size: 16px;
    color: #FFFFFF;
    font-weight: bold;
    text-shadow: 0px 4px 2px rgba(155, 64, 0, 0.3);
    width: 151px;
    margin-left: -20px;
}
.box19{
    border-radius: 50%;
    overflow: hidden;
    width: 100px;
    height: 100px;
    margin-right: -15px;
}
.img5{
    width: 100%;
    height: 100%;
}
.text4{
    font-size: 12px;
    color: #FF8304;
    width: 70px;
    height: 24px;
    background: #FFF2E8;
    border-radius: 11px;
    border: 1px solid #D45600;
    line-height: 24px;
    text-align: center;
    color: #FF8304;
    margin-top: 8px;
    margin-bottom: 4px;
    margin-left: -98px;
}
.text5{
    margin-left: -64px;
}
.text5 span:nth-child(1){
    font-size: 14px;
    font-weight: 400;
    color: #FFDD00;
    display: inline-block;
}
.text5 span:nth-child(2){
    display: inline-block;
    margin-left: 6px;
    font-size: 10px;
    font-weight: 400;
    color: #19223C;
}
.box77{
    width: 100%;
    margin-top: -177px;
}
.box7{
    width: 94%;
    height: 210px;
    border-radius: 8px;
    background: linear-gradient(180deg, #FFF2E8 0%, #FFFFFF 100%);
    padding: 17px 16px;
}
.box8{
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
}
.box8 div:nth-child(1){
    font-size: 16px;
    font-weight: bold;
    color: #19223C;
}
.box8 div:nth-child(2) {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
}
.box8 div:nth-child(2) img:nth-child(1){
    width: 12px;
    height: 12px;
}
.box8 div:nth-child(2) span:nth-child(2){
    font-size: 12px;
    font-weight: 400;
    color: #FF8304;
    margin-left: 4px;
}
.box9{
    width:100%;
    height: calc(100% - 28px) ;
    background-image: url("https://thirdpartimg.51ishare.com/%E7%BB%84%202049@2x%20(2).png");
    background-size: 100% 100%;
    background-repeat: no-repeat;
    background-position: 0 0;
    margin-top: 12px;
    position: relative;
    z-index: 1;
    flex-direction: row;
}

.box10{
     position: absolute;
     bottom: 0;
     right: 0;
     width: 48px;
     height: 20px;
     background: #FFF2E8;
     border-radius: 8px 0 8px 0;
     bottom: 0;
     right: 0;
     z-index: 50;
     padding: 0;
     display: flex;
     flex-direction: row;
     align-items: center;
     justify-content: center;
}
.box10 img:nth-child(1){
    width: 10px;
    height: 9px;
    margin-right: 4px;
}
.box10 span:nth-child(2){
    font-size: 9px;
    color: #FF8304;
}
.img6{
    width: 101px;
    height: 101px;
    margin-left: 20px;
    border-radius: 50%;
}
.text6{
    font-size: 14px;
    color: #19223C;
}
.text7{
    width: 159px;
    font-size: 16px;
    color: #19223C;
    font-weight: 600;
    margin-bottom: 4px;
    margin-top: 4px;
}
.text8{
    font-size: 11px;
    color: #19223C;
    margin-bottom: 8px;
}
.text9{
    width: 80px;
    height: 28px;
    background: #FF8304;
    border-radius: 19px;
    font-size: 12px;
    font-weight: 400;
    line-height: 28px;
    text-align: center;
    color: #FFFFFF;
}
.box11{
    width: 94%;
    position: relative;
    z-index: 20;
    background: #FFF5EB;
    padding: 8px 16px;
    border: 1px solid #FFFFFF; display: flex;
    flex-direction: row;
    justify-content: center;
}
.box11 img:nth-child(1){
    width: 16px;
    height: 3px;
    margin-top: 10px;

}
.box11 span:nth-child(2){
    font-size: 14px;
    color: #FF8304;
    font-weight: bold;
    margin-left: 28px;
    margin-right: 28px;
}
.box11 img:nth-child(3){
    width: 16px;
    height: 3px;
    margin-top: 10px;

}
.box12{
    width: 94%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    margin-top: 13px;
}
.box13{
    background-color: white;
    width:  165px;
    height: 195px;
    border-radius: 8px;
    margin-bottom: 12px;
}
.box13 div:nth-child(1){
    height: 130px;
    width: 100%;
    overflow: hidden;
    position: relative;
    border-radius: 8px 8px 0 0;
}
.box13 div:nth-child(1)>img{
    height: 180%;
    width: 100%;
    border-radius: 8px 8px 0 0;
}
.img7{
    height: 300px;
    width: 100%;
}
.box13 div:nth-child(2) {
    width: 94%;
    font-size: 14px;
    font-weight: 500;
    line-height: 20px;
    color: #333333;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    margin-left: 11px;
    margin-top: 15px;
}
.marinssd{
    margin-top: -250px;
}
.box14{
    width: 94%;
    height: 40px;
    background: #FFF2E8;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 400;
    color: #F27326;
    line-height: 40px;
    text-align: center;
}
.box15{
    width: 100%;
    height: 150px;
    margin-bottom: 16px;
}
.box17{
    background: #FFF;
    height: 100%;
    width: 94%;
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    border-radius: 8px;
    margin-top: 16px;
}
.box15 img:nth-child(1){
    width: 79px;
    height: 79px;
    margin-right: 32px;
}
.box15 span:nth-child(2){
    font-size: 14px;
    font-weight: 400;
    color: #666666;
}
.box16{
    margin-top: 16px;
    width: 100%;
}
.box18{
    width: 100%;
    height: 1800px;
}
.mask{
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.7);
    position: fixed;
    top: 0;
    left: 0;
    z-index:999;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
.box20{
    width: 160px;
    height: 160px;
    background: #666666;
    border-radius: 16px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 20px;
}
.img8{
    position: absolute;
    top: 0;
    left: -5px;
    width: 25px;
    height: 25px;
    animation: myBox 1s infinite 0s;
    animation-iteration-count: 1;
}
.img9{
    position: absolute;
    top: 0;
    left: 22px;
    width: 25px;
    height: 25px;
    animation: myBox 1s infinite 1s;
    animation-iteration-count: 1;
}
.img10{
    position: absolute;
    top: 0;
    left: 49px;
    width: 25px;
    height: 25px;
    animation: myBox 1s infinite 2s;
    animation-iteration-count: 1;
}
.img11{
    position: absolute;
    top: 0;
    left: 76px;
    width: 25px;
    height: 25px;
    animation: myBox 1s infinite 3s;
    animation-iteration-count: 1;
}
.duo{
    margin-top: -173px;
}
.img12{
    position: absolute;
    top: 0;
    left: 103px;
    width: 25px;
    height: 25px;
    animation: myBox 1s infinite 4s;
    animation-iteration-count: 1;
}
.box21{
    position: relative;
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 20px;
}
.text10{
    color: #FFB6C1;
    margin-top: 30px;
}
@keyframes myBox {
    0% {
        top: 0;
    }
    20% {
        top: -10px;
    }
    40% {
        top: -20px;
    }
    60% {
        top:-25px;
    }
    80% {
        top: -10px;
    }

    100% {
        top: 0;
    }
}