mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
通讯录可勾选部分好友导出
This commit is contained in:
@@ -10,6 +10,7 @@ interface ContactExportOptions {
|
|||||||
groups: boolean
|
groups: boolean
|
||||||
officials: boolean
|
officials: boolean
|
||||||
}
|
}
|
||||||
|
selectedUsernames?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,6 +41,11 @@ class ContactExportService {
|
|||||||
return true
|
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) {
|
if (contacts.length === 0) {
|
||||||
return { success: false, error: '没有符合条件的联系人' }
|
return { success: false, error: '没有符合条件的联系人' }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,6 +174,24 @@
|
|||||||
color: var(--text-secondary);
|
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,
|
.loading-state,
|
||||||
.empty-state {
|
.empty-state {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -214,11 +232,30 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--bg-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 {
|
.contact-avatar {
|
||||||
width: 44px;
|
width: 44px;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface ContactInfo {
|
|||||||
function ContactsPage() {
|
function ContactsPage() {
|
||||||
const [contacts, setContacts] = useState<ContactInfo[]>([])
|
const [contacts, setContacts] = useState<ContactInfo[]>([])
|
||||||
const [filteredContacts, setFilteredContacts] = useState<ContactInfo[]>([])
|
const [filteredContacts, setFilteredContacts] = useState<ContactInfo[]>([])
|
||||||
|
const [selectedUsernames, setSelectedUsernames] = useState<Set<string>>(new Set())
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [searchKeyword, setSearchKeyword] = useState('')
|
const [searchKeyword, setSearchKeyword] = useState('')
|
||||||
const [contactTypes, setContactTypes] = useState({
|
const [contactTypes, setContactTypes] = useState({
|
||||||
@@ -62,6 +63,7 @@ function ContactsPage() {
|
|||||||
|
|
||||||
setContacts(contactsResult.contacts)
|
setContacts(contactsResult.contacts)
|
||||||
setFilteredContacts(contactsResult.contacts)
|
setFilteredContacts(contactsResult.contacts)
|
||||||
|
setSelectedUsernames(new Set())
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载通讯录失败:', e)
|
console.error('加载通讯录失败:', e)
|
||||||
@@ -111,6 +113,37 @@ function ContactsPage() {
|
|||||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||||
}, [showFormatSelect])
|
}, [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) => {
|
const getAvatarLetter = (name: string) => {
|
||||||
if (!name) return '?'
|
if (!name) return '?'
|
||||||
return [...name][0] || '?'
|
return [...name][0] || '?'
|
||||||
@@ -154,6 +187,10 @@ function ContactsPage() {
|
|||||||
alert('请先选择导出位置')
|
alert('请先选择导出位置')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (selectedUsernames.size === 0) {
|
||||||
|
alert('请至少选择一个联系人')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setIsExporting(true)
|
setIsExporting(true)
|
||||||
try {
|
try {
|
||||||
@@ -164,7 +201,8 @@ function ContactsPage() {
|
|||||||
friends: contactTypes.friends,
|
friends: contactTypes.friends,
|
||||||
groups: contactTypes.groups,
|
groups: contactTypes.groups,
|
||||||
officials: contactTypes.officials
|
officials: contactTypes.officials
|
||||||
}
|
},
|
||||||
|
selectedUsernames: Array.from(selectedUsernames)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await window.electronAPI.export.exportContacts(exportFolder, exportOptions)
|
const result = await window.electronAPI.export.exportContacts(exportFolder, exportOptions)
|
||||||
@@ -251,6 +289,18 @@ function ContactsPage() {
|
|||||||
<div className="contacts-count">
|
<div className="contacts-count">
|
||||||
共 {filteredContacts.length} 个联系人
|
共 {filteredContacts.length} 个联系人
|
||||||
</div>
|
</div>
|
||||||
|
<div className="selection-toolbar">
|
||||||
|
<label className="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={allFilteredSelected}
|
||||||
|
onChange={e => toggleAllFilteredSelected(e.target.checked)}
|
||||||
|
disabled={filteredContacts.length === 0}
|
||||||
|
/>
|
||||||
|
<span>全选当前筛选结果</span>
|
||||||
|
</label>
|
||||||
|
<span className="selection-count">已选 {selectedUsernames.size}(当前筛选 {selectedInFilteredCount} / {filteredContacts.length})</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="loading-state">
|
<div className="loading-state">
|
||||||
@@ -263,8 +313,21 @@ function ContactsPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="contacts-list">
|
<div className="contacts-list">
|
||||||
{filteredContacts.map(contact => (
|
{filteredContacts.map(contact => {
|
||||||
<div key={contact.username} className="contact-item">
|
const isSelected = selectedUsernames.has(contact.username)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={contact.username}
|
||||||
|
className={`contact-item ${isSelected ? 'selected' : ''}`}
|
||||||
|
onClick={() => toggleContactSelected(contact.username, !isSelected)}
|
||||||
|
>
|
||||||
|
<label className="contact-select" onClick={e => e.stopPropagation()}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={isSelected}
|
||||||
|
onChange={e => toggleContactSelected(contact.username, e.target.checked)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
<div className="contact-avatar">
|
<div className="contact-avatar">
|
||||||
{contact.avatarUrl ? (
|
{contact.avatarUrl ? (
|
||||||
<img src={contact.avatarUrl} alt="" />
|
<img src={contact.avatarUrl} alt="" />
|
||||||
@@ -283,7 +346,8 @@ function ContactsPage() {
|
|||||||
<span>{getContactTypeName(contact.type)}</span>
|
<span>{getContactTypeName(contact.type)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -356,7 +420,7 @@ function ContactsPage() {
|
|||||||
<button
|
<button
|
||||||
className="export-btn"
|
className="export-btn"
|
||||||
onClick={startExport}
|
onClick={startExport}
|
||||||
disabled={!exportFolder || isExporting}
|
disabled={!exportFolder || isExporting || selectedUsernames.size === 0}
|
||||||
>
|
>
|
||||||
{isExporting ? (
|
{isExporting ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in New Issue
Block a user