> **本文回答**：命令 / 模式 / 输入事件 —— `command:started / ended` / `mode:changed` / `input:started / ended`。

## 一、事件列表

| 常量 | 字符串 | payload |
|---|---|---|
| `CommandStarted` | `command:started` | `{ name }` |
| `CommandEnded` | `command:ended` | `{ name }` |
| `ModeChanged` | `mode:changed` | `{ mode }`（`mode` 是新值，无 `from`/`to`） |
| `InputStarted` | `input:started` | `{ type, message?, keywords?, keywordTips?, defaultValue?, multiple? }`（`type` ∈ `point/entity/selection/string/real/integer/keyword`；字段面按 type 裁剪——point 只有 `type/message/keywords/keywordTips`，entity 多 `multiple`，selection 只有 `type/message`；`defaultValue` 在 string/real/integer/**keyword** 型发射——注意 keyword 型读的是 **options 的 `defaultKeyword`**，但 payload 字段名仍是 `defaultValue`，`getKeyword({defaultKeyword:'Y'})` → `{defaultValue:'Y'}`） |
| `InputEnded` | `input:ended` | `{ type }` |

## 二、命令生命周期

```js
const { VjmapextEvents } = vjmapext;
const bus = mapcadLayer.eventBus;

bus.on(VjmapextEvents.CommandStarted, ({ name }) => {
  statusBar.textContent = `执行中：${name}`;
});

bus.on(VjmapextEvents.CommandEnded, ({ name }) => {
  statusBar.textContent = '就绪';
});
```

**command:ended 不区分成功 / 取消**：用户 Esc 取消也会 fire。

## 三、命令统计 / 日志

```js
const stats = {};
bus.on(VjmapextEvents.CommandEnded, ({ name }) => {
  stats[name] = (stats[name] || 0) + 1;
});
```

## 四、input:started / input:ended

每次 `inputManager.getPoint / getSelections / ...` 开始 / 结束都 fire。用途：

- UI 里显示 / 隐藏命令行输入框
- 暂停地图的其他 hover 逻辑

```js
bus.on(VjmapextEvents.InputStarted, () => cmdLine.style.display = 'block');
bus.on(VjmapextEvents.InputEnded,   () => cmdLine.style.display = 'none');
```

## 五、mode:changed

```js
bus.on(VjmapextEvents.ModeChanged, ({ mode }) => {
  myToolbar.applyMode(mode);   // 你自己的工具栏 UI
});
```

详见 [drawing/mode](../drawing/mode.md)。

## 六、命令 + 实体：捕获命令生成的实体

```js
let capturing = false;
const captured = [];

bus.on(VjmapextEvents.CommandStarted, ({ name }) => {
  if (name === 'LINE') { capturing = true; captured.length = 0; }
});
bus.on(VjmapextEvents.CommandEnded, ({ name }) => {
  if (name === 'LINE') {
    capturing = false;
    console.log('LINE 生成了', captured.length, '个实体：', captured.map(e => e.id));
  }
});
bus.on(VjmapextEvents.EntityAdded, ({ entity }) => {
  if (capturing) captured.push(entity);
});
```

## 七、拦截 / 取消命令

`eventBus` 是单向广播，没法"拒绝"命令启动。要拦截命令：

```js
// 包装 executeCommand
const orig = mapcadLayer.executeCommand.bind(mapcadLayer);
mapcadLayer.executeCommand = (name, opts) => {
  if (!canExecute(name)) {
    alert('无权限：' + name);
    return Promise.resolve();
  }
  return orig(name, opts);
};
```

或通过 mode 限制（见 [drawing/mode](../drawing/mode.md)）。

## 八、命令嵌套

vjmapext 不支持嵌套命令：新命令启动会先取消旧命令。

```js
bus.on(VjmapextEvents.CommandStarted, ({ name }) => {
  if (name !== prevCmd) {
    console.log('自动取消了', prevCmd);
  }
  prevCmd = name;
});
```

## 九、InputStarted/Ended 的粒度

一条 LINE 命令内部可能多次调用 `getPoint` → 每次都 fire 一对 input:started / input:ended。用于：

- 显示 / 隐藏"请指定点"的提示条
- 命令行输入框的 focus / blur

## 十、枚举外附加事件（runtime 实发，不在 `VjmapextEvents`）

UMD 里还有两个字符串事件，订阅时用字面量：

| 事件 | 载荷 | 用途 |
|---|---|---|
| `command:prompt` | `{ message }` | 命令阶段提示文本（已 i18n 处理）。⚠️ 不是通用"输入提示"事件——只有命令体里显式 `eventBus.emit('command:prompt',…)` 的少数命令才发（如 STRETCH 起手发 `cmd.stretch.promptCrossing`）；LINE 等常规命令的"指定第一点"走 `input:started` 的 `message` 字段，**不发** `command:prompt`。自建命令行 UI 应以 `input:started` 为准 |
| `command:message` | `{ text }` | 命令产生的普通消息（如"已生成 N 个填充"） |

> `compact`（`{ preset, result }`，FX 闲置槽位整理）**不在 eventBus 上**——它由 FX 层自己的 emitter 发出，订阅要 `mapcadLayer.fx.on('compact', ({ preset, result }) => …)`（见 [perf/fx-quality](../perf/fx-quality.md)）；`eventBus.on('compact', …)` 永远收不到。

```js
bus.on('command:prompt', ({ message }) => cmdLine.textContent = message);
bus.on('command:message', ({ text }) => log(text));
```

## 十一、常见坑

1. **`command:ended` 里立即执行新命令** → 可能和上条命令清理冲突；加 `requestAnimationFrame`。
2. **没 off**：组件卸载时保存 handler 引用逐个 `eventBus.off(event, handler)`（`on()` 不返回退订函数）。**不要用 `eventBus.clear()`**——它清掉全部监听，连内部的也一起删（显示级别过滤、DimReactor 标注联动、FX 实体绑定、工具栏/命令行 UI），之后这些功能静默失效；整层卸载用 `map.removeControl(cad)`（`onRemove` 内部已 clear）。
3. **统计代码命中 undo/redo**：undo / redo 不走 `CommandStarted`（它们是内置 action 而非命令）；但**有**独立的非枚举事件 `undo:performed` / `redo:performed`（`UndoManager` 直发，用字面量订阅）。不要挂 `StoreChanged`——1.0.1 从未发射。
4. **等 `command:ended` 拿返回值**：命令没返回值；通过 `entity:added` 或业务自定义事件拿到"生成了什么"。
5. **input:started 对应非命令输入**：自定义命令内部的 `inputManager` 也算；不是只命令才 fire。

## 相关

- 命令执行 → [drawing/execute-command](../drawing/execute-command.md)
- 自定义命令 → [drawing/custom-command](../drawing/custom-command.md)
- 实体事件 → [bus-entity](bus-entity.md)
