index.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <template>
  2. <view class="share-page">
  3. <view class="poster-card">
  4. <canvas canvas-id="posterCanvas" id="posterCanvas" class="poster-canvas"></canvas>
  5. </view>
  6. <view class="invite-row">
  7. <text class="invite-label">我的邀请码</text>
  8. <text class="invite-code">{{ inviteCode || '加载中…' }}</text>
  9. <button class="copy-btn" @click="copyCode" :disabled="!inviteCode">复制</button>
  10. </view>
  11. <view class="invite-row share-text-row" v-if="shareText">
  12. <text class="invite-label">分享文案</text>
  13. <text class="share-text-preview" @click="copyShareText">点击复制带邀请码的分享文案 ›</text>
  14. </view>
  15. <button class="save-btn" @click="savePoster" :loading="saving" :disabled="saving || !inviteCode">保存海报图片</button>
  16. <view class="stats-link" @click="goStats">
  17. <text>查看我的转介绍统计</text>
  18. <text class="arrow">›</text>
  19. </view>
  20. </view>
  21. </template>
  22. <script>
  23. import { getMyPoster } from '@/utils/api.js'
  24. export default {
  25. data() {
  26. return {
  27. inviteCode: '',
  28. posterUrl: '',
  29. shareText: '',
  30. saving: false
  31. }
  32. },
  33. onLoad() {
  34. this.loadPoster()
  35. },
  36. onReady() {
  37. // canvas 就绪后绘制海报
  38. this.drawPoster()
  39. },
  40. methods: {
  41. loadPoster() {
  42. var self = this
  43. getMyPoster().then(function(resp) {
  44. var data = resp.data || {}
  45. self.inviteCode = data.inviteCode || ''
  46. self.posterUrl = data.posterUrl || ''
  47. self.shareText = data.shareText || ''
  48. if (self.inviteCode) {
  49. // 重新绘制带邀请码的海报
  50. setTimeout(function() {
  51. self.drawPoster()
  52. }, 300)
  53. }
  54. }).catch(function () {})
  55. },
  56. drawPoster() {
  57. var self = this
  58. var ctx = uni.createCanvasContext('posterCanvas', self)
  59. var width = 300
  60. var height = 450
  61. // 背景
  62. var gradient = ctx.createLinearGradient(0, 0, 0, height)
  63. gradient.addColorStop(0, '#FFF7ED')
  64. gradient.addColorStop(1, '#FED7AA')
  65. ctx.setFillStyle(gradient)
  66. ctx.fillRect(0, 0, width, height)
  67. // 顶部品牌区
  68. ctx.setFillStyle('#EA580C')
  69. ctx.fillRect(0, 0, width, 8)
  70. ctx.setFillStyle('#1E293B')
  71. ctx.setFontSize(22)
  72. ctx.setTextAlign('center')
  73. ctx.fillText('爱伴·AI之旅', width / 2, 60)
  74. ctx.setFillStyle('#64748B')
  75. ctx.setFontSize(13)
  76. ctx.fillText('邀请你一起加入 AI 成长营', width / 2, 90)
  77. // 中央邀请码卡片
  78. ctx.setFillStyle('#FFFFFF')
  79. this.roundRect(ctx, 40, 130, width - 80, 120, 12)
  80. ctx.setFillStyle('#F97316')
  81. ctx.setFontSize(15)
  82. ctx.fillText('我的邀请码', width / 2, 165)
  83. ctx.setFillStyle('#1E293B')
  84. ctx.setFontSize(34)
  85. ctx.fillText(self.inviteCode || '------', width / 2, 215)
  86. // 底部提示
  87. ctx.setFillStyle('#EA580C')
  88. ctx.setFontSize(14)
  89. ctx.fillText('报名时填写邀请码,一起享受专属权益', width / 2, 320)
  90. // 二维码占位区(生产环境接入 getwxacodeunlimit 后绘制小程序码)
  91. ctx.setFillStyle('#FFFFFF')
  92. this.roundRect(ctx, width / 2 - 45, 340, 90, 90, 8)
  93. ctx.setStrokeStyle('#E2E8F0')
  94. ctx.setLineWidth(1)
  95. this.roundRect(ctx, width / 2 - 45, 340, 90, 90, 8)
  96. ctx.stroke()
  97. ctx.setFillStyle('#CBD5E1')
  98. ctx.setFontSize(11)
  99. ctx.fillText('小程序码', width / 2, 390)
  100. ctx.setFillStyle('#94A3B8')
  101. ctx.setFontSize(12)
  102. ctx.fillText('爱伴·AI之旅训练营', width / 2, 420)
  103. ctx.draw(false)
  104. },
  105. roundRect(ctx, x, y, w, h, r) {
  106. ctx.beginPath()
  107. ctx.moveTo(x + r, y)
  108. ctx.lineTo(x + w - r, y)
  109. ctx.arcTo(x + w, y, x + w, y + r, r)
  110. ctx.lineTo(x + w, y + h - r)
  111. ctx.arcTo(x + w, y + h, x + w - r, y + h, r)
  112. ctx.lineTo(x + r, y + h)
  113. ctx.arcTo(x, y + h, x, y + h - r, r)
  114. ctx.lineTo(x, y + r)
  115. ctx.arcTo(x, y, x + r, y, r)
  116. ctx.closePath()
  117. ctx.fill()
  118. },
  119. copyCode() {
  120. if (!this.inviteCode) return
  121. uni.setClipboardData({
  122. data: this.inviteCode,
  123. success: function() {
  124. uni.showToast({ title: '邀请码已复制', icon: 'success' })
  125. }
  126. })
  127. },
  128. copyShareText() {
  129. if (!this.shareText) {
  130. uni.showToast({ title: '分享文案暂未生成', icon: 'none' })
  131. return
  132. }
  133. uni.setClipboardData({
  134. data: this.shareText,
  135. success: function() {
  136. uni.showToast({ title: '分享文案已复制', icon: 'success' })
  137. }
  138. })
  139. },
  140. savePoster() {
  141. var self = this
  142. if (this.saving) return
  143. this.saving = true
  144. uni.canvasToTempFilePath({
  145. canvasId: 'posterCanvas',
  146. success: function(res) {
  147. uni.saveImageToPhotosAlbum({
  148. filePath: res.tempFilePath,
  149. success: function() {
  150. uni.showToast({ title: '海报已保存到相册', icon: 'success' })
  151. },
  152. fail: function(err) {
  153. if (err && (err.errMsg || '').indexOf('auth') > -1) {
  154. uni.showModal({
  155. title: '需要相册权限',
  156. content: '请在设置中开启「保存到相册」权限',
  157. confirmText: '去设置',
  158. success: function(r) {
  159. if (r.confirm) {
  160. uni.openSetting({})
  161. }
  162. }
  163. })
  164. } else {
  165. uni.showToast({ title: '保存失败,请稍后重试', icon: 'none' })
  166. }
  167. },
  168. complete: function() {
  169. self.saving = false
  170. }
  171. })
  172. },
  173. fail: function() {
  174. self.saving = false
  175. uni.showToast({ title: '海报生成失败', icon: 'none' })
  176. }
  177. })
  178. },
  179. goStats() {
  180. uni.navigateTo({ url: '/pages/share/stats' })
  181. }
  182. }
  183. }
  184. </script>
  185. <style scoped>
  186. .share-page { min-height: 100vh; background: #F5F5F5; padding: 24rpx 32rpx; }
  187. .poster-card { background: #FFF; border-radius: 16rpx; padding: 24rpx; margin-bottom: 24rpx; display: flex; justify-content: center; }
  188. .poster-canvas { width: 300px; height: 450px; border-radius: 12rpx; }
  189. .invite-row { background: #FFF; border-radius: 16rpx; padding: 32rpx; margin-bottom: 24rpx; display: flex; align-items: center; }
  190. .share-text-row { padding: 20rpx 32rpx; }
  191. .share-text-preview { flex: 1; font-size: 24rpx; color: #F97316; margin-left: 24rpx; line-height: 1.5; }
  192. .share-text-preview:active { opacity: 0.7; }
  193. .invite-label { font-size: 28rpx; color: #1E293B; font-weight: 600; flex-shrink: 0; }
  194. .invite-code { flex: 1; font-size: 32rpx; color: #F97316; font-weight: 700; font-family: monospace; margin-left: 24rpx; letter-spacing: 4rpx; }
  195. .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; }
  196. .copy-btn[disabled] { background: #FDBA74; color: #FFF; }
  197. .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; }
  198. .save-btn:active { opacity: 0.85; }
  199. .stats-link { display: flex; align-items: center; justify-content: center; padding: 32rpx 0; color: #64748B; font-size: 28rpx; }
  200. .stats-link .arrow { color: #94A3B8; font-size: 32rpx; margin-left: 8rpx; }
  201. </style>