From d18a8714292b25031265d5b76584c9d261fa09c3 Mon Sep 17 00:00:00 2001 From: tisonhuang Date: Mon, 2 Mar 2026 11:31:07 +0800 Subject: [PATCH] fix(export): restore dialog scroll and adaptive format grid --- src/pages/ExportPage.scss | 66 +++++++- src/pages/ExportPage.tsx | 312 +++++++++++++++++++++++--------------- 2 files changed, 252 insertions(+), 126 deletions(-) diff --git a/src/pages/ExportPage.scss b/src/pages/ExportPage.scss index 73a2fbc..49efd28 100644 --- a/src/pages/ExportPage.scss +++ b/src/pages/ExportPage.scss @@ -655,6 +655,11 @@ &.checked { color: var(--primary); } + + &:disabled { + opacity: 0.4; + cursor: not-allowed; + } } .session-cell { @@ -736,6 +741,12 @@ &.running { background: color-mix(in srgb, var(--primary) 80%, #000); } + + &.no-session { + background: var(--bg-secondary); + color: var(--text-tertiary); + border: 1px dashed var(--border-color); + } } .row-export-time { @@ -816,17 +827,29 @@ display: flex; align-items: center; justify-content: center; + padding: 16px; z-index: 1000; } .export-dialog { - width: min(980px, calc(100vw - 40px)); - max-height: calc(100vh - 60px); - overflow: auto; + width: min(1080px, calc(100vw - 32px)); + max-height: calc(100vh - 32px); background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 14px; - padding: 16px; + padding: 14px 14px 12px; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.dialog-body { + min-height: 0; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 10px; + padding-right: 2px; } .dialog-header { @@ -859,7 +882,6 @@ border: 1px solid var(--border-color); border-radius: 10px; padding: 12px; - margin-bottom: 10px; background: var(--bg-secondary); h4 { @@ -912,17 +934,23 @@ .format-grid { display: grid; - grid-template-columns: repeat(4, minmax(130px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 8px; } .format-card { + width: 100%; + min-height: 82px; border: 1px solid var(--border-color); border-radius: 10px; padding: 10px; text-align: left; background: var(--bg-primary); cursor: pointer; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; .format-label { font-size: 13px; @@ -1033,9 +1061,13 @@ .dialog-actions { margin-top: 10px; + padding-top: 10px; + border-top: 1px solid var(--border-color); display: flex; justify-content: flex-end; gap: 8px; + flex-shrink: 0; + background: var(--card-bg); } .primary-btn, @@ -1132,7 +1164,7 @@ } .format-grid { - grid-template-columns: repeat(2, minmax(120px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } .display-name-options { @@ -1143,3 +1175,23 @@ grid-template-columns: repeat(2, minmax(120px, 1fr)); } } + +@media (max-width: 720px) { + .export-dialog-overlay { + padding: 10px; + } + + .export-dialog { + width: calc(100vw - 20px); + max-height: calc(100vh - 20px); + padding: 12px 10px 10px; + } + + .format-grid { + grid-template-columns: 1fr; + } + + .date-range-row { + grid-template-columns: 1fr; + } +} diff --git a/src/pages/ExportPage.tsx b/src/pages/ExportPage.tsx index e32ecc1..d7cdf25 100644 --- a/src/pages/ExportPage.tsx +++ b/src/pages/ExportPage.tsx @@ -57,6 +57,7 @@ interface ExportOptions { interface SessionRow extends AppChatSession { kind: ConversationTab wechatId?: string + hasSession: boolean } interface TaskProgress { @@ -207,6 +208,13 @@ const toKindByContactType = (session: AppChatSession, contact?: ContactInfo): Co return 'private' } +const toKindByContact = (contact: ContactInfo): ConversationTab => { + if (contact.type === 'group') return 'group' + if (contact.type === 'official') return 'official' + if (contact.type === 'former_friend') return 'former_friend' + return 'private' +} + const isContentScopeSession = (session: SessionRow): boolean => ( session.kind === 'private' || session.kind === 'group' || session.kind === 'former_friend' ) @@ -257,6 +265,50 @@ const toSessionRowsWithContacts = ( sessions: AppChatSession[], contactMap: Record ): SessionRow[] => { + const sessionMap = new Map() + for (const session of sessions || []) { + sessionMap.set(session.username, session) + } + + const contacts = Object.values(contactMap) + .filter((contact) => ( + contact.type === 'friend' || + contact.type === 'group' || + contact.type === 'official' || + contact.type === 'former_friend' + )) + + if (contacts.length > 0) { + return contacts + .map((contact) => { + const session = sessionMap.get(contact.username) + const latestTs = session?.sortTimestamp || session?.lastTimestamp || 0 + return { + ...(session || { + username: contact.username, + type: 0, + unreadCount: 0, + summary: '', + sortTimestamp: latestTs, + lastTimestamp: latestTs, + lastMsgType: 0 + }), + username: contact.username, + kind: toKindByContact(contact), + wechatId: contact.username, + displayName: contact.displayName || session?.displayName || contact.username, + avatarUrl: contact.avatarUrl || session?.avatarUrl, + hasSession: Boolean(session) + } as SessionRow + }) + .sort((a, b) => { + const latestA = a.sortTimestamp || a.lastTimestamp || 0 + const latestB = b.sortTimestamp || b.lastTimestamp || 0 + if (latestA !== latestB) return latestB - latestA + return (a.displayName || a.username).localeCompare(b.displayName || b.username, 'zh-Hans-CN') + }) + } + return sessions .map((session) => { const contact = contactMap[session.username] @@ -265,7 +317,8 @@ const toSessionRowsWithContacts = ( kind: toKindByContactType(session, contact), wechatId: contact?.username || session.username, displayName: contact?.displayName || session.displayName || session.username, - avatarUrl: contact?.avatarUrl || session.avatarUrl + avatarUrl: contact?.avatarUrl || session.avatarUrl, + hasSession: true } as SessionRow }) .sort((a, b) => (b.sortTimestamp || b.lastTimestamp || 0) - (a.sortTimestamp || a.lastTimestamp || 0)) @@ -570,6 +623,9 @@ function ExportPage() { const cachedContactMap = toContactMapFromCaches(cachedContacts, cachedAvatarEntries) if (cachedContacts.length > 0) { syncContactTypeCounts(Object.values(cachedContactMap)) + setSessions(toSessionRowsWithContacts([], cachedContactMap)) + setSessionDataSource('cache') + setIsLoading(false) } setSessionContactsUpdatedAt(cachedContactsItem?.updatedAt || null) setSessionAvatarUpdatedAt(cachedAvatarItem?.updatedAt || null) @@ -800,6 +856,8 @@ function ExportPage() { const selectedCount = selectedSessions.size const toggleSelectSession = (sessionId: string) => { + const target = sessions.find(session => session.username === sessionId) + if (!target?.hasSession) return setSelectedSessions(prev => { const next = new Set(prev) if (next.has(sessionId)) { @@ -812,7 +870,7 @@ function ExportPage() { } const toggleSelectAllVisible = () => { - const visibleIds = visibleSessions.map(session => session.username) + const visibleIds = visibleSessions.filter(session => session.hasSession).map(session => session.username) if (visibleIds.length === 0) return setSelectedSessions(prev => { @@ -1171,6 +1229,7 @@ function ExportPage() { } const openSingleExport = (session: SessionRow) => { + if (!session.hasSession) return openExportDialog({ scope: 'single', sessionIds: [session.username], @@ -1180,7 +1239,8 @@ function ExportPage() { } const openBatchExport = () => { - const ids = Array.from(selectedSessions) + const selectable = new Set(sessions.filter(session => session.hasSession).map(session => session.username)) + const ids = Array.from(selectedSessions).filter(id => selectable.has(id)) if (ids.length === 0) return const nameMap = new Map(sessions.map(session => [session.username, session.displayName || session.username])) const names = ids.map(id => nameMap.get(id) || id) @@ -1195,11 +1255,11 @@ function ExportPage() { const openContentExport = (contentType: ContentType) => { const ids = sessions - .filter(isContentScopeSession) + .filter(session => session.hasSession && isContentScopeSession(session)) .map(session => session.username) const names = sessions - .filter(isContentScopeSession) + .filter(session => session.hasSession && isContentScopeSession(session)) .map(session => session.displayName || session.username) openExportDialog({ @@ -1320,6 +1380,16 @@ function ExportPage() { } const renderActionCell = (session: SessionRow) => { + if (!session.hasSession) { + return ( +
+ +
+ ) + } + const isRunning = runningSessionIds.has(session.username) const isQueued = queuedSessionIds.has(session.username) const recent = formatRecentExportTime(lastExportBySession[session.username], nowTick) @@ -1347,22 +1417,24 @@ function ExportPage() { return ( 选择 - 会话名(头像/名称/微信号) + 联系人(头像/名称/微信号) 操作 ) } const renderRowCells = (session: SessionRow) => { - const checked = selectedSessions.has(session.username) + const selectable = session.hasSession + const checked = selectable && selectedSessions.has(session.username) return ( <> @@ -1661,134 +1733,136 @@ function ExportPage() { {exportDialog.open && (
-
event.stopPropagation()}> +
event.stopPropagation()}>

{exportDialog.title}

-
-

导出范围

-
- {scopeLabel} - {scopeCountLabel} -
-
- {exportDialog.sessionNames.slice(0, 20).map(name => ( - {name} - ))} - {exportDialog.sessionNames.length > 20 && ... 还有 {exportDialog.sessionNames.length - 20} 个} -
-
- -
-

对话文本导出格式选择

-
- {formatCandidateOptions.map(option => ( - - ))} -
-
- -
-

时间范围

-
- 导出全部时间 - +
+
+

导出范围

+
+ {scopeLabel} + {scopeCountLabel} +
+
+ {exportDialog.sessionNames.slice(0, 20).map(name => ( + {name} + ))} + {exportDialog.sessionNames.length > 20 && ... 还有 {exportDialog.sessionNames.length - 20} 个} +
- {!options.useAllTime && options.dateRange && ( -
-