头图

Case 1

Implementation background:

  • The size of the poster is based on the size of the picture, the width is 100%, the height is scaled proportionally, and the QR code is covered on the picture
  • If the poster image is not set, use another image, add a bottom fixed image at the bottom, and a QR code, and overlay the log image in the upper left corner of the image
    difficulty:
  • Because it is an image rendered by canvas, but the size of the image is not fixed, that is, the size of the canvas is not fixed (solution: first obtain the size of the image externally, and pass it into this component)
  • h5 is rendered normally, but the WeChat applet image cannot be rendered (solution: WeChat does not support canvas to directly render network images, so it needs to be cached locally first)
 <template>
  <view class="poster-box" ref="posterBox" @click="hidePoster">
    <canvas
      :style="{
        width: posterOptions.poster.canvasWidth + 'px',
        height: posterOptions.poster.canvasHeight + 'px',
      }"
      canvas-id="myCanvas"
    ></canvas>
    <!-- #ifdef MP  -->
    <image
      :src="base64"
      class="poster-image"
      :draggable="false"
      show-menu-by-longpress="true"
    ></image>
    <!-- #endif -->
    <!-- #ifdef H5 -->
    <image class="poster-image" :src="base64" :draggable="false"></image>
    <!-- #endif -->
  </view>
</template>

<script>
import Qr from '@/utils/wxqrcode'
export default {
  name: 'poster_canvas',
  data() {
    return {
      base64: '', // 海报绘制成图片以便保存
    }
  },
  props: {
    //图片地址
    posterOptions: {
      type: Object,
      require: true,
    },
  },

  mounted() {
    this.$nextTick(() => {
      this.imgToCanvas()
    })
  },
  methods: {
    async imgToCanvas() {
      let that = this
      
      let canvasHeight = this.posterOptions.isHavePosterImg
        ? this.posterOptions.poster.canvasHeight
        : this.posterOptions.poster.canvasHeight -
          this.posterOptions.buttonOption.h
      // 渲染大图
      let ctx = uni.createCanvasContext('myCanvas', that)
      console.log(11111,this.posterOptions)
      ctx.drawImage(
        this.posterOptions.poster.url,
        0,
        0,
        this.posterOptions.poster.canvasWidth,
        canvasHeight
      )
      if (!this.posterOptions.isHavePosterImg) {
        //渲染底部的图片
        ctx.drawImage(
          this.posterOptions.buttonOption.bottomUrl,
          this.posterOptions.buttonOption.x,
          this.posterOptions.buttonOption.y,
          this.posterOptions.buttonOption.w,
          this.posterOptions.buttonOption.h
        )
      }

      // 渲染二维码
      if (this.posterOptions.QrOption.isWeChatMiniApp) {
        ctx.drawImage(
          this.posterOptions.QrOption.QrUrl,
          this.posterOptions.QrOption.x,
          this.posterOptions.QrOption.y,
          this.posterOptions.QrOption.w,
          this.posterOptions.QrOption.h
        )
      } else {
        //普通二维码
        let qrCodeImg = Qr.createQrCodeImg(this.posterOptions.QrOption.QrUrl, {
          size: parseInt(300),
        })
        ctx.drawImage(
          qrCodeImg,
          this.posterOptions.QrOption.x,
          this.posterOptions.QrOption.y,
          this.posterOptions.QrOption.w,
          this.posterOptions.QrOption.h
        )
      }
      if (!this.posterOptions.isHavePosterImg) {
        // 渲染log 和文字
        ctx.drawImage(
          this.posterOptions.logOption.logUrl,
          this.posterOptions.logOption.x,
          this.posterOptions.logOption.y,
          this.posterOptions.logOption.w,
          this.posterOptions.logOption.h
        )
        ctx.fillStyle = this.posterOptions.textOption.color
        ctx.setFontSize(22)
        ctx.font = 'bold arial'
        ctx.fillText(
          this.posterOptions.textOption.text,
          this.posterOptions.textOption.x,
          this.posterOptions.textOption.y
        )
      }

      ctx.save() //保存
      ctx.draw() //绘制
      // 不加延迟的话,base64有时候会赋予undefined
      // 把当前画布指定区域的内容导出生成指定大小的图片,并返回文件路径
      setTimeout(() => {
        uni.canvasToTempFilePath(
          {
            canvasId: "myCanvas",
            fileType: "jpg",
            width:that.posterOptions.poster.canvasWidth,
            height:that.posterOptions.poster.canvasHeight,
            destWidth:that.posterOptions.poster.canvasWidth,
            destHeight:that.posterOptions.poster.canvasHeight,
            success: function (res) {
              that.base64 = res.tempFilePath;
            },
            fail: function (error) {
              console.log(error, "错误");
            },
          },that
        );
      }, 500);
    },
    hidePoster() {
      // #ifdef H5
      this.$parent.$parent.hidePoster()
      // #endif
      // #ifndef H5
      this.$parent.hidePoster()
      // #endif
    },
  },
}
</script>

<style lang="scss" scoped>
.poster-box {
  position: fixed;
  top: 0;
  z-index: 999;
  background-color: rgba(0, 0, 0, 0.8);
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.poster-image {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1000;
  width: 100%;
  height: 100%;
  opacity: 0;
}

</style>

use

 <template>
  <view
    class="contentBgc"
    :style="{ backgroundColor: detailObj.backgroundColor || defaultBgc }"
  >
    <image
      class="width-100"
      :src="detailObj.activityDetailHeadImg"
      mode="widthFix"
    />
    <view v-for="item in detailObj.plateDtoList" :key="item.id">
      <TopicThree v-if="detailObj.currentTemplate == 3" :itemObj="item" />
      <TopicTwo v-if="detailObj.currentTemplate == 2" :itemObj="item" />
      <TopicOne v-if="detailObj.currentTemplate == 1" :itemObj="item" />
    </view>
    <generateaposter v-if="isGetOk" @pChangeType="showPoster" />
    <SharePoster v-if="posterShow" :posterOptions="posterOptions"></SharePoster>
  </view>
</template>

<script>
import TopicOne from './components/topicOne.vue'
import TopicTwo from './components/topicTwo.vue'
import TopicThree from './components/topicThree.vue'
import { getSpecialTopicData } from '@/api/specialTopic.js'
// #ifdef H5
import { getShopWxConfig, share } from '@/utils/wxShare.js'
// #endif
import mixinWxshare from '@/utils/wxShareTimeline.js' //微信分享朋友圈
import generateaposter from '@/components/gpage/generateaposter.vue'
import SharePoster from './components/sharePoster.vue'
import { cmsWxacodeGetwxacode } from '@/api/product'
export default {
  mixins: [mixinWxshare],
  components: {
    TopicOne,
    TopicTwo,
    TopicThree,
    generateaposter,
    SharePoster,
  },
  onLoad(options) {
    this.activityUniqueCode = options.activityUniqueCode
    this.initData()
  },
  data() {
    return {
      defaultBgc: '#fff', //默认背景色
      share: this.$utils.getImgUrl('/static/mcShopImage/share.png'), //分享
      detailObj: {},
      activityUniqueCode: '',
      posterShow: false,
      isGetOk: false,
      posterOptions: {
        isHavePosterImg: false, //用户是否上传了分享海报的图片
        activityName: '',
        textOption: {
          text: '',
          x: 0, // X轴坐标
          y: 20, // Y轴坐标
          color: '#9c6d06',
        },
        poster: {
          url:
            'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fci.xiaohongshu.com%2F0429f29e-85f3-5826-9608-946fc1ee103a%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fci.xiaohongshu.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664356680&t=349aa739d52cd748c50b3afd89da2ba2',
          canvasWidth: 0,
          canvasHeight: 0,
        },
        QrOption: {
          QrUrl: '', // 二维码链接
          x: 20, // 图片X轴坐标
          y: 20, // 图片Y轴坐标
          w: 90, // 图片宽度
          h: 90, // 图片高度
          isWeChatMiniApp: false, // 是否是微信小程序二维码 true是微信小程序, false 不是微信小程序
        },
        logOption: {
          //log
          logUrl:
            'https://jemsfile.mochouu.com/provider/91949676/20220831/1661926456418.png',
          x: 15, // 图片X轴坐标
          y: 15, // 图片Y轴坐标
          w: 120, // 图片宽度
          h: 45, // 图片高度
        },
        buttonOption: {
          //底部的图片
          bottomUrl:
            'https://jemsfile.mochouu.com/provider/91949676/20220831/1661930915954.png',
          x: 0, // 图片X轴坐标
          y: 20, // 图片Y轴坐标
          w: 100, // 图片宽度
          h: 100, // 图片高度
        },
      },
      screenWidth: 0,
    }
  },
  mounted() {},
  // #ifndef H5
  /* 微信小程序分享 */
  onShareAppMessage: function(res) {
    // console.log('分享的userCode:'+this.userInfo.userCode)
    let userInfo = this.$utils.getStorageSync('userInfo')
    let routes = getCurrentPages() // 获取当前打开过的页面路由数组
    let curParam =
      routes[routes.length - 1].options ||
      routes[routes.length - 1].$route.query //获取路由参数
    if (userInfo) {
      curParam.userCode = userInfo.userCode
    }
    let a = this.$utils.pageliks(curParam, routes)
    let obj = {
      title: this.detailObj.shareTitle || this.detailObj.shareContent || '',
      desc:this.detailObj.shareContent||'',
      path: a,
      imageUrl: this.detailObj.shareImg || this.detailObj.verticalImgUrl || '',
    }
    return obj
  },
  // #endif
  methods: {
    hidePoster() {
      this.posterShow = false
    },
    getWxCode() {
      uni.showLoading({
        title: '加载中',
      })
      let that = this
      this.isGetOk = false
      let routes = getCurrentPages() // 获取当前打开过的页面路由数组
      let curRoute = routes[routes.length - 1].route //获取当前页面路由
      let curParam = routes[routes.length - 1].options //获取路由参数
      let obj = {}
      obj.curRoute = curRoute
      obj.curParam = curParam
      // 拼接参数
      let str = ''
      for (let key in obj.curParam) {
        str += '&' + key + '=' + obj.curParam[key]
      }
      str = str ? '?' + str.substr(1, str.length) : ''
      let data = {
        page: '/' + obj.curRoute + str,
      }
      cmsWxacodeGetwxacode(data).then((res) => {
        uni.hideLoading()
        if (res.code === 1000) {
          this.downImg(res.data, that.posterOptions.QrOption, 'QrUrl')
          that.$set(that.posterOptions.QrOption, 'isWeChatMiniApp', true)
          that.isGetOk = true
        }
      })
    },
    // 显示分享海报
    showPoster() {
      this.posterShow = true
    },
    /* 任务分配 */
    assignTasks(data) {
      /* 设置头部的title */
      uni.setNavigationBarTitle({
        title: data.activityName,
      })
      // 初始化分享朋友圈
      // #ifndef H5
      this.initWxShareData(
        this.detailObj.shareTitle || this.detailObj.shareContent,
        this.detailObj.shareImg || this.detailObj.verticalImgUrl
      )
      // #endif
      // #ifdef H5
      switch (uni.getSystemInfoSync().platform) {
        case 'android':
          this.fx(1)
          break
        case 'ios':
          this.fx(2)
          break
      }
      // #endif
    },
    /* 初始化数据 */
    initData() {
      let that = this
      getSpecialTopicData(this.activityUniqueCode).then((res) => {
        if (res.code == 1000) {
          const { plateDtoList, ...rest } = res.data
          let filterData = plateDtoList.map((it) => {
            const { productJson, ...re } = it
            let objArr = JSON.parse(productJson)
            return {
              productJson: objArr||[],
              ...re,
            }
          })
          this.detailObj = { plateDtoList: filterData, ...rest }
          this.assignTasks(this.detailObj)

          // #ifdef MP
          uni.getSystemInfo({
            success: (res) => {
              that.screenWidth = res.windowWidth
            },
          })

          this.getWxCode()
          // #endif
          // #ifdef H5
          that.screenWidth = document.documentElement.clientWidth
          this.posterOptions.QrOption.QrUrl = window.location.href
          this.isGetOk = true
          // #endif
          // 判断是否设置了分享海报的图片
          let isHaveImg = this.detailObj.posterImg ? true : false
          if(!isHaveImg){
            this.downImg(that.posterOptions.logOption.logUrl, that.posterOptions.logOption, 'logUrl')
            this.downImg(that.posterOptions.buttonOption.bottomUrl, that.posterOptions.buttonOption, 'bottomUrl')
          }
          this.$set(this.posterOptions, 'isHavePosterImg', isHaveImg)
          this.$set(
            this.posterOptions.textOption,
            'text',
            this.detailObj.activityName
          )
          // 设置img图片和canvas大小
          let posterImg = this.detailObj.posterImg
            ? this.detailObj.posterImg
            : this.detailObj.activityDetailHeadImg

          // 设置大图的url
          this.downImg(posterImg, this.posterOptions.poster, 'url')

          uni.getImageInfo({
            src: posterImg,
            success(res) {
              let imgW = res.width //图片宽度
              let imgH = res.height //图片高度
              let per = imgW / that.screenWidth
              that.$set(
                that.posterOptions.poster,
                'canvasWidth',
                that.screenWidth
              )
              // 是否设置分享海报
              let canvasHeight = isHaveImg
                ? imgH / per
                : imgH / per + that.posterOptions.buttonOption.h
              that.$set(that.posterOptions.poster, 'canvasHeight', canvasHeight)
              // 设置二维码位置
              let codeX = isHaveImg ? that.screenWidth - 95 : 5
              that.$set(that.posterOptions.QrOption, 'x', codeX)
              let codeY = isHaveImg ? imgH / per - 95 : canvasHeight - 95
              that.$set(that.posterOptions.QrOption, 'y', codeY)
              if (!isHaveImg) {
                // 如果没有设置分享海报
                that.$set(
                  that.posterOptions.buttonOption,
                  'w',
                  that.screenWidth
                )
                that.$set(that.posterOptions.buttonOption, 'y', imgH / per)
                that.$set(that.posterOptions.textOption, 'x', codeX + 120)
                that.$set(that.posterOptions.textOption, 'y', codeY + 40)
              }
            },
          })
        }
      })
    },
    // 将网络图片缓存到本地
    downImg(img, obj, name) {
      let that = this
      uni.downloadFile({
        url: img,
        success: (res) => {
          that.$set(obj, name, res.tempFilePath)
        },
      })
    },
    // 分享方法
    fx(val) {
      let url = window.location.href
      // 获取usercode
      let urls
      let userInfo = this.$utils.getStorageSync('userInfo')
      let routes = getCurrentPages() // 获取当前打开过的页面路由数组
      let curParam =
        routes[routes.length - 1].options ||
        routes[routes.length - 1].$route.query //获取路由参数
      if (userInfo) {
        curParam.userCode = userInfo.userCode
      }
      let a = this.$utils.pageliks(curParam, routes)
      let b = window.location.origin
      if (userInfo) {
        urls = `${b}${a}`
      } else {
        urls = url
      }
      let data = {
        url: url,
      }
      let shares = {
        title: this.detailObj.shareTitle || this.$config.serviceProvider,
        link: urls,
        desc:this.detailObj.shareContent||'',
        imgUrl: this.detailObj.shareImg || '',
      }
      if (val == 1) {
        getShopWxConfig(data, shares, val)
      } else {
        share(shares)
      }
    },
  },
}
</script>

<style lang="scss" scoped>
.width-100 {
  width: 100%;
}
.contentBgc {
  min-height: 100vh;
  width: 100vw;
  padding-bottom: 20rpx;
}
</style>

another

 <template>
  <div class="poster-wrapper" @click="closePoster($event)">
    <div class="poster-content">
      <canvas
        canvas-id="qrcode"
        v-if="qrcode"
        :style="{ opacity: 0, position: 'absolute', top: '-1000px' }"
      ></canvas>
      <canvas
        canvas-id="poster"
        v-if="!iscomplete"
        :style="{
          width: cansWidth + 'px',
          height: cansHeight + 'px',
          opacity: 0,
        }"
      ></canvas>
      <image
        v-if="iscomplete"
        :style="{ width: cansWidth + 'px', height: cansHeight + 'px' }"
        :src="tempFilePath"
        @longpress="longpress"
      ></image>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      bgImg:
        'https://cdn.img.up678.com/ueditor/upload/image/20211130/1638258070231028289.png', //画布背景图片
      cansWidth: 288, // 画布宽度
      cansHeight: 410, // 画布高度
      projectImgWidth: 223, // 中间展示图片宽度
      projectImgHeight: 167, // 中间展示图片高度
      schoolImgWidth: 110, // 中间展示高校宽度
      schoolImgHeight: 110, // 中间展示高校高度
      qrShow: true, // 二维码canvas
      qrData: null, // 二维码数据
      tempFilePath: '', // 生成图路径
      iscomplete: false, // 是否生成图片
    }
  },
  created() {
    this.ctx = uni.createCanvasContext('poster', this)
  },
  methods: {
    closePoster(e) {
      if (e.target.id === e.currentTarget.id) {
        // 关闭
        this.$emit('close')
      }
    },
    // 绘制分享图片
    async drawerposter(name = '南京雨花台区', url, projectImg) {
      uni.showLoading({
        title: '加载中....',
        mask: true,
      })
      await this.createQrcode(url)
      //背景
      await this.drawWebImg({
        url: this.bgImg,
        x: 0,
        y: 0,
        width: this.cansWidth,
        height: this.cansHeight,
      })
      // 展示图
      await this.drawWebImg({
        url: projectImg,
        x: 33,
        y: 90,
        width: this.projectImgWidth,
        height: this.projectImgHeight,
      })

      await this.drawerText({
        text: name,
        x: 15,
        y: 285,
        color: '#241D4A',
        size: 15,
        bold: true,
        center: true,
        shadowObj: { x: '0', y: '4', z: '4', color: 'rgba(173,77,0,0.22)' },
      })
      // 二维码
      await this.drawQrcode()
      this.tempFilePath = await this.saveCans()
      this.iscomplete = true
      uni.hideLoading()
    },
    // 绘制分享学校
    async drawposterschool(name, url, shoolImg) {
      uni.showLoading({
        title: '加载中...',
        mask: true,
      })
      await this.createQrcode()
      // 背景
      await this.drawWebImg({
        url: this.bgImg,
        x: 0,
        y: 0,
        width: this.schoolImgWidth,
        height: this.schoolImgHeight,
      })
      await this.drawText({
        text: name,
        x: 15,
        y: 285,
        color: '#241D4A',
        size: 15,
        bold: true,
        center: true,
        shadowObj: { x: '0', y: '4', z: '4', color: 'rgba(173,77,0,0.22' },
      })
      // 二维码
      await this.drawQrcode()
      this.tempFilePath = await this.saveCans()
      this.iscomplete = true
      uni.hideLoading()
    },
    drawWebImg(conf) {
      return new Promise((resolve, reject) => {
        uni.downloadFile({
          url: conf.url,
          success: (res) => {
            this.ctx.drawImage(
              res.tempFilePath,
              conf.x,
              conf.y,
              conf.width ? conf.width : '',
              conf.height ? conf.height : ''
            )
            this.ctx.draw(true, () => {
              resolve()
            })
          },
          fail: (err) => {
            reject(err)
          },
        })
      })
    },
    drawSchoolImg(conf) {
      return new Promise((resolve,reject)=>{
          uni.downloadFile({
            url:conf.url,
            success:(res)=>{
              this.ctx.save()
              this.ctx.beginPath()
              this.ctx.arc(135,170,70,0,2*Math.PI)
              // this.ctx.setFillStyle('blue')
              // this.ctx.fill()
              this.ctx.clip()
              this.ctx.drawImage(res.tempFilePath,conf.x,conf.y,conf.width,conf.height)
              this.ctx.restore()
              this.ctx.draw(true,()=>{
                resolve()
              })
            },
            fail:err=>{
              reject(err)
            }
          })
      })
    },
    drawText(conf){
      return new Promise((resolve,reject)=>{
        this.ctx.restore()
        this.stx.setFillStyle(conf.color)
        if(conf.bold)this.ctx.font = `normal bold ${conf.size}px sans-serif`
        this.ctx.setFontSize(conf.size)
        let x = conf.x
        conf.text = this.fittingString(this.ctx,conf.text,280)
        if(conf.center){
          let len = this.ctx.measureText(conf.text)
          x = this.cansWidth/2 - len.width/2 +2
        }
        this.ctx.fillText(conf.text,x,conf.y)
        this.ctx.draw(true,()=>{
          this.ctx.save()
          resolve()
        })
      })
    },
    // 文本标题溢出隐藏处理
    fittingString(_ctx,str,maxWidth){
      let strWidth = _ctx.measureText(str).width;
      const ellipsis = "..."
      const ellipsisWidth = _ctx.measureText(ellipsis).width;
      if(strWidth<=maxWidth||maxWidth<=ellipsisWidth){
        return str
      }else{
        var len = str.length
        while(strWidth >= maxWidth-ellipsis&&len-->0){
          str = str.slice(0,len)
          strWidth = _ctx.measureText(str).width
        }
        return str + ellipsis
      }
    },
    // 生成二维码
    createQrcode(qrcodeUrl){
      const config = {host:window.location.origin}
      return new Promise((resolve,reject)=>{
        let url = `${config.host}${qrcodeUrl}`
        try{
           new qrCode({
            canvasId:'qrcode',
            usingComponents:true,
            context:this,
            text:url,
            size:130,
            cbResult:(res)=>{
              this.qrShow = false
              this.qrData = res
              resolve()
            }
          })
        }catch(err){
          reject(err)
        }
      })
    },
    // 画二维码 this.qrData为生成的二维码资源
    drawQrcode(conf = {x:185,y:335,width:100,height:50}){
      return new Promise((resolve,reject)=>{
        this.ctx.drawImage(this.qrData,conf.x,conf.y,conf.width,conf.height)
        this.ctx.draw(true,()=>{
          resolve()
        })
      })
    },
    // canvs => images
    saveCans(){
      return new Promise((resolve,reject)=>{
        uni.canvasToTempFilePath({
          x:0,
          y:0,
          canvasId:'poster',
          success:(res)=>{
            resolve(res.tempFilePath)
          },
          fail:(err)=>{
            uni.hideLoading()
            reject(err)
          }
        },this)
      })
    }
  },
}
</script>

<style lang="scss" scoped></style>

HappyCodingTop
526 声望847 粉丝

Talk is cheap, show the code!!