time-clock.vue 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. <template>
  2. <div class="time-counter">
  3. {{ formattedTime }}
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. name: 'TimeClock',
  9. // props 定义(与 Vue3 逻辑一致,保留非负整数校验)
  10. props: {
  11. initialSeconds: {
  12. type: Number,
  13. required: true,
  14. validator(value) {
  15. // 校验:必须是非负整数
  16. return Number.isInteger(value) && value >= 0
  17. }
  18. }
  19. },
  20. data() {
  21. return {
  22. // 基础毫秒(作为起点)
  23. baseMillis: Math.floor(this.initialSeconds),
  24. // 启动时间戳(用于计算增量)
  25. startTs: Date.now(),
  26. // 全局tick推送的当前时间(订阅更新)
  27. nowTs: Date.now()
  28. }
  29. },
  30. computed: {
  31. // 格式化时间:00:00:00 格式(不显示毫秒)
  32. formattedTime() {
  33. const elapsed = this.baseMillis + (this.nowTs - this.startTs)
  34. const totalMs = Math.max(0, Math.floor(elapsed))
  35. const hours = String(Math.floor(totalMs / 3600000)).padStart(2, '0')
  36. const minutes = String(Math.floor((totalMs % 3600000) / 60000)).padStart(2, '0')
  37. const seconds = String(Math.floor((totalMs % 60000) / 1000)).padStart(2, '0')
  38. return `${hours}:${minutes}:${seconds}`
  39. }
  40. },
  41. watch: {
  42. // 监听初始秒数变化(对应 Vue3 的 watch 函数)
  43. initialSeconds: {
  44. handler(newVal) {
  45. // 同步为毫秒值并重置起点
  46. const safeVal = Number(newVal)
  47. this.baseMillis = Number.isFinite(safeVal) ? Math.floor(safeVal) : 0
  48. this.startTs = Date.now()
  49. },
  50. immediate: true
  51. }
  52. },
  53. methods: {
  54. // 订阅全局tick(单例定时器,所有实例共享,降低性能开销)
  55. subscribeTick() {
  56. const scope = globalThis.__timeClockScope || (globalThis.__timeClockScope = {
  57. subs: new Set(),
  58. timer: null
  59. })
  60. const cb = () => { this.nowTs = Date.now() }
  61. scope.subs.add(cb)
  62. if (!scope.timer) {
  63. // 每秒触发一次,保持跳秒显示与两位毫秒展示
  64. scope.timer = setInterval(() => {
  65. const now = Date.now()
  66. scope.subs.forEach(fn => fn(now))
  67. }, 1000)
  68. }
  69. // 返回取消订阅函数
  70. return () => {
  71. scope.subs.delete(cb)
  72. if (scope.subs.size === 0 && scope.timer) {
  73. clearInterval(scope.timer)
  74. scope.timer = null
  75. }
  76. }
  77. }
  78. },
  79. // 初始化时启动计时(对应 Vue3 的 setup 初始化逻辑)
  80. mounted() {
  81. this._unsub = this.subscribeTick()
  82. },
  83. // 组件卸载时清理定时器(避免内存泄漏,与 Vue3 一致)
  84. beforeDestroy() {
  85. if (this._unsub) {
  86. this._unsub()
  87. this._unsub = null
  88. }
  89. },
  90. beforeUnmount() {
  91. if (this._unsub) {
  92. this._unsub()
  93. this._unsub = null
  94. }
  95. }
  96. }
  97. </script>
  98. <style scoped>
  99. .time-counter {
  100. font-size: 14px;
  101. font-weight: 600;
  102. color: #ff0000;
  103. letter-spacing: 2px;
  104. }
  105. </style>