You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

203 lines
7.3 KiB
JavaScript

import React, { useMemo, useState } from 'react';
import { Button, Segmented, Select } from 'antd';
import { PlusOutlined, GlobalOutlined, BranchesOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import styles from './ActivityCalendar.less';
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const calendarEvents = [
{
id: 'earth-day',
title: '世界环保日',
start: dayjs('2020-12-14'),
end: dayjs('2020-12-15'),
tone: 'sand',
icon: <GlobalOutlined />,
},
{
id: 'tree-day',
title: '全国植树日',
start: dayjs('2020-12-24'),
end: dayjs('2020-12-31'),
tone: 'mint',
icon: <BranchesOutlined />,
},
];
const buildGrid = (referenceDate, viewMode) => {
if (viewMode === 'week') {
const startOfWeek = referenceDate.startOf('week');
const days = Array.from({ length: 7 }, (_, index) => startOfWeek.add(index, 'day'));
return { days, gridStart: startOfWeek, weeks: 1 };
}
const startOfMonth = referenceDate.startOf('month');
const gridStart = startOfMonth.startOf('week');
const days = Array.from({ length: 42 }, (_, index) => gridStart.add(index, 'day'));
return { days, gridStart, weeks: 6 };
};
const buildEventSegments = (events, gridStart, weeks) => {
const gridEnd = gridStart.add(weeks * 7 - 1, 'day').endOf('day');
const segments = [];
events.forEach((event) => {
const start = event.start;
const end = event.end;
if (end.isBefore(gridStart) || start.isAfter(gridEnd)) {
return;
}
for (let weekIndex = 0; weekIndex < weeks; weekIndex += 1) {
const weekStart = gridStart.add(weekIndex * 7, 'day');
const weekEnd = weekStart.add(6, 'day').endOf('day');
const segmentStart = start.isAfter(weekStart) ? start : weekStart;
const segmentEnd = end.isBefore(weekEnd) ? end : weekEnd;
if (segmentEnd.isBefore(weekStart) || segmentStart.isAfter(weekEnd)) {
continue;
}
const columnStart = segmentStart.diff(weekStart, 'day') + 1;
const columnEnd = segmentEnd.diff(weekStart, 'day') + 2;
segments.push({
id: `${event.id}-${weekIndex}`,
title: event.title,
tone: event.tone,
icon: event.icon,
rowStart: weekIndex + 1,
rowEnd: weekIndex + 2,
columnStart,
columnEnd,
});
}
});
return segments;
};
const ActivityCalendar = () => {
const [currentDate, setCurrentDate] = useState(dayjs('2020-12-01'));
const [viewMode, setViewMode] = useState('month');
const { days, gridStart, weeks } = useMemo(() => buildGrid(currentDate, viewMode), [currentDate, viewMode]);
const segments = useMemo(() => buildEventSegments(calendarEvents, gridStart, weeks), [gridStart, weeks]);
const yearOptions = useMemo(() => {
const baseYear = dayjs().year();
const startYear = Math.min(2020, baseYear - 4);
return Array.from({ length: 10 }, (_, index) => startYear + index);
}, []);
const handleYearChange = (year) => {
setCurrentDate((prev) => prev.year(year));
};
const handleMonthChange = (month) => {
setCurrentDate((prev) => prev.month(month - 1));
};
const handleViewChange = (mode) => {
setViewMode(mode);
};
const handleBackToToday = () => {
const today = dayjs();
setCurrentDate(today);
setViewMode('month');
};
return (
<div className={styles.container}>
<div className={styles.toolbar}>
<Button type="primary" icon={<PlusOutlined />} className={styles.createButton}>
新建活动
</Button>
<div className={styles.toolbarRight}>
<Select
size="middle"
value={currentDate.year()}
className={styles.select}
popupClassName={styles.dropdown}
onChange={handleYearChange}
options={yearOptions.map((year) => ({ value: year, label: `${year}` }))}
/>
<Select
size="middle"
value={currentDate.month() + 1}
className={styles.select}
popupClassName={styles.dropdown}
onChange={handleMonthChange}
options={Array.from({ length: 12 }, (_, index) => ({
value: index + 1,
label: `${index + 1}`,
}))}
/>
<Segmented
value={viewMode}
onChange={handleViewChange}
options={[
{ label: '月', value: 'month' },
{ label: '周', value: 'week' },
]}
className={styles.segmented}
/>
<Button className={styles.dayButton} onClick={handleBackToToday}>
</Button>
</div>
</div>
<div className={styles.calendarWrapper}>
<div className={styles.weekHeader}>
{weekDays.map((day) => (
<div key={day} className={styles.weekCell}>
{day}
</div>
))}
</div>
<div className={styles.calendarGrid}>
{days.map((day) => {
const isCurrentMonth = day.month() === currentDate.month();
return (
<div
key={day.format('YYYY-MM-DD')}
className={`${styles.dayCell} ${isCurrentMonth ? '' : styles.dimmed}`}
>
<span className={styles.dayNumber}>{day.date()}</span>
</div>
);
})}
{segments.map((segment) => (
<div
key={segment.id}
className={`${styles.eventBlock} ${styles[segment.tone]}`}
style={{
gridColumnStart: segment.columnStart,
gridColumnEnd: segment.columnEnd,
gridRowStart: segment.rowStart,
gridRowEnd: segment.rowEnd,
}}
>
<div className={styles.eventInfo}>
<span className={styles.eventDot} />
<span className={styles.eventTitle}>{segment.title}</span>
</div>
<div className={styles.eventIcon}>{segment.icon}</div>
</div>
))}
</div>
</div>
</div>
);
};
export default ActivityCalendar;