From 25325e80ee8bcf3761ad40bca354f13fe4cfdf0b Mon Sep 17 00:00:00 2001 From: xuncha <1658671838@qq.com> Date: Thu, 19 Feb 2026 16:49:46 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=9A=E8=AE=AF=E5=BD=95=E5=8F=AF=E5=8B=BE?= =?UTF-8?q?=E9=80=89=E9=83=A8=E5=88=86=E5=A5=BD=E5=8F=8B=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/services/contactExportService.ts | 6 ++ src/pages/ContactsPage.scss | 39 +++++++- src/pages/ContactsPage.tsx | 108 +++++++++++++++++----- 3 files changed, 130 insertions(+), 23 deletions(-) diff --git a/electron/services/contactExportService.ts b/electron/services/contactExportService.ts index 6a33432..11efee5 100644 --- a/electron/services/contactExportService.ts +++ b/electron/services/contactExportService.ts @@ -10,6 +10,7 @@ interface ContactExportOptions { groups: boolean officials: boolean } + selectedUsernames?: string[] } /** @@ -40,6 +41,11 @@ class ContactExportService { return true }) + if (Array.isArray(options.selectedUsernames) && options.selectedUsernames.length > 0) { + const selectedSet = new Set(options.selectedUsernames) + contacts = contacts.filter(c => selectedSet.has(c.username)) + } + if (contacts.length === 0) { return { success: false, error: '没有符合条件的联系人' } } diff --git a/src/pages/ContactsPage.scss b/src/pages/ContactsPage.scss index d64dc46..2609639 100644 --- a/src/pages/ContactsPage.scss +++ b/src/pages/ContactsPage.scss @@ -174,6 +174,24 @@ color: var(--text-secondary); } + .selection-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 0 20px 12px; + + .checkbox-item { + font-size: 13px; + color: var(--text-secondary); + } + + .selection-count { + font-size: 12px; + color: var(--text-tertiary); + } + } + .loading-state, .empty-state { flex: 1; @@ -214,11 +232,30 @@ border-radius: 10px; transition: all 0.2s; margin-bottom: 4px; + cursor: pointer; &:hover { background: var(--bg-hover); } + &.selected { + background: color-mix(in srgb, var(--primary) 12%, transparent); + } + + .contact-select { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + input[type="checkbox"] { + width: 16px; + height: 16px; + cursor: pointer; + accent-color: var(--primary); + } + } + .contact-avatar { width: 44px; height: 44px; @@ -548,4 +585,4 @@ to { transform: rotate(360deg); } -} \ No newline at end of file +} diff --git a/src/pages/ContactsPage.tsx b/src/pages/ContactsPage.tsx index 27868e7..43968ae 100644 --- a/src/pages/ContactsPage.tsx +++ b/src/pages/ContactsPage.tsx @@ -14,6 +14,7 @@ interface ContactInfo { function ContactsPage() { const [contacts, setContacts] = useState([]) const [filteredContacts, setFilteredContacts] = useState([]) + const [selectedUsernames, setSelectedUsernames] = useState>(new Set()) const [isLoading, setIsLoading] = useState(true) const [searchKeyword, setSearchKeyword] = useState('') const [contactTypes, setContactTypes] = useState({ @@ -62,6 +63,7 @@ function ContactsPage() { setContacts(contactsResult.contacts) setFilteredContacts(contactsResult.contacts) + setSelectedUsernames(new Set()) } } catch (e) { console.error('加载通讯录失败:', e) @@ -111,6 +113,37 @@ function ContactsPage() { return () => document.removeEventListener('mousedown', handleClickOutside) }, [showFormatSelect]) + const selectedInFilteredCount = filteredContacts.reduce((count, contact) => { + return selectedUsernames.has(contact.username) ? count + 1 : count + }, 0) + const allFilteredSelected = filteredContacts.length > 0 && selectedInFilteredCount === filteredContacts.length + + const toggleContactSelected = (username: string, checked: boolean) => { + setSelectedUsernames(prev => { + const next = new Set(prev) + if (checked) { + next.add(username) + } else { + next.delete(username) + } + return next + }) + } + + const toggleAllFilteredSelected = (checked: boolean) => { + setSelectedUsernames(prev => { + const next = new Set(prev) + filteredContacts.forEach(contact => { + if (checked) { + next.add(contact.username) + } else { + next.delete(contact.username) + } + }) + return next + }) + } + const getAvatarLetter = (name: string) => { if (!name) return '?' return [...name][0] || '?' @@ -154,6 +187,10 @@ function ContactsPage() { alert('请先选择导出位置') return } + if (selectedUsernames.size === 0) { + alert('请至少选择一个联系人') + return + } setIsExporting(true) try { @@ -164,7 +201,8 @@ function ContactsPage() { friends: contactTypes.friends, groups: contactTypes.groups, officials: contactTypes.officials - } + }, + selectedUsernames: Array.from(selectedUsernames) } const result = await window.electronAPI.export.exportContacts(exportFolder, exportOptions) @@ -251,6 +289,18 @@ function ContactsPage() {
共 {filteredContacts.length} 个联系人
+
+ + 已选 {selectedUsernames.size}(当前筛选 {selectedInFilteredCount} / {filteredContacts.length}) +
{isLoading ? (
@@ -263,27 +313,41 @@ function ContactsPage() {
) : (
- {filteredContacts.map(contact => ( -
-
- {contact.avatarUrl ? ( - - ) : ( - {getAvatarLetter(contact.displayName)} - )} + {filteredContacts.map(contact => { + const isSelected = selectedUsernames.has(contact.username) + return ( +
toggleContactSelected(contact.username, !isSelected)} + > + +
+ {contact.avatarUrl ? ( + + ) : ( + {getAvatarLetter(contact.displayName)} + )} +
+
+
{contact.displayName}
+ {contact.remark && contact.remark !== contact.displayName && ( +
备注: {contact.remark}
+ )} +
+
+ {getContactTypeIcon(contact.type)} + {getContactTypeName(contact.type)} +
-
-
{contact.displayName}
- {contact.remark && contact.remark !== contact.displayName && ( -
备注: {contact.remark}
- )} -
-
- {getContactTypeIcon(contact.type)} - {getContactTypeName(contact.type)} -
-
- ))} + ) + })}
)}
@@ -356,7 +420,7 @@ function ContactsPage() {