fix: 修复了企业微信会显示出id的问题 增加了新手教程提示

This commit is contained in:
xuncha
2026-01-13 17:50:56 +08:00
parent 756ee03aa0
commit cada002587
3 changed files with 90 additions and 79 deletions

6
.gitignore vendored
View File

@@ -42,6 +42,10 @@ release
# OS # OS
Thumbs.db Thumbs.db
# Electron dev cache
.electron/
.cache/
# 忽略 Visual Studio 临时文件夹 # 忽略 Visual Studio 临时文件夹
@@ -50,4 +54,4 @@ Thumbs.db
*.ipch *.ipch
*.aps *.aps
wcdb/ wcdb/

View File

@@ -97,7 +97,7 @@ const SessionItem = React.memo(function SessionItem({
formatTime: (timestamp: number) => string formatTime: (timestamp: number) => string
}) { }) {
// 缓存格式化的时间 // 缓存格式化的时间
const timeText = useMemo(() => const timeText = useMemo(() =>
formatTime(session.lastTimestamp || session.sortTimestamp), formatTime(session.lastTimestamp || session.sortTimestamp),
[formatTime, session.lastTimestamp, session.sortTimestamp] [formatTime, session.lastTimestamp, session.sortTimestamp]
) )
@@ -287,7 +287,7 @@ function ChatPage(_props: ChatPageProps) {
const [highlightedMessageKeys, setHighlightedMessageKeys] = useState<string[]>([]) const [highlightedMessageKeys, setHighlightedMessageKeys] = useState<string[]>([])
const [isRefreshingSessions, setIsRefreshingSessions] = useState(false) const [isRefreshingSessions, setIsRefreshingSessions] = useState(false)
const [hasInitialMessages, setHasInitialMessages] = useState(false) const [hasInitialMessages, setHasInitialMessages] = useState(false)
// 联系人信息加载控制 // 联系人信息加载控制
const isEnrichingRef = useRef(false) const isEnrichingRef = useRef(false)
const enrichCancelledRef = useRef(false) const enrichCancelledRef = useRef(false)
@@ -407,28 +407,28 @@ function ChatPage(_props: ChatPageProps) {
// 分批异步加载联系人信息(优化性能:防止重复加载,滚动时暂停,只在空闲时加载) // 分批异步加载联系人信息(优化性能:防止重复加载,滚动时暂停,只在空闲时加载)
const enrichSessionsContactInfo = async (sessions: ChatSession[]) => { const enrichSessionsContactInfo = async (sessions: ChatSession[]) => {
if (sessions.length === 0) return if (sessions.length === 0) return
// 防止重复加载 // 防止重复加载
if (isEnrichingRef.current) { if (isEnrichingRef.current) {
console.log('[性能监控] 联系人信息正在加载中,跳过重复请求') console.log('[性能监控] 联系人信息正在加载中,跳过重复请求')
return return
} }
isEnrichingRef.current = true isEnrichingRef.current = true
enrichCancelledRef.current = false enrichCancelledRef.current = false
console.log(`[性能监控] 开始加载联系人信息,会话数: ${sessions.length}`) console.log(`[性能监控] 开始加载联系人信息,会话数: ${sessions.length}`)
const totalStart = performance.now() const totalStart = performance.now()
// 延迟启动等待UI渲染完成 // 延迟启动等待UI渲染完成
await new Promise(resolve => setTimeout(resolve, 500)) await new Promise(resolve => setTimeout(resolve, 500))
// 检查是否被取消 // 检查是否被取消
if (enrichCancelledRef.current) { if (enrichCancelledRef.current) {
isEnrichingRef.current = false isEnrichingRef.current = false
return return
} }
try { try {
// 找出需要加载联系人信息的会话(没有缓存的) // 找出需要加载联系人信息的会话(没有缓存的)
const needEnrich = sessions.filter(s => !s.avatarUrl && (!s.displayName || s.displayName === s.username)) const needEnrich = sessions.filter(s => !s.avatarUrl && (!s.displayName || s.displayName === s.username))
@@ -443,7 +443,7 @@ function ChatPage(_props: ChatPageProps) {
// 进一步减少批次大小每批3个避免DLL调用阻塞 // 进一步减少批次大小每批3个避免DLL调用阻塞
const batchSize = 3 const batchSize = 3
let loadedCount = 0 let loadedCount = 0
for (let i = 0; i < needEnrich.length; i += batchSize) { for (let i = 0; i < needEnrich.length; i += batchSize) {
// 如果正在滚动,暂停加载 // 如果正在滚动,暂停加载
if (isScrollingRef.current) { if (isScrollingRef.current) {
@@ -454,14 +454,14 @@ function ChatPage(_props: ChatPageProps) {
} }
if (enrichCancelledRef.current) break if (enrichCancelledRef.current) break
} }
// 检查是否被取消 // 检查是否被取消
if (enrichCancelledRef.current) break if (enrichCancelledRef.current) break
const batchStart = performance.now() const batchStart = performance.now()
const batch = needEnrich.slice(i, i + batchSize) const batch = needEnrich.slice(i, i + batchSize)
const usernames = batch.map(s => s.username) const usernames = batch.map(s => s.username)
// 使用 requestIdleCallback 延迟执行避免阻塞UI // 使用 requestIdleCallback 延迟执行避免阻塞UI
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
if ('requestIdleCallback' in window) { if ('requestIdleCallback' in window) {
@@ -474,13 +474,13 @@ function ChatPage(_props: ChatPageProps) {
}, 300) }, 300)
} }
}) })
loadedCount += batch.length loadedCount += batch.length
const batchTime = performance.now() - batchStart const batchTime = performance.now() - batchStart
if (batchTime > 200) { if (batchTime > 200) {
console.warn(`[性能监控] 批次 ${Math.floor(i / batchSize) + 1}/${Math.ceil(needEnrich.length / batchSize)} 耗时: ${batchTime.toFixed(2)}ms (已加载: ${loadedCount}/${needEnrich.length})`) console.warn(`[性能监控] 批次 ${Math.floor(i / batchSize) + 1}/${Math.ceil(needEnrich.length / batchSize)} 耗时: ${batchTime.toFixed(2)}ms (已加载: ${loadedCount}/${needEnrich.length})`)
} }
// 批次间延迟给UI更多时间DLL调用可能阻塞需要更长的延迟 // 批次间延迟给UI更多时间DLL调用可能阻塞需要更长的延迟
if (i + batchSize < needEnrich.length && !enrichCancelledRef.current) { if (i + batchSize < needEnrich.length && !enrichCancelledRef.current) {
// 如果不在滚动,可以延迟短一点 // 如果不在滚动,可以延迟短一点
@@ -488,7 +488,7 @@ function ChatPage(_props: ChatPageProps) {
await new Promise(resolve => setTimeout(resolve, delay)) await new Promise(resolve => setTimeout(resolve, delay))
} }
} }
const totalTime = performance.now() - totalStart const totalTime = performance.now() - totalStart
if (!enrichCancelledRef.current) { if (!enrichCancelledRef.current) {
console.log(`[性能监控] 联系人信息加载完成,总耗时: ${totalTime.toFixed(2)}ms, 已加载: ${loadedCount}/${needEnrich.length}`) console.log(`[性能监控] 联系人信息加载完成,总耗时: ${totalTime.toFixed(2)}ms, 已加载: ${loadedCount}/${needEnrich.length}`)
@@ -570,19 +570,19 @@ function ChatPage(_props: ChatPageProps) {
try { try {
// 在 DLL 调用前让出控制权(使用 setTimeout 0 代替 setImmediate // 在 DLL 调用前让出控制权(使用 setTimeout 0 代替 setImmediate
await new Promise(resolve => setTimeout(resolve, 0)) await new Promise(resolve => setTimeout(resolve, 0))
const dllStart = performance.now() const dllStart = performance.now()
const result = await window.electronAPI.chat.enrichSessionsContactInfo(usernames) const result = await window.electronAPI.chat.enrichSessionsContactInfo(usernames)
const dllTime = performance.now() - dllStart const dllTime = performance.now() - dllStart
// DLL 调用后再次让出控制权 // DLL 调用后再次让出控制权
await new Promise(resolve => setTimeout(resolve, 0)) await new Promise(resolve => setTimeout(resolve, 0))
const totalTime = performance.now() - startTime const totalTime = performance.now() - startTime
if (dllTime > 50 || totalTime > 100) { if (dllTime > 50 || totalTime > 100) {
console.warn(`[性能监控] DLL调用耗时: ${dllTime.toFixed(2)}ms, 总耗时: ${totalTime.toFixed(2)}ms, usernames: ${usernames.length}`) console.warn(`[性能监控] DLL调用耗时: ${dllTime.toFixed(2)}ms, 总耗时: ${totalTime.toFixed(2)}ms, usernames: ${usernames.length}`)
} }
if (result.success && result.contacts) { if (result.success && result.contacts) {
// 将更新加入队列,而不是立即更新 // 将更新加入队列,而不是立即更新
for (const [username, contact] of Object.entries(result.contacts)) { for (const [username, contact] of Object.entries(result.contacts)) {
@@ -644,7 +644,7 @@ function ChatPage(_props: ChatPageProps) {
const session = sessionMapRef.current.get(sessionId) const session = sessionMapRef.current.get(sessionId)
const unreadCount = session?.unreadCount ?? 0 const unreadCount = session?.unreadCount ?? 0
const messageLimit = offset === 0 && unreadCount > 99 ? 30 : 50 const messageLimit = offset === 0 && unreadCount > 99 ? 30 : 50
if (offset === 0) { if (offset === 0) {
setLoadingMessages(true) setLoadingMessages(true)
setMessages([]) setMessages([])
@@ -742,7 +742,7 @@ function ChatPage(_props: ChatPageProps) {
scrollTimeoutRef.current = requestAnimationFrame(() => { scrollTimeoutRef.current = requestAnimationFrame(() => {
if (!messageListRef.current) return if (!messageListRef.current) return
const { scrollTop, clientHeight, scrollHeight } = messageListRef.current const { scrollTop, clientHeight, scrollHeight } = messageListRef.current
// 显示回到底部按钮:距离底部超过 300px // 显示回到底部按钮:距离底部超过 300px
@@ -842,7 +842,7 @@ function ChatPage(_props: ChatPageProps) {
if (!isConnected && !isConnecting) { if (!isConnected && !isConnecting) {
connect() connect()
} }
// 组件卸载时清理 // 组件卸载时清理
return () => { return () => {
avatarLoadQueue.clear() avatarLoadQueue.clear()
@@ -906,7 +906,7 @@ function ChatPage(_props: ChatPageProps) {
}) })
} }
if (payloads.length > 0) { if (payloads.length > 0) {
window.electronAPI.image.preload(payloads).catch(() => {}) window.electronAPI.image.preload(payloads).catch(() => { })
} }
}, [currentSessionId, messages]) }, [currentSessionId, messages])
@@ -1114,7 +1114,7 @@ function ChatPage(_props: ChatPageProps) {
))} ))}
</div> </div>
) : Array.isArray(filteredSessions) && filteredSessions.length > 0 ? ( ) : Array.isArray(filteredSessions) && filteredSessions.length > 0 ? (
<div <div
className="session-list" className="session-list"
ref={sessionListRef} ref={sessionListRef}
onScroll={() => { onScroll={() => {
@@ -1195,56 +1195,56 @@ function ChatPage(_props: ChatPageProps) {
ref={messageListRef} ref={messageListRef}
onScroll={handleScroll} onScroll={handleScroll}
> >
{hasMoreMessages && ( {hasMoreMessages && (
<div className={`load-more-trigger ${isLoadingMore ? 'loading' : ''}`}> <div className={`load-more-trigger ${isLoadingMore ? 'loading' : ''}`}>
{isLoadingMore ? ( {isLoadingMore ? (
<> <>
<Loader2 size={14} /> <Loader2 size={14} />
<span>...</span> <span>...</span>
</> </>
) : ( ) : (
<span></span> <span></span>
)} )}
</div>
)}
{messages.map((msg, index) => {
const prevMsg = index > 0 ? messages[index - 1] : undefined
const showDateDivider = shouldShowDateDivider(msg, prevMsg)
// 显示时间第一条消息或者与上一条消息间隔超过5分钟
const showTime = !prevMsg || (msg.createTime - prevMsg.createTime > 300)
const isSent = msg.isSend === 1
const isSystem = msg.localType === 10000
// 系统消息居中显示
const wrapperClass = isSystem ? 'system' : (isSent ? 'sent' : 'received')
const messageKey = getMessageKey(msg)
return (
<div key={messageKey} className={`message-wrapper ${wrapperClass} ${highlightedMessageSet.has(messageKey) ? 'new-message' : ''}`}>
{showDateDivider && (
<div className="date-divider">
<span>{formatDateDivider(msg.createTime)}</span>
</div>
)}
<MessageBubble
message={msg}
session={currentSession}
showTime={!showDateDivider && showTime}
myAvatarUrl={myAvatarUrl}
isGroupChat={isGroupChat(currentSession.username)}
/>
</div>
)
})}
{/* 回到底部按钮 */}
<div className={`scroll-to-bottom ${showScrollToBottom ? 'show' : ''}`} onClick={scrollToBottom}>
<ChevronDown size={16} />
<span></span>
</div> </div>
)}
{messages.map((msg, index) => {
const prevMsg = index > 0 ? messages[index - 1] : undefined
const showDateDivider = shouldShowDateDivider(msg, prevMsg)
// 显示时间第一条消息或者与上一条消息间隔超过5分钟
const showTime = !prevMsg || (msg.createTime - prevMsg.createTime > 300)
const isSent = msg.isSend === 1
const isSystem = msg.localType === 10000
// 系统消息居中显示
const wrapperClass = isSystem ? 'system' : (isSent ? 'sent' : 'received')
const messageKey = getMessageKey(msg)
return (
<div key={messageKey} className={`message-wrapper ${wrapperClass} ${highlightedMessageSet.has(messageKey) ? 'new-message' : ''}`}>
{showDateDivider && (
<div className="date-divider">
<span>{formatDateDivider(msg.createTime)}</span>
</div>
)}
<MessageBubble
message={msg}
session={currentSession}
showTime={!showDateDivider && showTime}
myAvatarUrl={myAvatarUrl}
isGroupChat={isGroupChat(currentSession.username)}
/>
</div>
)
})}
{/* 回到底部按钮 */}
<div className={`scroll-to-bottom ${showScrollToBottom ? 'show' : ''}`} onClick={scrollToBottom}>
<ChevronDown size={16} />
<span></span>
</div> </div>
</div>
{/* 会话详情面板 */} {/* 会话详情面板 */}
{showDetailPanel && ( {showDetailPanel && (
@@ -1434,7 +1434,7 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat }:
bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50) { bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50) {
return 'image/webp' return 'image/webp'
} }
} catch {} } catch { }
return 'image/jpeg' return 'image/jpeg'
}, []) }, [])
@@ -1501,7 +1501,7 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat }:
setSenderAvatarUrl(result.avatarUrl) setSenderAvatarUrl(result.avatarUrl)
setSenderName(result.displayName) setSenderName(result.displayName)
} }
}).catch(() => {}).finally(() => { }).catch(() => { }).finally(() => {
senderAvatarLoading.delete(sender) senderAvatarLoading.delete(sender)
}) })
} }
@@ -1597,7 +1597,7 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat }:
} }
setImageHasUpdate(Boolean(result.hasUpdate)) setImageHasUpdate(Boolean(result.hasUpdate))
} }
}).catch(() => {}) }).catch(() => { })
return () => { return () => {
cancelled = true cancelled = true
} }
@@ -1685,6 +1685,12 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat }:
// 是否有引用消息 // 是否有引用消息
const hasQuote = message.quotedContent && message.quotedContent.length > 0 const hasQuote = message.quotedContent && message.quotedContent.length > 0
// 去除企业微信 ID 前缀
const cleanMessageContent = (content: string) => {
if (!content) return ''
return content.replace(/^[a-zA-Z0-9]+@openim:\n?/, '')
}
// 解析混合文本和表情 // 解析混合文本和表情
const renderTextWithEmoji = (text: string) => { const renderTextWithEmoji = (text: string) => {
if (!text) return text if (!text) return text
@@ -1895,14 +1901,14 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat }:
<div className="bubble-content"> <div className="bubble-content">
<div className="quoted-message"> <div className="quoted-message">
{message.quotedSender && <span className="quoted-sender">{message.quotedSender}</span>} {message.quotedSender && <span className="quoted-sender">{message.quotedSender}</span>}
<span className="quoted-text">{renderTextWithEmoji(message.quotedContent || '')}</span> <span className="quoted-text">{renderTextWithEmoji(cleanMessageContent(message.quotedContent || ''))}</span>
</div> </div>
<div className="message-text">{renderTextWithEmoji(message.parsedContent)}</div> <div className="message-text">{renderTextWithEmoji(cleanMessageContent(message.parsedContent))}</div>
</div> </div>
) )
} }
// 普通消息 // 普通消息
return <div className="bubble-content">{renderTextWithEmoji(message.parsedContent)}</div> return <div className="bubble-content">{renderTextWithEmoji(cleanMessageContent(message.parsedContent))}</div>
} }
return ( return (

View File

@@ -506,6 +506,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
{dbKeyStatus && <div className="field-hint status-text">{dbKeyStatus}</div>} {dbKeyStatus && <div className="field-hint status-text">{dbKeyStatus}</div>}
<div className="field-hint"></div> <div className="field-hint"></div>
<div className="field-hint"> 10</div>
</div> </div>
)} )}