From bd3e9a63b7e026aa4a86b6d93920daa7c171ab79 Mon Sep 17 00:00:00 2001 From: xuncha <1658671838@qq.com> Date: Sat, 10 Jan 2026 23:41:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=9F=E4=B8=80ui=E9=A3=8E=E6=A0=BC?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D=E5=AF=BC=E5=87=BA=E5=BD=93=E5=A4=A9?= =?UTF-8?q?=E7=9A=84=E8=AE=B0=E5=BD=95=E4=B8=BA=E7=A9=BA=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/ExportPage.scss | 225 ++++++++++++++++++++++++++++++-------- src/pages/ExportPage.tsx | 196 ++++++++++++++++++++++++++------- 2 files changed, 341 insertions(+), 80 deletions(-) diff --git a/src/pages/ExportPage.scss b/src/pages/ExportPage.scss index 5734746..596fcd4 100644 --- a/src/pages/ExportPage.scss +++ b/src/pages/ExportPage.scss @@ -379,29 +379,21 @@ border-radius: 10px; font-size: 14px; color: var(--text-primary); + cursor: pointer; + transition: all 0.2s; + + &:hover { + background: var(--bg-hover); + } svg { color: var(--text-tertiary); + flex-shrink: 0; } span { flex: 1; } - - .change-btn { - background: none; - border: none; - padding: 4px; - cursor: pointer; - color: var(--text-tertiary); - display: flex; - align-items: center; - justify-content: center; - - &:hover { - color: var(--text-primary); - } - } } .media-options { @@ -655,7 +647,8 @@ padding: 28px 32px; border-radius: 16px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25); - min-width: 400px; + min-width: 420px; + max-width: 500px; h3 { font-size: 18px; @@ -664,41 +657,178 @@ margin: 0 0 20px; } - .date-inputs { + .quick-select { display: flex; - flex-direction: column; - gap: 16px; - margin-bottom: 24px; - } - - .date-input-group { - display: flex; - flex-direction: column; gap: 8px; + margin-bottom: 20px; - label { - font-size: 13px; - font-weight: 500; - color: var(--text-secondary); - } - - input[type="date"] { - padding: 10px 14px; + .quick-btn { + flex: 1; + padding: 10px 12px; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 8px; - font-size: 14px; + font-size: 13px; + font-weight: 500; color: var(--text-primary); - outline: none; - transition: border-color 0.2s; + cursor: pointer; + transition: all 0.2s; - &:focus { + &:hover { + background: var(--bg-hover); border-color: var(--primary); + color: var(--primary); } - &::-webkit-calendar-picker-indicator { - cursor: pointer; - filter: var(--icon-filter, none); + &:active { + transform: scale(0.98); + } + } + } + + .date-display { + display: flex; + align-items: center; + gap: 16px; + padding: 20px; + background: var(--bg-secondary); + border-radius: 12px; + margin-bottom: 24px; + + .date-display-item { + flex: 1; + display: flex; + flex-direction: column; + gap: 6px; + padding: 8px 12px; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s; + + &:hover { + background: rgba(var(--primary-rgb), 0.05); + } + + &.active { + background: rgba(var(--primary-rgb), 0.1); + border: 1px solid var(--primary); + } + + .date-label { + font-size: 12px; + color: var(--text-tertiary); + font-weight: 500; + } + + .date-value { + font-size: 15px; + color: var(--text-primary); + font-weight: 600; + } + } + + .date-separator { + font-size: 14px; + color: var(--text-tertiary); + padding: 0 4px; + } + } + + .calendar-container { + margin-bottom: 20px; + } + + .calendar-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + padding: 0 4px; + + .calendar-nav-btn { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + cursor: pointer; + color: var(--text-secondary); + transition: all 0.2s; + + &:hover { + background: var(--bg-hover); + border-color: var(--primary); + color: var(--primary); + } + + &:active { + transform: scale(0.95); + } + } + + .calendar-month { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); + } + } + + .calendar-weekdays { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 4px; + margin-bottom: 8px; + + .calendar-weekday { + text-align: center; + font-size: 12px; + font-weight: 500; + color: var(--text-tertiary); + padding: 8px 0; + } + } + + .calendar-days { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 4px; + + .calendar-day { + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + color: var(--text-primary); + border-radius: 8px; + cursor: pointer; + transition: all 0.2s; + position: relative; + + &.empty { + cursor: default; + } + + &:not(.empty):hover { + background: var(--bg-hover); + } + + &.in-range { + background: rgba(var(--primary-rgb), 0.08); + } + + &.start, + &.end { + background: var(--primary); + color: #fff; + font-weight: 600; + + &:hover { + background: var(--primary-hover); + } } } } @@ -715,6 +845,10 @@ font-weight: 500; cursor: pointer; transition: all 0.2s; + + &:active { + transform: scale(0.98); + } } .cancel-btn { @@ -741,6 +875,11 @@ } @keyframes exportSpin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/src/pages/ExportPage.tsx b/src/pages/ExportPage.tsx index 11f34c7..006db07 100644 --- a/src/pages/ExportPage.tsx +++ b/src/pages/ExportPage.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react' -import { Search, Download, FolderOpen, RefreshCw, Check, Calendar, FileJson, FileText, Table, Loader2, X, ChevronDown, FileSpreadsheet, Database, FileCode, CheckCircle, XCircle, ExternalLink } from 'lucide-react' +import { Search, Download, FolderOpen, RefreshCw, Check, Calendar, FileJson, FileText, Table, Loader2, X, ChevronDown, ChevronLeft, ChevronRight, FileSpreadsheet, Database, FileCode, CheckCircle, XCircle, ExternalLink } from 'lucide-react' import * as configService from '../services/config' import './ExportPage.scss' @@ -35,6 +35,8 @@ function ExportPage() { const [exportProgress, setExportProgress] = useState({ current: 0, total: 0, currentName: '' }) const [exportResult, setExportResult] = useState(null) const [showDatePicker, setShowDatePicker] = useState(false) + const [calendarDate, setCalendarDate] = useState(new Date()) + const [selectingStart, setSelectingStart] = useState(true) const [options, setOptions] = useState({ format: 'chatlab', @@ -143,7 +145,8 @@ function ExportPage() { format: options.format, dateRange: options.useAllTime ? null : options.dateRange ? { start: Math.floor(options.dateRange.start.getTime() / 1000), - end: Math.floor(options.dateRange.end.getTime() / 1000) + // 将结束日期设置为当天的 23:59:59,以包含当天的所有消息 + end: Math.floor(new Date(options.dateRange.end.getFullYear(), options.dateRange.end.getMonth(), options.dateRange.end.getDate(), 23, 59, 59).getTime() / 1000) } : null } @@ -165,6 +168,54 @@ function ExportPage() { } } + const getDaysInMonth = (date: Date) => { + const year = date.getFullYear() + const month = date.getMonth() + return new Date(year, month + 1, 0).getDate() + } + + const getFirstDayOfMonth = (date: Date) => { + const year = date.getFullYear() + const month = date.getMonth() + return new Date(year, month, 1).getDay() + } + + const generateCalendar = () => { + const daysInMonth = getDaysInMonth(calendarDate) + const firstDay = getFirstDayOfMonth(calendarDate) + const days: (number | null)[] = [] + + for (let i = 0; i < firstDay; i++) { + days.push(null) + } + + for (let i = 1; i <= daysInMonth; i++) { + days.push(i) + } + + return days + } + + const handleDateSelect = (day: number) => { + const year = calendarDate.getFullYear() + const month = calendarDate.getMonth() + const selectedDate = new Date(year, month, day) + + if (selectingStart) { + setOptions({ + ...options, + dateRange: options.dateRange ? { ...options.dateRange, start: selectedDate } : { start: selectedDate, end: new Date() } + }) + setSelectingStart(false) + } else { + setOptions({ + ...options, + dateRange: options.dateRange ? { ...options.dateRange, end: selectedDate } : { start: new Date(), end: selectedDate } + }) + setSelectingStart(true) + } + } + const formatOptions = [ { value: 'chatlab', label: 'ChatLab', icon: FileCode, desc: '标准格式,支持其他软件导入' }, { value: 'chatlab-jsonl', label: 'ChatLab JSONL', icon: FileCode, desc: '流式格式,适合大量消息' }, @@ -279,12 +330,10 @@ function ExportPage() { 导出全部时间 {!options.useAllTime && options.dateRange && ( -
+
setShowDatePicker(true)}> {formatDate(options.dateRange.start)} - {formatDate(options.dateRange.end)} - +
)}
@@ -377,38 +426,111 @@ function ExportPage() {
setShowDatePicker(false)}>
e.stopPropagation()}>

选择时间范围

-
-
- - { - const newDate = new Date(e.target.value) - if (options.dateRange) { - setOptions({ - ...options, - dateRange: { ...options.dateRange, start: newDate } - }) - } - }} - /> +
+ + + +
+
+
setSelectingStart(true)} + > + 开始日期 + + {options.dateRange?.start.toLocaleDateString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + })} +
-
- - { - const newDate = new Date(e.target.value) - if (options.dateRange) { - setOptions({ - ...options, - dateRange: { ...options.dateRange, end: newDate } - }) - } - }} - /> + +
setSelectingStart(false)} + > + 结束日期 + + {options.dateRange?.end.toLocaleDateString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + })} + +
+
+
+
+ + + {calendarDate.getFullYear()}年{calendarDate.getMonth() + 1}月 + + +
+
+ {['日', '一', '二', '三', '四', '五', '六'].map(day => ( +
{day}
+ ))} +
+
+ {generateCalendar().map((day, index) => { + if (day === null) { + return
+ } + + const currentDate = new Date(calendarDate.getFullYear(), calendarDate.getMonth(), day) + const isStart = options.dateRange?.start.toDateString() === currentDate.toDateString() + const isEnd = options.dateRange?.end.toDateString() === currentDate.toDateString() + const isInRange = options.dateRange && currentDate >= options.dateRange.start && currentDate <= options.dateRange.end + + return ( +
handleDateSelect(day)} + > + {day} +
+ ) + })}