peixue-dev/peidu/uniapp/distributor-package/pages/distributor/poster-new.vue

657 lines
16 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>