onboarding.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. <template>
  2. <view class="container">
  3. <!-- 进度统计 -->
  4. <view class="progress-card">
  5. <text class="progress-title">新手任务</text>
  6. <view class="progress-bar-wrap">
  7. <view class="progress-bar">
  8. <view class="progress-fill" :style="{ width: progressPercent + '%' }"></view>
  9. </view>
  10. <text class="progress-text">{{ completedCount }}/6 已完成</text>
  11. </view>
  12. <text class="progress-hint">完成所有任务,领取丰厚奖励!</text>
  13. </view>
  14. <!-- 任务列表 -->
  15. <view class="task-list">
  16. <view class="task-item" v-for="item in taskList" :key="item.taskType">
  17. <view class="task-left">
  18. <view class="task-icon">{{ item.icon }}</view>
  19. <view class="task-info">
  20. <text class="task-name">{{ item.name }}</text>
  21. <text class="task-reward">获得 {{ item.rewardPoints }} 积分 + {{ item.rewardEnergy }} 能量</text>
  22. </view>
  23. </view>
  24. <view class="task-right">
  25. <text class="task-status" v-if="item.status === 'CLAIMED'" :class="'status-claimed'">已领取</text>
  26. <text class="task-status" v-else-if="item.status === 'COMPLETED'" :class="'status-completed'">已完成</text>
  27. <text class="task-status" v-else :class="'status-pending'">待完成</text>
  28. <button
  29. v-if="item.status === 'COMPLETED'"
  30. class="claim-btn"
  31. @click="claimReward(item)"
  32. :disabled="item.claiming"
  33. >领取</button>
  34. </view>
  35. </view>
  36. </view>
  37. <!-- 加载状态 -->
  38. <view class="loading-state" v-if="loading">
  39. <text>加载中...</text>
  40. </view>
  41. </view>
  42. </template>
  43. <script>
  44. import { getOnboardingProgress, claimOnboardingReward } from '../../utils/api.js'
  45. var TASK_META = {
  46. REGISTER: { icon: '📝', name: '完成注册' },
  47. COMPLETE_PROFILE: { icon: '👤', name: '完善个人资料' },
  48. ADD_CHILD: { icon: '👨\u200D👩\u200D👧\u200D👦', name: '添加家庭成员' },
  49. FIRST_CHECKIN: { icon: '✅', name: '完成首次健康打卡' },
  50. FIRST_TASK: { icon: '🎯', name: '完成首次任务' },
  51. BIND_REFERRAL: { icon: '🤝', name: '绑定推荐人' }
  52. }
  53. export default {
  54. data() {
  55. return {
  56. taskList: [],
  57. loading: false
  58. }
  59. },
  60. computed: {
  61. completedCount() {
  62. var count = 0
  63. for (var i = 0; i < this.taskList.length; i++) {
  64. if (this.taskList[i].status === 'COMPLETED' || this.taskList[i].status === 'CLAIMED') {
  65. count++
  66. }
  67. }
  68. return count
  69. },
  70. progressPercent() {
  71. return Math.round((this.completedCount / 6) * 100)
  72. }
  73. },
  74. onLoad() {
  75. this.loadProgress()
  76. },
  77. onShow() {
  78. this.loadProgress()
  79. },
  80. methods: {
  81. async loadProgress() {
  82. this.loading = true
  83. try {
  84. var res = await getOnboardingProgress()
  85. var records = (res.data && res.data.records) || res.data || []
  86. if (!Array.isArray(records)) {
  87. records = []
  88. }
  89. this.taskList = records.map(function(item) {
  90. var meta = TASK_META[item.taskType] || { icon: '📋', name: item.taskType }
  91. return {
  92. taskType: item.taskType,
  93. icon: meta.icon,
  94. name: meta.name,
  95. rewardPoints: item.rewardPoints || 0,
  96. rewardEnergy: item.rewardEnergy || 0,
  97. status: item.status || 'PENDING',
  98. claiming: false
  99. }
  100. })
  101. } catch (e) {
  102. console.error('获取新手任务进度失败', e)
  103. }
  104. this.loading = false
  105. },
  106. async claimReward(item) {
  107. if (item.claiming) return
  108. item.claiming = true
  109. try {
  110. await claimOnboardingReward(item.taskType)
  111. item.status = 'CLAIMED'
  112. uni.showToast({
  113. title: '获得' + item.rewardPoints + '积分+' + item.rewardEnergy + '能量',
  114. icon: 'none',
  115. duration: 2000
  116. })
  117. } catch (e) {
  118. uni.showToast({ title: '领取失败', icon: 'none' })
  119. }
  120. item.claiming = false
  121. }
  122. }
  123. }
  124. </script>
  125. <style scoped>
  126. .container {
  127. padding: 30rpx;
  128. min-height: 100vh;
  129. background: #FFF7ED;
  130. }
  131. /* ===== 进度卡片 ===== */
  132. .progress-card {
  133. background: linear-gradient(135deg, #F97316, #EA580C);
  134. border-radius: 20rpx;
  135. padding: 40rpx;
  136. color: #fff;
  137. margin-bottom: 24rpx;
  138. }
  139. .progress-title {
  140. font-size: 36rpx;
  141. font-weight: bold;
  142. display: block;
  143. margin-bottom: 24rpx;
  144. }
  145. .progress-bar-wrap {
  146. display: flex;
  147. align-items: center;
  148. margin-bottom: 16rpx;
  149. }
  150. .progress-bar {
  151. flex: 1;
  152. height: 16rpx;
  153. background: rgba(255,255,255,0.3);
  154. border-radius: 8rpx;
  155. overflow: hidden;
  156. }
  157. .progress-fill {
  158. height: 100%;
  159. background: #fff;
  160. border-radius: 8rpx;
  161. transition: width 0.3s ease;
  162. }
  163. .progress-text {
  164. font-size: 24rpx;
  165. margin-left: 16rpx;
  166. white-space: nowrap;
  167. }
  168. .progress-hint {
  169. font-size: 22rpx;
  170. opacity: 0.85;
  171. display: block;
  172. }
  173. /* ===== 任务列表 ===== */
  174. .task-list {
  175. background: #fff;
  176. border-radius: 20rpx;
  177. overflow: hidden;
  178. box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
  179. }
  180. .task-item {
  181. display: flex;
  182. align-items: center;
  183. justify-content: space-between;
  184. padding: 28rpx 30rpx;
  185. border-bottom: 1rpx solid #FED7AA;
  186. }
  187. .task-item:last-child {
  188. border-bottom: none;
  189. }
  190. .task-left {
  191. display: flex;
  192. align-items: center;
  193. flex: 1;
  194. margin-right: 20rpx;
  195. }
  196. .task-icon {
  197. width: 72rpx;
  198. height: 72rpx;
  199. border-radius: 16rpx;
  200. background: #FEF3C7;
  201. display: flex;
  202. align-items: center;
  203. justify-content: center;
  204. font-size: 36rpx;
  205. margin-right: 20rpx;
  206. flex-shrink: 0;
  207. }
  208. .task-info {
  209. flex: 1;
  210. }
  211. .task-name {
  212. font-size: 28rpx;
  213. font-weight: 600;
  214. color: #1E293B;
  215. display: block;
  216. margin-bottom: 6rpx;
  217. }
  218. .task-reward {
  219. font-size: 22rpx;
  220. color: #F97316;
  221. display: block;
  222. }
  223. .task-right {
  224. display: flex;
  225. align-items: center;
  226. flex-shrink: 0;
  227. }
  228. .task-status {
  229. font-size: 22rpx;
  230. padding: 6rpx 16rpx;
  231. border-radius: 20rpx;
  232. margin-right: 12rpx;
  233. }
  234. .status-pending {
  235. background: #F1F5F9;
  236. color: #94A3B8;
  237. }
  238. .status-completed {
  239. background: #DCFCE7;
  240. color: #16A34A;
  241. }
  242. .status-claimed {
  243. background: #FEF3C7;
  244. color: #D97706;
  245. }
  246. .claim-btn {
  247. background: #F97316;
  248. color: #fff;
  249. font-size: 24rpx;
  250. border-radius: 30rpx;
  251. height: 56rpx;
  252. line-height: 56rpx;
  253. padding: 0 28rpx;
  254. font-weight: 500;
  255. }
  256. .claim-btn::after {
  257. border: none;
  258. }
  259. .claim-btn:active {
  260. opacity: 0.8;
  261. }
  262. .claim-btn[disabled] {
  263. opacity: 0.5;
  264. }
  265. .loading-state {
  266. text-align: center;
  267. padding: 60rpx;
  268. color: #94A3B8;
  269. font-size: 28rpx;
  270. }
  271. </style>