update at 2026-03-17 10:37:27
This commit is contained in:
164
calendar/src/components/SimpleAnalogClock.vue
Normal file
164
calendar/src/components/SimpleAnalogClock.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { buildClockState } from '@/lib/clock';
|
||||
import type { DashboardMode } from '@/lib/dashboard-mode';
|
||||
|
||||
const SIMPLE_CLOCK_SIZE = 480;
|
||||
// 5 分钟刻度的外沿需要和普通刻度落在同一圈上,
|
||||
// 所以这里按统一 outer inset 重新计算大刻度中心距离。
|
||||
const SIMPLE_HOUR_TICK_DISTANCE = 194.59034156799316;
|
||||
const SIMPLE_MINUTE_TICK_DISTANCE = 207.39662265777588;
|
||||
const SIMPLE_HOUR_HAND_FRONT = 148.8;
|
||||
const SIMPLE_HOUR_HAND_BACK = 67.2;
|
||||
// 分针前端要求和分钟刻度外端严格对齐。
|
||||
const SIMPLE_MINUTE_HAND_FRONT = 218.26450538635254;
|
||||
const SIMPLE_MINUTE_HAND_BACK = 72;
|
||||
const SIMPLE_MINUTE_HAND_THICKNESS = 9.6;
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
date: Date;
|
||||
mode: DashboardMode;
|
||||
size?: number;
|
||||
}>(),
|
||||
{
|
||||
size: SIMPLE_CLOCK_SIZE,
|
||||
},
|
||||
);
|
||||
|
||||
const clockState = computed(() => buildClockState(props.date));
|
||||
const scale = computed(() => props.size / SIMPLE_CLOCK_SIZE);
|
||||
|
||||
// simple 主题的表盘不是图片,而是按 Figma 参数直接绘制。
|
||||
const hourTicks = Array.from({ length: 12 }, (_, index) => index * 30);
|
||||
const minuteTicks = Array.from({ length: 60 }, (_, index) => index).filter((index) => index % 5 !== 0);
|
||||
|
||||
const stageStyle = computed(() => ({
|
||||
width: `${props.size}px`,
|
||||
height: `${props.size}px`,
|
||||
}));
|
||||
|
||||
function buildHourTickStyle(angle: number) {
|
||||
return {
|
||||
width: `${14.4 * scale.value}px`,
|
||||
height: `${47.34832763671875 * scale.value}px`,
|
||||
transform: `translate(-50%, -50%) rotate(${angle}deg) translateY(-${SIMPLE_HOUR_TICK_DISTANCE * scale.value}px)`,
|
||||
};
|
||||
}
|
||||
|
||||
function buildMinuteTickStyle(index: number) {
|
||||
return {
|
||||
width: `${6.520815372467041 * scale.value}px`,
|
||||
height: `${21.73549461364746 * scale.value}px`,
|
||||
transform: `translate(-50%, -50%) rotate(${index * 6}deg) translateY(-${SIMPLE_MINUTE_TICK_DISTANCE * scale.value}px)`,
|
||||
};
|
||||
}
|
||||
|
||||
const hourHandStyle = computed(() => ({
|
||||
width: `${14.4 * scale.value}px`,
|
||||
height: `${(SIMPLE_HOUR_HAND_FRONT + SIMPLE_HOUR_HAND_BACK) * scale.value}px`,
|
||||
transformOrigin: `50% ${SIMPLE_HOUR_HAND_BACK * scale.value}px`,
|
||||
transform: `translate(-50%, -${SIMPLE_HOUR_HAND_BACK * scale.value}px) rotate(${clockState.value.hourAngle}deg)`,
|
||||
}));
|
||||
|
||||
const minuteHandStyle = computed(() => ({
|
||||
width: `${SIMPLE_MINUTE_HAND_THICKNESS * scale.value}px`,
|
||||
height: `${(SIMPLE_MINUTE_HAND_FRONT + SIMPLE_MINUTE_HAND_BACK) * scale.value}px`,
|
||||
transformOrigin: `50% ${SIMPLE_MINUTE_HAND_BACK * scale.value}px`,
|
||||
transform: `translate(-50%, -${SIMPLE_MINUTE_HAND_BACK * scale.value}px) rotate(${clockState.value.minuteAngle}deg)`,
|
||||
}));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="simple-analog-clock" :style="stageStyle" data-clock-region="true">
|
||||
<template v-if="mode !== 'background'">
|
||||
<div class="simple-analog-clock__face-shadow" />
|
||||
<div class="simple-analog-clock__face" />
|
||||
|
||||
<div
|
||||
v-for="angle in hourTicks"
|
||||
:key="`hour-${angle}`"
|
||||
class="simple-analog-clock__tick simple-analog-clock__tick--hour"
|
||||
:style="buildHourTickStyle(angle)"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-for="index in minuteTicks"
|
||||
:key="`minute-${index}`"
|
||||
class="simple-analog-clock__tick simple-analog-clock__tick--minute"
|
||||
:style="buildMinuteTickStyle(index)"
|
||||
/>
|
||||
|
||||
<div class="simple-analog-clock__hand simple-analog-clock__hand--minute" :style="minuteHandStyle" />
|
||||
<div class="simple-analog-clock__hand simple-analog-clock__hand--hour" :style="hourHandStyle" />
|
||||
<div class="simple-analog-clock__center simple-analog-clock__center--bottom" />
|
||||
<div class="simple-analog-clock__center simple-analog-clock__center--top" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.simple-analog-clock {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.simple-analog-clock__face-shadow,
|
||||
.simple-analog-clock__face {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.simple-analog-clock__face-shadow {
|
||||
inset: -2.4px;
|
||||
box-shadow: 0 1.8px 5.4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.simple-analog-clock__face {
|
||||
inset: 0;
|
||||
border: 1.6px solid #1e1e1e;
|
||||
}
|
||||
|
||||
.simple-analog-clock__tick {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
background: #1e1e1e;
|
||||
}
|
||||
|
||||
.simple-analog-clock__hand {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
.simple-analog-clock__hand--minute {
|
||||
box-shadow: 0 3.6px 10.8px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.simple-analog-clock__hand--hour {
|
||||
box-shadow: 2.4px 2.4px 10.8px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.simple-analog-clock__center {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
.simple-analog-clock__center--bottom {
|
||||
width: 4.8px;
|
||||
height: 4.8px;
|
||||
}
|
||||
|
||||
.simple-analog-clock__center--top {
|
||||
width: 9.6px;
|
||||
height: 9.6px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user