2023-08-17 17:12:51 +08:00
|
|
|
|
import dayjs, { type Dayjs } from 'dayjs';
|
|
|
|
|
import React from 'react';
|
2023-08-25 11:42:35 +08:00
|
|
|
|
import { Lunar, HolidayUtil } from 'lunar-typescript';
|
2023-08-17 17:12:51 +08:00
|
|
|
|
import { createStyles } from 'antd-style';
|
|
|
|
|
import classNames from 'classnames';
|
|
|
|
|
import { Calendar, Col, Radio, Row, Select } from 'antd';
|
|
|
|
|
import type { CalendarProps } from 'antd';
|
|
|
|
|
|
|
|
|
|
const useStyle = createStyles(({ token, css, cx }) => {
|
|
|
|
|
const lunar = css`
|
|
|
|
|
color: ${token.colorTextTertiary};
|
|
|
|
|
font-size: ${token.fontSizeSM}px;
|
|
|
|
|
`;
|
|
|
|
|
return {
|
|
|
|
|
wrapper: css`
|
2023-08-25 11:42:35 +08:00
|
|
|
|
width: 450px;
|
2023-08-18 10:41:45 +08:00
|
|
|
|
border: 1px solid ${token.colorBorderSecondary};
|
|
|
|
|
border-radius: ${token.borderRadiusOuter};
|
|
|
|
|
padding: 5px;
|
|
|
|
|
`,
|
2023-08-17 17:12:51 +08:00
|
|
|
|
dateCell: css`
|
2023-08-18 10:41:45 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
&:before {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
top: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
margin: auto;
|
|
|
|
|
max-width: 40px;
|
|
|
|
|
max-height: 40px;
|
|
|
|
|
background: transparent;
|
|
|
|
|
transition: background 300ms;
|
|
|
|
|
border-radius: ${token.borderRadiusOuter}px;
|
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
&:hover:before {
|
|
|
|
|
background: rgba(0, 0, 0, 0.04);
|
|
|
|
|
}
|
|
|
|
|
`,
|
2023-08-17 17:12:51 +08:00
|
|
|
|
today: css`
|
2023-08-18 10:41:45 +08:00
|
|
|
|
&:before {
|
|
|
|
|
border: 1px solid ${token.colorPrimary};
|
|
|
|
|
}
|
2023-08-17 17:12:51 +08:00
|
|
|
|
`,
|
|
|
|
|
text: css`
|
2023-08-18 10:41:45 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
`,
|
2023-08-17 17:12:51 +08:00
|
|
|
|
lunar,
|
|
|
|
|
current: css`
|
2023-08-18 10:41:45 +08:00
|
|
|
|
color: ${token.colorTextLightSolid};
|
|
|
|
|
&:before {
|
|
|
|
|
background: ${token.colorPrimary};
|
|
|
|
|
}
|
|
|
|
|
&:hover:before {
|
|
|
|
|
background: ${token.colorPrimary};
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
}
|
|
|
|
|
.${cx(lunar)} {
|
|
|
|
|
color: ${token.colorTextLightSolid};
|
|
|
|
|
opacity: 0.9;
|
|
|
|
|
}
|
|
|
|
|
`,
|
2023-08-17 17:12:51 +08:00
|
|
|
|
monthCell: css`
|
2023-08-18 10:41:45 +08:00
|
|
|
|
width: 120px;
|
|
|
|
|
color: ${token.colorTextBase};
|
|
|
|
|
border-radius: ${token.borderRadiusOuter}px;
|
|
|
|
|
padding: 5px 0;
|
|
|
|
|
&:hover {
|
|
|
|
|
background: rgba(0, 0, 0, 0.04);
|
|
|
|
|
}
|
|
|
|
|
`,
|
2023-08-17 17:12:51 +08:00
|
|
|
|
monthCellCurrent: css`
|
2023-08-18 10:41:45 +08:00
|
|
|
|
color: ${token.colorTextLightSolid};
|
|
|
|
|
background: ${token.colorPrimary};
|
|
|
|
|
&:hover {
|
|
|
|
|
background: ${token.colorPrimary};
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
}
|
|
|
|
|
`,
|
2023-08-17 17:12:51 +08:00
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const App: React.FC = () => {
|
|
|
|
|
const { styles } = useStyle({ test: true });
|
|
|
|
|
|
|
|
|
|
const [selectDate, setSelectDate] = React.useState<Dayjs>(dayjs());
|
|
|
|
|
|
|
|
|
|
const onPanelChange = (value: Dayjs, mode: CalendarProps<Dayjs>['mode']) => {
|
|
|
|
|
console.log(value.format('YYYY-MM-DD'), mode);
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-25 11:42:35 +08:00
|
|
|
|
const onDateChange: CalendarProps<Dayjs>['onSelect'] = (value, selectInfo) => {
|
|
|
|
|
if (selectInfo.source === 'date') {
|
|
|
|
|
setSelectDate(value);
|
|
|
|
|
}
|
2023-08-17 17:12:51 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const cellRender: CalendarProps<Dayjs>['fullCellRender'] = (date, info) => {
|
2023-08-25 11:42:35 +08:00
|
|
|
|
const d = Lunar.fromDate(date.toDate());
|
|
|
|
|
const lunar = d.getDayInChinese();
|
|
|
|
|
const solarTerm = d.getJieQi();
|
|
|
|
|
const h = HolidayUtil.getHoliday(date.get('year'), date.get('month') + 1, date.get('date'));
|
|
|
|
|
const displayHoliday = h?.getTarget() === h?.getDay() ? h?.getName() : undefined;
|
2023-08-17 17:12:51 +08:00
|
|
|
|
if (info.type === 'date') {
|
|
|
|
|
return React.cloneElement(info.originNode, {
|
|
|
|
|
...info.originNode.props,
|
|
|
|
|
className: classNames(styles.dateCell, {
|
|
|
|
|
[styles.current]: selectDate.isSame(date, 'date'),
|
|
|
|
|
[styles.today]: date.isSame(dayjs(), 'date'),
|
|
|
|
|
}),
|
|
|
|
|
children: (
|
|
|
|
|
<div className={styles.text}>
|
|
|
|
|
{date.get('date')}
|
2023-08-25 11:42:35 +08:00
|
|
|
|
{info.type === 'date' && (
|
|
|
|
|
<div className={styles.lunar}>{displayHoliday || solarTerm || lunar}</div>
|
|
|
|
|
)}
|
2023-08-17 17:12:51 +08:00
|
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (info.type === 'month') {
|
|
|
|
|
// Due to the fact that a solar month is part of the lunar month X and part of the lunar month X+1,
|
|
|
|
|
// when rendering a month, always take X as the lunar month of the month
|
2023-08-25 11:42:35 +08:00
|
|
|
|
const d2 = Lunar.fromDate(new Date(date.get('year'), date.get('month')));
|
|
|
|
|
const month = d2.getMonthInChinese();
|
2023-08-17 17:12:51 +08:00
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={classNames(styles.monthCell, {
|
|
|
|
|
[styles.monthCellCurrent]: selectDate.isSame(date, 'month'),
|
|
|
|
|
})}
|
|
|
|
|
>
|
2023-08-25 11:42:35 +08:00
|
|
|
|
{date.get('month') + 1}月({month}月)
|
2023-08-17 17:12:51 +08:00
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getYearLabel = (year: number) => {
|
2023-08-25 11:42:35 +08:00
|
|
|
|
const d = Lunar.fromDate(new Date(year + 1, 0));
|
|
|
|
|
return `${d.getYearInChinese()}年(${d.getYearInGanZhi()}${d.getYearShengXiao()}年)`;
|
2023-08-17 17:12:51 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getMonthLabel = (month: number, value: Dayjs) => {
|
2023-08-25 11:42:35 +08:00
|
|
|
|
const d = Lunar.fromDate(new Date(value.year(), month));
|
|
|
|
|
const lunar = d.getMonthInChinese();
|
|
|
|
|
return `${month + 1}月(${lunar}月)`;
|
2023-08-17 17:12:51 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={styles.wrapper}>
|
|
|
|
|
<Calendar
|
|
|
|
|
fullCellRender={cellRender}
|
|
|
|
|
fullscreen={false}
|
|
|
|
|
onPanelChange={onPanelChange}
|
2023-08-25 11:42:35 +08:00
|
|
|
|
onSelect={onDateChange}
|
2023-08-17 17:12:51 +08:00
|
|
|
|
headerRender={({ value, type, onChange, onTypeChange }) => {
|
|
|
|
|
const start = 0;
|
|
|
|
|
const end = 12;
|
|
|
|
|
const monthOptions = [];
|
|
|
|
|
|
|
|
|
|
let current = value.clone();
|
|
|
|
|
const localeData = value.localeData();
|
|
|
|
|
const months = [];
|
|
|
|
|
for (let i = 0; i < 12; i++) {
|
|
|
|
|
current = current.month(i);
|
|
|
|
|
months.push(localeData.monthsShort(current));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let i = start; i < end; i++) {
|
|
|
|
|
monthOptions.push({
|
|
|
|
|
label: getMonthLabel(i, value),
|
|
|
|
|
value: i,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const year = value.year();
|
|
|
|
|
const month = value.month();
|
|
|
|
|
const options = [];
|
|
|
|
|
for (let i = year - 10; i < year + 10; i += 1) {
|
|
|
|
|
options.push({
|
|
|
|
|
label: getYearLabel(i),
|
|
|
|
|
value: i,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<Row justify="end" gutter={8} style={{ padding: 8 }}>
|
|
|
|
|
<Col>
|
|
|
|
|
<Select
|
|
|
|
|
size="small"
|
|
|
|
|
dropdownMatchSelectWidth={false}
|
|
|
|
|
className="my-year-select"
|
|
|
|
|
value={year}
|
|
|
|
|
options={options}
|
|
|
|
|
onChange={(newYear) => {
|
|
|
|
|
const now = value.clone().year(newYear);
|
|
|
|
|
onChange(now);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col>
|
|
|
|
|
<Select
|
|
|
|
|
size="small"
|
|
|
|
|
dropdownMatchSelectWidth={false}
|
|
|
|
|
value={month}
|
|
|
|
|
options={monthOptions}
|
|
|
|
|
onChange={(newMonth) => {
|
|
|
|
|
const now = value.clone().month(newMonth);
|
|
|
|
|
onChange(now);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col>
|
|
|
|
|
<Radio.Group
|
|
|
|
|
size="small"
|
|
|
|
|
onChange={(e) => onTypeChange(e.target.value)}
|
|
|
|
|
value={type}
|
|
|
|
|
>
|
|
|
|
|
<Radio.Button value="month">月</Radio.Button>
|
|
|
|
|
<Radio.Button value="year">年</Radio.Button>
|
|
|
|
|
</Radio.Group>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default App;
|