mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
perf(export): remove private relation stats and avatar backfill overhead
This commit is contained in:
@@ -2722,128 +2722,6 @@ class ExportService {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private async collectPrivateMutualGroupStats(
|
|
||||||
privateWxid: string,
|
|
||||||
myWxid: string
|
|
||||||
): Promise<{
|
|
||||||
totalGroups: number
|
|
||||||
totalMessagesByMe: number
|
|
||||||
totalMessagesByPeer: number
|
|
||||||
totalMessagesCombined: number
|
|
||||||
groups: Array<{
|
|
||||||
wxid: string
|
|
||||||
displayName: string
|
|
||||||
myMessageCount: number
|
|
||||||
peerMessageCount: number
|
|
||||||
totalMessageCount: number
|
|
||||||
}>
|
|
||||||
}> {
|
|
||||||
const normalizedPrivateWxid = String(privateWxid || '').trim()
|
|
||||||
const normalizedMyWxid = String(myWxid || '').trim()
|
|
||||||
if (!normalizedPrivateWxid || !normalizedMyWxid) {
|
|
||||||
return {
|
|
||||||
totalGroups: 0,
|
|
||||||
totalMessagesByMe: 0,
|
|
||||||
totalMessagesByPeer: 0,
|
|
||||||
totalMessagesCombined: 0,
|
|
||||||
groups: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionsResult = await wcdbService.getSessions()
|
|
||||||
if (!sessionsResult.success || !sessionsResult.sessions) {
|
|
||||||
return {
|
|
||||||
totalGroups: 0,
|
|
||||||
totalMessagesByMe: 0,
|
|
||||||
totalMessagesByPeer: 0,
|
|
||||||
totalMessagesCombined: 0,
|
|
||||||
groups: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const groupIds = Array.from(
|
|
||||||
new Set(
|
|
||||||
(sessionsResult.sessions as Array<Record<string, any>>)
|
|
||||||
.map((row) => String(row.username || row.user_name || row.userName || '').trim())
|
|
||||||
.filter((username) => username.endsWith('@chatroom'))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (groupIds.length === 0) {
|
|
||||||
return {
|
|
||||||
totalGroups: 0,
|
|
||||||
totalMessagesByMe: 0,
|
|
||||||
totalMessagesByPeer: 0,
|
|
||||||
totalMessagesCombined: 0,
|
|
||||||
groups: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutualGroups = await parallelLimit(groupIds, 4, async (groupId) => {
|
|
||||||
const membersResult = await wcdbService.getGroupMembers(groupId)
|
|
||||||
if (!membersResult.success || !membersResult.members || membersResult.members.length === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasMe = false
|
|
||||||
let hasPeer = false
|
|
||||||
for (const member of membersResult.members) {
|
|
||||||
const memberWxid = this.extractGroupMemberUsername(member)
|
|
||||||
if (!memberWxid) continue
|
|
||||||
if (!hasMe && this.isSameWxid(memberWxid, normalizedMyWxid)) {
|
|
||||||
hasMe = true
|
|
||||||
}
|
|
||||||
if (!hasPeer && this.isSameWxid(memberWxid, normalizedPrivateWxid)) {
|
|
||||||
hasPeer = true
|
|
||||||
}
|
|
||||||
if (hasMe && hasPeer) break
|
|
||||||
}
|
|
||||||
if (!hasMe || !hasPeer) return null
|
|
||||||
|
|
||||||
const [groupInfo, groupStatsResult] = await Promise.all([
|
|
||||||
this.getContactInfo(groupId),
|
|
||||||
wcdbService.getGroupStats(groupId, 0, 0)
|
|
||||||
])
|
|
||||||
const senderCountMap = groupStatsResult.success && groupStatsResult.data
|
|
||||||
? this.extractGroupSenderCountMap(groupStatsResult.data, groupId)
|
|
||||||
: new Map<string, number>()
|
|
||||||
const myMessageCount = this.sumSenderCountsByIdentity(senderCountMap, normalizedMyWxid)
|
|
||||||
const peerMessageCount = this.sumSenderCountsByIdentity(senderCountMap, normalizedPrivateWxid)
|
|
||||||
const totalMessageCount = myMessageCount + peerMessageCount
|
|
||||||
|
|
||||||
return {
|
|
||||||
wxid: groupId,
|
|
||||||
displayName: groupInfo.displayName || groupId,
|
|
||||||
myMessageCount,
|
|
||||||
peerMessageCount,
|
|
||||||
totalMessageCount
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const groups = mutualGroups
|
|
||||||
.filter((item): item is {
|
|
||||||
wxid: string
|
|
||||||
displayName: string
|
|
||||||
myMessageCount: number
|
|
||||||
peerMessageCount: number
|
|
||||||
totalMessageCount: number
|
|
||||||
} => Boolean(item))
|
|
||||||
.sort((a, b) => {
|
|
||||||
if (b.totalMessageCount !== a.totalMessageCount) return b.totalMessageCount - a.totalMessageCount
|
|
||||||
return a.displayName.localeCompare(b.displayName, 'zh-CN')
|
|
||||||
})
|
|
||||||
|
|
||||||
const totalMessagesByMe = groups.reduce((sum, item) => sum + item.myMessageCount, 0)
|
|
||||||
const totalMessagesByPeer = groups.reduce((sum, item) => sum + item.peerMessageCount, 0)
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalGroups: groups.length,
|
|
||||||
totalMessagesByMe,
|
|
||||||
totalMessagesByPeer,
|
|
||||||
totalMessagesCombined: totalMessagesByMe + totalMessagesByPeer,
|
|
||||||
groups
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveAvatarFile(avatarUrl?: string): { data?: Buffer; sourcePath?: string; sourceUrl?: string; ext: string; mime?: string } | null {
|
private resolveAvatarFile(avatarUrl?: string): { data?: Buffer; sourcePath?: string; sourceUrl?: string; ext: string; mime?: string } | null {
|
||||||
if (!avatarUrl) return null
|
if (!avatarUrl) return null
|
||||||
if (avatarUrl.startsWith('data:')) {
|
if (avatarUrl.startsWith('data:')) {
|
||||||
@@ -3998,19 +3876,6 @@ class ExportService {
|
|||||||
const arkmeSession: any = {
|
const arkmeSession: any = {
|
||||||
...sessionPayload
|
...sessionPayload
|
||||||
}
|
}
|
||||||
let privateMutualGroups: {
|
|
||||||
totalGroups: number
|
|
||||||
totalMessagesByMe: number
|
|
||||||
totalMessagesByPeer: number
|
|
||||||
totalMessagesCombined: number
|
|
||||||
groups: Array<{
|
|
||||||
wxid: string
|
|
||||||
displayName: string
|
|
||||||
myMessageCount: number
|
|
||||||
peerMessageCount: number
|
|
||||||
totalMessageCount: number
|
|
||||||
}>
|
|
||||||
} | undefined
|
|
||||||
let groupMembers: Array<{
|
let groupMembers: Array<{
|
||||||
wxid: string
|
wxid: string
|
||||||
displayName: string
|
displayName: string
|
||||||
@@ -4082,8 +3947,6 @@ class ExportService {
|
|||||||
if (b.messageCount !== a.messageCount) return b.messageCount - a.messageCount
|
if (b.messageCount !== a.messageCount) return b.messageCount - a.messageCount
|
||||||
return String(a.displayName || a.wxid).localeCompare(String(b.displayName || b.wxid), 'zh-CN')
|
return String(a.displayName || a.wxid).localeCompare(String(b.displayName || b.wxid), 'zh-CN')
|
||||||
})
|
})
|
||||||
} else if (!sessionId.startsWith('gh_')) {
|
|
||||||
privateMutualGroups = await this.collectPrivateMutualGroupStats(sessionId, cleanedMyWxid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const arkmeExport: any = {
|
const arkmeExport: any = {
|
||||||
@@ -4095,9 +3958,6 @@ class ExportService {
|
|||||||
senders,
|
senders,
|
||||||
messages: compactMessages
|
messages: compactMessages
|
||||||
}
|
}
|
||||||
if (privateMutualGroups) {
|
|
||||||
arkmeExport.privateMutualGroups = privateMutualGroups
|
|
||||||
}
|
|
||||||
if (groupMembers) {
|
if (groupMembers) {
|
||||||
arkmeExport.groupMembers = groupMembers
|
arkmeExport.groupMembers = groupMembers
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -863,12 +863,6 @@ function ExportPage() {
|
|||||||
const [contactsLoadIssue, setContactsLoadIssue] = useState<ContactsLoadIssue | null>(null)
|
const [contactsLoadIssue, setContactsLoadIssue] = useState<ContactsLoadIssue | null>(null)
|
||||||
const [showContactsDiagnostics, setShowContactsDiagnostics] = useState(false)
|
const [showContactsDiagnostics, setShowContactsDiagnostics] = useState(false)
|
||||||
const [contactsDiagnosticTick, setContactsDiagnosticTick] = useState(Date.now())
|
const [contactsDiagnosticTick, setContactsDiagnosticTick] = useState(Date.now())
|
||||||
const [contactsAvatarEnrichProgress, setContactsAvatarEnrichProgress] = useState({
|
|
||||||
loaded: 0,
|
|
||||||
total: 0,
|
|
||||||
running: false,
|
|
||||||
tab: null as ConversationTab | null
|
|
||||||
})
|
|
||||||
const [showSessionDetailPanel, setShowSessionDetailPanel] = useState(false)
|
const [showSessionDetailPanel, setShowSessionDetailPanel] = useState(false)
|
||||||
const [sessionDetail, setSessionDetail] = useState<SessionDetail | null>(null)
|
const [sessionDetail, setSessionDetail] = useState<SessionDetail | null>(null)
|
||||||
const [isLoadingSessionDetail, setIsLoadingSessionDetail] = useState(false)
|
const [isLoadingSessionDetail, setIsLoadingSessionDetail] = useState(false)
|
||||||
@@ -941,8 +935,6 @@ function ExportPage() {
|
|||||||
const preselectAppliedRef = useRef(false)
|
const preselectAppliedRef = useRef(false)
|
||||||
const exportCacheScopeRef = useRef('default')
|
const exportCacheScopeRef = useRef('default')
|
||||||
const exportCacheScopeReadyRef = useRef(false)
|
const exportCacheScopeReadyRef = useRef(false)
|
||||||
const activeTabRef = useRef<ConversationTab>('private')
|
|
||||||
const contactsDataRef = useRef<ContactInfo[]>([])
|
|
||||||
const contactsLoadVersionRef = useRef(0)
|
const contactsLoadVersionRef = useRef(0)
|
||||||
const contactsLoadAttemptRef = useRef(0)
|
const contactsLoadAttemptRef = useRef(0)
|
||||||
const contactsLoadTimeoutTimerRef = useRef<number | null>(null)
|
const contactsLoadTimeoutTimerRef = useRef<number | null>(null)
|
||||||
@@ -1059,128 +1051,8 @@ function ExportPage() {
|
|||||||
contactsLoadTimeoutMsRef.current = contactsLoadTimeoutMs
|
contactsLoadTimeoutMsRef.current = contactsLoadTimeoutMs
|
||||||
}, [contactsLoadTimeoutMs])
|
}, [contactsLoadTimeoutMs])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
activeTabRef.current = activeTab
|
|
||||||
}, [activeTab])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
contactsDataRef.current = contactsList
|
|
||||||
}, [contactsList])
|
|
||||||
|
|
||||||
const applyEnrichedContactsToList = useCallback((enrichedMap: Record<string, { displayName?: string; avatarUrl?: string }>) => {
|
|
||||||
if (!enrichedMap || Object.keys(enrichedMap).length === 0) return
|
|
||||||
setContactsList(prev => {
|
|
||||||
let changed = false
|
|
||||||
const next = prev.map(contact => {
|
|
||||||
const enriched = enrichedMap[contact.username]
|
|
||||||
if (!enriched) return contact
|
|
||||||
const displayName = enriched.displayName || contact.displayName
|
|
||||||
const avatarUrl = enriched.avatarUrl || contact.avatarUrl
|
|
||||||
if (displayName === contact.displayName && avatarUrl === contact.avatarUrl) {
|
|
||||||
return contact
|
|
||||||
}
|
|
||||||
changed = true
|
|
||||||
return {
|
|
||||||
...contact,
|
|
||||||
displayName,
|
|
||||||
avatarUrl
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return changed ? next : prev
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const enrichContactsListInBackground = useCallback(async (
|
|
||||||
sourceContacts: ContactInfo[],
|
|
||||||
loadVersion: number,
|
|
||||||
scopeKey: string,
|
|
||||||
targetTab: ConversationTab
|
|
||||||
) => {
|
|
||||||
const sourceByUsername = new Map<string, ContactInfo>()
|
|
||||||
for (const contact of sourceContacts) {
|
|
||||||
if (!contact.username) continue
|
|
||||||
sourceByUsername.set(contact.username, contact)
|
|
||||||
}
|
|
||||||
|
|
||||||
const usernames = Array.from(new Set(sourceContacts
|
|
||||||
.filter(contact => matchesContactTab(contact, targetTab))
|
|
||||||
.map(contact => contact.username)
|
|
||||||
.filter(Boolean)
|
|
||||||
.filter((username) => {
|
|
||||||
const currentContact = sourceByUsername.get(username)
|
|
||||||
return Boolean(currentContact && !currentContact.avatarUrl)
|
|
||||||
})))
|
|
||||||
|
|
||||||
const total = usernames.length
|
|
||||||
setContactsAvatarEnrichProgress({
|
|
||||||
loaded: 0,
|
|
||||||
total,
|
|
||||||
running: total > 0,
|
|
||||||
tab: targetTab
|
|
||||||
})
|
|
||||||
if (total === 0) return
|
|
||||||
|
|
||||||
for (let i = 0; i < total; i += EXPORT_AVATAR_ENRICH_BATCH_SIZE) {
|
|
||||||
if (contactsLoadVersionRef.current !== loadVersion) return
|
|
||||||
const batch = usernames.slice(i, i + EXPORT_AVATAR_ENRICH_BATCH_SIZE)
|
|
||||||
if (batch.length === 0) continue
|
|
||||||
|
|
||||||
try {
|
|
||||||
const avatarResult = await withTimeout(
|
|
||||||
window.electronAPI.chat.enrichSessionsContactInfo(batch, {
|
|
||||||
skipDisplayName: true,
|
|
||||||
onlyMissingAvatar: true
|
|
||||||
}),
|
|
||||||
CONTACT_ENRICH_TIMEOUT_MS
|
|
||||||
)
|
|
||||||
if (contactsLoadVersionRef.current !== loadVersion) return
|
|
||||||
if (avatarResult?.success && avatarResult.contacts) {
|
|
||||||
applyEnrichedContactsToList(avatarResult.contacts)
|
|
||||||
for (const [username, enriched] of Object.entries(avatarResult.contacts)) {
|
|
||||||
const prev = sourceByUsername.get(username)
|
|
||||||
if (!prev) continue
|
|
||||||
sourceByUsername.set(username, {
|
|
||||||
...prev,
|
|
||||||
displayName: enriched.displayName || prev.displayName,
|
|
||||||
avatarUrl: enriched.avatarUrl || prev.avatarUrl
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const batchContacts = batch
|
|
||||||
.map(username => sourceByUsername.get(username))
|
|
||||||
.filter((contact): contact is ContactInfo => Boolean(contact))
|
|
||||||
const upsertResult = upsertAvatarCacheFromContacts(
|
|
||||||
contactsAvatarCacheRef.current,
|
|
||||||
batchContacts,
|
|
||||||
{ markCheckedUsernames: batch }
|
|
||||||
)
|
|
||||||
contactsAvatarCacheRef.current = upsertResult.avatarEntries
|
|
||||||
if (upsertResult.updatedAt) {
|
|
||||||
setAvatarCacheUpdatedAt(upsertResult.updatedAt)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('导出页分批补全头像失败:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const loaded = Math.min(i + batch.length, total)
|
|
||||||
setContactsAvatarEnrichProgress({
|
|
||||||
loaded,
|
|
||||||
total,
|
|
||||||
running: loaded < total,
|
|
||||||
tab: targetTab
|
|
||||||
})
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
void configService.setContactsAvatarCache(scopeKey, contactsAvatarCacheRef.current).catch((error) => {
|
|
||||||
console.error('写入导出页头像缓存失败:', error)
|
|
||||||
})
|
|
||||||
}, [applyEnrichedContactsToList])
|
|
||||||
|
|
||||||
const loadContactsList = useCallback(async (options?: { scopeKey?: string }) => {
|
const loadContactsList = useCallback(async (options?: { scopeKey?: string }) => {
|
||||||
const scopeKey = options?.scopeKey || await ensureExportCacheScope()
|
const scopeKey = options?.scopeKey || await ensureExportCacheScope()
|
||||||
const targetTab = activeTabRef.current
|
|
||||||
const loadVersion = contactsLoadVersionRef.current + 1
|
const loadVersion = contactsLoadVersionRef.current + 1
|
||||||
contactsLoadVersionRef.current = loadVersion
|
contactsLoadVersionRef.current = loadVersion
|
||||||
contactsLoadAttemptRef.current += 1
|
contactsLoadAttemptRef.current += 1
|
||||||
@@ -1214,13 +1086,6 @@ function ExportPage() {
|
|||||||
contactsLoadTimeoutTimerRef.current = timeoutTimerId
|
contactsLoadTimeoutTimerRef.current = timeoutTimerId
|
||||||
|
|
||||||
setIsContactsListLoading(true)
|
setIsContactsListLoading(true)
|
||||||
setContactsAvatarEnrichProgress({
|
|
||||||
loaded: 0,
|
|
||||||
total: 0,
|
|
||||||
running: false,
|
|
||||||
tab: null
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const contactsResult = await window.electronAPI.chat.getContacts()
|
const contactsResult = await window.electronAPI.chat.getContacts()
|
||||||
if (contactsLoadVersionRef.current !== loadVersion) return
|
if (contactsLoadVersionRef.current !== loadVersion) return
|
||||||
@@ -1266,7 +1131,6 @@ function ExportPage() {
|
|||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
console.error('写入导出页通讯录缓存失败:', error)
|
console.error('写入导出页通讯录缓存失败:', error)
|
||||||
})
|
})
|
||||||
void enrichContactsListInBackground(contactsWithAvatarCache, loadVersion, scopeKey, targetTab)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1301,7 +1165,7 @@ function ExportPage() {
|
|||||||
setIsContactsListLoading(false)
|
setIsContactsListLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [ensureExportCacheScope, enrichContactsListInBackground, syncContactTypeCounts])
|
}, [ensureExportCacheScope, syncContactTypeCounts])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isExportRoute) return
|
if (!isExportRoute) return
|
||||||
@@ -1341,29 +1205,9 @@ function ExportPage() {
|
|||||||
}
|
}
|
||||||
}, [isExportRoute, ensureExportCacheScope, loadContactsList, syncContactTypeCounts])
|
}, [isExportRoute, ensureExportCacheScope, loadContactsList, syncContactTypeCounts])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isExportRoute || isContactsListLoading || contactsDataRef.current.length === 0) return
|
|
||||||
let cancelled = false
|
|
||||||
const loadVersion = contactsLoadVersionRef.current
|
|
||||||
void (async () => {
|
|
||||||
const scopeKey = await ensureExportCacheScope()
|
|
||||||
if (cancelled || contactsLoadVersionRef.current !== loadVersion) return
|
|
||||||
await enrichContactsListInBackground(contactsDataRef.current, loadVersion, scopeKey, activeTab)
|
|
||||||
})()
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
}
|
|
||||||
}, [activeTab, ensureExportCacheScope, enrichContactsListInBackground, isContactsListLoading, isExportRoute])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isExportRoute) return
|
if (isExportRoute) return
|
||||||
contactsLoadVersionRef.current += 1
|
contactsLoadVersionRef.current += 1
|
||||||
setContactsAvatarEnrichProgress({
|
|
||||||
loaded: 0,
|
|
||||||
total: 0,
|
|
||||||
running: false,
|
|
||||||
tab: null
|
|
||||||
})
|
|
||||||
}, [isExportRoute])
|
}, [isExportRoute])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -3203,8 +3047,6 @@ function ExportPage() {
|
|||||||
contact.avatarUrl ? count + 1 : count
|
contact.avatarUrl ? count + 1 : count
|
||||||
), 0)
|
), 0)
|
||||||
}, [contactsList])
|
}, [contactsList])
|
||||||
const isCurrentTabAvatarEnrichRunning = contactsAvatarEnrichProgress.running && contactsAvatarEnrichProgress.tab === activeTab
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!contactsListRef.current) return
|
if (!contactsListRef.current) return
|
||||||
contactsListRef.current.scrollTop = 0
|
contactsListRef.current.scrollTop = 0
|
||||||
@@ -3938,20 +3780,15 @@ function ExportPage() {
|
|||||||
{avatarCacheUpdatedAtLabel ? ` · 更新于 ${avatarCacheUpdatedAtLabel}` : ''}
|
{avatarCacheUpdatedAtLabel ? ` · 更新于 ${avatarCacheUpdatedAtLabel}` : ''}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{(isContactsListLoading || isCurrentTabAvatarEnrichRunning) && contactsList.length > 0 && (
|
{isContactsListLoading && contactsList.length > 0 && (
|
||||||
<span className="meta-item syncing">后台同步中...</span>
|
<span className="meta-item syncing">后台同步中...</span>
|
||||||
)}
|
)}
|
||||||
{isCurrentTabAvatarEnrichRunning && (
|
|
||||||
<span className="meta-item syncing">
|
|
||||||
头像补全中 {contactsAvatarEnrichProgress.loaded}/{contactsAvatarEnrichProgress.total}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{contactsList.length > 0 && (isContactsListLoading || isCurrentTabAvatarEnrichRunning) && (
|
{contactsList.length > 0 && isContactsListLoading && (
|
||||||
<div className="table-stage-hint">
|
<div className="table-stage-hint">
|
||||||
<Loader2 size={14} className="spin" />
|
<Loader2 size={14} className="spin" />
|
||||||
{isContactsListLoading ? '联系人列表同步中…' : '正在补充头像…'}
|
联系人列表同步中…
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user