server
本文回答:服务端打开 / 保存的核心是
DrawingManagerService:drawingManager.openDrawing(params)→Engine.view.openDbDoc(file, data)+ 设currentDoc.serverSource+setOriginalJson(json);保存 = 把currentDoc.toDb()与originalJson一起交给drawingManager.saveDrawing(...)——diff 在前端用 WASM 计算后只上传 patch,依赖parentId检测协作冲突。手写保存要处理no_change/idMapping/conflict三种结果,推荐直接用SAVESERVER。
一、最小代码:从服务端打开
import { DrawingManagerService, Engine } from 'vjcad';
const drawingManager = new DrawingManagerService();
const result = await drawingManager.openDrawing({
type: 'imports', // 'imports' | 'designs'
mapid: 'example_map',
version: 'v1',
branch: 'main',
patchId: 'base', // 可省略 = 最新
readOnly: false
});
if (result.success) {
const file = new File(
[result.webcadJson],
'drawing.webcad',
{ type: 'application/json' }
);
await Engine.view.openDbDoc(file, result.webcadData);
// 关键:保存来源信息(后续保存依赖)
Engine.currentDoc.serverSource = {
type: 'imports',
mapid: 'example_map',
version: 'v1',
branchName: 'main',
lastPatchId: result.latestPatchId || 'base'
};
await Engine.currentDoc.setOriginalJson(result.webcadJson);
}
MCP 参考:vjcad/doc/file/file-operations.md、vjcad/example/file/01openfromserver.js。
二、IOpenDrawingParams 表
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
type |
'imports'|'designs' |
是 | 图纸类型 |
mapid |
string | imports必填 | 图纸 ID |
version |
string | imports必填 | 版本号 |
designPath |
string | designs必填 | 设计图路径 |
branch |
string | 否 | 默认 main |
patchId |
string | 否 | 默认最新 |
readOnly |
boolean | 否 | 是否只读 |
clipbounds |
[minX, minY, maxX, maxY] |
否 | 裁剪范围 |
editAreas |
Array<{minX, minY, maxX, maxY}> |
否 | 多瓦片编辑区 |
editLayers |
string[] |
否 | 编辑图层名 |
三、IOpenDrawingResult 表
| 属性 | 类型 | 说明 |
|---|---|---|
success |
boolean | 是否成功 |
error |
string | 错误信息 |
webcadJson |
string | 解压的 webcad JSON |
webcadData |
any | 解析后对象 |
latestPatchId |
string | 最新 Patch ID |
isReadOnly |
boolean | 是否只读 |
MCP 参考:vjcad/doc/file/file-operations.md。
四、保存到服务端(增量)
const currentJson = JSON.stringify(Engine.currentDoc.toDb());
const originalJson = await Engine.currentDoc.getOriginalJson();
const src = Engine.currentDoc.serverSource;
const r = await drawingManager.saveDrawing({
type: src.type,
mapid: src.mapid,
version: src.version,
branchName: src.branchName,
originalJson, // 用于 diff 计算
currentJson,
parentId: src.lastPatchId, // 冲突检测
drawingName: '我的图纸',
author: '作者',
remark: '修改说明'
});
if (r.status && r.patchId === 'no_change') {
// 无变化:什么都不更新(别把 'no_change' 当成下一次的 parentId)
} else if (r.status) {
// 内置 SAVESERVER 的做法:先清实体 _modified 标记(GROUP 子实体需递归)再重新序列化作为新基准
for (const block of Engine.currentDoc.blocks.items) block.items.forEach(e => e.clearModifiedForDiff?.());
let cleanJson = JSON.stringify(Engine.currentDoc.toDb());
if (r.idMapping && Object.keys(r.idMapping).length) {
// 服务端给新增实体重分配了 id({ oldId: newId }):须同步更新本地实体 id 与 cleanJson 后再存基准
}
await Engine.currentDoc.setOriginalJson(cleanJson);
Engine.currentDoc.serverSource.lastPatchId = r.patchId;
} else if (r.conflict?.hasConflict) {
// 并发冲突:按 r.conflict.conflictingEntities / conflictingLayers 处理后,带 conflictResolution 重新 saveDrawing
} else {
console.error(r.error, r.errorCode);
}
返回值
ISaveWebcadPatchResponse:{status, patchId?, branch?, designPath?, isNew?, error?, errorCode?, idMapping?, conflict?}。无变化时返回{status: true, patchId: 'no_change'};idMapping与冲突处理细节较多,推荐直接用SAVESERVER命令(内置处理了上述全部分支并同步本地缓存)。
MCP 参考:vjcad/doc/file/file-operations.md、vjcad/example/file/03saveserver.js。
五、ISaveDrawingParams 表(保存)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
type |
'imports'|'designs' |
是 | 图纸类型 |
mapid |
string | imports必填 | — |
version |
string | imports必填 | — |
designPath |
string | designs可选 | 空 = 新建 |
branchName |
string | 否 | 默认 main |
originalJson |
string | 否 | 用于 diff |
currentJson |
string | 是 | 当前 webcad JSON |
parentId |
string | 否 | 父 Patch(冲突检测) |
drawingName |
string | 否 | 图纸名 |
author |
string | 否 | 作者 |
remark |
string | 否 | 备注 |
六、增量保存特点
- 只保存修改部分:diff 在浏览器端用 WASM
createWebcadDiff(originalJson, currentJson)计算,只上传 patch(超阈值再压缩),节省带宽 / 存储。 - 缺少
originalJson时走整图分支:把currentJson作为vcadData整图上传,不带parentId(失去冲突检测),等同新建保存。 - 支持版本历史:每次保存生成新 patch。
- 支持冲突检测:通过
parentId检测并发修改。
MCP 参考:vjcad/doc/file/file-operations.md。
七、命令版
await Engine.editor.executerWithOp('OPENFROMSERVER'); // 弹出图纸浏览器
await Engine.editor.executerWithOp('SAVESERVER'); // 增量保存
八、图纸列表查询
const all = await drawingManager.listAllDrawings();
console.log('后台 DWG:', all.serverMaps);
console.log('已导入:', all.imports);
console.log('设计图:', all.designs);
const drafts = await drawingManager.listWebcadDraws({
mapid: 'example_map',
version: 'v1'
});
MCP 参考:vjcad/doc/file/file-operations.md。
⚠️ 分支族方法名的双宿主分工:
DrawingManagerService上是友好名createBranch/deleteBranch/mergeBranch/listBranches/deleteDrawing;底层Service类(new vjcad.Service(url, token),负责原始 HTTP 调用)上才是createWebcadBranch/deleteWebcadBranch/mergeWebcadBranch/deleteWebcadDraw。listWebcadDraws两个宿主都有。在drawingManager上找createWebcadBranch会得到undefined(反之亦然),跨文档抄代码时注意名字前缀。
反例
- 打开后没有设
currentDoc.serverSource:SAVESERVER时找不到归属,保存失败。 - 没
setOriginalJson:getOriginalJson()为空,saveDrawing走整图vcadData分支——不做增量 diff、不带parentId,失去冲突检测。 - 保存成功后无条件
lastPatchId = r.patchId:无变化时patchId是'no_change',下次会拿它当parentId。 - 不传
parentId:多人协作时数据相互覆盖。
参考(官方文档 MCP)
- 文档:
vjcad/doc/file/file-operations.md - 示例:
vjcad/example/file/01openfromserver.js、03saveserver.js