657 lines
16 KiB
Vue
657 lines
16 KiB
Vue
<template>
|
||
<view class="poster-page">
|
||
<!-- 顶部提示 -->
|
||
<view class="tip-card">
|
||
<text class="tip-icon">📱</text>
|
||
<text class="tip-text">点击"保存海报"按钮,将海报保存到相册</text>
|
||
</view>
|
||
|
||
<!-- 海报预览 -->
|
||
<view class="poster-container">
|
||
<view class="poster-canvas" id="posterCanvas">
|
||
<view class="poster-content">
|
||
<!-- 课程信息 -->
|
||
<view class="course-info" v-if="courseInfo">
|
||
<view v-if="courseInfo.coverImage" class="course-image-wrapper">
|
||
<image class="course-image" :src="courseInfo.coverImage" mode="aspectFill"></image>
|
||
</view>
|
||
<view v-else class="course-image-placeholder">
|
||
<text class="placeholder-icon">📚</text>
|
||
<text class="placeholder-text">课程封面</text>
|
||
</view>
|
||
<view class="course-details">
|
||
<text class="course-name">{{ courseInfo.name }}</text>
|
||
<text class="course-price">¥{{ courseInfo.price }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 分销员信息 -->
|
||
<view class="distributor-info">
|
||
<text class="distributor-name">分销员</text>
|
||
<text class="distributor-desc">为您推荐优质课程</text>
|
||
</view>
|
||
|
||
<!-- 二维码 -->
|
||
<view class="qrcode-section">
|
||
<view v-if="qrcodeUrl" class="qrcode-wrapper">
|
||
<image class="qrcode" :src="qrcodeUrl" mode="aspectFit"></image>
|
||
<text class="qrcode-tip">扫码了解详情</text>
|
||
</view>
|
||
<view v-else class="qrcode-placeholder">
|
||
<text class="placeholder-icon">📱</text>
|
||
<text class="placeholder-text">二维码生成中...</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 推广码 -->
|
||
<view class="promotion-code">
|
||
<text class="code-label">推广码:</text>
|
||
<text class="code-value">{{ promotionCode }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Canvas 用于生成海报图片 -->
|
||
<canvas canvas-id="posterCanvas" id="posterCanvasElement" class="poster-canvas-hidden"></canvas>
|
||
|
||
<!-- 操作按钮 -->
|
||
<view class="action-buttons">
|
||
<button class="btn-primary" @click="savePoster">
|
||
<text class="btn-icon">💾</text>
|
||
<text>保存海报</text>
|
||
</button>
|
||
<button class="btn-secondary" @click="sharePoster">
|
||
<text class="btn-icon">📤</text>
|
||
<text>分享海报</text>
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 功能说明 -->
|
||
<view class="info-section">
|
||
<view class="info-title">使用说明</view>
|
||
<view class="info-list">
|
||
<view class="info-item">
|
||
<text class="info-number">1</text>
|
||
<text class="info-text">点击"保存海报"按钮保存到相册</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="info-number">2</text>
|
||
<text class="info-text">分享给微信好友或朋友圈</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="info-number">3</text>
|
||
<text class="info-text">好友扫码购买即可获得佣金</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { distributorApi } from '@/api/index.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
courseId: null,
|
||
courseInfo: null,
|
||
distributorName: '分销员',
|
||
promotionCode: '',
|
||
qrcodeUrl: '',
|
||
canvasWidth: 690, // canvas 宽度(rpx转px)
|
||
canvasHeight: 1000 // canvas 高度
|
||
}
|
||
},
|
||
|
||
onLoad(options) {
|
||
if (options.courseId) {
|
||
this.courseId = options.courseId
|
||
this.loadCourseInfo()
|
||
}
|
||
this.loadDistributorInfo()
|
||
},
|
||
|
||
// 微信小程序分享配置
|
||
onShareAppMessage() {
|
||
return {
|
||
title: this.courseInfo ? this.courseInfo.name : '习正陪伴 - 优质课程推荐',
|
||
path: `/pages/service/detail?id=${this.courseId}&code=${this.promotionCode}`,
|
||
imageUrl: this.courseInfo ? this.courseInfo.coverImage : ''
|
||
}
|
||
},
|
||
|
||
methods: {
|
||
async loadCourseInfo() {
|
||
try {
|
||
// TODO: 调用API获取课程信息
|
||
// const res = await courseApi.getCourseDetail(this.courseId)
|
||
// 暂时使用模拟数据
|
||
this.courseInfo = {
|
||
id: this.courseId,
|
||
name: '基准式陪伴 - 100小时套餐',
|
||
coverImage: '',
|
||
price: 1999.00
|
||
}
|
||
} catch (error) {
|
||
console.error('加载课程信息失败:', error)
|
||
}
|
||
},
|
||
|
||
async loadDistributorInfo() {
|
||
try {
|
||
const res = await distributorApi.getPromotionCode()
|
||
if (res.code === 200 && res.data) {
|
||
this.promotionCode = res.data.code || ''
|
||
this.qrcodeUrl = res.data.qrCode || ''
|
||
}
|
||
|
||
// 获取用户信息
|
||
const userInfo = uni.getStorageSync('userInfo')
|
||
if (userInfo) {
|
||
this.distributorName = userInfo.name || '分销员'
|
||
}
|
||
} catch (error) {
|
||
console.error('加载分销员信息失败:', error)
|
||
}
|
||
},
|
||
|
||
async savePoster() {
|
||
try {
|
||
uni.showLoading({ title: '正在生成海报...' })
|
||
|
||
// 绘制海报到 canvas
|
||
const tempFilePath = await this.drawPosterToCanvas()
|
||
|
||
if (tempFilePath) {
|
||
// 保存到相册
|
||
uni.saveImageToPhotosAlbum({
|
||
filePath: tempFilePath,
|
||
success: () => {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '海报已保存到相册',
|
||
icon: 'success',
|
||
duration: 2000
|
||
})
|
||
},
|
||
fail: (err) => {
|
||
uni.hideLoading()
|
||
console.error('保存失败:', err)
|
||
|
||
if (err.errMsg && err.errMsg.includes('auth')) {
|
||
uni.showModal({
|
||
title: '需要授权',
|
||
content: '请允许访问相册,以便保存海报',
|
||
success: (modalRes) => {
|
||
if (modalRes.confirm) {
|
||
uni.openSetting()
|
||
}
|
||
}
|
||
})
|
||
} else {
|
||
uni.showToast({
|
||
title: '保存失败,请重试',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
})
|
||
} else {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '生成海报失败', icon: 'none' })
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('保存海报失败:', error)
|
||
uni.showToast({ title: '保存失败,请重试', icon: 'none' })
|
||
}
|
||
},
|
||
|
||
// 绘制海报到 Canvas
|
||
drawPosterToCanvas() {
|
||
return new Promise((resolve, reject) => {
|
||
const ctx = uni.createCanvasContext('posterCanvas', this)
|
||
const pixelRatio = uni.getSystemInfoSync().pixelRatio || 2
|
||
|
||
// 设置 canvas 尺寸
|
||
const width = this.canvasWidth
|
||
const height = this.canvasHeight
|
||
|
||
// 绘制背景渐变
|
||
const gradient = ctx.createLinearGradient(0, 0, 0, height)
|
||
gradient.addColorStop(0, '#7dd3c0')
|
||
gradient.addColorStop(1, '#5fb8a8')
|
||
ctx.fillStyle = gradient
|
||
ctx.fillRect(0, 0, width, height)
|
||
|
||
// 绘制圆角
|
||
ctx.setFillStyle('#7dd3c0')
|
||
ctx.fillRect(0, 0, width, height)
|
||
|
||
let currentY = 40
|
||
|
||
// 绘制课程信息卡片
|
||
this.drawRoundRect(ctx, 40, currentY, width - 80, 300, 16, '#ffffff')
|
||
|
||
// 绘制课程名称
|
||
ctx.setFontSize(24)
|
||
ctx.setFillStyle('#333333')
|
||
ctx.setTextAlign('left')
|
||
ctx.fillText(this.courseInfo ? this.courseInfo.name : '课程名称', 60, currentY + 220)
|
||
|
||
// 绘制价格
|
||
ctx.setFontSize(32)
|
||
ctx.setFillStyle('#ff6b6b')
|
||
ctx.fillText(`¥${this.courseInfo ? this.courseInfo.price : '0'}`, 60, currentY + 260)
|
||
|
||
currentY += 340
|
||
|
||
// 绘制分销员信息
|
||
ctx.setFontSize(28)
|
||
ctx.setFillStyle('#ffffff')
|
||
ctx.setTextAlign('center')
|
||
ctx.fillText('分销员', width / 2, currentY)
|
||
|
||
ctx.setFontSize(20)
|
||
ctx.setFillStyle('rgba(255, 255, 255, 0.9)')
|
||
ctx.fillText('为您推荐优质课程', width / 2, currentY + 35)
|
||
|
||
currentY += 70
|
||
|
||
// 绘制二维码区域
|
||
this.drawRoundRect(ctx, 40, currentY, width - 80, 380, 16, '#ffffff')
|
||
|
||
// 如果有二维码URL,绘制二维码
|
||
if (this.qrcodeUrl) {
|
||
// 下载二维码图片
|
||
uni.downloadFile({
|
||
url: this.qrcodeUrl,
|
||
success: (res) => {
|
||
if (res.statusCode === 200) {
|
||
ctx.drawImage(res.tempFilePath, (width - 240) / 2, currentY + 30, 240, 240)
|
||
|
||
// 绘制二维码提示文字
|
||
ctx.setFontSize(20)
|
||
ctx.setFillStyle('#666666')
|
||
ctx.setTextAlign('center')
|
||
ctx.fillText('扫码了解详情', width / 2, currentY + 300)
|
||
|
||
// 继续绘制推广码
|
||
this.drawPromotionCode(ctx, width, currentY + 380)
|
||
|
||
// 绘制完成,导出图片
|
||
ctx.draw(false, () => {
|
||
setTimeout(() => {
|
||
uni.canvasToTempFilePath({
|
||
canvasId: 'posterCanvas',
|
||
success: (canvasRes) => {
|
||
resolve(canvasRes.tempFilePath)
|
||
},
|
||
fail: (err) => {
|
||
console.error('canvas导出失败:', err)
|
||
reject(err)
|
||
}
|
||
}, this)
|
||
}, 500)
|
||
})
|
||
}
|
||
},
|
||
fail: () => {
|
||
// 二维码下载失败,继续绘制其他内容
|
||
this.drawPromotionCode(ctx, width, currentY + 380)
|
||
|
||
ctx.draw(false, () => {
|
||
setTimeout(() => {
|
||
uni.canvasToTempFilePath({
|
||
canvasId: 'posterCanvas',
|
||
success: (canvasRes) => {
|
||
resolve(canvasRes.tempFilePath)
|
||
},
|
||
fail: (err) => {
|
||
console.error('canvas导出失败:', err)
|
||
reject(err)
|
||
}
|
||
}, this)
|
||
}, 500)
|
||
})
|
||
}
|
||
})
|
||
} else {
|
||
// 没有二维码,直接绘制推广码
|
||
this.drawPromotionCode(ctx, width, currentY + 380)
|
||
|
||
ctx.draw(false, () => {
|
||
setTimeout(() => {
|
||
uni.canvasToTempFilePath({
|
||
canvasId: 'posterCanvas',
|
||
success: (canvasRes) => {
|
||
resolve(canvasRes.tempFilePath)
|
||
},
|
||
fail: (err) => {
|
||
console.error('canvas导出失败:', err)
|
||
reject(err)
|
||
}
|
||
}, this)
|
||
}, 500)
|
||
})
|
||
}
|
||
})
|
||
},
|
||
|
||
// 绘制推广码
|
||
drawPromotionCode(ctx, width, y) {
|
||
// 绘制推广码背景
|
||
ctx.setFillStyle('rgba(255, 255, 255, 0.2)')
|
||
this.drawRoundRect(ctx, 40, y + 30, width - 80, 60, 12, 'rgba(255, 255, 255, 0.2)')
|
||
|
||
// 绘制推广码文字
|
||
ctx.setFontSize(22)
|
||
ctx.setFillStyle('rgba(255, 255, 255, 0.9)')
|
||
ctx.setTextAlign('center')
|
||
ctx.fillText(`推广码:${this.promotionCode}`, width / 2, y + 65)
|
||
},
|
||
|
||
// 绘制圆角矩形
|
||
drawRoundRect(ctx, x, y, width, height, radius, fillStyle) {
|
||
ctx.setFillStyle(fillStyle)
|
||
ctx.beginPath()
|
||
ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 1.5)
|
||
ctx.arc(x + width - radius, y + radius, radius, Math.PI * 1.5, Math.PI * 2)
|
||
ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI * 0.5)
|
||
ctx.arc(x + radius, y + height - radius, radius, Math.PI * 0.5, Math.PI)
|
||
ctx.closePath()
|
||
ctx.fill()
|
||
},
|
||
|
||
sharePoster() {
|
||
uni.showModal({
|
||
title: '分享海报',
|
||
content: '请点击右上角"..."按钮,选择"转发"分享给好友',
|
||
showCancel: false
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.poster-page {
|
||
min-height: 100vh;
|
||
background: #f0f9f7;
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.tip-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
background: #fff3e0;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.tip-icon {
|
||
font-size: 40rpx;
|
||
}
|
||
|
||
.tip-text {
|
||
flex: 1;
|
||
font-size: 26rpx;
|
||
color: #ff9800;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.poster-container {
|
||
background: #ffffff;
|
||
border-radius: 20rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.poster-canvas {
|
||
background: linear-gradient(135deg, #7dd3c0 0%, #5fb8a8 100%);
|
||
border-radius: 16rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.poster-content {
|
||
padding: 40rpx;
|
||
}
|
||
|
||
.course-info {
|
||
background: #ffffff;
|
||
border-radius: 16rpx;
|
||
overflow: hidden;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.course-image-wrapper {
|
||
width: 100%;
|
||
height: 300rpx;
|
||
}
|
||
|
||
.course-image {
|
||
width: 100%;
|
||
height: 300rpx;
|
||
}
|
||
|
||
.course-image-placeholder {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
height: 300rpx;
|
||
background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
|
||
}
|
||
|
||
.course-image-placeholder .placeholder-icon {
|
||
font-size: 100rpx;
|
||
margin-bottom: 15rpx;
|
||
opacity: 0.4;
|
||
}
|
||
|
||
.course-image-placeholder .placeholder-text {
|
||
font-size: 28rpx;
|
||
color: #999999;
|
||
}
|
||
|
||
.course-details {
|
||
padding: 30rpx;
|
||
}
|
||
|
||
.course-name {
|
||
display: block;
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
.course-price {
|
||
display: block;
|
||
font-size: 40rpx;
|
||
font-weight: bold;
|
||
color: #ff6b6b;
|
||
}
|
||
|
||
.distributor-info {
|
||
text-align: center;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.distributor-name {
|
||
display: block;
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #ffffff;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.distributor-desc {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: rgba(255, 255, 255, 0.9);
|
||
}
|
||
|
||
.qrcode-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
background: #ffffff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.qrcode-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.qrcode {
|
||
width: 300rpx;
|
||
height: 300rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.qrcode-tip {
|
||
font-size: 26rpx;
|
||
color: #666666;
|
||
}
|
||
|
||
.qrcode-placeholder {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 300rpx;
|
||
height: 300rpx;
|
||
background: #f5f5f5;
|
||
border-radius: 12rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.placeholder-icon {
|
||
font-size: 80rpx;
|
||
margin-bottom: 20rpx;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.placeholder-text {
|
||
font-size: 26rpx;
|
||
color: #999999;
|
||
}
|
||
|
||
.promotion-code {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 12rpx;
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.code-label {
|
||
font-size: 28rpx;
|
||
color: rgba(255, 255, 255, 0.9);
|
||
}
|
||
|
||
.code-value {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #ffffff;
|
||
letter-spacing: 2rpx;
|
||
}
|
||
|
||
.poster-canvas-hidden {
|
||
position: fixed;
|
||
left: -9999rpx;
|
||
top: -9999rpx;
|
||
width: 690rpx;
|
||
height: 1000rpx;
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.btn-primary,
|
||
.btn-secondary {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10rpx;
|
||
padding: 28rpx;
|
||
border-radius: 16rpx;
|
||
font-size: 30rpx;
|
||
border: none;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, #7dd3c0 0%, #5fb8a8 100%);
|
||
color: #ffffff;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #ffffff;
|
||
color: #7dd3c0;
|
||
border: 2rpx solid #7dd3c0;
|
||
}
|
||
|
||
.btn-icon {
|
||
font-size: 36rpx;
|
||
}
|
||
|
||
.info-section {
|
||
background: #ffffff;
|
||
border-radius: 20rpx;
|
||
padding: 30rpx;
|
||
}
|
||
|
||
.info-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.info-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.info-number {
|
||
width: 50rpx;
|
||
height: 50rpx;
|
||
line-height: 50rpx;
|
||
text-align: center;
|
||
background: #7dd3c0;
|
||
color: #ffffff;
|
||
border-radius: 50%;
|
||
font-size: 24rpx;
|
||
font-weight: bold;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.info-text {
|
||
flex: 1;
|
||
font-size: 28rpx;
|
||
color: #666666;
|
||
}
|
||
</style>
|