feat: 海报生成功能+推广优化

1. 阅读页:
   - 添加"生成海报"和"分享给好友"按钮
   - 海报包含章节摘要+邀请码+小程序码占位
   - 优化推广提示区域,添加推广中心入口

2. 分销中心:
   - 完善海报生成功能(推广海报)
   - 去掉"更多分享方式",改为"复制朋友圈文案"
   - 添加"分享给好友"小程序卡片按钮

3. 分享链接自动带分销ID
This commit is contained in:
卡若
2026-01-25 19:52:38 +08:00
parent 4dd2f9f4a7
commit a702cd9086
6 changed files with 551 additions and 56 deletions

View File

@@ -47,7 +47,9 @@ Page({
// 弹窗
showShareModal: false,
showLoginModal: false,
showPosterModal: false,
isPaying: false,
isGeneratingPoster: false,
// 免费章节
freeIds: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3']
@@ -530,6 +532,160 @@ Page({
wx.navigateTo({ url: '/pages/referral/referral' })
},
// 生成海报
async generatePoster() {
wx.showLoading({ title: '生成中...' })
this.setData({ showPosterModal: true, isGeneratingPoster: true })
try {
const ctx = wx.createCanvasContext('posterCanvas', this)
const { section, contentParagraphs } = this.data
const userInfo = app.globalData.userInfo
const referralCode = userInfo?.referralCode || 'SOUL'
// 海报尺寸 300x450
const width = 300
const height = 450
// 背景渐变
const grd = ctx.createLinearGradient(0, 0, 0, height)
grd.addColorStop(0, '#1a1a2e')
grd.addColorStop(1, '#16213e')
ctx.setFillStyle(grd)
ctx.fillRect(0, 0, width, height)
// 顶部装饰条
ctx.setFillStyle('#00CED1')
ctx.fillRect(0, 0, width, 4)
// 标题区域
ctx.setFillStyle('#ffffff')
ctx.setFontSize(14)
ctx.fillText('📚 Soul创业派对', 20, 35)
// 章节标题
ctx.setFontSize(18)
ctx.setFillStyle('#ffffff')
const title = section?.title || '精彩内容'
const titleLines = this.wrapText(ctx, title, width - 40, 18)
let y = 70
titleLines.forEach(line => {
ctx.fillText(line, 20, y)
y += 26
})
// 分隔线
ctx.setStrokeStyle('rgba(255,255,255,0.1)')
ctx.beginPath()
ctx.moveTo(20, y + 10)
ctx.lineTo(width - 20, y + 10)
ctx.stroke()
// 内容摘要
ctx.setFontSize(12)
ctx.setFillStyle('rgba(255,255,255,0.8)')
y += 30
const summary = contentParagraphs.slice(0, 3).join(' ').slice(0, 150) + '...'
const summaryLines = this.wrapText(ctx, summary, width - 40, 12)
summaryLines.slice(0, 6).forEach(line => {
ctx.fillText(line, 20, y)
y += 20
})
// 底部区域背景
ctx.setFillStyle('rgba(0,206,209,0.1)')
ctx.fillRect(0, height - 120, width, 120)
// 小程序码占位(实际需要获取小程序码图片)
ctx.setFillStyle('#ffffff')
ctx.beginPath()
ctx.arc(width - 55, height - 60, 35, 0, Math.PI * 2)
ctx.fill()
ctx.setFillStyle('#00CED1')
ctx.setFontSize(10)
ctx.fillText('扫码阅读', width - 72, height - 58)
// 邀请信息
ctx.setFillStyle('#ffffff')
ctx.setFontSize(12)
ctx.fillText('长按识别 · 阅读全文', 20, height - 70)
ctx.setFillStyle('#FFD700')
ctx.setFontSize(11)
ctx.fillText(`邀请码: ${referralCode}`, 20, height - 50)
ctx.setFillStyle('rgba(255,255,255,0.6)')
ctx.setFontSize(10)
ctx.fillText('好友购买你获90%收益', 20, height - 32)
ctx.draw(true, () => {
wx.hideLoading()
this.setData({ isGeneratingPoster: false })
})
} catch (e) {
console.error('生成海报失败:', e)
wx.hideLoading()
wx.showToast({ title: '生成失败', icon: 'none' })
this.setData({ showPosterModal: false, isGeneratingPoster: false })
}
},
// 文字换行处理
wrapText(ctx, text, maxWidth, fontSize) {
const lines = []
let line = ''
for (let i = 0; i < text.length; i++) {
const testLine = line + text[i]
const metrics = ctx.measureText(testLine)
if (metrics.width > maxWidth && line) {
lines.push(line)
line = text[i]
} else {
line = testLine
}
}
if (line) lines.push(line)
return lines
},
// 关闭海报弹窗
closePosterModal() {
this.setData({ showPosterModal: false })
},
// 保存海报到相册
savePoster() {
wx.canvasToTempFilePath({
canvasId: 'posterCanvas',
success: (res) => {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
wx.showToast({ title: '已保存到相册', icon: 'success' })
this.setData({ showPosterModal: false })
},
fail: (err) => {
if (err.errMsg.includes('auth deny')) {
wx.showModal({
title: '提示',
content: '需要相册权限才能保存海报',
confirmText: '去设置',
success: (res) => {
if (res.confirm) {
wx.openSetting()
}
}
})
} else {
wx.showToast({ title: '保存失败', icon: 'none' })
}
}
})
},
fail: () => {
wx.showToast({ title: '生成图片失败', icon: 'none' })
}
}, this)
},
// 阻止冒泡
stopPropagation() {}
})

View File

@@ -80,13 +80,37 @@
</view>
</view>
<!-- 推广提示 -->
<view class="share-tip" bindtap="showShare">
<view class="tip-content">
<text class="tip-title">推荐好友,共同成长</text>
<text class="tip-desc">邀请好友加入享90%推广收益</text>
<!-- 分享操作区 -->
<view class="action-section">
<view class="action-header">
<text class="action-title">分享这篇内容</text>
</view>
<view class="action-buttons">
<button class="action-btn btn-share" open-type="share">
<text class="action-icon">💬</text>
<text class="action-text">分享给好友</text>
</button>
<view class="action-btn btn-poster" bindtap="generatePoster">
<text class="action-icon">🖼️</text>
<text class="action-text">生成海报</text>
</view>
</view>
</view>
<!-- 推广提示 -->
<view class="promo-section">
<view class="promo-card" bindtap="goToReferral">
<view class="promo-left">
<view class="promo-icon">💰</view>
<view class="promo-info">
<text class="promo-title">推荐好友,共同成长</text>
<text class="promo-desc">邀请好友购买享90%推广收益</text>
</view>
</view>
<view class="promo-right">
<text class="promo-arrow">→</text>
</view>
</view>
<view class="tip-btn">立即推广</view>
</view>
</view>
</view>
@@ -171,34 +195,27 @@
</view>
</view>
<!-- 分享弹窗 -->
<view class="modal-overlay" wx:if="{{showShareModal}}" bindtap="closeShareModal">
<view class="modal-content share-modal" catchtap="stopPropagation">
<!-- 海报生成弹窗 -->
<view class="modal-overlay" wx:if="{{showPosterModal}}" bindtap="closePosterModal">
<view class="modal-content poster-modal" catchtap="stopPropagation">
<view class="modal-header">
<text class="modal-title">分享文章</text>
<view class="modal-close" bindtap="closeShareModal">✕</view>
<text class="modal-title">生成海报</text>
<view class="modal-close" bindtap="closePosterModal">✕</view>
</view>
<view class="share-link-box">
<text class="link-label">你的专属分享链接</text>
<text class="link-url">https://soul.quwanzhi.com/read/{{sectionId}}</text>
<text class="link-tip">邀请码: 好友购买你获得90%佣金</text>
<!-- 海报预览 -->
<view class="poster-preview">
<canvas canvas-id="posterCanvas" class="poster-canvas" style="width: 300px; height: 450px;"></canvas>
</view>
<view class="share-buttons">
<view class="share-btn" bindtap="copyLink">
<view class="share-btn-icon icon-copy">📋</view>
<text class="share-btn-text">复制链接</text>
</view>
<button class="share-btn" open-type="share">
<view class="share-btn-icon icon-wechat">微</view>
<text class="share-btn-text">微信好友</text>
</button>
<view class="share-btn" bindtap="goToReferral">
<view class="share-btn-icon icon-poster">🖼️</view>
<text class="share-btn-text">生成海报</text>
<view class="poster-actions">
<view class="poster-btn btn-save" bindtap="savePoster">
<text class="btn-icon">💾</text>
<text>保存到相册</text>
</view>
</view>
<text class="poster-tip">长按海报可直接分享到微信</text>
</view>
</view>

View File

@@ -396,42 +396,123 @@
color: rgba(255, 255, 255, 0.6);
}
/* ===== 分享提示 ===== */
.share-tip {
/* ===== 分享操作区 ===== */
.action-section {
margin-top: 64rpx;
padding: 32rpx;
background: rgba(255, 255, 255, 0.03);
border-radius: 24rpx;
border: 2rpx solid rgba(255, 255, 255, 0.05);
}
.action-header {
margin-bottom: 24rpx;
}
.action-title {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.6);
}
.action-buttons {
display: flex;
gap: 24rpx;
}
.action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
padding: 28rpx 24rpx;
border-radius: 20rpx;
border: none;
background: transparent;
line-height: normal;
}
.action-btn::after {
border: none;
}
.btn-share {
background: rgba(7, 193, 96, 0.15);
border: 2rpx solid rgba(7, 193, 96, 0.3);
}
.btn-poster {
background: rgba(255, 215, 0, 0.15);
border: 2rpx solid rgba(255, 215, 0, 0.3);
}
.action-icon {
font-size: 36rpx;
}
.action-text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
}
/* ===== 推广提示区 ===== */
.promo-section {
margin-top: 32rpx;
}
.promo-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
background: linear-gradient(90deg, rgba(255, 215, 0, 0.1) 0%, transparent 100%);
background: linear-gradient(135deg, rgba(255, 215, 0, 0.1) 0%, rgba(255, 165, 0, 0.05) 100%);
border: 2rpx solid rgba(255, 215, 0, 0.2);
border-radius: 24rpx;
}
.tip-content {
.promo-left {
display: flex;
align-items: center;
gap: 20rpx;
}
.promo-icon {
width: 80rpx;
height: 80rpx;
background: rgba(255, 215, 0, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
}
.promo-info {
flex: 1;
}
.tip-title {
font-size: 28rpx;
.promo-title {
font-size: 30rpx;
color: #ffffff;
font-weight: 500;
font-weight: 600;
display: block;
}
.tip-desc {
.promo-desc {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
color: rgba(255, 255, 255, 0.5);
display: block;
margin-top: 8rpx;
}
.tip-btn {
padding: 16rpx 32rpx;
background: #FFD700;
color: #000000;
font-size: 28rpx;
font-weight: 500;
border-radius: 16rpx;
.promo-right {
padding-left: 20rpx;
}
.promo-arrow {
font-size: 32rpx;
color: #FFD700;
}
/* ===== 弹窗 ===== */
@@ -792,3 +873,56 @@
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
/* ===== 海报弹窗 ===== */
.poster-modal {
padding-bottom: calc(64rpx + env(safe-area-inset-bottom));
}
.poster-preview {
display: flex;
justify-content: center;
margin: 32rpx 0;
padding: 24rpx;
background: rgba(0, 0, 0, 0.3);
border-radius: 24rpx;
}
.poster-canvas {
border-radius: 16rpx;
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.5);
}
.poster-actions {
display: flex;
gap: 24rpx;
margin-bottom: 24rpx;
}
.poster-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
padding: 28rpx;
border-radius: 24rpx;
font-size: 30rpx;
font-weight: 500;
}
.btn-save {
background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
color: #ffffff;
}
.btn-icon {
font-size: 32rpx;
}
.poster-tip {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.4);
text-align: center;
display: block;
}