diff --git a/frontend/components/chat/ChatOverlays.vue b/frontend/components/chat/ChatOverlays.vue index 0b8999e..efd53e2 100644 --- a/frontend/components/chat/ChatOverlays.vue +++ b/frontend/components/chat/ChatOverlays.vue @@ -1290,52 +1290,77 @@
-
+
范围
-
- - +
+ + + +
格式
-
-
+
时间范围(可选)
-
+
-
@@ -1367,32 +1392,8 @@
-
- - - -
点击 tab 筛选
+
+ 点击上方范围可筛选并默认全选当前结果,再次点击可取消全选;下方整行可点选会话
-
- +
{{ (c.name || c.username || '?').charAt(0) }}
-
+
{{ c.name }} {{ c.isGroup ? '(群)' : '' }}
{{ c.username }}
-
+
无匹配会话
diff --git a/frontend/composables/chat/useChatExport.js b/frontend/composables/chat/useChatExport.js index bad2c48..5843f76 100644 --- a/frontend/composables/chat/useChatExport.js +++ b/frontend/composables/chat/useChatExport.js @@ -73,20 +73,35 @@ export const useChatExport = ({ api, apiBase, contacts, selectedAccount, selecte return Math.round(clamp01(done / total) * 100) }) - const exportFilteredContacts = computed(() => { - const query = String(exportSearchQuery.value || '').trim().toLowerCase() + const normalizeExportSelectedUsernames = (list) => { + const seen = new Set() + return (Array.isArray(list) ? list : []).reduce((acc, item) => { + const username = String(item || '').trim() + if (!username || seen.has(username)) return acc + seen.add(username) + acc.push(username) + return acc + }, []) + } + + const getExportFilteredContacts = ({ tab = exportListTab.value, query = exportSearchQuery.value } = {}) => { + const normalizedQuery = String(query || '').trim().toLowerCase() let list = Array.isArray(contacts.value) ? contacts.value : [] - const tab = String(exportListTab.value || 'all') - if (tab === 'groups') list = list.filter((contact) => !!contact?.isGroup) - if (tab === 'singles') list = list.filter((contact) => !contact?.isGroup) + const normalizedTab = String(tab || 'all') + if (normalizedTab === 'groups') list = list.filter((contact) => !!contact?.isGroup) + if (normalizedTab === 'singles') list = list.filter((contact) => !contact?.isGroup) - if (!query) return list + if (!normalizedQuery) return list return list.filter((contact) => { const name = String(contact?.name || '').toLowerCase() const username = String(contact?.username || '').toLowerCase() - return name.includes(query) || username.includes(query) + return name.includes(normalizedQuery) || username.includes(normalizedQuery) }) + } + + const exportFilteredContacts = computed(() => { + return getExportFilteredContacts() }) const exportContactCounts = computed(() => { @@ -96,6 +111,60 @@ export const useChatExport = ({ api, apiBase, contacts, selectedAccount, selecte return { total, groups, singles: total - groups } }) + const exportSelectedUsernameSet = computed(() => { + return new Set(normalizeExportSelectedUsernames(exportSelectedUsernames.value)) + }) + + const setExportSelectedUsernames = (list) => { + exportSelectedUsernames.value = normalizeExportSelectedUsernames(list) + } + + const getExportFilteredUsernames = (tab = exportListTab.value) => { + return getExportFilteredContacts({ tab }) + .map((contact) => String(contact?.username || '').trim()) + .filter(Boolean) + } + + const selectExportFilteredContacts = (tab = exportListTab.value) => { + setExportSelectedUsernames(getExportFilteredUsernames(tab)) + } + + const clearExportFilteredContacts = () => { + setExportSelectedUsernames([]) + } + + const areExportFilteredContactsAllSelected = (tab = exportListTab.value) => { + const usernames = getExportFilteredUsernames(tab) + if (usernames.length !== exportSelectedUsernameSet.value.size) return false + return usernames.every((username) => exportSelectedUsernameSet.value.has(username)) + } + + const onExportListTabClick = (tab) => { + const nextTab = String(tab || 'all') + const isSameTab = String(exportListTab.value || 'all') === nextTab + exportListTab.value = nextTab + + if (isSameTab) { + if (areExportFilteredContactsAllSelected(nextTab)) { + clearExportFilteredContacts(nextTab) + } else { + selectExportFilteredContacts(nextTab) + } + return + } + + selectExportFilteredContacts(nextTab) + } + + const isExportContactSelected = (username) => { + return exportSelectedUsernameSet.value.has(String(username || '').trim()) + } + + const onExportBatchScopeClick = (tab) => { + exportScope.value = 'selected' + onExportListTabClick(tab) + } + const isDesktopExportRuntime = () => { return !!(process.client && window?.wechatDesktop?.chooseDirectory) } @@ -269,12 +338,17 @@ export const useChatExport = ({ api, apiBase, contacts, selectedAccount, selecte exportModalOpen.value = true exportError.value = '' exportSaveMsg.value = '' + exportSearchQuery.value = '' exportListTab.value = 'all' + exportSelectedUsernames.value = [] exportStartLocal.value = '' exportEndLocal.value = '' exportMessageTypes.value = exportMessageTypeOptions.map((item) => item.value) exportAutoSavedFor.value = '' - exportScope.value = selectedContact.value?.username ? 'current' : 'all' + exportScope.value = selectedContact.value?.username ? 'current' : 'selected' + if (!selectedContact.value?.username) { + selectExportFilteredContacts('all') + } } const closeExportModal = () => { @@ -296,6 +370,12 @@ export const useChatExport = ({ api, apiBase, contacts, selectedAccount, selecte } }) + watch(exportScope, (scope, previousScope) => { + if (scope !== 'selected' || previousScope === 'selected') return + if (exportSelectedUsernames.value.length > 0) return + selectExportFilteredContacts(exportListTab.value) + }) + watch( () => ({ exportId: String(exportJob.value?.exportId || ''), @@ -447,6 +527,9 @@ export const useChatExport = ({ api, apiBase, contacts, selectedAccount, selecte exportCurrentPercent, exportFilteredContacts, exportContactCounts, + onExportBatchScopeClick, + onExportListTabClick, + isExportContactSelected, hasWebExportFolder, chooseExportFolder, getExportDownloadUrl,