feat(export): 多会话导出布局选择与无媒体直出

- 多会话媒体导出支持共享/分会话目录
- 无媒体导出时直接输出到目标目录
This commit is contained in:
xuncha
2026-01-21 19:37:05 +08:00
parent 076c008329
commit 97c1aa582d
4 changed files with 203 additions and 39 deletions

View File

@@ -31,6 +31,8 @@ interface ExportResult {
error?: string
}
type SessionLayout = 'shared' | 'per-session'
function ExportPage() {
const [sessions, setSessions] = useState<ChatSession[]>([])
const [filteredSessions, setFilteredSessions] = useState<ChatSession[]>([])
@@ -44,6 +46,7 @@ function ExportPage() {
const [showDatePicker, setShowDatePicker] = useState(false)
const [calendarDate, setCalendarDate] = useState(new Date())
const [selectingStart, setSelectingStart] = useState(true)
const [showMediaLayoutPrompt, setShowMediaLayoutPrompt] = useState(false)
const [options, setOptions] = useState<ExportOptions>({
format: 'excel',
@@ -212,7 +215,7 @@ function ExportPage() {
}
}
const startExport = async () => {
const runExport = async (sessionLayout: SessionLayout) => {
if (selectedSessions.size === 0 || !exportFolder) return
setIsExporting(true)
@@ -228,11 +231,12 @@ function ExportPage() {
exportImages: options.exportMedia && options.exportImages,
exportVoices: options.exportMedia && options.exportVoices,
exportEmojis: options.exportMedia && options.exportEmojis,
exportVoiceAsText: options.exportVoiceAsText, // 独立于 exportMedia
exportVoiceAsText: options.exportVoiceAsText, // ?????????exportMedia
excelCompactColumns: options.excelCompactColumns,
sessionLayout,
dateRange: options.useAllTime ? null : options.dateRange ? {
start: Math.floor(options.dateRange.start.getTime() / 1000),
// 将结束日期设置为当天的 23:59:59,以包含当天的所有消息
// ?????????????????????????????????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
}
@@ -245,16 +249,28 @@ function ExportPage() {
)
setExportResult(result)
} else {
setExportResult({ success: false, error: `${options.format.toUpperCase()} 格式导出功能开发中...` })
setExportResult({ success: false, error: `${options.format.toUpperCase()} ???????????????????????????...` })
}
} catch (e) {
console.error('导出失败:', e)
console.error('????????????:', e)
setExportResult({ success: false, error: String(e) })
} finally {
setIsExporting(false)
}
}
const startExport = () => {
if (selectedSessions.size === 0 || !exportFolder) return
if (options.exportMedia && selectedSessions.size > 1) {
setShowMediaLayoutPrompt(true)
return
}
const layout: SessionLayout = options.exportMedia ? 'per-session' : 'shared'
runExport(layout)
}
const getDaysInMonth = (date: Date) => {
const year = date.getFullYear()
const month = date.getMonth()
@@ -613,6 +629,43 @@ function ExportPage() {
</div>
</div>
{/* 媒体导出布局选择弹窗 */}
{showMediaLayoutPrompt && (
<div className="export-overlay" onClick={() => setShowMediaLayoutPrompt(false)}>
<div className="export-layout-modal" onClick={e => e.stopPropagation()}>
<h3></h3>
<p className="layout-subtitle"></p>
<div className="layout-options">
<button
className="layout-option-btn primary"
onClick={() => {
setShowMediaLayoutPrompt(false)
runExport('shared')
}}
>
<span className="layout-title"></span>
<span className="layout-desc"> media </span>
</button>
<button
className="layout-option-btn"
onClick={() => {
setShowMediaLayoutPrompt(false)
runExport('per-session')
}}
>
<span className="layout-title"></span>
<span className="layout-desc"></span>
</button>
</div>
<div className="layout-actions">
<button className="layout-cancel-btn" onClick={() => setShowMediaLayoutPrompt(false)}>
</button>
</div>
</div>
</div>
)}
{/* 导出进度弹窗 */}
{isExporting && (
<div className="export-overlay">