# 统一页面控制 (/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/extend-whiteboard/unified-page-control)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
适用于 **Whiteboard Android SDK 2.16.134**。

多窗口模式通过同一套接口控制主白板和课件。先完成[加入白板房间](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/get-started/join-room-wb)，并等待入房成功；业务发起翻页、缩放以及新增或移除主白板页面还需要可写权限。

## 目标与操作 [#目标与操作]

| 目标 | `target` 输入 | 支持操作 |
| --- | --- | --- |
| MainView 主白板 | `mainView` | 前后页、跳页、缩放；[新增、移除页面](#mainview-add-remove)使用独立 API；不支持动画步骤 |
| Slide 动态 PPT | 该窗口的 appId | 前后页、跳页、前后动画步骤、缩放（先启用 Slide 的 enableScale） |
| Presentation 静态 PPT | 该窗口的 appId | 前后页、跳页、缩放；不支持动画步骤 |
| DocsViewer 文档 | 该窗口的 appId | 前后页、跳页；不支持 scalePage |

省略 target 时跟随当前焦点，没有焦点时使用 MainView。不能把 `Slide` 或 `Presentation` 类型名当作 appId。查询结果的 target 则是 `mainView`、`Slide`、`Presentation`、`DocsViewer` 之一，用于标识目标类型。

下表列出 `dispatchDocsEvent` 支持的命令；新增和移除页面使用下文的独立 API。

| 命令 | 用途与参数 |
| --- | --- |
| prevPage / nextPage | 前一页 / 后一页 |
| prevStep / nextStep | 前一动画步骤 / 后一动画步骤 |
| jumpToPage | page 为从 1 开始的整数，不能直接使用零基场景索引 |
| scalePage | scale 为相对适配倍率，1 表示适配大小；可选范围由 pageScaleRange 约束 |

<a id="mainview-add-remove"></a>
## 新增与移除主白板页面 [#新增与移除主白板页面]

以下公开 API 固定操作 MainView 主白板的场景页，不受当前焦点或 `target` 影响，也不用于增加或删除课件源文件中的页面。先完成多窗口入房并取得可写权限。`dispatchDocsEvent` 不接受 `addPage`、`removePage` 命令。

通过已入房的 `Room room` 调用。

| 操作 | `Room` 公开 API | 参数与结果 |
| --- | --- | --- |
| 末尾新增空白页 | `void addPage()` | 追加到主白板场景集末尾 |
| 指定位置新增页面 | `void addPage(Scene scene, boolean after)` | `scene` 可为 `null`，表示空白页；`after = true` 插到主白板当前页之后，`false` 追加到末尾。方法无完成回调 |
| 移除当前页面 | `void removePage(Promise<Boolean> promise)` | 删除主白板当前页，`promise` 可空 |
| 移除指定页面 | `void removePage(int index, Promise<Boolean> promise)` | `index` 为零基索引。回调 `true` 表示删除成功；仅剩一页或索引越界时回调 `false`；异常在 `catchEx` 接收 |

下面代码用于两个独立的按钮处理逻辑：

```java
import android.util.Log;
import com.herewhite.sdk.Room;
import com.herewhite.sdk.domain.Promise;
import com.herewhite.sdk.domain.SDKError;

// room 为已入房的 Room。

// 新增按钮：在主白板当前页之后插入空白页。
room.addPage(null, true);

// 删除按钮：删除主白板第一页；省略索引的重载可删除当前页。
room.removePage(0, new Promise<Boolean>() {
    @Override public void then(Boolean removed) {
        if (!Boolean.TRUE.equals(removed)) {
            Log.w("Whiteboard", "页面未删除，请检查页数与索引");
        }
    }
    @Override public void catchEx(SDKError error) {
        Log.e("Whiteboard", "删除页面失败", error);
    }
});
```

新增页面后不会自动跳到新页，需要时再发送指定 `target: "mainView"` 的跳页命令。主白板至少保留一页；仅剩一页时不能删除。

**索引区别：** 删除 API 的 `index` 从 **0** 开始，`index = 0` 表示第一页；`dispatchDocsEvent("jumpToPage", ...)` 的 `page` 从 **1** 开始。传入待删除页索引前，请确认主白板的实际页数。

## 发送命令与主动查询 [#发送命令与主动查询]

```java
// room 为 joinRoom 成功返回的 Room；appId 是 addApp 返回的窗口 ID。
room.dispatchDocsEvent(WindowDocsEvent.JumpToPage(2).withTarget(appId),
    new Promise<DispatchDocsEventResult>() {
        @Override public void then(DispatchDocsEventResult result) {
            if (!result.isAccepted()) Log.w("Whiteboard", result.getReason());
        }
        @Override public void catchEx(SDKError error) { Log.e("Whiteboard", "命令失败", error); }
    });
room.getPageState(new WindowPageStateOptions().withTarget(appId),
    new Promise<UnifiedPageState>() {
        @Override public void then(UnifiedPageState state) {
            Log.i("Whiteboard", "page=" + state.getPage() + "/" + state.getPageCount()
                + ", scale=" + state.getScale());
        }
        @Override public void catchEx(SDKError error) { Log.e("Whiteboard", "查询失败", error); }
    });
```

`dispatchDocsEvent` 的 accepted 只表示命令被接收；accepted=false 时读取 reason/message。Bridge 或宿主异常走 Promise 拒绝或原生错误回调。原因码包括 invalidEvent、invalidOptions、targetNotFound、targetNotSupported、eventNotSupported、notWritable、stateUnavailable、outOfRange、commandFailed。

`getPageState` 主动查询可观察的状态，不会触发翻页。返回 page/pageCount（从 1 开始）、target、可选 appId 与 scale。尚未完成初始化或状态确认时可能失败；可等待状态事件后再次查询，或设置有限重试次数与截止时间。

## 监听实际页码与缩放 [#监听实际页码与缩放]

```java
whiteSdk.setUnifiedPageStateListener(new UnifiedPageStateListener() {
    @Override public void onUnifiedPageStateChange(UnifiedPageStateChange state) {
        if ("failure".equals(state.getStatus())) {
            Log.w("Whiteboard", state.getReason());
            return;
        }
        Log.i("Whiteboard", "type=" + state.getChangeType() + ", page=" + state.getPage()
            + ", scale=" + state.getScale());
    }
});
// 页面退出时清理监听：
// whiteSdk.setUnifiedPageStateListener(null);
```

结果/参数类型位于 `com.herewhite.sdk.domain`，监听接口位于 `com.herewhite.sdk.window`。`Promise`、`SDKError` 位于 `com.herewhite.sdk.domain`。

观察事件的 status 为 pending/success；`changeType: page` 表示页变化，`changeType: scale` 表示缩放变化；旧兼容事件可能省略 changeType。异步执行失败时 status=failure，并包含 event、reason 和可选 message。不要从失败事件读取有效页码/倍率。

主动读取倍率使用 `getPageState` 返回的 scale；该值可能不存在，且它不是主白板相机的绝对 camera.scale。命令接收、焦点提交和页状态通知都不能代替课件全部资源或首帧完成信号。

## 与 WindowManager 示例的对应关系 [#与-windowmanager-示例的对应关系]

WindowManager example 中 `dispatchUnifiedDocsEvent` 包装正式 `dispatchDocsEvent`，`queryUnifiedPageState` 包装正式 `getPageState`。`scaleChangeEvent` 是示例保存缩放通知的 UI 状态变量，来自 unifiedPageStateChange 中 changeType=scale 的消息。这三个示例名称均不是可从 SDK 导入的独立方法或事件。

需要统一主白板与课件的书写参考尺寸时，继续阅读[统一笔记](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/extend-whiteboard/unified-notes)。
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
适用于 **Whiteboard iOS SDK 2.16.147**。

多窗口模式通过同一套接口控制主白板和课件。先完成[加入白板房间](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/get-started/join-room-wb)，并等待入房成功；业务发起翻页、缩放以及新增或移除主白板页面还需要可写权限。

## 目标与操作 [#目标与操作-1]

| 目标 | `target` 输入 | 支持操作 |
| --- | --- | --- |
| MainView 主白板 | `mainView` | 前后页、跳页、缩放；[新增、移除页面](#mainview-add-remove)使用独立 API；不支持动画步骤 |
| Slide 动态 PPT | 该窗口的 appId | 前后页、跳页、前后动画步骤、缩放（先启用 Slide 的 enableScale） |
| Presentation 静态 PPT | 该窗口的 appId | 前后页、跳页、缩放；不支持动画步骤 |
| DocsViewer 文档 | 该窗口的 appId | 前后页、跳页；不支持 scalePage |

省略 target 时跟随当前焦点，没有焦点时使用 MainView。不能把 `Slide` 或 `Presentation` 类型名当作 appId。查询结果的 target 则是 `mainView`、`Slide`、`Presentation`、`DocsViewer` 之一，用于标识目标类型。

下表列出 `dispatchDocsEvent` 支持的命令；新增和移除页面使用下文的独立 API。

| 命令 | 用途与参数 |
| --- | --- |
| prevPage / nextPage | 前一页 / 后一页 |
| prevStep / nextStep | 前一动画步骤 / 后一动画步骤 |
| jumpToPage | page 为从 1 开始的整数，不能直接使用零基场景索引 |
| scalePage | scale 为相对适配倍率，1 表示适配大小；可选范围由 pageScaleRange 约束 |

<a id="mainview-add-remove"></a>
## 新增与移除主白板页面 [#新增与移除主白板页面-1]

以下公开 API 固定操作 MainView 主白板的场景页，不受当前焦点或 `target` 影响，也不用于增加或删除课件源文件中的页面。先完成多窗口入房并取得可写权限。`dispatchDocsEvent` 不接受 `addPage`、`removePage` 命令。

通过已入房的 `WhiteRoom *room` 调用。

| 操作 | `WhiteRoom` 公开 API（Objective-C selector） | 参数与结果 |
| --- | --- | --- |
| 当前页之后新增空白页 | `addPage` / `addPage:` | 无参数形式或传入完成回调；默认插入到主白板当前页之后 |
| 指定位置新增页面 | `addPageWithScene:afterCurrentScene:` / `addPageWithScene:afterCurrentScene:completionHandler:` | `scene` 可为 `nil`；`afterCurrentScene = YES` 插到当前页之后，`NO` 追加到末尾；带回调形式用 `BOOL success` 返回结果 |
| 移除当前页面 | `removePage:` | 传入完成回调，删除主白板当前页 |
| 移除指定页面 | `removePage:completionHandler:` | 第一个参数为从 0 开始的 `NSUInteger` 索引。`success = YES` 表示成功；仅剩一页、索引越界或调用异常时为 `NO` |

下面分别定义新增和删除按钮的处理逻辑：

```objc
void (^addMainViewPage)(void) = ^{
    [room addPageWithScene:nil afterCurrentScene:YES
         completionHandler:^(BOOL success) {
        if (!success) NSLog(@"新增页面失败");
    }];
};

void (^removeFirstMainViewPage)(void) = ^{
    [room removePage:0 completionHandler:^(BOOL success) {
        if (!success) NSLog(@"页面未删除，请检查页数与索引");
    }];
};
// 在对应按钮处理方法中调用 addMainViewPage() 或 removeFirstMainViewPage()。
```

新增页面后不会自动跳到新页，需要时再发送指定 `target: "mainView"` 的跳页命令。主白板至少保留一页；仅剩一页时不能删除。

**索引区别：** 删除 API 的 `index` 从 **0** 开始，`index = 0` 表示第一页；`dispatchDocsEvent("jumpToPage", ...)` 的 `page` 从 **1** 开始。传入待删除页索引前，请确认主白板的实际页数。

## 发送命令与主动查询 [#发送命令与主动查询-1]

```objc
WhiteWindowDocsEventOptions *options = [WhiteWindowDocsEventOptions new];
options.target = appId;
options.page = @2;
[room dispatchDocsEvent:WhiteWindowDocsEventJumpToPage options:options
     completionHandler:^(WhiteDispatchDocsEventResult *result) {
    if (!result.accepted) NSLog(@"%@ %@", result.reason, result.message);
}];
WhiteWindowPageStateOptions *query = [WhiteWindowPageStateOptions new];
query.target = appId;
[room getPageState:query completionHandler:^(WhiteUnifiedPageState *state, NSError *error) {
    if (error) { NSLog(@"%@", error); return; }
    NSLog(@"page=%ld/%ld scale=%@", (long)state.page, (long)state.pageCount, state.scale);
}];
```

`dispatchDocsEvent` 的 accepted 只表示命令被接收；accepted=false 时读取 reason/message。Bridge 或宿主异常走 Promise 拒绝或原生错误回调。原因码包括 invalidEvent、invalidOptions、targetNotFound、targetNotSupported、eventNotSupported、notWritable、stateUnavailable、outOfRange、commandFailed。

`getPageState` 主动查询可观察的状态，不会触发翻页。返回 page/pageCount（从 1 开始）、target、可选 appId 与 scale。尚未完成初始化或状态确认时可能失败；可等待状态事件后再次查询，或设置有限重试次数与截止时间。

## 监听实际页码与缩放 [#监听实际页码与缩放-1]

```objc
// 在 joinRoom 时传入的 WhiteRoomCallbackDelegate 对象中实现：
- (void)onUnifiedPageStateChange:(WhiteUnifiedPageStateChange *)state {
    if ([state.status isEqualToString:@"failure"]) {
        NSLog(@"%@ %@", state.reason, state.message); return;
    }
    NSLog(@"type=%@ page=%ld scale=%@", state.changeType, (long)state.page, state.scale);
}
```

宿主保持 delegate 存活，离开房间后停止更新已销毁的页面。

观察事件的 status 为 pending/success；`changeType: page` 表示页变化，`changeType: scale` 表示缩放变化；旧兼容事件可能省略 changeType。异步执行失败时 status=failure，并包含 event、reason 和可选 message。不要从失败事件读取有效页码/倍率。

主动读取倍率使用 `getPageState` 返回的 scale；该值可能不存在，且它不是主白板相机的绝对 camera.scale。命令接收、焦点提交和页状态通知都不能代替课件全部资源或首帧完成信号。

## 与 WindowManager 示例的对应关系 [#与-windowmanager-示例的对应关系-1]

WindowManager example 中 `dispatchUnifiedDocsEvent` 包装正式 `dispatchDocsEvent`，`queryUnifiedPageState` 包装正式 `getPageState`。`scaleChangeEvent` 是示例保存缩放通知的 UI 状态变量，来自 unifiedPageStateChange 中 changeType=scale 的消息。这三个示例名称均不是可从 SDK 导入的独立方法或事件。

需要统一主白板与课件的书写参考尺寸时，继续阅读[统一笔记](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/extend-whiteboard/unified-notes)。
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
适用于 **Whiteboard Web SDK 2.16.59**。

多窗口模式通过同一套接口控制主白板和课件。先完成[加入白板房间](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/get-started/join-room-wb)，并等待入房成功；业务发起翻页、缩放以及新增或移除主白板页面还需要可写权限。

## 目标与操作 [#目标与操作-2]

| 目标 | `target` 输入 | 支持操作 |
| --- | --- | --- |
| MainView 主白板 | `mainView` | 前后页、跳页、缩放；[新增、移除页面](#mainview-add-remove)使用独立 API；不支持动画步骤 |
| Slide 动态 PPT | 该窗口的 appId | 前后页、跳页、前后动画步骤、缩放（先启用 Slide 的 enableScale） |
| Presentation 静态 PPT | 该窗口的 appId | 前后页、跳页、缩放；不支持动画步骤 |
| DocsViewer 文档 | 该窗口的 appId | 前后页、跳页；不支持 scalePage |

省略 target 时跟随当前焦点，没有焦点时使用 MainView。不能把 `Slide` 或 `Presentation` 类型名当作 appId。查询结果的 target 则是 `mainView`、`Slide`、`Presentation`、`DocsViewer` 之一，用于标识目标类型。

下表列出 `dispatchDocsEvent` 支持的命令；新增和移除页面使用下文的独立 API。

| 命令 | 用途与参数 |
| --- | --- |
| prevPage / nextPage | 前一页 / 后一页 |
| prevStep / nextStep | 前一动画步骤 / 后一动画步骤 |
| jumpToPage | page 为从 1 开始的整数，不能直接使用零基场景索引 |
| scalePage | scale 为相对适配倍率，1 表示适配大小；可选范围由 pageScaleRange 约束 |

<a id="mainview-add-remove"></a>
## 新增与移除主白板页面 [#新增与移除主白板页面-2]

以下公开 API 固定操作 MainView 主白板的场景页，不受当前焦点或 `target` 影响，也不用于增加或删除课件源文件中的页面。先完成多窗口入房并取得可写权限。`dispatchDocsEvent` 不接受 `addPage`、`removePage` 命令。

`manager` 为 `WindowManager.mount` 成功返回的实例。

| 操作 | 公开 API | 参数与结果 |
| --- | --- | --- |
| 新增页面 | `manager.addPage(params?: AddPageParams): Promise<void>` | `after: true` 插到主白板当前页之后；省略或为 `false` 时追加到末尾。`scene` 可选，省略时新增空白页 |
| 移除页面 | `manager.removePage(index?: number): Promise<boolean>` | 省略 `index` 删除主白板当前页；传入零基索引删除指定页。返回 `true` 表示删除成功；仅剩一页或索引越界时返回 `false`；调用异常需捕获 Promise 拒绝 |

下面分别演示两个业务操作，按需调用：

```typescript
async function addMainViewPage() {
  await manager.addPage({ after: true });
}

async function removeFirstMainViewPage() {
  const removed = await manager.removePage(0);
  if (!removed) console.warn("页面未删除，请检查页数与索引");
}
```

`AddPageParams` 可从 `@netless/window-manager` 包入口导入。

新增页面后不会自动跳到新页，需要时再发送指定 `target: "mainView"` 的跳页命令。主白板至少保留一页；仅剩一页时不能删除。

**索引区别：** 删除 API 的 `index` 从 **0** 开始，`index = 0` 表示第一页；`dispatchDocsEvent("jumpToPage", ...)` 的 `page` 从 **1** 开始。传入待删除页索引前，请确认主白板的实际页数。

## 发送命令与主动查询 [#发送命令与主动查询-2]

```typescript
// manager 为 WindowManager.mount 成功返回的实例。
const target = appId; // 或 "mainView"
const result = await manager.dispatchDocsEvent("jumpToPage", { target, page: 2 });
if (!result.accepted) console.warn(result.reason, result.message);
try {
  const state = await manager.getPageState({ target });
  console.log(state.page, state.pageCount, state.scale);
} catch (error) {
  console.warn("当前状态尚不可用", error);
}
```

`dispatchDocsEvent` 的 accepted 只表示命令被接收；accepted=false 时读取 reason/message。Bridge 或宿主异常走 Promise 拒绝或原生错误回调。原因码包括 invalidEvent、invalidOptions、targetNotFound、targetNotSupported、eventNotSupported、notWritable、stateUnavailable、outOfRange、commandFailed。

`getPageState` 主动查询可观察的状态，不会触发翻页。返回 page/pageCount（从 1 开始）、target、可选 appId 与 scale。尚未完成初始化或状态确认时可能失败；可等待状态事件后再次查询，或设置有限重试次数与截止时间。

## 监听实际页码与缩放 [#监听实际页码与缩放-2]

```typescript
const unsubscribe = manager.emitter.on("unifiedPageStateChange", state => {
  if (state.status === "failure") {
    console.warn(state.event, state.reason, state.message);
    return;
  }
  if (state.changeType === "scale") console.log("缩放", state.scale);
  else console.log("页码", state.page, state.pageCount);
});
// 页面退出或组件卸载时执行：
unsubscribe();
```

观察事件的 status 为 pending/success；`changeType: page` 表示页变化，`changeType: scale` 表示缩放变化；旧兼容事件可能省略 changeType。异步执行失败时 status=failure，并包含 event、reason 和可选 message。不要从失败事件读取有效页码/倍率。

主动读取倍率使用 `getPageState` 返回的 scale；该值可能不存在，且它不是主白板相机的绝对 camera.scale。命令接收、焦点提交和页状态通知都不能代替课件全部资源或首帧完成信号。

## 与 WindowManager 示例的对应关系 [#与-windowmanager-示例的对应关系-2]

WindowManager example 中 `dispatchUnifiedDocsEvent` 包装正式 `dispatchDocsEvent`，`queryUnifiedPageState` 包装正式 `getPageState`。`scaleChangeEvent` 是示例保存缩放通知的 UI 状态变量，来自 unifiedPageStateChange 中 changeType=scale 的消息。这三个示例名称均不是可从 SDK 导入的独立方法或事件。

需要统一主白板与课件的书写参考尺寸时，继续阅读[统一笔记](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/extend-whiteboard/unified-notes)。
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="harmonyos" />
适用于 **Whiteboard HarmonyOS SDK 0.2.11**。

多窗口模式通过同一套接口控制主白板和课件。先完成[加入白板房间](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/get-started/join-room-wb)，并等待入房成功；业务发起翻页、缩放以及新增或移除主白板页面还需要可写权限。

## 目标与操作 [#目标与操作-3]

| 目标 | `target` 输入 | 支持操作 |
| --- | --- | --- |
| MainView 主白板 | `mainView` | 前后页、跳页、缩放；[新增、移除页面](#mainview-add-remove)使用独立 API；不支持动画步骤 |
| Slide 动态 PPT | 该窗口的 appId | 前后页、跳页、前后动画步骤、缩放（先启用 Slide 的 enableScale） |
| Presentation 静态 PPT | 该窗口的 appId | 前后页、跳页、缩放；不支持动画步骤 |
| DocsViewer 文档 | 该窗口的 appId | 前后页、跳页；不支持 scalePage |

省略 target 时跟随当前焦点，没有焦点时使用 MainView。不能把 `Slide` 或 `Presentation` 类型名当作 appId。查询结果的 target 则是 `mainView`、`Slide`、`Presentation`、`DocsViewer` 之一，用于标识目标类型。

下表列出 `dispatchDocsEvent` 支持的命令；新增和移除页面使用下文的独立 API。

| 命令 | 用途与参数 |
| --- | --- |
| prevPage / nextPage | 前一页 / 后一页 |
| prevStep / nextStep | 前一动画步骤 / 后一动画步骤 |
| jumpToPage | page 为从 1 开始的整数，不能直接使用零基场景索引 |
| scalePage | scale 为相对适配倍率，1 表示适配大小；可选范围由 pageScaleRange 约束 |

<a id="mainview-add-remove"></a>
## 新增与移除主白板页面 [#新增与移除主白板页面-3]

以下公开 API 固定操作 MainView 主白板的场景页，不受当前焦点或 `target` 影响，也不用于增加或删除课件源文件中的页面。先完成多窗口入房并取得可写权限。`dispatchDocsEvent` 不接受 `addPage`、`removePage` 命令。

通过入房成功后的 `WhiteboardController controller` 调用。参数类型均从 `@shengwang/whiteboard` 包入口导出。

| 操作 | `WhiteboardController` 公开 API | 参数与结果 |
| --- | --- | --- |
| 新增页面 | `addPage(param: AddPageParam = {}): void` | `after: true` 插到主白板当前页之后；省略或为 `false` 时追加到末尾。`scene` 可选，省略时新增空白页 |
| 移除页面 | `removePage(param: RemovePageParam = {}): void` | 省略 `index` 删除主白板当前页；`index` 从 0 开始，指定待删除页 |

```typescript
import { AddPageParam, RemovePageParam } from '@shengwang/whiteboard';

// 新增按钮：在主白板当前页之后插入空白页。
const addParam: AddPageParam = { after: true };
controller.addPage(addParam);

// 删除按钮：删除主白板第一页；controller.removePage() 删除当前页。
const removeParam: RemovePageParam = { index: 0 };
controller.removePage(removeParam);
```

0.2.11 的这两个方法均无完成回调，也不返回 Promise 或布尔结果。调用返回不表示新增或删除成功，请结合本页的主白板状态查询与 `unifiedPageStateChange` 观察实际页数变化。

新增页面后不会自动跳到新页，需要时再发送指定 `target: "mainView"` 的跳页命令。主白板至少保留一页；仅剩一页时不能删除。

**索引区别：** 删除 API 的 `index` 从 **0** 开始，`index = 0` 表示第一页；`dispatchDocsEvent("jumpToPage", ...)` 的 `page` 从 **1** 开始。传入待删除页索引前，请确认主白板的实际页数。

## 发送命令与主动查询 [#发送命令与主动查询-3]

```typescript
import { DispatchDocsEventRejectedResult } from '@shengwang/whiteboard';
// controller 已在 onRoomReady 后可用。
const result = await controller.dispatchDocsEvent({
  event: 'jumpToPage', options: { target: appId, page: 2 }
});
if (!result.accepted) {
  const rejected = result as DispatchDocsEventRejectedResult;
  console.warn(rejected.reason, rejected.message);
}
try {
  const state = await controller.getPageState({ target: appId });
  console.info(state.page, state.pageCount, state.scale);
} catch (error) { console.warn('当前状态尚不可用'); }
```

`dispatchDocsEvent` 的 accepted 只表示命令被接收；accepted=false 时读取 reason/message。Bridge 或宿主异常走 Promise 拒绝或原生错误回调。原因码包括 invalidEvent、invalidOptions、targetNotFound、targetNotSupported、eventNotSupported、notWritable、stateUnavailable、outOfRange、commandFailed。

`getPageState` 主动查询可观察的状态，不会触发翻页。返回 page/pageCount（从 1 开始）、target、可选 appId 与 scale。尚未完成初始化或状态确认时可能失败；可等待状态事件后再次查询，或设置有限重试次数与截止时间。

## 监听实际页码与缩放 [#监听实际页码与缩放-3]

```typescript
const callbacks = new WhiteboardCallbacks()
  .setOnUnifiedPageStateChange(state => {
    if (state.status === 'failure') {
      console.warn(state.reason, state.message); return;
    }
    console.info(state.changeType, state.page, state.scale);
  });
// 将 callbacks 传给本次 WhiteboardOptions；在页面退出后停止向页面提交状态。
```

观察事件的 status 为 pending/success；`changeType: page` 表示页变化，`changeType: scale` 表示缩放变化；旧兼容事件可能省略 changeType。异步执行失败时 status=failure，并包含 event、reason 和可选 message。不要从失败事件读取有效页码/倍率。

主动读取倍率使用 `getPageState` 返回的 scale；该值可能不存在，且它不是主白板相机的绝对 camera.scale。命令接收、焦点提交和页状态通知都不能代替课件全部资源或首帧完成信号。

## 与 WindowManager 示例的对应关系 [#与-windowmanager-示例的对应关系-3]

WindowManager example 中 `dispatchUnifiedDocsEvent` 包装正式 `dispatchDocsEvent`，`queryUnifiedPageState` 包装正式 `getPageState`。`scaleChangeEvent` 是示例保存缩放通知的 UI 状态变量，来自 unifiedPageStateChange 中 changeType=scale 的消息。这三个示例名称均不是可从 SDK 导入的独立方法或事件。

需要统一主白板与课件的书写参考尺寸时，继续阅读[统一笔记](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/extend-whiteboard/unified-notes)。
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
