Forráskód Böngészése

分享: 学员端分享邀请功能(首页/沙龙/课程主页带邀请码)

liaoxg 5 napja
szülő
commit
e9329f95b4

+ 24 - 0
train-frontend/pages/course/detail.vue

@@ -119,6 +119,7 @@
 import { getMyCourse, getCourseProgress, enterCourse, getCourseModules } from '@/utils/api.js'
 import { formatDateTime } from '@/utils/format.js'
 import { requireLogin } from '@/utils/guard.js'
+import { loadInviteCode, getInviteCode, buildSharePath } from '@/utils/share.js'
 
 export default {
   data() {
@@ -187,9 +188,32 @@ export default {
   },
   onLoad(options) {
     this.courseId = (options && options.courseId) || ''
+    loadInviteCode()
     this.loadAll()
   },
+  // 分享给好友:带 courseId 回跳课程主页
+  onShareAppMessage() {
+    return {
+      title: this.shareTitle(),
+      path: buildSharePath('/pages/course/detail' + (this.courseId ? '?courseId=' + this.courseId : ''))
+    }
+  },
+  // 分享到朋友圈
+  onShareTimeline() {
+    var query = this.courseId ? 'courseId=' + this.courseId : ''
+    var code = getInviteCode()
+    if (code) query += (query ? '&' : '') + 'inviteCode=' + code
+    return {
+      title: this.shareTitle(),
+      query: query
+    }
+  },
   methods: {
+    shareTitle() {
+      var c = this.course || {}
+      if (c.name) return (c.level || '') + ' · ' + c.name
+      return '爱伴·AI之旅|课程详情'
+    },
     loadAll() {
       var self = this
       getMyCourse(self.courseId).then(function(resp) {

+ 17 - 0
train-frontend/pages/index/index.vue

@@ -141,6 +141,7 @@
 <script>
 import { wechatLogin, getCourseList, getMyEnrollments, getMyBadges, getCourseProgress, getMyCert, getSalonList } from '@/utils/api.js'
 import { formatDateTime } from '@/utils/format.js'
+import { loadInviteCode, getInviteCode, buildSharePath } from '@/utils/share.js'
 
 export default {
   data() {
@@ -217,6 +218,7 @@ export default {
     }
   },
   onShow() {
+    loadInviteCode()
     this.loadInfo()
   },
   onLoad() {
@@ -235,6 +237,21 @@ export default {
       uni.$off('showLoginSheet')
     } catch (e) {}
   },
+  // 分享给好友
+  onShareAppMessage() {
+    return {
+      title: '爱伴·AI之旅|让 AI 成为您的贴心伙伴',
+      path: buildSharePath('/pages/index/index')
+    }
+  },
+  // 分享到朋友圈
+  onShareTimeline() {
+    var code = getInviteCode()
+    return {
+      title: '爱伴·AI之旅|让 AI 成为您的贴心伙伴',
+      query: code ? 'inviteCode=' + code : ''
+    }
+  },
   methods: {
     loadInfo() {
       if (!this.isLoggedIn) return

+ 41 - 0
train-frontend/pages/salon/list.vue

@@ -42,6 +42,13 @@
           </view>
           
           <view class="salon-foot">
+            <button 
+              class="share-btn" 
+              open-type="share" 
+              @click="setShareTarget(item)"
+            >
+              分享
+            </button>
             <button 
               v-if="canMemberFree" 
               class="member-btn" 
@@ -68,6 +75,7 @@
 import { getSalonList, getMyMember } from '@/utils/api.js'
 import { fenToYuan, formatDateTime } from '@/utils/format.js'
 import { guardPage } from '@/utils/guard.js'
+import { loadInviteCode, getInviteCode, buildSharePath } from '@/utils/share.js'
 
 export default {
   data() {
@@ -75,6 +83,7 @@ export default {
       salons: [],
       loading: false,
       member: {},
+      shareTarget: null,
       themeMap: {
         ai_basic: 'AI 认知基础',
         wealth: '家庭财富管理',
@@ -91,11 +100,41 @@ export default {
     }
   },
   onShow() {
+    loadInviteCode()
     if (!guardPage()) return
     this.loadSalons()
     this.loadMember()
   },
+  // 分享给好友:带 salonId 的沙龙分享(沙龙门未设详情页,跳转列表页)
+  onShareAppMessage() {
+    var t = this.shareTarget
+    if (t && t.id) {
+      return {
+        title: t.title,
+        path: buildSharePath('/pages/salon/list?salonId=' + t.id)
+      }
+    }
+    return {
+      title: '沙龙活动|爱伴·AI之旅',
+      path: buildSharePath('/pages/salon/list')
+    }
+  },
+  // 分享到朋友圈
+  onShareTimeline() {
+    var t = this.shareTarget
+    var query = t && t.id ? 'salonId=' + t.id : ''
+    var code = getInviteCode()
+    if (code) query += (query ? '&' : '') + 'inviteCode=' + code
+    return {
+      title: (t && t.title) ? t.title : '沙龙活动|爱伴·AI之旅',
+      query: query
+    }
+  },
   methods: {
+    setShareTarget(item) {
+      // open-type="share" 按钮:先记录当前要分享的沙龙,再触发 onShareAppMessage
+      this.shareTarget = item || null
+    },
     fenToYuan: fenToYuan,
     formatDateTime: formatDateTime,
     mapTheme(theme) {
@@ -169,6 +208,8 @@ export default {
 .salon-price-free { font-size: 28rpx; color: #22C55E; font-weight: 600; }
 .member-price { font-size: 24rpx; color: #5A4F4B; }
 .salon-foot { display: flex; align-items: center; justify-content: flex-end; }
+.share-btn { width: 140rpx; height: 64rpx; line-height: 64rpx; font-size: 26rpx; background: #FFF; color: #FC7A57; border-radius: 32rpx; border: 2rpx solid #FED7AA; margin: 0 16rpx 0 0; padding: 0; }
+.share-btn:active { opacity: 0.85; }
 .member-btn { width: 180rpx; height: 64rpx; line-height: 64rpx; font-size: 26rpx; background: #FFF0E8; color: #FC7A57; border-radius: 32rpx; border: 2rpx solid #FED7AA; margin: 0 16rpx 0 0; }
 .member-btn:active { opacity: 0.85; }
 .member-btn:disabled { background: #CBD5E1; color: #FFF; border-color: #CBD5E1; }

+ 53 - 0
train-frontend/utils/share.js

@@ -0,0 +1,53 @@
+/**
+ * 分享工具:小程序分享(onShareAppMessage / onShareTimeline)共用逻辑。
+ *
+ * 邀请码(转介绍跟踪):仅登录用户有,复用 train_user.invite_code,
+ * 经 /api/invite/myposter 惰性加载一次,按 userId 缓存(模块级 + storage)。
+ * 未登录 / 加载失败时不带邀请码,分享仍可用。
+ */
+import { getMyPoster } from '@/utils/api.js'
+
+let cached = { userId: '', code: '' }
+
+function currentUserId() {
+  return uni.getStorageSync('userId') || ''
+}
+
+function readInviteCode() {
+  var userId = currentUserId()
+  if (!userId) return ''
+  if (cached.userId === userId && cached.code) return cached.code
+  var code = uni.getStorageSync('invite_' + userId)
+  if (code) {
+    cached = { userId: userId, code: code }
+  }
+  return cached.userId === userId ? cached.code : ''
+}
+
+/** 后台预载当前用户邀请码(页面 onLoad/onShow 调用,不阻塞 UI、不弹登录框) */
+export function loadInviteCode() {
+  var userId = currentUserId()
+  if (!userId || readInviteCode()) return
+  getMyPoster().then(function(resp) {
+    var data = resp.data || {}
+    var code = data.inviteCode || ''
+    if (code) {
+      cached = { userId: userId, code: code }
+      uni.setStorageSync('invite_' + userId, code)
+    }
+  }).catch(function() {
+    // 静默失败:分享路径不带邀请码
+  })
+}
+
+/** 同步读取已缓存的邀请码(onShareAppMessage / onShareTimeline 回调内使用) */
+export function getInviteCode() {
+  return readInviteCode()
+}
+
+/** 拼分享路径:path(如 /pages/course/detail?courseId=1)末尾追加 inviteCode 参数 */
+export function buildSharePath(path) {
+  var code = readInviteCode()
+  if (!code) return path
+  return path + (path.indexOf('?') > -1 ? '&' : '?') + 'inviteCode=' + code
+}