diff --git a/src/pages/ExportPage.scss b/src/pages/ExportPage.scss index 6c5ecd2..3db772c 100644 --- a/src/pages/ExportPage.scss +++ b/src/pages/ExportPage.scss @@ -2071,6 +2071,42 @@ } } +.section-header-action { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + + h4 { + margin-bottom: 0; + } +} + +.time-range-trigger { + border: 1px solid var(--border-color); + background: var(--bg-primary); + border-radius: 999px; + color: var(--text-primary); + font-size: 12px; + min-height: 32px; + padding: 0 10px; + display: inline-flex; + align-items: center; + gap: 8px; + cursor: pointer; + + &:hover { + border-color: rgba(var(--primary-rgb), 0.45); + color: var(--primary); + } + + .time-range-arrow { + color: var(--text-tertiary); + font-weight: 700; + line-height: 1; + } +} + .scope-tag-row { display: flex; align-items: center; @@ -2301,6 +2337,96 @@ } } +.time-range-dialog-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.35); + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + z-index: 1015; +} + +.time-range-dialog { + width: min(520px, calc(100vw - 32px)); + max-height: calc(100vh - 64px); + overflow-y: auto; + border-radius: 12px; + border: 1px solid var(--border-color); + background: var(--card-bg); + padding: 12px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.time-range-dialog-header { + display: flex; + align-items: center; + justify-content: space-between; + + h4 { + margin: 0; + font-size: 14px; + color: var(--text-primary); + } +} + +.time-range-preset-list { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.time-range-preset-item { + border: 1px solid var(--border-color); + border-radius: 10px; + background: var(--bg-secondary); + color: var(--text-primary); + min-height: 38px; + padding: 0 10px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + font-size: 12px; + cursor: pointer; + + &.active { + border-color: var(--primary); + background: rgba(var(--primary-rgb), 0.08); + color: var(--primary); + } +} + +.time-range-custom-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + + label { + display: flex; + flex-direction: column; + gap: 5px; + font-size: 12px; + color: var(--text-secondary); + } + + input { + border-radius: 8px; + border: 1px solid var(--border-color); + background: var(--bg-primary); + color: var(--text-primary); + padding: 8px; + } +} + +.time-range-dialog-actions { + display: flex; + justify-content: flex-end; +} + @keyframes exportSpin { from { transform: rotate(0deg); @@ -2438,6 +2564,14 @@ grid-template-columns: 1fr; } + .time-range-preset-list { + grid-template-columns: 1fr; + } + + .time-range-custom-row { + grid-template-columns: 1fr; + } + .task-center-modal-overlay { padding: 12px 10px; } diff --git a/src/pages/ExportPage.tsx b/src/pages/ExportPage.tsx index 75071d4..8f42aaf 100644 --- a/src/pages/ExportPage.tsx +++ b/src/pages/ExportPage.tsx @@ -49,6 +49,7 @@ type ContentCardType = ContentType | 'sns' type SessionLayout = 'shared' | 'per-session' type DisplayNamePreference = 'group-nickname' | 'remark' | 'nickname' +type DateRangePreset = 'all' | 'today' | 'yesterday' | 'last3days' | 'last7days' | 'last30days' | 'custom' type TextExportFormat = 'chatlab' | 'chatlab-jsonl' | 'json' | 'arkme-json' | 'html' | 'txt' | 'excel' | 'weclone' | 'sql' type SnsTimelineExportFormat = 'json' | 'html' | 'arkmejson' @@ -415,6 +416,52 @@ const formatRecentExportTime = (timestamp?: number, now = Date.now()): string => return formatAbsoluteDate(timestamp) } +const startOfDay = (date: Date): Date => { + const next = new Date(date) + next.setHours(0, 0, 0, 0) + return next +} + +const endOfDay = (date: Date): Date => { + const next = new Date(date) + next.setHours(23, 59, 59, 999) + return next +} + +const createDefaultDateRange = (): { start: Date; end: Date } => { + const now = new Date() + return { + start: startOfDay(now), + end: now + } +} + +const createDateRangeByPreset = ( + preset: Exclude, + now = new Date() +): { start: Date; end: Date } => { + const end = new Date(now) + const baseStart = startOfDay(now) + + if (preset === 'today') { + return { start: baseStart, end } + } + + if (preset === 'yesterday') { + const yesterday = new Date(baseStart) + yesterday.setDate(yesterday.getDate() - 1) + return { + start: yesterday, + end: endOfDay(yesterday) + } + } + + const daysBack = preset === 'last3days' ? 2 : preset === 'last7days' ? 6 : 29 + const start = new Date(baseStart) + start.setDate(start.getDate() - daysBack) + return { start, end } +} + const formatDateInputValue = (date: Date): string => { const y = date.getFullYear() const m = `${date.getMonth() + 1}`.padStart(2, '0') @@ -1108,6 +1155,8 @@ function ExportPage() { const [snsExportImages, setSnsExportImages] = useState(false) const [snsExportLivePhotos, setSnsExportLivePhotos] = useState(false) const [snsExportVideos, setSnsExportVideos] = useState(false) + const [isTimeRangeDialogOpen, setIsTimeRangeDialogOpen] = useState(false) + const [timeRangePreset, setTimeRangePreset] = useState('all') const [options, setOptions] = useState({ format: 'json', @@ -2175,14 +2224,11 @@ function ExportPage() { const openExportDialog = useCallback((payload: Omit) => { setExportDialog({ open: true, ...payload }) + setIsTimeRangeDialogOpen(false) + setTimeRangePreset('all') setOptions(prev => { - const nextDateRange = prev.dateRange ?? (() => { - const now = new Date() - const start = new Date(now) - start.setHours(0, 0, 0, 0) - return { start, end: now } - })() + const nextDateRange = prev.dateRange ?? createDefaultDateRange() const next: ExportOptions = { ...prev, @@ -2218,8 +2264,84 @@ function ExportPage() { const closeExportDialog = useCallback(() => { setExportDialog(prev => ({ ...prev, open: false })) + setIsTimeRangeDialogOpen(false) }, []) + const applyTimeRangePreset = useCallback((preset: Exclude) => { + setTimeRangePreset(preset) + if (preset === 'all') { + setOptions(prev => ({ + ...prev, + useAllTime: true, + dateRange: prev.dateRange ?? createDefaultDateRange() + })) + return + } + const range = createDateRangeByPreset(preset) + setOptions(prev => ({ + ...prev, + useAllTime: false, + dateRange: range + })) + }, []) + + const activateCustomTimeRange = useCallback(() => { + setTimeRangePreset('custom') + setOptions(prev => ({ + ...prev, + useAllTime: false, + dateRange: prev.dateRange ?? createDefaultDateRange() + })) + }, []) + + const updateCustomDateRangeStart = useCallback((value: string) => { + const start = parseDateInput(value, false) + setTimeRangePreset('custom') + setOptions(prev => ({ + ...prev, + useAllTime: false, + dateRange: prev.dateRange + ? { + start, + end: prev.dateRange.end < start ? parseDateInput(value, true) : prev.dateRange.end + } + : { start, end: new Date() } + })) + }, []) + + const updateCustomDateRangeEnd = useCallback((value: string) => { + const end = parseDateInput(value, true) + setTimeRangePreset('custom') + setOptions(prev => ({ + ...prev, + useAllTime: false, + dateRange: prev.dateRange + ? { + start: prev.dateRange.start > end ? parseDateInput(value, false) : prev.dateRange.start, + end + } + : { start: new Date(), end } + })) + }, []) + + const timeRangeSummaryLabel = useMemo(() => { + if (options.useAllTime) return '默认导出全部时间' + if (timeRangePreset === 'today') return '今天' + if (timeRangePreset === 'yesterday') return '昨天' + if (timeRangePreset === 'last3days') return '最近三天' + if (timeRangePreset === 'last7days') return '最近一周' + if (timeRangePreset === 'last30days') return '最近一个月' + if (options.dateRange) { + return `${formatDateInputValue(options.dateRange.start)} 至 ${formatDateInputValue(options.dateRange.end)}` + } + return '自定义时间范围' + }, [options.useAllTime, options.dateRange, timeRangePreset]) + + const isTimeRangePresetActive = useCallback((preset: DateRangePreset): boolean => { + if (preset === 'all') return options.useAllTime + return !options.useAllTime && timeRangePreset === preset + }, [options.useAllTime, timeRangePreset]) + useEffect(() => { const unsubscribe = onOpenSingleExport((payload) => { void (async () => { @@ -4351,57 +4473,17 @@ function ExportPage() { )}
-

时间范围

-
- 导出全部时间 - +
+

时间范围

+
- - {!options.useAllTime && options.dateRange && ( -
- - -
- )}
{shouldShowMediaSection && ( @@ -4462,6 +4544,83 @@ function ExportPage() { 创建导出任务
+ + {isTimeRangeDialogOpen && ( +
setIsTimeRangeDialogOpen(false)}> +
event.stopPropagation()}> +
+

时间范围设置

+ +
+ +
+ {([ + { value: 'all', label: '默认导出全部时间' }, + { value: 'today', label: '今天' }, + { value: 'yesterday', label: '昨天' }, + { value: 'last3days', label: '最近三天' }, + { value: 'last7days', label: '最近一周' }, + { value: 'last30days', label: '最近一个月' }, + { value: 'custom', label: '自定义' } + ] as Array<{ value: DateRangePreset; label: string }>).map((preset) => { + const isActive = isTimeRangePresetActive(preset.value) + return ( + + ) + })} +
+ + {!options.useAllTime && timeRangePreset === 'custom' && options.dateRange && ( +
+ + +
+ )} + +
+ +
+
+
+ )} , document.body