Browse Source

feat: add share referral code page to profile

E2E Test Bot 2 days ago
parent
commit
0640e7a1e1

+ 19 - 7
cfc-frontend/pages.json

@@ -839,14 +839,20 @@
             "navigationBarTitleText": "确认订单"
           }
         },
-        {
-          "path": "result",
-          "style": {
-            "navigationBarTitleText": "支付结果"
-          }
+      {
+        "path": "service-role-apply",
+        "style": {
+          "navigationBarTitleText": "服务角色申请"
         }
-      ]
-    },
+      },
+      {
+        "path": "share",
+        "style": {
+          "navigationBarTitleText": "分享推广码"
+        }
+      }
+    ]
+  },
     {
       "root": "pages/wealth-sub",
       "pages": [
@@ -1796,6 +1802,12 @@
           "style": {
             "navigationBarTitleText": "服务角色申请"
           }
+        },
+        {
+          "path": "share",
+          "style": {
+            "navigationBarTitleText": "分享推广码"
+          }
         }
       ]
     },

+ 1 - 1
cfc-frontend/pages/index-home/index.vue

@@ -681,7 +681,7 @@ title: '添加家庭成员',
     // 新用户把推荐码写入 storage,登录注册后由 login / user-edit 消费绑定推荐人
     // 注意:type=parent_invite 的家庭邀请码不存推荐码 storage,避免后续误绑
     if (options && !uni.getStorageSync('token')) {
-      var refCode = options.refCode
+      var refCode = options.refCode || options.ref
       if (!refCode && options.inviteCode && options.type !== 'parent_invite') {
         refCode = options.inviteCode  // 兼容老推荐码链接
       }

+ 7 - 0
cfc-frontend/pages/profile/components/ProfileMenu.vue

@@ -74,6 +74,10 @@
           <text>👁 对家庭成员可见</text>
           <switch :checked="showToFamily === 1" @change="onShowToFamilyChange" color="#5B9BD5" />
         </view>
+        <view class="menu-item" @click="goToShare">
+          <text>📤 分享推广码</text>
+          <text class="arrow">›</text>
+        </view>
         <view class="menu-item" @click="showInviteActionSheet">
           <text>📨 邀请加入</text>
           <text class="arrow">›</text>
@@ -216,6 +220,9 @@ var res = await getReferralCode()
       uni.navigateTo({ url: '/pages/shop/after-sales/after-sales' })
     },
     
+    goToShare() {
+      uni.navigateTo({ url: '/pages/profile/share' })
+    },
     showInviteActionSheet() {
       var self = this
       var ui = uni.getStorageSync('userInfo') || {}

+ 381 - 0
cfc-frontend/pages/profile/share.vue

@@ -0,0 +1,381 @@
+<template>
+  <view class="container">
+    <!-- 未登录 -->
+    <view v-if="!isLoggedIn" class="empty-state">
+      <text class="empty-icon">👤</text>
+      <text class="empty-title">请先登录</text>
+      <text class="empty-desc">登录后即可生成您的专属推广码</text>
+      <button class="btn-login" @click="goLogin">去登录</button>
+    </view>
+
+    <!-- 已登录 -->
+    <view v-else class="content">
+      <!-- 头部 -->
+      <view class="header">
+        <text class="title">分享推广码</text>
+        <text class="subtitle">将二维码分享给好友,扫码注册即成为您的下线</text>
+      </view>
+
+      <!-- 我的推广码 -->
+      <view class="code-card">
+        <view class="code-label">
+          <text class="label-text">我的推广码</text>
+          <view class="copy-btn" @click="copyCode">
+            <text class="copy-icon">📋</text>
+            <text class="copy-text">{{ copied ? '已复制' : '复制' }}</text>
+          </view>
+        </view>
+        <text class="code-value">{{ referralCode || '加载中...' }}</text>
+      </view>
+
+      <!-- 二维码区域 -->
+      <view class="qrcode-section" v-if="qrLoading || qrBase64">
+        <view class="qrcode-label">
+          <text class="label-text">扫码进入首页</text>
+        </view>
+        <view class="qrcode-wrap">
+          <image
+            v-if="qrBase64"
+            class="qrcode-img"
+            :src="qrBase64"
+            mode="aspectFit"
+          />
+          <view v-else class="qrcode-loading">
+            <text class="loading-spinner">⏳</text>
+            <text class="loading-text">生成中...</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 操作按钮 -->
+      <view class="actions" v-if="qrBase64">
+        <view class="action-btn save-btn" @click="saveQrcode">
+          <text class="action-icon">💾</text>
+          <text class="action-text">保存图片</text>
+        </view>
+        <view class="action-btn share-btn" @click="shareToWeixin">
+          <text class="action-icon">📤</text>
+          <text class="action-text">分享给朋友</text>
+        </view>
+      </view>
+
+      <!-- 说明 -->
+      <view class="tips">
+        <view class="tip-item">
+          <text class="tip-icon">💡</text>
+          <text class="tip-text">好友扫码后自动绑定推荐关系</text>
+        </view>
+        <view class="tip-item">
+          <text class="tip-icon">💡</text>
+          <text class="tip-text">好友注册后您将获得积分奖励</text>
+        </view>
+        <view class="tip-item">
+          <text class="tip-icon">💡</text>
+          <text class="tip-text">推广码可长期使用,无过期时间</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getReferralCode, extractReferralCode, getShareQrCode } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      referralCode: '',
+      qrBase64: '',
+      qrLoading: false,
+      copied: false
+    }
+  },
+  computed: {
+    isLoggedIn() {
+      return !!uni.getStorageSync('token')
+    }
+  },
+  onLoad() {
+    this.loadReferralCode()
+    this._onLoadFired = true
+  },
+  onShow() {
+    if (this._onLoadFired) {
+      this._onLoadFired = false
+    } else if (this.isLoggedIn && !this.referralCode) {
+      this.loadReferralCode()
+    }
+  },
+  methods: {
+    async loadReferralCode() {
+      if (!this.isLoggedIn) return
+      try {
+        var res = await getReferralCode()
+        var code = extractReferralCode(res)
+        this.referralCode = code || ''
+        if (this.referralCode) {
+          this.generateQrcode()
+        }
+      } catch (e) {
+        console.error('获取推广码失败', e)
+      }
+    },
+    generateQrcode() {
+      if (!this.referralCode) return
+      this.qrLoading = true
+      var scene = 'ref=' + this.referralCode
+      // 首页路径,扫码后进入首页并绑定推荐人
+      getShareQrCode('/pages/index-home/index', scene)
+        .then(function(res) {
+          if (res && res.code === 200 && res.data) {
+            this.qrBase64 = res.data.qrCodeBase64
+          }
+        }.bind(this))
+        .catch(function(e) {
+          console.error('生成二维码失败', e)
+          uni.showToast({ title: '生成失败,请重试', icon: 'none' })
+        })
+        .finally(function() {
+          this.qrLoading = false
+        }.bind(this))
+    },
+    copyCode() {
+      if (!this.referralCode) return
+      this.copied = true
+      uni.setClipboardData({
+        data: this.referralCode,
+        success: function() {
+          uni.showToast({ title: '推广码已复制', icon: 'success' })
+          setTimeout(function() {
+            this.copied = false
+          }.bind(this), 2000)
+        }.bind(this)
+      })
+    },
+    saveQrcode() {
+      if (!this.qrBase64) return
+      // 将 base64 转为临时文件再保存
+      var self = this
+      var rawBase64 = this.qrBase64.indexOf('base64,') >= 0
+        ? this.qrBase64.split('base64,')[1]
+        : this.qrBase64
+      var fsm = uni.getFileSystemManager()
+      var tempPath = wx.env.USER_DATA_PATH + '/share_qrcode_' + Date.now() + '.png'
+      fsm.writeFileSync(tempPath, rawBase64, 'base64')
+      uni.saveImageToPhotosAlbum({
+        filePath: tempPath,
+        success: function() {
+          uni.showToast({ title: '保存成功', icon: 'success' })
+        },
+        fail: function() {
+          uni.showToast({ title: '保存失败', icon: 'none' })
+        }
+      })
+    },
+    shareToWeixin() {
+      if (!this.referralCode) return
+      // 通过 uni.share 分享小程序卡片
+      var path = '/pages/index-home/index?refCode=' + this.referralCode
+      uni.share({
+        provider: 'weixin',
+        scene: { type: 0 },
+        type: 5,
+        imageUrl: this.qrBase64,
+        path: path,
+        title: '浠艾福 — 邀请你加入',
+        success: function() {
+          uni.showToast({ title: '分享成功', icon: 'success' })
+        },
+        fail: function() {
+          uni.showToast({ title: '分享失败', icon: 'none' })
+        }
+      })
+    },
+    goLogin() {
+      uni.navigateTo({ url: '/pages/login/login' })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: #F8F4EF;
+  padding-bottom: 60rpx;
+}
+
+/* 未登录 */
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  min-height: 70vh;
+  padding: 60rpx;
+}
+.empty-icon { font-size: 100rpx; margin-bottom: 24rpx; }
+.empty-title { font-size: 36rpx; font-weight: bold; color: #333; margin-bottom: 12rpx; }
+.empty-desc { font-size: 26rpx; color: #999; margin-bottom: 40rpx; }
+.btn-login {
+  width: 60%;
+  height: 80rpx;
+  line-height: 80rpx;
+  background: linear-gradient(135deg, #667eea, #764ba2);
+  color: #fff;
+  font-size: 30rpx;
+  font-weight: bold;
+  border-radius: 40rpx;
+  text-align: center;
+  border: none;
+}
+.btn-login::after { border: none; }
+
+/* 内容区 */
+.content { padding: 0 30rpx; }
+
+.header {
+  text-align: center;
+  padding: 60rpx 0 40rpx;
+}
+.title {
+  font-size: 44rpx;
+  font-weight: bold;
+  color: #1E293B;
+  display: block;
+  margin-bottom: 12rpx;
+}
+.subtitle {
+  font-size: 26rpx;
+  color: #94A3B8;
+  display: block;
+}
+
+/* 推广码卡片 */
+.code-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 32rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.code-label {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.label-text {
+  font-size: 26rpx;
+  color: #94A3B8;
+}
+.copy-btn {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+  padding: 8rpx 20rpx;
+  background: #FFF7ED;
+  border-radius: 20rpx;
+  border: 1rpx solid #FED7AA;
+}
+.copy-icon { font-size: 24rpx; }
+.copy-text { font-size: 24rpx; color: #F97316; }
+.code-value {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #F97316;
+  font-family: monospace;
+  text-align: center;
+  display: block;
+  letter-spacing: 8rpx;
+}
+
+/* 二维码区域 */
+.qrcode-section {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 32rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.qrcode-label {
+  margin-bottom: 24rpx;
+}
+.qrcode-wrap {
+  width: 360rpx;
+  height: 360rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #FAFAFA;
+  border-radius: 16rpx;
+  border: 2rpx dashed #E5E7EB;
+}
+.qrcode-img {
+  width: 340rpx;
+  height: 340rpx;
+  border-radius: 12rpx;
+}
+.qrcode-loading {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 16rpx;
+}
+.loading-spinner { font-size: 48rpx; }
+.loading-text { font-size: 24rpx; color: #94A3B8; }
+
+/* 操作按钮 */
+.actions {
+  display: flex;
+  gap: 20rpx;
+  margin-bottom: 30rpx;
+}
+.action-btn {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 12rpx;
+  padding: 32rpx;
+  border-radius: 20rpx;
+}
+.save-btn {
+  background: #fff;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.share-btn {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+.action-icon { font-size: 48rpx; }
+.action-text {
+  font-size: 26rpx;
+  font-weight: bold;
+}
+.save-btn .action-text { color: #333; }
+.share-btn .action-text { color: #fff; }
+
+/* 说明 */
+.tips {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 32rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.tip-item {
+  display: flex;
+  align-items: flex-start;
+  gap: 12rpx;
+  margin-bottom: 16rpx;
+}
+.tip-item:last-child { margin-bottom: 0; }
+.tip-icon { font-size: 28rpx; flex-shrink: 0; }
+.tip-text {
+  font-size: 24rpx;
+  color: #64748B;
+  line-height: 1.6;
+}
+</style>