# 文件操作

打开、保存、导入导出图纸的完整 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 压缩） |

---

## 命令方式（简单场景）

```javascript
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

```javascript
const { DrawingManagerService, Engine } = vjcad;

const drawingManager = new DrawingManagerService();
```

### 从服务端打开图纸

```javascript
// 打开指定图纸
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`。

### 保存到服务端

```javascript
// 获取当前文档数据
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 存储，支持离线编辑。

### 获取本地存储服务

```javascript
const { getLocalStorageService, Engine } = vjcad;

const localService = getLocalStorageService();
```

### 保存到本地缓存

```javascript
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}`);
}
```

### 获取本地图纸列表

```javascript
const drawings = await localService.listDrawings();

drawings.forEach(drawing => {
    console.log(`${drawing.id}: ${drawing.drawingName}`);
});
```

### 从本地缓存加载

```javascript
const loadResult = await localService.loadDrawingById(drawingId);

if (loadResult.success) {
    // 返回 { success, record, webcadJson }——没有 serverSource 字段，
    // 来源信息在 record 上（type/mapid/version/designPath/branchName/lastPatchId…）
    const { record, webcadJson } = loadResult;
}
```

### 获取缓存统计

```javascript
const stats = await localService.getStats();
console.log(`共 ${stats.totalCount} 个图纸, ${stats.totalSize} 字节`);
```

### 删除本地图纸

```javascript
await localService.deleteDrawingById(drawingId);   // 按数字 id（listDrawings 返回的 id）删除
await localService.deleteDrawing(drawingKey);      // 按字符串 key（listDrawings 返回的 key）删除
// 注意：deleteDrawing 传数字 id 什么也不会删，却照样返回 success: true
```

---

## 导入 DWG/DXF

### 完整流程

```javascript
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

### 命令方式

```javascript
await Engine.editor.executerWithOp('EXPORTDWG');
```

### API 方式

```javascript
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);
}
```

---

## 新建文档

```javascript
// 新建空白文档
Engine.view.newDocument();

// 设置文档名称
Engine.currentDoc.name = '新图纸';
```

---

## 关键属性

### Engine.currentDoc.serverSource

保存图纸的服务器来源信息，用于后续保存和同步。

```javascript
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）。

```javascript
Engine.currentDoc.isReadOnly = true;   // 仅标记，实体仍可选可改
```

---

## 增量保存说明

WebCAD 使用增量保存机制：

1. **原始数据**：通过 `setOriginalJson()` 保存的基准数据
2. **当前数据**：通过 `toDb()` 获取的当前文档数据
3. **Diff 计算**：服务端比较两者差异，只保存变更部分
4. **Patch 链**：每次保存生成一个 Patch，形成版本历史

好处：
- 节省存储空间
- 支持版本回溯
- 支持多人协作冲突检测

---
