onboarding.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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 }}/{{ taskList.length }} 已完成</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. COMPLETE_SELF_CHECK: { icon: '🔍', name: '完成一次五维自检' }
  53. }
  54. export default {
  55. data() {
  56. return {
  57. taskList: [],
  58. loading: false
  59. }
  60. },
  61. computed: {
  62. completedCount() {
  63. var count = 0
  64. for (var i = 0; i < this.taskList.length; i++) {
  65. if (this.taskList[i].status === 'COMPLETED' || this.taskList[i].status === 'CLAIMED') {
  66. count++
  67. }
  68. }
  69. return count
  70. },
  71. progressPercent() {
  72. var total = this.taskList.length || 6
  73. return Math.round((this.completedCount / total) * 100)
  74. }
  75. },
  76. onLoad() {
  77. this.loadProgress()
  78. },
  79. onShow() {
  80. this.loadProgress()
  81. },
  82. methods: {
  83. async loadProgress() {
  84. this.loading = true
  85. try {
  86. var res = await getOnboardingProgress()
  87. var records = (res.data && res.data.records) || res.data || []
  88. if (!Array.isArray(records)) {
  89. records = []
  90. }
  91. this.taskList = records.map(function(item) {
  92. var meta = TASK_META[item.taskType] || { icon: '📋', name: item.taskType }
  93. return {
  94. taskType: item.taskType,
  95. icon: meta.icon,
  96. name: meta.name,
  97. rewardPoints: item.rewardPoints || 0,
  98. rewardEnergy: item.rewardEnergy || 0,
  99. status: item.status || 'PENDING',
  100. claiming: false
  101. }
  102. })
  103. } catch (e) {
  104. console.error('获取新手任务进度失败', e)
  105. }
  106. this.loading = false
  107. },
  108. async claimReward(item) {
  109. if (item.claiming) return
  110. item.claiming = true
  111. try {
  112. await claimOnboardingReward(item.taskType)
  113. item.status = 'CLAIMED'
  114. uni.showToast({
  115. title: '获得' + item.rewardPoints + '积分+' + item.rewardEnergy + '能量',
  116. icon: 'none',
  117. duration: 2000
  118. })
  119. } catch (e) {
  120. uni.showToast({ title: '领取失败', icon: 'none' })
  121. }
  122. item.claiming = false
  123. }
  124. }
  125. }
  126. </script>
  127. <style scoped>
  128. .container {
  129. padding: 30rpx;
  130. min-height: 100vh;
  131. background: #FFF7ED;
  132. }
  133. /* ===== 进度卡片 ===== */
  134. .progress-card {
  135. background: linear-gradient(135deg, #F97316, #EA580C);
  136. border-radius: 20rpx;
  137. padding: 40rpx;
  138. color: #fff;
  139. margin-bottom: 24rpx;
  140. }
  141. .progress-title {
  142. font-size: 36rpx;
  143. font-weight: bold;
  144. display: block;
  145. margin-bottom: 24rpx;
  146. }
  147. .progress-bar-wrap {
  148. display: flex;
  149. align-items: center;
  150. margin-bottom: 16rpx;
  151. }
  152. .progress-bar {
  153. flex: 1;
  154. height: 16rpx;
  155. background: rgba(255,255,255,0.3);
  156. border-radius: 8rpx;
  157. overflow: hidden;
  158. }
  159. .progress-fill {
  160. height: 100%;
  161. background: #fff;
  162. border-radius: 8rpx;
  163. transition: width 0.3s ease;
  164. }
  165. .progress-text {
  166. font-size: 24rpx;
  167. margin-left: 16rpx;
  168. white-space: nowrap;
  169. }
  170. .progress-hint {
  171. font-size: 22rpx;
  172. opacity: 0.85;
  173. display: block;
  174. }
  175. /* ===== 任务列表 ===== */
  176. .task-list {
  177. background: #fff;
  178. border-radius: 20rpx;
  179. overflow: hidden;
  180. box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
  181. }
  182. .task-item {
  183. display: flex;
  184. align-items: center;
  185. justify-content: space-between;
  186. padding: 28rpx 30rpx;
  187. border-bottom: 1rpx solid #FED7AA;
  188. }
  189. .task-item:last-child {
  190. border-bottom: none;
  191. }
  192. .task-left {
  193. display: flex;
  194. align-items: center;
  195. flex: 1;
  196. margin-right: 20rpx;
  197. }
  198. .task-icon {
  199. width: 72rpx;
  200. height: 72rpx;
  201. border-radius: 16rpx;
  202. background: #FEF3C7;
  203. display: flex;
  204. align-items: center;
  205. justify-content: center;
  206. font-size: 36rpx;
  207. margin-right: 20rpx;
  208. flex-shrink: 0;
  209. }
  210. .task-info {
  211. flex: 1;
  212. }
  213. .task-name {
  214. font-size: 28rpx;
  215. font-weight: 600;
  216. color: #1E293B;
  217. display: block;
  218. margin-bottom: 6rpx;
  219. }
  220. .task-reward {
  221. font-size: 22rpx;
  222. color: #F97316;
  223. display: block;
  224. }
  225. .task-right {
  226. display: flex;
  227. align-items: center;
  228. flex-shrink: 0;
  229. }
  230. .task-status {
  231. font-size: 22rpx;
  232. padding: 6rpx 16rpx;
  233. border-radius: 20rpx;
  234. margin-right: 12rpx;
  235. }
  236. .status-pending {
  237. background: #F1F5F9;
  238. color: #94A3B8;
  239. }
  240. .status-completed {
  241. background: #DCFCE7;
  242. color: #16A34A;
  243. }
  244. .status-claimed {
  245. background: #FEF3C7;
  246. color: #D97706;
  247. }
  248. .claim-btn {
  249. background: #F97316;
  250. color: #fff;
  251. font-size: 24rpx;
  252. border-radius: 30rpx;
  253. height: 56rpx;
  254. line-height: 56rpx;
  255. padding: 0 28rpx;
  256. font-weight: 500;
  257. }
  258. .claim-btn::after {
  259. border: none;
  260. }
  261. .claim-btn:active {
  262. opacity: 0.8;
  263. }
  264. .claim-btn[disabled] {
  265. opacity: 0.5;
  266. }
  267. .loading-state {
  268. text-align: center;
  269. padding: 60rpx;
  270. color: #94A3B8;
  271. font-size: 28rpx;
  272. }
  273. </style>