# WorkUtils 插件 UI 与代码可维护性审查 审查日期:2026-07-30 ## 1. 结论 当前产品已经形成统一的左侧导航、颜色和基础按钮风格,但各插件仍是“每个 Window 自己搭 UI、自己开线程、自己管理 loading、自己处理错误”的实现方式。视觉统一停留在颜色层,交互状态和代码结构没有统一。 主要风险: 1. `Window` 类过大:8 个窗口平均约 824 行,`MobileUiAutomationWindow` 达 1702 行。 2. 异步状态没有统一模型:存在 `new Thread`、`Task`、`CompletableFuture`、布尔字段和手动 `Platform.runLater` 等多套实现。 3. loading 行为不一致:有的只禁用按钮,有的改文字,有的显示旋转图标,有的同时显示停止按钮;异常分支容易漏恢复状态。 4. UI 直接暴露敏感配置:Git 远程 URL 显示账号,数据库生成器显示完整 JDBC URL 和用户名。 5. 操作层级不清:刷新、执行、交换、终止、删除等按钮经常处于同一视觉层级。 6. 空状态普遍只是大片空白,用户无法判断是“尚未执行”“没有数据”还是“执行失败”。 7. UI、业务编排、文件操作、历史记录和线程控制混在同一个类里,AI 修改局部功能时需要理解整个窗口,误改概率高。 建议先建设公共 UI/异步基础设施,再逐插件迁移。不要同时重写所有插件。 ## 2. 统一设计与代码基线 ### 2.1 页面结构 所有插件统一为: 1. `PageHeader`:标题、简短说明、帮助入口。 2. `ConfigCard`:输入与选项。 3. `ActionBar`:一个主操作、零到两个次操作、一个条件显示的取消操作。 4. `OperationStatus`:阶段、进度、耗时、当前处理对象。 5. `ResultArea`:结果、空状态、失败状态。 6. `HistoryTable`:历史记录放在独立 Tab 或折叠区,不抢占主任务空间。 ### 2.2 按钮状态 按钮不应由业务代码直接反复调用 `setText`、`setDisable`、`setGraphic`。统一使用状态: ```java public enum OperationState { IDLE, VALIDATING, RUNNING, CANCELLING, SUCCEEDED, FAILED } ``` 统一按钮规范: | 状态 | 主按钮 | 取消按钮 | 输入区 | |---|---|---|---| | IDLE | 可用,显示动作名称 | 隐藏 | 可编辑 | | VALIDATING | spinner +“校验中” | 隐藏 | 禁用 | | RUNNING | spinner + 阶段名称 | 显示 | 禁用 | | CANCELLING | “正在停止…” | 禁用 | 禁用 | | SUCCEEDED | 恢复动作名称 | 隐藏 | 可编辑 | | FAILED | 恢复动作名称 | 隐藏 | 可编辑 | 建议增加公共组件: ```java public final class AsyncAction { private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(OperationState.IDLE); private final StringProperty message = new SimpleStringProperty(""); private final DoubleProperty progress = new SimpleDoubleProperty(-1); public void run( Supplier work, Consumer onSuccess, Consumer onFailure) { if (state.get() == OperationState.RUNNING) return; state.set(OperationState.RUNNING); Task task = new Task<>() { @Override protected T call() { return work.get(); } }; task.setOnSucceeded(e -> finish(() -> onSuccess.accept(task.getValue()))); task.setOnFailed(e -> finish(() -> onFailure.accept(task.getException()))); AppExecutors.io().execute(task); } private void finish(Runnable callback) { try { callback.run(); } finally { state.set(OperationState.IDLE); } } } ``` `LoadingButtonBinder` 只负责展示: ```java LoadingButtonBinder.bind( exportButton, exportAction.stateProperty(), "开始导出", "导出中…" ); ``` 这样异常、提前返回和取消都不会遗漏恢复按钮。 ### 2.3 推荐的插件目录边界 ```text biz// .java # 插件元数据和装配 application/ .java # 一次用户任务的编排 .java .java domain/ ... # 纯业务模型和规则 infrastructure/ ... # Git、DB、文件、进程等外部访问 ui/ Pane.java # 只组装区域 ViewModel.java # 可观察状态与命令 components/ ... ``` 限制规则: - `Pane` 不直接创建线程、访问文件、调用 Git/DB 或拼接业务结果。 - `ViewModel` 不创建 JavaFX 节点。 - `UseCase` 不依赖 JavaFX。 - 表格 cell 不直接执行文件操作,调用明确的 command。 - 单文件建议不超过 350 行;超过 500 行必须拆分。 - 每个异步操作只有一个拥有者,禁止窗口和 Service 同时管理线程生命周期。 ## 3. 主框架 证据:`screenshots/00-home.png` ### UI 问题 - 欢迎页信息价值低,大片空白;没有最近任务、常用插件或配置健康状态。 - 顶部只有重复的插件图标和名称,无法承载状态、帮助或全局通知。 - 导航图标来源不统一,部分图标显示风格和宽度不一致。 - 插件说明只存在元数据中,导航和页面内没有有效呈现。 ### 重构 - 欢迎页改为“最近使用 + 配置状态 + 快捷入口”,不要重复罗列功能名称。 - `MainWindow` 只负责路由与壳层;拆出 `NavigationPane`、`WorkspaceHeader`、`WelcomePane`。 - 增加统一 `NotificationCenter`,插件错误使用页内 banner 或 toast,不要各自弹 Alert。 - 用图标库统一图标,不再把 Emoji 当 UI 图标。 ## 4. Git 仓库同步 证据:`screenshots/01-git-sync.png` ### UI 问题 - 项目表直接展示含账号的远程 URL,既敏感又占据大量宽度。 - “Git …”列标题被截断,列宽策略失效。 - 69 个项目没有搜索、状态筛选和批量选择反馈。 - “同步执行”和“损坏仓库处理”以 Tab 分割,但用户无法看到同步任务与项目选择的关系。 - loading 主要靠禁用“同步”和启用“停止”,缺少总进度、当前项目、成功/失败计数。 ### 设计 - 项目列表顶部:搜索、状态筛选、`已选择 N 项`。 - URL 默认显示 `host/group/repo`,账号信息掩码;完整值仅通过“复制地址”获得。 - 同步操作采用固定底部 ActionBar:`同步选中项目` + 条件显示 `停止`。 - 运行时展示:总进度、当前仓库、成功/跳过/失败计数;失败项留在结果列表。 - “损坏仓库处理”作为失败结果的后续动作,而不是并列一级 Tab。 ### 代码重构 - `GitSyncWindow` 拆为 `ProjectTablePane`、`SyncProgressPane`、`DamagedRepoPane`。 - 从 `GitSyncPlugin` 中迁出 Git 命令、进程输出和 URL 解析,形成 `GitRepositoryGateway`。 - `SyncRepositoriesUseCase` 返回结构化事件,不向 UI 传原始日志字符串: ```java sealed interface SyncEvent { record Started(int total) implements SyncEvent {} record RepositoryStarted(String repository, int index) implements SyncEvent {} record RepositoryFinished(String repository, SyncOutcome outcome) implements SyncEvent {} record Completed(SyncSummary summary) implements SyncEvent {} } ``` ## 5. Git 分支评审 证据:`screenshots/02-git-review.png` ### UI 问题 - 页面信息密度过高,“粘贴识别”“手动字段”“IDEA 命令”“执行日志”同时出现。 - 主按钮“准备评审”含义不明确,实际上会拉取、切分支并打开 IDEA,副作用较大。 - 来源/目标依靠 A、B 解释,用户仍容易反向操作。 - 运行时 `runButton` 和 `historyOpenButton` 被同一 `running` 状态控制,两个无关动作耦合。 - 日志区默认空白,缺少阶段式反馈。 ### 设计 - 改为三步:①识别评审信息;②确认仓库和分支方向;③执行并查看结果。 - 主按钮文案明确为“切换到目标分支并打开评审”。 - 在执行前展示摘要:`来源 A → 目标 B`、将执行的 Git 操作、工作区是否干净。 - 每个操作拥有独立状态:识别、刷新、执行、打开历史互不锁死。 ### 代码重构 - `ReviewTextParser` 独立且纯函数化。 - `GitReviewViewModel` 持有表单和 `reviewAction`。 - `GitReviewService` 拆成 `GitStatusReader`、`BranchPreparationService`、`IdeaLauncher`。 - 用 `ReviewPlan` 表达执行前计划,使 UI 可预览,也便于单测。 ## 6. 文本对比 证据:`screenshots/03-diff.png` ### UI 问题 - 空状态完全空白,没有示例或输入提示。 - 结果区在未比较时仍占据大半页面。 - “开始对比”对于本地 CPU 操作没有 loading;大文本可能冻结 JavaFX 线程。 - 上一处/下一处在无结果时只是灰色,未解释差异数量。 - 原文/对比文缺少文件导入、编码和换行符信息。 ### 设计 - 输入区增加拖入文件/粘贴入口和字符、行数统计。 - 首次使用显示简短示例;比较后将结果区扩展。 - ActionBar 显示 `发现 N 处差异`,导航按钮显示当前位置 `3/18`。 - 大文本比较进入后台任务,按钮显示“比较中…”,并支持取消。 ### 代码重构 - 当前 `DiffWindow` 同时包含 LCS、字符分段、HTML 渲染和 UI,应拆为: - `DiffEngine` - `DiffResult` - `DiffHtmlRenderer` - `DiffViewModel` - `DiffPane` - `DiffResult` 保留结构化行和字符片段,UI 不再解析 HTML 来导航。 ## 7. MyBatis SQL 导出 证据:`screenshots/04-mybatis-export.png` ### UI 问题 - “执行日志”在未执行时是空白大框;历史表反而成为页面视觉重点。 - “导出/终止”始终并列,终止按钮即使禁用也持续占位。 - 缺少项目范围、目标目录和预估数量摘要。 - 历史表列标题被截断,文件名过长,核心信息难扫描。 - loading 有进度文本,但没有进度条和错误计数。 ### 设计 - 配置卡显示过滤条件、项目范围、输出目录。 - 主按钮为“导出 SQL”;运行后才出现“停止”。 - 状态区显示 `12/69`、当前项目、已导出 SQL 数、错误数。 - 历史记录放第二 Tab,文件名单行省略,Tooltip 展示完整路径。 ### 代码重构 - `MyBatisExportWindow` 和 `ScannerWindow` 的历史表、打开/目录菜单高度重复,提取 `ExportHistoryPane`。 - 用 `ExportRequest` 替代多个散落参数。 - `MyBatisExportService` 通过 `ProgressSink` 上报结构化进度。 ## 8. MyBatis 逆向工程 证据:`screenshots/05-mybatis-generator.png` ### UI 问题 - 顶部展示完整 JDBC URL 和用户名,存在泄露风险。 - 左侧项目树与右侧表查询、输出目录并排,用户任务顺序不清。 - 三个输出路径通常相关,却要求逐项浏览/填充。 - “取消”实际是关闭窗口,在嵌入模式下语义不成立。 - 查询表和生成操作没有统一 loading,也缺少连接失败后的就地恢复。 ### 设计 - 分成 ①数据源 ②选择表 ③生成配置 ④确认生成 四个区域或步骤。 - 数据源仅显示别名、数据库名、主机掩码和连接状态,完整连接串只在设置页维护。 - 输出目录选择一个项目根目录,再实时预览三个派生目录。 - “生成”前显示将覆盖/创建的文件摘要;完成后展示文件列表和打开目录。 - 嵌入模式去掉“取消”,改为“重置配置”。 ### 代码重构 - `GeneratorFormState` 统一持有表选择与目录规则。 - `DatasourceSummary` 是脱敏 DTO,UI 禁止接触密码和原始 URL。 - `QueryTablesUseCase` 与 `GenerateArtifactsUseCase` 使用两个独立 `AsyncAction`。 - 路径推导放入 `OutputLayoutPolicy`,避免三个按钮各自维护逻辑。 ## 9. 扫描功能 证据:`screenshots/06-scanner.png` ### UI 问题 - 页面写“扫描类型:关键词扫描”,但描述称支持关键词/Redis/Dubbo,入口和能力不一致。 - 正则匹配只是一个 Checkbox,缺少风险提示和表达式校验。 - 当前状态只显示“就绪”,位置和视觉权重过低。 - 历史表抢占主页面,空白结果/当前任务没有清晰区分。 - 开始扫描只禁用按钮,不展示 spinner、进度或取消能力。 ### 设计 - 扫描类型使用单选卡或下拉,并根据类型切换专属字段。 - 正则模式提供即时校验和示例。 - 执行区域展示扫描目录、项目数、匹配数和当前项目。 - 当前结果与历史记录分 Tab。 - 主按钮 loading 文案为“扫描中 12/69”,提供取消。 ### 代码重构 - 将 `ScanConfig` 升级为按类型区分的 sealed request,避免大量可空字段。 - `ScannerService` 不使用默认 `CompletableFuture` 线程池,统一注入受控 Executor。 - `ScannerWindow` 与历史文件操作解耦,复用公共历史组件。 ## 10. API 接口文档导出 证据:`screenshots/07-api-doc.png` ### UI 问题 - 三张模式卡占据大量空间,但下方关键输入没有标签,依赖 placeholder。 - “全限定名”“批量导出”的说明文字对非作者用户不够明确。 - 主按钮横跨整个页面,视觉重量过大。 - 导出配置、导出历史、基类配置同属一级 Tab,但基类配置是低频系统设置。 - 文件/目录选择也使用 loading,操作通常瞬时完成,产生假的忙碌感。 - loading 通过节点属性保存旧文字,且部分代码再次直接覆盖文字,状态来源不唯一。 ### 设计 - 模式切换改为紧凑 Segmented Control。 - 每种模式只显示相关字段,并有明确 Label、示例和校验错误。 - 主按钮右对齐,显示导出范围摘要。 - 基类配置移入系统设置或高级设置。 - 只有解析/生成过程进入 loading;文件选择不显示 spinner。 - 结果页显示生成文件、接口数、方法数、耗时和“打开/复制路径”。 ### 代码重构 - `ApiDocWindow` 拆成 `ExportConfigPane`、`ExportHistoryPane`、`BaseClassConfigPane`。 - `ExportMode` 各自实现 request 构造,避免 `startExport` 中按模式分支。 - `ApiDocExportUseCase` 负责 parse/generate/history,UI 只提交 `ApiDocExportRequest`。 - 删除窗口内 `setBtnLoading`,接入统一 `AsyncAction`。 - HTML 调试代理作为单独能力开关,不在生成 HTML 时隐式启动。 ## 11. 手机 UI 自动化 运行时当前禁用,结论基于源码;不能作为截图可验证的 UI 结论。 ### 主要问题 - `MobileUiAutomationWindow` 1702 行、67 个方法、30 个 action、36 处线程/回调相关代码,是最高风险文件。 - 环境安装、设备管理、用例运行、录制、截图轮询、步骤编辑、报告历史全部集中在一个类。 - 使用 `isRunning`、按钮文字和内联 style 共同表达状态,容易不一致。 - 截图轮询拥有独立线程,窗口生命周期与线程停止逻辑难验证。 - “运行/停止”和“开始录制/停止并生成用例”通过改文字复用按钮,状态机不明确。 ### 设计 - 一级拆为“运行用例”“录制用例”“环境与设备”三个页面。 - 顶部设备条只显示当前设备和健康状态;安装工具放入环境页。 - 运行页显示用例、参数、实时步骤和报告;录制页聚焦截图与步骤列表。 - 运行和录制使用显式状态机,停止按钮独立出现。 ### 代码重构 至少拆成: ```text mobileuiauto/ui/ MobileAutomationPane.java DeviceBar.java run/RunCasePane.java run/RunCaseViewModel.java record/RecordCasePane.java record/RecordCaseViewModel.java environment/EnvironmentPane.java environment/EnvironmentViewModel.java ``` 新增两个状态机: ```java enum RunState { IDLE, STARTING, RUNNING, STOPPING, SUCCEEDED, FAILED } enum RecordState { IDLE, STARTING, RECORDING, GENERATING, FAILED } ``` 截图轮询封装为 `ScreenshotStream extends AutoCloseable`,由录制 ViewModel 持有并在页面离开时关闭。 ## 12. 系统设置 ### UI 问题 - Tab 数量多但缺少分类,配置文件编辑依赖外部编辑器。 - “重新加载”的反馈不明确。 - 数据源、项目路径、插件管理本质是结构化配置,不应主要依赖文本文件。 ### 重构 - 分类为“常规 / 项目与数据源 / 插件 / 外观”。 - 配置项使用表单并提供校验、保存成功状态和恢复默认。 - 所有敏感字段支持显示/隐藏;列表页面只使用脱敏 DTO。 ## 13. 建议实施顺序 1. 建立 `OperationState`、`AsyncAction`、`LoadingButtonBinder`、`OperationStatusPane`。 2. 提取 `ExportHistoryPane`、`EmptyStatePane`、`NotificationCenter`。 3. 先迁移 Scanner 和 MyBatis SQL 导出,验证公共组件。 4. 迁移 API 文档导出和 MyBatis 逆向工程。 5. 拆分 Git 同步和 Git 评审。 6. 抽离 DiffEngine。 7. 最后单独重构手机 UI 自动化,不与其他插件并行大改。 ## 14. AI 友好编码约束 - 每个需求先定位到一个 UseCase 和一个 ViewModel,避免直接修改 Window。 - 不允许新增裸 `new Thread`。 - 不允许在业务代码直接调用 `Button#setText/setDisable/setGraphic`。 - 不允许使用 `Node#getProperties()` 保存业务状态。 - 不允许在 Window 中出现 Git、SQL、HTTP、进程命令或文件遍历。 - 所有跨线程结果必须是不可变 DTO。 - 所有状态使用 enum,不使用多个 boolean 拼状态。 - 每个 ViewModel 为每个 action 提供独立状态,禁止一个 `running` 锁住无关按钮。 - 为 Request 校验、状态迁移、UseCase 成功/失败/取消路径编写单元测试。 - UI 测试只验证绑定和可见性,不重复测试业务逻辑。 ## 15. 证据范围 已实际捕获并复核:主界面、Git 同步、Git 分支评审、文本对比、MyBatis SQL 导出、MyBatis 逆向工程、扫描、API 文档导出。 未实际触发具有副作用的同步、切分支、数据库查询、生成、导出和删除操作,因此 loading、取消、错误恢复的结论同时参考了源码。手机 UI 自动化因当前配置禁用,仅完成源码审查。截图不能证明完整键盘访问、焦点顺序、屏幕阅读器语义或颜色对比合规,这些需要专项测试。