# 文件操作

打开、保存、导入导出图纸的完整指南。

---

## 概览

| 操作 | 命令方式 | API 方式 |
|------|---------|---------|
| 从服务端打开 | `OPENFROMSERVER` | `DrawingManagerService.openDrawing()` |
| 保存到服务端 | `SAVESERVER` | `DrawingManagerService.saveDrawing()` |
| 从本地缓存打开 | `OPENFROMLOCAL` | `localService.loadDrawingById()` |
| 保存到本地缓存 | `SAVELOCAL` | `localService.saveDrawing()` |
| 导入 DWG | `IMPORTDWG` | `service.uploadMap()` + `openDrawing()` |
| 导出 DWG | `EXPORTDWG` | `drawingManager.exportToDwg()` |
| 快速保存 | `QSAVE` | 下载 WASM 压缩的 .vcad 文件（含 `_meta.serverSource`） |
| 另存为 | `SAVEAS` | 下载 .vcad 文件（`OPEN` 同时接受 .vcad/.webcad；手工 `JSON.stringify(toDb())` 得到的是未压缩 .webcad，不等价于 QSAVE） |

---

## 命令方式（简单场景）

```javascript
const { Engine } = vjcad;

await Engine.editor.executerWithOp('OPENFROMSERVER');   // 从服务端打开
await Engine.editor.executerWithOp('SAVESERVER');       // 保存到服务端
await Engine.editor.executerWithOp('OPENFROMLOCAL');    // 从本地缓存打开
await Engine.editor.executerWithOp('SAVELOCAL');        // 保存到本地缓存
await Engine.editor.executerWithOp('IMPORTDWG');        // 导入 DWG
await Engine.editor.executerWithOp('EXPORTDWG');        // 导出 DWG
await Engine.editor.executerWithOp('QSAVE');            // 快速保存
await Engine.editor.executerWithOp('SAVEAS');            // 另存为
```

---

## 服务端文件操作 API

### 从服务端打开图纸

```javascript
const { DrawingManagerService, Engine } = vjcad;
const drawingManager = new DrawingManagerService();

const openResult = await drawingManager.openDrawing({
    type: 'imports',            // 'imports' | 'designs'
    mapid: 'drawing-123',
    version: 'v1',
    branch: 'main',
    patchId: 'base',           // 可选，不传则使用最新
    readOnly: false
});

if (openResult.success) {
    const virtualFile = new File(
        [openResult.webcadJson], 'drawing.webcad',
        { type: 'application/json' }
    );
    await Engine.view.openDbDoc(virtualFile, openResult.webcadData);

    // 保存来源信息（用于后续保存）
    Engine.currentDoc.serverSource = {
        type: 'imports',
        mapid: 'drawing-123',
        version: 'v1',
        branchName: 'main',
        lastPatchId: openResult.latestPatchId || 'base'
    };
    await Engine.currentDoc.setOriginalJson(openResult.webcadJson);
}
```

### 保存到服务端

```javascript
const currentDoc = Engine.currentDoc;
const currentJson = JSON.stringify(currentDoc.toDb());
const originalJson = await currentDoc.getOriginalJson();
const src = currentDoc.serverSource;

const saveResult = await drawingManager.saveDrawing({
    type: src.type,
    mapid: src.mapid,
    version: src.version,
    branchName: src.branchName,
    originalJson: originalJson,
    currentJson: currentJson,
    parentId: src.lastPatchId,
    drawingName: '我的图纸',
    author: '作者名',
    remark: '修改说明'
});

if (saveResult.status && saveResult.patchId !== 'no_change') {   // 无变化时 patchId 为 'no_change'，什么都不更新
    await currentDoc.setOriginalJson(currentJson);
    currentDoc.serverSource.lastPatchId = saveResult.patchId;
}
// 另需处理：saveResult.idMapping（服务端给新增实体重分配 id，须先更新本地实体 id 再存基准）、
// !saveResult.status && saveResult.conflict?.hasConflict（按 conflictingEntities 处理后带 conflictResolution 重存）。
// 推荐直接用 SAVESERVER 命令，详见 file/server.md
```

---

## 本地缓存操作 API

```javascript
const { getLocalStorageService, Engine } = vjcad;
const localService = getLocalStorageService();

// 保存（serverSource 必填；新图先补 { type: 'designs', designPath: 'local/xxx' }，否则抛错返回 success:false）
if (!Engine.currentDoc.serverSource) {
    Engine.currentDoc.serverSource = { type: 'designs', designPath: 'local/我的图纸' };
}
const saveResult = await localService.saveDrawing({
    serverSource: Engine.currentDoc.serverSource,
    webcadJson: JSON.stringify(Engine.currentDoc.toDb()),
    drawingName: '我的图纸'
});

// 列表
const drawings = await localService.listDrawings();

// 加载（打开后恢复 serverSource 时要带上 record.originalJsonCompressed，
// 且 record.type === 'tiles' 改回 'imports'，否则之后 SAVESERVER 无基准、不做增量 diff）
const loadResult = await localService.loadDrawingById(drawingId);

// 删除
await localService.deleteDrawingById(drawingId);

// 统计
const stats = await localService.getStats();
```

---

## 导入 DWG/DXF

```javascript
const { DrawingManagerService, MapOpenWay, openMapDarkStyle, Engine } = vjcad;

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();

    // 上传文件
    const uploadResult = await service.uploadMap(file);
    if (uploadResult.error) return;

    // 解析 DWG
    const mapid = file.name.replace(/\.(dwg|dxf)$/i, '') + '_' + Date.now();
    const openResult = await service.openMap({
        mapid, fileid: uploadResult.fileid,
        uploadname: file.name,
        mapopenway: MapOpenWay.Memory,
        style: openMapDarkStyle()
    }, true);
    if (openResult.error) return;

    // 获取数据并加载
    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);
    }
};

input.click();
```

---

## 导出 DWG

```javascript
const { DrawingManagerService, Engine } = vjcad;

const drawingManager = new DrawingManagerService();
const service = drawingManager.getService();
const webcadJson = JSON.stringify(Engine.currentDoc.toDb());

const exportResult = await drawingManager.exportToDwg({
    type: 'imports',
    webcadJson: webcadJson,
    mapid: '你的图ID',
    version: '图纸版本'
});

// 实际返回 { status: true, path, filename, entityCount } 或 { status: false, error }
// 只返回服务端文件名/路径，不含二进制（没有 success / data 字段；d.ts 里的 downloadUrl 从不赋值）
if (exportResult.status) {
    console.log('已导出:', exportResult.filename, exportResult.path, '实体数:', exportResult.entityCount);
    // 下载需按服务端路径拼 URL（参考 EXPORTDWG 命令）：
    // `${SystemConstants.SERVICE_ROOT_URL}<下载目录>/${exportResult.filename}?token=${SystemConstants.ACCESS_TOKEN}`
} else {
    console.error('导出失败:', exportResult.error);
}
```

---

## 新建文档

```javascript
Engine.view.newDocument();
Engine.currentDoc.name = '新图纸';
```

---

## 增量保存说明

WebCAD 使用增量保存机制：

1. **原始数据** — 通过 `setOriginalJson()` 保存的基准数据
2. **当前数据** — 通过 `toDb()` 获取的当前文档数据
3. **Diff 计算** — 前端用 WASM `createWebcadDiff` 比较差异，只上传 patch；缺少原始数据时整图上传（不带 `parentId`，失去冲突检测）
4. **Patch 链** — 每次保存生成 Patch，形成版本历史

| 图纸类型 | 说明 |
|----------|------|
| `imports` | 已导入的 DWG 图纸，支持增量 patch |
| `designs` | 从零创建的设计图 |
