From 65830d70fe4be8cfc6a4b065e8160928d0f4bff1 Mon Sep 17 00:00:00 2001 From: Wxw-Gu Date: Thu, 20 Aug 2026 10:18:59 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ui):=20=E7=BB=9F=E4=B8=80=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E5=8E=9F=E7=94=9F=E6=8C=89=E9=92=AE=E3=80=81=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=A1=86=E5=8F=8A=E4=BA=A4=E4=BA=92=E6=8E=A7=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/DatabaseConnectionPage.tsx | 671 +++++++++--------- .../src/components/chat/ChatHeader.tsx | 82 ++- .../src/components/chat/ChatStatusBar.tsx | 19 +- .../chat/ConversationContentSearch.tsx | 19 +- .../src/components/chat/ExportMenu.tsx | 16 +- .../chat/PersonalWechatSendDialog.tsx | 163 +++-- .../conversation/ConversationSearch.tsx | 7 +- .../ConversationSidebarHeader.tsx | 9 +- .../components/reports/AiReportWorkspace.tsx | 28 +- .../reports/MessageTypeSelector.tsx | 20 +- .../src/components/reports/ModelSummary.tsx | 55 +- .../reports/ReportDensitySelector.tsx | 37 +- .../components/reports/ReportEmptyState.tsx | 5 +- .../components/reports/ReportExportStatus.tsx | 5 +- .../reports/ReportGroupMemberSelector.tsx | 30 +- .../reports/ReportHistorySidebar.tsx | 10 +- .../components/reports/ReportInfoPanel.tsx | 9 +- .../reports/ReportMemberNameSelector.tsx | 21 +- .../reports/ReportRangeSelector.tsx | 37 +- .../reports/ReportSettingsPanel.tsx | 5 +- .../reports/ReportSourceSidebar.tsx | 10 +- .../reports/ReportTaskStatusPanel.tsx | 51 +- .../reports/ReportTemplateSelector.tsx | 119 ++-- .../src/components/reports/ReportZoomBar.tsx | 17 +- .../components/search/AISearchWorkspace.tsx | 69 +- src/renderer/src/components/ui/index.ts | 1 + .../src/components/ui/segmented-control.tsx | 32 + src/renderer/src/components/ui/select.tsx | 4 +- .../features/agent-hub/AgentHubWorkspace.tsx | 66 +- .../components/ApiRequestTester.tsx | 27 +- .../api-center/components/ApiRuntimePanel.tsx | 53 +- .../api-center/components/EndpointCatalog.tsx | 19 +- .../components/ReaderSkillOverview.tsx | 40 +- .../api-center/components/SkillDetails.tsx | 13 +- .../components/SkillInstallFlow.tsx | 18 +- .../components/SkillTargetSelector.tsx | 19 +- .../account-database/AccountOverview.tsx | 33 +- .../ai-model/AIImageUnderstandingTest.tsx | 14 +- .../settings/ai-model/AIProviderCard.tsx | 17 +- .../settings/ai-model/AIProviderEditor.tsx | 190 +++-- .../database-key/DatabaseKeyAutoDetect.tsx | 19 +- .../database-key/DatabaseKeyDangerZone.tsx | 26 +- .../database-key/DatabaseKeyDiagnostics.tsx | 5 +- .../database-key/DatabaseKeyEditor.tsx | 16 +- .../database-key/DatabaseKeyStatus.tsx | 10 +- .../AutoDetectImageKeySection.tsx | 15 +- .../image-decryption/ImageDecryptStatus.tsx | 5 +- .../image-decryption/ImageTestSection.tsx | 122 ++-- .../features/settings/pages/AIModelPage.tsx | 5 +- .../src/features/settings/pages/AboutPage.tsx | 55 +- .../settings/pages/AccountDatabasePage.tsx | 15 +- .../features/settings/pages/AdvancedPage.tsx | 11 +- .../settings/pages/CacheCleanupPage.tsx | 40 +- .../settings/pages/RecallProtectionPage.tsx | 13 +- .../settings/pages/TextToSpeechPage.tsx | 122 ++-- .../settings/pages/VoiceRecognitionPage.tsx | 150 ++-- src/renderer/src/styles/api.scss | 131 +--- src/renderer/src/styles/chat.scss | 496 +------------ src/renderer/src/styles/conversation.scss | 54 +- src/renderer/src/styles/reports-core.scss | 300 +------- .../src/styles/reports-templates.scss | 105 +-- src/renderer/src/styles/search.scss | 89 --- .../src/styles/settings-advanced.scss | 11 - .../src/styles/settings-preferences.scss | 107 +-- .../src/styles/settings-text-to-speech.scss | 119 +--- src/renderer/src/styles/settings.scss | 189 +---- .../account-database-controls.test.tsx | 64 ++ tests/component/agent-hub-controls.test.tsx | 78 ++ tests/component/ai-model-settings.test.tsx | 114 ++- .../ai-search-workspace-regression.test.tsx | 44 ++ tests/component/api-center-controls.test.tsx | 178 +++++ tests/component/cache-cleanup.test.tsx | 11 + tests/component/chat-menus.test.tsx | 94 ++- .../conversation-sidebar-header.test.tsx | 43 +- tests/component/conversation-sidebar.test.tsx | 55 +- tests/component/database-connection.test.tsx | 49 +- .../component/database-key-controls.test.tsx | 130 ++++ .../component/image-decryption-test.test.tsx | 104 ++- .../personal-wechat-send-dialog.test.tsx | 17 + tests/component/report-features.test.tsx | 89 ++- tests/component/settings-basic-pages.test.tsx | 74 ++ tests/component/text-to-speech-page.test.tsx | 48 +- .../voice-recognition-settings.test.tsx | 10 +- tests/e2e/app.spec.ts | 354 ++++++++- tests/e2e/support/electron-main.cjs | 73 +- tests/e2e/visual.spec.ts | 519 +++++++++++++- tests/setup/component.ts | 15 + 87 files changed, 3685 insertions(+), 2768 deletions(-) create mode 100644 src/renderer/src/components/ui/segmented-control.tsx create mode 100644 tests/component/account-database-controls.test.tsx create mode 100644 tests/component/agent-hub-controls.test.tsx create mode 100644 tests/component/api-center-controls.test.tsx create mode 100644 tests/component/database-key-controls.test.tsx create mode 100644 tests/component/settings-basic-pages.test.tsx diff --git a/src/renderer/src/components/DatabaseConnectionPage.tsx b/src/renderer/src/components/DatabaseConnectionPage.tsx index 8e4ad2b..f21a179 100644 --- a/src/renderer/src/components/DatabaseConnectionPage.tsx +++ b/src/renderer/src/components/DatabaseConnectionPage.tsx @@ -1,6 +1,7 @@ import React from 'react' import type { DatabaseKeyEnvironment, WechatAccountCandidate } from '../../../shared/database-key' import { WINDOWS_VC_RUNTIME_DOWNLOAD_URL } from '../../../shared/windows-runtime' +import { Button, IconButton, Input, Tabs, TabsList, TabsTrigger } from './ui' const GUIDE_URL = 'https://github.com/Wxw-Gu/WechatExplorer/blob/main/docs/user-guide/getting-started.md' @@ -207,366 +208,360 @@ export function DatabaseConnectionPage({ 查看 5 分钟上手教程 → -
- - -
+ 开始连接 + 高级用户:已有数据库密钥?手动连接 + - {mode === 'automatic' ? ( -
-
- {Array.from({ length: 6 }, (_, index) => ( - - ))} -
-
-
- - - -
- - {statusKind === 'error' - ? '当前步骤未完成' - : [ - '检查本机环境', - '让微信停在登录页面', - '确认开始准备', - `正在完成 ${isMac ? 'macOS' : 'Windows'} 授权`, - '获取成功, 现在可以重新登录微信了', - '验证数据库连接' - ][guideStep - 1]} - -

- {statusKind === 'error' - ? status - : status || - [ - '确认下方检测结果;没有找到目录时可以手动选择。', - '请退出当前微信账号,让微信停留在登录页面,然后点击“我已准备好”。', - '开始后请按页面提示完成系统授权。', - '正在准备连接组件,请不要关闭微信或 TraceMemo。', - '请回到微信完成登录,登录成功后再回来验证。', - '正在验证密钥和本地数据库,请稍候。' - ][guideStep - 1]} -

-
+ {mode === 'automatic' ? ( +
+
+ {Array.from({ length: 6 }, (_, index) => ( + + ))}
- {guideStep === 1 && ( - <> -
-
-
操作系统
-
{environment?.osVersion || (isMac ? 'macOS' : 'Windows')}
-
-
-
微信版本
-
{environment?.wechatVersion || '未检测到'}
-
-
-
数据结构
-
{environment?.dataStructureVersion || '未检测到'}
-
-
-
- 存储路径 - -
-
- - onDbRootChange(event.target.value)} - placeholder={defaultPath} - title={dbRoot || defaultPath} - aria-label="微信数据存储路径" - spellCheck={false} - onFocus={(event) => event.currentTarget.select()} - /> - - {dbRoot || defaultPath} +
+
+ + + +
+ + {statusKind === 'error' + ? '当前步骤未完成' + : [ + '检查本机环境', + '让微信停在登录页面', + '确认开始准备', + `正在完成 ${isMac ? 'macOS' : 'Windows'} 授权`, + '获取成功, 现在可以重新登录微信了', + '验证数据库连接' + ][guideStep - 1]} + +

+ {statusKind === 'error' + ? status + : status || + [ + '确认下方检测结果;没有找到目录时可以手动选择。', + '请退出当前微信账号,让微信停留在登录页面,然后点击“我已准备好”。', + '开始后请按页面提示完成系统授权。', + '正在准备连接组件,请不要关闭微信或 TraceMemo。', + '请回到微信完成登录,登录成功后再回来验证。', + '正在验证密钥和本地数据库,请稍候。' + ][guideStep - 1]} +

+
+
+ {guideStep === 1 && ( + <> +
+
+
操作系统
+
{environment?.osVersion || (isMac ? 'macOS' : 'Windows')}
+
+
+
微信版本
+
{environment?.wechatVersion || '未检测到'}
+
+
+
数据结构
+
{environment?.dataStructureVersion || '未检测到'}
+
+
+
+ 存储路径 + +
+
+ + onDbRootChange(event.target.value)} + placeholder={defaultPath} + title={dbRoot || defaultPath} + aria-label="微信数据存储路径" + spellCheck={false} + onFocus={(event) => event.currentTarget.select()} + /> + + {dbRoot || defaultPath} + - - +
+
+
+
微信状态
+
{environment?.wechatRunning ? '运行中' : '未检测到'}
+
+
+ {accounts.length > 0 && ( +
+

选择微信账号

+ {accounts.map((account) => ( + + ))} + -
-
-
-
微信状态
-
{environment?.wechatRunning ? '运行中' : '未检测到'}
-
-
- {accounts.length > 0 && ( -
-

选择微信账号

- {accounts.map((account) => ( - - ))} - -
- )} + 选择其他账号 + + + )} + + )} +
+ {guideStep === 1 && ( + <> + + + )} -
- {guideStep === 1 && ( - <> - + )} + {guideStep === 3 && ( + + )} + {guideStep === 4 && ( + + )} + {guideStep === 5 && ( + - - + )} + {guideStep > 1 && !isFetching && !isConnecting && ( +
+ + +
+ )} + {(isFetching || isConnecting) && ( + - - )} - {guideStep === 2 && ( - - )} - {guideStep === 3 && ( - - )} - {guideStep === 4 && ( - - )} - {guideStep === 5 && ( - - )} - {guideStep === 6 && ( - - )} - {guideStep > 1 && !isFetching && !isConnecting && ( -
- - + 取消 + + )} +

+ {isMac ? ( + <> + macOS 首次获取密钥需要关闭 SIP。{' '} + + 查看说明 + + + ) : ( + <> + Windows 需要 Microsoft Visual C++ 2015-2022 x64 运行库。{' '} + + 下载运行库 + + + )} +

+ {showMacKeyFaq && isMac && ( + + 获取失败?查看连接排查 + + )} +
+ ) : ( +
+

+ 仅适用于已经通过其他方式获得当前微信账号数据库密钥的高级用户。第一次使用请返回“开始连接”。 +

+
+ +
+ onDbKeyChange(event.target.value)} + placeholder="输入或粘贴 64 位数据库密钥" + autoComplete="off" + spellCheck={false} + /> + + + +
+ 密钥通过系统安全存储加密保存在当前设备。
- )} - {(isFetching || isConnecting) && ( - - )} -

- {isMac ? ( - <> - macOS 首次获取密钥需要关闭 SIP。{' '} - - 查看说明 - - - ) : ( - <> - Windows 需要 Microsoft Visual C++ 2015-2022 x64 运行库。{' '} + {platform === 'win32' && ( +

+ +
+ onDbRootChange(event.target.value)} + placeholder={defaultPath} + title={dbRoot || defaultPath} + spellCheck={false} + onFocus={(event) => event.currentTarget.select()} + /> + +
+
+ )} + {status &&
{status}
} + {platform === 'win32' && ( +

+ 无法加载数据库组件时,请安装 Microsoft Visual C++ 2015-2022 x64 运行库。{' '} 下载运行库 - +

)} -

- {showMacKeyFaq && isMac && ( - - 获取失败?查看连接排查 - - )} -
- ) : ( -
-

- 仅适用于已经通过其他方式获得当前微信账号数据库密钥的高级用户。第一次使用请返回“开始连接”。 -

-
- -
- onDbKeyChange(event.target.value)} - placeholder="输入或粘贴 64 位数据库密钥" - autoComplete="off" - spellCheck={false} - /> - -
- 密钥通过系统安全存储加密保存在当前设备。 -
- {platform === 'win32' && ( -
- -
- onDbRootChange(event.target.value)} - placeholder={defaultPath} - title={dbRoot || defaultPath} - spellCheck={false} - onFocus={(event) => event.currentTarget.select()} - /> - -
-
- )} - {status &&
{status}
} - {platform === 'win32' && ( -

- 无法加载数据库组件时,请安装 Microsoft Visual C++ 2015-2022 x64 运行库。{' '} - - 下载运行库 - -

- )} - - {isConnecting && ( - - )} - -
- )} + {isConnecting ? '正在连接…' : '连接数据库'} + + {isConnecting && ( + + )} + +
+ )} +
- + TraceMemo
diff --git a/src/renderer/src/components/chat/ChatHeader.tsx b/src/renderer/src/components/chat/ChatHeader.tsx index 8a0d5f8..3ece724 100644 --- a/src/renderer/src/components/chat/ChatHeader.tsx +++ b/src/renderer/src/components/chat/ChatHeader.tsx @@ -1,6 +1,16 @@ import React, { useState } from 'react' import { Contact } from '../../../../shared/types' -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui' +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + IconButton, + Tooltip, + TooltipContent, + TooltipTrigger +} from '../ui' import { ConversationContentSearch } from './ConversationContentSearch' import { AiIcon, MoreIcon, RefreshIcon, SearchIcon, SendIcon } from './icons' import { supportsPersonalWechatSend } from '../../utils/runtime-environment' @@ -43,7 +53,7 @@ export function ChatHeader({ } return ( -
+
{contact.avatar ? ( @@ -69,54 +79,72 @@ export function ChatHeader({ onClose={handleCloseSearch} /> ) : ( - + )} - + - + onRefreshData?.()}>刷新数据 - - - - + + + 仅支持 macOS + + )} + +
) diff --git a/src/renderer/src/components/chat/ChatStatusBar.tsx b/src/renderer/src/components/chat/ChatStatusBar.tsx index 235a7ec..a9f80a7 100644 --- a/src/renderer/src/components/chat/ChatStatusBar.tsx +++ b/src/renderer/src/components/chat/ChatStatusBar.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { Button, Switch } from '../ui' import { RefreshIcon } from './icons' interface ChatStatusBarProps { @@ -27,11 +28,7 @@ export function ChatStatusBar({
已显示当前范围 {count} 条消息
头像或名称缺失时,请先在微信中进入该聊天 - + - +
) diff --git a/src/renderer/src/components/chat/ConversationContentSearch.tsx b/src/renderer/src/components/chat/ConversationContentSearch.tsx index ac4a29b..ec7f926 100644 --- a/src/renderer/src/components/chat/ConversationContentSearch.tsx +++ b/src/renderer/src/components/chat/ConversationContentSearch.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { IconButton, Input } from '../ui' import { CloseIcon, SearchIcon } from './icons' interface ConversationContentSearchProps { @@ -16,22 +17,30 @@ export function ConversationContentSearch({ }: ConversationContentSearchProps): React.ReactElement { return (
- + - onChange(event.target.value)} + className="h-9 pr-10 pl-9 text-xs min-[901px]:pr-[7.5rem]" autoFocus /> - + {value ? `${resultCount} 条结果` : '输入关键词'} - +
) } diff --git a/src/renderer/src/components/chat/ExportMenu.tsx b/src/renderer/src/components/chat/ExportMenu.tsx index 4b7f7fb..dbca64d 100644 --- a/src/renderer/src/components/chat/ExportMenu.tsx +++ b/src/renderer/src/components/chat/ExportMenu.tsx @@ -1,5 +1,11 @@ import React from 'react' -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui' +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger +} from '../ui' import { ArrowDownIcon, ExportIcon } from './icons' export type ExportRange = number | 'all' @@ -22,16 +28,16 @@ export function ExportMenu({ disabled, onExport }: ExportMenuProps): React.React return ( - + {EXPORT_OPTIONS.map((option, index) => ( diff --git a/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx b/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx index 5ca12c4..4d4ef3e 100644 --- a/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx +++ b/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx @@ -2,7 +2,17 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { Contact } from '../../../../shared/types' import type { PersonalWechatSenderStatus } from '../../../../shared/personal-wechat' import type { TextToSpeechSettings, TextToSpeechVoice } from '../../../../shared/text-to-speech' -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui' +import { + Button, + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + SegmentedControl, + SegmentedControlItem, + Textarea +} from '../ui' type SendMode = 'image' | 'voice' type VoiceSource = 'generated' | 'file' @@ -440,22 +450,36 @@ export function PersonalWechatSendDialog({
{(status?.state === 'unsupported_version' || status?.state === 'runtime_missing') && onOpenTextToSpeechSettings ? ( - + ) : null} - - + +
@@ -468,35 +492,32 @@ export function PersonalWechatSendDialog({ ))} -
- - -
+ + {mode === 'image' ? (
图片 - + {image ? (
{image.name} @@ -508,28 +529,20 @@ export function PersonalWechatSendDialog({
) : (
-
- - -
+ + {voiceSource === 'generated' ? (
@@ -539,14 +552,18 @@ export function PersonalWechatSendDialog({ {selectedTtsVoice?.name || '尚未选择音色'}
{onOpenTextToSpeechSettings ? ( - + ) : null}