> **本文回答**：`mapcadLayer.eventBus` 实体事件 —— `entity:added / deleted / modified / click / dblclick / mouseenter / mouseleave`。

## 一、事件列表

```js
const { VjmapextEvents } = vjmapext;
```

| 常量 | 字符串 | payload |
|---|---|---|
| `EntityAdded` | `entity:added` | `{ entity }` |
| `EntityDeleted` | `entity:deleted` | `{ entity }` |
| `EntityModified` | `entity:modified` | `{ entityId, styleOnly? }` ⚠️ **是 `entityId` 不是 `entity`** |
| `EntityClick` | `entity:click` | `{ entityId, entity, cadPoint, originalEvent }` |
| `EntityDblClick` | `entity:dblclick` | `{ entityId, entity, cadPoint, originalEvent }` |
| `EntityMouseEnter` | `entity:mouseenter` | `{ entityId, entity, originalEvent }` |
| `EntityMouseLeave` | `entity:mouseleave` | `{ entityId, entity }` |

## 二、生命周期：增删改

```js
const bus = mapcadLayer.eventBus;

bus.on(VjmapextEvents.EntityAdded, ({ entity }) => {
  console.log('new:', entity.id, entity.type);
});

bus.on(VjmapextEvents.EntityDeleted, ({ entity }) => {
  console.log('del:', entity.id);
});

bus.on(VjmapextEvents.EntityModified, ({ entityId, styleOnly }) => {
  const entity = mapcadLayer.store.get(entityId);
  if (styleOnly) {
    // 只动了颜色 / 线宽 / 可见；不必重算空间索引
  } else {
    // 几何变化；重算你自己的索引
  }
});
```

### styleOnly 标志

`entity.styleChanged()` 触发 `modified` 且 `styleOnly=true`；`entity.changed()` 触发 `modified` 且**不带 `styleOnly` 字段**（即 `undefined`，不是 `false`）。

## 三、实体鼠标事件

```js
bus.on(VjmapextEvents.EntityClick, ({ entity, originalEvent }) => {
  console.log('click:', entity.id, entity.type);
});

bus.on(VjmapextEvents.EntityDblClick, ({ entity }) => {
  if (entity.type === 'TEXT' || entity.type === 'MTEXT') {
    mapcadLayer.executeCommand('TEXTEDIT');
  }
});

bus.on(VjmapextEvents.EntityMouseEnter, ({ entity }) => {
  entity.setCustomProperty('__prevColor', entity.color);
  entity.color = 0xff0000;
  entity.styleChanged();
});

bus.on(VjmapextEvents.EntityMouseLeave, ({ entity }) => {
  entity.color = entity.getCustomProperty('__prevColor')?.value ?? entity.color;  // 返回 {type,value}
  entity.styleChanged();
});
```

## 四、结合修饰键

`entity:click` 在内置选中逻辑**之前**同步发出，handler 返回后内置逻辑会再执行（普通单击 `setSelection([id])` 替换选集、Shift+单击 toggle）——handler 里直接 `setSelection` 会被立刻覆盖。内置的多选键是 **Shift**；非要 Ctrl 多选，得等内置逻辑跑完再改：

```js
bus.on(VjmapextEvents.EntityClick, ({ entity, originalEvent }) => {
  if (!originalEvent.ctrlKey || originalEvent.shiftKey) return;   // 其余交给内置逻辑
  const ids = mapcadLayer.store.getSelectedIds();                  // 内置逻辑执行前的选集（副本）
  const i = ids.indexOf(entity.id);
  if (i >= 0) ids.splice(i, 1);
  else ids.push(entity.id);
  queueMicrotask(() => mapcadLayer.store.setSelection(ids));      // 在内置 setSelection([id]) 之后再覆盖
});
```

## 五、实体批量统计

```js
let count = 0;
bus.on(VjmapextEvents.EntityAdded, () => { count++; refresh(); });
bus.on(VjmapextEvents.EntityDeleted, () => { count--; refresh(); });
```

## 六、性能提示

- 上万 entity 同时 add 会触发上万次 `entity:added` → handler 里不要做昂贵操作，或合并到 `command:ended` 统一处理（`store:changed` 在 1.0.1 从未发射，不能用作聚合出口，见 [bus-render-store](bus-render-store.md)）。
- `entity:mouseenter / leave` 只在**悬停对象切换**时 fire（同一实体上移动不重复发），且只在空闲态（非命令输入/拖拽中）检测；悬停到夹点上也会先发 leave。handler 本身不必节流，但别做重活。

## 七、事件级联防死循环

```js
bus.on(VjmapextEvents.EntityModified, ({ entityId }) => {
  const entity = mapcadLayer.store.get(entityId);
  // 错误示例：这里再次改 entity → 又触发 modified → 无限
  entity.color = 0xff0000;
  entity.styleChanged();
});
```

正确做法：

```js
const processed = new WeakSet();
bus.on(VjmapextEvents.EntityModified, ({ entityId, styleOnly }) => {
  if (styleOnly) return;        // 样式变化不再二次改
  const entity = mapcadLayer.store.get(entityId);
  if (processed.has(entity)) return;
  processed.add(entity);
  entity.color = 0xff0000;
  entity.styleChanged();
});
```

## 八、在命令期间捕获新增

```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, '条');
  }
});
bus.on(VjmapextEvents.EntityAdded, ({ entity }) => {
  if (capturing) captured.push(entity);
});
```

## 九、off / once

```js
const handler = ({ entity }) => { ... };
bus.on(VjmapextEvents.EntityAdded, handler);
bus.off(VjmapextEvents.EntityAdded, handler);

bus.once(VjmapextEvents.EntityAdded, handler);   // 只触发一次
```

## 十、常见坑

1. **mouseenter 每次进离重复绑样式**：用 `_prevColor` 缓存，避免连续 enter 导致绿色反转。
2. **`entity:added` 在 `store.add` 之后**：不要在 handler 里再 `store.add` 同一个实体。
3. **命令中 fire `entity:click`？** 命令执行期间默认 fire 的是 input 事件，实体 click 可能被屏蔽。
4. **selection 和 click 顺序**：实体 click → 自动 selection:changed；监听任一都可以得到选中。
5. **匿名函数无 off** → 清理困难。

## 相关

- 选集事件 → [bus-selection](bus-selection.md)
- 命令事件 → [bus-command-mode](bus-command-mode.md)
- 实体生命周期 → [entity/lifecycle](../entity/lifecycle.md)
