| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- <template>
- <view class="share-page">
- <!-- 生产:后端已生成带参小程序码海报,直接展示图片 -->
- <view class="poster-card" v-if="posterUrl">
- <image class="poster-image" :src="posterUrl" mode="widthFix"></image>
- </view>
- <!-- test-mode/降级:本地 canvas 渲染带邀请码海报 -->
- <view class="poster-card" v-else>
- <canvas canvas-id="posterCanvas" id="posterCanvas" class="poster-canvas"></canvas>
- </view>
- <view class="invite-row">
- <text class="invite-label">我的邀请码</text>
- <text class="invite-code">{{ inviteCode || '加载中…' }}</text>
- <button class="copy-btn" @click="copyCode" :disabled="!inviteCode">复制</button>
- </view>
- <view class="invite-row share-text-row" v-if="shareText">
- <text class="invite-label">分享文案</text>
- <text class="share-text-preview" @click="copyShareText">点击复制带邀请码的分享文案 ›</text>
- </view>
- <button class="save-btn" @click="savePoster" :loading="saving" :disabled="saving || !inviteCode">保存海报图片</button>
- <view class="stats-link" @click="goStats">
- <text>查看我的转介绍统计</text>
- <text class="arrow">›</text>
- </view>
- </view>
- </template>
- <script>
- import { getMyPoster } from '@/utils/api.js'
- import { guardPage } from '@/utils/guard.js'
- export default {
- data() {
- return {
- inviteCode: '',
- posterUrl: '',
- shareText: '',
- saving: false
- }
- },
- onLoad() {
- if (!guardPage()) return
- this.loadPoster()
- },
- onReady() {
- // canvas 就绪后绘制海报
- this.drawPoster()
- },
- methods: {
- loadPoster() {
- var self = this
- getMyPoster().then(function(resp) {
- var data = resp.data || {}
- self.inviteCode = data.inviteCode || ''
- self.posterUrl = data.posterUrl || ''
- self.shareText = data.shareText || ''
- if (self.inviteCode) {
- // 重新绘制带邀请码的海报
- setTimeout(function() {
- self.drawPoster()
- }, 300)
- }
- }).catch(function () {})
- },
- drawPoster() {
- var self = this
- var ctx = uni.createCanvasContext('posterCanvas', self)
- var width = 300
- var height = 450
- // 背景
- var gradient = ctx.createLinearGradient(0, 0, 0, height)
- gradient.addColorStop(0, '#FFF7ED')
- gradient.addColorStop(1, '#FED7AA')
- ctx.setFillStyle(gradient)
- ctx.fillRect(0, 0, width, height)
- // 顶部品牌区
- ctx.setFillStyle('#EA580C')
- ctx.fillRect(0, 0, width, 8)
- ctx.setFillStyle('#1E293B')
- ctx.setFontSize(22)
- ctx.setTextAlign('center')
- ctx.fillText('爱伴·AI之旅', width / 2, 60)
- ctx.setFillStyle('#64748B')
- ctx.setFontSize(13)
- ctx.fillText('我的专属邀请码', width / 2, 90)
- // 中央邀请码卡片
- ctx.setFillStyle('#FFFFFF')
- this.roundRect(ctx, 40, 130, width - 80, 120, 12)
- ctx.setFillStyle('#F97316')
- ctx.setFontSize(15)
- ctx.fillText('我的邀请码', width / 2, 165)
- ctx.setFillStyle('#1E293B')
- ctx.setFontSize(34)
- ctx.fillText(self.inviteCode || '------', width / 2, 215)
- // 底部提示
- ctx.setFillStyle('#EA580C')
- ctx.setFontSize(14)
- ctx.fillText('扫码加入训练营', width / 2, 320)
- // 二维码占位区(生产环境接入 getwxacodeunlimit 后绘制小程序码)
- ctx.setFillStyle('#FFFFFF')
- this.roundRect(ctx, width / 2 - 45, 340, 90, 90, 8)
- ctx.setStrokeStyle('#E2E8F0')
- ctx.setLineWidth(1)
- this.roundRect(ctx, width / 2 - 45, 340, 90, 90, 8)
- ctx.stroke()
- ctx.setFillStyle('#CBD5E1')
- ctx.setFontSize(11)
- ctx.fillText('小程序码', width / 2, 390)
- ctx.setFillStyle('#94A3B8')
- ctx.setFontSize(12)
- ctx.fillText('爱伴·AI之旅训练营', width / 2, 420)
- ctx.draw(false)
- },
- roundRect(ctx, x, y, w, h, r) {
- ctx.beginPath()
- ctx.moveTo(x + r, y)
- ctx.lineTo(x + w - r, y)
- ctx.arcTo(x + w, y, x + w, y + r, r)
- ctx.lineTo(x + w, y + h - r)
- ctx.arcTo(x + w, y + h, x + w - r, y + h, r)
- ctx.lineTo(x + r, y + h)
- ctx.arcTo(x, y + h, x, y + h - r, r)
- ctx.lineTo(x, y + r)
- ctx.arcTo(x, y, x + r, y, r)
- ctx.closePath()
- ctx.fill()
- },
- copyCode() {
- if (!this.inviteCode) return
- uni.setClipboardData({
- data: this.inviteCode,
- success: function() {
- uni.showToast({ title: '邀请码已复制', icon: 'success' })
- }
- })
- },
- copyShareText() {
- if (!this.shareText) {
- uni.showToast({ title: '分享文案暂未生成', icon: 'none' })
- return
- }
- uni.setClipboardData({
- data: this.shareText,
- success: function() {
- uni.showToast({ title: '分享文案已复制', icon: 'success' })
- }
- })
- },
- savePoster() {
- var self = this
- if (this.saving) return
- this.saving = true
- // 后端已生成海报:保存远程图片到相册
- if (this.posterUrl) {
- uni.downloadFile({
- url: this.posterUrl,
- success: function(res) {
- if (res.statusCode !== 200) {
- self.saving = false
- uni.showToast({ title: '海报下载失败', icon: 'none' })
- return
- }
- uni.saveImageToPhotosAlbum({
- filePath: res.tempFilePath,
- success: function() {
- uni.showToast({ title: '海报已保存到相册', icon: 'success' })
- },
- fail: function() {
- self.saving = false
- uni.showToast({ title: '保存失败,请检查相册权限', icon: 'none' })
- },
- complete: function() {
- self.saving = false
- }
- })
- },
- fail: function() {
- self.saving = false
- uni.showToast({ title: '海报下载失败', icon: 'none' })
- }
- })
- return
- }
- // 本地 canvas 渲染海报
- uni.canvasToTempFilePath({
- canvasId: 'posterCanvas',
- success: function(res) {
- uni.saveImageToPhotosAlbum({
- filePath: res.tempFilePath,
- success: function() {
- uni.showToast({ title: '海报已保存到相册', icon: 'success' })
- },
- fail: function(err) {
- if (err && (err.errMsg || '').indexOf('auth') > -1) {
- uni.showModal({
- title: '需要相册权限',
- content: '请在设置中开启「保存到相册」权限',
- confirmText: '去设置',
- success: function(r) {
- if (r.confirm) {
- uni.openSetting({})
- }
- }
- })
- } else {
- uni.showToast({ title: '保存失败,请稍后重试', icon: 'none' })
- }
- },
- complete: function() {
- self.saving = false
- }
- })
- },
- fail: function() {
- self.saving = false
- uni.showToast({ title: '海报生成失败', icon: 'none' })
- }
- })
- },
- goStats() {
- uni.navigateTo({ url: '/pages/share/stats' })
- }
- }
- }
- </script>
- <style scoped>
- .share-page { min-height: 100vh; background: #F5F5F5; padding: 24rpx 32rpx; }
- .poster-card { background: #FFF; border-radius: 16rpx; padding: 24rpx; margin-bottom: 24rpx; display: flex; justify-content: center; }
- .poster-canvas { width: 300px; height: 450px; border-radius: 12rpx; }
- .poster-image { width: 100%; border-radius: 12rpx; }
- .invite-row { background: #FFF; border-radius: 16rpx; padding: 32rpx; margin-bottom: 24rpx; display: flex; align-items: center; }
- .share-text-row { padding: 20rpx 32rpx; }
- .share-text-preview { flex: 1; font-size: 24rpx; color: #F97316; margin-left: 24rpx; line-height: 1.5; }
- .share-text-preview:active { opacity: 0.7; }
- .invite-label { font-size: 28rpx; color: #1E293B; font-weight: 600; flex-shrink: 0; }
- .invite-code { flex: 1; font-size: 32rpx; color: #F97316; font-weight: 700; font-family: monospace; margin-left: 24rpx; letter-spacing: 4rpx; }
- .copy-btn { flex-shrink: 0; height: 60rpx; line-height: 60rpx; padding: 0 32rpx; background: #F97316; color: #FFF; font-size: 26rpx; border-radius: 30rpx; border: none; }
- .copy-btn[disabled] { background: #FDBA74; color: #FFF; }
- .save-btn { width: 100%; height: 88rpx; line-height: 88rpx; background: #F97316; color: #FFF; font-size: 30rpx; font-weight: 600; border-radius: 44rpx; border: none; margin-top: 8rpx; }
- .save-btn:active { opacity: 0.85; }
- .stats-link { display: flex; align-items: center; justify-content: center; padding: 32rpx 0; color: #64748B; font-size: 28rpx; }
- .stats-link .arrow { color: #94A3B8; font-size: 32rpx; margin-left: 8rpx; }
- </style>
|