mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-26 07:26:46 +00:00
fix(export): 修复朋友圈导出控制按钮
This commit is contained in:
@@ -2729,6 +2729,54 @@
|
|||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.export-progress-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-progress-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 82px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: var(--hover-bg);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
border-color: color-mix(in srgb, var(--primary) 36%, var(--border-color));
|
||||||
|
background: rgba(var(--primary-rgb), 0.1);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
border-color: color-mix(in srgb, #ff4d4f 36%, var(--border-color));
|
||||||
|
background: color-mix(in srgb, #ff4d4f 10%, var(--bg-secondary));
|
||||||
|
color: #d9363e;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.export-result {
|
.export-result {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useLayoutEffect, useState, useRef, useCallback, useMemo } from 'react'
|
import { useEffect, useLayoutEffect, useState, useRef, useCallback, useMemo } from 'react'
|
||||||
import { RefreshCw, Search, X, Download, FolderOpen, FileJson, FileText, Image, CheckCircle, AlertCircle, Calendar, Info, Shield, ShieldOff, Loader2 } from 'lucide-react'
|
import { RefreshCw, Search, X, Download, FolderOpen, FileJson, FileText, Image, CheckCircle, AlertCircle, Calendar, Info, Shield, ShieldOff, Loader2, Pause, Play, Square } from 'lucide-react'
|
||||||
import './SnsPage.scss'
|
import './SnsPage.scss'
|
||||||
import { SnsPost } from '../types/sns'
|
import { SnsPost } from '../types/sns'
|
||||||
import { SnsPostItem } from '../components/Sns/SnsPostItem'
|
import { SnsPostItem } from '../components/Sns/SnsPostItem'
|
||||||
@@ -64,10 +64,42 @@ interface SnsOverviewStats {
|
|||||||
|
|
||||||
type OverviewStatsStatus = 'loading' | 'ready' | 'error'
|
type OverviewStatsStatus = 'loading' | 'ready' | 'error'
|
||||||
type SnsExportScope = { kind: 'all' } | { kind: 'selected'; usernames: string[] }
|
type SnsExportScope = { kind: 'all' } | { kind: 'selected'; usernames: string[] }
|
||||||
|
type SnsExportTaskStatus = 'idle' | 'running' | 'pause_requested' | 'paused' | 'cancel_requested'
|
||||||
|
|
||||||
|
interface SnsExportProgress {
|
||||||
|
current: number
|
||||||
|
total: number
|
||||||
|
status: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SnsExportResult {
|
||||||
|
success: boolean
|
||||||
|
filePath?: string
|
||||||
|
postCount?: number
|
||||||
|
mediaCount?: number
|
||||||
|
paused?: boolean
|
||||||
|
stopped?: boolean
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SnsExportRequest {
|
||||||
|
taskId: string
|
||||||
|
outputDir: string
|
||||||
|
format: 'json' | 'html' | 'arkmejson'
|
||||||
|
usernames?: string[]
|
||||||
|
keyword?: string
|
||||||
|
exportImages: boolean
|
||||||
|
exportLivePhotos: boolean
|
||||||
|
exportVideos: boolean
|
||||||
|
startTime?: number
|
||||||
|
endTime?: number
|
||||||
|
}
|
||||||
|
|
||||||
const SIDEBAR_USER_PROFILE_CACHE_KEY = 'sidebar_user_profile_cache_v1'
|
const SIDEBAR_USER_PROFILE_CACHE_KEY = 'sidebar_user_profile_cache_v1'
|
||||||
const SNS_CACHE_MIGRATION_PROMPT_SESSION_KEY = 'sns_cache_migration_prompted_v1'
|
const SNS_CACHE_MIGRATION_PROMPT_SESSION_KEY = 'sns_cache_migration_prompted_v1'
|
||||||
|
|
||||||
|
const createSnsExportTaskId = (): string => `sns-export-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||||||
|
|
||||||
interface SnsCacheMigrationItem {
|
interface SnsCacheMigrationItem {
|
||||||
label: string
|
label: string
|
||||||
sourceDir: string
|
sourceDir: string
|
||||||
@@ -179,8 +211,9 @@ export default function SnsPage() {
|
|||||||
() => createExportDateRangeSelectionFromPreset('all')
|
() => createExportDateRangeSelectionFromPreset('all')
|
||||||
)
|
)
|
||||||
const [isExporting, setIsExporting] = useState(false)
|
const [isExporting, setIsExporting] = useState(false)
|
||||||
const [exportProgress, setExportProgress] = useState<{ current: number; total: number; status: string } | null>(null)
|
const [exportTaskStatus, setExportTaskStatus] = useState<SnsExportTaskStatus>('idle')
|
||||||
const [exportResult, setExportResult] = useState<{ success: boolean; filePath?: string; postCount?: number; mediaCount?: number; error?: string } | null>(null)
|
const [exportProgress, setExportProgress] = useState<SnsExportProgress | null>(null)
|
||||||
|
const [exportResult, setExportResult] = useState<SnsExportResult | null>(null)
|
||||||
const [refreshSpin, setRefreshSpin] = useState(false)
|
const [refreshSpin, setRefreshSpin] = useState(false)
|
||||||
const [isExportDateRangeDialogOpen, setIsExportDateRangeDialogOpen] = useState(false)
|
const [isExportDateRangeDialogOpen, setIsExportDateRangeDialogOpen] = useState(false)
|
||||||
|
|
||||||
@@ -211,6 +244,8 @@ export default function SnsPage() {
|
|||||||
const snsUserPostCountsCacheScopeKeyRef = useRef('')
|
const snsUserPostCountsCacheScopeKeyRef = useRef('')
|
||||||
const activeContactsLoadTaskIdRef = useRef<string | null>(null)
|
const activeContactsLoadTaskIdRef = useRef<string | null>(null)
|
||||||
const activeContactsCountTaskIdRef = useRef<string | null>(null)
|
const activeContactsCountTaskIdRef = useRef<string | null>(null)
|
||||||
|
const activeExportTaskIdRef = useRef<string | null>(null)
|
||||||
|
const activeExportRequestRef = useRef<SnsExportRequest | null>(null)
|
||||||
const scrollAdjustmentRef = useRef<{ scrollHeight: number; scrollTop: number } | null>(null)
|
const scrollAdjustmentRef = useRef<{ scrollHeight: number; scrollTop: number } | null>(null)
|
||||||
const pendingResetFeedRef = useRef(false)
|
const pendingResetFeedRef = useRef(false)
|
||||||
const contactsLoadTokenRef = useRef(0)
|
const contactsLoadTokenRef = useRef(0)
|
||||||
@@ -465,7 +500,11 @@ export default function SnsPage() {
|
|||||||
: overviewStatsStatus === 'loading' || contactsLoading
|
: overviewStatsStatus === 'loading' || contactsLoading
|
||||||
)
|
)
|
||||||
|
|
||||||
const canStartExport = Boolean(exportFolder) && !isExporting && (
|
const isExportLocked = isExporting || exportTaskStatus !== 'idle'
|
||||||
|
const canPauseExport = exportTaskStatus === 'running'
|
||||||
|
const canResumeExport = exportTaskStatus === 'paused' || exportTaskStatus === 'pause_requested'
|
||||||
|
const canCancelExport = exportTaskStatus !== 'idle'
|
||||||
|
const canStartExport = Boolean(exportFolder) && !isExportLocked && (
|
||||||
exportScope.kind === 'all' || exportScope.usernames.length > 0
|
exportScope.kind === 'all' || exportScope.usernames.length > 0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -772,14 +811,205 @@ export default function SnsPage() {
|
|||||||
|
|
||||||
const exportDateRangeLabel = useMemo(() => getExportDateRangeLabel(exportDateRangeSelection), [exportDateRangeSelection])
|
const exportDateRangeLabel = useMemo(() => getExportDateRangeLabel(exportDateRangeSelection), [exportDateRangeSelection])
|
||||||
|
|
||||||
|
const clearActiveExportTask = useCallback(() => {
|
||||||
|
activeExportTaskIdRef.current = null
|
||||||
|
activeExportRequestRef.current = null
|
||||||
|
setExportTaskStatus('idle')
|
||||||
|
setIsExporting(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const buildSnsExportRequest = useCallback((taskId: string): SnsExportRequest => ({
|
||||||
|
taskId,
|
||||||
|
outputDir: exportFolder,
|
||||||
|
format: exportFormat,
|
||||||
|
usernames: exportScope.kind === 'selected' ? [...exportScope.usernames] : undefined,
|
||||||
|
keyword: searchKeyword || undefined,
|
||||||
|
exportImages,
|
||||||
|
exportLivePhotos,
|
||||||
|
exportVideos,
|
||||||
|
startTime: exportDateRangeSelection.useAllTime
|
||||||
|
? undefined
|
||||||
|
: Math.floor(exportDateRangeSelection.dateRange.start.getTime() / 1000),
|
||||||
|
endTime: exportDateRangeSelection.useAllTime
|
||||||
|
? undefined
|
||||||
|
: Math.floor(exportDateRangeSelection.dateRange.end.getTime() / 1000)
|
||||||
|
}), [
|
||||||
|
exportDateRangeSelection,
|
||||||
|
exportFolder,
|
||||||
|
exportFormat,
|
||||||
|
exportImages,
|
||||||
|
exportLivePhotos,
|
||||||
|
exportScope,
|
||||||
|
exportVideos,
|
||||||
|
searchKeyword
|
||||||
|
])
|
||||||
|
|
||||||
|
const runSnsExport = useCallback(async (request: SnsExportRequest, statusText = '准备导出...') => {
|
||||||
|
activeExportTaskIdRef.current = request.taskId
|
||||||
|
activeExportRequestRef.current = request
|
||||||
|
setIsExporting(true)
|
||||||
|
setExportTaskStatus('running')
|
||||||
|
setExportResult(null)
|
||||||
|
setExportProgress(prev => prev || { current: 0, total: 0, status: statusText })
|
||||||
|
|
||||||
|
let keepTaskActive = false
|
||||||
|
const removeProgress = window.electronAPI.sns.onExportProgress((progress: SnsExportProgress) => {
|
||||||
|
setExportProgress(progress)
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await window.electronAPI.sns.exportTimeline(request)
|
||||||
|
if (!result.success) {
|
||||||
|
setExportResult(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.paused) {
|
||||||
|
keepTaskActive = true
|
||||||
|
setExportTaskStatus('paused')
|
||||||
|
setExportProgress(prev => ({
|
||||||
|
current: Math.max(prev?.current || 0, result.postCount || 0),
|
||||||
|
total: Math.max(prev?.total || 0, result.postCount || 0),
|
||||||
|
status: '已暂停,可继续或取消'
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.stopped) {
|
||||||
|
setExportResult(null)
|
||||||
|
setExportProgress(null)
|
||||||
|
setShowExportDialog(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setExportResult(result)
|
||||||
|
} catch (e: any) {
|
||||||
|
setExportResult({ success: false, error: e.message || String(e) })
|
||||||
|
} finally {
|
||||||
|
removeProgress()
|
||||||
|
setIsExporting(false)
|
||||||
|
if (!keepTaskActive) {
|
||||||
|
activeExportTaskIdRef.current = null
|
||||||
|
activeExportRequestRef.current = null
|
||||||
|
setExportTaskStatus('idle')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleStartSnsExport = useCallback(() => {
|
||||||
|
if (!canStartExport) return
|
||||||
|
const request = buildSnsExportRequest(createSnsExportTaskId())
|
||||||
|
setExportProgress({ current: 0, total: 0, status: '准备导出...' })
|
||||||
|
void runSnsExport(request)
|
||||||
|
}, [buildSnsExportRequest, canStartExport, runSnsExport])
|
||||||
|
|
||||||
|
const handlePauseSnsExport = useCallback(() => {
|
||||||
|
const taskId = activeExportTaskIdRef.current
|
||||||
|
if (!taskId || exportTaskStatus !== 'running') return
|
||||||
|
setExportTaskStatus('pause_requested')
|
||||||
|
setExportProgress(prev => ({
|
||||||
|
current: prev?.current || 0,
|
||||||
|
total: prev?.total || 0,
|
||||||
|
status: '暂停请求已发送,正在等待安全检查点'
|
||||||
|
}))
|
||||||
|
window.electronAPI.export.pauseTask(taskId).then(result => {
|
||||||
|
if (result.success) return
|
||||||
|
setExportTaskStatus(current => current === 'pause_requested' ? 'running' : current)
|
||||||
|
setExportProgress(prev => ({
|
||||||
|
current: prev?.current || 0,
|
||||||
|
total: prev?.total || 0,
|
||||||
|
status: result.error || '暂停请求失败'
|
||||||
|
}))
|
||||||
|
}).catch(error => {
|
||||||
|
setExportTaskStatus(current => current === 'pause_requested' ? 'running' : current)
|
||||||
|
setExportProgress(prev => ({
|
||||||
|
current: prev?.current || 0,
|
||||||
|
total: prev?.total || 0,
|
||||||
|
status: String(error)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}, [exportTaskStatus])
|
||||||
|
|
||||||
|
const handleResumeSnsExport = useCallback(() => {
|
||||||
|
const taskId = activeExportTaskIdRef.current
|
||||||
|
const request = activeExportRequestRef.current
|
||||||
|
if (!taskId || !request || (exportTaskStatus !== 'paused' && exportTaskStatus !== 'pause_requested')) return
|
||||||
|
setExportTaskStatus('running')
|
||||||
|
setExportProgress(prev => ({
|
||||||
|
current: prev?.current || 0,
|
||||||
|
total: prev?.total || 0,
|
||||||
|
status: '正在继续导出...'
|
||||||
|
}))
|
||||||
|
window.electronAPI.export.resumeTask(taskId).then(result => {
|
||||||
|
if (!result.success) {
|
||||||
|
setExportTaskStatus('paused')
|
||||||
|
setExportProgress(prev => ({
|
||||||
|
current: prev?.current || 0,
|
||||||
|
total: prev?.total || 0,
|
||||||
|
status: result.error || '继续任务失败'
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
void runSnsExport(request, '正在继续导出...')
|
||||||
|
}).catch(error => {
|
||||||
|
setExportTaskStatus('paused')
|
||||||
|
setExportProgress(prev => ({
|
||||||
|
current: prev?.current || 0,
|
||||||
|
total: prev?.total || 0,
|
||||||
|
status: String(error)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}, [exportTaskStatus, runSnsExport])
|
||||||
|
|
||||||
|
const handleCancelSnsExport = useCallback(() => {
|
||||||
|
const taskId = activeExportTaskIdRef.current
|
||||||
|
if (!taskId || exportTaskStatus === 'idle' || exportTaskStatus === 'cancel_requested') return
|
||||||
|
const shouldCloseAfterAck = exportTaskStatus === 'paused' || !isExporting
|
||||||
|
setExportTaskStatus('cancel_requested')
|
||||||
|
setExportProgress(prev => ({
|
||||||
|
current: prev?.current || 0,
|
||||||
|
total: prev?.total || 0,
|
||||||
|
status: '取消请求已发送,正在安全停止并清理'
|
||||||
|
}))
|
||||||
|
window.electronAPI.export.cancelTask(taskId).then(result => {
|
||||||
|
if (!result.success) {
|
||||||
|
setExportTaskStatus(shouldCloseAfterAck ? 'paused' : 'running')
|
||||||
|
setExportProgress(prev => ({
|
||||||
|
current: prev?.current || 0,
|
||||||
|
total: prev?.total || 0,
|
||||||
|
status: result.error || '取消任务失败'
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (shouldCloseAfterAck) {
|
||||||
|
clearActiveExportTask()
|
||||||
|
setExportResult(null)
|
||||||
|
setExportProgress(null)
|
||||||
|
setShowExportDialog(false)
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
setExportTaskStatus(shouldCloseAfterAck ? 'paused' : 'running')
|
||||||
|
setExportProgress(prev => ({
|
||||||
|
current: prev?.current || 0,
|
||||||
|
total: prev?.total || 0,
|
||||||
|
status: String(error)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}, [clearActiveExportTask, exportTaskStatus, isExporting])
|
||||||
|
|
||||||
const openExportDialog = useCallback((scope: SnsExportScope) => {
|
const openExportDialog = useCallback((scope: SnsExportScope) => {
|
||||||
|
if (isExportLocked) {
|
||||||
|
setShowExportDialog(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
setExportScope(scope)
|
setExportScope(scope)
|
||||||
setExportResult(null)
|
setExportResult(null)
|
||||||
setExportProgress(null)
|
setExportProgress(null)
|
||||||
|
clearActiveExportTask()
|
||||||
setExportDateRangeSelection(createExportDateRangeSelectionFromPreset('all'))
|
setExportDateRangeSelection(createExportDateRangeSelectionFromPreset('all'))
|
||||||
setIsExportDateRangeDialogOpen(false)
|
setIsExportDateRangeDialogOpen(false)
|
||||||
setShowExportDialog(true)
|
setShowExportDialog(true)
|
||||||
}, [])
|
}, [clearActiveExportTask, isExportLocked])
|
||||||
|
|
||||||
const loadPosts = useCallback(async (options: { reset?: boolean, direction?: 'older' | 'newer' } = {}) => {
|
const loadPosts = useCallback(async (options: { reset?: boolean, direction?: 'older' | 'newer' } = {}) => {
|
||||||
const { reset = false, direction = 'older' } = options
|
const { reset = false, direction = 'older' } = options
|
||||||
@@ -2048,11 +2278,11 @@ export default function SnsPage() {
|
|||||||
|
|
||||||
{/* 导出对话框 */}
|
{/* 导出对话框 */}
|
||||||
{showExportDialog && (
|
{showExportDialog && (
|
||||||
<div className="modal-overlay" onClick={() => !isExporting && setShowExportDialog(false)}>
|
<div className="modal-overlay" onClick={() => !isExportLocked && setShowExportDialog(false)}>
|
||||||
<div className="export-dialog" onClick={(e) => e.stopPropagation()}>
|
<div className="export-dialog" onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="export-dialog-header">
|
<div className="export-dialog-header">
|
||||||
<h3>导出朋友圈</h3>
|
<h3>导出朋友圈</h3>
|
||||||
<button className="close-btn" onClick={() => !isExporting && setShowExportDialog(false)} disabled={isExporting}>
|
<button className="close-btn" onClick={() => !isExportLocked && setShowExportDialog(false)} disabled={isExportLocked}>
|
||||||
<X size={20} />
|
<X size={20} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -2078,7 +2308,7 @@ export default function SnsPage() {
|
|||||||
<button
|
<button
|
||||||
className={`format-option ${exportFormat === 'html' ? 'active' : ''}`}
|
className={`format-option ${exportFormat === 'html' ? 'active' : ''}`}
|
||||||
onClick={() => setExportFormat('html')}
|
onClick={() => setExportFormat('html')}
|
||||||
disabled={isExporting}
|
disabled={isExportLocked}
|
||||||
>
|
>
|
||||||
<FileText size={20} />
|
<FileText size={20} />
|
||||||
<span>HTML</span>
|
<span>HTML</span>
|
||||||
@@ -2087,7 +2317,7 @@ export default function SnsPage() {
|
|||||||
<button
|
<button
|
||||||
className={`format-option ${exportFormat === 'json' ? 'active' : ''}`}
|
className={`format-option ${exportFormat === 'json' ? 'active' : ''}`}
|
||||||
onClick={() => setExportFormat('json')}
|
onClick={() => setExportFormat('json')}
|
||||||
disabled={isExporting}
|
disabled={isExportLocked}
|
||||||
>
|
>
|
||||||
<FileJson size={20} />
|
<FileJson size={20} />
|
||||||
<span>JSON</span>
|
<span>JSON</span>
|
||||||
@@ -2096,7 +2326,7 @@ export default function SnsPage() {
|
|||||||
<button
|
<button
|
||||||
className={`format-option ${exportFormat === 'arkmejson' ? 'active' : ''}`}
|
className={`format-option ${exportFormat === 'arkmejson' ? 'active' : ''}`}
|
||||||
onClick={() => setExportFormat('arkmejson')}
|
onClick={() => setExportFormat('arkmejson')}
|
||||||
disabled={isExporting}
|
disabled={isExportLocked}
|
||||||
>
|
>
|
||||||
<FileJson size={20} />
|
<FileJson size={20} />
|
||||||
<span>ArkmeJSON</span>
|
<span>ArkmeJSON</span>
|
||||||
@@ -2124,7 +2354,7 @@ export default function SnsPage() {
|
|||||||
setExportFolder(result.filePath)
|
setExportFolder(result.filePath)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={isExporting}
|
disabled={isExportLocked}
|
||||||
>
|
>
|
||||||
<FolderOpen size={16} />
|
<FolderOpen size={16} />
|
||||||
</button>
|
</button>
|
||||||
@@ -2139,9 +2369,9 @@ export default function SnsPage() {
|
|||||||
type="button"
|
type="button"
|
||||||
className="time-range-trigger sns-export-time-range-trigger"
|
className="time-range-trigger sns-export-time-range-trigger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isExporting) setIsExportDateRangeDialogOpen(true)
|
if (!isExportLocked) setIsExportDateRangeDialogOpen(true)
|
||||||
}}
|
}}
|
||||||
disabled={isExporting}
|
disabled={isExportLocked}
|
||||||
>
|
>
|
||||||
<span>{exportDateRangeLabel}</span>
|
<span>{exportDateRangeLabel}</span>
|
||||||
<span className="time-range-arrow">></span>
|
<span className="time-range-arrow">></span>
|
||||||
@@ -2161,7 +2391,7 @@ export default function SnsPage() {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={exportImages}
|
checked={exportImages}
|
||||||
onChange={(e) => setExportImages(e.target.checked)}
|
onChange={(e) => setExportImages(e.target.checked)}
|
||||||
disabled={isExporting}
|
disabled={isExportLocked}
|
||||||
/>
|
/>
|
||||||
图片
|
图片
|
||||||
</label>
|
</label>
|
||||||
@@ -2170,7 +2400,7 @@ export default function SnsPage() {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={exportLivePhotos}
|
checked={exportLivePhotos}
|
||||||
onChange={(e) => setExportLivePhotos(e.target.checked)}
|
onChange={(e) => setExportLivePhotos(e.target.checked)}
|
||||||
disabled={isExporting}
|
disabled={isExportLocked}
|
||||||
/>
|
/>
|
||||||
实况图
|
实况图
|
||||||
</label>
|
</label>
|
||||||
@@ -2179,7 +2409,7 @@ export default function SnsPage() {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={exportVideos}
|
checked={exportVideos}
|
||||||
onChange={(e) => setExportVideos(e.target.checked)}
|
onChange={(e) => setExportVideos(e.target.checked)}
|
||||||
disabled={isExporting}
|
disabled={isExportLocked}
|
||||||
/>
|
/>
|
||||||
视频
|
视频
|
||||||
</label>
|
</label>
|
||||||
@@ -2194,7 +2424,7 @@ export default function SnsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 进度条 */}
|
{/* 进度条 */}
|
||||||
{isExporting && exportProgress && (
|
{isExportLocked && exportProgress && (
|
||||||
<div className="export-progress">
|
<div className="export-progress">
|
||||||
<div className="export-progress-bar">
|
<div className="export-progress-bar">
|
||||||
<div
|
<div
|
||||||
@@ -2203,6 +2433,39 @@ export default function SnsPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="export-progress-text">{exportProgress.status}</span>
|
<span className="export-progress-text">{exportProgress.status}</span>
|
||||||
|
<div className="export-progress-actions">
|
||||||
|
{canPauseExport && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="export-progress-btn"
|
||||||
|
onClick={handlePauseSnsExport}
|
||||||
|
>
|
||||||
|
<Pause size={14} />
|
||||||
|
暂停
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{canResumeExport && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="export-progress-btn primary"
|
||||||
|
onClick={handleResumeSnsExport}
|
||||||
|
>
|
||||||
|
<Play size={14} />
|
||||||
|
继续
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{canCancelExport && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="export-progress-btn danger"
|
||||||
|
onClick={handleCancelSnsExport}
|
||||||
|
disabled={exportTaskStatus === 'cancel_requested'}
|
||||||
|
>
|
||||||
|
<Square size={14} />
|
||||||
|
{exportTaskStatus === 'cancel_requested' ? '取消中' : '取消'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -2211,47 +2474,14 @@ export default function SnsPage() {
|
|||||||
<button
|
<button
|
||||||
className="export-cancel-btn"
|
className="export-cancel-btn"
|
||||||
onClick={() => setShowExportDialog(false)}
|
onClick={() => setShowExportDialog(false)}
|
||||||
disabled={isExporting}
|
disabled={isExportLocked}
|
||||||
>
|
>
|
||||||
取消
|
取消
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="export-start-btn"
|
className="export-start-btn"
|
||||||
disabled={!canStartExport}
|
disabled={!canStartExport}
|
||||||
onClick={async () => {
|
onClick={handleStartSnsExport}
|
||||||
setIsExporting(true)
|
|
||||||
setExportProgress({ current: 0, total: 0, status: '准备导出...' })
|
|
||||||
setExportResult(null)
|
|
||||||
|
|
||||||
// 监听进度
|
|
||||||
const removeProgress = window.electronAPI.sns.onExportProgress((progress: any) => {
|
|
||||||
setExportProgress(progress)
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await window.electronAPI.sns.exportTimeline({
|
|
||||||
outputDir: exportFolder,
|
|
||||||
format: exportFormat,
|
|
||||||
usernames: exportScope.kind === 'selected' ? exportScope.usernames : undefined,
|
|
||||||
keyword: searchKeyword || undefined,
|
|
||||||
exportImages,
|
|
||||||
exportLivePhotos,
|
|
||||||
exportVideos,
|
|
||||||
startTime: exportDateRangeSelection.useAllTime
|
|
||||||
? undefined
|
|
||||||
: Math.floor(exportDateRangeSelection.dateRange.start.getTime() / 1000),
|
|
||||||
endTime: exportDateRangeSelection.useAllTime
|
|
||||||
? undefined
|
|
||||||
: Math.floor(exportDateRangeSelection.dateRange.end.getTime() / 1000)
|
|
||||||
})
|
|
||||||
setExportResult(result)
|
|
||||||
} catch (e: any) {
|
|
||||||
setExportResult({ success: false, error: e.message || String(e) })
|
|
||||||
} finally {
|
|
||||||
setIsExporting(false)
|
|
||||||
removeProgress()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{isExporting ? '导出中...' : '开始导出'}
|
{isExporting ? '导出中...' : '开始导出'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user