You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

218 regels
5.2 KiB

  1. <template>
  2. <view class="landing-page">
  3. <view class="invite-card">
  4. <view class="avatar-wrap">
  5. <image v-if="inviter.avatar" class="avatar" :src="inviter.avatar" mode="aspectFill" />
  6. <text v-else class="avatar-text">AI</text>
  7. </view>
  8. <text class="from-text">{{ inviter.user_name || '你的好友' }} 邀请你</text>
  9. <text class="title">{{ config.landing_title || '一起体验 AI ONLINE' }}</text>
  10. <text class="desc">{{ config.landing_desc || '登录后领取点数奖励,AI取名、动漫头像、证件照等功能都能使用。' }}</text>
  11. <view class="gift-row">
  12. <view class="gift-item">
  13. <text class="gift-num">{{ config.invitee_points || 0 }}</text>
  14. <text class="gift-label">你可领取点数</text>
  15. </view>
  16. <view class="gift-item">
  17. <text class="gift-num">{{ config.inviter_points || 0 }}</text>
  18. <text class="gift-label">好友获得点数</text>
  19. </view>
  20. </view>
  21. <view class="claim-btn" @click="claim">{{ userStore.isLogin ? '进入 AI ONLINE' : '登录并领取' }}</view>
  22. <text class="rule-text">{{ config.rule_text || '好友通过你的邀请登录注册成功后,你和好友都可获得点数奖励。' }}</text>
  23. </view>
  24. </view>
  25. </template>
  26. <script setup>
  27. import { ref } from 'vue'
  28. import { onLoad, onShareAppMessage } from '@dcloudio/uni-app'
  29. import { inviteApi } from '@/api/index.js'
  30. import { useUserStore } from '@/store/user.js'
  31. import { useApp } from '@/utils/useApp.js'
  32. import { buildShareOptions } from '@/utils/share.js'
  33. const userStore = useUserStore()
  34. const { toast } = useApp()
  35. const config = ref({})
  36. const inviter = ref({})
  37. const inviteCode = ref('')
  38. onLoad((options = {}) => {
  39. userStore.restore()
  40. inviteCode.value = options.invite_code || options.invite_open_id || parseInviteScene(options.scene || '')
  41. if (inviteCode.value) uni.setStorageSync('invite_code', inviteCode.value)
  42. if (options.source) uni.setStorageSync('source', options.source)
  43. loadConfig(options.scene || '')
  44. })
  45. onShareAppMessage(() => buildShareOptions({
  46. title: config.value.share_title || '邀请你体验 AI ONLINE',
  47. path: `/pages/invite/landing?invite_code=${inviteCode.value}`,
  48. imageUrl: config.value.share_image || '',
  49. source: 'share_invite_landing'
  50. }))
  51. function parseInviteScene(scene) {
  52. if (!scene) return ''
  53. let text = ''
  54. try {
  55. text = decodeURIComponent(scene)
  56. } catch (e) {
  57. text = scene
  58. }
  59. const match = text.match(/(?:^|[&?])i=([^&]+)/) || text.match(/(?:^|[&?])i_([^&]+)/)
  60. if (match) return match[1]
  61. if (text.indexOf('i_') === 0) return text.slice(2)
  62. return text
  63. }
  64. async function loadConfig(scene) {
  65. try {
  66. const res = await inviteApi.getConfig({
  67. invite_code: inviteCode.value,
  68. scene
  69. })
  70. config.value = res.data.config || {}
  71. inviter.value = res.data.inviter || {}
  72. if (res.data.invite_code) {
  73. inviteCode.value = res.data.invite_code
  74. uni.setStorageSync('invite_code', inviteCode.value)
  75. }
  76. } catch (e) {
  77. toast(e.msg || '邀请信息加载失败')
  78. }
  79. }
  80. function claim() {
  81. if (userStore.isLogin) {
  82. uni.switchTab({ url: '/pages/toolbox/toolbox' })
  83. return
  84. }
  85. uni.navigateTo({ url: '/pages/login/login' })
  86. }
  87. </script>
  88. <style lang="scss" scoped>
  89. .landing-page {
  90. min-height: 100vh;
  91. padding: 44rpx 24rpx;
  92. background: linear-gradient(180deg, #eef7ff 0%, #f7f9fc 42%, #ffffff 100%);
  93. box-sizing: border-box;
  94. }
  95. .invite-card {
  96. min-height: calc(100vh - 88rpx);
  97. padding: 52rpx 34rpx 38rpx;
  98. border-radius: 34rpx;
  99. background: #ffffff;
  100. box-shadow: 0 20rpx 50rpx rgba(30, 66, 120, 0.08);
  101. display: flex;
  102. flex-direction: column;
  103. align-items: center;
  104. box-sizing: border-box;
  105. }
  106. .avatar-wrap {
  107. width: 124rpx;
  108. height: 124rpx;
  109. border-radius: 50%;
  110. background: #eaf5ff;
  111. box-shadow: 0 10rpx 24rpx rgba(11, 140, 255, 0.18);
  112. display: flex;
  113. align-items: center;
  114. justify-content: center;
  115. overflow: hidden;
  116. }
  117. .avatar {
  118. width: 100%;
  119. height: 100%;
  120. }
  121. .avatar-text {
  122. color: #0b8cff;
  123. font-size: 34rpx;
  124. font-weight: 900;
  125. }
  126. .from-text {
  127. margin-top: 24rpx;
  128. color: #7f8896;
  129. font-size: 25rpx;
  130. line-height: 1;
  131. }
  132. .title {
  133. margin-top: 28rpx;
  134. color: #172033;
  135. font-size: 46rpx;
  136. font-weight: 900;
  137. line-height: 1.2;
  138. text-align: center;
  139. }
  140. .desc {
  141. margin-top: 20rpx;
  142. color: #687386;
  143. font-size: 28rpx;
  144. line-height: 1.55;
  145. text-align: center;
  146. }
  147. .gift-row {
  148. width: 100%;
  149. margin-top: 42rpx;
  150. display: grid;
  151. grid-template-columns: repeat(2, minmax(0, 1fr));
  152. gap: 18rpx;
  153. }
  154. .gift-item {
  155. height: 176rpx;
  156. border-radius: 26rpx;
  157. background: #f3f9ff;
  158. display: flex;
  159. flex-direction: column;
  160. align-items: center;
  161. justify-content: center;
  162. }
  163. .gift-num {
  164. color: #0b8cff;
  165. font-size: 54rpx;
  166. font-weight: 900;
  167. line-height: 1;
  168. }
  169. .gift-label {
  170. margin-top: 14rpx;
  171. color: #687386;
  172. font-size: 23rpx;
  173. }
  174. .claim-btn {
  175. width: 100%;
  176. height: 92rpx;
  177. margin-top: auto;
  178. border-radius: 999rpx;
  179. color: #ffffff;
  180. background: #0b8cff;
  181. font-size: 30rpx;
  182. font-weight: 900;
  183. line-height: 92rpx;
  184. text-align: center;
  185. box-shadow: 0 14rpx 28rpx rgba(11, 140, 255, 0.22);
  186. }
  187. .rule-text {
  188. margin-top: 22rpx;
  189. color: #9aa4b2;
  190. font-size: 23rpx;
  191. line-height: 1.45;
  192. text-align: center;
  193. }
  194. </style>