-
Notifications
You must be signed in to change notification settings - Fork 2.5k
feat: date-picker 组件优化日期选择器,支持自定义日期字段顺序 #6940
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 17 commits
44a75c4
3fbe174
a71a9dd
6a4811f
398f75b
585573e
db7ed93
f232314
9f645e5
a0d3b28
222386c
f652cf4
1f737b4
6c73792
687940b
fd37168
2b131b5
3488ea9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
|
@@ -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[] = [] | ||
|
||
|
@@ -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) | ||
} | ||
}) | ||
|
||
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 | ||
) | ||
|
||
) | ||
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 | ||
|
@@ -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, | ||
}) | ||
|
||
|
@@ -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]) | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这部分根据
precision
生成默认columns
的逻辑,在convertStringArrayToDate
函数(第 252-260 行)中也存在。代码重复会增加未来维护的成本。建议将这部分逻辑提取到一个独立的辅助函数中,例如getDefaultColumns(precision)
,然后在两处都调用这个新函数,以遵循 DRY (Don't Repeat Yourself) 原则。