mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 15:25:50 +00:00
feat: support batch-select SNS contacts
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Search, User, X, Loader2 } from 'lucide-react'
|
import { Search, User, X, Loader2, CheckSquare, Square, Download } from 'lucide-react'
|
||||||
import { Avatar } from '../Avatar'
|
import { Avatar } from '../Avatar'
|
||||||
|
|
||||||
interface Contact {
|
interface Contact {
|
||||||
@@ -25,7 +25,12 @@ interface SnsFilterPanelProps {
|
|||||||
setContactSearch: (val: string) => void
|
setContactSearch: (val: string) => void
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
contactsCountProgress?: ContactsCountProgress
|
contactsCountProgress?: ContactsCountProgress
|
||||||
|
selectedContactUsernames: string[]
|
||||||
|
activeContactUsername?: string
|
||||||
onOpenContactTimeline: (contact: Contact) => void
|
onOpenContactTimeline: (contact: Contact) => void
|
||||||
|
onToggleContactSelected: (contact: Contact) => void
|
||||||
|
onClearSelectedContacts: () => void
|
||||||
|
onExportSelectedContacts: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
||||||
@@ -37,12 +42,21 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
setContactSearch,
|
setContactSearch,
|
||||||
loading,
|
loading,
|
||||||
contactsCountProgress,
|
contactsCountProgress,
|
||||||
onOpenContactTimeline
|
selectedContactUsernames,
|
||||||
|
activeContactUsername,
|
||||||
|
onOpenContactTimeline,
|
||||||
|
onToggleContactSelected,
|
||||||
|
onClearSelectedContacts,
|
||||||
|
onExportSelectedContacts
|
||||||
}) => {
|
}) => {
|
||||||
const filteredContacts = contacts.filter(c =>
|
const filteredContacts = contacts.filter(c =>
|
||||||
(c.displayName || '').toLowerCase().includes(contactSearch.toLowerCase()) ||
|
(c.displayName || '').toLowerCase().includes(contactSearch.toLowerCase()) ||
|
||||||
c.username.toLowerCase().includes(contactSearch.toLowerCase())
|
c.username.toLowerCase().includes(contactSearch.toLowerCase())
|
||||||
)
|
)
|
||||||
|
const selectedContactLookup = React.useMemo(
|
||||||
|
() => new Set(selectedContactUsernames),
|
||||||
|
[selectedContactUsernames]
|
||||||
|
)
|
||||||
|
|
||||||
const clearFilters = () => {
|
const clearFilters = () => {
|
||||||
setSearchKeyword('')
|
setSearchKeyword('')
|
||||||
@@ -122,35 +136,69 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="contact-interaction-hint">
|
||||||
|
点左侧可多选下载,点右侧可查看单人详情
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="contact-list-scroll">
|
<div className="contact-list-scroll">
|
||||||
{filteredContacts.map(contact => {
|
{filteredContacts.map(contact => {
|
||||||
const isPostCountReady = contact.postCountStatus === 'ready'
|
const isPostCountReady = contact.postCountStatus === 'ready'
|
||||||
|
const isSelected = selectedContactLookup.has(contact.username)
|
||||||
|
const isActive = activeContactUsername === contact.username
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={contact.username}
|
key={contact.username}
|
||||||
className="contact-row"
|
className={`contact-row${isSelected ? ' is-selected' : ''}${isActive ? ' is-active' : ''}`}
|
||||||
onClick={() => onOpenContactTimeline(contact)}
|
>
|
||||||
>
|
<button
|
||||||
<Avatar src={contact.avatarUrl} name={contact.displayName} size={36} shape="rounded" />
|
type="button"
|
||||||
<div className="contact-meta">
|
className={`contact-select-btn${isSelected ? ' checked' : ''}`}
|
||||||
<span className="contact-name">{contact.displayName}</span>
|
onClick={() => onToggleContactSelected(contact)}
|
||||||
|
title={isSelected ? `取消选择 ${contact.displayName}` : `选择 ${contact.displayName}`}
|
||||||
|
aria-pressed={isSelected}
|
||||||
|
>
|
||||||
|
{isSelected ? <CheckSquare size={16} /> : <Square size={16} />}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="contact-main-btn"
|
||||||
|
onClick={() => onOpenContactTimeline(contact)}
|
||||||
|
title={`查看 ${contact.displayName} 的朋友圈`}
|
||||||
|
>
|
||||||
|
<Avatar src={contact.avatarUrl} name={contact.displayName} size={36} shape="rounded" />
|
||||||
|
<div className="contact-meta">
|
||||||
|
<span className="contact-name">{contact.displayName}</span>
|
||||||
|
</div>
|
||||||
|
<div className="contact-post-count-wrap">
|
||||||
|
{isPostCountReady ? (
|
||||||
|
<span className="contact-post-count">{Math.max(0, Math.floor(Number(contact.postCount || 0)))}条</span>
|
||||||
|
) : (
|
||||||
|
<span className="contact-post-count-loading" title="统计中">
|
||||||
|
<Loader2 size={13} className="spinning" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="contact-post-count-wrap">
|
|
||||||
{isPostCountReady ? (
|
|
||||||
<span className="contact-post-count">{Math.max(0, Math.floor(Number(contact.postCount || 0)))}条</span>
|
|
||||||
) : (
|
|
||||||
<span className="contact-post-count-loading" title="统计中">
|
|
||||||
<Loader2 size={13} className="spinning" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
{filteredContacts.length === 0 && (
|
{filteredContacts.length === 0 && (
|
||||||
<div className="empty-state">{getEmptyStateText()}</div>
|
<div className="empty-state">{getEmptyStateText()}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{selectedContactUsernames.length > 0 && (
|
||||||
|
<div className="contact-batch-bar">
|
||||||
|
<span className="contact-batch-summary">已选 {selectedContactUsernames.length} 人</span>
|
||||||
|
<button type="button" className="contact-batch-btn" onClick={onClearSelectedContacts}>
|
||||||
|
清空
|
||||||
|
</button>
|
||||||
|
<button type="button" className="contact-batch-btn primary" onClick={onExportSelectedContacts}>
|
||||||
|
<Download size={14} />
|
||||||
|
<span>下载所选</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -1211,6 +1211,13 @@
|
|||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact-interaction-hint {
|
||||||
|
padding: 10px 16px 0;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
.contact-list-scroll {
|
.contact-list-scroll {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -1218,23 +1225,75 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
/* Remove gap to allow borders to merge */
|
|
||||||
|
|
||||||
.contact-row {
|
.contact-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
padding: 10px;
|
|
||||||
border-radius: var(--sns-border-radius-md);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s ease, transform 0.2s ease;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
border-radius: var(--sns-border-radius-md);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--hover-bg);
|
|
||||||
transform: translateX(2px);
|
transform: translateX(2px);
|
||||||
z-index: 10;
|
}
|
||||||
|
|
||||||
|
&.is-selected .contact-main-btn {
|
||||||
|
background: rgba(var(--primary-rgb), 0.06);
|
||||||
|
border-color: color-mix(in srgb, var(--primary) 20%, var(--border-color));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active .contact-main-btn {
|
||||||
|
background: rgba(var(--primary-rgb), 0.12);
|
||||||
|
border-color: color-mix(in srgb, var(--primary) 48%, var(--border-color));
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(var(--primary-rgb), 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active .contact-name {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-select-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s ease, color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(var(--primary-rgb), 0.1);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-main-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: var(--sns-border-radius-md);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s ease, border-color 0.2s ease;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--hover-bg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-meta {
|
.contact-meta {
|
||||||
@@ -1282,6 +1341,51 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact-batch-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 16px 14px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
background: color-mix(in srgb, var(--bg-primary) 86%, var(--bg-secondary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-batch-summary {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-batch-btn {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border-radius: var(--sns-border-radius-md);
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 10px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--text-tertiary);
|
||||||
|
background: var(--hover-bg);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
border-color: color-mix(in srgb, var(--primary) 35%, var(--border-color));
|
||||||
|
background: rgba(var(--primary-rgb), 0.12);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ interface SnsOverviewStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OverviewStatsStatus = 'loading' | 'ready' | 'error'
|
type OverviewStatsStatus = 'loading' | 'ready' | 'error'
|
||||||
|
type SnsExportScope = { kind: 'all' } | { kind: 'selected'; usernames: string[] }
|
||||||
|
|
||||||
const SIDEBAR_USER_PROFILE_CACHE_KEY = 'sidebar_user_profile_cache_v1'
|
const SIDEBAR_USER_PROFILE_CACHE_KEY = 'sidebar_user_profile_cache_v1'
|
||||||
|
|
||||||
@@ -123,6 +124,7 @@ export default function SnsPage() {
|
|||||||
total: 0,
|
total: 0,
|
||||||
running: false
|
running: false
|
||||||
})
|
})
|
||||||
|
const [selectedContactUsernames, setSelectedContactUsernames] = useState<string[]>([])
|
||||||
const [currentUserProfile, setCurrentUserProfile] = useState<SidebarUserProfile>(() => readSidebarUserProfileCache() || {
|
const [currentUserProfile, setCurrentUserProfile] = useState<SidebarUserProfile>(() => readSidebarUserProfileCache() || {
|
||||||
wxid: '',
|
wxid: '',
|
||||||
displayName: ''
|
displayName: ''
|
||||||
@@ -140,6 +142,7 @@ export default function SnsPage() {
|
|||||||
|
|
||||||
// 导出相关状态
|
// 导出相关状态
|
||||||
const [showExportDialog, setShowExportDialog] = useState(false)
|
const [showExportDialog, setShowExportDialog] = useState(false)
|
||||||
|
const [exportScope, setExportScope] = useState<SnsExportScope>({ kind: 'all' })
|
||||||
const [exportFormat, setExportFormat] = useState<'json' | 'html' | 'arkmejson'>('html')
|
const [exportFormat, setExportFormat] = useState<'json' | 'html' | 'arkmejson'>('html')
|
||||||
const [exportFolder, setExportFolder] = useState('')
|
const [exportFolder, setExportFolder] = useState('')
|
||||||
const [exportImages, setExportImages] = useState(false)
|
const [exportImages, setExportImages] = useState(false)
|
||||||
@@ -186,6 +189,13 @@ export default function SnsPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
contactsRef.current = contacts
|
contactsRef.current = contacts
|
||||||
}, [contacts])
|
}, [contacts])
|
||||||
|
useEffect(() => {
|
||||||
|
const contactLookup = new Set(contacts.map((contact) => contact.username))
|
||||||
|
setSelectedContactUsernames((prev) => {
|
||||||
|
const next = prev.filter((username) => contactLookup.has(username))
|
||||||
|
return next.length === prev.length ? prev : next
|
||||||
|
})
|
||||||
|
}, [contacts])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
overviewStatsRef.current = overviewStats
|
overviewStatsRef.current = overviewStats
|
||||||
}, [overviewStats])
|
}, [overviewStats])
|
||||||
@@ -376,6 +386,14 @@ export default function SnsPage() {
|
|||||||
return contacts.find((contact) => contact.username === normalizedTargetUsername) || null
|
return contacts.find((contact) => contact.username === normalizedTargetUsername) || null
|
||||||
}, [authorTimelineTarget, contacts])
|
}, [authorTimelineTarget, contacts])
|
||||||
|
|
||||||
|
const exportSelectedContactsSummary = useMemo(() => {
|
||||||
|
if (exportScope.kind !== 'selected' || exportScope.usernames.length === 0) return ''
|
||||||
|
const contactMap = new Map(contacts.map((contact) => [contact.username, contact]))
|
||||||
|
const names = exportScope.usernames.map((username) => contactMap.get(username)?.displayName || username)
|
||||||
|
if (names.length <= 2) return names.join('、')
|
||||||
|
return `${names.slice(0, 2).join('、')} 等 ${names.length} 位联系人`
|
||||||
|
}, [contacts, exportScope])
|
||||||
|
|
||||||
const myTimelineCount = useMemo(() => {
|
const myTimelineCount = useMemo(() => {
|
||||||
if (resolvedCurrentUserContact?.postCountStatus === 'ready' && typeof resolvedCurrentUserContact.postCount === 'number') {
|
if (resolvedCurrentUserContact?.postCountStatus === 'ready' && typeof resolvedCurrentUserContact.postCount === 'number') {
|
||||||
return normalizePostCount(resolvedCurrentUserContact.postCount)
|
return normalizePostCount(resolvedCurrentUserContact.postCount)
|
||||||
@@ -389,6 +407,10 @@ export default function SnsPage() {
|
|||||||
: overviewStatsStatus === 'loading' || contactsLoading
|
: overviewStatsStatus === 'loading' || contactsLoading
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const canStartExport = Boolean(exportFolder) && !isExporting && (
|
||||||
|
exportScope.kind === 'all' || exportScope.usernames.length > 0
|
||||||
|
)
|
||||||
|
|
||||||
const openCurrentUserTimeline = useCallback(() => {
|
const openCurrentUserTimeline = useCallback(() => {
|
||||||
if (!resolvedCurrentUserContact) return
|
if (!resolvedCurrentUserContact) return
|
||||||
setAuthorTimelineTarget({
|
setAuthorTimelineTarget({
|
||||||
@@ -561,6 +583,15 @@ export default function SnsPage() {
|
|||||||
|
|
||||||
const exportDateRangeLabel = useMemo(() => getExportDateRangeLabel(exportDateRangeSelection), [exportDateRangeSelection])
|
const exportDateRangeLabel = useMemo(() => getExportDateRangeLabel(exportDateRangeSelection), [exportDateRangeSelection])
|
||||||
|
|
||||||
|
const openExportDialog = useCallback((scope: SnsExportScope) => {
|
||||||
|
setExportScope(scope)
|
||||||
|
setExportResult(null)
|
||||||
|
setExportProgress(null)
|
||||||
|
setExportDateRangeSelection(createExportDateRangeSelectionFromPreset('all'))
|
||||||
|
setIsExportDateRangeDialogOpen(false)
|
||||||
|
setShowExportDialog(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
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
|
||||||
if (loadingRef.current) return
|
if (loadingRef.current) return
|
||||||
@@ -1040,6 +1071,23 @@ export default function SnsPage() {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const toggleContactSelected = useCallback((contact: Contact) => {
|
||||||
|
setSelectedContactUsernames((prev) => (
|
||||||
|
prev.includes(contact.username)
|
||||||
|
? prev.filter((username) => username !== contact.username)
|
||||||
|
: [...prev, contact.username]
|
||||||
|
))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const clearSelectedContacts = useCallback(() => {
|
||||||
|
setSelectedContactUsernames([])
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const openSelectedContactsExport = useCallback(() => {
|
||||||
|
if (selectedContactUsernames.length === 0) return
|
||||||
|
openExportDialog({ kind: 'selected', usernames: [...selectedContactUsernames] })
|
||||||
|
}, [openExportDialog, selectedContactUsernames])
|
||||||
|
|
||||||
const handlePostDelete = useCallback((postId: string, username: string) => {
|
const handlePostDelete = useCallback((postId: string, username: string) => {
|
||||||
setPosts(prev => {
|
setPosts(prev => {
|
||||||
const next = prev.filter(p => p.id !== postId)
|
const next = prev.filter(p => p.id !== postId)
|
||||||
@@ -1264,13 +1312,7 @@ export default function SnsPage() {
|
|||||||
<Shield size={20} />
|
<Shield size={20} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => openExportDialog({ kind: 'all' })}
|
||||||
setExportResult(null)
|
|
||||||
setExportProgress(null)
|
|
||||||
setExportDateRangeSelection(createExportDateRangeSelectionFromPreset('all'))
|
|
||||||
setIsExportDateRangeDialogOpen(false)
|
|
||||||
setShowExportDialog(true)
|
|
||||||
}}
|
|
||||||
className="icon-btn export-btn"
|
className="icon-btn export-btn"
|
||||||
title="导出朋友圈"
|
title="导出朋友圈"
|
||||||
>
|
>
|
||||||
@@ -1377,7 +1419,12 @@ export default function SnsPage() {
|
|||||||
setContactSearch={setContactSearch}
|
setContactSearch={setContactSearch}
|
||||||
loading={contactsLoading}
|
loading={contactsLoading}
|
||||||
contactsCountProgress={contactsCountProgress}
|
contactsCountProgress={contactsCountProgress}
|
||||||
|
selectedContactUsernames={selectedContactUsernames}
|
||||||
|
activeContactUsername={authorTimelineTarget?.username}
|
||||||
onOpenContactTimeline={openContactTimeline}
|
onOpenContactTimeline={openContactTimeline}
|
||||||
|
onToggleContactSelected={toggleContactSelected}
|
||||||
|
onClearSelectedContacts={clearSelectedContacts}
|
||||||
|
onExportSelectedContacts={openSelectedContactsExport}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Dialogs and Overlays */}
|
{/* Dialogs and Overlays */}
|
||||||
@@ -1522,9 +1569,12 @@ export default function SnsPage() {
|
|||||||
|
|
||||||
<div className="export-dialog-body">
|
<div className="export-dialog-body">
|
||||||
{/* 筛选条件提示 */}
|
{/* 筛选条件提示 */}
|
||||||
{searchKeyword && (
|
{(searchKeyword || exportScope.kind === 'selected') && (
|
||||||
<div className="export-filter-info">
|
<div className="export-filter-info">
|
||||||
<span className="filter-badge">筛选导出</span>
|
<span className="filter-badge">导出范围</span>
|
||||||
|
{exportScope.kind === 'selected' && (
|
||||||
|
<span className="filter-tag">联系人: {exportSelectedContactsSummary}</span>
|
||||||
|
)}
|
||||||
{searchKeyword && <span className="filter-tag">关键词: "{searchKeyword}"</span>}
|
{searchKeyword && <span className="filter-tag">关键词: "{searchKeyword}"</span>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1650,7 +1700,7 @@ export default function SnsPage() {
|
|||||||
{/* 同步提示 */}
|
{/* 同步提示 */}
|
||||||
<div className="export-sync-hint">
|
<div className="export-sync-hint">
|
||||||
<Info size={14} />
|
<Info size={14} />
|
||||||
<span>将同步主页面的关键词搜索</span>
|
<span>{exportScope.kind === 'selected' ? '将同步主页面的关键词搜索,并仅导出所选联系人' : '将同步主页面的关键词搜索'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 进度条 */}
|
{/* 进度条 */}
|
||||||
@@ -1677,7 +1727,7 @@ export default function SnsPage() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="export-start-btn"
|
className="export-start-btn"
|
||||||
disabled={!exportFolder || isExporting}
|
disabled={!canStartExport}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setIsExporting(true)
|
setIsExporting(true)
|
||||||
setExportProgress({ current: 0, total: 0, status: '准备导出...' })
|
setExportProgress({ current: 0, total: 0, status: '准备导出...' })
|
||||||
@@ -1692,6 +1742,7 @@ export default function SnsPage() {
|
|||||||
const result = await window.electronAPI.sns.exportTimeline({
|
const result = await window.electronAPI.sns.exportTimeline({
|
||||||
outputDir: exportFolder,
|
outputDir: exportFolder,
|
||||||
format: exportFormat,
|
format: exportFormat,
|
||||||
|
usernames: exportScope.kind === 'selected' ? exportScope.usernames : undefined,
|
||||||
keyword: searchKeyword || undefined,
|
keyword: searchKeyword || undefined,
|
||||||
exportImages,
|
exportImages,
|
||||||
exportLivePhotos,
|
exportLivePhotos,
|
||||||
|
|||||||
Reference in New Issue
Block a user