文件操作
打开、保存、导入导出图纸的完整 API 指南。
概览
WebCAD 支持多种文件操作方式:
| 操作 | 命令方式 | API 方式 |
|---|---|---|
| 从服务端打开 | OPENFROMSERVER |
DrawingManagerService.openDrawing() |
| 保存到服务端 | SAVESERVER |
DrawingManagerService.saveDrawing() |
| 从本地缓存打开 | OPENFROMLOCAL |
localService.loadDrawingById() |
| 保存到本地缓存 | SAVELOCAL |
localService.saveDrawing() |
| 导入 DWG | IMPORTDWG |
service.uploadMap() + openDrawing() |
| 导出 DWG | EXPORTDWG |
drawingManager.exportToDwg() |
| 快速保存 | QSAVE |
下载 .vcad 文件(WASM 压缩) |
| 另存为 | SAVEAS |
下载 .vcad 文件(WASM 压缩) |
命令方式(简单场景)
const { Engine } = vjcad;
// 从服务端打开(弹出图纸浏览器)
await Engine.editor.executerWithOp('OPENFROMSERVER');
// 保存到服务端
await Engine.editor.executerWithOp('SAVESERVER');
// 从本地缓存打开
await Engine.editor.executerWithOp('OPENFROMLOCAL');
// 保存到本地缓存
await Engine.editor.executerWithOp('SAVELOCAL');
// 导入 DWG(弹出文件选择器)
await Engine.editor.executerWithOp('IMPORTDWG');
// 导出为 DWG
await Engine.editor.executerWithOp('EXPORTDWG');
// 快速保存(下载 .vcad 文件)
await Engine.editor.executerWithOp('QSAVE');
// 另存为(可指定文件名)
await Engine.editor.executerWithOp('SAVEAS');
服务端文件操作 API
DrawingManagerService
const { DrawingManagerService, Engine } = vjcad;
const drawingManager = new DrawingManagerService();
从服务端打开图纸
// 打开指定图纸
const openResult = await drawingManager.openDrawing({
type: 'imports', // 类型:'imports' | 'designs'
mapid: 'drawing-123', // 图纸ID
version: 'v1', // 版本号
branch: 'main', // 分支名称
// patchId 可选:不传 = 该分支最新 patch;传 'base' = 原始导入版本(此时 latestPatchId 也回传 'base',而非分支最新)
// readOnly: true 只会让返回值 isReadOnly=true,并不会只读加载——只读要给 openDbDoc 传第 3 参
});
if (!openResult.success) {
console.error(`打开失败: ${openResult.error}`);
return;
}
// 加载到编辑器
const webcadData = openResult.webcadData;
const jsonString = openResult.webcadJson;
const docName = `drawing-123_v1_main`;
const virtualFile = new File([jsonString], docName, { type: 'application/json' });
await Engine.view.openDbDoc(virtualFile, webcadData); // 第 3 参 true = 只读加载(模型空间实体只渲染到只读层,不进 items,不可选不可改)
// 保存来源信息(用于后续保存)——openDbDoc 不会设置它,且 setOriginalJson 在没有 serverSource 时直接返回 false
Engine.currentDoc.serverSource = {
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
branchName: 'main',
lastPatchId: openResult.latestPatchId || 'base'
};
// 保存原始数据(用于增量保存时的 diff 计算)
await Engine.currentDoc.setOriginalJson(openResult.webcadJson);
openDrawing 其他可选参数:clipbounds: [x1, y1, x2, y2] + selByCrossing(只取范围内实体,默认完全包含、true 为相交)、editAreas / editLayers(瓦片编辑区域/图层恢复用)、designPath(type:'designs' 时必填);editableFilter 与 readOnly 一样只影响返回的 isReadOnly。
保存到服务端
// 获取当前文档数据
const currentDoc = Engine.currentDoc;
const currentJson = JSON.stringify(currentDoc.toDb());
const originalJson = await currentDoc.getOriginalJson();
const serverSource = currentDoc.serverSource;
// 增量保存
const saveResult = await drawingManager.saveDrawing({
type: serverSource.type,
mapid: serverSource.mapid,
version: serverSource.version,
branchName: serverSource.branchName,
originalJson: originalJson, // 原始数据
currentJson: currentJson, // 当前数据
parentId: serverSource.lastPatchId,
drawingName: '我的图纸',
author: '作者名',
remark: '修改说明'
});
if (saveResult.status) {
if (saveResult.patchId === 'no_change') {
console.log('没有变化,未生成 patch'); // 不要把 'no_change' 写进 lastPatchId
} else {
console.log(`保存成功! Patch ID: ${saveResult.patchId}`);
// saveResult.idMapping({旧id: 新id})非空 = 服务器给新增实体重分配了 ID,须先按映射改本地实体 id
// 再更新原始数据(SAVESERVER 命令内部会做;手写 API 时可改用该命令,或保存后重新 openDrawing)
await currentDoc.setOriginalJson(currentJson);
currentDoc.serverSource.lastPatchId = saveResult.patchId;
Engine.undoManager.saveModified(); // 清除文档"已修改"标记(SAVESERVER 也会调用)
}
}
图纸类型说明
| 类型 | 说明 |
|---|---|
imports |
已导入的 DWG 图纸,支持增量 patch |
designs |
设计图,从零创建的图纸 |
本地缓存操作 API
本地缓存使用 IndexedDB 存储,支持离线编辑。
获取本地存储服务
const { getLocalStorageService, Engine } = vjcad;
const localService = getLocalStorageService();
保存到本地缓存
const currentDoc = Engine.currentDoc;
const currentJson = JSON.stringify(currentDoc.toDb());
// serverSource 必填(内部读 serverSource.type 生成 key;undefined 会被捕获后返回 success:false)
// 新建图纸没有 serverSource 时仿照 SAVELOCAL 命令补一个临时来源
const serverSource = currentDoc.serverSource
?? (currentDoc.serverSource = { type: 'designs', designPath: `local/${currentDoc.name}` });
const saveResult = await localService.saveDrawing({
serverSource,
webcadJson: currentJson,
serviceUrl: 'https://api.example.com',
drawingName: '我的图纸'
});
if (saveResult.success) {
console.log(`保存成功! ID: ${saveResult.id}`);
}
获取本地图纸列表
const drawings = await localService.listDrawings();
drawings.forEach(drawing => {
console.log(`${drawing.id}: ${drawing.drawingName}`);
});
从本地缓存加载
const loadResult = await localService.loadDrawingById(drawingId);
if (loadResult.success) {
// 返回 { success, record, webcadJson }——没有 serverSource 字段,
// 来源信息在 record 上(type/mapid/version/designPath/branchName/lastPatchId…)
const { record, webcadJson } = loadResult;
}
获取缓存统计
const stats = await localService.getStats();
console.log(`共 ${stats.totalCount} 个图纸, ${stats.totalSize} 字节`);
删除本地图纸
await localService.deleteDrawingById(drawingId); // 按数字 id(listDrawings 返回的 id)删除
await localService.deleteDrawing(drawingKey); // 按字符串 key(listDrawings 返回的 key)删除
// 注意:deleteDrawing 传数字 id 什么也不会删,却照样返回 success: true
导入 DWG/DXF
完整流程
const { DrawingManagerService, Service, MapOpenWay, openMapDarkStyle, Engine } = vjcad;
// 1. 创建文件输入
const input = document.createElement('input');
input.type = 'file';
input.accept = '.dwg,.dxf';
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
const drawingManager = new DrawingManagerService();
const service = drawingManager.getService();
// 2. 上传文件到服务器
const uploadResult = await service.uploadMap(file);
if (uploadResult.error) {
console.error('上传失败:', uploadResult.error);
return;
}
// 3. 生成图纸 ID
const mapid = file.name.replace(/\.(dwg|dxf)$/i, '') + '_' + Date.now();
// 4. 解析 DWG 文件
const openResult = await service.openMap({
mapid: mapid,
fileid: uploadResult.fileid,
uploadname: file.name,
mapopenway: MapOpenWay.Memory,
style: openMapDarkStyle()
}, true);
if (openResult.error) {
console.error('解析失败:', openResult.error);
return;
}
// 5. 获取 webcad 数据并加载
const drawingResult = await drawingManager.openDrawing({
type: 'imports',
mapid: openResult.mapid,
version: openResult.version,
branch: 'main'
});
if (drawingResult.success) {
const virtualFile = new File(
[drawingResult.webcadJson],
'drawing.webcad',
{ type: 'application/json' }
);
await Engine.view.openDbDoc(virtualFile, drawingResult.webcadData);
// 6. 保存来源信息
Engine.currentDoc.serverSource = {
type: 'imports',
mapid: openResult.mapid,
version: openResult.version,
branchName: 'main',
lastPatchId: drawingResult.latestPatchId || 'base'
};
await Engine.currentDoc.setOriginalJson(drawingResult.webcadJson);
}
};
// 触发文件选择
input.click();
支持的格式
| 格式 | 说明 |
|---|---|
.dwg |
AutoCAD 原生格式 |
.dxf |
图形交换格式 |
导出 DWG
命令方式
await Engine.editor.executerWithOp('EXPORTDWG');
API 方式
const { DrawingManagerService, Engine } = vjcad;
const drawingManager = new DrawingManagerService();
const service = drawingManager.getService();
// 获取当前文档数据
const currentDoc = Engine.currentDoc;
const webcadJson = JSON.stringify(currentDoc.toDb());
// 调用导出接口
const exportResult = await drawingManager.exportToDwg({
type: 'imports',
webcadJson: webcadJson,
mapid: '你的图ID',
version: '图纸版本'
});
// 返回 { status, error?, path, filename, entityCount }——DWG 生成在服务器端,
// 没有 success / data 字段,也没有文件二进制(类型里的 downloadUrl 实际不填充)
if (exportResult.status) {
console.log(`已导出 ${exportResult.filename},实体数 ${exportResult.entityCount}`);
// EXPORTDWG 命令随后打开云端管理页让用户下载(公有云地址;私有化把服务地址的 /api/v1 换成 /_cloud)
const fileid = exportResult.filename.replace('.dwg', '');
window.open(`https://vjmap.com/app/cloud/#/upload?fileid=${fileid}&mapid=${fileid}`);
} else {
console.error(exportResult.error);
}
新建文档
// 新建空白文档
Engine.view.newDocument();
// 设置文档名称
Engine.currentDoc.name = '新图纸';
关键属性
Engine.currentDoc.serverSource
保存图纸的服务器来源信息,用于后续保存和同步。
Engine.currentDoc.serverSource = {
type: 'imports', // 图纸类型
mapid: 'drawing-123', // 图纸 ID
version: 'v1', // 版本号
branchName: 'main', // 分支名称
lastPatchId: 'patch-456' // 最后的 Patch ID
};
Engine.currentDoc.isReadOnly
只是一个状态标记:只被 QSAVE/SAVEAS(只读时改走另存为)和文档状态指示读取,直接赋值不会阻止编辑。真正的只读加载是 Engine.view.openDbDoc(file, webcadData, true)(第 3 参,同时会把此标记置 true)。
Engine.currentDoc.isReadOnly = true; // 仅标记,实体仍可选可改
增量保存说明
WebCAD 使用增量保存机制:
- 原始数据:通过
setOriginalJson()保存的基准数据 - 当前数据:通过
toDb()获取的当前文档数据 - Diff 计算:服务端比较两者差异,只保存变更部分
- Patch 链:每次保存生成一个 Patch,形成版本历史
好处:
- 节省存储空间
- 支持版本回溯
- 支持多人协作冲突检测