tui-countdown.vue 7.8 KB

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