UniApp Vue3 生成海报并分享到朋友圈

0 阅读3分钟

UniApp Vue3 生成海报并分享到朋友圈

纯前端生成海报 + 保存到相册 + 引导分享朋友圈

2.png 3.png 4.png 5.png

核心技术

  • uni-canvas(UniApp 官方画布,兼容多端)
  • Vue3 + Composition API
  • 异步绘制 + 加载等待(解决图片不显示问题)
  • 保存到相册 + 引导分享朋友圈

一、核心代码

1. 页面 vue 文件

<template>
  <view class="container">
    <!-- 生成海报按钮 -->
    <button type="primary" @click="createPoster" :loading="loading">
      生成分享海报
    </button>
    <!-- 海报预览弹窗 -->
    <view v-if="showPoster" class="modal" @click="showPoster = false">
      <view class="poster-box" @click.stop>
        <!-- 画布:用于生成海报(隐藏在页面外) -->
        <canvas
          type="2d"
          id="posterCanvas"
          canvas-id="posterCanvas"
          class="poster-canvas"
        />        
        <!-- 预览图 -->
        <image :src="posterUrl" mode="widthFix" class="poster-img" />        
        <!-- 操作按钮 -->
        <view class="btn-group">
          <button type="primary" @click="savePoster">保存海报到相册</button>
          <view class="tip">
            保存后,打开微信朋友圈 → 从相册选择图片发布
          </view>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 状态
const loading = ref(false)       // 生成中loading
const showPoster = ref(false)    // 显示海报弹窗
const posterUrl = ref('')        // 生成的海报临时路径
let canvas = null                // 画布实例
let ctx = null                   // 画布上下文
// 页面加载后获取画布
onMounted(() => {
  const query = uni.createSelectorQuery().in(getCurrentInstance())
  query.select('#posterCanvas')
    .fields({ node: true, size: true })
    .exec((res) => {
      if (!res[0]) return
      canvas = res[0].node
      ctx = canvas.getContext('2d')
    })
})
const createPoster = async () => {
  if (!canvas || !ctx) {
    uni.showToast({ title: '画布初始化失败', icon: 'none' })
    return
  }
  loading.value = true
  uni.showLoading({ title: '海报生成中...' })
  // 画布尺寸(海报尺寸)
  const dpr = uni.getSystemInfoSync().pixelRatio
  canvas.width = 300 * dpr
  canvas.height = 400 * dpr
  ctx.scale(dpr, dpr)
  try {
    // 1. 绘制白色背景
    ctx.fillStyle = '#ffffff'
    ctx.fillRect(0, 0, 300, 400)
    // 2. 绘制标题
    ctx.fillStyle = '#333'
    ctx.font = 'bold 18px sans-serif'
    ctx.fillText('我的商品海报', 30, 40)
    // 3. 绘制商品图片(替换成你的网络图片/本地图片)
    const coverImg = await loadImage('/static/poster.png')
    ctx.drawImage(coverImg, 30, 60, 240, 240)
    // 4. 绘制价格
    ctx.fillStyle = '#ff3333'
    ctx.font = '16px sans-serif'
    ctx.fillText('¥ 99.00', 30, 330)
    // 5. 绘制二维码(替换成你的二维码)
    const qrcodeImg = await loadImage('/static/qrcode.png')
    ctx.drawImage(qrcodeImg, 180, 310, 60, 60)
    // 6. 导出海报图片
    await saveCanvasToTempFilePath()
    showPoster.value = true
    uni.hideLoading()
  } catch (err) {
    console.error('生成失败:', err)
    uni.showToast({ title: '生成失败', icon: 'none' })
    uni.hideLoading()
  } finally {
    loading.value = false
  }
}
const loadImage = (src) => {
  return new Promise((resolve, reject) => {
    const img = canvas.createImage()
    img.onload = () => resolve(img)
    img.onerror = (e) => reject(e)
    img.src = src
  })
}
const saveCanvasToTempFilePath = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      uni.canvasToTempFilePath({
        canvas: canvas,
        width: 300,
        height: 400,
        destWidth: 900,
        destHeight: 1200,
        success: (res) => {
          posterUrl.value = res.tempFilePath
          resolve()
        }
      }, getCurrentInstance())
    }, 300)
  })
}
const savePoster = () => {
  if (!posterUrl.value) return
  
  uni.saveImageToPhotosAlbum({
    filePath: posterUrl.value,
    success: () => {
      uni.showModal({
        title: '保存成功',
        content: '已保存到相册,请到朋友圈发布',
        showCancel: false
      })
    },
    fail: () => {
      uni.showToast({ title: '保存失败', icon: 'none' })
    }
  })
}
</script>
<style scoped>
.container { padding: 40rpx;}
/* 弹窗遮罩 */
.modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; z-index: 999;}
/* 海报容器 */
.poster-box { width: 600rpx; background: #fff; border-radius: 16rpx; padding: 20rpx; position: relative;}
/* 画布:隐藏在页面外,不影响UI */
.poster-canvas { position: absolute; left: -9999rpx; top: 0; width: 300px;height: 400px;}
/* 预览图 */
.poster-img {width: 100%;border-radius: 8rpx;}
/* 按钮组 */
.btn-group {margin-top: 20rpx;text-align: center;}
.tip {font-size: 24rpx;color: #999;margin-top: 16rpx;line-height: 1.5;}
</style>

二、替换商品主图

  1. 商品主图

    loadImage('/static/poster.png')
    

    换成你的网络图片或本地图片。

  2. 二维码图片

    loadImage('/static/qrcode.png')
    

    /static/qrcode.png 换成你自己的二维码。

  3. 文字内容(标题、价格等)可自由修改。


三、微信小程序权限配置

打开项目根目录 manifest.json → 切换到源码视图,添加以下权限:

"mp-weixin": {
  "permission": {
    "scope.writePhotosAlbum": {
      "desc": "需要保存海报到相册"
    }
  }
}

使用流程

  1. 点击生成分享海报
  2. 自动绘制 → 弹出预览图
  3. 点击保存海报到相册
  4. 提示保存成功
  5. 手动打开微信朋友圈 → 从相册选择图片发布