|
|
@@ -0,0 +1,171 @@
|
|
|
+<template>
|
|
|
+ <div class="clock-board-wrapper" :class="{ selected }"
|
|
|
+ :style="{ width: '100%', height: '100%', position: 'relative' }">
|
|
|
+ <div class="clock-board" :style="clockStyle">{{ formattedTime }}</div>
|
|
|
+ <template v-if="selected">
|
|
|
+ <div v-for="dir in ['tr', 'tl', 'br', 'bl']" :key="dir" class="resize-handle" :class="dir"
|
|
|
+ @mousedown.stop="onResizeMouseDown($event, dir)"></div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
|
+const emit = defineEmits(['resize']);
|
|
|
+interface Props {
|
|
|
+ color?: string;
|
|
|
+ fontSize?: string | number;
|
|
|
+ fontWeight?: string | number;
|
|
|
+ align?: 'left' | 'center' | 'right';
|
|
|
+ width?: number;
|
|
|
+ height?: number;
|
|
|
+ selected?: boolean;
|
|
|
+ format?: '24h' | '12h' | 'date';
|
|
|
+}
|
|
|
+const props = defineProps<Props>();
|
|
|
+const selected = computed(() => !!props.selected);
|
|
|
+const clockStyle = computed(() => ({
|
|
|
+ color: props.color || '#222',
|
|
|
+ fontSize: typeof props.fontSize === 'number' ? props.fontSize + 'px' : props.fontSize || '24px',
|
|
|
+ fontWeight: props.fontWeight || 'normal',
|
|
|
+ textAlign: props.align || 'center',
|
|
|
+ width: '100%',
|
|
|
+ height: '100%',
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: props.align === 'left' ? 'flex-start' : props.align === 'right' ? 'flex-end' : 'center',
|
|
|
+ userSelect: 'none'
|
|
|
+}));
|
|
|
+
|
|
|
+const formats = {
|
|
|
+ '24h': '24小时制 (HH:mm:ss)',
|
|
|
+ '12h': '12小时制 (hh:mm:ss A)',
|
|
|
+ 'date': '日期+时间 (YYYY-MM-DD HH:mm:ss)'
|
|
|
+};
|
|
|
+import { watch } from 'vue';
|
|
|
+
|
|
|
+const format = ref<'24h' | '12h' | 'date'>(props.format || '24h');
|
|
|
+
|
|
|
+// 保持 format 与 props.format 同步
|
|
|
+watch(
|
|
|
+ () => props.format,
|
|
|
+ (val) => {
|
|
|
+ if (val && val !== format.value) {
|
|
|
+ format.value = val;
|
|
|
+ }
|
|
|
+ }
|
|
|
+);
|
|
|
+
|
|
|
+watch(format, (val) => { });
|
|
|
+
|
|
|
+const now = ref(new Date());
|
|
|
+
|
|
|
+const formattedTime = computed(() => {
|
|
|
+ const d = now.value;
|
|
|
+ let result;
|
|
|
+ if (format.value === '24h') {
|
|
|
+ result = d.toLocaleTimeString('zh-CN', { hour12: false });
|
|
|
+ } else if (format.value === '12h') {
|
|
|
+ result = d.toLocaleTimeString('zh-CN', { hour12: true });
|
|
|
+ } else {
|
|
|
+ // 日期+时间
|
|
|
+ const date = d.toLocaleDateString('zh-CN');
|
|
|
+ const time = d.toLocaleTimeString('zh-CN', { hour12: false });
|
|
|
+ result = `${date} ${time}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+});
|
|
|
+
|
|
|
+let timer: number | undefined;
|
|
|
+onMounted(() => {
|
|
|
+ timer = window.setInterval(() => {
|
|
|
+ now.value = new Date();
|
|
|
+ }, 1000);
|
|
|
+});
|
|
|
+onUnmounted(() => {
|
|
|
+ if (timer) clearInterval(timer);
|
|
|
+});
|
|
|
+
|
|
|
+function onResizeMouseDown(e: MouseEvent, dir: string) {
|
|
|
+ e.stopPropagation();
|
|
|
+ const startX = e.clientX,
|
|
|
+ startY = e.clientY;
|
|
|
+ const startWidth = Number(props.width) || 200;
|
|
|
+ const startHeight = Number(props.height) || 40;
|
|
|
+ function onMove(ev: MouseEvent) {
|
|
|
+ let newWidth = startWidth,
|
|
|
+ newHeight = startHeight;
|
|
|
+ if (dir.includes('r')) newWidth += ev.clientX - startX;
|
|
|
+ if (dir.includes('l')) newWidth -= ev.clientX - startX;
|
|
|
+ if (dir.includes('b')) newHeight += ev.clientY - startY;
|
|
|
+ if (dir.includes('t')) newHeight -= ev.clientY - startY;
|
|
|
+ emit('resize', { width: Math.max(20, newWidth), height: Math.max(20, newHeight) });
|
|
|
+ }
|
|
|
+ function onUp() {
|
|
|
+ document.removeEventListener('mousemove', onMove);
|
|
|
+ document.removeEventListener('mouseup', onUp);
|
|
|
+ }
|
|
|
+ document.addEventListener('mousemove', onMove);
|
|
|
+ document.addEventListener('mouseup', onUp);
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.clock-board-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.clock-board {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.resize-handle {
|
|
|
+ position: absolute;
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #aaa;
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
+.resize-handle.tr {
|
|
|
+ top: -5px;
|
|
|
+ right: -5px;
|
|
|
+ cursor: ne-resize;
|
|
|
+}
|
|
|
+
|
|
|
+.resize-handle.tl {
|
|
|
+ top: -5px;
|
|
|
+ left: -5px;
|
|
|
+ cursor: nw-resize;
|
|
|
+}
|
|
|
+
|
|
|
+.resize-handle.br {
|
|
|
+ bottom: -5px;
|
|
|
+ right: -5px;
|
|
|
+ cursor: se-resize;
|
|
|
+}
|
|
|
+
|
|
|
+.resize-handle.bl {
|
|
|
+ bottom: -5px;
|
|
|
+ left: -5px;
|
|
|
+ cursor: sw-resize;
|
|
|
+}
|
|
|
+
|
|
|
+.clock-format-selector {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 8px;
|
|
|
+ right: 8px;
|
|
|
+ z-index: 20;
|
|
|
+ background: rgba(255, 255, 255, 0.7);
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 2px 6px;
|
|
|
+}
|
|
|
+</style>
|