# 交接文档:数据集详情图表 + 全 App 网络层异步化 > 给下一个会话:读完本文件即可无缝接手。最后更新 2026-06-12。 > 分支 **`feat/dataset-detail-chart`**,领先 main **68 commits**,工作区干净,**测试 116/116 全绿**。 --- ## 0. 一句话现状 geopro(Qt6/C++ 离线桌面客户端,1:1 复刻赛盈地空 web)已完成两大块: 1. **数据集详情图表**(仅 ERT 反演 `dd_inversion_data`,QwtPlot 落地,用户已验收)。 2. **全 App 网络层 100% 异步化**(详情/导航/登录/项目列表全异步,同步 `QEventLoop` 阻塞路径已彻底删除,无技术债)。 均**未合并入 main**,分支挂起等收尾。 --- ## 1. 背景 - geopro = **Qt6 / C++ 离线桌面客户端**,目标**像素级 1:1 复刻** web 系统 `http://tenant.geomative.cn/#/projectSpace/datasetMange/datasetInfo`(赛盈地空)。 - **硬约束**:禁 QWebEngine/Chromium(离线)。图表用本地 **QwtPlot(轴/交互/图例)+ VTK 算法层(等值线几何)+ 连续/离散色阶**。验收 = **视觉等价 + 交互一致**(非字节级 diff)。 - **标准测试 ds**:`id=1458990939709440`(ddCode=dd_inversion_data)。 - 站点 baseUrl:`http://tenant.geomative.cn/pop-api`。Auth header:`geomativeauthorization: Geomative `。 --- ## 2. 技术栈与构建/测试 - Qt6 Widgets + **Qwt 6.2**(源码 `external/qwt-src/`,**用 `cmake/qwt.cmake` 以 CMake 构建**静态库,**不要用 qmake**——本机 VS2026 缺 vswhere)。 - **VTK 9.6 仅算法层**(`vtkBandedPolyDataContourFilter`/`vtkStripper`/`vtkSplineFilter`/`vtkDataSetSurfaceFilter`),不做 VTK 渲染。 - CMake(VS 自带)+ Ninja + MSVC 14.51;GoogleTest/CTest;vcpkg(仅非 Qt 依赖)。 - **构建**:`powershell.exe -ExecutionPolicy Bypass -File scripts/dev-build.ps1` - ⚠️ `LNK1104 无法打开 geopro_desktop.exe` = exe 在运行:先 `Get-Process geopro_desktop -ErrorAction SilentlyContinue | Stop-Process -Force` 再构建。 - ⚠️ **改头文件后偶发** ninja 增量陈旧导致链接报「符号在 main.cpp.obj 重复定义」:删 `build/release/src/app/CMakeFiles/geopro_desktop.dir/main.cpp.obj` 后重建。 - **测试**:`powershell.exe -ExecutionPolicy Bypass -File scripts/dev-test.ps1`(**只跑 ctest 不构建——必须先 dev-build**)。当前 **116/116 绿**。 - 单测过滤:`build/release/tests/geopro_tests.exe --gtest_filter=ApiBatch.*` - ⚠️ 直接跑 exe 时 4 个 `CrsTransform/VoxelRegister/Terrain` 失败是缺 `PROJ_DATA` 环境项;**ctest(dev-test)会注入环境、全绿**——以 dev-test 的 "X/X passed" 为准。 - **网络/署名**:AuthLiveTest 联网真打站点;提交信息**全局禁用署名**(勿加 Co-Authored-By)。 --- ## 3. 第一块:数据集详情图表(已完成,仅 dd_inversion_data) 详情含两页签,均经用户逐项验收: - **原数据**:Plotly scattergl 风格散点(方形点/白描边/连续色阶/x 轴顶部)。 - **网格数据**:填充等值面(栅格 QImage)+ 黑色等值线(vtkStripper+vtkSplineFilter 样条平滑,**每条线只标一个**数值)+ NaN 白边裁剪 + 底部异常表/描述。 数据 API(均经 Playwright 实测): | 页 | 接口 | |---|---| | 原数据 | `lvl/colorGradation/getDetail`(type1) + `dd/ert/inversion/getErtRawDataScatterGraph/{id}` | | 网格 | `dd/ert/inversion/rows/{id}`(服务端网格化,**波动 1–4s**) + `colorGradation/getDetail`(type2) + `exception/queryException/{id}` | > ⚠️ 架构偏离:原 spec 定 QGraphicsView,**实际落地用 QwtPlot**(见 `plans/2026-06-11-dataset-detail-chart-v2-qwt.md`,权威)。 **策略分派已打通**(本次会话):控制器从硬编码 `dd_inversion_data` 改为走 `ChartStrategyRegistry`(在 **controller 层** `src/controller/IDatasetChartStrategy.hpp`,含 `hasGridPhase()`),未注册类型优雅降级「暂不支持」。`ErtInversionStrategy`(app 层)已注册。**这是接其余 dd 类型的地基**。 --- ## 4. 第二块:全 App 网络层异步化(本次会话完成,无技术债) **动机**:原 `ApiClient` 用 `QEventLoop` 死等每个请求 → 全 App 冻 UI(网格 rows 1–4s 最痛)。 **核心安全不变量(spec §5.0,务必遵守)**:「abort 后绝不回灌」靠三件套—— 1. 每层 `aborted_` 入口守卫(`disconnect` 只是尽力而为,挡不住已入队的迟到信号); 2. 控制器**句柄身份比对**(`if (load != current_) return;` 丢弃迟到信号); 3. **一律 `deleteLater`**,禁止同步 delete。 错误判定口径:业务 `code != 200 || !rawError.isEmpty()`(HTTP 200 也可能 code=500)。 **net 层原语**(`src/net/`,AUTOMOC 已 ON): - `IApiCall`/`ApiCall`:单请求句柄(包 QNetworkReply),`finished(ApiResponse)` + `abort()`,自管理 deleteLater。 - `ApiBatch`:**并发汇聚** N 个 IApiCall,全成功 `succeeded(QList)` / 任一失败 **fail-fast** `failed(i,resp)`+abort 其余。 - `ApiChain`:**串行依赖链**(上步结果喂下步工厂,工厂可抛转 failed)。**契约:首个 step 工厂不得同步抛/同步 fire**(否则信号在调用方连接前丢失;生产路径都发异步请求,满足)。 - `ApiResponseParse::buildResponse`:sync/async 共用解析(已无 sync 调用方,仅 ApiCall 用)。 - `ApiClient`:仅 `getAsync/postJsonAsync`(**同步 `get/postJson`/`await`/QEventLoop 已删除**)。 **data 层**: - 详情:`ChartLoad`/`GridLoad`(抽象基 + `ApiChartLoad`/`ApiGridLoad` 实现,包 ApiBatch + 注入 parse)、`IAsyncDatasetRepository`、`ApiDatasetRepository.loadChartAsync/loadGridAsync`、`DatasetLoads.hpp`(ChartParts/GridParts)。 - 导航:`NavRequest`(单非模板句柄,`done(QVariant)`/`failed(QString)`;`ApiNavRequest` 包 IApiCall + 解析器)、`NavLoads.hpp`(各类型 `Q_DECLARE_METATYPE`)、`IAsyncProjectRepository`(9 方法 `...Async` 后缀,返回 `NavRequest*`,薄封装;汇聚/链编排放控制器)、`ApiProjectRepository` 仅实现异步接口。 **controller 层**: - `DatasetDetailController`:abort-and-replace + 句柄身份比对 + `loadStarted(dsId,Phase)` + 析构 abort;走 `ChartStrategyRegistry` 分派。 - `WorkbenchNavController`:全异步(NavRequest 续延依赖链 + 控制器内并发计数 + abort-and-replace + 身份比对);**删除 `busy_`/`BusyGuard`/`drainPendingCheckedTms`**;`busyChanged(bool)` 语义改为「有在飞句柄」(去抖);`setCheckedTms` 入口去重 + 「最后一次为准」由 abort-and-replace 承接 + `tmExceptionCache_` 缓存命中不发请求。对外信号面**零改动**(main.cpp 接线没动)。 **app 层**: - `AuthService`(net 层):`fetchCaptchaAsync()→CaptchaLoad`、`loginAsync()→LoginLoad`(内用 ApiChain:verifyCodeCheck→RSA(step2 工厂内)→login2)。`LoginWindow`:不冻 + 可取消(析构 abort),删 `repaint()` hack。 - `ProjectListDialog`:改用 `IAsyncProjectRepository`(NavRequest + abort-and-replace + 身份比对 + 析构 abort),过滤/分页/选项目行为等价。 - `LoadingOverlay`:网格懒加载「加载中」遮罩,接 `loadStarted`、就绪/失败隐藏。 - `main.cpp`:`qRegisterMetaType()`;装配注入 registry/异步 repo(引用绑定,接线信号面不变)。 **执行方式**:全程 subagent-driven(每块 implementer + spec 符合度评审 + 代码质量评审 opus + follow-up 加固)。测试 75 → 116(+41,含 abort 回灌防护回归用例)。 --- ## 5. 关键文件地图 - net:`src/net/{IApiCall.hpp,ApiCall.*,ApiBatch.*,ApiChain.*,ApiResponseParse.*,ApiClient.*,AuthService.*,AuthLoads.*}` - data:`src/data/api/{DatasetLoads.hpp,DatasetLoadHandles.*,ApiDatasetRepository.*,NavRequest.*,NavLoads.hpp,ApiProjectRepository.*,DatasetChartDto.*}`、`src/data/repo/{IAsyncDatasetRepository.hpp,IAsyncProjectRepository.hpp,IDatasetRepository.hpp(本地样例同步,保留),RepoTypes.hpp}` - controller:`src/controller/{DatasetDetailController.*,WorkbenchNavController.*,IDatasetChartStrategy.hpp}` - app:`src/app/main.cpp`、`src/app/panels/{DatasetDetailPanel.*,DatasetDetailPage.*,LoadingOverlay.*,chart/*}`、`src/app/login/LoginWindow.*`、`src/app/ProjectListDialog.*`、`src/app/panels/chart/ErtInversionStrategy.hpp` - 测试:`tests/net/{FakeApiCall.hpp,test_api_batch.cpp,test_api_chain.cpp,test_auth.cpp(live),test_auth_loads.cpp}`、`tests/data/{test_nav_request.cpp,test_dataset_load_handles.cpp}`、`tests/controller/{test_dataset_detail_controller.cpp,test_workbench_nav_controller.cpp}`、`tests/app/test_chart_strategy_registry.cpp` --- ## 6. 尚未完成 / 下一步(按优先级) ### A. 收尾本分支(最先) 分支领先 main 68 commits、116/116 绿、工作区干净。用户多次选「保持现状」未合并。下一步可问用户:①合并回 main(本地) ②推送建 PR(origin=gitea `https://gitea.geomative.cn/gaozheng/geopro.git`) ③保持现状。用 `superpowers:finishing-a-development-branch`。 ### B. 其余 dd 类型详情图(计划已写,多数 BLOCKED) 计划:`plans/2026-06-11-dataset-detail-other-dd-types.md`。 - **Phase 1(策略分派打通):✅ 已完成**(本次会话)。 - **Phase 0(样本探查):需做**——用 Playwright 登录原版 web,对每类 dd 找有数据对象、抓真实响应存 fixtures + 渲染规格。**这是后续所有类型的前置。** - **Phase 2(ERT 测量散点)/ Phase 3(TEM 折线,新 LineChartView)**:仅当 Phase 0 确认有样本才解锁。 - **Phase 4:BLOCKED**——dd_grid/轨迹/测井/GPR,**当前租户无活数据样本**(GPR 对象无数据、无测井数据),按 1:1 复刻铁律不能凭空实现。 - 矩阵详见计划文件。 ### C. 工具条编辑类功能(网格页占位按钮,未做,单独立项) 白化 / 滤波处理 / 色阶配置 / 异常框注 / 自动标注 / 网格化 / 另存为 / 导出 / 描述富文本 / 大视图(Esc)全屏。当前是占位按钮。交互较重,建议先 brainstorm 拆解。spec §2.3 列为范围外。 ### D. 纯整洁 follow-up(非债、非阻断) - 删 `DatasetDetailController::ChartData.grid/gridScale` 死字段(从未填充,预存)。 - 若未来引入 cross-thread/QueuedConnection,再补 `qRegisterMetaType>()`。 --- ## 7. 相关文档 - **spec**:`docs/superpowers/specs/2026-06-11-apiclient-async-design.md`(异步设计 + §5.0 安全不变量 + §7 错误判定/退出契约 + 顶部「状态更新」=已完成)、`docs/superpowers/specs/2026-06-11-dataset-detail-view-design.md`(详情视图,顶部状态=仅 ERT 反演完成 + QwtPlot 偏离)。 - **plan**:`docs/superpowers/plans/2026-06-11-apiclient-async-datasetdetail.md`(详情试点)、`2026-06-11-apiclient-async-rollout.md`(导航 Part A + 登录 Part B,均落地)、`2026-06-11-dataset-detail-other-dd-types.md`(其余 dd 类型,Phase 1 done)、`2026-06-11-dataset-detail-chart-v2-qwt.md`(QwtPlot 返工,权威)。 - API 文档:`docs/apis`(business_OpenAPI.json)。 - 外部方案:`docs/Geopro3.0_二维图表返工技术方案.md`。 --- ## 8. 记忆(务必遵守,`~/.claude/projects/D--Git-lanbingtech-geopro/memory/`) - `reply-in-chinese` — 全程中文回复。 - `study-original-via-playwright` — **任何不确定必须用 Playwright 实地看原版,禁止臆测**(尤其做新 dd 类型详情图前抓样本/规格)。 - `no-embellishment-replicate-exactly` — 严格 1:1,不加原版没有的特性(曾因等值线「周期重复标注」被纠正)。 --- ## 9. 工作方式备注(本次会话沿用,效果好) - 用户偏好 **subagent-driven** 执行 + 实现后 **code review + spec 符合度** 双评审(opus 评审);「非必要不停,一口气做完」。 - **真并行构建在本项目不安全**(dev-build 硬编码单一 build 目录 + 多任务共改 main.cpp/CMakeLists)→ 用顺序执行;worktree 隔离不了构建(硬编码路径 + 冷配置开销)。 - 破坏性接口改形需**原子落地**(同批提交)保持构建绿(详情试点 Task5+6 合并、Part A A4+A5、本次清债 都是此教训)。 - 评审 follow-up 多为小加固,直接在新代码上补;「不为不可能的场景写错误处理」(CLAUDE.md §2)——评审若建议为构造保证不会发生的情况加防御,可不采纳。