tui-countdown.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. <template>
  2. <view class="tui-countdown-box">
  3. <view class="tui-countdown-item" :style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(d, width) + 'rpx', height: height + 'rpx' }" v-if="days">
  4. <view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']" :style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
  5. {{ d }}
  6. </view>
  7. </view>
  8. <view
  9. class="tui-countdown-colon"
  10. :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
  11. :style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
  12. v-if="days"
  13. >
  14. {{ isColon ? ':' : '天' }}
  15. </view>
  16. <view class="tui-countdown-item" :style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(h, width) + 'rpx', height: height + 'rpx' }" v-if="hours">
  17. <view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']" :style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
  18. {{ h }}
  19. </view>
  20. </view>
  21. <view
  22. class="tui-countdown-colon"
  23. :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
  24. :style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
  25. v-if="hours"
  26. >
  27. {{ isColon ? ':' : '时' }}
  28. </view>
  29. <view
  30. class="tui-countdown-item"
  31. :style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(i, width) + 'rpx', height: height + 'rpx' }"
  32. v-if="minutes"
  33. >
  34. <view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']" :style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
  35. {{ i }}
  36. </view>
  37. </view>
  38. <view
  39. class="tui-countdown-colon"
  40. :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
  41. :style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
  42. v-if="minutes"
  43. >
  44. {{ isColon ? ':' : '分' }}
  45. </view>
  46. <view
  47. class="tui-countdown-item"
  48. :style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(s, width) + 'rpx', height: height + 'rpx' }"
  49. v-if="seconds"
  50. >
  51. <view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']" :style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
  52. {{ s }}
  53. </view>
  54. </view>
  55. <view
  56. class="tui-countdown-colon"
  57. :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
  58. :style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
  59. v-if="seconds && !isColon"
  60. >
  61. {{ unitEn ? 's' : '秒' }}
  62. </view>
  63. <view class="tui-countdown-colon" :style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }" v-if="seconds && isMs && isColon">.</view>
  64. <view
  65. class="tui-countdown__ms"
  66. :style="{
  67. background: backgroundColor,
  68. borderColor: borderColor,
  69. fontSize: msSize + 'rpx',
  70. color: msColor,
  71. height: height + 'rpx',
  72. width: msWidth > 0 ? msWidth + 'rpx' : 'auto'
  73. }"
  74. v-if="seconds && isMs"
  75. >
  76. <view :class="{ 'tui-ms__list': ani }">
  77. <view class="tui-ms__item" :style="{ height: height + 'rpx' }" v-for="(item, index) in ms" :key="index">
  78. <view :class="[scale ? 'tui-countdown-scale' : '']">{{item}}</view>
  79. </view>
  80. </view>
  81. </view>
  82. </view>
  83. </template>
  84. <script>
  85. export default {
  86. name: 'tuiCountdown',
  87. emits: ['end','time'],
  88. props: {
  89. //数字框宽度
  90. width: {
  91. type: Number,
  92. default: 32
  93. },
  94. //数字框高度
  95. height: {
  96. type: Number,
  97. default: 32
  98. },
  99. //数字框border颜色
  100. borderColor: {
  101. type: String,
  102. default: '#333'
  103. },
  104. //数字框背景颜色
  105. backgroundColor: {
  106. type: String,
  107. default: '#fff'
  108. },
  109. //数字框字体大小
  110. size: {
  111. type: Number,
  112. default: 24
  113. },
  114. //数字框字体颜色
  115. color: {
  116. type: String,
  117. default: '#333'
  118. },
  119. //是否缩放 0.9
  120. scale: {
  121. type: Boolean,
  122. default: false
  123. },
  124. //冒号大小
  125. colonSize: {
  126. type: Number,
  127. default: 28
  128. },
  129. //冒号颜色
  130. colonColor: {
  131. type: String,
  132. default: '#333'
  133. },
  134. //剩余时间 (单位:秒)
  135. time: {
  136. type: Number,
  137. default: 0
  138. },
  139. //是否包含天
  140. days: {
  141. type: Boolean,
  142. default: false
  143. },
  144. //是否包含小时
  145. hours: {
  146. type: Boolean,
  147. default: true
  148. },
  149. //是否包含分钟
  150. minutes: {
  151. type: Boolean,
  152. default: true
  153. },
  154. //是否包含秒
  155. seconds: {
  156. type: Boolean,
  157. default: true
  158. },
  159. //单位用英文缩写表示 仅seconds秒数有效
  160. unitEn: {
  161. type: Boolean,
  162. default: false
  163. },
  164. //是否展示为冒号,false为文字
  165. isColon: {
  166. type: Boolean,
  167. default: true
  168. },
  169. //是否返回剩余时间
  170. returnTime: {
  171. type: Boolean,
  172. default: false
  173. },
  174. //是否显示毫秒
  175. isMs: {
  176. type: Boolean,
  177. default: false
  178. },
  179. msWidth: {
  180. type: Number,
  181. default: 32
  182. },
  183. msSize: {
  184. type: Number,
  185. default: 24
  186. },
  187. msColor: {
  188. type: String,
  189. default: '#333'
  190. }
  191. },
  192. watch: {
  193. time(val) {
  194. this.clearTimer();
  195. this.doLoop();
  196. }
  197. },
  198. data() {
  199. return {
  200. countdown: null,
  201. d: '0',
  202. h: '00',
  203. i: '00',
  204. s: '00',
  205. //此处若从9到1,结束需要特殊处理
  206. ms: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
  207. ani: false
  208. };
  209. },
  210. created() {
  211. this.clearTimer();
  212. this.doLoop();
  213. },
  214. // #ifndef VUE3
  215. beforeDestroy() {
  216. this.clearTimer();
  217. },
  218. // #endif
  219. // #ifdef VUE3
  220. beforeUnmount(){
  221. this.clearTimer();
  222. },
  223. // #endif
  224. methods: {
  225. getWidth: function(num, width) {
  226. return num > 99 ? (width / 2) * num.toString().length : width;
  227. },
  228. clearTimer() {
  229. clearInterval(this.countdown);
  230. this.countdown = null;
  231. },
  232. endOfTime() {
  233. this.ani = false;
  234. this.clearTimer();
  235. this.$emit('end', {});
  236. },
  237. doLoop: function() {
  238. let seconds = this.time || 0;
  239. this.ani = true;
  240. this.countDown(seconds);
  241. this.countdown = setInterval(() => {
  242. seconds--;
  243. if (seconds < 0) {
  244. this.endOfTime();
  245. return;
  246. }
  247. this.countDown(seconds);
  248. if (this.returnTime) {
  249. this.$emit('time', { seconds: seconds });
  250. }
  251. }, 1000);
  252. },
  253. countDown(seconds) {
  254. let [day, hour, minute, second] = [0, 0, 0, 0];
  255. if (seconds > 0) {
  256. day = this.days ? Math.floor(seconds / (60 * 60 * 24)) : 0;
  257. hour = this.hours ? Math.floor(seconds / (60 * 60)) - day * 24 : 0;
  258. minute = this.minutes ? Math.floor(seconds / 60) - hour * 60 - day * 24 * 60 : 0;
  259. second = Math.floor(seconds) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
  260. } else {
  261. this.endOfTime();
  262. }
  263. hour = hour < 10 ? '0' + hour : hour;
  264. minute = minute < 10 ? '0' + minute : minute;
  265. second = second < 10 ? '0' + second : second;
  266. this.d = day;
  267. this.h = hour;
  268. this.i = minute;
  269. this.s = second;
  270. }
  271. }
  272. };
  273. </script>
  274. <style scoped>
  275. .tui-countdown-box {
  276. display: flex;
  277. align-items: center;
  278. }
  279. .tui-countdown-box {
  280. display: flex;
  281. align-items: center;
  282. }
  283. .tui-countdown-item {
  284. border: 1rpx solid;
  285. display: flex;
  286. align-items: center;
  287. justify-content: center;
  288. border-radius: 6rpx;
  289. white-space: nowrap;
  290. transform: translateZ(0);
  291. }
  292. .tui-countdown-time {
  293. margin: 0;
  294. padding: 0;
  295. }
  296. .tui-countdown-colon {
  297. display: flex;
  298. justify-content: center;
  299. padding: 0 5rpx;
  300. }
  301. .tui-colon-pad {
  302. padding: 0 !important;
  303. }
  304. .tui-countdown-scale {
  305. transform: scale(0.9);
  306. transform-origin: center center;
  307. }
  308. .tui-countdown__ms {
  309. border: 1rpx solid;
  310. overflow: hidden;
  311. border-radius: 6rpx;
  312. }
  313. /*ms使用css3代替js频繁更新操作,性能优化*/
  314. .tui-ms__list {
  315. animation: loop 1s steps(10) infinite;
  316. }
  317. @keyframes loop {
  318. from {
  319. transform: translateY(0);
  320. }
  321. to {
  322. transform: translateY(-100%);
  323. }
  324. }
  325. .tui-ms__item {
  326. display: flex;
  327. align-items: center;
  328. justify-content: center;
  329. }
  330. </style>