HealthCheckinCard.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <template>
  2. <view class="health-checkin-card">
  3. <view class="hcc-header">
  4. <text class="hcc-title">🏃 健康打卡</text>
  5. <text class="hcc-more" v-if="streak > 0">连续 {{ streak }} 天</text>
  6. </view>
  7. <view class="hcc-body">
  8. <template v-if="todayCheckedIn">
  9. <view class="hcc-done">
  10. <text class="hcc-done-icon">✓</text>
  11. <text class="hcc-done-text">今日已打卡</text>
  12. </view>
  13. <text class="hcc-energy">+{{ earnedEnergy }} 身能量</text>
  14. </template>
  15. <template v-else>
  16. <view class="hcc-undo" @click="doCheckin">
  17. <text class="hcc-btn">立即打卡</text>
  18. <text class="hcc-btn-tip">完成健康打卡获得身能量</text>
  19. </view>
  20. </template>
  21. </view>
  22. </view>
  23. </template>
  24. <script>
  25. import api from '@/utils/api.js'
  26. export default {
  27. props: {
  28. childId: { type: Number, default: null }
  29. },
  30. data: function() {
  31. return {
  32. checkins: [],
  33. loading: false
  34. }
  35. },
  36. computed: {
  37. todayCheckedIn: function() {
  38. if (!this.checkins || this.checkins.length === 0) return false
  39. var today = this.getDateString(new Date())
  40. return this.checkins.some(function(c) {
  41. if (!c.checkinDate) return false
  42. var d = c.checkinDate
  43. if (typeof d === 'string') {
  44. return d.slice(0, 10) === today
  45. }
  46. return this.getDateString(new Date(d)).slice(0, 10) === today
  47. }.bind(this))
  48. },
  49. streak: function() {
  50. if (!this.checkins || this.checkins.length === 0) return 0
  51. var sorted = this.checkins.slice().sort(function(a, b) {
  52. if (!a.checkinDate || !b.checkinDate) return 0
  53. var da = new Date(a.checkinDate)
  54. var db = new Date(b.checkinDate)
  55. return db - da
  56. })
  57. var streak = 0
  58. var today = new Date()
  59. today.setHours(0, 0, 0, 0)
  60. var checkDate = new Date(today)
  61. for (var i = 0; i < sorted.length; i++) {
  62. var cDate = new Date(sorted[i].checkinDate)
  63. cDate.setHours(0, 0, 0, 0)
  64. var diff = Math.round((checkDate - cDate) / (1000 * 60 * 60 * 24))
  65. if (diff === 0 || diff === 1) {
  66. if (diff === 1) streak++
  67. if (diff === 0 && streak === 0) streak = 1
  68. checkDate = cDate
  69. checkDate.setDate(checkDate.getDate() - 1)
  70. } else {
  71. break
  72. }
  73. }
  74. return streak
  75. },
  76. earnedEnergy: function() {
  77. if (!this.checkins || this.checkins.length === 0) return 5
  78. var last = this.checkins[0]
  79. return last && last.earnedEnergy ? last.earnedEnergy : 5
  80. }
  81. },
  82. created: function() {
  83. this.loadCheckins()
  84. },
  85. methods: {
  86. getDateString: function(date) {
  87. var y = date.getFullYear()
  88. var m = ('' + (date.getMonth() + 1)).padStart(2, '0')
  89. var d = ('' + date.getDate()).padStart(2, '0')
  90. return y + '-' + m + '-' + d
  91. },
  92. loadCheckins: function() {
  93. var self = this
  94. if (!this.childId) return
  95. var now = new Date()
  96. var yearMonth = now.getFullYear() + '-' + ('' + (now.getMonth() + 1)).padStart(2, '0')
  97. api.healthCheckinList({
  98. childId: self.childId,
  99. yearMonth: yearMonth
  100. }).then(function(res) {
  101. if (res.data) {
  102. self.checkins = res.data
  103. }
  104. }).catch(function() {
  105. self.checkins = []
  106. })
  107. },
  108. doCheckin: function() {
  109. var self = this
  110. if (!this.childId) {
  111. uni.showToast({ title: '请先选择孩子', icon: 'none' })
  112. return
  113. }
  114. if (this.loading) return
  115. this.loading = true
  116. api.healthCheckinCreate({
  117. childId: self.childId,
  118. checkinDate: self.getDateString(new Date()),
  119. behaviors: [],
  120. mood: 'happy'
  121. }).then(function(res) {
  122. self.loading = false
  123. uni.showToast({ title: '打卡成功!+' + (res.data && res.data.earnedEnergy || 5) + '身能量', icon: 'none' })
  124. self.checkins.unshift(res.data)
  125. }).catch(function() {
  126. self.loading = false
  127. })
  128. }
  129. }
  130. }
  131. </script>
  132. <style scoped>
  133. .health-checkin-card {
  134. background: #fff;
  135. border-radius: 20rpx;
  136. padding: 24rpx;
  137. margin: 20rpx 20rpx 0 20rpx;
  138. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  139. }
  140. .hcc-header {
  141. display: flex;
  142. justify-content: space-between;
  143. align-items: center;
  144. margin-bottom: 16rpx;
  145. }
  146. .hcc-title { font-size: 28rpx; font-weight: 700; color: #333; }
  147. .hcc-more { font-size: 22rpx; color: #F97316; }
  148. .hcc-body { display: flex; align-items: center; justify-content: center; }
  149. .hcc-done {
  150. display: flex;
  151. align-items: center;
  152. gap: 12rpx;
  153. padding: 20rpx 0;
  154. }
  155. .hcc-done-icon {
  156. width: 56rpx;
  157. height: 56rpx;
  158. border-radius: 28rpx;
  159. background: linear-gradient(135deg, #10B981, #34D399);
  160. color: #fff;
  161. font-size: 28rpx;
  162. display: flex;
  163. align-items: center;
  164. justify-content: center;
  165. }
  166. .hcc-done-text { font-size: 30rpx; color: #10B981; font-weight: 600; }
  167. .hcc-energy { font-size: 22rpx; color: #999; margin-left: 16rpx; }
  168. .hcc-undo {
  169. display: flex;
  170. flex-direction: column;
  171. align-items: center;
  172. padding: 12rpx 0;
  173. width: 100%;
  174. }
  175. .hcc-btn {
  176. background: linear-gradient(135deg, #FF8C42, #FFB074);
  177. color: #fff;
  178. font-size: 28rpx;
  179. font-weight: 600;
  180. padding: 16rpx 60rpx;
  181. border-radius: 40rpx;
  182. }
  183. .hcc-btn:active { opacity: 0.8; }
  184. .hcc-btn-tip { font-size: 22rpx; color: #999; margin-top: 8rpx; }
  185. </style>