乐于分享
好东西不私藏

UniApp 小程序转发朋友圈、分享好友配置全解

UniApp 小程序转发朋友圈、分享好友配置全解

UniApp 小程序转发朋友圈、分享好友配置全解

本文面向使用 UniApp 开发微信小程序的开发者,涵盖分享好友、分享朋友圈、海报生成、常见问题避坑等完整方案。所有代码均为 UniApp 原生写法,可直接复制使用。


一、分享好友 vs 分享朋友圈的区别

微信小程序的分享能力分为两大类:分享给好友 和 分享到朋友圈。两者在 API、 UI 样式、功能限制上都有明显差异。

特性
分享好友
分享到朋友圈
触发 API
onShareAppMessageonShareTimeline
回调时机
用户点击分享按钮时
用户点击"分享到朋友圈"时
分享卡片样式
小程序卡片的方图(5:4 或正方形)
朋友圈海报样式(正方形)
支持小程序码
❌ 不支持
✅ 支持
自定义页面路径
✅ 支持
❌ 不支持(只能分享当前页)
参数传递
通过 path 传递
通过 query 传递(长度限制 20 字符)
图片要求
5:4 或正方形,最小 200px
正方形,建议 200x200px 以上

UniApp 中如何开启分享菜单

在页面或 pages.json 中配置 enableShareMenu

// 页面 onLoad 中调用onLoad() {  // 开启分享菜单,显示"分享到朋友圈"入口  uni.showShareMenu({    withShareTicket: true,    menus: ['shareAppMessage', 'shareTimeline']  })}

或在 pages.json 页面配置中全局开启:

{  "pages": [    {      "path": "pages/index/index",      "style": {        "enableShareMenu": true      }    }  ],  "globalStyle": {    "enableShareMenu": true  }}

⚠️ 重要:不调用 uni.showShareMenu,朋友圈分享入口不会显示。


二、onShareAppMessage 分享好友

基础用法

// pages/index/index.vueexport default {  data() {    return {      productId: '',      productName: ''    }  },  onLoad(options) {    this.productId = options.id || ''    this.productName = options.name || '默认商品'  },  // #ifdef MP-WEIXIN  onShareAppMessage(res) {    return {      title: `${this.productName} - 限时特惠中!`,  // 分享标题      path: `/pages/index/index?id=${this.productId}`,  // 分享后打开的页面路径      imageUrl: 'https://xxx.com/static/share-card.png'  // 分享图片    }  },  // #endif}

动态标题:根据页面数据生成

// 商品详情页示例onShareAppMessage(res) {  const good = this.currentGood  return {    title: good.goods_name || '发现一个好物',    path: `/pages/goods/detail?id=${good.id}&from=share`,    imageUrl: good.goods_image || '/static/default-share.png'  }}

图片尺寸要求

  • 比例:推荐 5:4(横向),也支持正方形

  • 最小尺寸:200px × 200px

  • 格式:PNG、JPG 均可

  • 文件大小:建议不超过 500KB(过大会影响加载速度)

通过分享传参

// 发送方onShareAppMessage() {  return {    title: '邀请好友赢好礼',    path: '/pages/invite/index?scene=abc123&from=share',    imageUrl: 'https://xxx.com/invite.png'  }}// 接收方 onLoad 中获取onLoad(query) {  console.log('分享参数:', query)  // { scene: 'abc123', from: 'share' }}

💡 传参技巧:参数过长时,建议用 Base64 编码或让后端生成短链接映射。


三、onShareTimeline 分享朋友圈

基础用法

// pages/index/index.vueexport default {  // #ifdef MP-WEIXIN  onShareTimeline() {    return {      title: '朋友圈分享标题 - 点进来看看',  // 朋友圈卡片标题      query: 'id=123&from=timeline',  // 页面参数      imageUrl: 'https://xxx.com/poster-square.png'  // 海报图片    }  },  // #endif}

朋友圈分享的限制

限制项
说明
页面路径
只能分享当前页面
,无法自定义 path
参数传递
通过 query 字段,长度限制 20 个字符
图片数量
只支持单张图片
,必须是正方形
图片尺寸
建议 200×200px 以上,比例失真会被裁剪
域名
图片必须是已配置的合法域名

动态生成朋友圈内容

onShareTimeline() {  const good = this.currentGood  // 压缩参数,避免超长  const query = `id=${good.id}&from=tl`  return {    title: good.goods_name,    query: query,    imageUrl: good.share_poster || '/static/default-poster.png'  }}

完整配置:同时支持好友和朋友圈

export default {  data() {    return {      shareId: '',      shareTitle: '默认分享标题'    }  },  onLoad(options) {    this.shareId = options.id || '0'    this.shareTitle = options.title || '默认分享标题'    // 开启分享菜单    uni.showShareMenu({      withShareTicket: true,      menus: ['shareAppMessage', 'shareTimeline']    })  },  // #ifdef MP-WEIXIN  onShareAppMessage(res) {    return {      title: this.shareTitle,      path: `/pages/index/index?id=${this.shareId}&from=share`,      imageUrl: '/static/share-friend.png'    }  },  onShareTimeline() {    return {      title: this.shareTitle + ' - 限时优惠',      query: `id=${this.shareId}`,      imageUrl: '/static/share-timeline.png'    }  },  // #endif}

四、UniApp 特有的分享能力

uni.share - App 端分享到多平台

App 端支持分享到微信、QQ、微博等平台:

// #ifdef APP-PLUSuni.share({  provider: 'weixin',  type: 0,  // 0: 文字  1: 图片  5: 小程序  title: '分享标题',  summary: '分享摘要',  href: 'https://xxx.com/page',  imageUrl: '/static/share.png',  success: (res) => {    console.log('分享成功', res)    uni.showToast({ title: '分享成功' })  },  fail: (err) => {    console.error('分享失败', err)  }})// #endif

uni.share - 分享小程序卡片(App 端)

// #ifdef APP-PLUSuni.share({  provider: 'weixin',  type: 5,  // 5: 小程序  title: '小程序标题',  scene: 'WXSceneSession',  // WXSceneSession=好友  WXSceneTimeline=朋友圈  miniProgram: {    id: 'gh_xxxxxxx',  // 小程序原始 id    path: '/pages/index/index',    type: 0,  // 0-正式版  1-开发版  2-体验版    webUrl: 'https://xxx.com/h5-fallback'  // 非微信环境打开的 H5 页面  },  success: (res) => {    console.log('分享成功')  },  fail: (err) => {    console.error('分享失败', err)  }})// #endif

uni.shareWithSystem - 系统分享面板

调起系统原生分享菜单(所有平台通用):

// #ifdef APP-PLUSuni.shareWithSystem({  title: '分享标题',  summary: '分享内容摘要',  href: 'https://xxx.com/page',  imageUrl: '/static/share.png',  success: (res) => {    console.log('分享成功')  },  fail: (err) => {    console.error('分享失败', err)  }})// #endif

button 触发分享

使用 button 的 open-type="share" 触发分享:

<template>  <button open-type="share">分享给好友</button></template>
// #ifdef MP-WEIXINonShareAppMessage(res) {  // 区分不同 button 触发的分享  if (res.from === 'button') {    console.log('来自button点击分享', res.target)  }  return {    title: '分享标题',    path: '/pages/index/index',    imageUrl: '/static/share.png'  }}// #endif

五、分享到朋友圈(H5 + 小程序双端)

H5 端分享说明:UniApp H5 端调微信分享必须使用微信官方 JSSDK(wx. API),这是微信平台限制,非代码问题。小程序端请继续使用下方 UniApp 原生写法。

H5 端分享(微信 JSSDK)

<!-- public/index.html 引入 JSSDK --><script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
// #ifdef H5import wx from 'weixin-js-sdk'export default {  onLoad() {    this.initWxShare()  },  methods: {    async initWxShare() {      // 1. 从后端获取签名      const signPackage = await this.$api.getWxSignPackage()      // 2. 配置 JSSDK      wx.config({        debug: false,        appId: signPackage.appId,        timestamp: signPackage.timestamp,        nonceStr: signPackage.nonceStr,        signature: signPackage.signature,        jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData']      })      wx.ready(() => {        // 分享给好友        wx.updateAppMessageShareData({          title: '分享标题',          desc: '分享描述',          link: window.location.href,          imgUrl: 'https://xxx.com/share.png'        })        // 分享到朋友圈        wx.updateTimelineShareData({          title: '朋友圈标题',          link: window.location.href,          imgUrl: 'https://xxx.com/share.png'        })      })    }  }}// #endif

⚠️ H5 注意:必须在微信公众平台配置 JS 安全域名,且调用 JSSDK 的页面 URL 必须与入口 URL 一致。


六、UniApp 端海报生成方案

方案概述

朋友圈分享不支持小程序码,但可以通过生成带小程序码的海报实现裂变。用户保存海报到相册后扫码进入小程序。

整体流程

用户点击"生成海报" → 调用云函数获取小程序码 → Canvas 绘制海报 → 用户保存到相册

步骤 1:获取小程序码(UniCloud 云函数)

//云函数 /uni-cloud-jql/functions/getQRCode/index.js'use strict';const crypto = require('crypto')exports.main = async (event, context) => {  const {    scene,      // 参数,最大32个可见字符    page,       // 页面路径,非主包需配置 appid    width,      // 二维码宽度    auto_color, // 自动配置线条颜色    env_version // 小程序版本 trial-体验版 develop-开发版 release-正式版  } = event  const accessToken = await getAccessToken()  const apiUrl = `https://api.weixin.qq.com/wxa/getUnlimited?access_token=${accessToken}`  const res = await uniCloud.httpclient.request(apiUrl, {    method: 'POST',    data: {      scene: scene || 'from=poster',      page: page || 'pages/index/index',      width: width || 280,      auto_color: auto_color || false,      env_version: env_version || 'release',      is_hyaline: true  // 透明背景    },    dataType: 'json'  })  // 返回 Buffer  return res.data.buffer || res.data}// 获取 access_token(需缓存)async function getAccessToken() {  const db = uniCloud.database()  const conf = db.collection('mp-config')  // 从数据库读取缓存的 token  const { data } = await conf.where({ key: 'access_token' }).get()  const record = data[0]  if (record && record.expire_time > Date.now()) {    return record.value  }  // 重新获取  const res = await uniCloud.httpclient.request(    `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${SECRET}`  )  const token = JSON.parse(res.data).access_token  // 存入数据库缓存(提前5分钟过期)  if (record) {    await conf.doc(record._id).update({      value: token,      expire_time: Date.now() + 7100 * 1000    })  } else {    await conf.add({      key: 'access_token',      value: token,      expire_time: Date.now() + 7100 * 1000    })  }  return token}

步骤 2:前端调用获取小程序码

methods: {  async generatePoster() {    uni.showLoading({ title: '生成中...' })    try {      // 调用云函数获取小程序码      const res = await uniCloud.callFunction({        name: 'getQRCode',        data: {          scene: `id=${this.goodsId}&uid=${this.userId}`,          page: 'pages/goods/detail',          width: 280        }      })      // 将返回的 Buffer 转成本地临时路径      const filePath = await this.bufferToTempFile(res.result)      // 绘制海报      await this.drawPoster(filePath)    } catch (e) {      uni.showModal({ title: '生成失败', content: e.message })    } finally {      uni.hideLoading()    }  },  // Buffer 转临时文件  bufferToTempFile(buffer) {    return new Promise((resolve, reject) => {      const fs = uni.getFileSystemManager()      const fileName = `${Date.now()}_qrcode.png`      const filePath = `${uni.env.USER_DATA_PATH}/${fileName}`      fs.writeFile({        filePath,        data: buffer,        encoding: 'base64',        success: () => resolve(filePath),        fail: reject      })    })  },  // 绘制海报  async drawPoster(qrcodePath) {    const ctx = uni.createCanvasContext('posterCanvas', this)    const dpr = uni.getSystemInfoSync().pixelRatio    const canvasWidth = 600    const canvasHeight = 800    // 设置 canvas 尺寸(真实像素)    this.canvasWidth = canvasWidth    this.canvasHeight = canvasHeight    this.canvasDpr = dpr    // 绘制背景    ctx.fillStyle = '#FFFFFF'    ctx.fillRect(0, 0, canvasWidth, canvasHeight)    // 绘制商品图(假设已下载到本地)    ctx.drawImage(this.localImagePath, 50, 50, 500, 400)    // 绘制商品名称    ctx.setFontSize(36)    ctx.setFillStyle('#333333')    ctx.fillText(this.goodsName, 50, 500)    // 绘制价格    ctx.setFontSize(48)    ctx.setFillStyle('#FF5000')    ctx.fillText(`¥${this.goodsPrice}`, 50, 570)    // 绘制小程序码    ctx.drawImage(qrcodePath, 200, 600, 200, 200)    // 绘制引导文案    ctx.setFontSize(28)    ctx.setFillStyle('#666666')    ctx.fillText('长按识别小程序码', 175, 850)    ctx.draw(false, () => {      // 导出图片      uni.canvasToTempFilePath({        canvasId: 'posterCanvas',        x: 0,        y: 0,        width: canvasWidth,        height: canvasHeight,        destWidth: canvasWidth * 3,        destHeight: canvasHeight * 3,        success: (res) => {          this.posterPath = res.tempFilePath        },        fail: (err) => {          console.error('导出失败', err)        }      })    })  }}
<!-- 海报画布(隐藏) --><canvas   canvas-id="posterCanvas"   id="posterCanvas"  style="position:fixed;left:-9999px;width:600px;height:900px;"></canvas><!-- 显示海报 --><view v-if="posterPath">  <image :src="posterPath" mode="aspectFit" />  <button @click="savePoster">保存到相册</button></view>

步骤 3:保存到相册(权限处理)

methods: {  async savePoster() {    // 1. 检查权限    const setting = await uni.getSetting()    if (!setting.authSetting['scope.writePhotosAlbum']) {      // 2. 请求权限      const res = await uni.authorize({ scope: 'scope.writePhotosAlbum' })      if (res.errMsg.includes('auth deny')) {        // 3. 用户拒绝过,引导去设置页        const modalRes = await uni.showModal({          title: '提示',          content: '需要您授权保存图片到相册',          confirmText: '去授权'        })        if (modalRes.confirm) {          await uni.openSetting()        }        return      }    }    // 4. 保存图片    await uni.saveImageToPhotosAlbum({      filePath: this.posterPath,      success: () => {        uni.showToast({ title: '已保存到相册' })      },      fail: (err) => {        uni.showToast({ title: '保存失败', icon: 'none' })      }    })  }}

完整海报组件封装

// components/poster-generator/poster-generator.vue<template>  <view class="poster-wrapper">    <canvas       canvas-id="posterCanvas"       :style="{ width: canvasStyle.width, height: canvasStyle.height }"      class="poster-canvas"    ></canvas>    <view v-if="posterPath" class="poster-preview">      <image :src="posterPath" mode="widthFix" class="poster-image" />      <button type="primary" @click="saveToAlbum" class="save-btn">        保存到相册      </button>    </view>  </view></template><script>export default {  props: {    // 商品数据    goods: {      type: Object,      required: true    },    // 用户 ID    userId: {      type: String,      default: ''    }  },  data() {    return {      posterPath: '',      localImagePath: '',      canvasStyle: {        width: '600rpx',        height: '900rpx'      }    }  },  methods: {    async generate() {      uni.showLoading({ title: '生成海报中...' })      try {        // 1. 下载商品主图到本地        await this.downloadGoodsImage()        // 2. 获取小程序码        const qrcodeRes = await this.getWxacode()        const qrcodePath = await this.bufferToTempFile(qrcodeRes.result)        // 3. 绘制海报        await this.drawPoster(qrcodePath)      } catch (e) {        console.error('生成海报失败', e)        uni.showModal({ title: '提示', content: '海报生成失败,请重试' })      } finally {        uni.hideLoading()      }    },    async downloadGoodsImage() {      return new Promise((resolve, reject) => {        uni.downloadFile({          url: this.goods.image,          success: (res) => {            this.localImagePath = res.tempFilePath            resolve(res)          },          fail: reject        })      })    },    async getWxacode() {      return uniCloud.callFunction({        name: 'getQRCode',        data: {          scene: `id=${this.goods.id}&uid=${this.userId}&from=poster`,          page: 'pages/goods/detail',          width: 280        }      })    },    bufferToTempFile(buffer) {      return new Promise((resolve, reject) => {        const fs = uni.getFileSystemManager()        const fileName = `qr_${Date.now()}.png`        const filePath = `${uni.env.USER_DATA_PATH}/${fileName}`        fs.writeFile({          filePath,          data: buffer,          encoding: 'base64',          success: () => resolve(filePath),          fail: reject        })      })    },    drawPoster(qrcodePath) {      return new Promise((resolve) => {        const ctx = uni.createCanvasContext('posterCanvas', this)        // 清空画布        ctx.fillStyle = '#FFFFFF'        ctx.fillRect(0, 0, 600, 900)        // 绘制商品图        ctx.drawImage(this.localImagePath, 0, 0, 600, 450)        // 绘制标题        ctx.setFontSize(32)        ctx.setFillStyle('#333333')        ctx.fillText(this.goods.name, 30, 510, 540)        // 绘制价格        ctx.setFontSize(48)        ctx.setFillStyle('#FF4A00')        ctx.fillText(`¥${this.goods.price}`, 30, 600)        // 绘制小程序码        ctx.drawImage(qrcodePath, 200, 650, 200, 200)        // 绘制引导文案        ctx.setFontSize(24)        ctx.setFillStyle('#999999')        ctx.textAlign = 'center'        ctx.fillText('长按识别 立即购买', 300, 900)        ctx.draw(false, () => {          uni.canvasToTempFilePath({            canvasId: 'posterCanvas',            success: (res) => {              this.posterPath = res.tempFilePath              resolve()            }          })        })      })    },    async saveToAlbum() {      try {        await uni.saveImageToPhotosAlbum({ filePath: this.posterPath })        uni.showToast({ title: '已保存到相册', icon: 'success' })        this.$emit('saved')      } catch (e) {        if (e.errMsg.includes('auth deny')) {          const res = await uni.showModal({            title: '提示',            content: '需要授权保存图片',            confirmText: '去授权'          })          if (res.confirm) {            uni.openSetting()          }        }      }    }  }}</script><style scoped>.poster-wrapper {  padding: 20rpx;}.poster-canvas {  position: fixed;  left: -9999px;}.poster-image {  width: 100%;  border-radius: 16rpx;}.save-btn {  margin-top: 30rpx;}</style>

七、分享成功后通知后端

分享埋点方案

// #ifdef MP-WEIXINonShareAppMessage(res) {  // 分享前上报  this.reportShare('friend', {    page: '/pages/index/index',    goodsId: this.goodsId,    shareType: res.from // 'button' | 'menu'  })  return {    title: this.shareTitle,    path: `/pages/index/index?id=${this.goodsId}`,    imageUrl: this.shareImage  }},onShareTimeline() {  // 分享到朋友圈上报  this.reportShare('timeline', {    page: '/pages/index/index',    goodsId: this.goodsId  })  return {    title: this.shareTitle,    query: `id=${this.goodsId}`  }},methods: {  async reportShare(type, data) {    try {      await uniCloud.callFunction({        name: 'reportShare',        data: {          type,          ...data,          uid: uni.getStorageSync('userId'),          shareTime: Date.now()        }      })    } catch (e) {      console.error('埋点上报失败', e)    }  }}// #endif

云函数记录分享

// /uni-cloud-jql/functions/reportShare/index.js'use strict';exports.main = async (event, context) => {  const db = uniCloud.database()  const collection = db.collection('share-log')  await collection.add({    uid: event.uid,    type: event.type,           // 'friend' | 'timeline'    page: event.page,    goodsId: event.goodsId,    shareTime: event.shareTime,    createTime: Date.now()  })  return { success: true }}

防刷机制

// 云函数防刷exports.main = async (event, context) => {  const db = uniCloud.database()  const collection = db.collection('share-log')  const uid = event.uid  const now = Date.now()  const oneHourAgo = now - 3600 * 1000  // 1. 检查1小时内分享次数  const count = await collection.where({    uid,    shareTime: db.command.gte(oneHourAgo)  }).count()  if (count > 50) {    return { success: false, message: '分享过于频繁' }  }  // 2. 记录分享  await collection.add({...})  return { success: true }}

八、常见问题与避坑(UniApp 场景)

问题
原因
解决方案
分享图片不显示
图片域名未配置或格式不支持
在微信公众平台配置合法域名,使用 HTTPS + PNG/JPG
朋友圈分享入口不显示
未调用 uni.showShareMenu
在 onLoad 中调用并指定 menus: ['shareAppMessage', 'shareTimeline']
小程序码生成失败
参数超长或 page 未在合法域名下
压缩参数(≤32字符),确保 page 路径已发布
App 端分享失败
未配置微信开放平台
登录微信开放平台申请移动应用并关联小程序
H5 端无法分享
微信 JSSDK 未引入或签名错误
引入 JSSDK,从后端获取正确的签名
分享带参数失效
参数过长被截断
用 encodeURIComponent 编码,或后端生成短链接映射
canvas 海报绘制模糊
导出时像素比未处理
使用 destWidth/destHeight 放大 2-3 倍导出
canvas 导出失败
跨域或文件权限问题
使用 uni.env.USER_DATA_PATH 路径,或在 uniCloud 云函数中处理
onShareTimeline 分享当前页不对
只能分享当前页面
确保用户在目标页面再触发分享
button open-type=“share” 无效
组件层级或样式问题
检查 button 是否被其他元素遮挡

避坑重点

  1. 图片域名白名单:所有 imageUrl 必须是已在微信公众平台配置过的合法域名

  2. onShareTimeline 只能分享当前页:无法通过 path 指定页面

  3. 朋友圈 query 长度限制 20 字符:超长参数用后端映射或 encode

  4. canvas 导出在开发工具正常、真机失败:检查高清导出参数和路径

  5. App 端分享小程序需要开放平台:需将移动应用与小程序关联


九、快速复制模板

完整分享配置模板(双端合一)

// pages/goods/detail/detail.vueexport default {  data() {    return {      goodsId: '',      goodsName: '',      goodsPrice: 0,      shareImage: ''    }  },  onLoad(options) {    this.goodsId = options.id || ''    this.loadGoodsDetail()    // 开启分享菜单    uni.showShareMenu({      withShareTicket: true,      menus: ['shareAppMessage', 'shareTimeline']    })  },  // #ifdef MP-WEIXIN  // 分享给好友  onShareAppMessage(res) {    // 区分触发来源    const trigger = res.from || 'menu'    console.log('分享来源:', trigger)    return {      title: `${this.goodsName} - ¥${this.goodsPrice}限时抢`,      path: `/pages/goods/detail/detail?id=${this.goodsId}&from=share`,      imageUrl: this.shareImage || '/static/default-share.png'    }  },  // 分享到朋友圈  onShareTimeline() {    return {      title: `${this.goodsName} - 限时优惠中`,      query: `id=${this.goodsId}`,      imageUrl: this.shareImage || '/static/default-poster.png'    }  },  // #endif  // #ifdef APP-PLUS  onShareAppMessage(res) {    return {      title: `${this.goodsName} - ¥${this.goodsPrice}`,      content: '发现一个超值的商品,点击查看',      imageUrl: this.shareImage,      href: `https://your-domain.com/pages/goods/detail?id=${this.goodsId}`    }  }  // #endif}

App 端多平台分享模板

// components/share-button/share-button.vue<template>  <button @click="handleShare">分享</button></template><script>export default {  methods: {    handleShare() {      // #ifdef APP-PLUS      uni.showActionSheet({        itemList: ['微信好友', '朋友圈', 'QQ', '微博', '系统分享'],        success: (res) => {          const actions = ['weixin', 'weixin', 'qq', 'sinaweibo', '']          const type = actions[res.tapIndex]          if (res.tapIndex === 4) {            // 系统分享            this.shareWithSystem()          } else {            this.shareTo(type)          }        }      })      // #endif      // #ifdef MP-WEIXIN      // 小程序端直接使用原生分享      // #endif    },    shareTo(provider) {      // #ifdef APP-PLUS      uni.share({        provider,        type: 0,        title: '分享标题',        summary: '分享内容',        imageUrl: '/static/share.png',        href: 'https://your-domain.com',        success: (res) => uni.showToast({ title: '分享成功' }),        fail: (err) => uni.showToast({ title: '分享失败', icon: 'none' })      })      // #endif    },    shareWithSystem() {      // #ifdef APP-PLUS      uni.shareWithSystem({        title: '分享标题',        summary: '分享内容',        href: 'https://your-domain.com',        imageUrl: '/static/share.png',        success: (res) => console.log('分享成功'),        fail: (err) => console.error('分享失败', err)      })      // #endif    }  }}</script>

权限处理完整流程

// components/poster-save/poster-save.vueexport default {  methods: {    async checkAndRequestAuth(scope, title, content) {      return new Promise(async (resolve, reject) => {        // 获取当前权限状态        const setting = await uni.getSetting()        if (setting.authSetting[scope] === true) {          // 已有权限          resolve(true)        } else if (setting.authSetting[scope] === undefined) {          // 从未请求过权限          try {            await uni.authorize({ scope })            resolve(true)          } catch (e) {            // 用户拒绝,尝试引导            this.showAuthDialog(title, content)            reject(e)          }        } else {          // 之前被拒绝          this.showAuthDialog(title, content)          reject(new Error('auth denied'))        }      })    },    showAuthDialog(title, content) {      uni.showModal({        title,        content,        confirmText: '去授权',        success: async (res) => {          if (res.confirm) {            // 打开设置页            await uni.openSetting()            // 用户可能在设置页关闭了权限,返回后需重新检查            const setting = await uni.getSetting()            if (!setting.authSetting['scope.writePhotosAlbum']) {              uni.showToast({ title: '请开启相册权限', icon: 'none' })            }          }        }      })    }  }}

总结

场景
关键 API
注意事项
小程序分享好友
onShareAppMessage
图片 5:4/正方形,path 可自定义
小程序分享朋友圈
onShareTimeline
只支持当前页,query ≤20 字符
App 端分享
uni.share
 / uni.shareWithSystem
需要开放平台绑定
H5 端分享
微信 JSSDK
需要后端签名服务
海报生成
uni.createCanvasContext
 + 小程序码
注意高清导出和权限处理
分享埋点
云函数异步记录
添加防刷机制

📌 项目推荐:将分享能力封装为独立组件,统一管理分享配置、权限处理和海报生成,便于多项目复用。