ProfileBadges.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. <template>
  2. <view class="section">
  3. <view class="section-header">
  4. <text class="section-title">🏅 成就徽章</text>
  5. <text class="section-more" @click="goAllBadges">全部 ›</text>
  6. </view>
  7. <view class="badge-grid">
  8. <view class="badge-item" v-for="badge in badges" :key="badge.id">
  9. <view :class="['badge-icon-wrap', badge.unlocked ? 'unlocked' : 'locked']">
  10. <text class="badge-icon">{{ badge.icon }}</text>
  11. </view>
  12. <text class="badge-name">{{ badge.name }}</text>
  13. </view>
  14. </view>
  15. </view>
  16. </template>
  17. <script>
  18. import { getUserBadges } from '../../../utils/api.js'
  19. export default {
  20. name: 'ProfileBadges',
  21. data() {
  22. return {
  23. badgesLoaded: false,
  24. badges: [
  25. { id: 1, icon: '🏆', name: '连续7天', unlocked: false },
  26. { id: 2, icon: '⭐', name: '积分达人', unlocked: false },
  27. { id: 3, icon: '🔥', name: '执行之星', unlocked: false },
  28. { id: 4, icon: '🌟', name: '学习先锋', unlocked: false }
  29. ]
  30. }
  31. },
  32. onShow() {
  33. if (!uni.getStorageSync('token')) return
  34. this.loadBadges()
  35. },
  36. methods: {
  37. getDefaultBadges() {
  38. return [
  39. { id: 1, icon: '\u{1F3C6}', name: '连续7天', unlocked: false },
  40. { id: 2, icon: '\u{2B50}', name: '积分达人', unlocked: false },
  41. { id: 3, icon: '\u{1F525}', name: '执行之星', unlocked: false },
  42. { id: 4, icon: '\u{1F31F}', name: '学习先锋', unlocked: false },
  43. { id: 5, icon: '\u{1F4AA}', name: '体能达人', unlocked: false },
  44. { id: 6, icon: '\u{1F9E0}', name: '心智成长', unlocked: false },
  45. { id: 7, icon: '👨‍👩‍👧‍👦', name: '家庭之星', unlocked: false },
  46. { id: 8, icon: '\u{1F4DA}', name: '阅读达人', unlocked: false },
  47. { id: 9, icon: '\u{1F3AF}', name: '全能冠军', unlocked: false }
  48. ]
  49. },
  50. loadBadges() {
  51. let memberId = uni.getStorageSync('currentChildId') || null
  52. if (!memberId) {
  53. // Try to get first child id
  54. const children = uni.getStorageSync('childrenList')
  55. if (children && children.length > 0) {
  56. memberId = children[0].id
  57. }
  58. }
  59. const defaultBadges = this.getDefaultBadges()
  60. if (memberId) {
  61. getUserBadges(memberId).then((res) => {
  62. if (res.code === 200 && res.data && res.data.length > 0) {
  63. const list = res.data.map((item, i) => ({
  64. id: item.badge && item.badge.id || i + 1,
  65. icon: item.badge && item.badge.icon || '⭐',
  66. name: item.badge && item.badge.name || '未知勋章',
  67. unlocked: true
  68. }))
  69. while (list.length < 9) {
  70. const idx = list.length
  71. list.push({
  72. id: defaultBadges[idx].id,
  73. icon: defaultBadges[idx].icon,
  74. name: defaultBadges[idx].name,
  75. unlocked: false
  76. })
  77. }
  78. this.badges = list.slice(0, 9)
  79. } else {
  80. this.loadBadgesLocal(defaultBadges)
  81. }
  82. }).catch(() => {
  83. this.loadBadgesLocal(defaultBadges)
  84. })
  85. } else {
  86. this.loadBadgesLocal(defaultBadges)
  87. }
  88. },
  89. loadBadgesLocal(defaultBadges) {
  90. const streakDays = parseInt(uni.getStorageSync('streakDays') || 0)
  91. const totalPoints = parseInt(uni.getStorageSync('totalPoints') || 0)
  92. const completedTasks = parseInt(uni.getStorageSync('completedTasks') || 0)
  93. const badges = JSON.parse(JSON.stringify(defaultBadges))
  94. if (streakDays >= 7) badges[0].unlocked = true
  95. if (totalPoints >= 1000) badges[1].unlocked = true
  96. if (completedTasks >= 50) badges[2].unlocked = true
  97. if (totalPoints >= 500) badges[3].unlocked = true
  98. if (streakDays >= 30) badges[4].unlocked = true
  99. if (completedTasks >= 100) badges[8].unlocked = true
  100. this.badges = badges
  101. this.badgesLoaded = true
  102. },
  103. goAllBadges() {
  104. uni.showToast({ title: '即将上线', icon: 'none' })
  105. }
  106. }
  107. }
  108. </script>
  109. <style scoped>
  110. .section {
  111. margin: 20rpx 30rpx;
  112. }
  113. .section-header {
  114. display: flex;
  115. justify-content: space-between;
  116. align-items: center;
  117. margin-bottom: 20rpx;
  118. }
  119. .section-title {
  120. font-size: 30rpx;
  121. font-weight: bold;
  122. color: #333;
  123. }
  124. .section-more {
  125. font-size: 24rpx;
  126. color: #999;
  127. }
  128. .badge-grid {
  129. display: flex;
  130. flex-direction: row;
  131. flex-wrap: wrap;
  132. background: #fff;
  133. border-radius: 20rpx;
  134. padding: 24rpx 16rpx;
  135. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  136. }
  137. .badge-item {
  138. width: 20%;
  139. display: flex;
  140. flex-direction: column;
  141. align-items: center;
  142. margin-bottom: 16rpx;
  143. }
  144. .badge-icon-wrap {
  145. width: 80rpx;
  146. height: 80rpx;
  147. border-radius: 50%;
  148. display: flex;
  149. align-items: center;
  150. justify-content: center;
  151. margin-bottom: 8rpx;
  152. font-size: 40rpx;
  153. }
  154. .badge-icon-wrap.unlocked {
  155. background: linear-gradient(135deg, #fdfbfb, #ebedee);
  156. box-shadow: 0 4rpx 16rpx rgba(253,160,91,0.3);
  157. }
  158. .badge-icon-wrap.locked {
  159. background: #f0f0f0;
  160. opacity: 0.5;
  161. }
  162. .badge-name {
  163. font-size: 20rpx;
  164. color: #999;
  165. text-align: center;
  166. }
  167. </style>