Merge pull request #664 from xunchahaha/dev

修复导出页意外的横向滑动条 朋友圈导出新增多选
This commit is contained in:
xuncha
2026-04-07 19:15:04 +08:00
committed by GitHub
4 changed files with 97 additions and 10 deletions

View File

@@ -29,6 +29,7 @@ interface SnsFilterPanelProps {
activeContactUsername?: string activeContactUsername?: string
onOpenContactTimeline: (contact: Contact) => void onOpenContactTimeline: (contact: Contact) => void
onToggleContactSelected: (contact: Contact) => void onToggleContactSelected: (contact: Contact) => void
onToggleFilteredContacts: (usernames: string[], shouldSelect: boolean) => void
onClearSelectedContacts: () => void onClearSelectedContacts: () => void
onExportSelectedContacts: () => void onExportSelectedContacts: () => void
} }
@@ -46,6 +47,7 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
activeContactUsername, activeContactUsername,
onOpenContactTimeline, onOpenContactTimeline,
onToggleContactSelected, onToggleContactSelected,
onToggleFilteredContacts,
onClearSelectedContacts, onClearSelectedContacts,
onExportSelectedContacts onExportSelectedContacts
}) => { }) => {
@@ -57,6 +59,16 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
() => new Set(selectedContactUsernames), () => new Set(selectedContactUsernames),
[selectedContactUsernames] [selectedContactUsernames]
) )
const filteredContactUsernames = React.useMemo(
() => filteredContacts.map((contact) => contact.username),
[filteredContacts]
)
const selectedFilteredCount = React.useMemo(
() => filteredContactUsernames.filter((username) => selectedContactLookup.has(username)).length,
[filteredContactUsernames, selectedContactLookup]
)
const hasFilteredContacts = filteredContactUsernames.length > 0
const allFilteredSelected = hasFilteredContacts && selectedFilteredCount === filteredContactUsernames.length
const clearFilters = () => { const clearFilters = () => {
setSearchKeyword('') setSearchKeyword('')
@@ -128,6 +140,20 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
)} )}
</div> </div>
<div className="contact-selection-toolbar">
<span className="contact-selection-summary">
{filteredContactUsernames.length} {selectedFilteredCount}
</span>
<button
type="button"
className={`contact-selection-toggle${allFilteredSelected ? ' active' : ''}`}
onClick={() => onToggleFilteredContacts(filteredContactUsernames, !allFilteredSelected)}
disabled={!hasFilteredContacts}
>
{allFilteredSelected ? '取消全选' : '全选'}
</button>
</div>
{contactsCountProgress && contactsCountProgress.total > 0 && ( {contactsCountProgress && contactsCountProgress.total > 0 && (
<div className="contact-count-progress"> <div className="contact-count-progress">
{contactsCountProgress.running {contactsCountProgress.running

View File

@@ -6601,19 +6601,15 @@ function ExportPage() {
const taskQueuedCount = tasks.filter(task => task.status === 'queued').length const taskQueuedCount = tasks.filter(task => task.status === 'queued').length
const taskCenterAlertCount = taskRunningCount + taskQueuedCount const taskCenterAlertCount = taskRunningCount + taskQueuedCount
const hasFilteredContacts = filteredContacts.length > 0 const hasFilteredContacts = filteredContacts.length > 0
const CONTACTS_ACTION_STICKY_WIDTH = 184 const optionalMetricColumnCount = (shouldShowSnsColumn ? 1 : 0) + (shouldShowMutualFriendsColumn ? 1 : 0)
const contactsTableMinWidth = useMemo(() => { const contactsMetricColumnCount = 4 + optionalMetricColumnCount
const baseWidth = 24 + 34 + 44 + 280 + 120 + (4 * 72) + CONTACTS_ACTION_STICKY_WIDTH + (8 * 12) const contactsColumnGapCount = 6 + optionalMetricColumnCount
const snsWidth = shouldShowSnsColumn ? 72 + 12 : 0
const mutualFriendsWidth = shouldShowMutualFriendsColumn ? 72 + 12 : 0
return baseWidth + snsWidth + mutualFriendsWidth
}, [shouldShowMutualFriendsColumn, shouldShowSnsColumn])
const contactsTableStyle = useMemo(() => ( const contactsTableStyle = useMemo(() => (
{ {
['--contacts-table-min-width' as const]: `${contactsTableMinWidth}px` ['--contacts-table-min-width' as const]: `calc((2 * var(--contacts-inline-padding)) + var(--contacts-left-sticky-width) + var(--contacts-message-col-width) + (${contactsMetricColumnCount} * var(--contacts-media-col-width)) + var(--contacts-actions-sticky-width) + (${contactsColumnGapCount} * var(--contacts-column-gap)))`
} as CSSProperties } as CSSProperties
), [contactsTableMinWidth]) ), [contactsColumnGapCount, contactsMetricColumnCount])
const hasContactsHorizontalOverflow = contactsHorizontalScrollMetrics.contentWidth - contactsHorizontalScrollMetrics.viewportWidth > 1 const hasContactsHorizontalOverflow = contactsHorizontalScrollMetrics.contentWidth - contactsHorizontalScrollMetrics.viewportWidth > 4
const contactsBottomScrollbarInnerStyle = useMemo<CSSProperties>(() => ({ const contactsBottomScrollbarInnerStyle = useMemo<CSSProperties>(() => ({
width: `${Math.max(contactsHorizontalScrollMetrics.contentWidth, contactsHorizontalScrollMetrics.viewportWidth)}px` width: `${Math.max(contactsHorizontalScrollMetrics.contentWidth, contactsHorizontalScrollMetrics.viewportWidth)}px`
}), [contactsHorizontalScrollMetrics.contentWidth, contactsHorizontalScrollMetrics.viewportWidth]) }), [contactsHorizontalScrollMetrics.contentWidth, contactsHorizontalScrollMetrics.viewportWidth])

View File

@@ -1265,6 +1265,52 @@
} }
} }
.contact-selection-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 10px 16px;
border-bottom: 1px dashed color-mix(in srgb, var(--border-color) 72%, transparent);
}
.contact-selection-summary {
min-width: 0;
font-size: 11px;
color: var(--text-tertiary);
font-variant-numeric: tabular-nums;
}
.contact-selection-toggle {
flex-shrink: 0;
border: 1px solid color-mix(in srgb, var(--primary) 16%, var(--border-color));
background: color-mix(in srgb, var(--bg-primary) 84%, rgba(var(--primary-rgb), 0.08));
color: var(--text-secondary);
border-radius: 999px;
padding: 5px 10px;
font-size: 12px;
font-weight: 600;
line-height: 1.2;
cursor: pointer;
transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
&:hover:not(:disabled) {
border-color: color-mix(in srgb, var(--primary) 40%, var(--border-color));
color: var(--primary);
}
&.active {
border-color: color-mix(in srgb, var(--primary) 40%, var(--border-color));
background: rgba(var(--primary-rgb), 0.12);
color: var(--primary);
}
&:disabled {
opacity: 0.45;
cursor: not-allowed;
}
}
.contact-count-progress { .contact-count-progress {
padding: 8px 16px 10px; padding: 8px 16px 10px;
font-size: 11px; font-size: 11px;

View File

@@ -1398,6 +1398,24 @@ export default function SnsPage() {
setSelectedContactUsernames([]) setSelectedContactUsernames([])
}, []) }, [])
const toggleSelectFilteredContacts = useCallback((usernames: string[], shouldSelect: boolean) => {
const normalizedTargets = Array.from(new Set(
usernames
.map((username) => String(username || '').trim())
.filter(Boolean)
))
if (normalizedTargets.length === 0) return
setSelectedContactUsernames((prev) => {
if (shouldSelect) {
const next = new Set(prev)
normalizedTargets.forEach((username) => next.add(username))
return Array.from(next)
}
return prev.filter((username) => !normalizedTargets.includes(username))
})
}, [])
const openSelectedContactsExport = useCallback(() => { const openSelectedContactsExport = useCallback(() => {
if (selectedContactUsernames.length === 0) return if (selectedContactUsernames.length === 0) return
openExportDialog({ kind: 'selected', usernames: [...selectedContactUsernames] }) openExportDialog({ kind: 'selected', usernames: [...selectedContactUsernames] })
@@ -1783,6 +1801,7 @@ export default function SnsPage() {
activeContactUsername={authorTimelineTarget?.username} activeContactUsername={authorTimelineTarget?.username}
onOpenContactTimeline={openContactTimeline} onOpenContactTimeline={openContactTimeline}
onToggleContactSelected={toggleContactSelected} onToggleContactSelected={toggleContactSelected}
onToggleFilteredContacts={toggleSelectFilteredContacts}
onClearSelectedContacts={clearSelectedContacts} onClearSelectedContacts={clearSelectedContacts}
onExportSelectedContacts={openSelectedContactsExport} onExportSelectedContacts={openSelectedContactsExport}
/> />