Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
44a75c4
fix: 修复在next.js环境下的报错
Passing-of-A-Dream Jul 29, 2025
3fbe174
refactor(deps): 从 ahooks 的 useIsomorphicLayoutEffect 迁移到 rc-util 的 us…
Passing-of-A-Dream Jul 30, 2025
a71a9dd
Merge branch 'ant-design:master' into master
Passing-of-A-Dream Jul 30, 2025
6a4811f
Merge branch 'ant-design:master' into master
Passing-of-A-Dream Aug 6, 2025
398f75b
Merge branch 'ant-design:master' into master
Passing-of-A-Dream Aug 11, 2025
585573e
Merge branch 'ant-design:master' into master
Passing-of-A-Dream Aug 11, 2025
db7ed93
Merge branch 'ant-design:master' into master
Passing-of-A-Dream Aug 19, 2025
f232314
refactor(DatePicker): 增加columns列,用于控制列显示
Passing-of-A-Dream Sep 2, 2025
9f645e5
test(DatePicker): 增加测试用例
Passing-of-A-Dream Sep 2, 2025
a0d3b28
refactor(DatePicker): 将列常量类型定义为常量,优化导入方式
Passing-of-A-Dream Sep 2, 2025
222386c
feat(DatePicker): 增强日期选择器,支持自定义列和默认值处理
Passing-of-A-Dream Sep 3, 2025
f652cf4
fix(DatePicker): 优化日期选择器列生成逻辑,确保列精度正确过滤
Passing-of-A-Dream Sep 3, 2025
1f737b4
refactor(DatePicker): 优化生成日期选择器列的逻辑,使用映射关系简化代码
Passing-of-A-Dream Sep 8, 2025
6c73792
chore: trigger CI/CD pipeline
Passing-of-A-Dream Sep 8, 2025
687940b
refactor: 优化日期选择器日期转换逻辑
Passing-of-A-Dream Sep 16, 2025
fd37168
fix: 修复日期选择器列生成逻辑
Passing-of-A-Dream Sep 16, 2025
2b131b5
refactor: 优化日期选择器列生成逻辑
Passing-of-A-Dream Sep 16, 2025
3488ea9
refactor: 优化日期选择器工具函数逻辑
Passing-of-A-Dream Sep 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 155 additions & 87 deletions src/components/date-picker/date-picker-date-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ import isoWeeksInYear from 'dayjs/plugin/isoWeeksInYear'
import { RenderLabel } from '../date-picker-view/date-picker-view'
import { PickerColumn } from '../picker'
import type { DatePickerFilter } from './date-picker-utils'
import { TILL_NOW } from './util'
import {
DateColumnType,
DAY_COLUMN,
HOUR_COLUMN,
MINUTE_COLUMN,
MONTH_COLUMN,
SECOND_COLUMN,
TILL_NOW,
YEAR_COLUMN,
} from './util'

dayjs.extend(isoWeek)
dayjs.extend(isoWeeksInYear)
Expand All @@ -28,14 +37,24 @@ const precisionRankRecord: Record<DatePrecision, number> = {
second: 5,
}

const columnToPrecisionMap: Record<DateColumnType, DatePrecision> = {
[YEAR_COLUMN]: 'year',
[MONTH_COLUMN]: 'month',
[DAY_COLUMN]: 'day',
[HOUR_COLUMN]: 'hour',
[MINUTE_COLUMN]: 'minute',
[SECOND_COLUMN]: 'second',
}

export function generateDatePickerColumns(
selected: string[],
min: Date,
max: Date,
precision: DatePrecision,
renderLabel: RenderLabel,
filter: DatePickerFilter | undefined,
tillNow?: boolean
tillNow?: boolean,
columns?: DateColumnType[]
) {
const ret: PickerColumn[] = []

Expand All @@ -54,16 +73,41 @@ export function generateDatePickerColumns(
const maxSecond = max.getSeconds()

const rank = precisionRankRecord[precision]
const defaultColumns: DateColumnType[] = []
Object.keys(columnToPrecisionMap).forEach(columnType => {
const precision = columnToPrecisionMap[columnType as DateColumnType]
if (rank >= precisionRankRecord[precision]) {
defaultColumns.push(columnType as DateColumnType)
}
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这部分根据 precision 生成默认 columns 的逻辑,在 convertStringArrayToDate 函数(第 252-260 行)中也存在。代码重复会增加未来维护的成本。建议将这部分逻辑提取到一个独立的辅助函数中,例如 getDefaultColumns(precision),然后在两处都调用这个新函数,以遵循 DRY (Don't Repeat Yourself) 原则。


const finalColumns = columns?.length ? columns : defaultColumns
const renderedColumns = finalColumns.filter(columnType => {
const columnPrecision = columnToPrecisionMap[columnType]
return rank >= precisionRankRecord[columnPrecision]
})
function getValue(type: DateColumnType): number | null {
const index = renderedColumns.indexOf(type)
if (index >= 0 && index < selected.length) {
const val = parseInt(selected[index], 10)
return isNaN(val) ? null : val
}
return null
}

const selectedYear = parseInt(selected[0])
const selectedYear = getValue(YEAR_COLUMN) ?? min.getFullYear()
const selectedMonth = getValue(MONTH_COLUMN) ?? 1
const selectedDay = getValue(DAY_COLUMN) ?? 1
const selectedHour = getValue(HOUR_COLUMN) ?? 0
const selectedMinute = getValue(MINUTE_COLUMN) ?? 0
const selectedSecond = getValue(SECOND_COLUMN) ?? 0
const firstDayInSelectedMonth = dayjs(
convertStringArrayToDate([selected[0], selected[1], '1'])
convertStringArrayToDate(
[selectedYear, selectedMonth, '1'],
columns ?? defaultColumns,
precision
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

此处 convertStringArrayToDate 的使用方式存在问题。该函数期望的第一个参数 value 数组中的元素顺序与第二个参数 columns 数组的顺序严格对应。然而,这里硬编码了 [selectedYear, selectedMonth, '1'],它假定了列的顺序是年、月、日,当 columns prop 自定义顺序时(例如 ['month', 'day', 'year']),这会导致错误的日期计算,进而影响天数滚轮的范围。

由于 selectedYearselectedMonth 已经包含了正确的回退值,可以直接使用它们来构造日期,这样更清晰且能避免上述 bug。

    new Date(selectedYear, selectedMonth - 1, 1)

)
const selectedMonth = parseInt(selected[1])
const selectedDay = parseInt(selected[2])
const selectedHour = parseInt(selected[3])
const selectedMinute = parseInt(selected[4])
const selectedSecond = parseInt(selected[5])

const isInMinYear = selectedYear === minYear
const isInMaxYear = selectedYear === maxYear
Expand All @@ -79,99 +123,97 @@ export function generateDatePickerColumns(
const generateColumn = (
from: number,
to: number,
precision: DatePrecision
precision: DatePrecision,
columnType: DateColumnType
) => {
let column: number[] = []
for (let i = from; i <= to; i++) {
column.push(i)
}
const prefix = selected.slice(0, precisionRankRecord[precision])
const pos = Math.max(0, renderedColumns.indexOf(columnType))
const prefix = selected.slice(0, pos)
const partialColumns = renderedColumns.slice(0, pos).concat(columnType)
const currentFilter = filter?.[precision]
if (currentFilter && typeof currentFilter === 'function') {
column = column.filter(i =>
currentFilter(i, {
get date() {
const stringArray = [...prefix, i.toString()]
return convertStringArrayToDate(stringArray)
return convertStringArrayToDate(
stringArray,
partialColumns,
precision
)
},
})
)
}
return column
}

if (rank >= precisionRankRecord.year) {
const lower = minYear
const upper = maxYear
const years = generateColumn(lower, upper, 'year')
ret.push(
years.map(v => ({
label: renderLabel('year', v, { selected: selectedYear === v }),
value: v.toString(),
}))
)
const columnConfigs = {
[YEAR_COLUMN]: {
lower: minYear,
upper: maxYear,
selected: selectedYear,
precision: 'year' as DatePrecision,
},
[MONTH_COLUMN]: {
lower: isInMinYear ? minMonth : 1,
upper: isInMaxYear ? maxMonth : 12,
selected: selectedMonth,
precision: 'month' as DatePrecision,
},
[DAY_COLUMN]: {
lower: isInMinMonth ? minDay : 1,
upper: isInMaxMonth ? maxDay : firstDayInSelectedMonth.daysInMonth(),
selected: selectedDay,
precision: 'day' as DatePrecision,
},
[HOUR_COLUMN]: {
lower: isInMinDay ? minHour : 0,
upper: isInMaxDay ? maxHour : 23,
selected: selectedHour,
precision: 'hour' as DatePrecision,
},
[MINUTE_COLUMN]: {
lower: isInMinHour ? minMinute : 0,
upper: isInMaxHour ? maxMinute : 59,
selected: selectedMinute,
precision: 'minute' as DatePrecision,
},
[SECOND_COLUMN]: {
lower: isInMinMinute ? minSecond : 0,
upper: isInMaxMinute ? maxSecond : 59,
selected: selectedSecond,
precision: 'second' as DatePrecision,
},
}

if (rank >= precisionRankRecord.month) {
const lower = isInMinYear ? minMonth : 1
const upper = isInMaxYear ? maxMonth : 12
const months = generateColumn(lower, upper, 'month')
ret.push(
months.map(v => ({
label: renderLabel('month', v, { selected: selectedMonth === v }),
value: v.toString(),
}))
)
}
if (rank >= precisionRankRecord.day) {
const lower = isInMinMonth ? minDay : 1
const upper = isInMaxMonth ? maxDay : firstDayInSelectedMonth.daysInMonth()
const days = generateColumn(lower, upper, 'day')
ret.push(
days.map(v => ({
label: renderLabel('day', v, { selected: selectedDay === v }),
value: v.toString(),
}))
)
}
if (rank >= precisionRankRecord.hour) {
const lower = isInMinDay ? minHour : 0
const upper = isInMaxDay ? maxHour : 23
const hours = generateColumn(lower, upper, 'hour')
ret.push(
hours.map(v => ({
label: renderLabel('hour', v, { selected: selectedHour === v }),
value: v.toString(),
}))
renderedColumns.forEach(columnType => {
const config = columnConfigs[columnType]
if (!config) return

const column = generateColumn(
config.lower,
config.upper,
config.precision,
columnType
)
}
if (rank >= precisionRankRecord.minute) {
const lower = isInMinHour ? minMinute : 0
const upper = isInMaxHour ? maxMinute : 59
const minutes = generateColumn(lower, upper, 'minute')
ret.push(
minutes.map(v => ({
label: renderLabel('minute', v, { selected: selectedMinute === v }),
column.map(v => ({
label: renderLabel(config.precision, v, {
selected: config.selected === v,
}),
value: v.toString(),
}))
)
}
if (rank >= precisionRankRecord.second) {
const lower = isInMinMinute ? minSecond : 0
const upper = isInMaxMinute ? maxSecond : 59
const seconds = generateColumn(lower, upper, 'second')
ret.push(
seconds.map(v => ({
label: renderLabel('second', v, { selected: selectedSecond === v }),
value: v.toString(),
}))
)
}
})

// Till Now
if (tillNow) {
if (tillNow && ret.length > 0) {
ret[0].push({
label: renderLabel('now', null!, { selected: selected[0] === TILL_NOW }),
label: renderLabel('now', 0, { selected: selected[0] === TILL_NOW }),
value: TILL_NOW,
})

Expand Down Expand Up @@ -201,19 +243,45 @@ export function convertDateToStringArray(

export function convertStringArrayToDate<
T extends string | number | null | undefined,
>(value: T[]): Date {
const yearString = value[0] ?? '1900'
const monthString = value[1] ?? '1'
const dateString = value[2] ?? '1'
const hourString = value[3] ?? '0'
const minuteString = value[4] ?? '0'
const secondString = value[5] ?? '0'
>(
value: T[],
columns: DateColumnType[] | undefined,
precision: DatePrecision
): Date {
let finalColumns = columns
if (!finalColumns || finalColumns.length === 0) {
const rank = precisionRankRecord[precision]
finalColumns = (
Object.keys(columnToPrecisionMap) as DateColumnType[]
).filter(
columnType =>
rank >= precisionRankRecord[columnToPrecisionMap[columnType]]
)
}

const now = new Date()
const dateParts = {
[YEAR_COLUMN]: now.getFullYear().toString(),
[MONTH_COLUMN]: (now.getMonth() + 1).toString(),
[DAY_COLUMN]: now.getDate().toString(),
[HOUR_COLUMN]: now.getHours().toString(),
[MINUTE_COLUMN]: now.getMinutes().toString(),
[SECOND_COLUMN]: now.getSeconds().toString(),
}

finalColumns.forEach((columnType, index) => {
const val = value[index]
if (val !== null && val !== undefined) {
dateParts[columnType] = val.toString()
}
})

return new Date(
parseInt(yearString as string),
parseInt(monthString as string) - 1,
parseInt(dateString as string),
parseInt(hourString as string),
parseInt(minuteString as string),
parseInt(secondString as string)
parseInt(dateParts[YEAR_COLUMN]),
parseInt(dateParts[MONTH_COLUMN]) - 1,
parseInt(dateParts[DAY_COLUMN]),
parseInt(dateParts[HOUR_COLUMN]),
parseInt(dateParts[MINUTE_COLUMN]),
parseInt(dateParts[SECOND_COLUMN])
)
}
Loading
Loading