diff --git a/electron/services/chatService.ts b/electron/services/chatService.ts
index e55dae7..81ea83a 100644
--- a/electron/services/chatService.ts
+++ b/electron/services/chatService.ts
@@ -103,7 +103,7 @@ export interface ContactInfo {
remark?: string
nickname?: string
avatarUrl?: string
- type: 'friend' | 'group' | 'official' | 'other'
+ type: 'friend' | 'group' | 'official' | 'deleted_friend' | 'other'
}
// 表情包缓存
@@ -603,7 +603,7 @@ class ChatService {
// 使用execQuery直接查询加密的contact.db
// kind='contact', path=null表示使用已打开的contact.db
const contactQuery = `
- SELECT username, remark, nick_name, alias, local_type
+ SELECT username, remark, nick_name, alias, local_type, flag
FROM contact
`
@@ -663,28 +663,31 @@ class ChatService {
}
// 判断类型 - 正确规则:wxid开头且有alias的是好友
- let type: 'friend' | 'group' | 'official' | 'other' = 'other'
+ let type: 'friend' | 'group' | 'official' | 'deleted_friend' | 'other' = 'other'
const localType = row.local_type || 0
+ const flag = Number(row.flag ?? 0)
+
if (username.includes('@chatroom')) {
type = 'group'
} else if (username.startsWith('gh_')) {
+ if (flag === 0) continue
type = 'official'
} else if (localType === 3 || localType === 4) {
+ if (flag === 0) continue
+ if (flag === 4) continue
type = 'official'
} else if (username.startsWith('wxid_') && row.alias) {
- // wxid开头且有alias的是好友
- type = 'friend'
+ type = flag === 0 ? 'deleted_friend' : 'friend'
} else if (localType === 1) {
- // local_type=1 也是好友
- type = 'friend'
+ type = flag === 0 ? 'deleted_friend' : 'friend'
} else if (localType === 2) {
// local_type=2 是群成员但非好友,跳过
continue
} else if (localType === 0) {
// local_type=0 可能是好友或其他,检查是否有备注或昵称
if (row.remark || row.nick_name) {
- type = 'friend'
+ type = flag === 0 ? 'deleted_friend' : 'friend'
} else {
continue
}
diff --git a/src/components/NotificationToast.scss b/src/components/NotificationToast.scss
index cb405a3..b48013c 100644
--- a/src/components/NotificationToast.scss
+++ b/src/components/NotificationToast.scss
@@ -6,6 +6,13 @@
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid var(--border-light);
+
+ // 浅色模式下使用不透明背景,避免透明窗口中通知过于透明
+ [data-mode="light"] &,
+ :not([data-mode]) & {
+ background: rgba(255, 255, 255, 1);
+ }
+
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
padding: 12px;
@@ -39,7 +46,7 @@
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
- // Ensure background is solid
+ // 确保背景不透明
background: var(--bg-secondary, #2c2c2c);
color: var(--text-primary, #ffffff);
diff --git a/src/pages/ChatPage.scss b/src/pages/ChatPage.scss
index 05fb05a..40ad9ca 100644
--- a/src/pages/ChatPage.scss
+++ b/src/pages/ChatPage.scss
@@ -1288,6 +1288,21 @@
z-index: 2;
}
+.empty-chat-inline {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ padding: 60px 0;
+ color: var(--text-tertiary);
+ font-size: 14px;
+
+ svg {
+ opacity: 0.4;
+ }
+}
+
.message-list * {
-webkit-app-region: no-drag !important;
}
diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx
index f0f5641..26b019a 100644
--- a/src/pages/ChatPage.tsx
+++ b/src/pages/ChatPage.tsx
@@ -1261,6 +1261,7 @@ function ChatPage(_props: ChatPageProps) {
useEffect(() => {
if (currentSessionId && messages.length === 0 && !isLoadingMessages && !isLoadingMore) {
+ setHasInitialMessages(false)
loadMessages(currentSessionId, 0)
}
}, [currentSessionId, messages.length, isLoadingMessages, isLoadingMore])
@@ -1327,8 +1328,21 @@ function ChatPage(_props: ChatPageProps) {
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
}, [])
- // 获取当前会话信息
- const currentSession = Array.isArray(sessions) ? sessions.find(s => s.username === currentSessionId) : undefined
+ // 获取当前会话信息(从通讯录跳转时可能不在 sessions 列表中,构造 fallback)
+ const currentSession = (() => {
+ const found = Array.isArray(sessions) ? sessions.find(s => s.username === currentSessionId) : undefined
+ if (found || !currentSessionId) return found
+ return {
+ username: currentSessionId,
+ type: 0,
+ unreadCount: 0,
+ summary: '',
+ sortTimestamp: 0,
+ lastTimestamp: 0,
+ lastMsgType: 0,
+ displayName: currentSessionId,
+ } as ChatSession
+ })()
// 判断是否为群聊
const isGroupChat = (username: string) => username.includes('@chatroom')
@@ -2048,6 +2062,13 @@ function ChatPage(_props: ChatPageProps) {
)}
+ {!isLoadingMessages && messages.length === 0 && !hasMoreMessages && (
+
+
+ 该联系人没有聊天记录
+
+ )}
+
{messages.map((msg, index) => {
const prevMsg = index > 0 ? messages[index - 1] : undefined
const showDateDivider = shouldShowDateDivider(msg, prevMsg)
diff --git a/src/pages/ContactsPage.scss b/src/pages/ContactsPage.scss
index 2609639..7f5a1b3 100644
--- a/src/pages/ContactsPage.scss
+++ b/src/pages/ContactsPage.scss
@@ -7,8 +7,8 @@
// 左侧联系人面板
.contacts-panel {
- width: 380px;
- min-width: 380px;
+ width: 400px;
+ min-width: 400px;
display: flex;
flex-direction: column;
border-right: 1px solid var(--border-color);
@@ -55,6 +55,11 @@
.spin {
animation: contactsSpin 1s linear infinite;
}
+
+ &.export-mode-btn.active {
+ background: var(--primary);
+ color: #fff;
+ }
}
}
@@ -231,8 +236,8 @@
padding: 12px;
border-radius: 10px;
transition: all 0.2s;
- margin-bottom: 4px;
cursor: pointer;
+ margin-bottom: 4px;
&:hover {
background: var(--bg-hover);
@@ -242,6 +247,10 @@
background: color-mix(in srgb, var(--primary) 12%, transparent);
}
+ &.active {
+ background: var(--bg-tertiary);
+ }
+
.contact-select {
display: flex;
align-items: center;
@@ -334,6 +343,93 @@
}
}
+ // 右侧详情面板内的联系人资料
+ .detail-profile {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+ padding-bottom: 24px;
+ border-bottom: 1px solid var(--border-color);
+ margin-bottom: 20px;
+
+ .detail-avatar {
+ width: 80px;
+ height: 80px;
+ border-radius: 16px;
+ background: linear-gradient(135deg, var(--primary), var(--primary-hover));
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+
+ img { width: 100%; height: 100%; object-fit: cover; }
+ span { color: #fff; font-size: 28px; font-weight: 600; }
+ }
+
+ .detail-name {
+ font-size: 18px;
+ font-weight: 600;
+ color: var(--text-primary);
+ }
+ }
+
+ .detail-info-list {
+ margin-bottom: 24px;
+
+ .detail-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 0;
+ font-size: 13px;
+ border-bottom: 1px solid var(--border-color);
+
+ &:last-child { border-bottom: none; }
+ }
+
+ .detail-label {
+ color: var(--text-tertiary);
+ min-width: 48px;
+ flex-shrink: 0;
+ }
+
+ .detail-value {
+ color: var(--text-primary);
+ word-break: break-all;
+ }
+ }
+
+ .goto-chat-btn {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 12px;
+ background: var(--primary);
+ color: #fff;
+ border: none;
+ border-radius: 10px;
+ font-size: 14px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+
+ &:hover { background: var(--primary-hover); }
+ }
+
+ .empty-detail {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+ color: var(--text-tertiary);
+ font-size: 14px;
+ }
+
// 右侧设置面板
.settings-panel {
flex: 1;
diff --git a/src/pages/ContactsPage.tsx b/src/pages/ContactsPage.tsx
index 43968ae..198c809 100644
--- a/src/pages/ContactsPage.tsx
+++ b/src/pages/ContactsPage.tsx
@@ -1,5 +1,7 @@
import { useState, useEffect, useCallback, useRef } from 'react'
-import { Search, RefreshCw, X, User, Users, MessageSquare, Loader2, FolderOpen, Download, ChevronDown } from 'lucide-react'
+import { useNavigate } from 'react-router-dom'
+import { Search, RefreshCw, X, User, Users, MessageSquare, Loader2, FolderOpen, Download, ChevronDown, MessageCircle, UserX } from 'lucide-react'
+import { useChatStore } from '../stores/chatStore'
import './ContactsPage.scss'
interface ContactInfo {
@@ -8,7 +10,7 @@ interface ContactInfo {
remark?: string
nickname?: string
avatarUrl?: string
- type: 'friend' | 'group' | 'official' | 'other'
+ type: 'friend' | 'group' | 'official' | 'deleted_friend' | 'other'
}
function ContactsPage() {
@@ -20,9 +22,16 @@ function ContactsPage() {
const [contactTypes, setContactTypes] = useState({
friends: true,
groups: true,
- officials: true
+ officials: true,
+ deletedFriends: false
})
+ // 导出模式与查看详情
+ const [exportMode, setExportMode] = useState(false)
+ const [selectedContact, setSelectedContact] = useState(null)
+ const navigate = useNavigate()
+ const { setCurrentSession } = useChatStore()
+
// 导出相关状态
const [exportFormat, setExportFormat] = useState<'json' | 'csv' | 'vcf'>('json')
const [exportAvatars, setExportAvatars] = useState(true)
@@ -85,6 +94,7 @@ function ContactsPage() {
if (c.type === 'friend' && !contactTypes.friends) return false
if (c.type === 'group' && !contactTypes.groups) return false
if (c.type === 'official' && !contactTypes.officials) return false
+ if (c.type === 'deleted_friend' && !contactTypes.deletedFriends) return false
return true
})
@@ -154,6 +164,7 @@ function ContactsPage() {
case 'friend': return
case 'group': return
case 'official': return
+ case 'deleted_friend': return
default: return
}
}
@@ -163,6 +174,7 @@ function ContactsPage() {
case 'friend': return '好友'
case 'group': return '群聊'
case 'official': return '公众号'
+ case 'deleted_friend': return '已删除'
default: return '其他'
}
}
@@ -236,9 +248,18 @@ function ContactsPage() {
通讯录
-
+
+
+
+
@@ -258,49 +279,41 @@ function ContactsPage() {
+
共 {filteredContacts.length} 个联系人
-
-
- 已选 {selectedUsernames.size}(当前筛选 {selectedInFilteredCount} / {filteredContacts.length})
-
+
+ {exportMode && (
+
+
+ 已选 {selectedUsernames.size}(当前筛选 {selectedInFilteredCount} / {filteredContacts.length})
+
+ )}
{isLoading ? (
@@ -314,20 +327,29 @@ function ContactsPage() {
) : (
{filteredContacts.map(contact => {
- const isSelected = selectedUsernames.has(contact.username)
+ const isChecked = selectedUsernames.has(contact.username)
+ const isActive = !exportMode && selectedContact?.username === contact.username
return (
toggleContactSelected(contact.username, !isSelected)}
+ className={`contact-item ${exportMode && isChecked ? 'selected' : ''} ${isActive ? 'active' : ''}`}
+ onClick={() => {
+ if (exportMode) {
+ toggleContactSelected(contact.username, !isChecked)
+ } else {
+ setSelectedContact(isActive ? null : contact)
+ }
+ }}
>
-
+ {exportMode && (
+
+ )}
{contact.avatarUrl ? (

@@ -352,90 +374,129 @@ function ContactsPage() {
)}
- {/* 右侧:导出设置 */}
-
-
-
导出设置
-
+ {/* 右侧面板 */}
+ {exportMode ? (
+
+
+
导出设置
+
-
-
-
导出格式
-
-
-
-
-
导出选项
-
-
-
-
-
导出位置
-
-
- {exportFolder || '未设置'}
-
-
-
- 选择导出目录
+ ) : selectedContact ? (
+
+
+
联系人详情
+
+
+
+
+ {selectedContact.avatarUrl ? (
+

+ ) : (
+
{getAvatarLetter(selectedContact.displayName)}
+ )}
+
+
{selectedContact.displayName}
+
+ {getContactTypeIcon(selectedContact.type)}
+ {getContactTypeName(selectedContact.type)}
+
+
-
-
- {isExporting ? (
- <>
-
- 导出中...
- >
- ) : (
- <>
-
- 开始导出
- >
- )}
-
+
+
用户名{selectedContact.username}
+
昵称{selectedContact.nickname || selectedContact.displayName}
+ {selectedContact.remark &&
备注{selectedContact.remark}
}
+
类型{getContactTypeName(selectedContact.type)}
+
+
+
{
+ setCurrentSession(selectedContact.username)
+ navigate('/chat')
+ }}
+ >
+
+ 查看聊天记录
+
+
-
+ ) : (
+
+ )}
)
}
diff --git a/src/pages/NotificationWindow.tsx b/src/pages/NotificationWindow.tsx
index deb6616..2e9acd0 100644
--- a/src/pages/NotificationWindow.tsx
+++ b/src/pages/NotificationWindow.tsx
@@ -1,9 +1,11 @@
import { useEffect, useState, useRef } from 'react'
import { NotificationToast, type NotificationData } from '../components/NotificationToast'
+import { useThemeStore } from '../stores/themeStore'
import '../components/NotificationToast.scss'
import './NotificationWindow.scss'
export default function NotificationWindow() {
+ const { currentTheme, themeMode } = useThemeStore()
const [notification, setNotification] = useState
(null)
const [prevNotification, setPrevNotification] = useState(null)
@@ -17,6 +19,12 @@ export default function NotificationWindow() {
const notificationRef = useRef(null)
+ // 应用主题到通知窗口
+ useEffect(() => {
+ document.documentElement.setAttribute('data-theme', currentTheme)
+ document.documentElement.setAttribute('data-mode', themeMode)
+ }, [currentTheme, themeMode])
+
useEffect(() => {
notificationRef.current = notification
}, [notification])