16 KiB
Batch 2 实施依据:对象/数据集对话框(新建/编辑/导入/导出/插件)
实地研究原系统 http://tenant.geomative.cn(项目「香港威立雅」projectId=1439735554211840,
projectTypeId 1445121423155200)所得。API 经页面 token replay + 真实 DOM 操作 + fetch 录制捕获。
口径:成功码
code==200;列表载荷在data(非对象时包成data.value)。 token 头geomativeauthorization;basehttp://tenant.geomative.cn/pop-api。
更新(2026-06-15):权威来源已厘清,前述"⚠️未捕获"壁垒解除
三个来源各司其职,别再混用:
- xlsx「客户端」页签 = 客户端菜单/交互规格(权威)。
- 原 web 压缩 bundle = 提交体的"地面真相"(首要权威):线上正在跑、能成功建对象的真实前端代码,它发什么服务端就收什么。
bundle 抓
http://tenant.geomative.cn/static/js/各路由 chunk 后 grep 还原:GsForm-*.js(GS表单) /tmForm-D7d9h9Nb.js(TM表单,数据管理页) /ImportForm-*.js(导入) /projectStructure-*.js。 docs/apisOpenAPI = 交叉校验(次级):自动生成、已知有缺漏/错标(如 GS 名称字段标成projectName)。与 bundle 冲突时以 bundle 为准,并在 §B 并列标注、留一次真实请求验证。- 原型
prototype.geomative.cn= 粗略视觉 mockup,菜单画不全,不作为菜单删减依据。
A. xlsx 规定的菜单(= 现有实现已对齐,勿按原型删减)
| 区域 | 菜单/动作(xlsx 原文) | 对话框来源(xlsx 标注) |
|---|---|---|
| 对象-添加+ | 新建GS(当前为TM时无效)、新建TM | GS→项目配置\项目结构\添加;TM→数据管理\新增方法对象 |
| 对象-右键 | 显示/隐藏、定位、属性、异常详情、编辑、新建GS、新建TM、导入DS(1..n)、删除 | 编辑→数据管理\编辑;导入→数据管理\导入 |
| 数据集-添加+ | 新建/导入一个数据集 | 数据管理\可新建数据类型\导入 |
| 数据集-右键 | 数据集详情、属性、插件1/2/3…、导出、删除 | 导出→批量导出\文件导出 |
注:xlsx 第32行"显示项目(也是一个GS)"——项目根本身就是 GS 节点。最强证据是真实数据:queryProjectStruct 返回的结构里确有 TM(type=2) 直接挂在项目根(type=1) 下(如 NERT1、"123")——若根不允许建 TM,该数据无从产生。故项目根(作为 GS)允许新建 TM。
B. 提交体(bundle 为准,OpenAPI 并列校验,替换下文旧"⚠️未捕获")
bundle 还原(= 客户端应照此发送):
POST /business/gsObject { gsTypeId, parentId, projectId, name, responsiblePersonName, properties }
PUT /business/gsObject { gsTypeId, id, projectId, name, responsiblePersonName, properties } // 编辑无 parentId
· gsTypeId(测试对象类型)、responsiblePersonName(负责人)、name 为顶层固定字段;动态字段在 properties
· parentId 仅 POST 用 = 右键所在节点 id(含项目根)
POST /business/tmObject { tmTypeId, name, properties, projectId, parentId, parentType:"1" }
PUT /business/tmObject { tmTypeId, id, name, properties, projectId, parentId, parentType:"1" }
· bundle 中 create/edit 用同一 spread,故 PUT 也带 parentId/parentType
· parentId = 右键所在节点 id(GS 或项目根);parentType 恒字符串 "1"
POST /business/dsObject/import (参数走 query string;file 为 multipart 上传项)
{ file*, dsTypeId*, projectId*, structParentConfType*(int), structParentId*, scriptCode?, scriptParamListJsonStr?, aliasName? }
· 选脚本先 getDynamicForm(typeId=scriptId, type=6) 取脚本参数;checkImport 校验坐标
getDynamicForm 的 type:1=GS对象 / 2=TM对象 / 6=导入脚本参数(已确认)。
✅ 线上实测已确认(intercept-abort,未写库):动态字段嵌套在 properties.<fieldCode> 下。
证据:编辑弹窗真实字段 DOM id —— GS properties_topography/geotechnical/climate/hydrology、TM properties_supervisor/notes 等;顶层固定字段(gsTypeId/responsiblePersonName/name、tmTypeId/name)与 properties 分开渲染。
bundle ↔ OpenAPI 冲突(已由压缩源码分析确证,以 bundle 为准;OpenAPI 为文档生成误差):
下表结论来自 GsForm/tmForm 压缩源码还原(= 线上前端真实请求构造)。线上 app 正是发这些 body 且能成功建/改对象,故服务端接受度亦由线上运行佐证。OpenAPI 的 projectName/整数等是自动生成的文档误差,不采信。
| 字段 | bundle(线上真实代码) | OpenAPI(生成文档) | 取舍 | 实测状态(含源码分析) |
|---|---|---|---|---|
| GS 名称 | 顶层 name |
POST 标 projectName、PUT 无 |
用 name(projectName 系 DTO 误标) |
✅ 源码已证 |
| TM 名称 | 顶层 name |
无 name |
用 name(OpenAPI 漏列) |
✅ 源码已证 |
parentType |
字符串 "1" |
integer | 发字符串 "1" |
✅ 源码已证 |
| PUT tmObject 的 parentId/parentType | 带(create/edit 同一 spread) | 无 | 带(匹配线上) | ✅ 源码已证 |
动态字段位置 properties.<code> |
是 | —— | 随 bundle | ✅ 源码 + 运行时双证 |
注:拦截-阻断的运行时抓包在本数据集走不通(变更表单必填项多、arco 校验拦截、且不写生产库),但源码分析已足以确证 body,无需真实写入。
C. 客户端待改项(对照现有实现)
ObjectFormDialog提交体字段错误(最关键):- 现:
{typeId, type, projectId, id?, properties}+ extraBody{structParentId, structParentConfType} - 应(按 bundle):GS→
{gsTypeId, parentId(仅新建), name, responsiblePersonName, properties}; TM→{tmTypeId, parentId, parentType:"1", name, properties}(+ 编辑加 id) structParentId/structParentConfType是导入的字段,被误用于新建对象(OpenAPI 证实这两字段只在dsObject/import出现)。
- 现:
- 项目根节点:现为"非交互无菜单",应按 GS 处理(提供 新建GS/新建TM/属性,依 xlsx 第32行 + 真实数据"TM 挂根")。
- 新建TM 方法类型来源(决策):用
queryTmType?projectId=&gsId=——带 gsId 能按所选 GS 过滤方法类型,比 web 全局 tmMethodList 更准。 - 父对象确定逻辑(决策):客户端不复刻 web 数据管理页"先选左树"的选择器;父对象 = 右键所在节点(GS 或项目根),
parentId直接取该节点 id。 DynamicFormEditor顶层固定字段:GS 须含 gsTypeId 下拉 + responsiblePersonName + name;TM 新建须含 tmTypeId 下拉 + name。displayComponentType全集映射 —— 已由源码确证,见 §E.1。
D. 状态(原"待确认"已全部由源码分析落地)
- ✅
displayComponentType完整映射 → §E.1 - ✅ 导出 body → §E.2
- ✅ 插件机制 → §E.3(结论:web 无"ds→插件"菜单,客户端此项为原创设计,需产品决策)
- §B 字段已由源码确证,不再需要真实写入回证。
- (
PUT /dsObject/updateDsObjectbody OpenAPI 已定义{dsObjectId, description, attachedParameters, ...},见下文第六节。)
E. 源码补全(FieldItem / 导出 / 插件,均来自压缩源码)
E.1 displayComponentType → 控件(FieldItem index-1LyDq-Qg.js,权威全集)
| 值 | 控件 | 备注 |
|---|---|---|
| 1 | a-input 单行文本 | |
| 2 | a-input 禁用 | 只读文本 |
| 3 | a-checkbox | label=item.name |
| 4 | a-select 下拉 | options=optionsObject |
| 5 | a-input 单行文本 | 同 1 |
| 6 | a-date-picker 日期 | |
| 7 | a-time-picker 时间 | |
| 8 | a-date-picker show-time | 日期时间 YYYY-MM-DD HH:mm:ss |
| 9 | a-textarea 多行文本 | |
| 10 | a-input-number 数字 | |
| 11 | a-tree-select 树选择 | data=optionsObject, fieldNames{key:value,title:label,children:childList} |
| 其他 | a-input-number mode=button | 步进数字 |
- 字段路径
${fieldPrefix=properties}.${fieldCode}→ 确认动态值在properties.<fieldCode>。 requiredType语义纠正:1=必填可编辑;2=只读禁用(disabled: isDisabled||requiredType===2,required 仅 ===1);其他=可选可编辑。- comp 类型由后端按类型配置(如"测区"把地形地貌配成 6=日期选择器),客户端按本表渲染即可。
E.2 导出 body(ExportModal ExportModal-DWdo-HxP.js,两套)
模板导出 POST /business/templateExport/export { dsObjectIdList:[ds ids], templateId }
文件导出 POST /business/dataFileExport/export { idList:[ds ids], fileType [, startTime, endTime] }
// startTime/endTime 仅 fileType==5(时序数据)
文件下载 POST /business/dataFileExport/download (responseType=blob)
selectType(checked/currentPage) 仅决定取哪些 id,不进 body。- 批量导出页流程:选数据类型/时间范围 → 勾选文件(ds ids) → 「导出」开 ExportModal(选模板) → 提交。
- 客户端数据集右键「导出」(单 ds, 按模板) →
templateExport/export { dsObjectIdList:[该ds], templateId };模板来自 localStoragetemplate.fileTemplateList 或dataFileExport/queryFileType。
E.3 插件机制(结论:web 无对应交互,客户端为原创设计)
- web 中没有"数据集右键→插件"菜单。模型从模型管理页按类型用专用 Modal 调起(resipy=ERT反演、gprpy=GPR处理 等,见
resipyModel-*.js/gprpyModel-*.js/modelManage-*.js)。 - 关联方向是模型→可用数据集:
GET /business/model/{scriptType}/dsObjectList/{projectId}(如model/resipy/dsObjectList/{projectId}),即每个模型声明它能跑哪些 ds。 - 全局模型目录:
GET /business/model/list;任务分页:POST /business/model/task/page。 - xlsx 的"插件1/2/3 = 列出与当前 ds 关联的插件"是客户端原创(ds→模型 的反向)。web 无单一直达接口,需:用 model/list + 各模型 dsObjectList 反查匹配,或后端新增"按 ds 列模型"接口。此项需产品/后端决策,非源码可定。
一、项目结构(已用于现有功能)
GET /business/projectStruct/queryProjectStruct/{projectId}
→ 扁平节点 [{id,parentId,name,type(1=GS,2=TM),typeName,typeId,confCode,collectTime}],根 parentId="0"。
二、编辑 / 新建对象 —— 动态表单(核心,最大工作量)
表单 schema 来源(统一端点,编辑弹窗打开时调用)
POST /business/project/getDynamicForm
body {"typeId": <类型id>, "id": <对象id>, "type": <1=GS|2=TM>, "projectId": <projectId>}
→ 返回结构同 getDetail:
{
typeId, confCode, name(类型名), description,
formList: [ { groupName, values: [ FieldDef... ] } ], // 可多组(对应弹窗内分页签:基本信息/测线布设/数据质量检查...)
properties: { <fieldCode>: <当前值> } // 编辑预填;新建时为空
}
(getGsObjectDetail / tmObject/getDetail 返回同样的 formList/properties,可互为参考。 TM 的 getDetail 还含 dsList / dsClassifyTypeList / gridFieldList。)
FieldDef 字段定义
confFieldId, fieldUseType(1=核心字段,2=普通),
fieldCode(键), fieldName(标签),
displayComponentType(控件类型), requiredType(1/2 必填标志——待核实方向),
displaySort, fieldDataType(4=字符串,5=日期,6=日期时间...),
fieldConfigJsonObject:{fieldChnFormat,fieldRemark,fieldEngFormat},
optionsObject(下拉项;普通字段为 null)
displayComponentType 已观察样例(需补全映射)
TM「常规高密度电阻率法」编辑弹窗实测控件:
- 方法名称(只读文本)
- 基本信息组:名称/电极数/电极间距/测线长 = 只读文本(核心字段 fieldUseType=1,编辑时禁用)
- 设备 = 下拉(必填) ; 布设日期 = 日期选择(必填) ; 天气 = 下拉(必填)
- 布设人/审核人 = 文本(必填) ; 备注 = 文本 GS「测区」formList 含:创建人/创建日期(comp6)/名称(comp1)/创建时间(comp7)/地形地貌(comp8)/岩土性质... → 推测 comp1=单行文本, 6=日期, 7=日期时间, 8=多行文本;落地前需逐一在原版核实。
新建 TM 的类型选择
GET /business/tmObject/queryTmType?projectId=..&gsId=..
→ [{label:"瞬变电磁方法", value:<tmTypeId>, code:"TEM01"}]
新建流程:选 GS → queryTmType 选方法类型 → getDynamicForm(typeId,type=2,无id) 取空表单 → 填 → POST。
⚠️ 未捕获(真实壁垒,不可猜) → 已解除,见上「更新 §B」
下列 body 当时认为不可得;实为 OpenAPI requestBody + bundle 已给出,参见文首「更新 §B」。
→ 见 §BPOST /business/gsObject→ 见 §BPOST /business/tmObject→ 见 §BPUT /business/gsObject/PUT /business/tmObjectPUT /business/dsObject/updateDsObject(更新DS body)仍待确认
三、删除(已实现 Batch1)
DELETE /business/gsObject/{id} DELETE /business/tmObject/{id} DELETE /business/dsObject/{id}
四、导入 DS
TM 的 getDetail.dsList = 该 TM 可承载的 ds 类型 [{dsTypeId,nameChn,nameEng,canImport,canExport,...}](canImport=true 的可导入)。
GET /business/dsObject/query/script?dsTypeId=..&tmTypeBaseConfId=.. → 该类型可用导入脚本。
POST /business/dsObject/checkImport → 校验脚本所需轨迹/坐标文件是否存在。
POST /business/dsObject/import(query 参数已知:aliasName, dsTypeId*, file*, projectId*, scriptCode, scriptParamListJsonStr, structParentConfType*, structParentId*)
⚠️ file 为上传项(multipart);scriptParamListJsonStr 结构未捕获。
五、导出
POST /business/templateExport/queryExportObject body {projectId} → 可选导出对象树 [{id,parentId,name,check}]。
GET /business/templateExport/queryDataType/{tmTypeBaseConfId} → 按方法查数据类型。
POST /business/templateExport/export(body 未捕获)。
模板列表见 localStorage template:templateTypeList[数据管理-数据报告/异常体报告], fileTypeList[WORD/EXCEL], fileTemplateList。
另:数据集详情页「导出」按钮点击未发请求/未弹窗(可能直接下载或需先配模板)——待核实。
六、插件(数据集右键)
GET /business/model/list → 全局模型目录 [{id,scriptCode,scriptName,scriptOperationType,formItemList}]
例:script_ert_inner_inversion「ert反演(默认)」、script_radar_resultant_data_processing「雷达数据处理(默认)」。
⚠️ "与当前 ds 关联"的过滤逻辑未捕获(model/list 不含 ds 类型关联;各模型用 dsObjectList/{projectId} 声明可接受的数据源)。
POST /business/model/task/page → 模型任务分页(用于"数据集任务"面板)。
实施建议顺序
body 已由「更新 §B」给出,下列"先捕获"前置多数已不需要;仅 §D 三项仍待确认。
- 动态表单引擎
DynamicFormEditor(Qt):按 formList 渲染 comp1/6/7/8 + 下拉(optionsObject) + 必填校验 + 只读核心字段;编辑用 properties 预填;GS/TM 顶层固定字段(§C-5:gsTypeId/responsiblePersonName/name、tmTypeId/name)。displayComponentType 全集映射仍需核实。 - 修编辑/新建提交体字段(§C-1):GS→gsTypeId+parentId(仅新建)+name+responsiblePersonName;TM→tmTypeId+parentId+parentType:"1"+name。
- 项目根菜单按 GS 处理(§C-2);新建TM 类型用 queryTmType(§C-3);父对象=右键节点(§C-4)。
- 导入 DS(dsList 选类型 → query/script → 文件 → checkImport → import,body 见 §B)。
- 导出(queryExportObject 选对象 + 模板 → export)。export body 待确认(§D)。
- 插件子菜单(model/list 列出;调用/关联逻辑待定,§D)。
捕获提交载荷的方法(已在原版页面注入录制器 window.__rec)
在 Playwright 浏览器对原系统执行一次「编辑保存/新建保存/导入/导出」(填完必填项),
即可从 window.__rec 读到真实 method+url+body。
注:编辑/新建/导入的 body 已由 bundle 还原(§B),此法现仅用于验证 §B 冲突表 4 项与捕获导出 export body(会改数据,谨慎)。