mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
feat(export): refine time range dialog mode switching
This commit is contained in:
@@ -2349,7 +2349,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.time-range-dialog {
|
.time-range-dialog {
|
||||||
width: min(920px, calc(100vw - 32px));
|
width: min(480px, calc(100vw - 32px));
|
||||||
max-height: calc(100vh - 64px);
|
max-height: calc(100vh - 64px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@@ -2374,24 +2374,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.time-range-preset-list {
|
.time-range-preset-list {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
flex-wrap: nowrap;
|
||||||
gap: 8px;
|
gap: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-range-preset-item {
|
.time-range-preset-item {
|
||||||
|
flex: 0 0 auto;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
min-height: 38px;
|
min-height: 30px;
|
||||||
padding: 0 10px;
|
padding: 0 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 4px;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-color: var(--primary);
|
border-color: var(--primary);
|
||||||
@@ -2403,14 +2411,30 @@
|
|||||||
.time-range-calendar-grid {
|
.time-range-calendar-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-range-mode-banner {
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.4;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
|
||||||
|
&.range {
|
||||||
|
border-color: rgba(var(--primary-rgb), 0.4);
|
||||||
|
background: rgba(var(--primary-rgb), 0.1);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-range-calendar-panel {
|
.time-range-calendar-panel {
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
padding: 10px;
|
padding: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-range-calendar-panel-header {
|
.time-range-calendar-panel-header {
|
||||||
@@ -2430,23 +2454,40 @@
|
|||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
small {
|
||||||
font-size: 13px;
|
font-size: 10px;
|
||||||
color: var(--text-primary);
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-range-date-input {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
height: 24px;
|
||||||
|
padding: 0 7px;
|
||||||
|
font-size: 11px;
|
||||||
|
|
||||||
|
&.invalid {
|
||||||
|
border-color: #e84d4d;
|
||||||
|
box-shadow: 0 0 0 1px rgba(232, 77, 77, 0.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-range-calendar-nav {
|
.time-range-calendar-nav {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 4px;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: 22px;
|
width: 20px;
|
||||||
height: 22px;
|
height: 20px;
|
||||||
border-radius: 6px;
|
border-radius: 5px;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
@@ -2457,32 +2498,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.time-range-calendar-weekdays {
|
.time-range-calendar-weekdays {
|
||||||
margin-top: 8px;
|
margin-top: 6px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
grid-template-columns: repeat(7, 1fr);
|
||||||
gap: 4px;
|
gap: 2px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-range-calendar-days {
|
.time-range-calendar-days {
|
||||||
margin-top: 6px;
|
margin-top: 4px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
grid-template-columns: repeat(7, 1fr);
|
||||||
gap: 4px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-range-calendar-day {
|
.time-range-calendar-day {
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
min-height: 28px;
|
min-height: 20px;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: 12px;
|
font-size: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
@@ -2499,16 +2540,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-range-full-hint {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
line-height: 1.45;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px dashed var(--border-color);
|
|
||||||
padding: 8px 10px;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-range-custom-row {
|
.time-range-custom-row {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
@@ -2672,7 +2703,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.time-range-preset-list {
|
.time-range-preset-list {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-range-calendar-grid {
|
.time-range-calendar-grid {
|
||||||
|
|||||||
@@ -499,6 +499,26 @@ const formatDateInputValue = (date: Date): string => {
|
|||||||
return `${y}-${m}-${d}`
|
return `${y}-${m}-${d}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parseDateInputValue = (raw: string): Date | null => {
|
||||||
|
const text = String(raw || '').trim()
|
||||||
|
const matched = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text)
|
||||||
|
if (!matched) return null
|
||||||
|
const year = Number(matched[1])
|
||||||
|
const month = Number(matched[2])
|
||||||
|
const day = Number(matched[3])
|
||||||
|
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) return null
|
||||||
|
if (month < 1 || month > 12 || day < 1 || day > 31) return null
|
||||||
|
const parsed = new Date(year, month - 1, day)
|
||||||
|
if (
|
||||||
|
parsed.getFullYear() !== year ||
|
||||||
|
parsed.getMonth() !== month - 1 ||
|
||||||
|
parsed.getDate() !== day
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
const toMonthStart = (date: Date): Date => new Date(date.getFullYear(), date.getMonth(), 1)
|
const toMonthStart = (date: Date): Date => new Date(date.getFullYear(), date.getMonth(), 1)
|
||||||
|
|
||||||
const addMonths = (date: Date, delta: number): Date => {
|
const addMonths = (date: Date, delta: number): Date => {
|
||||||
@@ -1212,6 +1232,8 @@ function ExportPage() {
|
|||||||
const [isTimeRangeDialogOpen, setIsTimeRangeDialogOpen] = useState(false)
|
const [isTimeRangeDialogOpen, setIsTimeRangeDialogOpen] = useState(false)
|
||||||
const [timeRangePreset, setTimeRangePreset] = useState<DateRangePreset>('all')
|
const [timeRangePreset, setTimeRangePreset] = useState<DateRangePreset>('all')
|
||||||
const [timeRangeDialogDraft, setTimeRangeDialogDraft] = useState<TimeRangeDialogDraft | null>(null)
|
const [timeRangeDialogDraft, setTimeRangeDialogDraft] = useState<TimeRangeDialogDraft | null>(null)
|
||||||
|
const [timeRangeDateInput, setTimeRangeDateInput] = useState<{ start: string; end: string }>({ start: '', end: '' })
|
||||||
|
const [timeRangeDateInputError, setTimeRangeDateInputError] = useState<{ start: boolean; end: boolean }>({ start: false, end: false })
|
||||||
|
|
||||||
const [options, setOptions] = useState<ExportOptions>({
|
const [options, setOptions] = useState<ExportOptions>({
|
||||||
format: 'json',
|
format: 'json',
|
||||||
@@ -2321,6 +2343,8 @@ function ExportPage() {
|
|||||||
setExportDialog(prev => ({ ...prev, open: false }))
|
setExportDialog(prev => ({ ...prev, open: false }))
|
||||||
setIsTimeRangeDialogOpen(false)
|
setIsTimeRangeDialogOpen(false)
|
||||||
setTimeRangeDialogDraft(null)
|
setTimeRangeDialogDraft(null)
|
||||||
|
setTimeRangeDateInput({ start: '', end: '' })
|
||||||
|
setTimeRangeDateInputError({ start: false, end: false })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const buildTimeRangeDialogDraft = useCallback((): TimeRangeDialogDraft => {
|
const buildTimeRangeDialogDraft = useCallback((): TimeRangeDialogDraft => {
|
||||||
@@ -2338,23 +2362,33 @@ function ExportPage() {
|
|||||||
}, [options.dateRange, options.useAllTime, timeRangePreset])
|
}, [options.dateRange, options.useAllTime, timeRangePreset])
|
||||||
|
|
||||||
const openTimeRangeDialog = useCallback(() => {
|
const openTimeRangeDialog = useCallback(() => {
|
||||||
setTimeRangeDialogDraft(buildTimeRangeDialogDraft())
|
const draft = buildTimeRangeDialogDraft()
|
||||||
|
setTimeRangeDialogDraft(draft)
|
||||||
setIsTimeRangeDialogOpen(true)
|
setIsTimeRangeDialogOpen(true)
|
||||||
}, [buildTimeRangeDialogDraft])
|
}, [buildTimeRangeDialogDraft])
|
||||||
|
|
||||||
const closeTimeRangeDialog = useCallback(() => {
|
const closeTimeRangeDialog = useCallback(() => {
|
||||||
setIsTimeRangeDialogOpen(false)
|
setIsTimeRangeDialogOpen(false)
|
||||||
setTimeRangeDialogDraft(null)
|
setTimeRangeDialogDraft(null)
|
||||||
|
setTimeRangeDateInput({ start: '', end: '' })
|
||||||
|
setTimeRangeDateInputError({ start: false, end: false })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const applyTimeRangePresetToDraft = useCallback((preset: Exclude<DateRangePreset, 'custom'>) => {
|
const applyTimeRangePresetToDraft = useCallback((preset: Exclude<DateRangePreset, 'custom'>) => {
|
||||||
setTimeRangeDialogDraft(prev => {
|
setTimeRangeDialogDraft(prev => {
|
||||||
const base = prev ?? buildTimeRangeDialogDraft()
|
const base = prev ?? buildTimeRangeDialogDraft()
|
||||||
if (preset === 'all') {
|
if (preset === 'all') {
|
||||||
|
const previewRange = createDefaultDateRange()
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
preset,
|
preset,
|
||||||
useAllTime: true
|
useAllTime: true,
|
||||||
|
dateRange: {
|
||||||
|
start: previewRange.start,
|
||||||
|
end: previewRange.end
|
||||||
|
},
|
||||||
|
startPanelMonth: toMonthStart(previewRange.start),
|
||||||
|
endPanelMonth: toMonthStart(previewRange.end)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const range = createDateRangeByPreset(preset)
|
const range = createDateRangeByPreset(preset)
|
||||||
@@ -2372,6 +2406,10 @@ function ExportPage() {
|
|||||||
})
|
})
|
||||||
}, [buildTimeRangeDialogDraft])
|
}, [buildTimeRangeDialogDraft])
|
||||||
|
|
||||||
|
const handleTimeRangePresetClick = useCallback((preset: Exclude<DateRangePreset, 'custom'>) => {
|
||||||
|
applyTimeRangePresetToDraft(preset)
|
||||||
|
}, [applyTimeRangePresetToDraft])
|
||||||
|
|
||||||
const updateTimeRangeDraftStart = useCallback((targetDate: Date) => {
|
const updateTimeRangeDraftStart = useCallback((targetDate: Date) => {
|
||||||
const start = startOfDay(targetDate)
|
const start = startOfDay(targetDate)
|
||||||
setTimeRangeDialogDraft(prev => {
|
setTimeRangeDialogDraft(prev => {
|
||||||
@@ -2395,20 +2433,45 @@ function ExportPage() {
|
|||||||
const end = endOfDay(targetDate)
|
const end = endOfDay(targetDate)
|
||||||
setTimeRangeDialogDraft(prev => {
|
setTimeRangeDialogDraft(prev => {
|
||||||
const base = prev ?? buildTimeRangeDialogDraft()
|
const base = prev ?? buildTimeRangeDialogDraft()
|
||||||
const nextEnd = end < base.dateRange.start ? endOfDay(base.dateRange.start) : end
|
const isAllTimeMode = base.useAllTime
|
||||||
|
const nextStart = isAllTimeMode
|
||||||
|
? startOfDay(targetDate)
|
||||||
|
: base.dateRange.start
|
||||||
|
const nextEnd = end < nextStart ? endOfDay(nextStart) : end
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
preset: 'custom',
|
preset: 'custom',
|
||||||
useAllTime: false,
|
useAllTime: false,
|
||||||
dateRange: {
|
dateRange: {
|
||||||
start: base.dateRange.start,
|
start: nextStart,
|
||||||
end: nextEnd
|
end: nextEnd
|
||||||
},
|
},
|
||||||
|
startPanelMonth: toMonthStart(nextStart),
|
||||||
endPanelMonth: toMonthStart(nextEnd)
|
endPanelMonth: toMonthStart(nextEnd)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [buildTimeRangeDialogDraft])
|
}, [buildTimeRangeDialogDraft])
|
||||||
|
|
||||||
|
const commitTimeRangeStartFromInput = useCallback(() => {
|
||||||
|
const parsed = parseDateInputValue(timeRangeDateInput.start)
|
||||||
|
if (!parsed) {
|
||||||
|
setTimeRangeDateInputError(prev => ({ ...prev, start: true }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setTimeRangeDateInputError(prev => ({ ...prev, start: false }))
|
||||||
|
updateTimeRangeDraftStart(parsed)
|
||||||
|
}, [timeRangeDateInput.start, updateTimeRangeDraftStart])
|
||||||
|
|
||||||
|
const commitTimeRangeEndFromInput = useCallback(() => {
|
||||||
|
const parsed = parseDateInputValue(timeRangeDateInput.end)
|
||||||
|
if (!parsed) {
|
||||||
|
setTimeRangeDateInputError(prev => ({ ...prev, end: true }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setTimeRangeDateInputError(prev => ({ ...prev, end: false }))
|
||||||
|
updateTimeRangeDraftEnd(parsed)
|
||||||
|
}, [timeRangeDateInput.end, updateTimeRangeDraftEnd])
|
||||||
|
|
||||||
const shiftTimeRangePanelMonth = useCallback((panel: 'start' | 'end', delta: number) => {
|
const shiftTimeRangePanelMonth = useCallback((panel: 'start' | 'end', delta: number) => {
|
||||||
setTimeRangeDialogDraft(prev => {
|
setTimeRangeDialogDraft(prev => {
|
||||||
const base = prev ?? buildTimeRangeDialogDraft()
|
const base = prev ?? buildTimeRangeDialogDraft()
|
||||||
@@ -2445,7 +2508,7 @@ function ExportPage() {
|
|||||||
if (timeRangePreset === 'yesterday') return '昨天'
|
if (timeRangePreset === 'yesterday') return '昨天'
|
||||||
if (timeRangePreset === 'last3days') return '最近3天'
|
if (timeRangePreset === 'last3days') return '最近3天'
|
||||||
if (timeRangePreset === 'last7days') return '最近一周'
|
if (timeRangePreset === 'last7days') return '最近一周'
|
||||||
if (timeRangePreset === 'last30days') return '最近一个月'
|
if (timeRangePreset === 'last30days') return '最近30 天'
|
||||||
if (timeRangePreset === 'last1year') return '最近一年'
|
if (timeRangePreset === 'last1year') return '最近一年'
|
||||||
if (timeRangePreset === 'last2years') return '最近两年'
|
if (timeRangePreset === 'last2years') return '最近两年'
|
||||||
if (options.dateRange) {
|
if (options.dateRange) {
|
||||||
@@ -2455,6 +2518,23 @@ function ExportPage() {
|
|||||||
}, [options.useAllTime, options.dateRange, timeRangePreset])
|
}, [options.useAllTime, options.dateRange, timeRangePreset])
|
||||||
|
|
||||||
const activeTimeRangeDialogDraft = timeRangeDialogDraft ?? buildTimeRangeDialogDraft()
|
const activeTimeRangeDialogDraft = timeRangeDialogDraft ?? buildTimeRangeDialogDraft()
|
||||||
|
const isRangeModeActive = !activeTimeRangeDialogDraft.useAllTime
|
||||||
|
const timeRangeModeText = isRangeModeActive
|
||||||
|
? '当前导出模式:按时间范围导出'
|
||||||
|
: '当前导出模式:全部时间导出(选择下方日期将切换为按时间范围导出)'
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isTimeRangeDialogOpen) return
|
||||||
|
setTimeRangeDateInput({
|
||||||
|
start: formatDateInputValue(activeTimeRangeDialogDraft.dateRange.start),
|
||||||
|
end: formatDateInputValue(activeTimeRangeDialogDraft.dateRange.end)
|
||||||
|
})
|
||||||
|
setTimeRangeDateInputError({ start: false, end: false })
|
||||||
|
}, [
|
||||||
|
isTimeRangeDialogOpen,
|
||||||
|
activeTimeRangeDialogDraft.dateRange.start.getTime(),
|
||||||
|
activeTimeRangeDialogDraft.dateRange.end.getTime()
|
||||||
|
])
|
||||||
|
|
||||||
const isTimeRangePresetActive = useCallback((preset: DateRangePreset): boolean => {
|
const isTimeRangePresetActive = useCallback((preset: DateRangePreset): boolean => {
|
||||||
if (preset === 'all') return activeTimeRangeDialogDraft.useAllTime
|
if (preset === 'all') return activeTimeRangeDialogDraft.useAllTime
|
||||||
@@ -4696,9 +4776,8 @@ function ExportPage() {
|
|||||||
{ value: 'yesterday', label: '昨天' },
|
{ value: 'yesterday', label: '昨天' },
|
||||||
{ value: 'last3days', label: '最近3天' },
|
{ value: 'last3days', label: '最近3天' },
|
||||||
{ value: 'last7days', label: '最近一周' },
|
{ value: 'last7days', label: '最近一周' },
|
||||||
{ value: 'last30days', label: '最近一个月' },
|
{ value: 'last30days', label: '最近30 天' },
|
||||||
{ value: 'last1year', label: '最近一年' },
|
{ value: 'last1year', label: '最近一年' }
|
||||||
{ value: 'last2years', label: '最近两年' }
|
|
||||||
] as Array<{ value: Exclude<DateRangePreset, 'custom'>; label: string }>).map((preset) => {
|
] as Array<{ value: Exclude<DateRangePreset, 'custom'>; label: string }>).map((preset) => {
|
||||||
const isActive = isTimeRangePresetActive(preset.value)
|
const isActive = isTimeRangePresetActive(preset.value)
|
||||||
return (
|
return (
|
||||||
@@ -4706,7 +4785,7 @@ function ExportPage() {
|
|||||||
key={preset.value}
|
key={preset.value}
|
||||||
type="button"
|
type="button"
|
||||||
className={`time-range-preset-item ${isActive ? 'active' : ''}`}
|
className={`time-range-preset-item ${isActive ? 'active' : ''}`}
|
||||||
onClick={() => applyTimeRangePresetToDraft(preset.value)}
|
onClick={() => handleTimeRangePresetClick(preset.value)}
|
||||||
>
|
>
|
||||||
<span>{preset.label}</span>
|
<span>{preset.label}</span>
|
||||||
{isActive && <Check size={14} />}
|
{isActive && <Check size={14} />}
|
||||||
@@ -4715,12 +4794,36 @@ function ExportPage() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={`time-range-mode-banner ${isRangeModeActive ? 'range' : 'all'}`}>
|
||||||
|
{timeRangeModeText}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="time-range-calendar-grid">
|
<div className="time-range-calendar-grid">
|
||||||
<section className="time-range-calendar-panel">
|
<section className="time-range-calendar-panel">
|
||||||
<div className="time-range-calendar-panel-header">
|
<div className="time-range-calendar-panel-header">
|
||||||
<div className="time-range-calendar-date-label">
|
<div className="time-range-calendar-date-label">
|
||||||
<span>起始日期</span>
|
<span>起始日期</span>
|
||||||
<strong>{formatDateInputValue(activeTimeRangeDialogDraft.dateRange.start)}</strong>
|
<input
|
||||||
|
type="text"
|
||||||
|
className={`time-range-date-input ${timeRangeDateInputError.start ? 'invalid' : ''}`}
|
||||||
|
value={timeRangeDateInput.start}
|
||||||
|
placeholder="YYYY-MM-DD"
|
||||||
|
onChange={(event) => {
|
||||||
|
const nextValue = event.target.value
|
||||||
|
setTimeRangeDateInput(prev => ({ ...prev, start: nextValue }))
|
||||||
|
if (timeRangeDateInputError.start) {
|
||||||
|
setTimeRangeDateInputError(prev => ({ ...prev, start: false }))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key !== 'Enter') return
|
||||||
|
event.preventDefault()
|
||||||
|
commitTimeRangeStartFromInput()
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
commitTimeRangeStartFromInput()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="time-range-calendar-nav">
|
<div className="time-range-calendar-nav">
|
||||||
<button type="button" onClick={() => shiftTimeRangePanelMonth('start', -1)} aria-label="上个月">‹</button>
|
<button type="button" onClick={() => shiftTimeRangePanelMonth('start', -1)} aria-label="上个月">‹</button>
|
||||||
@@ -4735,7 +4838,8 @@ function ExportPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="time-range-calendar-days">
|
<div className="time-range-calendar-days">
|
||||||
{startPanelCells.map((cell) => {
|
{startPanelCells.map((cell) => {
|
||||||
const isSelected = isSameDay(cell.date, activeTimeRangeDialogDraft.dateRange.start)
|
const isSelected = !activeTimeRangeDialogDraft.useAllTime &&
|
||||||
|
isSameDay(cell.date, activeTimeRangeDialogDraft.dateRange.start)
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={`start-${cell.date.getTime()}`}
|
key={`start-${cell.date.getTime()}`}
|
||||||
@@ -4754,7 +4858,27 @@ function ExportPage() {
|
|||||||
<div className="time-range-calendar-panel-header">
|
<div className="time-range-calendar-panel-header">
|
||||||
<div className="time-range-calendar-date-label">
|
<div className="time-range-calendar-date-label">
|
||||||
<span>截止日期</span>
|
<span>截止日期</span>
|
||||||
<strong>{formatDateInputValue(activeTimeRangeDialogDraft.dateRange.end)}</strong>
|
<input
|
||||||
|
type="text"
|
||||||
|
className={`time-range-date-input ${timeRangeDateInputError.end ? 'invalid' : ''}`}
|
||||||
|
value={timeRangeDateInput.end}
|
||||||
|
placeholder="YYYY-MM-DD"
|
||||||
|
onChange={(event) => {
|
||||||
|
const nextValue = event.target.value
|
||||||
|
setTimeRangeDateInput(prev => ({ ...prev, end: nextValue }))
|
||||||
|
if (timeRangeDateInputError.end) {
|
||||||
|
setTimeRangeDateInputError(prev => ({ ...prev, end: false }))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key !== 'Enter') return
|
||||||
|
event.preventDefault()
|
||||||
|
commitTimeRangeEndFromInput()
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
commitTimeRangeEndFromInput()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="time-range-calendar-nav">
|
<div className="time-range-calendar-nav">
|
||||||
<button type="button" onClick={() => shiftTimeRangePanelMonth('end', -1)} aria-label="上个月">‹</button>
|
<button type="button" onClick={() => shiftTimeRangePanelMonth('end', -1)} aria-label="上个月">‹</button>
|
||||||
@@ -4769,7 +4893,8 @@ function ExportPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="time-range-calendar-days">
|
<div className="time-range-calendar-days">
|
||||||
{endPanelCells.map((cell) => {
|
{endPanelCells.map((cell) => {
|
||||||
const isSelected = isSameDay(cell.date, activeTimeRangeDialogDraft.dateRange.end)
|
const isSelected = !activeTimeRangeDialogDraft.useAllTime &&
|
||||||
|
isSameDay(cell.date, activeTimeRangeDialogDraft.dateRange.end)
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={`end-${cell.date.getTime()}`}
|
key={`end-${cell.date.getTime()}`}
|
||||||
@@ -4785,12 +4910,6 @@ function ExportPage() {
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activeTimeRangeDialogDraft.useAllTime && (
|
|
||||||
<div className="time-range-full-hint">
|
|
||||||
已选择“全部时间”,确认后会按默认导出全部时间;下方日期仅用于切换为自定义范围时预览。
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="time-range-dialog-actions">
|
<div className="time-range-dialog-actions">
|
||||||
<button type="button" className="secondary-btn" onClick={closeTimeRangeDialog}>
|
<button type="button" className="secondary-btn" onClick={closeTimeRangeDialog}>
|
||||||
取消
|
取消
|
||||||
|
|||||||
Reference in New Issue
Block a user