Browse Source

Phase12: 分享海报增加sharePath与shareText字段,小程序展示分享文案与二维码占位

liaoxg 2 weeks ago
parent
commit
372b4ece18

+ 13 - 2
train-backend/src/main/java/com/train/controller/InviteController.java

@@ -24,6 +24,7 @@ import java.util.UUID;
  * <p>邀请码复用 train_user.invite_code;未设置时按需生成(折校:可进校友校验证写入)。
  * 海报 URL 为占位(test-mode 返回空,前端用邀请码本地渲染海报);
  * 生产环境接入微信 getwxacodeunlimit 生成小程序码后回填。
+ * 同时返回 sharePath(小程序带参路径)/ shareText(分享文案),便于前端拼装分享卡。
  */
 @Tag(name = "裂变分享", description = "专属海报/邀请码、转介绍统计")
 @RestController
@@ -36,8 +37,8 @@ public class InviteController {
     private TrainInviteMapper trainInviteMapper;
 
     /**
-     * 我的专属海报 + 邀请码。
-     * @return {inviteCode, posterUrl}
+     * 我的专属海报 + 邀请码 + 分享文案
+     * @return {inviteCode, posterUrl, sharePath, shareText}
      */
     @Operation(summary = "我的专属海报+邀请码")
     @PostMapping("/myposter")
@@ -52,9 +53,19 @@ public class InviteController {
                 trainUserMapper.updateById(user);
             }
         }
+        // 小程序带参路径(扫码/长按识别进入报名页)
+        String sharePath = "/pages/enroll/list?inviteCode=" + inviteCode;
+        // 分享文案(可复制的纯文本,分享给微信好友时粘贴用)
+        String nickname = user != null && StringUtils.hasText(user.getNickname()) ? user.getNickname() : "营员";
+        String shareText = "【" + nickname + "】邀请你加入「爱伴·AI之旅」训练营\n" +
+                "邀请码:" + inviteCode + "\n" +
+                "报名时填写邀请码可享专属权益,扫码或点击:\n" +
+                sharePath;
         Map<String, Object> row = new HashMap<>();
         row.put("inviteCode", inviteCode);
         row.put("posterUrl", ""); // 占位:test-mode 不生成海报图;生产接入 getwxacodeunlimit
+        row.put("sharePath", sharePath);
+        row.put("shareText", shareText);
         return Result.success(row);
     }
 

+ 34 - 2
train-frontend/pages/share/index.vue

@@ -10,6 +10,11 @@
       <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">
@@ -27,6 +32,7 @@ export default {
     return {
       inviteCode: '',
       posterUrl: '',
+      shareText: '',
       saving: false
     }
   },
@@ -44,13 +50,14 @@ export default {
         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() {})
+      }).catch(function () {})
     },
     drawPoster() {
       var self = this
@@ -86,9 +93,19 @@ export default {
       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, 400)
+      ctx.fillText('爱伴·AI之旅训练营', width / 2, 420)
       ctx.draw(false)
     },
     roundRect(ctx, x, y, w, h, r) {
@@ -114,6 +131,18 @@ export default {
         }
       })
     },
+    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
@@ -165,6 +194,9 @@ export default {
 .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; }
 .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; }