|
| 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