invite.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. <template>
  2. <view class="container">
  3. <!-- 邀请码展示 -->
  4. <view class="code-card">
  5. <text class="code-title">我的邀请码</text>
  6. <text class="code-value">{{ referralCode || '加载中...' }}</text>
  7. <view class="code-actions">
  8. <button class="action-btn" @click="copyCode">复制邀请码</button>
  9. <button class="action-btn share" @click="shareInvite">分享给好友</button>
  10. </view>
  11. </view>
  12. <!-- 绑定推荐人 -->
  13. <view class="bind-card">
  14. <text class="bind-title">我有推荐人</text>
  15. <view class="bind-row">
  16. <input v-model="bindCode" type="text" placeholder="输入推荐人的邀请码" class="bind-input" />
  17. <button class="bind-btn" @click="bindReferral">绑定</button>
  18. </view>
  19. </view>
  20. <!-- 已邀请列表 -->
  21. <view class="list-section">
  22. <text class="section-title">已邀请好友 ({{ total || 0 }})</text>
  23. <view class="empty-state" v-if="!loading && list.length === 0">
  24. <text>还没有邀请好友,快去分享吧</text>
  25. </view>
  26. <view class="invite-item" v-for="item in list" :key="item.id">
  27. <text class="invite-avatar">&#x1F464;</text>
  28. <view class="invite-info">
  29. <text class="invite-name">{{ item.nickname || '好友' }}</text>
  30. <text class="invite-time">{{ formatTime(item.createdAt) }}</text>
  31. </view>
  32. </view>
  33. <view class="loading-state" v-if="loading">
  34. <text>加载中...</text>
  35. </view>
  36. </view>
  37. </view>
  38. </template>
  39. <script>
  40. import { getReferralCode, bindReferral, getReferralList } from '../../utils/api.js'
  41. export default {
  42. data() {
  43. return {
  44. referralCode: '',
  45. bindCode: '',
  46. list: [],
  47. total: 0,
  48. page: 1,
  49. loading: false,
  50. hasMore: true
  51. }
  52. },
  53. onLoad() {
  54. this.loadCode()
  55. this.loadList()
  56. },
  57. onReachBottom() {
  58. if (this.hasMore && !this.loading) {
  59. this.page++
  60. this.loadList()
  61. }
  62. },
  63. methods: {
  64. async loadCode() {
  65. try {
  66. const res = await getReferralCode()
  67. if (res.data) {
  68. this.referralCode = res.data.referralCode || res.data.code || ''
  69. }
  70. } catch (e) {
  71. console.error('获取邀请码失败', e)
  72. }
  73. },
  74. async loadList() {
  75. this.loading = true
  76. try {
  77. const res = await getReferralList(this.page)
  78. if (res.data) {
  79. if (res.data.records) {
  80. this.list = this.page === 1 ? res.data.records : [...this.list, ...res.data.records]
  81. }
  82. this.total = res.data.total || 0
  83. if (res.data.records) {
  84. this.hasMore = res.data.records.length >= 10
  85. } else {
  86. this.hasMore = false
  87. }
  88. }
  89. } catch (e) {
  90. console.error('获取邀请列表失败', e)
  91. }
  92. this.loading = false
  93. },
  94. copyCode() {
  95. if (!this.referralCode) return
  96. uni.setClipboardData({
  97. data: this.referralCode,
  98. success: () => {
  99. uni.showToast({ title: '已复制邀请码', icon: 'success' })
  100. }
  101. })
  102. },
  103. shareInvite() {
  104. uni.showToast({ title: '即将上线', icon: 'none' })
  105. },
  106. async bindReferral() {
  107. if (!this.bindCode) {
  108. uni.showToast({ title: '请输入邀请码', icon: 'none' })
  109. return
  110. }
  111. if (this.bindCode === this.referralCode) {
  112. uni.showToast({ title: '不能绑定自己的邀请码', icon: 'none' })
  113. return
  114. }
  115. try {
  116. await bindReferral(this.bindCode)
  117. uni.showToast({ title: '绑定成功', icon: 'success' })
  118. this.bindCode = ''
  119. } catch (e) {
  120. uni.showToast({ title: e.message || '绑定失败', icon: 'none' })
  121. }
  122. },
  123. formatTime(time) {
  124. if (!time) return ''
  125. const t = new Date(time)
  126. return t.getFullYear() + '-' + String(t.getMonth()+1).padStart(2,'0') + '-' + String(t.getDate()).padStart(2,'0')
  127. }
  128. }
  129. }
  130. </script>
  131. <style scoped>
  132. .container {
  133. padding: 30rpx;
  134. min-height: 100vh;
  135. background: #f5f7fa;
  136. }
  137. .code-card {
  138. background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
  139. border-radius: 20rpx;
  140. padding: 40rpx;
  141. text-align: center;
  142. color: #fff;
  143. margin-bottom: 30rpx;
  144. }
  145. .code-title {
  146. font-size: 26rpx;
  147. opacity: 0.9;
  148. }
  149. .code-value {
  150. font-size: 48rpx;
  151. font-weight: bold;
  152. letter-spacing: 6rpx;
  153. margin: 20rpx 0 30rpx;
  154. display: block;
  155. }
  156. .code-actions {
  157. display: flex;
  158. gap: 20rpx;
  159. }
  160. .action-btn {
  161. flex: 1;
  162. background: rgba(255,255,255,0.2);
  163. color: #fff;
  164. font-size: 26rpx;
  165. border-radius: 40rpx;
  166. height: 64rpx;
  167. line-height: 64rpx;
  168. border: 1rpx solid rgba(255,255,255,0.3);
  169. }
  170. .action-btn::after { border: none; }
  171. .bind-card {
  172. background: #fff;
  173. border-radius: 20rpx;
  174. padding: 30rpx;
  175. margin-bottom: 30rpx;
  176. }
  177. .bind-title {
  178. font-size: 28rpx;
  179. font-weight: bold;
  180. color: #333;
  181. display: block;
  182. margin-bottom: 16rpx;
  183. }
  184. .bind-row {
  185. display: flex;
  186. gap: 20rpx;
  187. }
  188. .bind-input {
  189. flex: 1;
  190. border: 1rpx solid #ddd;
  191. border-radius: 10rpx;
  192. padding: 20rpx;
  193. font-size: 28rpx;
  194. }
  195. .bind-btn {
  196. background: #3A7CC4;
  197. color: #fff;
  198. font-size: 26rpx;
  199. border-radius: 10rpx;
  200. height: 64rpx;
  201. line-height: 64rpx;
  202. padding: 0 30rpx;
  203. }
  204. .bind-btn::after { border: none; }
  205. .list-section {
  206. background: #fff;
  207. border-radius: 20rpx;
  208. padding: 30rpx;
  209. }
  210. .section-title {
  211. font-size: 28rpx;
  212. font-weight: bold;
  213. color: #333;
  214. display: block;
  215. margin-bottom: 20rpx;
  216. }
  217. .invite-item {
  218. display: flex;
  219. align-items: center;
  220. padding: 20rpx 0;
  221. border-bottom: 1rpx solid #f0f0f0;
  222. }
  223. .invite-item:last-child { border-bottom: none; }
  224. .invite-avatar {
  225. font-size: 48rpx;
  226. margin-right: 20rpx;
  227. }
  228. .invite-info { flex: 1; }
  229. .invite-name {
  230. font-size: 28rpx;
  231. color: #333;
  232. display: block;
  233. }
  234. .invite-time {
  235. font-size: 22rpx;
  236. color: #999;
  237. margin-top: 4rpx;
  238. }
  239. .empty-state, .loading-state {
  240. text-align: center;
  241. padding: 60rpx;
  242. color: #999;
  243. font-size: 28rpx;
  244. }
  245. </style>