ClockBoard.vue 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <template>
  2. <div class="clock-board-wrapper" :class="{ selected }"
  3. :style="{ width: '100%', height: '100%', position: 'relative' }">
  4. <div class="clock-board" :style="clockStyle">{{ formattedTime }}</div>
  5. <template v-if="selected">
  6. <div v-for="dir in ['tr', 'tl', 'br', 'bl']" :key="dir" class="resize-handle" :class="dir"
  7. @mousedown.stop="onResizeMouseDown($event, dir)"></div>
  8. </template>
  9. </div>
  10. </template>
  11. <script setup lang="ts">
  12. import { ref, computed, onMounted, onUnmounted } from 'vue';
  13. const emit = defineEmits(['resize']);
  14. interface Props {
  15. color?: string;
  16. fontSize?: string | number;
  17. fontWeight?: string | number;
  18. align?: 'left' | 'center' | 'right';
  19. width?: number;
  20. height?: number;
  21. selected?: boolean;
  22. format?: '24h' | '12h' | 'date';
  23. }
  24. const props = defineProps<Props>();
  25. const selected = computed(() => !!props.selected);
  26. const clockStyle = computed(() => ({
  27. color: props.color || '#222',
  28. fontSize: typeof props.fontSize === 'number' ? props.fontSize + 'px' : props.fontSize || '24px',
  29. fontWeight: props.fontWeight || 'normal',
  30. textAlign: props.align || 'center',
  31. width: '100%',
  32. height: '100%',
  33. display: 'flex',
  34. alignItems: 'center',
  35. justifyContent: props.align === 'left' ? 'flex-start' : props.align === 'right' ? 'flex-end' : 'center',
  36. userSelect: 'none'
  37. }));
  38. const formats = {
  39. '24h': '24小时制 (HH:mm:ss)',
  40. '12h': '12小时制 (hh:mm:ss A)',
  41. 'date': '日期+时间 (YYYY-MM-DD HH:mm:ss)'
  42. };
  43. import { watch } from 'vue';
  44. const format = ref<'24h' | '12h' | 'date'>(props.format || '24h');
  45. // 保持 format 与 props.format 同步
  46. watch(
  47. () => props.format,
  48. (val) => {
  49. if (val && val !== format.value) {
  50. format.value = val;
  51. }
  52. }
  53. );
  54. watch(format, (val) => { });
  55. const now = ref(new Date());
  56. const formattedTime = computed(() => {
  57. const d = now.value;
  58. let result;
  59. if (format.value === '24h') {
  60. result = d.toLocaleTimeString('zh-CN', { hour12: false });
  61. } else if (format.value === '12h') {
  62. result = d.toLocaleTimeString('zh-CN', { hour12: true });
  63. } else {
  64. // 日期+时间
  65. const date = d.toLocaleDateString('zh-CN');
  66. const time = d.toLocaleTimeString('zh-CN', { hour12: false });
  67. result = `${date} ${time}`;
  68. }
  69. return result;
  70. });
  71. let timer: number | undefined;
  72. onMounted(() => {
  73. timer = window.setInterval(() => {
  74. now.value = new Date();
  75. }, 1000);
  76. });
  77. onUnmounted(() => {
  78. if (timer) clearInterval(timer);
  79. });
  80. function onResizeMouseDown(e: MouseEvent, dir: string) {
  81. e.stopPropagation();
  82. const startX = e.clientX,
  83. startY = e.clientY;
  84. const startWidth = Number(props.width) || 200;
  85. const startHeight = Number(props.height) || 40;
  86. function onMove(ev: MouseEvent) {
  87. let newWidth = startWidth,
  88. newHeight = startHeight;
  89. if (dir.includes('r')) newWidth += ev.clientX - startX;
  90. if (dir.includes('l')) newWidth -= ev.clientX - startX;
  91. if (dir.includes('b')) newHeight += ev.clientY - startY;
  92. if (dir.includes('t')) newHeight -= ev.clientY - startY;
  93. emit('resize', { width: Math.max(20, newWidth), height: Math.max(20, newHeight) });
  94. }
  95. function onUp() {
  96. document.removeEventListener('mousemove', onMove);
  97. document.removeEventListener('mouseup', onUp);
  98. }
  99. document.addEventListener('mousemove', onMove);
  100. document.addEventListener('mouseup', onUp);
  101. }
  102. </script>
  103. <style scoped>
  104. .clock-board-wrapper {
  105. width: 100%;
  106. height: 100%;
  107. position: relative;
  108. }
  109. .clock-board {
  110. width: 100%;
  111. height: 100%;
  112. display: flex;
  113. align-items: center;
  114. justify-content: center;
  115. }
  116. .resize-handle {
  117. position: absolute;
  118. width: 10px;
  119. height: 10px;
  120. background: #fff;
  121. border: 1px solid #aaa;
  122. z-index: 10;
  123. }
  124. .resize-handle.tr {
  125. top: -5px;
  126. right: -5px;
  127. cursor: ne-resize;
  128. }
  129. .resize-handle.tl {
  130. top: -5px;
  131. left: -5px;
  132. cursor: nw-resize;
  133. }
  134. .resize-handle.br {
  135. bottom: -5px;
  136. right: -5px;
  137. cursor: se-resize;
  138. }
  139. .resize-handle.bl {
  140. bottom: -5px;
  141. left: -5px;
  142. cursor: sw-resize;
  143. }
  144. .clock-format-selector {
  145. position: absolute;
  146. bottom: 8px;
  147. right: 8px;
  148. z-index: 20;
  149. background: rgba(255, 255, 255, 0.7);
  150. border-radius: 4px;
  151. padding: 2px 6px;
  152. }
  153. </style>