Skip to content

Commit 7222ea4

Browse files
Wesley-0808github-actions[bot]uyarnCopilottdesign-bot
authored
feat(DatePicker): add cell API, support custom cell (#6495)
* feat: docs * feat: support cell api * fix: lint * chore: common * chore: update common * chore: update demos * chore: update demos * Update packages/components/date-picker/date-picker.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: optimize Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: stash changelog [ci skip] --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Uyarn <uyarnchen@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: tdesign-bot <tdesign@tencent.com>
1 parent 3840d7b commit 7222ea4

File tree

16 files changed

+566
-23
lines changed

16 files changed

+566
-23
lines changed

packages/components/date-picker/DatePicker.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import type { TagInputRemoveContext } from '../tag-input';
4040
export default defineComponent({
4141
name: 'TDatePicker',
4242
props,
43-
setup(props) {
43+
setup(props, { slots }) {
4444
const COMPONENT_NAME = usePrefixClass('date-picker');
4545

4646
const {
@@ -425,6 +425,7 @@ export default defineComponent({
425425
needConfirm: props.needConfirm,
426426
disableTime: props.disableTime,
427427
range: props.range,
428+
cell: props.cell,
428429
onCellClick,
429430
onCellMouseEnter,
430431
onCellMouseLeave,
@@ -461,7 +462,7 @@ export default defineComponent({
461462
popupVisible={!isReadOnly.value && popupVisible.value}
462463
valueDisplay={() => renderTNodeJSX('valueDisplay', { params: valueDisplayParams.value })}
463464
{...(props.selectInputProps as TdDatePickerProps['selectInputProps'])}
464-
panel={() => <TSinglePanel {...panelProps.value} />}
465+
panel={() => <TSinglePanel {...panelProps.value} v-slots={slots} />}
465466
tagInputProps={{
466467
onRemove: onTagRemoveClick,
467468
}}

packages/components/date-picker/DateRangePicker.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ export default defineComponent({
538538
onYearChange,
539539
onMonthChange,
540540
onTimePickerChange,
541+
cell: props.cell,
541542
}));
542543

543544
return () => (
@@ -552,7 +553,7 @@ export default defineComponent({
552553
popupProps={popupProps.value}
553554
rangeInputProps={rangeInputProps.value}
554555
popupVisible={popupVisible.value}
555-
panel={() => <TRangePanel {...panelProps.value} />}
556+
panel={() => <TRangePanel {...panelProps.value} v-slots={slots} />}
556557
/>
557558
</div>
558559
);
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
<template>
2+
<t-space direction="vertical">
3+
<!-- 示例1: 显示农历日期 -->
4+
<t-date-picker v-model="date1" placeholder="显示农历日期">
5+
<template #cell="{ value }">
6+
<div class="lunar-cell">
7+
<span class="solar-day">{{ value.getDate() }}</span>
8+
<span class="lunar-day">{{ getLunarDay(value) }}</span>
9+
</div>
10+
</template>
11+
</t-date-picker>
12+
13+
<!-- 示例2: 范围选择器中标记特殊日期 -->
14+
<t-date-range-picker v-model="date2">
15+
<template #cell="{ value }">
16+
<div v-if="isSpecialDay(value)" class="custom-range-cell">
17+
<t-badge count="New" dot style="width: 100%">
18+
{{ value.getDate() }}
19+
</t-badge>
20+
</div>
21+
<span v-else>
22+
{{ value.getDate() }}
23+
</span>
24+
</template>
25+
</t-date-range-picker>
26+
</t-space>
27+
</template>
28+
29+
<script lang="ts" setup>
30+
import { ref } from 'vue';
31+
import type { DateRangeValue } from 'tdesign-vue-next';
32+
33+
const date1 = ref<string>('');
34+
const date2 = ref<DateRangeValue>(['', '']);
35+
36+
function isSpecialDay(date: Date): boolean {
37+
return date.getDate() === 1 || date.getDate() === 15;
38+
}
39+
40+
/** 农历年份信息类型 */
41+
interface LunarYearInfo {
42+
monthDays: number[];
43+
leapMonth: number;
44+
leapDays: number;
45+
}
46+
47+
/** 农历日期信息类型 */
48+
interface LunarDateInfo {
49+
year: number;
50+
month: number;
51+
day: number;
52+
isLeapMonth: boolean;
53+
monthName: string;
54+
dayName: string;
55+
}
56+
57+
/**
58+
* 农历年份信息(1900-2099年)
59+
* 数据来源:天文历法计算
60+
* 格式说明:
61+
* - months: 12个月的天数,30或29
62+
* - leapMonth: 闰月月份,0表示无闰月
63+
* - leapDays: 闰月天数,30或29
64+
*/
65+
function getLunarYearInfo(year: number): LunarYearInfo {
66+
/**
67+
* 农历数据表,每个数字编码了一年的信息:
68+
* - 低4位(0-3): 闰月月份(0-12,0表示无闰月)
69+
* - 中间12位(4-15): 1-12月每月天数(1=30天, 0=29天)
70+
* - 第17位(16): 闰月天数(1=30天, 0=29天)
71+
*/
72+
// prettier-ignore
73+
const DATA = [
74+
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
75+
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
76+
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
77+
0x06566, 0x0d4a0, 0x0ea50, 0x16a95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
78+
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
79+
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
80+
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
81+
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
82+
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
83+
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
84+
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
85+
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
86+
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
87+
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
88+
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
89+
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
90+
0x092e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
91+
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
92+
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
93+
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
94+
];
95+
96+
const yearData = DATA[year - 1900];
97+
98+
// 解析闰月月份(低4位)
99+
const leapMonth = yearData & 0xf;
100+
101+
// 解析闰月天数(第17位,1=30天,0=29天)
102+
const leapDays = leapMonth ? (yearData & 0x10000 ? 30 : 29) : 0;
103+
104+
// 解析每月天数(5-16位)
105+
const monthDays = [];
106+
for (let month = 1; month <= 12; month++) {
107+
// 从高位到低位依次判断每月天数
108+
const bit = 0x8000 >> (month - 1);
109+
monthDays.push(yearData & bit ? 30 : 29);
110+
}
111+
112+
return { monthDays, leapMonth, leapDays };
113+
}
114+
115+
/** 农历月份名称 */
116+
const LUNAR_MONTH_NAMES = ['', '', '', '', '', '', '', '', '', '', '', ''];
117+
118+
/** 农历日期名称 */
119+
// prettier-ignore
120+
const LUNAR_DAY_NAMES = [
121+
'初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
122+
'十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
123+
'廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十',
124+
];
125+
126+
/**
127+
* 计算农历年的总天数
128+
*/
129+
function getLunarYearTotalDays(year: number): number {
130+
const { monthDays, leapDays } = getLunarYearInfo(year);
131+
// 12个月的天数之和 + 闰月天数
132+
return monthDays.reduce((sum, days) => sum + days, 0) + leapDays;
133+
}
134+
135+
/**
136+
* 公历日期转农历日期
137+
* @param solarDate - 公历日期
138+
* @returns 农历日期信息
139+
*/
140+
function solarToLunar(solarDate: Date): LunarDateInfo {
141+
// 农历1900年正月初一对应的公历日期
142+
const BASE_DATE = new Date(1900, 0, 31);
143+
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
144+
145+
// 计算与基准日期相差的天数
146+
let remainingDays = Math.floor((solarDate.getTime() - BASE_DATE.getTime()) / MILLISECONDS_PER_DAY);
147+
148+
// 计算农历年份
149+
let lunarYear = 1900;
150+
let yearDays = 0;
151+
while (lunarYear < 2100 && remainingDays > 0) {
152+
yearDays = getLunarYearTotalDays(lunarYear);
153+
remainingDays -= yearDays;
154+
lunarYear++;
155+
}
156+
// 如果超减了,回退一年
157+
if (remainingDays < 0) {
158+
remainingDays += yearDays;
159+
lunarYear--;
160+
}
161+
162+
// 获取该年的农历信息
163+
const { monthDays, leapMonth, leapDays } = getLunarYearInfo(lunarYear);
164+
165+
// 计算农历月份和日期
166+
let lunarMonth = 1;
167+
let isLeapMonth = false;
168+
let monthDay = 0;
169+
170+
for (let month = 1; month <= 12; month++) {
171+
// 当前月的天数
172+
monthDay = monthDays[month - 1];
173+
174+
if (remainingDays < monthDay) {
175+
lunarMonth = month;
176+
break;
177+
}
178+
remainingDays -= monthDay;
179+
180+
// 处理闰月
181+
if (leapMonth === month) {
182+
if (remainingDays < leapDays) {
183+
lunarMonth = month;
184+
isLeapMonth = true;
185+
break;
186+
}
187+
remainingDays -= leapDays;
188+
}
189+
190+
if (month === 12) {
191+
lunarMonth = 12;
192+
}
193+
}
194+
195+
const lunarDay = remainingDays + 1;
196+
197+
return {
198+
year: lunarYear,
199+
month: lunarMonth,
200+
day: lunarDay,
201+
isLeapMonth,
202+
// 格式化月份名称
203+
monthName: (isLeapMonth ? '' : '') + LUNAR_MONTH_NAMES[lunarMonth - 1] + '',
204+
// 格式化日期名称
205+
dayName: LUNAR_DAY_NAMES[lunarDay - 1],
206+
};
207+
}
208+
209+
/**
210+
* 获取农历日期的显示文本
211+
* 规则:每月初一显示月份名,其他日期显示日期名
212+
*/
213+
function getLunarDay(date: Date): string {
214+
const lunar = solarToLunar(date);
215+
return lunar.day === 1 ? lunar.monthName : lunar.dayName;
216+
}
217+
</script>
218+
219+
<style>
220+
.lunar-cell {
221+
width: 100%;
222+
height: 100%;
223+
display: flex;
224+
flex-direction: column;
225+
align-items: center;
226+
justify-content: center;
227+
line-height: 1.2;
228+
}
229+
230+
.solar-day {
231+
font-size: 14px;
232+
}
233+
234+
.lunar-day {
235+
font-size: 10px;
236+
color: var(--td-text-color-placeholder);
237+
transform: scale(0.9);
238+
}
239+
240+
.t-date-picker__cell--active .lunar-day {
241+
color: var(--td-text-color-anti);
242+
}
243+
244+
.t-date-picker__cell-inner:has(.lunar-cell) {
245+
height: 40px;
246+
}
247+
248+
.custom-range-cell {
249+
width: 100%;
250+
}
251+
252+
.t-date-picker__cell--active .t-date-picker__cell-inner .t-badge {
253+
color: var(--td-text-color-anti);
254+
}
255+
</style>

0 commit comments

Comments
 (0)