交互与状态
Core 将“数据是否参与布局”和“节点当前 UI 状态”分开管理。展开与收起会触发布局更新,其他状态字段通过增量事件交给渲染层解释。
展开节点
ts
await sdk.expand({ nodeId: 'tech' })- 静态模式从初始
data中找到直接子节点。 - 动态模式调用
api.loadChildren('tech')。 - 加载前推送
loading: true。 - 合并子节点后重新布局并触发
UpdateType.Expand。
Vue 的内置展开按钮已经调用该方法,并在重排后补偿画布位移,使被操作节点尽量保持在原位置。
收起节点
ts
sdk.collapse({ nodeId: 'tech' })收起会递归移除该节点的所有后代,使它们不再参与当前布局,同时将后代的 expanded 状态重置为 false。
切换方向
ts
sdk.setDirection('right')
await sdk.loadData()setDirection() 会更新配置并触发 onDirectionChange,但坐标重算由下一次 loadData()、expand() 或 collapse() 触发。为了立即得到完整的新布局,显式调用 loadData() 最清晰。
节点状态
ts
sdk.setNodeStatus('tech', {
highlight: true,
visible: true,
})setNodeStatus() 对当前状态做浅合并,并立即触发一次状态增量事件。
| 字段 | 由谁维护 | 典型用途 |
|---|---|---|
expanded | SDK 展开/收起流程 | 展开按钮、卡片状态 |
loading | SDK 动态加载流程 | 加载动画、禁用重复操作 |
hasChildren | 静态关系或 isLeaf 推导 | 决定是否显示展开按钮 |
highlight | 业务代码 | 搜索命中、当前选择 |
visible | 业务代码 | 自定义显示策略 |
highlight 和 visible 是状态,不是默认样式
内置 Vue 卡片只自动使用 expanded、loading 和 hasChildren。业务卡片需要读取状态并自行实现高亮或隐藏效果。
读取完整快照:
ts
const statusMap = sdk.getStatusMap()
const techStatus = statusMap.get('tech')返回值是新的 Map,修改它不会直接改写 SDK 内部状态。
订阅布局更新
ts
import type { PositionedNode } from '@org-tree/core'
import { UpdateType } from '@org-tree/core'
const stop = sdk.onUpdate((nodes: PositionedNode[], type) => {
if (type === UpdateType.Init)
console.log('首次布局完成', nodes)
})
// 页面卸载时
stop()如果订阅发生在首次布局之后,onUpdate 会立即回放最近一次布局,避免渲染层错过初始数据。
订阅状态增量
ts
const stop = sdk.onStatusChange((diff) => {
diff.forEach((status, nodeId) => {
console.log(nodeId, status)
})
})回调参数只包含本批变化的节点。新订阅者会立即收到 SDK 已累积的完整最新状态。
订阅方向变化
ts
const stop = sdk.onDirectionChange((direction) => {
console.log('当前方向', direction)
})方向事件不会在订阅时回放。首次读取方向使用 sdk.getDirection()。
管理订阅生命周期
ts
const stops = [
sdk.onUpdate(handleUpdate),
sdk.onStatusChange(handleStatus),
sdk.onDirectionChange(handleDirection),
]
onBeforeUnmount(() => {
stops.forEach(stop => stop())
})OrgTree 组件内部建立的订阅会在组件卸载时自动清理;这里只处理业务方自己建立的订阅。
搜索与定位
Core 负责坐标,不包含业务搜索索引。推荐先在原始数据中检索,再定位已渲染节点:
ts
const result = data.find(node => node.name.includes(keyword))
if (result) {
document
.getElementById(`org-tree-node-${result.id}`)
?.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
})
}动态模式下,如果目标节点的祖先尚未加载,需要先按路径依次展开,再定位最终节点。