From 641abc57b96303036e3530669d18cbd4a28b2c7c Mon Sep 17 00:00:00 2001
From: cc <98377878+hicccc77@users.noreply.github.com>
Date: Sat, 14 Mar 2026 22:23:10 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20#389=20=EF=BC=9B=E5=B9=B6?=
=?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E5=BC=95=E5=AF=BC=E9=A1=B5=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
electron/services/keyService.ts | 28 +------
src/components/ConfirmDialog.scss | 123 ++++++++++++++++++++++++++++++
src/components/ConfirmDialog.tsx | 32 ++++++++
src/pages/ChatPage.tsx | 36 ++++-----
src/pages/SettingsPage.scss | 1 +
src/pages/SettingsPage.tsx | 47 ++++++++----
src/pages/WelcomePage.scss | 15 +++-
src/pages/WelcomePage.tsx | 59 +++++++++++---
8 files changed, 274 insertions(+), 67 deletions(-)
create mode 100644 src/components/ConfirmDialog.scss
create mode 100644 src/components/ConfirmDialog.tsx
diff --git a/electron/services/keyService.ts b/electron/services/keyService.ts
index 66345c2..25b0aa1 100644
--- a/electron/services/keyService.ts
+++ b/electron/services/keyService.ts
@@ -606,34 +606,14 @@ export class KeyService {
const logs: string[] = []
- onStatus?.('正在定位微信安装路径...', 0)
- let wechatPath = await this.findWeChatInstallPath()
- if (!wechatPath) {
- const err = '未找到微信安装路径,请确认已安装PC微信'
+ onStatus?.('正在查找微信进程...', 0)
+ const pid = await this.findWeChatPid()
+ if (!pid) {
+ const err = '未找到微信进程,请先启动微信'
onStatus?.(err, 2)
return { success: false, error: err }
}
- onStatus?.('正在关闭微信以进行获取...', 0)
- const closed = await this.killWeChatProcesses()
- if (!closed) {
- const err = '无法自动关闭微信,请手动退出后重试'
- onStatus?.(err, 2)
- return { success: false, error: err }
- }
-
- onStatus?.('正在启动微信...', 0)
- const sub = spawn(wechatPath, {
- detached: true,
- stdio: 'ignore',
- cwd: dirname(wechatPath)
- })
- sub.unref()
-
- onStatus?.('等待微信界面就绪...', 0)
- const pid = await this.waitForWeChatWindow()
- if (!pid) return { success: false, error: '启动微信失败或等待界面就绪超时' }
-
onStatus?.(`检测到微信窗口 (PID: ${pid}),正在获取...`, 0)
onStatus?.('正在检测微信界面组件...', 0)
await this.waitForWeChatWindowComponents(pid, 15000)
diff --git a/src/components/ConfirmDialog.scss b/src/components/ConfirmDialog.scss
new file mode 100644
index 0000000..5acd32d
--- /dev/null
+++ b/src/components/ConfirmDialog.scss
@@ -0,0 +1,123 @@
+.confirm-dialog-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(4px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 100;
+ animation: fadeIn 0.2s ease-out;
+
+ .confirm-dialog {
+ width: 480px;
+ background: var(--bg-primary);
+ border-radius: 20px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ position: relative;
+ animation: slideUp 0.2s ease-out;
+ overflow: hidden;
+
+ .close-btn {
+ position: absolute;
+ top: 16px;
+ right: 16px;
+ background: rgba(0, 0, 0, 0.05);
+ border: none;
+ color: var(--text-secondary);
+ cursor: pointer;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.1);
+ color: var(--text-primary);
+ }
+ }
+
+ .dialog-title {
+ padding: 40px 40px 16px;
+ font-size: 18px;
+ font-weight: 600;
+ color: var(--text-primary);
+ }
+
+ .dialog-content {
+ padding: 0 40px 24px;
+
+ p {
+ font-size: 15px;
+ color: var(--text-primary);
+ line-height: 1.6;
+ margin: 0 0 16px 0;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+ }
+
+ .dialog-actions {
+ padding: 0 40px 40px;
+ display: flex;
+ justify-content: flex-end;
+ gap: 12px;
+
+ button {
+ padding: 12px 24px;
+ border-radius: 12px;
+ font-size: 15px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s;
+ border: none;
+
+ &.btn-cancel {
+ background: var(--bg-tertiary);
+ color: var(--text-secondary);
+
+ &:hover {
+ background: var(--bg-hover);
+ }
+ }
+
+ &.btn-confirm {
+ background: var(--primary);
+ color: var(--on-primary);
+
+ &:hover {
+ background: var(--primary-hover);
+ }
+
+ &:active {
+ transform: scale(0.98);
+ }
+ }
+ }
+ }
+ }
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+@keyframes slideUp {
+ from {
+ transform: translateY(20px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
diff --git a/src/components/ConfirmDialog.tsx b/src/components/ConfirmDialog.tsx
new file mode 100644
index 0000000..7126edf
--- /dev/null
+++ b/src/components/ConfirmDialog.tsx
@@ -0,0 +1,32 @@
+import { X } from 'lucide-react'
+import './ConfirmDialog.scss'
+
+interface ConfirmDialogProps {
+ open: boolean
+ title?: string
+ message: string
+ onConfirm: () => void
+ onCancel: () => void
+}
+
+export default function ConfirmDialog({ open, title, message, onConfirm, onCancel }: ConfirmDialogProps) {
+ if (!open) return null
+
+ return (
+
+
e.stopPropagation()}>
+
+ {title &&
{title}
}
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx
index 559fc03..50e1534 100644
--- a/src/pages/ChatPage.tsx
+++ b/src/pages/ChatPage.tsx
@@ -5202,6 +5202,24 @@ function MessageBubble({
const [emojiError, setEmojiError] = useState(false)
const [emojiLoading, setEmojiLoading] = useState(false)
+ // 缓存相关的 state 必须在所有 Hooks 之前声明
+ const cacheKey = message.emojiMd5 || message.emojiCdnUrl || ''
+ const [emojiLocalPath, setEmojiLocalPath] = useState(
+ () => emojiDataUrlCache.get(cacheKey) || message.emojiLocalPath
+ )
+ const imageCacheKey = message.imageMd5 || message.imageDatName || `local:${message.localId}`
+ const [imageLocalPath, setImageLocalPath] = useState(
+ () => imageDataUrlCache.get(imageCacheKey)
+ )
+ const voiceCacheKey = `voice:${message.localId}`
+ const [voiceDataUrl, setVoiceDataUrl] = useState(
+ () => voiceDataUrlCache.get(voiceCacheKey)
+ )
+ const voiceTranscriptCacheKey = `voice-transcript:${message.localId}`
+ const [voiceTranscript, setVoiceTranscript] = useState(
+ () => voiceTranscriptCache.get(voiceTranscriptCacheKey)
+ )
+
// State variables...
const [imageError, setImageError] = useState(false)
const [imageLoading, setImageLoading] = useState(false)
@@ -5282,24 +5300,6 @@ function MessageBubble({
loadConfig()
}, [])
- // 从缓存获取表情包 data URL
- const cacheKey = message.emojiMd5 || message.emojiCdnUrl || ''
- const [emojiLocalPath, setEmojiLocalPath] = useState(
- () => emojiDataUrlCache.get(cacheKey) || message.emojiLocalPath
- )
- const imageCacheKey = message.imageMd5 || message.imageDatName || `local:${message.localId}`
- const [imageLocalPath, setImageLocalPath] = useState(
- () => imageDataUrlCache.get(imageCacheKey)
- )
- const voiceCacheKey = `voice:${message.localId}`
- const [voiceDataUrl, setVoiceDataUrl] = useState(
- () => voiceDataUrlCache.get(voiceCacheKey)
- )
- const voiceTranscriptCacheKey = `voice-transcript:${message.localId}`
- const [voiceTranscript, setVoiceTranscript] = useState(
- () => voiceTranscriptCache.get(voiceTranscriptCacheKey)
- )
-
const formatTime = (timestamp: number): string => {
if (!Number.isFinite(timestamp) || timestamp <= 0) return '未知时间'
const date = new Date(timestamp * 1000)
diff --git a/src/pages/SettingsPage.scss b/src/pages/SettingsPage.scss
index d1608eb..04a5751 100644
--- a/src/pages/SettingsPage.scss
+++ b/src/pages/SettingsPage.scss
@@ -41,6 +41,7 @@
}
.settings-page {
+ position: relative;
display: flex;
flex-direction: column;
width: min(1160px, calc(100vw - 96px));
diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx
index ae49e8e..d963b14 100644
--- a/src/pages/SettingsPage.tsx
+++ b/src/pages/SettingsPage.tsx
@@ -557,24 +557,37 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
}
}
+ const validatePath = (path: string): string | null => {
+ if (!path) return null
+ if (/[\u4e00-\u9fa5]/.test(path)) {
+ return '路径包含中文字符,请迁移至全英文目录'
+ }
+ return null
+ }
+
const handleAutoDetectPath = async () => {
if (isDetectingPath) return
setIsDetectingPath(true)
try {
const result = await window.electronAPI.dbPath.autoDetect()
if (result.success && result.path) {
- setDbPath(result.path)
- await configService.setDbPath(result.path)
- showMessage(`自动检测成功:${result.path}`, true)
+ const validationError = validatePath(result.path)
+ if (validationError) {
+ showMessage(validationError, false)
+ } else {
+ setDbPath(result.path)
+ await configService.setDbPath(result.path)
+ showMessage(`自动检测成功:${result.path}`, true)
- const wxids = await window.electronAPI.dbPath.scanWxids(result.path)
- setWxidOptions(wxids)
- if (wxids.length === 1) {
- await applyWxidSelection(wxids[0].wxid, {
- toastText: `已检测到账号:${wxids[0].wxid}`
- })
- } else if (wxids.length > 1) {
- setShowWxidSelect(true)
+ const wxids = await window.electronAPI.dbPath.scanWxids(result.path)
+ setWxidOptions(wxids)
+ if (wxids.length === 1) {
+ await applyWxidSelection(wxids[0].wxid, {
+ toastText: `已检测到账号:${wxids[0].wxid}`
+ })
+ } else if (wxids.length > 1) {
+ setShowWxidSelect(true)
+ }
}
} else {
showMessage(result.error || '未能自动检测到数据库目录', false)
@@ -591,9 +604,14 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
const result = await dialog.openFile({ title: '选择微信数据库根目录', properties: ['openDirectory'] })
if (!result.canceled && result.filePaths.length > 0) {
const selectedPath = result.filePaths[0]
- setDbPath(selectedPath)
- await configService.setDbPath(selectedPath)
- showMessage('已选择数据库目录', true)
+ const validationError = validatePath(selectedPath)
+ if (validationError) {
+ showMessage(validationError, false)
+ } else {
+ setDbPath(selectedPath)
+ await configService.setDbPath(selectedPath)
+ showMessage('已选择数据库目录', true)
+ }
}
} catch (e: any) {
showMessage('选择目录失败', false)
@@ -1287,7 +1305,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
xwechat_files 目录
- 目录路径不可包含中文,如有中文请去微信-设置-存储位置点击更改,迁移至全英文目录
(null)
+ const [showDbKeyConfirm, setShowDbKeyConfirm] = useState(false)
// 安全相关 state
const [enableAuth, setEnableAuth] = useState(false)
@@ -123,6 +125,14 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
useEffect(() => {
const removeDb = window.electronAPI.key.onDbKeyStatus((payload: { message: string; level: number }) => {
setDbKeyStatus(payload.message)
+ if (payload.message.includes('现在可以登录') || payload.message.includes('Hook安装成功')) {
+ window.electronAPI.notification?.show({
+ title: 'WeFlow 准备就绪',
+ content: '现在可以登录微信了',
+ avatarUrl: './logo.png',
+ sessionId: 'weflow-system'
+ })
+ }
})
const removeImage = window.electronAPI.key.onImageKeyStatus((payload: { message: string, percent?: number }) => {
let msg = payload.message;
@@ -187,6 +197,15 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
window.electronAPI.window.close()
}
+ const validatePath = (path: string): string | null => {
+ if (!path) return null
+ // 检测中文字符和其他可能有问题的特殊字符
+ if (/[\u4e00-\u9fa5]/.test(path)) {
+ return '路径包含中文字符,请迁移至全英文目录'
+ }
+ return null
+ }
+
const handleSelectPath = async () => {
try {
const result = await dialog.openFile({
@@ -195,8 +214,14 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
})
if (!result.canceled && result.filePaths.length > 0) {
- setDbPath(result.filePaths[0])
- setError('')
+ const selectedPath = result.filePaths[0]
+ const validationError = validatePath(selectedPath)
+ if (validationError) {
+ setError(validationError)
+ } else {
+ setDbPath(selectedPath)
+ setError('')
+ }
}
} catch (e) {
setError('选择目录失败')
@@ -210,8 +235,13 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
try {
const result = await window.electronAPI.dbPath.autoDetect()
if (result.success && result.path) {
- setDbPath(result.path)
- setError('')
+ const validationError = validatePath(result.path)
+ if (validationError) {
+ setError(validationError)
+ } else {
+ setDbPath(result.path)
+ setError('')
+ }
} else {
setError(result.error || '未能检测到数据库目录')
}
@@ -287,6 +317,11 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
const handleAutoGetDbKey = async () => {
if (isFetchingDbKey) return
+ setShowDbKeyConfirm(true)
+ }
+
+ const handleDbKeyConfirm = async () => {
+ setShowDbKeyConfirm(false)
setIsFetchingDbKey(true)
setError('')
setIsManualStartPrompt(false)
@@ -297,7 +332,6 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
setDecryptKey(result.key)
setDbKeyStatus('密钥获取成功')
setError('')
- // 获取成功后自动扫描并填入 wxid
await handleScanWxid(true)
} else {
if (result.error?.includes('未找到微信安装路径') || result.error?.includes('启动微信失败')) {
@@ -613,9 +647,6 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
请选择微信-设置-存储位置对应的目录
-
- 目录路径不可包含中文,如有中文请先在微信中迁移至全英文目录
-
)}
@@ -705,7 +736,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
)}
- {dbKeyStatus && {dbKeyStatus}
}
+ {dbKeyStatus && {dbKeyStatus}
}
点击自动获取后微信将重启,请留意弹窗提示
)}
@@ -840,6 +871,16 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
)}
+
+ setShowDbKeyConfirm(false)}
+ />
)