文章目录
-
- 引言
- 一、微信小程序分享能力概览
-
- 1.1 官方支持的能力
- 1.2 各功能的技术实现难度
- 二、环境准备和基础配置
-
- 2.1 项目结构
- 2.2 基础配置
- 三、转发给好友/群功能实现
-
- 3.1 页面级分享配置
- 3.2 自定义转发按钮
- 四、朋友圈分享功能实现(核心难点)
-
- 4.1 朋友圈分享的限制与解决方案
- 4.2 Canvas 生成分享图片
- 4.3 朋友圈分享页面实现
- 4.4 朋友圈分享页面布局
- 五、复制链接功能实现
-
- 5.1 链接生成工具
- 5.2 复制链接功能集成
- 5.3 复制链接组件
- 六、高级功能实现
-
- 6.1 带参数的分享统计
- 6.2 分享海报模板系统
- 七、性能优化和最佳实践
-
- 7.1 Canvas 性能优化
- 7.2 内存管理
- 八、错误处理和兼容性
-
- 8.1 错误处理
- 8.2 兼容性处理
- 九、完整示例和测试
-
- 9.1 完整分享页面示例
- 十、总结与最佳实践
-
- 10.1 核心要点总结
- 10.2 最佳实践建议
- 10.3 注意事项
- 参考资料
引言
在微信小程序的运营和推广中,分享功能是实现用户增长和内容传播的核心能力。然而,微信对小程序分享有诸多限制,特别是朋友圈分享功能的实现相对复杂。本文将全面解析微信小程序分享功能的实现方案,涵盖转发好友、朋友圈分享(图片+二维码)、链接复制三大核心功能。
一、微信小程序分享能力概览
1.1 官方支持的能力
| 转发给好友/群 | ✅ 完全支持 | 通过按钮或右上角菜单 |
| 分享到朋友圈 | ⚠️ 限制支持 | 需生成图片,用户手动保存分享 |
| 复制链接 | ✅ 完全支持 | 可复制页面路径或带参数链接 |
| 生成海报 | ✅ 自定义实现 | 需要canvas绘制 |
1.2 各功能的技术实现难度
┌─────────────────────────────────┐
│ 功能实现难度对比 │
├─────────────────────────────────┤
│ 转发好友/群 ★☆☆☆☆ 最简单 │
│ 复制链接 ★★☆☆☆ 较简单 │
│ 生成分享图 ★★★☆☆ 中等 │
│ 朋友圈分享 ★★★★☆ 较复杂 │
└─────────────────────────────────┘
二、环境准备和基础配置
2.1 项目结构
share-feature-demo/
├── pages/
│ ├── share/
│ │ ├── index.js # 分享页面逻辑
│ │ ├── index.wxml # 分享页面结构
│ │ ├── index.wxss # 分享页面样式
│ │ └── index.json # 页面配置文件
│ └── detail/
│ └── … # 详情页
├── components/
│ ├── share-modal/ # 分享模态框组件
│ ├── share-poster/ # 分享海报组件
│ └── share-menu/ # 分享菜单组件
├── utils/
│ ├── share.js # 分享工具类
│ ├── canvas.js # Canvas绘图工具
│ └── qrcode.js # 二维码生成工具
├── images/ # 图片资源
└── app.js # 全局配置
2.2 基础配置
// app.json 基础配置
{
"pages": [
"pages/share/index",
"pages/detail/index"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "分享功能演示",
"navigationBarTextStyle": "black",
"navigationStyle": "default"
},
"requiredBackgroundModes": [
"audio"
],
"permission": {
"scope.writePhotosAlbum": {
"desc": "需要保存图片到相册权限"
}
},
"usingComponents": {},
"sitemapLocation": "sitemap.json"
}
三、转发给好友/群功能实现
3.1 页面级分享配置
// pages/detail/index.js – 页面分享配置
Page({
data: {
articleId: '123',
articleTitle: '这是一篇测试文章',
articleDesc: '文章详细描述内容…',
coverImage: 'https://example.com/cover.jpg'
},
onLoad(options) {
// 获取文章ID
this.setData({
articleId: options.id || '123'
});
},
/**
* 监听用户点击右上角菜单的「转发」按钮
* 官方文档:https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html#onShareAppMessage-Object-object
*/
onShareAppMessage() {
const { articleId, articleTitle, articleDesc, coverImage } = this.data;
return {
title: articleTitle,
path: `/pages/detail/index?id=${articleId}`,
imageUrl: coverImage,
desc: articleDesc,
success: (res) => {
console.log('转发成功', res);
// 可在此处添加转发成功的数据统计
this.reportShareSuccess('friend', articleId);
},
fail: (err) => {
console.error('转发失败', err);
}
};
},
/**
* 监听用户点击右上角菜单的「分享到朋友圈」按钮
* 注意:此功能需基础库 2.11.3 以上支持
*/
onShareTimeline() {
const { articleId, articleTitle, coverImage } = this.data;
return {
title: articleTitle,
query: `id=${articleId}`,
imageUrl: coverImage
};
},
/**
* 自定义分享按钮 – 页面内按钮触发分享
*/
onCustomShare() {
const { articleId, articleTitle, articleDesc, coverImage } = this.data;
// 显示分享菜单
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline'],
success: () => {
console.log('显示分享菜单成功');
},
fail: (err) => {
console.error('显示分享菜单失败', err);
}
});
},
/**
* 统计分享数据
*/
reportShareSuccess(type, articleId) {
wx.request({
url: 'https://your-api.com/report/share',
method: 'POST',
data: {
type: type,
articleId: articleId,
userId: wx.getStorageSync('userId'),
timestamp: Date.now()
}
});
}
});
3.2 自定义转发按钮
<!– pages/detail/index.wxml –>
<view class="container">
<!– 文章内容 –>
<view class="article-content">
<text class="title">{{articleTitle}}</text>
<text class="content">{{articleContent}}</text>
</view>
<!– 分享操作区域 –>
<view class="share-actions">
<!– 自定义分享按钮 –>
<button class="share-btn" open-type="share">
<image src="/images/share-icon.png" class="share-icon"></image>
<text>分享给好友</text>
</button>
<!– 朋友圈分享 –>
<button class="share-timeline-btn" bindtap="shareToTimeline">
<image src="/images/timeline-icon.png" class="share-icon"></image>
<text>分享到朋友圈</text>
</button>
<!– 复制链接 –>
<button class="copy-link-btn" bindtap="copyPageLink">
<image src="/images/link-icon.png" class="share-icon"></image>
<text>复制链接</text>
</button>
</view>
<!– 分享弹窗 –>
<share-modal
id="shareModal"
bind:onShareFriend="onShareFriend"
bind:onShareTimeline="onShareToTimeline"
bind:onCopyLink="onCopyPageLink"
/>
</view>
/* pages/detail/index.wxss */
.share-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
padding: 20rpx 0;
background: #ffffff;
box-shadow: 0 -2rpx 20rpx rgba(0, 0, 0, 0.1);
}
.share-btn,
.share-timeline-btn,
.copy-link-btn {
display: flex;
flex-direction: column;
align-items: center;
background: transparent;
border: none;
padding: 0;
margin: 0;
}
.share-btn::after,
.share-timeline-btn::after,
.copy-link-btn::after {
border: none;
}
.share-icon {
width: 60rpx;
height: 60rpx;
margin-bottom: 10rpx;
}
.share-btn text,
.share-timeline-btn text,
.copy-link-btn text {
font-size: 24rpx;
color: #666;
}
四、朋友圈分享功能实现(核心难点)
4.1 朋友圈分享的限制与解决方案
限制分析:
解决方案:
4.2 Canvas 生成分享图片
// utils/share.js – 分享工具类
const ShareUtils = {
/**
* 生成朋友圈分享图片
* @param {Object} params 分享参数
* @returns {Promise} 图片临时路径
*/
generateShareImage(params) {
return new Promise((resolve, reject) => {
const {
title = '分享标题',
description = '分享描述',
qrCodeUrl = '',
backgroundImage = '',
avatarUrl = '',
userName = '微信用户'
} = params;
// 获取系统信息,用于适配不同设备
wx.getSystemInfo({
success: (sysInfo) => {
const { windowWidth, pixelRatio } = sysInfo;
const canvasWidth = 750; // 设计稿宽度
const canvasHeight = 1334; // 设计稿高度
const scale = windowWidth / canvasWidth; // 缩放比例
// 创建Canvas上下文
const ctx = wx.createCanvasContext('shareCanvas', this);
// 1. 绘制背景
if (backgroundImage) {
ctx.drawImage(backgroundImage, 0, 0, canvasWidth, canvasHeight);
} else {
// 纯色背景
ctx.setFillStyle('#ffffff');
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
}
// 2. 绘制顶部渐变遮罩
const gradient = ctx.createLinearGradient(0, 0, 0, 300);
gradient.addColorStop(0, 'rgba(0, 0, 0, 0.6)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.setFillStyle(gradient);
ctx.fillRect(0, 0, canvasWidth, 300);
// 3. 绘制标题
ctx.setFontSize(40);
ctx.setFillStyle('#ffffff');
ctx.setTextAlign('center');
this.drawText(ctx, title, canvasWidth / 2, 200, 40, canvasWidth – 200);
// 4. 绘制描述
ctx.setFontSize(28);
ctx.setFillStyle('#f0f0f0');
this.drawText(ctx, description, canvasWidth / 2, 280, 28, canvasWidth – 200);
// 5. 绘制用户信息
if (avatarUrl) {
// 绘制圆形头像
ctx.save();
ctx.beginPath();
ctx.arc(100, 500, 40, 0, 2 * Math.PI);
ctx.clip();
ctx.drawImage(avatarUrl, 60, 460, 80, 80);
ctx.restore();
}
// 6. 绘制用户名
ctx.setFontSize(32);
ctx.setFillStyle('#333333');
ctx.setTextAlign('left');
ctx.fillText(userName, 160, 530);
// 7. 绘制时间
ctx.setFontSize(24);
ctx.setFillStyle('#999999');
const dateStr = this.formatDate(new Date());
ctx.fillText(dateStr, 160, 570);
// 8. 绘制小程序码
if (qrCodeUrl) {
const qrSize = 200;
const qrX = canvasWidth – qrSize – 50;
const qrY = canvasHeight – qrSize – 50;
// 绘制二维码背景
ctx.setFillStyle('#ffffff');
ctx.fillRect(qrX – 10, qrY – 10, qrSize + 20, qrSize + 20);
// 绘制二维码
ctx.drawImage(qrCodeUrl, qrX, qrY, qrSize, qrSize);
// 绘制提示文字
ctx.setFontSize(24);
ctx.setFillStyle('#666666');
ctx.setTextAlign('center');
ctx.fillText('长按识别小程序码', qrX + qrSize / 2, qrY + qrSize + 40);
}
// 9. 绘制底部提示
ctx.setFontSize(28);
ctx.setFillStyle('#333333');
ctx.setTextAlign('center');
ctx.fillText('分享自小程序', canvasWidth / 2, canvasHeight – 180);
// 绘制完成
ctx.draw(false, () => {
// 将Canvas内容生成图片
wx.canvasToTempFilePath({
canvasId: 'shareCanvas',
quality: 1,
destWidth: canvasWidth * pixelRatio,
destHeight: canvasHeight * pixelRatio,
success: (res) => {
console.log('图片生成成功', res.tempFilePath);
resolve(res.tempFilePath);
},
fail: (err) => {
console.error('图片生成失败', err);
reject(err);
}
}, this);
});
},
fail: (err) => {
reject(err);
}
});
});
},
/**
* 绘制多行文本
* @param {Object} ctx Canvas上下文
* @param {String} text 文本内容
* @param {Number} x 起始x坐标
* @param {Number} y 起始y坐标
* @param {Number} lineHeight 行高
* @param {Number} maxWidth 最大宽度
*/
drawText(ctx, text, x, y, lineHeight, maxWidth) {
const lines = [];
let currentLine = '';
// 分割文本为多行
for (let char of text) {
const testLine = currentLine + char;
const metrics = ctx.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxWidth && currentLine !== '') {
lines.push(currentLine);
currentLine = char;
} else {
currentLine = testLine;
}
}
if (currentLine !== '') {
lines.push(currentLine);
}
// 绘制每一行
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i], x, y + (i * lineHeight));
}
},
/**
* 格式化日期
*/
formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}–${month}–${day}`;
},
/**
* 保存图片到相册
*/
saveImageToPhotosAlbum(tempFilePath) {
return new Promise((resolve, reject) => {
// 检查权限
wx.getSetting({
success: (res) => {
if (!res.authSetting['scope.writePhotosAlbum']) {
// 未授权,申请权限
wx.authorize({
scope: 'scope.writePhotosAlbum',
success: () => {
this.doSaveImage(tempFilePath, resolve, reject);
},
fail: () => {
// 引导用户手动开启权限
wx.showModal({
title: '提示',
content: '需要您授权保存图片到相册',
confirmText: '去设置',
success: (modalRes) => {
if (modalRes.confirm) {
wx.openSetting({
success: (settingRes) => {
if (settingRes.authSetting['scope.writePhotosAlbum']) {
this.doSaveImage(tempFilePath, resolve, reject);
}
}
});
}
}
});
}
});
} else {
// 已授权,直接保存
this.doSaveImage(tempFilePath, resolve, reject);
}
}
});
});
},
/**
* 执行保存图片
*/
doSaveImage(tempFilePath, resolve, reject) {
wx.saveImageToPhotosAlbum({
filePath: tempFilePath,
success: (res) => {
console.log('图片保存成功', res);
wx.showToast({
title: '图片已保存到相册',
icon: 'success',
duration: 2000
});
resolve(res);
},
fail: (err) => {
console.error('图片保存失败', err);
wx.showToast({
title: '保存失败,请重试',
icon: 'none'
});
reject(err);
}
});
}
};
module.exports = ShareUtils;
4.3 朋友圈分享页面实现
// pages/share/index.js
import ShareUtils from '../../utils/share';
import QRCode from '../../utils/qrcode';
Page({
data: {
// 分享数据
shareData: {
title: '',
description: '',
imageUrl: '',
path: ''
},
// Canvas相关
canvasWidth: 750,
canvasHeight: 1334,
// 生成状态
isGenerating: false,
// 生成的图片路径
generatedImage: '',
// 用户信息
userInfo: {},
// 小程序码
qrCodeUrl: ''
},
onLoad(options) {
// 从参数或全局获取分享数据
this.initShareData(options);
// 获取用户信息
this.getUserInfo();
// 生成小程序码
this.generateQRCode();
},
/**
* 初始化分享数据
*/
initShareData(options) {
const app = getApp();
const shareData = app.globalData.shareData || {};
this.setData({
shareData: {
title: options.title || shareData.title || '分享标题',
description: options.desc || shareData.description || '分享描述',
imageUrl: options.image || shareData.imageUrl || '',
path: options.path || shareData.path || 'pages/index/index'
}
});
},
/**
* 获取用户信息
*/
getUserInfo() {
// 尝试从缓存获取
const userInfo = wx.getStorageSync('userInfo');
if (userInfo) {
this.setData({ userInfo });
} else {
// 请求用户授权
wx.getUserProfile({
desc: '用于生成个性化分享图',
success: (res) => {
this.setData({ userInfo: res.userInfo });
wx.setStorageSync('userInfo', res.userInfo);
},
fail: () => {
// 使用默认信息
this.setData({
userInfo: {
nickName: '微信用户',
avatarUrl: '/images/default-avatar.png'
}
});
}
});
}
},
/**
* 生成小程序码
*/
generateQRCode() {
const { shareData } = this.data;
// 生成小程序码URL
const qrCodeData = {
scene: `id=${shareData.scene || 'default'}`,
page: shareData.path,
width: 200
};
// 调用后端API生成小程序码
wx.request({
url: 'https://your-api.com/generate-qrcode',
method: 'POST',
data: qrCodeData,
success: (res) => {
if (res.data.success) {
this.setData({ qrCodeUrl: res.data.qrCodeUrl });
}
},
fail: () => {
// 前端生成简单二维码
const qrCodeBase64 = QRCode.generateQRCode(
`pages/index/index?scene=${qrCodeData.scene}`,
200
);
this.setData({ qrCodeUrl: qrCodeBase64 });
}
});
},
/**
* 生成分享图片
*/
async generateShareImage() {
if (this.data.isGenerating) {
return;
}
this.setData({ isGenerating: true });
try {
const { shareData, userInfo, qrCodeUrl } = this.data;
// 显示加载提示
wx.showLoading({
title: '正在生成图片…',
mask: true
});
// 确保图片资源已加载
await this.preloadImages();
// 生成分享图片
const imagePath = await ShareUtils.generateShareImage({
title: shareData.title,
description: shareData.description,
qrCodeUrl: qrCodeUrl,
backgroundImage: shareData.imageUrl,
avatarUrl: userInfo.avatarUrl,
userName: userInfo.nickName
});
this.setData({
generatedImage: imagePath,
isGenerating: false
});
wx.hideLoading();
// 显示预览
this.previewImage(imagePath);
} catch (error) {
console.error('生成图片失败', error);
this.setData({ isGenerating: false });
wx.hideLoading();
wx.showToast({
title: '生成失败,请重试',
icon: 'none'
});
}
},
/**
* 预加载图片
*/
preloadImages() {
const { shareData, qrCodeUrl } = this.data;
const imageUrls = [];
if (shareData.imageUrl) {
imageUrls.push(shareData.imageUrl);
}
if (qrCodeUrl) {
imageUrls.push(qrCodeUrl);
}
if (imageUrls.length === 0) {
return Promise.resolve();
}
return Promise.all(
imageUrls.map(url => {
return new Promise((resolve) => {
wx.getImageInfo({
src: url,
success: resolve,
fail: resolve // 即使失败也继续
});
});
})
);
},
/**
* 预览图片
*/
previewImage(imagePath) {
wx.previewImage({
urls: [imagePath],
current: imagePath
});
},
/**
* 保存图片到相册
*/
async saveToAlbum() {
const { generatedImage } = this.data;
if (!generatedImage) {
wx.showToast({
title: '请先生成图片',
icon: 'none'
});
return;
}
try {
await ShareUtils.saveImageToPhotosAlbum(generatedImage);
// 保存成功后的操作
this.onSaveSuccess();
} catch (error) {
console.error('保存失败', error);
}
},
/**
* 保存成功后的回调
*/
onSaveSuccess() {
// 显示引导分享的弹窗
wx.showModal({
title: '图片已保存',
content: '请打开微信朋友圈,选择刚才保存的图片进行分享',
showCancel: false,
confirmText: '知道了',
success: () => {
// 记录分享行为
this.reportShareAction('timeline');
}
});
},
/**
* 上报分享行为
*/
reportShareAction(type) {
const { shareData } = this.data;
wx.request({
url: 'https://your-api.com/track/share',
method: 'POST',
data: {
type: type,
contentId: shareData.id,
timestamp: Date.now()
}
});
},
/**
* 分享给好友
*/
shareToFriend() {
const { shareData } = this.data;
wx.shareAppMessage({
title: shareData.title,
path: shareData.path,
imageUrl: shareData.imageUrl,
success: () => {
this.reportShareAction('friend');
}
});
}
});
4.4 朋友圈分享页面布局
<!– pages/share/index.wxml –>
<view class="share-page">
<!– Canvas区域(隐藏) –>
<canvas
canvas-id="shareCanvas"
class="share-canvas"
style="width: {{canvasWidth}}px; height: {{canvasHeight}}px;"
></canvas>
<!– 内容区域 –>
<view class="content">
<!– 分享预览 –>
<view class="preview-section" wx:if="{{generatedImage}}">
<view class="section-title">分享预览</view>
<image
src="{{generatedImage}}"
class="preview-image"
mode="widthFix"
bindtap="previewImage"
></image>
</view>
<!– 分享信息 –>
<view class="info-section">
<view class="section-title">分享内容</view>
<view class="info-item">
<text class="info-label">标题:</text>
<text class="info-value">{{shareData.title}}</text>
</view>
<view class="info-item">
<text class="info-label">描述:</text>
<text class="info-value">{{shareData.description}}</text>
</view>
</view>
<!– 操作按钮 –>
<view class="action-section">
<!– 生成图片按钮 –>
<button
class="btn generate-btn"
bindtap="generateShareImage"
disabled="{{isGenerating}}"
>
<text wx:if="{{!isGenerating}}">生成分享图片</text>
<text wx:else>生成中…</text>
</button>
<!– 保存图片按钮 –>
<button
class="btn save-btn"
bindtap="saveToAlbum"
wx:if="{{generatedImage}}"
>
保存到相册
</button>
<!– 分享给好友按钮 –>
<button
class="btn friend-btn"
bindtap="shareToFriend"
open-type="share"
>
分享给好友
</button>
<!– 复制链接按钮 –>
<button
class="btn link-btn"
bindtap="copyShareLink"
>
复制链接
</button>
</view>
<!– 使用说明 –>
<view class="guide-section">
<view class="section-title">朋友圈分享步骤</view>
<view class="steps">
<view class="step">
<text class="step-number">1</text>
<text class="step-text">点击"生成分享图片"</text>
</view>
<view class="step">
<text class="step-number">2</text>
<text class="step-text">点击"保存到相册"</text>
</view>
<view class="step">
<text class="step-number">3</text>
<text class="step-text">打开微信朋友圈</text>
</view>
<view class="step">
<text class="step-number">4</text>
<text class="step-text">选择刚才保存的图片发布</text>
</view>
</view>
</view>
</view>
</view>
/* pages/share/index.wxss */
.share-page {
min-height: 100vh;
background: #f5f5f5;
}
/* 隐藏Canvas */
.share-canvas {
position: fixed;
left: -9999px;
top: -9999px;
}
.content {
padding: 30rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
padding-bottom: 10rpx;
border-bottom: 2rpx solid #eee;
}
/* 预览区域 */
.preview-section {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.preview-image {
width: 100%;
border-radius: 8rpx;
display: block;
}
/* 信息区域 */
.info-section {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.info-item {
display: flex;
margin-bottom: 20rpx;
}
.info-label {
font-size: 28rpx;
color: #666;
width: 120rpx;
flex-shrink: 0;
}
.info-value {
font-size: 28rpx;
color: #333;
flex: 1;
}
/* 操作按钮区域 */
.action-section {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.btn {
height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
border: none;
}
.btn::after {
border: none;
}
.generate-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.generate-btn:disabled {
background: #cccccc;
color: #999999;
}
.save-btn {
background: #07c160;
color: white;
}
.friend-btn {
background: #1aad19;
color: white;
}
.link-btn {
background: #10aeff;
color: white;
}
/* 引导区域 */
.guide-section {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.steps {
margin-top: 20rpx;
}
.step {
display: flex;
align-items: center;
margin-bottom: 25rpx;
}
.step-number {
width: 50rpx;
height: 50rpx;
background: #667eea;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
margin-right: 20rpx;
flex-shrink: 0;
}
.step-text {
font-size: 28rpx;
color: #333;
}
五、复制链接功能实现
5.1 链接生成工具
// utils/link.js – 链接生成工具
const LinkUtils = {
/**
* 生成小程序页面链接
* @param {String} path 页面路径
* @param {Object} params 页面参数
* @returns {String} 完整链接
*/
generateMiniProgramLink(path, params = {}) {
// 构建query字符串
const queryString = Object.keys(params)
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&');
// 返回小程序路径格式
return queryString ? `/${path}?${queryString}` : `/${path}`;
},
/**
* 生成网页链接(用于H5分享)
* @param {String} path 页面路径
* @param {Object} params 页面参数
* @returns {String} 网页链接
*/
generateWebLink(path, params = {}) {
const baseUrl = 'https://your-domain.com/miniprogram';
const queryString = Object.keys(params)
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&');
return queryString ? `${baseUrl}/${path}?${queryString}` : `${baseUrl}/${path}`;
},
/**
* 生成带分享者信息的链接
* @param {String} path 页面路径
* @param {Object} params 页面参数
* @param {String} shareUserId 分享者用户ID
* @returns {String} 带分享参数的链接
*/
generateShareLink(path, params = {}, shareUserId = '') {
const linkParams = {
…params,
_t: Date.now(), // 时间戳,防缓存
_v: '1.0' // 版本号
};
if (shareUserId) {
linkParams._s = shareUserId; // 分享者标识
linkParams._st = this.generateShareToken(shareUserId); // 分享令牌
}
return this.generateMiniProgramLink(path, linkParams);
},
/**
* 生成分享令牌(防篡改)
*/
generateShareToken(userId) {
const timestamp = Date.now();
const str = `${userId}_${timestamp}_${process.env.SHARE_SECRET || 'default_secret'}`;
// 简单hash,实际项目中应该使用更安全的算法
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) – hash) + str.charCodeAt(i);
hash = hash & hash;
}
return Math.abs(hash).toString(16).substr(0, 8);
},
/**
* 验证分享令牌
*/
verifyShareToken(userId, timestamp, token) {
const expectedToken = this.generateShareToken(userId, timestamp);
return token === expectedToken;
},
/**
* 复制链接到剪贴板
*/
copyToClipboard(text, showToast = true) {
return new Promise((resolve, reject) => {
wx.setClipboardData({
data: text,
success: () => {
if (showToast) {
wx.showToast({
title: '链接已复制',
icon: 'success',
duration: 2000
});
}
resolve();
},
fail: (err) => {
console.error('复制失败', err);
if (showToast) {
wx.showToast({
title: '复制失败',
icon: 'none'
});
}
reject(err);
}
});
});
},
/**
* 获取当前页面链接(包括参数)
*/
getCurrentPageLink() {
const pages = getCurrentPages();
const currentPage = pages[pages.length – 1];
if (!currentPage) {
return '';
}
const { route, options } = currentPage;
return this.generateMiniProgramLink(route, options);
},
/**
* 解析链接参数
*/
parseLink(link) {
const url = new URL(link, 'https://dummy.com');
const params = {};
url.searchParams.forEach((value, key) => {
params[key] = value;
});
return {
path: url.pathname.replace(/^\\//, ''),
params: params
};
}
};
module.exports = LinkUtils;
5.2 复制链接功能集成
// 在需要复制链接的页面中使用
Page({
data: {
shareLink: ''
},
onLoad() {
this.generateShareLink();
},
/**
* 生成分享链接
*/
generateShareLink() {
const LinkUtils = require('../../utils/link');
const userId = wx.getStorageSync('userId') || '';
const shareLink = LinkUtils.generateShareLink(
'pages/detail/index',
{ id: '123', from: 'share' },
userId
);
this.setData({ shareLink });
},
/**
* 复制页面链接
*/
copyPageLink() {
const LinkUtils = require('../../utils/link');
const link = this.data.shareLink || LinkUtils.getCurrentPageLink();
LinkUtils.copyToClipboard(link).then(() => {
// 复制成功后的操作
this.onCopySuccess();
}).catch(err => {
console.error('复制失败', err);
});
},
/**
* 复制短链接
*/
async copyShortLink() {
try {
// 生成短链接
const shortLink = await this.generateShortLink();
const LinkUtils = require('../../utils/link');
await LinkUtils.copyToClipboard(shortLink);
this.onCopySuccess();
} catch (error) {
console.error('生成短链接失败', error);
// 降级:复制原始链接
this.copyPageLink();
}
},
/**
* 生成短链接
*/
generateShortLink() {
return new Promise((resolve, reject) => {
wx.request({
url: 'https://your-api.com/shorten',
method: 'POST',
data: {
long_url: this.data.shareLink
},
success: (res) => {
if (res.data.success) {
resolve(res.data.short_url);
} else {
reject(new Error(res.data.message));
}
},
fail: reject
});
});
},
/**
* 复制成功回调
*/
onCopySuccess() {
// 记录复制行为
this.reportCopyAction();
// 显示引导信息
wx.showModal({
title: '链接已复制',
content: '您可以粘贴到浏览器中打开,或发送给好友',
showCancel: false,
confirmText: '知道了'
});
},
/**
* 上报复制行为
*/
reportCopyAction() {
wx.request({
url: 'https://your-api.com/track/copy',
method: 'POST',
data: {
type: 'link_copy',
link: this.data.shareLink,
timestamp: Date.now()
}
});
}
});
5.3 复制链接组件
<!– components/copy-link/index.wxml –>
<view class="copy-link-container">
<!– 链接显示区域 –>
<view class="link-display">
<view class="link-label">分享链接:</view>
<view class="link-content">
<text class="link-text">{{shortLink || fullLink}}</text>
<button
class="copy-btn"
bindtap="onCopy"
size="mini"
>
<image src="/images/copy-icon.png" class="copy-icon"></image>
复制
</button>
</view>
</view>
<!– 链接选项 –>
<view class="link-options" wx:if="{{showOptions}}">
<view class="option-item" bindtap="copyFullLink">
<image src="/images/link-icon.png" class="option-icon"></image>
<text>复制完整链接</text>
</view>
<view class="option-item" bindtap="copyShortLink" wx:if="{{shortLink}}">
<image src="/images/short-link-icon.png" class="option-icon"></image>
<text>复制短链接</text>
</view>
<view class="option-item" bindtap="generateNewLink">
<image src="/images/refresh-icon.png" class="option-icon"></image>
<text>生成新链接</text>
</view>
</view>
</view>
// components/copy-link/index.js
const LinkUtils = require('../../utils/link');
Component({
properties: {
// 页面路径
pagePath: {
type: String,
value: ''
},
// 页面参数
pageParams: {
type: Object,
value: {}
},
// 是否显示选项
showOptions: {
type: Boolean,
value: true
},
// 自动生成短链接
autoGenerateShortLink: {
type: Boolean,
value: false
}
},
data: {
fullLink: '',
shortLink: '',
isGenerating: false
},
lifetimes: {
attached() {
this.generateLinks();
}
},
methods: {
/**
* 生成链接
*/
generateLinks() {
const { pagePath, pageParams, autoGenerateShortLink } = this.properties;
const userId = wx.getStorageSync('userId') || '';
// 生成完整链接
const fullLink = LinkUtils.generateShareLink(
pagePath || 'pages/index/index',
pageParams,
userId
);
this.setData({ fullLink });
// 自动生成短链接
if (autoGenerateShortLink) {
this.generateShortLink(fullLink);
}
},
/**
* 生成短链接
*/
async generateShortLink(fullLink) {
this.setData({ isGenerating: true });
try {
const shortLink = await this.fetchShortLink(fullLink);
this.setData({ shortLink, isGenerating: false });
} catch (error) {
console.error('生成短链接失败', error);
this.setData({ isGenerating: false });
}
},
/**
* 请求短链接
*/
fetchShortLink(fullLink) {
return new Promise((resolve, reject) => {
wx.request({
url: 'https://your-api.com/shorten',
method: 'POST',
data: { long_url: fullLink },
success: (res) => {
if (res.data.success) {
resolve(res.data.short_url);
} else {
reject(new Error(res.data.message));
}
},
fail: reject
});
});
},
/**
* 复制链接
*/
onCopy() {
const linkToCopy = this.data.shortLink || this.data.fullLink;
LinkUtils.copyToClipboard(linkToCopy).then(() => {
this.triggerEvent('copysuccess', { link: linkToCopy });
}).catch(err => {
this.triggerEvent('copyerror', { error: err });
});
},
/**
* 复制完整链接
*/
copyFullLink() {
LinkUtils.copyToClipboard(this.data.fullLink).then(() => {
this.triggerEvent('copysuccess', { link: this.data.fullLink, type: 'full' });
});
},
/**
* 复制短链接
*/
copyShortLink() {
if (!this.data.shortLink) {
wx.showToast({
title: '正在生成短链接…',
icon: 'none'
});
return;
}
LinkUtils.copyToClipboard(this.data.shortLink).then(() => {
this.triggerEvent('copysuccess', { link: this.data.shortLink, type: 'short' });
});
},
/**
* 生成新链接
*/
generateNewLink() {
this.generateLinks();
wx.showToast({
title: '已生成新链接',
icon: 'success'
});
}
}
});
六、高级功能实现
6.1 带参数的分享统计
// services/shareService.js – 分享统计服务
class ShareService {
constructor() {
this.baseUrl = 'https://your-api.com';
}
/**
* 记录分享行为
*/
async recordShare(data) {
const { type, contentId, shareUserId, targetUserId = '', platform = 'wechat' } = data;
return await this.request('/share/record', 'POST', {
type,
content_id: contentId,
share_user_id: shareUserId,
target_user_id: targetUserId,
platform,
timestamp: Date.now(),
ip: await this.getClientIP(),
user_agent: this.getUserAgent()
});
}
/**
* 获取分享统计数据
*/
async getShareStats(contentId, startDate, endDate) {
return await this.request('/share/stats', 'GET', {
content_id: contentId,
start_date: startDate,
end_date: endDate
});
}
/**
* 获取热门分享内容
*/
async getHotShares(limit = 10) {
return await this.request('/share/hot', 'GET', { limit });
}
/**
* 生成分享奖励
*/
async generateShareReward(shareRecordId) {
return await this.request('/share/reward', 'POST', {
share_record_id: shareRecordId
});
}
/**
* 验证分享来源
*/
async verifyShareSource(shareToken) {
return await this.request('/share/verify', 'POST', {
share_token: shareToken
});
}
/**
* 统一请求方法
*/
async request(endpoint, method, data) {
return new Promise((resolve, reject) => {
wx.request({
url: `${this.baseUrl}${endpoint}`,
method,
data,
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${wx.getStorageSync('token')}`
},
success: (res) => {
if (res.data.code === 200) {
resolve(res.data.data);
} else {
reject(new Error(res.data.message));
}
},
fail: reject
});
});
}
/**
* 获取客户端IP(通过服务端转发)
*/
async getClientIP() {
try {
const res = await this.request('/utils/ip', 'GET');
return res.ip;
} catch (error) {
return '0.0.0.0';
}
}
/**
* 获取用户代理
*/
getUserAgent() {
const systemInfo = wx.getSystemInfoSync();
return JSON.stringify({
platform: systemInfo.platform,
system: systemInfo.system,
version: systemInfo.version,
model: systemInfo.model,
sdkVersion: systemInfo.SDKVersion
});
}
}
module.exports = ShareService;
6.2 分享海报模板系统
// services/posterService.js – 海报模板服务
class PosterService {
constructor() {
this.templates = {
'default': {
width: 750,
height: 1334,
backgroundColor: '#ffffff',
elements: [
{
type: 'image',
url: '',
x: 0,
y: 0,
width: 750,
height: 1334,
zIndex: 0
},
{
type: 'text',
content: '',
x: 375,
y: 200,
fontSize: 40,
color: '#ffffff',
fontWeight: 'bold',
textAlign: 'center',
maxWidth: 650,
zIndex: 10
},
{
type: 'avatar',
url: '',
x: 100,
y: 500,
width: 80,
height: 80,
borderRadius: 40,
zIndex: 20
}
]
},
'simple': {
width: 600,
height: 800,
backgroundColor: '#f8f9fa',
elements: []
}
};
}
/**
* 根据模板生成海报配置
*/
generateConfig(templateName, data) {
const template = this.templates[templateName] || this.templates['default'];
const config = JSON.parse(JSON.stringify(template));
// 填充数据
this.fillTemplateData(config, data);
return config;
}
/**
* 填充模板数据
*/
fillTemplateData(config, data) {
config.elements.forEach(element => {
switch (element.type) {
case 'text':
if (data.title && element.content.includes('{{title}}')) {
element.content = element.content.replace('{{title}}', data.title);
}
if (data.description && element.content.includes('{{description}}')) {
element.content = element.content.replace('{{description}}', data.description);
}
break;
case 'image':
if (data.imageUrl) {
element.url = data.imageUrl;
}
break;
case 'avatar':
if (data.avatarUrl) {
element.url = data.avatarUrl;
}
break;
case 'qrcode':
if (data.qrCodeUrl) {
element.url = data.qrCodeUrl;
}
break;
}
});
return config;
}
/**
* 根据配置绘制海报
*/
async drawPoster(config, canvasId) {
const ctx = wx.createCanvasContext(canvasId);
// 绘制背景
ctx.setFillStyle(config.backgroundColor);
ctx.fillRect(0, 0, config.width, config.height);
// 按zIndex排序
const sortedElements = […config.elements].sort((a, b) => a.zIndex – b.zIndex);
// 绘制每个元素
for (const element of sortedElements) {
await this.drawElement(ctx, element);
}
// 绘制完成
ctx.draw();
// 生成图片
return new Promise((resolve, reject) => {
setTimeout(() => {
wx.canvasToTempFilePath({
canvasId: canvasId,
success: resolve,
fail: reject
});
}, 500);
});
}
/**
* 绘制单个元素
*/
async drawElement(ctx, element) {
switch (element.type) {
case 'text':
this.drawText(ctx, element);
break;
case 'image':
await this.drawImage(ctx, element);
break;
case 'qrcode':
await this.drawQRCode(ctx, element);
break;
}
}
/**
* 绘制文本
*/
drawText(ctx, element) {
ctx.setFontSize(element.fontSize);
ctx.setFillStyle(element.color);
ctx.setTextAlign(element.textAlign);
if (element.fontWeight === 'bold') {
ctx.font = `bold ${element.fontSize}px sans-serif`;
}
// 处理多行文本
const lines = this.wrapText(ctx, element.content, element.maxWidth, element.fontSize);
lines.forEach((line, index) => {
ctx.fillText(line, element.x, element.y + (index * element.fontSize * 1.2));
});
}
/**
* 绘制图片
*/
drawImage(ctx, element) {
return new Promise((resolve) => {
wx.getImageInfo({
src: element.url,
success: (res) => {
// 绘制圆形图片
if (element.borderRadius > 0) {
ctx.save();
ctx.beginPath();
ctx.arc(
element.x + element.width / 2,
element.y + element.height / 2,
Math.min(element.width, element.height) / 2,
0,
2 * Math.PI
);
ctx.clip();
ctx.drawImage(res.path, element.x, element.y, element.width, element.height);
ctx.restore();
} else {
ctx.drawImage(res.path, element.x, element.y, element.width, element.height);
}
resolve();
},
fail: () => {
// 图片加载失败,绘制占位符
ctx.setFillStyle('#cccccc');
ctx.fillRect(element.x, element.y, element.width, element.height);
resolve();
}
});
});
}
/**
* 绘制二维码
*/
drawQRCode(ctx, element) {
return this.drawImage(ctx, element);
}
/**
* 文本换行处理
*/
wrapText(ctx, text, maxWidth, fontSize) {
const lines = [];
let currentLine = '';
for (let char of text) {
const testLine = currentLine + char;
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && currentLine !== '') {
lines.push(currentLine);
currentLine = char;
} else {
currentLine = testLine;
}
}
if (currentLine !== '') {
lines.push(currentLine);
}
return lines;
}
}
module.exports = PosterService;
七、性能优化和最佳实践
7.1 Canvas 性能优化
// utils/canvasOptimizer.js – Canvas性能优化
class CanvasOptimizer {
/**
* 批量绘制优化
*/
static batchDraw(ctx, drawCalls) {
// 合并相似操作
const operations = this.mergeOperations(drawCalls);
operations.forEach(op => {
switch (op.type) {
case 'rect':
ctx.setFillStyle(op.style);
ctx.fillRect(op.x, op.y, op.width, op.height);
break;
case 'text':
ctx.setFontSize(op.fontSize);
ctx.setFillStyle(op.color);
ctx.fillText(op.text, op.x, op.y);
break;
case 'image':
ctx.drawImage(op.src, op.x, op.y, op.width, op.height);
break;
}
});
}
/**
* 合并相似操作
*/
static mergeOperations(drawCalls) {
const merged = [];
let currentRect = null;
let currentTextStyle = null;
drawCalls.forEach(call => {
if (call.type === 'rect' && call.style) {
if (currentRect && currentRect.style === call.style) {
// 合并相邻的相同样式矩形
// … 合并逻辑
} else {
merged.push(call);
currentRect = call;
}
} else {
merged.push(call);
currentRect = null;
}
});
return merged;
}
/**
* 图片预加载
*/
static preloadImages(imageUrls) {
return Promise.all(
imageUrls.map(url => {
return new Promise((resolve) => {
const img = wx.createImage();
img.src = url;
img.onload = resolve;
img.onerror = resolve;
});
})
);
}
/**
* 使用离屏Canvas
*/
static createOffscreenCanvas() {
// 微信小程序不支持真正的离屏Canvas
// 可以通过隐藏Canvas实现类似效果
return wx.createCanvasContext('offscreenCanvas');
}
/**
* 避免频繁重绘
*/
static debounceDraw(callback, delay = 100) {
let timer = null;
return function() {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
callback();
timer = null;
}, delay);
};
}
}
7.2 内存管理
// 分享图片内存管理
class ImageMemoryManager {
constructor() {
this.cachedImages = new Map();
this.maxCacheSize = 10;
}
/**
* 获取图片信息(带缓存)
*/
getImageInfo(src) {
return new Promise((resolve) => {
// 检查缓存
if (this.cachedImages.has(src)) {
resolve(this.cachedImages.get(src));
return;
}
wx.getImageInfo({
src,
success: (res) => {
// 加入缓存
this.addToCache(src, res);
resolve(res);
},
fail: () => {
// 返回默认图片信息
resolve({
path: src,
width: 100,
height: 100
});
}
});
});
}
/**
* 添加到缓存
*/
addToCache(src, imageInfo) {
if (this.cachedImages.size >= this.maxCacheSize) {
// 移除最久未使用的
const firstKey = this.cachedImages.keys().next().value;
this.cachedImages.delete(firstKey);
}
this.cachedImages.set(src, imageInfo);
}
/**
* 清理缓存
*/
clearCache() {
this.cachedImages.clear();
}
/**
* 清理临时文件
*/
clearTempFiles() {
const fs = wx.getFileSystemManager();
// 清理过期的临时文件
// 注意:微信小程序清理临时文件需要用户授权
}
}
八、错误处理和兼容性
8.1 错误处理
// utils/errorHandler.js – 错误处理
class ShareErrorHandler {
static handleError(error, context) {
console.error(`[Share Error] ${context}:`, error);
// 根据错误类型显示不同提示
if (error.errMsg) {
this.handleWxError(error, context);
} else if (error instanceof Error) {
this.handleJsError(error, context);
} else {
this.handleUnknownError(error, context);
}
// 上报错误
this.reportError(error, context);
}
static handleWxError(error, context) {
const errorMap = {
'saveImageToPhotosAlbum:fail auth deny': {
title: '保存失败',
message: '需要您授权保存图片到相册',
action: () => {
wx.openSetting({
success: (res) => {
if (res.authSetting['scope.writePhotosAlbum']) {
wx.showToast({
title: '授权成功,请重试',
icon: 'success'
});
}
}
});
}
},
'canvasToTempFilePath:fail': {
title: '生成图片失败',
message: '请确保Canvas已正确绘制',
action: null
}
};
const handler = errorMap[error.errMsg];
if (handler) {
wx.showModal({
title: handler.title,
content: handler.message,
success: (res) => {
if (res.confirm && handler.action) {
handler.action();
}
}
});
} else {
wx.showToast({
title: '操作失败,请重试',
icon: 'none'
});
}
}
static handleJsError(error, context) {
wx.showToast({
title: '发生错误,请重试',
icon: 'none'
});
}
static handleUnknownError(error, context) {
wx.showToast({
title: '未知错误',
icon: 'none'
});
}
static reportError(error, context) {
// 上报到监控平台
const errorData = {
type: 'share_error',
context,
error: error.message || error.errMsg || 'unknown',
stack: error.stack,
timestamp: Date.now(),
version: wx.getSystemInfoSync().SDKVersion
};
wx.request({
url: 'https://your-api.com/error/report',
method: 'POST',
data: errorData
});
}
}
8.2 兼容性处理
// utils/compatibility.js – 兼容性检查
class CompatibilityChecker {
/**
* 检查基础库版本
*/
static checkBaseLibrary(feature) {
const version = wx.getSystemInfoSync().SDKVersion;
const featureMap = {
'shareTimeline': '2.11.3',
'canvasToTempFilePath': '1.6.0',
'getUserProfile': '2.10.4'
};
const requiredVersion = featureMap[feature];
if (!requiredVersion) {
return true;
}
return this.compareVersion(version, requiredVersion) >= 0;
}
/**
* 比较版本号
*/
static compareVersion(v1, v2) {
const arr1 = v1.split('.').map(Number);
const arr2 = v2.split('.').map(Number);
for (let i = 0; i < Math.max(arr1.length, arr2.length); i++) {
const num1 = arr1[i] || 0;
const num2 = arr2[i] || 0;
if (num1 > num2) return 1;
if (num1 < num2) return –1;
}
return 0;
}
/**
* 获取功能支持情况
*/
static getFeatureSupport() {
const sysInfo = wx.getSystemInfoSync();
return {
// Canvas相关
canvas: this.checkBaseLibrary('canvasToTempFilePath'),
// 分享相关
shareTimeline: this.checkBaseLibrary('shareTimeline'),
// 用户信息
getUserProfile: this.checkBaseLibrary('getUserProfile'),
// 系统能力
canIUse: (api) => {
return wx.canIUse && wx.canIUse(api);
},
// 设备信息
device: {
platform: sysInfo.platform,
system: sysInfo.system,
model: sysInfo.model
}
};
}
/**
* 显示兼容性提示
*/
static showCompatibilityTip(feature) {
const tips = {
'shareTimeline': '朋友圈分享需要微信版本7.0.10及以上',
'canvas': '图片生成需要微信版本7.0.0及以上',
'getUserProfile': '获取用户信息需要微信版本7.0.9及以上'
};
const tip = tips[feature];
if (tip) {
wx.showModal({
title: '功能不可用',
content: tip,
showCancel: false,
confirmText: '知道了'
});
}
}
}
九、完整示例和测试
9.1 完整分享页面示例
// pages/share-demo/index.js
import ShareUtils from '../../utils/share';
import LinkUtils from '../../utils/link';
import ShareService from '../../services/shareService';
import CompatibilityChecker from '../../utils/compatibility';
Page({
data: {
// 分享内容
content: {
id: '123',
title: '测试分享内容',
description: '这是一个测试分享的描述信息',
imageUrl: 'https://example.com/image.jpg',
type: 'article'
},
// 用户信息
userInfo: {},
// 状态
isLoading: false,
generatedImage: '',
shareLink: '',
// 兼容性
isCompatible: true,
supportFeatures: {}
},
onLoad(options) {
this.initPage(options);
},
/**
* 初始化页面
*/
async initPage(options) {
// 检查兼容性
this.checkCompatibility();
// 获取内容
await this.fetchContent(options.id);
// 获取用户信息
await this.getUserInfo();
// 生成分享链接
this.generateShareLink();
},
/**
* 检查兼容性
*/
checkCompatibility() {
const supportFeatures = CompatibilityChecker.getFeatureSupport();
const isCompatible = supportFeatures.canvas && supportFeatures.shareTimeline;
this.setData({
isCompatible,
supportFeatures
});
if (!isCompatible) {
CompatibilityChecker.showCompatibilityTip('canvas');
}
},
/**
* 获取内容
*/
async fetchContent(contentId) {
this.setData({ isLoading: true });
try {
// 模拟API请求
const content = await this.mockFetchContent(contentId);
this.setData({ content, isLoading: false });
} catch (error) {
this.setData({ isLoading: false });
wx.showToast({
title: '加载失败',
icon: 'none'
});
}
},
/**
* 模拟获取内容
*/
mockFetchContent(id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id,
title: `测试内容 ${id}`,
description: '这是测试内容的详细描述信息',
imageUrl: '/images/default-cover.jpg',
type: 'article'
});
}, 500);
});
},
/**
* 获取用户信息
*/
async getUserInfo() {
try {
const userInfo = wx.getStorageSync('userInfo');
if (userInfo) {
this.setData({ userInfo });
return;
}
// 请求授权
const { userInfo } = await wx.getUserProfile({
desc: '用于生成个性化分享'
});
this.setData({ userInfo });
wx.setStorageSync('userInfo', userInfo);
} catch (error) {
// 使用默认用户信息
this.setData({
userInfo: {
nickName: '微信用户',
avatarUrl: '/images/default-avatar.png'
}
});
}
},
/**
* 生成分享链接
*/
generateShareLink() {
const { content, userInfo } = this.data;
const userId = userInfo.id || 'anonymous';
const shareLink = LinkUtils.generateShareLink(
'pages/detail/index',
{ id: content.id, type: content.type },
userId
);
this.setData({ shareLink });
},
/**
* 生成分享图片
*/
async onGenerateImage() {
if (!this.data.isCompatible) {
CompatibilityChecker.showCompatibilityTip('canvas');
return;
}
this.setData({ isLoading: true });
try {
const { content, userInfo } = this.data;
// 生成二维码
const qrCodeUrl = await this.generateQRCode();
// 生成分享图片
const imagePath = await ShareUtils.generateShareImage({
title: content.title,
description: content.description,
qrCodeUrl,
backgroundImage: content.imageUrl,
avatarUrl: userInfo.avatarUrl,
userName: userInfo.nickName
});
this.setData({
generatedImage: imagePath,
isLoading: false
});
// 预览图片
wx.previewImage({
urls: [imagePath]
});
} catch (error) {
console.error('生成图片失败', error);
this.setData({ isLoading: false });
ShareErrorHandler.handleError(error, 'generate_share_image');
}
},
/**
* 生成二维码
*/
async generateQRCode() {
return new Promise((resolve) => {
// 实际项目中应该调用后端API生成小程序码
// 这里使用简单二维码示例
resolve('/images/qrcode-demo.png');
});
},
/**
* 保存到相册
*/
async onSaveToAlbum() {
const { generatedImage } = this.data;
if (!generatedImage) {
wx.showToast({
title: '请先生成图片',
icon: 'none'
});
return;
}
try {
await ShareUtils.saveImageToPhotosAlbum(generatedImage);
// 记录分享行为
await this.recordShare('timeline');
// 显示引导
this.showTimelineGuide();
} catch (error) {
ShareErrorHandler.handleError(error, 'save_to_album');
}
},
/**
* 分享给好友
*/
onShareToFriend() {
const { content } = this.data;
wx.shareAppMessage({
title: content.title,
path: `/pages/detail/index?id=${content.id}`,
imageUrl: content.imageUrl,
success: async () => {
// 记录分享行为
await this.recordShare('friend');
wx.showToast({
title: '分享成功',
icon: 'success'
});
},
fail: (error) => {
ShareErrorHandler.handleError(error, 'share_to_friend');
}
});
},
/**
* 复制链接
*/
onCopyLink() {
const { shareLink } = this.data;
LinkUtils.copyToClipboard(shareLink).then(async () => {
// 记录复制行为
await this.recordShare('copy_link');
wx.showModal({
title: '链接已复制',
content: '您可以粘贴到任意地方分享给好友',
showCancel: false
});
}).catch(error => {
ShareErrorHandler.handleError(error, 'copy_link');
});
},
/**
* 显示朋友圈引导
*/
showTimelineGuide() {
wx.showModal({
title: '图片已保存到相册',
content: '请打开微信朋友圈,选择刚才保存的图片进行分享',
confirmText: '知道了',
cancelText: '再次保存',
success: (res) => {
if (res.cancel) {
this.onSaveToAlbum();
}
}
});
},
/**
* 记录分享行为
*/
async recordShare(type) {
const { content, userInfo } = this.data;
const shareService = new ShareService();
try {
await shareService.recordShare({
type,
contentId: content.id,
shareUserId: userInfo.id || 'anonymous'
});
} catch (error) {
console.error('记录分享失败', error);
}
}
});
十、总结与最佳实践
10.1 核心要点总结
10.2 最佳实践建议
用户体验优先:
- 提供清晰的分享引导
- 优化图片生成速度
- 设计美观的分享图片模板
性能优化:
- 预加载图片资源
- 使用 Canvas 批量绘制
- 合理管理内存
错误处理:
- 兼容不同微信版本
- 处理权限拒绝情况
- 提供友好的错误提示
数据统计:
- 记录分享行为
- 分析分享效果
- 优化分享策略
10.3 注意事项
通过本文的完整实现方案,您可以构建出功能完善、体验优良的微信小程序分享系统。随着微信平台的不断更新,建议持续关注官方文档,及时适配新功能。
参考资料

网硕互联帮助中心







评论前必须登录!
注册