diff --git a/electron/main.ts b/electron/main.ts index bb6576f..31e2d34 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -660,17 +660,14 @@ function createImageViewerWindow(imagePath: string, liveVideoPath?: string) { nodeIntegration: false, webSecurity: false // 允许加载本地文件 }, - titleBarStyle: 'hidden', - titleBarOverlay: { - color: '#00000000', - symbolColor: '#ffffff', - height: 40 - }, + frame: false, show: false, backgroundColor: '#000000', autoHideMenuBar: true }) + setupCustomTitleBarWindow(win) + win.once('ready-to-show', () => { win.show() }) @@ -975,6 +972,17 @@ function registerIpcHandlers() { } }) + ipcMain.handle('log:clear', async () => { + try { + const logPath = join(app.getPath('userData'), 'logs', 'wcdb.log') + await mkdir(dirname(logPath), { recursive: true }) + await writeFile(logPath, '', 'utf8') + return { success: true } + } catch (e) { + return { success: false, error: String(e) } + } + }) + ipcMain.handle('diagnostics:getExportCardLogs', async (_, options?: { limit?: number }) => { return exportCardDiagnosticsService.snapshot(options?.limit) }) diff --git a/electron/preload.ts b/electron/preload.ts index 2f2874c..2dcc561 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -70,6 +70,7 @@ contextBridge.exposeInMainWorld('electronAPI', { log: { getPath: () => ipcRenderer.invoke('log:getPath'), read: () => ipcRenderer.invoke('log:read'), + clear: () => ipcRenderer.invoke('log:clear'), debug: (data: any) => ipcRenderer.send('log:debug', data) }, diff --git a/electron/services/chatService.ts b/electron/services/chatService.ts index 6f104fd..0b81fe2 100644 --- a/electron/services/chatService.ts +++ b/electron/services/chatService.ts @@ -359,8 +359,9 @@ class ChatService { // 这种方式更高效,且不占用 JS 线程,并能直接监听 session/message 目录变更 wcdbService.setMonitor((type, json) => { this.handleSessionStatsMonitorChange(type, json) + const windows = BrowserWindow.getAllWindows() // 广播给所有渲染进程窗口 - BrowserWindow.getAllWindows().forEach((win) => { + windows.forEach((win) => { if (!win.isDestroyed()) { win.webContents.send('wcdb-change', { type, json }) } diff --git a/electron/services/keyServiceMac.ts b/electron/services/keyServiceMac.ts index 4392c81..006e7cf 100644 --- a/electron/services/keyServiceMac.ts +++ b/electron/services/keyServiceMac.ts @@ -23,6 +23,7 @@ export class KeyServiceMac { private machVmRegion: any = null private machVmReadOverwrite: any = null private machPortDeallocate: any = null + private _needsElevation = false private getHelperPath(): string { const isPackaged = app.isPackaged @@ -49,6 +50,26 @@ export class KeyServiceMac { throw new Error('xkey_helper not found') } + private getImageScanHelperPath(): string { + const isPackaged = app.isPackaged + const candidates: string[] = [] + + if (isPackaged) { + candidates.push(join(process.resourcesPath, 'resources', 'image_scan_helper')) + candidates.push(join(process.resourcesPath, 'image_scan_helper')) + } else { + const cwd = process.cwd() + candidates.push(join(cwd, 'resources', 'image_scan_helper')) + candidates.push(join(app.getAppPath(), 'resources', 'image_scan_helper')) + } + + for (const path of candidates) { + if (existsSync(path)) return path + } + + throw new Error('image_scan_helper not found') + } + private getDylibPath(): string { const isPackaged = app.isPackaged const candidates: string[] = [] @@ -258,7 +279,7 @@ export class KeyServiceMac { stdout += data stdoutBuf += data const parts = stdoutBuf.split(/\r?\n/) - stdoutBuf = parts.pop() || '' + stdoutBuf = parts.pop()! }) child.stderr.on('data', (chunk: Buffer | string) => { @@ -266,7 +287,7 @@ export class KeyServiceMac { stderr += data stderrBuf += data const parts = stderrBuf.split(/\r?\n/) - stderrBuf = parts.pop() || '' + stderrBuf = parts.pop()! for (const line of parts) processHelperLine(line.trim()) }) @@ -337,13 +358,13 @@ export class KeyServiceMac { const result = await execFileAsync('osascript', scriptLines.flatMap(line => ['-e', line]), { timeout: waitMs + 20_000 }) - stdout = result.stdout || '' + stdout = result.stdout } catch (e: any) { const msg = `${e?.stderr || ''}\n${e?.stdout || ''}\n${e?.message || ''}`.trim() throw new Error(msg || 'elevated helper execution failed') } - const lines = String(stdout || '').split(/\r?\n/).map(x => x.trim()).filter(Boolean) + const lines = String(stdout).split(/\r?\n/).map(x => x.trim()).filter(Boolean) const last = lines[lines.length - 1] if (!last) throw new Error('elevated helper returned empty output') @@ -614,6 +635,32 @@ export class KeyServiceMac { ciphertext: Buffer, onProgress?: (message: string) => void ): Promise { + // 优先通过 image_scan_helper 子进程调用 + try { + const helperPath = this.getImageScanHelperPath() + const ciphertextHex = ciphertext.toString('hex') + + // 1) 直接运行 helper(有正式签名的 debugger entitlement 时可用) + if (!this._needsElevation) { + const direct = await this._spawnScanHelper(helperPath, pid, ciphertextHex, false) + if (direct.key) return direct.key + if (direct.permissionError) { + console.warn('[KeyServiceMac] task_for_pid 权限不足,切换到 osascript 提权模式') + this._needsElevation = true + onProgress?.('需要管理员权限,请在弹出的对话框中输入密码...') + } + } + + // 2) 通过 osascript 以管理员权限运行 helper(SIP 下 ad-hoc 签名无法获取 task_for_pid) + if (this._needsElevation) { + const elevated = await this._spawnScanHelper(helperPath, pid, ciphertextHex, true) + if (elevated.key) return elevated.key + } + } catch (e: any) { + console.warn('[KeyServiceMac] image_scan_helper unavailable, fallback to Mach API:', e?.message) + } + + // fallback: 直接通过 Mach API 扫描内存(Electron 进程可能没有 task_for_pid 权限) if (!this.ensureMachApis()) return null const VM_PROT_READ = 0x1 @@ -708,6 +755,45 @@ export class KeyServiceMac { } } + private _spawnScanHelper( + helperPath: string, pid: number, ciphertextHex: string, elevated: boolean + ): Promise<{ key: string | null; permissionError: boolean }> { + return new Promise((resolve, reject) => { + let child: ReturnType + if (elevated) { + const shellCmd = `'${helperPath}' ${pid} ${ciphertextHex}` + child = spawn('osascript', ['-e', `do shell script ${JSON.stringify(shellCmd)} with administrator privileges`], + { stdio: ['ignore', 'pipe', 'pipe'] }) + } else { + child = spawn(helperPath, [String(pid), ciphertextHex], { stdio: ['ignore', 'pipe', 'pipe'] }) + } + const tag = elevated ? '[image_scan_helper:elevated]' : '[image_scan_helper]' + let stdout = '', stderr = '' + child.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString() }) + child.stderr.on('data', (chunk: Buffer) => { + stderr += chunk.toString() + console.log(tag, chunk.toString().trim()) + }) + child.on('error', reject) + child.on('close', () => { + const permissionError = !elevated && stderr.includes('task_for_pid failed') + try { + const lines = stdout.split(/\r?\n/).map(x => x.trim()).filter(Boolean) + const last = lines[lines.length - 1] + if (!last) { resolve({ key: null, permissionError }); return } + const payload = JSON.parse(last) + resolve({ + key: payload?.success && payload?.aesKey ? payload.aesKey : null, + permissionError + }) + } catch { + resolve({ key: null, permissionError }) + } + }) + setTimeout(() => { try { child.kill('SIGTERM') } catch {} }, elevated ? 60_000 : 30_000) + }) + } + private async findWeChatPid(): Promise { const { execSync } = await import('child_process') try { diff --git a/electron/services/wcdbCore.ts b/electron/services/wcdbCore.ts index d664443..a56cf78 100644 --- a/electron/services/wcdbCore.ts +++ b/electron/services/wcdbCore.ts @@ -173,7 +173,6 @@ export class WcdbCore { } } catch {} } - this.connectMonitorPipe(pipePath) return true } catch (e) { @@ -190,13 +189,18 @@ export class WcdbCore { setTimeout(() => { if (!this.monitorCallback) return - this.monitorPipeClient = net.createConnection(this.monitorPipePath, () => { - }) + this.monitorPipeClient = net.createConnection(this.monitorPipePath, () => {}) let buffer = '' this.monitorPipeClient.on('data', (data: Buffer) => { - buffer += data.toString('utf8') - const lines = buffer.split('\n') + const rawChunk = data.toString('utf8') + // macOS 侧可能使用 '\0' 或无换行分隔,统一归一化并兜底拆包 + const normalizedChunk = rawChunk + .replace(/\u0000/g, '\n') + .replace(/}\s*{/g, '}\n{') + + buffer += normalizedChunk + const lines = buffer.split(/\r?\n/) buffer = lines.pop() || '' for (const line of lines) { if (line.trim()) { @@ -208,9 +212,22 @@ export class WcdbCore { } } } + + // 兜底:如果没有分隔符但已形成完整 JSON,则直接上报 + const tail = buffer.trim() + if (tail.startsWith('{') && tail.endsWith('}')) { + try { + const parsed = JSON.parse(tail) + this.monitorCallback?.(parsed.action || 'update', tail) + buffer = '' + } catch { + // 不可解析则继续等待下一块数据 + } + } }) this.monitorPipeClient.on('error', () => { + // 保持静默,与现有错误处理策略一致 }) this.monitorPipeClient.on('close', () => { diff --git a/electron/services/wcdbService.ts b/electron/services/wcdbService.ts index 6aee8e9..2f5715f 100644 --- a/electron/services/wcdbService.ts +++ b/electron/services/wcdbService.ts @@ -136,7 +136,7 @@ export class WcdbService { */ setMonitor(callback: (type: string, json: string) => void): void { this.monitorListener = callback; - this.callWorker('setMonitor').catch(() => { }); + this.callWorker<{ success?: boolean }>('setMonitor').catch(() => { }); } /** diff --git a/electron/wcdbWorker.ts b/electron/wcdbWorker.ts index 333527a..8a49cad 100644 --- a/electron/wcdbWorker.ts +++ b/electron/wcdbWorker.ts @@ -20,15 +20,17 @@ if (parentPort) { result = { success: true } break case 'setMonitor': - core.setMonitor((type, json) => { + { + const monitorOk = core.setMonitor((type, json) => { parentPort!.postMessage({ id: -1, type: 'monitor', payload: { type, json } }) }) - result = { success: true } + result = { success: monitorOk } break + } case 'testConnection': result = await core.testConnection(payload.dbPath, payload.hexKey, payload.wxid) break diff --git a/resources/image_scan_entitlements.plist b/resources/image_scan_entitlements.plist new file mode 100644 index 0000000..023065e --- /dev/null +++ b/resources/image_scan_entitlements.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.debugger + + com.apple.security.cs.allow-unsigned-executable-memory + + + diff --git a/resources/image_scan_helper b/resources/image_scan_helper new file mode 100755 index 0000000..b10856d Binary files /dev/null and b/resources/image_scan_helper differ diff --git a/resources/image_scan_helper.c b/resources/image_scan_helper.c new file mode 100644 index 0000000..39bcf27 --- /dev/null +++ b/resources/image_scan_helper.c @@ -0,0 +1,77 @@ +/* + * image_scan_helper - 轻量包装程序 + * 加载 libwx_key.dylib 并调用 ScanMemoryForImageKey + * 用法: image_scan_helper + * 输出: JSON {"success":true,"aesKey":"..."} 或 {"success":false,"error":"..."} + */ +#include +#include +#include +#include +#include +#include + +typedef const char* (*ScanMemoryForImageKeyFn)(int pid, const char* ciphertext); +typedef void (*FreeStringFn)(const char* str); + +int main(int argc, char* argv[]) { + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + printf("{\"success\":false,\"error\":\"invalid arguments\"}\n"); + return 1; + } + + int pid = atoi(argv[1]); + const char* ciphertext_hex = argv[2]; + + if (pid <= 0) { + printf("{\"success\":false,\"error\":\"invalid pid\"}\n"); + return 1; + } + + /* 定位 dylib: 与自身同目录下的 libwx_key.dylib */ + char exe_path[4096]; + uint32_t size = sizeof(exe_path); + if (_NSGetExecutablePath(exe_path, &size) != 0) { + printf("{\"success\":false,\"error\":\"cannot get executable path\"}\n"); + return 1; + } + + char* dir = dirname(exe_path); + char dylib_path[4096]; + snprintf(dylib_path, sizeof(dylib_path), "%s/libwx_key.dylib", dir); + + void* handle = dlopen(dylib_path, RTLD_LAZY); + if (!handle) { + printf("{\"success\":false,\"error\":\"dlopen failed: %s\"}\n", dlerror()); + return 1; + } + + ScanMemoryForImageKeyFn scan_fn = (ScanMemoryForImageKeyFn)dlsym(handle, "ScanMemoryForImageKey"); + if (!scan_fn) { + printf("{\"success\":false,\"error\":\"symbol not found: ScanMemoryForImageKey\"}\n"); + dlclose(handle); + return 1; + } + + FreeStringFn free_fn = (FreeStringFn)dlsym(handle, "FreeString"); + + fprintf(stderr, "[image_scan_helper] calling ScanMemoryForImageKey(pid=%d, ciphertext=%s)\n", pid, ciphertext_hex); + + const char* result = scan_fn(pid, ciphertext_hex); + + if (result && strlen(result) > 0) { + /* 检查是否是错误 */ + if (strncmp(result, "ERROR", 5) == 0) { + printf("{\"success\":false,\"error\":\"%s\"}\n", result); + } else { + printf("{\"success\":true,\"aesKey\":\"%s\"}\n", result); + } + if (free_fn) free_fn(result); + } else { + printf("{\"success\":false,\"error\":\"no key found\"}\n"); + } + + dlclose(handle); + return 0; +} diff --git a/resources/libwx_key.dylib b/resources/libwx_key.dylib index 58c22fd..59c673a 100755 Binary files a/resources/libwx_key.dylib and b/resources/libwx_key.dylib differ diff --git a/resources/macos/libwcdb_api.dylib b/resources/macos/libwcdb_api.dylib index fd33a96..da70360 100755 Binary files a/resources/macos/libwcdb_api.dylib and b/resources/macos/libwcdb_api.dylib differ diff --git a/resources/wcdb_api.dll b/resources/wcdb_api.dll index 87794fa..3a58257 100644 Binary files a/resources/wcdb_api.dll and b/resources/wcdb_api.dll differ diff --git a/resources/xkey_helper b/resources/xkey_helper index d43fd1d..02ae7c5 100755 Binary files a/resources/xkey_helper and b/resources/xkey_helper differ diff --git a/src/components/GlobalSessionMonitor.tsx b/src/components/GlobalSessionMonitor.tsx index 40d8243..a1abf71 100644 --- a/src/components/GlobalSessionMonitor.tsx +++ b/src/components/GlobalSessionMonitor.tsx @@ -46,7 +46,6 @@ export function GlobalSessionMonitor() { return () => { removeListener() } - } else { } return () => { } }, []) diff --git a/src/components/TitleBar.scss b/src/components/TitleBar.scss index b90b64c..8c3c9b8 100644 --- a/src/components/TitleBar.scss +++ b/src/components/TitleBar.scss @@ -36,6 +36,8 @@ font-size: 15px; font-weight: 500; color: var(--text-secondary); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } .title-sidebar-toggle { @@ -90,3 +92,55 @@ color: #fff; } } + +.image-controls { + display: flex; + align-items: center; + gap: 8px; + margin-right: auto; + padding-left: 16px; + -webkit-app-region: no-drag; + + button { + background: transparent; + border: none; + color: var(--text-secondary); + cursor: pointer; + padding: 6px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + + &:hover { + background: var(--bg-tertiary); + color: var(--text-primary); + } + + &:disabled { + cursor: default; + opacity: 1; + } + + &.live-play-btn.active { + background: rgba(var(--primary-rgb, 76, 132, 255), 0.16); + color: var(--primary, #4c84ff); + } + } + + .scale-text { + min-width: 50px; + text-align: center; + color: var(--text-secondary); + font-size: 12px; + font-variant-numeric: tabular-nums; + } + + .divider { + width: 1px; + height: 14px; + background: var(--border-color); + margin: 0 4px; + } +} diff --git a/src/components/TitleBar.tsx b/src/components/TitleBar.tsx index d208eda..491a7ea 100644 --- a/src/components/TitleBar.tsx +++ b/src/components/TitleBar.tsx @@ -7,13 +7,17 @@ interface TitleBarProps { sidebarCollapsed?: boolean onToggleSidebar?: () => void showWindowControls?: boolean + customControls?: React.ReactNode + showLogo?: boolean } function TitleBar({ title, sidebarCollapsed = false, onToggleSidebar, - showWindowControls = true + showWindowControls = true, + customControls, + showLogo = true }: TitleBarProps = {}) { const [isMaximized, setIsMaximized] = useState(false) @@ -32,7 +36,7 @@ function TitleBar({ return (
- WeFlow + {showLogo && WeFlow} {title || 'WeFlow'} {onToggleSidebar ? (
+ {customControls} {showWindowControls ? (
-

优化

{updateInfo.releaseNotes ? (
) : ( diff --git a/src/pages/ChatPage.scss b/src/pages/ChatPage.scss index 4b02936..e00feb5 100644 --- a/src/pages/ChatPage.scss +++ b/src/pages/ChatPage.scss @@ -4442,18 +4442,23 @@ // 折叠群入口样式 .session-item.fold-entry { - background: var(--card-inner-bg, rgba(0,0,0,0.03)); + cursor: pointer; + transition: background-color 0.2s; + + &:hover { + background: var(--hover-bg, rgba(0,0,0,0.05)); + } .fold-entry-avatar { width: 48px; height: 48px; border-radius: 8px; - background: var(--primary-color, #07c160); + background: #fff; display: flex; align-items: center; justify-content: center; flex-shrink: 0; - color: #fff; + color: #fa9d3b; } .session-name { diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index 09f33db..559fc03 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -356,18 +356,19 @@ const SessionItem = React.memo(function SessionItem({ if (isFoldEntry) { return (
onSelect(session)} >
- +
- 折叠的群聊 + 折叠的聊天 + {timeText}
- {session.summary || ''} + {session.summary || '暂无消息'}
@@ -2966,10 +2967,51 @@ function ChatPage(props: ChatPageProps) { setFilteredSessions([]) return } - const visible = sessions.filter(s => { + + // 检查是否有折叠的群聊 + const foldedGroups = sessions.filter(s => s.isFolded && !s.username.toLowerCase().includes('placeholder_foldgroup')) + const hasFoldedGroups = foldedGroups.length > 0 + + let visible = sessions.filter(s => { if (s.isFolded && !s.username.toLowerCase().includes('placeholder_foldgroup')) return false return true }) + + // 如果有折叠的群聊,但列表中没有入口,则插入入口 + if (hasFoldedGroups && !visible.some(s => s.username.toLowerCase().includes('placeholder_foldgroup'))) { + // 找到最新的折叠消息 + const latestFolded = foldedGroups.reduce((latest, current) => { + const latestTime = latest.sortTimestamp || latest.lastTimestamp + const currentTime = current.sortTimestamp || current.lastTimestamp + return currentTime > latestTime ? current : latest + }) + + const foldEntry: ChatSession = { + username: 'placeholder_foldgroup', + displayName: '折叠的聊天', + summary: `${latestFolded.displayName || latestFolded.username}: ${latestFolded.summary}`, + type: 0, + sortTimestamp: latestFolded.sortTimestamp || latestFolded.lastTimestamp, + lastTimestamp: latestFolded.lastTimestamp || latestFolded.sortTimestamp, + lastMsgType: 0, + unreadCount: foldedGroups.reduce((sum, s) => sum + (s.unreadCount || 0), 0), + isMuted: false, + isFolded: false + } + + // 按时间戳插入到正确位置 + const foldTime = foldEntry.sortTimestamp || foldEntry.lastTimestamp + const insertIndex = visible.findIndex(s => { + const sTime = s.sortTimestamp || s.lastTimestamp + return sTime < foldTime + }) + if (insertIndex === -1) { + visible.push(foldEntry) + } else { + visible.splice(insertIndex, 0, foldEntry) + } + } + if (!searchKeyword.trim()) { setFilteredSessions(visible) return diff --git a/src/pages/ImageWindow.scss b/src/pages/ImageWindow.scss index c1d842d..4b9f48b 100644 --- a/src/pages/ImageWindow.scss +++ b/src/pages/ImageWindow.scss @@ -7,76 +7,6 @@ overflow: hidden; user-select: none; - .title-bar { - height: 40px; - min-height: 40px; - display: flex; - justify-content: space-between; - align-items: center; - background: var(--bg-secondary); - border-bottom: 1px solid var(--border-color); - padding-right: 140px; // 为原生窗口控件留出空间 - - .window-drag-area { - flex: 1; - height: 100%; - -webkit-app-region: drag; - } - - .title-bar-controls { - display: flex; - align-items: center; - gap: 8px; - -webkit-app-region: no-drag; - margin-right: 16px; - - button { - background: transparent; - border: none; - color: var(--text-secondary); - cursor: pointer; - padding: 6px; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s; - - &:hover { - background: var(--bg-tertiary); - color: var(--text-primary); - } - - &:disabled { - cursor: default; - opacity: 1; - } - - &.live-play-btn { - &.active { - background: rgba(var(--primary-rgb, 76, 132, 255), 0.16); - color: var(--primary, #4c84ff); - } - } - } - - .scale-text { - min-width: 50px; - text-align: center; - color: var(--text-secondary); - font-size: 12px; - font-variant-numeric: tabular-nums; - } - - .divider { - width: 1px; - height: 14px; - background: var(--border-color); - margin: 0 4px; - } - } - } - .image-viewport { flex: 1; display: flex; diff --git a/src/pages/ImageWindow.tsx b/src/pages/ImageWindow.tsx index e6b2e5d..6b9777e 100644 --- a/src/pages/ImageWindow.tsx +++ b/src/pages/ImageWindow.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useRef, useCallback } from 'react' import { useSearchParams } from 'react-router-dom' import { ZoomIn, ZoomOut, RotateCw, RotateCcw } from 'lucide-react' import { LivePhotoIcon } from '../components/LivePhotoIcon' +import TitleBar from '../components/TitleBar' import './ImageWindow.scss' export default function ImageWindow() { @@ -207,31 +208,35 @@ export default function ImageWindow() { return (
-
-
-
- {hasLiveVideo && ( - <> - -
- - )} - - {Math.round(displayScale * 100)}% - -
- - -
-
+ + {hasLiveVideo && ( + <> + +
+ + )} + + {Math.round(displayScale * 100)}% + +
+ + +
+ } + />
{ + const confirmed = window.confirm('确定清空 wcdb.log 吗?') + if (!confirmed) return + try { + const result = await window.electronAPI.log.clear() + if (!result.success) { + showMessage(result.error || '清空日志失败', false) + return + } + showMessage('日志已清空', true) + } catch (e: any) { + showMessage(`清空日志失败: ${e}`, false) + } + } + const handleClearAnalyticsCache = async () => { if (isClearingCache) return setIsClearingAnalyticsCache(true) @@ -1379,15 +1394,12 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { scheduleConfigSave('keys', () => syncCurrentKeys({ imageAesKey: value, wxid })) }} /> -
- ⚠️ 快速获取方案基于本地缓存计算,可能因账号信息不匹配而不准确。若图片无法解密,请使用「内存扫描」方案。 -
- -
{isFetchingImageKey ? ( @@ -1399,7 +1411,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { ) : ( imageKeyStatus &&
{imageKeyStatus}
)} - 内存扫描需要微信正在运行,并在微信中打开 2-3 张图片大图后再点击 + 优先推荐缓存计算方案。若图片无法解密,可使用内存扫描(需微信运行并打开 2-3 张图片大图)
@@ -1430,6 +1442,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { +
@@ -2046,7 +2061,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { {isCheckingUpdate ? '检查中...' : '检查更新'} -
)} diff --git a/src/pages/WelcomePage.tsx b/src/pages/WelcomePage.tsx index 9c1d62c..26311e0 100644 --- a/src/pages/WelcomePage.tsx +++ b/src/pages/WelcomePage.tsx @@ -780,9 +780,6 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { {currentStep.id === 'image' && (
-
- ⚠️ 快速获取方案基于本地缓存计算,可能因账号信息不匹配而不准确。若图片无法解密,请使用下方「内存扫描」方案。 -
@@ -795,11 +792,11 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
- -
@@ -813,7 +810,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { imageKeyStatus &&
{imageKeyStatus}
)} -
内存扫描需要微信正在运行,并在微信中打开 2-3 张图片大图后再点击
+
优先推荐缓存计算方案。若图片无法解密,可使用内存扫描(需微信运行并打开 2-3 张图片大图)
)}
diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index 64806fc..efe7735 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -69,6 +69,7 @@ export interface ElectronAPI { log: { getPath: () => Promise read: () => Promise<{ success: boolean; content?: string; error?: string }> + clear: () => Promise<{ success: boolean; error?: string }> debug: (data: any) => void } diagnostics: {