feat:添加导出文件

This commit is contained in:
姜北尘
2026-04-04 19:45:05 +08:00
parent e628154b78
commit 64010ad86b
7 changed files with 308 additions and 31 deletions

View File

@@ -67,7 +67,7 @@ import './ExportPage.scss'
type ConversationTab = 'private' | 'group' | 'official' | 'former_friend'
type TaskStatus = 'queued' | 'running' | 'success' | 'error'
type TaskScope = 'single' | 'multi' | 'content' | 'sns'
type ContentType = 'text' | 'voice' | 'image' | 'video' | 'emoji'
type ContentType = 'text' | 'voice' | 'image' | 'video' | 'emoji' | 'file'
type ContentCardType = ContentType | 'sns'
type SnsRankMode = 'likes' | 'comments'
@@ -88,6 +88,8 @@ interface ExportOptions {
exportVoices: boolean
exportVideos: boolean
exportEmojis: boolean
exportFiles: boolean
maxFileSizeMb: number
exportVoiceAsText: boolean
excelCompactColumns: boolean
txtColumns: string[]
@@ -195,7 +197,8 @@ const contentTypeLabels: Record<ContentType, string> = {
voice: '语音',
image: '图片',
video: '视频',
emoji: '表情包'
emoji: '表情包',
file: '文件'
}
const backgroundTaskSourceLabels: Record<string, string> = {
@@ -1598,7 +1601,8 @@ function ExportPage() {
images: true,
videos: true,
voices: true,
emojis: true
emojis: true,
files: true
})
const [exportDefaultVoiceAsText, setExportDefaultVoiceAsText] = useState(false)
const [exportDefaultExcelCompactColumns, setExportDefaultExcelCompactColumns] = useState(true)
@@ -1617,7 +1621,9 @@ function ExportPage() {
exportImages: true,
exportVoices: true,
exportVideos: true,
exportEmojis: true,
exportEmojis: true,
exportFiles: true,
maxFileSizeMb: 200,
exportVoiceAsText: false,
excelCompactColumns: true,
txtColumns: defaultTxtColumns,
@@ -2281,7 +2287,8 @@ function ExportPage() {
images: true,
videos: true,
voices: true,
emojis: true
emojis: true,
files: true
})
setExportDefaultVoiceAsText(savedVoiceAsText ?? false)
setExportDefaultExcelCompactColumns(savedExcelCompactColumns ?? true)
@@ -2310,12 +2317,14 @@ function ExportPage() {
(savedMedia?.images ?? prev.exportImages) ||
(savedMedia?.voices ?? prev.exportVoices) ||
(savedMedia?.videos ?? prev.exportVideos) ||
(savedMedia?.emojis ?? prev.exportEmojis)
(savedMedia?.emojis ?? prev.exportEmojis) ||
(savedMedia?.files ?? prev.exportFiles)
),
exportImages: savedMedia?.images ?? prev.exportImages,
exportVoices: savedMedia?.voices ?? prev.exportVoices,
exportVideos: savedMedia?.videos ?? prev.exportVideos,
exportEmojis: savedMedia?.emojis ?? prev.exportEmojis,
exportFiles: savedMedia?.files ?? prev.exportFiles,
exportVoiceAsText: savedVoiceAsText ?? prev.exportVoiceAsText,
excelCompactColumns: savedExcelCompactColumns ?? prev.excelCompactColumns,
txtColumns,
@@ -4088,12 +4097,15 @@ function ExportPage() {
exportDefaultMedia.images ||
exportDefaultMedia.voices ||
exportDefaultMedia.videos ||
exportDefaultMedia.emojis
exportDefaultMedia.emojis ||
exportDefaultMedia.files
),
exportImages: exportDefaultMedia.images,
exportVoices: exportDefaultMedia.voices,
exportVideos: exportDefaultMedia.videos,
exportEmojis: exportDefaultMedia.emojis,
exportFiles: exportDefaultMedia.files,
maxFileSizeMb: prev.maxFileSizeMb,
exportVoiceAsText: exportDefaultVoiceAsText,
excelCompactColumns: exportDefaultExcelCompactColumns,
exportConcurrency: exportDefaultConcurrency,
@@ -4111,12 +4123,14 @@ function ExportPage() {
next.exportVoices = false
next.exportVideos = false
next.exportEmojis = false
next.exportFiles = false
} else {
next.exportMedia = true
next.exportImages = payload.contentType === 'image'
next.exportVoices = payload.contentType === 'voice'
next.exportVideos = payload.contentType === 'video'
next.exportEmojis = payload.contentType === 'emoji'
next.exportFiles = payload.contentType === 'file'
next.exportVoiceAsText = false
}
}
@@ -4335,7 +4349,13 @@ function ExportPage() {
const buildExportOptions = (scope: TaskScope, contentType?: ContentType): ElectronExportOptions => {
const sessionLayout: SessionLayout = writeLayout === 'C' ? 'per-session' : 'shared'
const exportMediaEnabled = Boolean(options.exportImages || options.exportVoices || options.exportVideos || options.exportEmojis)
const exportMediaEnabled = Boolean(
options.exportImages ||
options.exportVoices ||
options.exportVideos ||
options.exportEmojis ||
options.exportFiles
)
const base: ElectronExportOptions = {
format: options.format,
@@ -4345,6 +4365,8 @@ function ExportPage() {
exportVoices: options.exportVoices,
exportVideos: options.exportVideos,
exportEmojis: options.exportEmojis,
exportFiles: options.exportFiles,
maxFileSizeMb: options.maxFileSizeMb,
exportVoiceAsText: options.exportVoiceAsText,
excelCompactColumns: options.excelCompactColumns,
txtColumns: options.txtColumns,
@@ -4375,7 +4397,8 @@ function ExportPage() {
exportImages: false,
exportVoices: false,
exportVideos: false,
exportEmojis: false
exportEmojis: false,
exportFiles: false
}
}
@@ -4387,6 +4410,7 @@ function ExportPage() {
exportVoices: contentType === 'voice',
exportVideos: contentType === 'video',
exportEmojis: contentType === 'emoji',
exportFiles: contentType === 'file',
exportVoiceAsText: false
}
}
@@ -4452,6 +4476,7 @@ function ExportPage() {
if (opts.exportVoices) labels.push('语音')
if (opts.exportVideos) labels.push('视频')
if (opts.exportEmojis) labels.push('表情包')
if (opts.exportFiles) labels.push('文件')
}
return Array.from(new Set(labels)).join('、')
}, [])
@@ -4507,6 +4532,7 @@ function ExportPage() {
if (opts.exportImages) types.push('image')
if (opts.exportVideos) types.push('video')
if (opts.exportEmojis) types.push('emoji')
if (opts.exportFiles) types.push('file')
}
return types
}
@@ -4937,7 +4963,8 @@ function ExportPage() {
images: options.exportImages,
voices: options.exportVoices,
videos: options.exportVideos,
emojis: options.exportEmojis
emojis: options.exportEmojis,
files: options.exportFiles
})
await configService.setExportDefaultVoiceAsText(options.exportVoiceAsText)
await configService.setExportDefaultExcelCompactColumns(options.excelCompactColumns)
@@ -6955,11 +6982,12 @@ function ExportPage() {
setExportDefaultMedia(mediaPatch)
setOptions(prev => ({
...prev,
exportMedia: Boolean(mediaPatch.images || mediaPatch.voices || mediaPatch.videos || mediaPatch.emojis),
exportMedia: Boolean(mediaPatch.images || mediaPatch.voices || mediaPatch.videos || mediaPatch.emojis || mediaPatch.files),
exportImages: mediaPatch.images,
exportVoices: mediaPatch.voices,
exportVideos: mediaPatch.videos,
exportEmojis: mediaPatch.emojis
exportEmojis: mediaPatch.emojis,
exportFiles: mediaPatch.files
}))
}
if (typeof patch.voiceAsText === 'boolean') {
@@ -8159,16 +8187,36 @@ function ExportPage() {
<label><input type="checkbox" checked={options.exportVoices} onChange={event => setOptions(prev => ({ ...prev, exportVoices: event.target.checked }))} /> </label>
<label><input type="checkbox" checked={options.exportVideos} onChange={event => setOptions(prev => ({ ...prev, exportVideos: event.target.checked }))} /> </label>
<label><input type="checkbox" checked={options.exportEmojis} onChange={event => setOptions(prev => ({ ...prev, exportEmojis: event.target.checked }))} /> </label>
<label><input type="checkbox" checked={options.exportFiles} onChange={event => setOptions(prev => ({ ...prev, exportFiles: event.target.checked }))} /> </label>
</>
)}
</div>
{exportDialog.scope === 'sns' && (
<div className="format-note"></div>
{exportDialog.scope !== 'sns' && options.exportFiles && (
<div className="format-note">使 MD5 </div>
)}
</div>
)}
{shouldShowImageDeepSearchToggle && (
{shouldShowMediaSection && exportDialog.scope !== 'sns' && options.exportFiles && (
<div className="dialog-section">
<h4></h4>
<div className="format-note">0 </div>
<div className="dialog-input-row">
<input
type="number"
min={0}
step={10}
value={options.maxFileSizeMb}
onChange={event => {
const raw = Number(event.target.value)
setOptions(prev => ({ ...prev, maxFileSizeMb: Number.isFinite(raw) ? Math.max(0, Math.floor(raw)) : 0 }))
}}
/>
<span>MB</span>
</div>
</div>
)}
<div className="dialog-section">
<div className="dialog-switch-row">
<div className="dialog-switch-copy">