2024-10-01 01:22:25 +08:00

188 lines
5.4 KiB
Vue

<script lang="ts" setup>
import dayjs from 'dayjs';
import type { ThingType } from './type';
defineComponent({ name: 'GantView' });
interface Props {
value?: number; // 选中的日期 时间戳
yStep?: number;
minuteStep?: number;
weekDates?: { date: number; day: string; weekDay: string }[];
groupedEvents?: Map<number, ThingType[]>;
}
const props = withDefaults(defineProps<Props>(), {
yStep: 80,
minuteStep: 80 / 60,
value: dayjs().valueOf(),
weekDates: () => [],
groupedEvents: () => new Map()
});
const emit = defineEmits<Emits>();
interface Emits {
(e: 'clickThing', value: ThingType): void;
}
// Y 轴为 00:00 - 24:00 的时间轴 , 时间步进为 1H , 用dayjs 转换为 00:00 格式
const Y_AXIS = Array.from({ length: 24 }, (_, i) => {
return {
label: dayjs().startOf('day').add(i, 'hour').format('HH:00'),
top: i * props.yStep
};
});
/** ** 正确的格式一定为某一天的起始 *** */
const selectedDate = computed(() => {
return dayjs(props.value).startOf('day').valueOf();
});
watch(selectedDate, () => {
scrollToThing();
});
onMounted(() => {
nextTick(() => {
scrollToThing();
});
});
const scrollbarRef = ref<any | null>(null);
function scrollToThing() {
const _thing = props.groupedEvents.get(selectedDate.value);
const currentTime = dayjs().startOf('minute');
if (!_thing) {
scrollbarRef.value?.scrollTo({
top: currentTime.diff(currentTime.startOf('day'), 'minute') * props.minuteStep - props.yStep,
behavior: 'smooth'
});
return;
}
let closestEvent: ThingType | null = null;
// 查找与当前时刻最接近的事件
for (const event of _thing) {
const eventTime = dayjs(event.startTime);
if (
!closestEvent ||
Math.abs(eventTime.diff(currentTime)) < Math.abs(dayjs(closestEvent.startTime).diff(currentTime))
) {
closestEvent = event;
}
}
// 如果没有找到与当前时刻最接近的事件,则查找当前时刻未来的第一个事件
if (!closestEvent) {
closestEvent = _thing.find(event => dayjs(event.startTime).isAfter(currentTime)) || null;
}
// 如果还没有找到,则查找列表中与当前时刻最相近的事件
if (!closestEvent) {
closestEvent = _thing.reduce((prev, curr) => {
const prevDiff = Math.abs(dayjs(prev.startTime).diff(currentTime));
const currDiff = Math.abs(dayjs(curr.startTime).diff(currentTime));
return currDiff < prevDiff ? curr : prev;
});
}
// 如果还没有找到,则使用列表中的第一个事件
if (!closestEvent && _thing[0]) {
closestEvent = _thing[0];
}
// 滚动到找到的事件位置
if (closestEvent && closestEvent.toTop) {
scrollbarRef.value?.scrollTo({
top: closestEvent!.toTop - props.yStep,
behavior: 'smooth'
});
}
}
</script>
<template>
<div class="relative flex-1 border border-red border-solid">
<div ref="scrollbarRef" class="absolute left-0 top-0 w-full h-full">
<div class="flex flex-row">
<!-- time line -->
<div class="flex flex-col pl-2 pr-1">
<div
v-for="(item, inex) in Y_AXIS"
:key="inex"
:style="{
height: `${props.yStep}rpx`
}"
>
<span
class="relative block translate-y-[-50%] text-[#999999FF] line-height-none"
:class="{
'translate-y-0': inex === 0
}"
>
{{ item.label }}
</span>
</div>
</div>
<div class="relative flex flex-1 flex-col">
<!-- 网格 -->
<div
class="absolute left-0 top-0 z-1 w-full h-full"
:style="{
'background-image': `linear-gradient(to right, #f7f7f7 1rpx, transparent 1rpx, transparent calc(100% / 7.5)),linear-gradient(to bottom, #f7f7f7 1rpx, transparent 1rpx, transparent ${props.yStep}rpx)`,
'background-size': `calc(100%/7.5) ${props.yStep}rpx`
}"
/>
<!-- 事件组 分为 7day -->
<div class="z-2 w-[calc((100%/7.5)*7)] flex flex-1 flex-row">
<!--
:style="{
'background-color': item.date === selectedDate ? `rgb(var(--primary-100-color) / 0.2)` : 'transparent'
}"
-->
<div
v-for="(item, index) in props.weekDates"
:key="index"
class="relative flex flex-1 transition-background-color will-change-background-color"
>
<!-- 'background-color': addColorAlpha(itm.color, 0.06) -->
<div
v-for="itm in props.groupedEvents.get(item.date)"
:key="itm.id"
class="absolute w-full flex-shrink-0 cursor-pointer flex-self-stretch py-1"
:style="{
top: `${itm.toTop}rpx`,
height: `${itm.height}rpx`,
background: itm.color,
borderTop: `2rpx solid ${itm.color}`,
}"
@click="emit('clickThing', itm)"
>
<span
class="text-3"
:style="{
color: itm.color
}"
>
{{ itm.name }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped></style>