CountDown.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. <!--
  2. 通用倒计时组件
  3. 用途:团购详情页活动倒计时、拼团剩余时间等
  4. 入参 endTime 为秒级 unix;结束后触发 ended 事件
  5. 剩余 ≥24h 时拆出「天」单位,小时限制在 0–23(与首页活动专区一致)
  6. -->
  7. <template>
  8. <view v-if="visible" class="count-down" :class="theme">
  9. <text v-if="label" class="cd-label">{{ label }}</text>
  10. <view class="cd-blocks">
  11. <!-- 剩余超过 24 小时时展示「天」单位,避免小时数无限增大 -->
  12. <template v-if="display.showDay">
  13. <text class="cd-unit">{{ display.d }}</text>
  14. <text class="cd-day">天</text>
  15. </template>
  16. <text class="cd-unit">{{ display.h }}</text>
  17. <text class="cd-colon">:</text>
  18. <text class="cd-unit">{{ display.m }}</text>
  19. <text class="cd-colon">:</text>
  20. <text class="cd-unit">{{ display.s }}</text>
  21. </view>
  22. </view>
  23. </template>
  24. <script>
  25. export default {
  26. name: 'CountDown',
  27. props: {
  28. /** 结束时间(秒级 unix 时间戳) */
  29. endTime: { type: [Number, String], default: 0 },
  30. /** 文案前缀,如「距结束」 */
  31. label: { type: String, default: '' },
  32. /** 主题:default | pink */
  33. theme: { type: String, default: 'default' }
  34. },
  35. data() {
  36. return {
  37. visible: false,
  38. // showDay:剩余 ≥24h 时展示天;d/h/m/s 为补零后的展示字符串
  39. display: { showDay: false, d: '00', h: '00', m: '00', s: '00' },
  40. _timer: null
  41. }
  42. },
  43. watch: {
  44. endTime: {
  45. immediate: true,
  46. handler() {
  47. this.restart()
  48. }
  49. }
  50. },
  51. beforeDestroy() {
  52. this.clearTimer()
  53. },
  54. methods: {
  55. clearTimer() {
  56. if (this._timer) {
  57. clearInterval(this._timer)
  58. this._timer = null
  59. }
  60. },
  61. restart() {
  62. this.clearTimer()
  63. this.tick()
  64. this._timer = setInterval(this.tick, 1000)
  65. },
  66. /**
  67. * 按 endTime 刷新展示;剩余 ≥24h 拆出天单位,小时为当天剩余 0–23
  68. */
  69. tick() {
  70. const end = Number(this.endTime) || 0
  71. const now = Math.floor(Date.now() / 1000)
  72. if (end <= 0 || now >= end) {
  73. this.visible = false
  74. this.display = { showDay: false, d: '00', h: '00', m: '00', s: '00' }
  75. this.clearTimer()
  76. this.$emit('ended')
  77. return
  78. }
  79. this.visible = true
  80. let diff = end - now
  81. if (diff < 0) diff = 0
  82. const d = Math.floor(diff / 86400)
  83. const h = Math.floor((diff % 86400) / 3600)
  84. const m = Math.floor((diff % 3600) / 60)
  85. const s = diff % 60
  86. const pad = (n) => String(n).padStart(2, '0')
  87. this.display = {
  88. showDay: d > 0,
  89. d: pad(d),
  90. h: pad(h),
  91. m: pad(m),
  92. s: pad(s)
  93. }
  94. }
  95. }
  96. }
  97. </script>
  98. <style lang="scss" scoped>
  99. .count-down {
  100. display: flex;
  101. flex-direction: row;
  102. align-items: center;
  103. }
  104. .cd-label {
  105. font-size: 22upx;
  106. color: #999;
  107. margin-right: 8upx;
  108. }
  109. .cd-blocks {
  110. display: flex;
  111. flex-direction: row;
  112. align-items: center;
  113. }
  114. .cd-unit {
  115. min-width: 40upx;
  116. height: 36upx;
  117. line-height: 36upx;
  118. text-align: center;
  119. font-size: 22upx;
  120. color: #fff;
  121. background: #ff4d6d;
  122. border-radius: 6upx;
  123. padding: 0 6upx;
  124. }
  125. .cd-colon {
  126. margin: 0 4upx;
  127. color: #ff4d6d;
  128. font-size: 22upx;
  129. font-weight: 600;
  130. }
  131. .cd-day {
  132. margin: 0 6upx 0 4upx;
  133. font-size: 22upx;
  134. color: #ff4d6d;
  135. font-weight: 600;
  136. }
  137. .pink {
  138. .cd-unit {
  139. background: #ff6b8a;
  140. }
  141. .cd-day,
  142. .cd-colon {
  143. color: #ff6b8a;
  144. }
  145. }
  146. </style>