当前位置: 首页 > article >正文

uni生成海报并保存

需求:通过不访问服务器,生成海报,解决服务访问压力和带宽问题,在本地生成海报图片

<template>
  <!--页面属性配置节点,设置页面跟字体大小  -->
  <!--  <page-meta :root-font-size="fontSize + 'px'"></page-meta>-->
  <view class="posPoster">
    <view class="percard">
      <canvas
        canvas-id="myCanvas"
        :style="{
          width: state.canvasWidth,
          height: state.canvasHeight,
          transform: 'scale(' + scale + ') translate(-' + translate + '%, -' + translate + '%)'
        }"
      ></canvas>
    </view>
    <!-- 底部按钮占位,防止底部按钮挡住海报 -->
    <view class="bottomPosHeight"></view>
  </view>
  <uv-modal
    ref="modalRef"
    title="长按保存到本地"
    :showCancelButton="false"
    :showConfirmButton="false"
    :textStyle="{ color: '#c22632' }"
  >
    <uv-image width="650rpx" mode="widthFix" v-if="isShow" :src="state.imgs"></uv-image>
  </uv-modal>
  <view class="sureEvaluate" @click="saveImg()">下载到本地</view>
</template>

<script setup>
  import { onLoad, onReady } from '@dcloudio/uni-app'
  import html2canvas from 'html2canvas'
  const scale = ref(0.5)
  const picWidth = reactive(750)
  const picHeight = reactive(1080)
  const translate = computed(() => {
    console.log(state.screenWidth, state.ratio)
    return ((picWidth - state.screenWidth) / (state.screenWidth * 2)) * 100
  })
  const isShow = ref(false)

  const modalRef = ref()
  const state = reactive({
    fontSize: '',
    isShowBtn: false,
    myObj: {
      number: '',
      bgImg: '', //背景图
      name: ''
    },
    canvasWidth: picWidth, //画布宽度
    canvasHeight: picHeight, //画布高度
    ratio: window.devicePixelRatio || 1, //计算UI设计稿和你手机的屏幕宽度比例(例如UI设计稿是750宽度 你手机是350宽度 比例就是2  那么你画布画图时候 所有的尺寸大小、宽高、位置、定位左右上下都需要除以 / 比例2 )
    widths: '',
    heights: '',
    imgs: ''
  })

  const saveImg = () => {
    isShow.value = true
    console.log(modalRef)
    modalRef.value.open()
    alert('长按保存到本地')
   
  }

  /**
   * 计算海报尺寸,适配各种手机,因为我的项目没有使用rpx用的rem所以需要换算,后来为了提出单独组件,在scss里用了rpx。
   */
  onLoad((option) => {
    const pop = getCurrentPages().pop()
    const eventChannel = pop.$vm.getOpenerEventChannel()
    eventChannel.on('acceptDataFromOpenerPage', function (data) {
      console.log(1111, data)
      state.myObj.name = data.name
    })

    uni.getSystemInfo({
      success: (res) => {
        console.log(res.screenWidth)
        console.log(state.ratio)
        state.screenWidth = res.screenWidth
        scale.value = res.screenWidth / picWidth
        state.screenHeight = res.screenHeight
        state.canvasWidth = picWidth + 'px'
        state.canvasHeight = picHeight + 'px'
        state.ratio = 750 / res.screenWidth
        state.widths = (res.screenWidth / 750) * 750
        state.heights = (res.screenWidth / 750) * 1080
        state.fontSize = res.screenWidth / 750

        downImgUrl()
      }
    })

    uni.showLoading({
      title: '生成中...'
    })
  })

  const downImgUrl = () => {
    uni.getImageInfo({
      src: state.myObj.bgImg,
      success: function (res) {
        console.log(1234, res)
        state.myObj.bgImg = res.path
        drawPageImg()
        state.isShowBtn = true
      }
    })
  }

  //画一个带圆角矩形
  const ctxCircular = (ctx, img, x, y, width, height, r, shadow) => {
    ctx.beginPath() //开始绘制
    ctx.save() //保存(canvas)状态
    ctx.moveTo(x + r, y)
    ctx.lineTo(x + width - r, y)
    ctx.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 2)
    ctx.lineTo(x + width, y + height - r)
    ctx.arc(x + width - r, y + height - r, r, 0, Math.PI * 0.5)
    ctx.lineTo(x + r, y + height)
    ctx.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI)
    ctx.lineTo(x, y + r)
    ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)
    if (shadow == 1) {
      ctx.shadowBlur = 20 // 模糊效果程度的
      ctx.shadowColor = 'red' // 阴影颜色
    }
    ctx.fill() //对当前路径中的内容进行填充
    ctx.clip() //从原始画布中剪切任意形状和尺寸
    ctx.closePath() //关闭一个路径
    ctx.drawImage(img, x, y, width, height)
    ctx.restore() //恢复(canvas)状态
    ctx.globalCompositeOperation = 'source-over'
  }

  //画一个矩形也就是整个海报的背景
  const ctxRectangle = (ctx, x, y, width, height, r, gnt) => {
    ctx.beginPath()
    ctx.save() //保存状态
    ctx.moveTo(x + r, y)
    ctx.lineTo(x + width - r, y)
    ctx.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 2)
    ctx.lineTo(x + width, y + height - r)
    ctx.arc(x + width - r, y + height - r, r, 0, Math.PI * 0.5)
    ctx.lineTo(x + r, y + height)
    ctx.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI)
    ctx.lineTo(x, y + r)
    ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)
    ctx.fillStyle = gnt
    ctx.fill() //对当前路径中的内容进行填充
    ctx.closePath() //关闭一个路径
  }

  // 文字
  const ctxText = (ctx, textFont, textAlign, textFillStyle, textName, x, y, w) => {
    ctx.beginPath()
    ctx.save() //保存状态
    //字体
    ;(ctx.font = textFont),
      //字体样式
      (ctx.textAlign = textAlign)
    //字体颜色
    ctx.fillStyle = textFillStyle
    //填充字体

    var temp = ''
    var row = []
    let gxqm = ''
    if (textName) {
      gxqm = textName
    } else {
      gxqm = '失败'
    }
    let gexingqianming = gxqm.split('')
    for (var a = 0; a < gexingqianming.length; a++) {
      if (ctx.measureText(temp).width < w) {
      } else {
        row.push(temp)
        temp = ''
      }
      temp += gexingqianming[a]
    }
    row.push(temp)
    for (var b = 0; b < row.length; b++) {
      ctx.fillText(row[b] || '', x, y + (b + 1) * 20)
    }
    // ctx.fillText(textName || '', x, y)
    ctx.globalCompositeOperation = 'source-over'
  }

  // 文字
  const ctxTextWrap = (ctx, text, x, y, w, size) => {
    //自动换行介绍
    var temp = ''
    var row = []
    let gxqm = ''
    if (text) {
      gxqm = text
    } else {
      gxqm = ''
    }
    let gexingqianming = gxqm.split('')
    for (var a = 0; a < gexingqianming.length; a++) {
      if (ctx.measureText(temp).width < w) {
      } else {
        row.push(temp)
        temp = ''
      }
      temp += gexingqianming[a]
    }
    row.push(temp)
    ctx.font = `${size}px arail`
    ctx.textAlign = 'left'
    ctx.fillStyle = '#333'
    ctx.globalCompositeOperation = 'source-over'
    for (var b = 0; b < row.length; b++) {
      ctx.fillText(row[b] || '', x, y + (b + 1) * 20)
    }
  }

  // 文字
  const ctxTextWrapA = (ctx, text, x, y, w, size, color) => {
    //自动换行介绍
    var temp = ''
    var row = []
    let gxqm = ''
    if (text) {
      gxqm = text
    } else {
      gxqm = ''
    }
    let gexingqianming = gxqm.split('')
    for (var a = 0; a < gexingqianming.length; a++) {
      if (ctx.measureText(temp).width < w) {
      } else {
        row.push(temp)
        temp = ''
      }
      temp += gexingqianming[a]
    }
    row.push(temp)
    ctx.font = `${size}px arail`
    ctx.textAlign = 'left'
    ctx.fillStyle = `#${color}`
    ctx.globalCompositeOperation = 'source-over'
    for (var b = 0; b < row.length; b++) {
      ctx.fillText(row[b] || '', x, y + (b + 1) * 20)
    }
  }

  // 文字
  const ctxTextWrapB = (ctx, text, x, y, w, size, color) => {
    //自动换行介绍
    var temp = ''
    var row = []
    let gxqm = ''
    if (text) {
      gxqm = text
    } else {
      gxqm = ''
    }
    let gexingqianming = gxqm.split('')
    for (var a = 0; a < gexingqianming.length; a++) {
      if (ctx.measureText(temp).width < w) {
      } else {
        row.push(temp)
        temp = ''
      }
      temp += gexingqianming[a]
    }
    row.push(temp)
    // ctx.font = `arail normal bold ${size}px sans-serif`
    // ctx.textAlign = 'left'
    // ctx.fillStyle = `#${color}`
    // ctx.globalCompositeOperation = 'source-over'
    for (var b = 0; b < row.length; b++) {
      ctx.fillText(row[b] || '', x, y + (b + 1) * 20)
    }
  }

  // 使用画布绘制页面
  const drawPageImg = () => {
    // 生成画布
    const ctx = uni.createCanvasContext('myCanvas')
    // 绘制背景
    ctx.drawImage(
      state.myObj.bgImg, //图像资源
      0 / state.ratio, //图像的左上角在目标canvas上 X 轴的位置
      0 / state.ratio, //图像的左上角在目标canvas上 Y 轴的位置
      picWidth, //在目标画布上绘制图像的宽度
      picHeight //在目标画布上绘制图像的高度
    )
    let fontBase = Math.round(picWidth * 0.02580645)
    let fontBase2 = Math.round(fontBase * 0.84375)
    let fontBase3 = Math.round(fontBase * 0.875)
    let fontSizeStyle = fontBase + 'px 思源宋体'
    const date = state.myObj.arrivalDate.split('-')

    //编号
    ctxText(
      ctx,
      fontSizeStyle,
      'right',
      '',
      state.myObj.number,
      picWidth * 0.871,
      picHeight * 0.361,
      picWidth * 0.242
    )

    //姓名
    ctxText(
      ctx,
      fontSizeStyle,
      'right',
      '',
      state.myObj.name.length == 2 ? state.myObj.name.split('').join('  ') : state.myObj.name,
      picWidth * 0.2218,
      picHeight * 0.411,
      600 / state.ratio
    )
   
    ctx.draw(false, () => {
      // alert('draw')
      setTimeout(() => {
        // 将canvas 变成图片方便发送给好友或者保存
        uni.canvasToTempFilePath({
          canvasId: 'myCanvas',
          fileType: 'jpg',
          success: (res) => {
            // alert(JSON.stringify(res))
            isShow.value = true
            uni.hideLoading()
            // alert(res.tempFilePath)
            state.imgs = res.tempFilePath
          },
          fail: function (error) {
            // alert(JSON.stringify(error))
            uni.hideLoading()
            uni.showToast({
              icon: 'none',
              position: 'bottom',
              title: '绘制图片失败'
            })
          }
        })
      }, 100)
    })
  }
</script>

<style scoped>
  .posPoster {
    width: 750rpx;
    height: 1338rpx;
    position: relative;
  }

  .percard {
    width: 750rpx;
    height: 1080rpx;
    overflow: hidden;
    position: absolute;
    left: 50%;
    transform: translate(-50%, 0);
  }

  .sureEvaluate {
    width: 690rpx;
    height: 88rpx;
    line-height: 88rpx;
    border-radius: 53rpx;
    background: #e6e8ff;
    position: fixed;
    bottom: 50rpx;
    left: 30rpx;
    border: none;
    outline: none;
    box-shadow: none;
    z-index: 999;
    text-align: center;
  }

  .bottomPosHeight {
    width: 750rpx;
    height: 138rpx;
  }
</style>


http://www.kler.cn/a/383745.html

相关文章:

  • 在培训班学网络安全有用吗
  • 【前端】html的8个常用标签
  • 行业人才缺口达百万,无人机“飞手”之渴如何解?0基础无人机学习技术详解
  • 泷羽sec学习打卡-shodan扫描1
  • 【ROS2】接口(话题、服务、动作)的创建,编译后生成的C++代码详解
  • Istio Gateway发布服务
  • 【MyBatis】【基于轻量型架构的WEB开发】课程 课后习题 章节测试
  • 基于C++深度优先遍历迷宫
  • 如何将数据从 AWS S3 导入到 Elastic Cloud - 第 3 部分:Elastic S3 连接器
  • Java 8 Stream用法与常见问题和解决方式
  • Redis内存管理——针对实习面试
  • Charles简单压力测试
  • Unity中RTS游戏的设计模式处理: 游戏中的对象选择和命令委托的基本框架
  • 解读JobScheduler的jobs.xml
  • 判断二叉搜索树(递归)
  • 【LeetCode】【算法】647. 回文子串
  • 卡码网KamaCoder 127. 骑士的攻击
  • 梧桐数据库之查询特定日期的套餐价格分享
  • (超级详细版)Java基础:Java常用变量详解
  • T507 buildroot linux4.9之MCP2515 can网络开发调试
  • 耕地类项目知识点汇总(持续完善中……)
  • ubuntu22.04安装conda
  • 鸿蒙-promptAction.showToast基于PC屏幕底部提示
  • Ubuntu 安装 RTL8811cu 网卡驱动
  • CTFshow之信息收集第1关到10关。详细讲解
  • SpringBoot基础系列学习(二):配置详解