HealthCheckinCard.vue 5.4 KB

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