当前位置: 首页 > news >正文

logicFlow________文档2

# LogicFlow 官方指南 - 自定义连线与事件处理

## 📖 概述

LogicFlow 是滴滴开源的一款流程图编辑框架,具有强大的扩展性和自定义能力。本文档基于LogicFlow官网最新版本,详细介绍如何实现自定义连线、自定义事件处理等核心功能。

## 🚀 快速开始

### 安装依赖

```bash
npm install @logicflow/core @logicflow/extension
```

### 基础引入

```javascript
import LogicFlow from '@logicflow/core'
import '@logicflow/core/dist/index.css'

// 扩展插件
import { Menu, DndPanel, SelectionSelect, Control, MiniMap } from '@logicflow/extension'
import '@logicflow/extension/dist/index.css'
```

### 初始化LogicFlow

```javascript
const lf = new LogicFlow({
container: document.getElementById('container'), // 容器元素
width: 1000, // 画布宽度
height: 800, // 画布高度
grid: { // 网格配置
size: 10, // 网格大小
visible: true, // 是否显示网格
},
keyboard: { // 键盘快捷键
enabled: true,
},
plugins: [Menu, DndPanel, SelectionSelect, Control, MiniMap], // 插件配置
})
```

## 🔗 自定义连线详解

### 1. 连线的基本结构

LogicFlow 中的连线由两部分组成:
- **Model(模型)**: 负责数据管理和业务逻辑
- **View(视图)**: 负责图形渲染和样式

### 2. 内置连线类型

LogicFlow 提供了多种内置连线类型:

```javascript
// 直线连线
import { LineEdge, LineEdgeModel } from '@logicflow/core'

// 折线连线
import { PolylineEdge, PolylineEdgeModel } from '@logicflow/core'

// 贝塞尔曲线连线
import { BezierEdge, BezierEdgeModel } from '@logicflow/core'
```

### 3. 自定义直线连线

#### 3.1 创建自定义直线连线模型

```javascript
import { LineEdgeModel } from '@logicflow/core'

class CustomLineModel extends LineEdgeModel {
/**
* 设置模型属性
*/
setAttributes() {
// 基础样式配置
this.stroke = '#1890ff' // 连线颜色
this.strokeWidth = 2 // 连线宽度
this.strokeDasharray = '5,5' // 虚线样式
this.hoverStroke = '#f00' // 悬停时颜色
this.selectedStroke = '#00ff00' // 选中时颜色
 
// 箭头配置
this.endArrow = true // 显示终点箭头
this.startArrow = false // 显示起点箭头
this.arrowConfig = {
markerWidth: 12,
markerHeight: 12,
refX: 10,
}
}

/**
* 获取连线样式
*/
getEdgeStyle() {
const style = super.getEdgeStyle()
const { properties } = this
 
// 根据业务属性动态设置样式
if (properties.status === 'success') {
style.stroke = '#52c41a'
} else if (properties.status === 'error') {
style.stroke = '#ff4d4f'
} else if (properties.status === 'warning') {
style.stroke = '#faad14'
}
 
return style
}

/**
* 获取箭头样式
*/
getArrowStyle() {
const style = super.getArrowStyle()
const { properties } = this
 
// 根据连线类型设置不同箭头样式
if (properties.type === 'approval') {
style.fill = '#52c41a'
style.stroke = '#52c41a'
} else if (properties.type === 'reject') {
style.fill = '#ff4d4f'
style.stroke = '#ff4d4f'
}
 
return style
}

/**
* 获取文本样式
*/
getTextStyle() {
const style = super.getTextStyle()
return {
...style,
fontSize: 12,
color: '#666',
background: {
fill: '#ffffff',
stroke: '#1890ff',
radius: 3,
padding: [4, 6],
}
}
}

/**
* 自定义连线规则
*/
getConnectedSourceRules() {
const rules = super.getConnectedSourceRules()
return [
...rules,
{
message: '开始节点只能连出一条线',
validate: (source, target, sourceAnchor, targetAnchor) => {
// 检查源节点类型
if (source.type === 'start') {
const edges = this.graphModel.edges
const sourceEdges = edges.filter(edge => edge.sourceNodeId === source.id)
return sourceEdges.length === 0
}
return true
}
}
]
}
}
```

#### 3.2 创建自定义直线连线视图

```javascript
import { LineEdge, h } from '@logicflow/core'

class CustomLineView extends LineEdge {
/**
* 获取连线形状
*/
getShape() {
const { model } = this.props
const { x1, y1, x2, y2, properties } = model
const style = model.getEdgeStyle()

// 基础路径
const path = `M ${x1}${y1} L ${x2}${y2}`
 
return h('g', {}, [
// 主连线
h('path', {
d: path,
...style,
className: 'lf-edge-path'
}),
// 动画效果(可选)
properties.animated && h('path', {
d: path,
stroke: style.stroke,
strokeWidth: style.strokeWidth,
fill: 'none',
strokeDasharray: '5,5',
className: 'lf-edge-animation'
}),
// 自定义标记
this.getCustomMarker()
])
}

/**
* 获取连线文本
*/
getText() {
const { model } = this.props
const { x1, y1, x2, y2, text } = model
 
if (!text || !text.value) return null

// 计算文本位置(连线中点)
const centerX = (x1 + x2) / 2
const centerY = (y1 + y2) / 2
 
const textStyle = model.getTextStyle()

return h('g', {}, [
// 文本背景
h('rect', {
x: centerX - 20,
y: centerY - 8,
width: 40,
height: 16,
fill: textStyle.background.fill,
stroke: textStyle.background.stroke,
rx: textStyle.background.radius,
}),
// 文本内容
h('text', {
x: centerX,
y: centerY,
fontSize: textStyle.fontSize,
fill: textStyle.color,
textAnchor: 'middle',
dominantBaseline: 'central',
}, text.value)
])
}

/**
* 获取自定义标记
*/
getCustomMarker() {
const { model } = this.props
const { properties, x1, y1, x2, y2 } = model
 
// 在连线上添加自定义图标
if (properties.showIcon) {
const centerX = (x1 + x2) / 2
const centerY = (y1 + y2) / 2
 
return h('circle', {
cx: centerX,
cy: centerY - 15,
r: 6,
fill: '#1890ff',
stroke: '#fff',
strokeWidth: 2,
})
}
 
return null
}

/**
* 获取调整点
*/
getAdjustPointShape() {
// 隐藏调整点或自定义调整点样式
return null
}
}
```

### 4. 自定义折线连线

#### 4.1 创建折线连线模型

```javascript
import { PolylineEdgeModel } from '@logicflow/core'

class CustomPolylineModel extends PolylineEdgeModel {
setAttributes() {
this.stroke = '#1890ff'
this.strokeWidth = 2
this.hoverStroke = '#40a9ff'
this.selectedStroke = '#096dd9'
 
// 折线特有配置
this.radius = 5 // 转折点圆角半径
this.offset = 30 // 起始/结束点偏移距离
}

/**
* 自定义折线路径算法
*/
getEdgeStyle() {
const style = super.getEdgeStyle()
const { properties } = this
 
// 根据优先级设置样式
if (properties.priority === 'high') {
style.stroke = '#ff4d4f'
style.strokeWidth = 3
} else if (properties.priority === 'low') {
style.strokeDasharray = '3,3'
}
 
return style
}

/**
* 更新折线路径
*/
updatePath() {
// 可以自定义折线的路径计算逻辑
const { startPoint, endPoint } = this
const { x: x1, y: y1 } = startPoint
const { x: x2, y: y2 } = endPoint
 
// 简单的折线路径:水平-垂直-水平
const midX = (x1 + x2) / 2
const points = [
{ x: x1, y: y1 },
{ x: midX, y: y1 },
{ x: midX, y: y2 },
{ x: x2, y: y2 },
]
 
this.pointsList = points
this.path = this.getPath(points)
}
}
```

#### 4.2 创建折线连线视图

```javascript
import { PolylineEdge, h } from '@logicflow/core'

class CustomPolylineView extends PolylineEdge {
getShape() {
const { model } = this.props
const { pointsList, properties } = model
const style = model.getEdgeStyle()

// 生成路径
const points = pointsList.map(point => `${point.x},${point.y}`).join(' ')
 
return h('g', {}, [
// 主路径
h('polyline', {
points,
...style,
fill: 'none',
className: 'lf-edge-path'
}),
// 流动动画(可选)
properties.flow && this.getFlowAnimation(pointsList),
// 转折点标记
...this.getCornerMarkers(pointsList)
])
}

/**
* 获取流动动画
*/
getFlowAnimation(pointsList) {
const points = pointsList.map(point => `${point.x},${point.y}`).join(' ')
 
return h('polyline', {
points,
stroke: '#1890ff',
strokeWidth: 2,
fill: 'none',
strokeDasharray: '5,5',
className: 'flow-animation',
style: {
animation: 'flow 2s linear infinite'
}
})
}

/**
* 获取转折点标记
*/
getCornerMarkers(pointsList) {
const markers = []
 
// 跳过起点和终点
for (let i = 1; i < pointsList.length - 1; i++) {
const point = pointsList[i]
markers.push(
h('circle', {
cx: point.x,
cy: point.y,
r: 3,
fill: '#1890ff',
stroke: '#fff',
strokeWidth: 1,
className: 'corner-marker'
})
)
}
 
return markers
}
}
```

### 5. 自定义贝塞尔曲线连线

#### 5.1 创建贝塞尔曲线模型

```javascript
import { BezierEdgeModel } from '@logicflow/core'

class CustomBezierModel extends BezierEdgeModel {
setAttributes() {
this.stroke = '#722ed1'
this.strokeWidth = 2
this.fill = 'none'
 
// 贝塞尔曲线控制点配置
this.adjustLine = true // 显示调整线
this.adjustAnchor = true // 显示调整点
}

/**
* 自定义控制点计算
*/
getControlPoints() {
const { startPoint, endPoint } = this
const { x: x1, y: y1 } = startPoint
const { x: x2, y: y2 } = endPoint
 
// 计算控制点位置
const distance = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
const offset = Math.min(distance / 3, 100)
 
const cp1 = {
x: x1 + offset,
y: y1
}
 
const cp2 = {
x: x2 - offset,
y: y2
}
 
return { cp1, cp2 }
}

/**
* 获取曲线路径
*/
getPath() {
const { startPoint, endPoint } = this
const { cp1, cp2 } = this.getControlPoints()
 
return `M ${startPoint.x}${startPoint.y} C ${cp1.x}${cp1.y}, ${cp2.x}${cp2.y}, ${endPoint.x}${endPoint.y}`
}
}
```

#### 5.2 创建贝塞尔曲线视图

```javascript
import { BezierEdge, h } from '@logicflow/core'

class CustomBezierView extends BezierEdge {
getShape() {
const { model } = this.props
const path = model.getPath()
const style = model.getEdgeStyle()

return h('g', {}, [
// 主曲线
h('path', {
d: path,
...style,
className: 'lf-edge-path'
}),
// 控制点和控制线(开发模式)
this.getControlPointsShape()
])
}

/**
* 获取控制点形状
*/
getControlPointsShape() {
const { model } = this.props
const { adjustLine, adjustAnchor } = model
 
if (!adjustLine || !adjustAnchor) return null
 
const { startPoint, endPoint } = model
const { cp1, cp2 } = model.getControlPoints()
 
return h('g', { className: 'control-points' }, [
// 控制线
h('line', {
x1: startPoint.x,
y1: startPoint.y,
x2: cp1.x,
y2: cp1.y,
stroke: '#ddd',
strokeWidth: 1,
strokeDasharray: '3,3'
}),
h('line', {
x1: endPoint.x,
y1: endPoint.y,
x2: cp2.x,
y2: cp2.y,
stroke: '#ddd',
strokeWidth: 1,
strokeDasharray: '3,3'
}),
// 控制点
h('circle', {
cx: cp1.x,
cy: cp1.y,
r: 4,
fill: '#1890ff',
stroke: '#fff',
strokeWidth: 2,
className: 'control-point'
}),
h('circle', {
cx: cp2.x,
cy: cp2.y,
r: 4,
fill: '#1890ff',
stroke: '#fff',
strokeWidth: 2,
className: 'control-point'
})
])
}
}
```

### 6. 注册和使用自定义连线

```javascript
// 注册自定义连线
lf.register({
type: 'custom-line',
view: CustomLineView,
model: CustomLineModel,
})

lf.register({
type: 'custom-polyline',
view: CustomPolylineView,
model: CustomPolylineModel,
})

lf.register({
type: 'custom-bezier',
view: CustomBezierView,
model: CustomBezierModel,
})

// 设置默认连线类型
lf.setDefaultEdgeType('custom-line')

// 渲染数据
lf.render({
nodes: [
{ id: 'node1', type: 'rect', x: 100, y: 100, text: '开始' },
{ id: 'node2', type: 'rect', x: 300, y: 100, text: '处理' },
{ id: 'node3', type: 'rect', x: 500, y: 100, text: '结束' },
],
edges: [
{
id: 'edge1',
type: 'custom-line',
sourceNodeId: 'node1',
targetNodeId: 'node2',
text: '同意',
properties: {
status: 'success',
priority: 'high',
animated: true
}
},
{
id: 'edge2',
type: 'custom-polyline',
sourceNodeId: 'node2',
targetNodeId: 'node3',
text: '完成',
properties: {
flow: true,
priority: 'normal'
}
}
]
})
```

## 🎯 自定义事件处理详解

### 1. 基础事件监听

LogicFlow 提供了丰富的事件系统,支持监听各种用户交互:

```javascript
// 节点事件
lf.on('node:click', ({ data, e }) => {
console.log('节点点击', data, e)
})

lf.on('node:dblclick', ({ data, e }) => {
console.log('节点双击', data, e)
})

lf.on('node:mousedown', ({ data, e }) => {
console.log('节点鼠标按下', data, e)
})

lf.on('node:mouseup', ({ data, e }) => {
console.log('节点鼠标抬起', data, e)
})

lf.on('node:mousemove', ({ data, e }) => {
console.log('节点鼠标移动', data, e)
})

lf.on('node:mouseenter', ({ data, e }) => {
console.log('鼠标进入节点', data, e)
})

lf.on('node:mouseleave', ({ data, e }) => {
console.log('鼠标离开节点', data, e)
})

lf.on('node:contextmenu', ({ data, e }) => {
console.log('节点右键菜单', data, e)
e.preventDefault() // 阻止默认右键菜单
})

// 连线事件
lf.on('edge:click', ({ data, e }) => {
console.log('连线点击', data, e)
})

lf.on('edge:dblclick', ({ data, e }) => {
console.log('连线双击', data, e)
})

lf.on('edge:contextmenu', ({ data, e }) => {
console.log('连线右键菜单', data, e)
})

// 画布事件
lf.on('blank:click', ({ e }) => {
console.log('画布点击', e)
})

lf.on('blank:dblclick', ({ e }) => {
console.log('画布双击', e)
})

lf.on('blank:contextmenu', ({ e }) => {
console.log('画布右键菜单', e)
})

// 选择事件
lf.on('selection:selected', ({ data }) => {
console.log('元素被选中', data)
})

lf.on('selection:cleared', () => {
console.log('选择被清空')
})
```

### 2. 数据变化事件

```javascript
// 节点数据变化
lf.on('node:add', ({ data }) => {
console.log('节点添加', data)
})

lf.on('node:delete', ({ data }) => {
console.log('节点删除', data)
})

lf.on('node:drag', ({ data }) => {
console.log('节点拖拽', data)
})

lf.on('node:drop', ({ data }) => {
console.log('节点拖拽结束', data)
})

// 连线数据变化
lf.on('edge:add', ({ data }) => {
console.log('连线添加', data)
})

lf.on('edge:delete', ({ data }) => {
console.log('连线删除', data)
})

// 文本变化
lf.on('text:update', ({ data }) => {
console.log('文本更新', data)
})

// 属性变化
lf.on('properties:change', ({ data }) => {
console.log('属性变化', data)
})
```

### 3. 高级事件处理

#### 3.1 自定义事件处理器

```javascript
class CustomEventHandler {
constructor(lf) {
this.lf = lf
this.init()
}

init() {
// 节点点击处理
this.lf.on('node:click', this.handleNodeClick.bind(this))
 
// 连线点击处理
this.lf.on('edge:click', this.handleEdgeClick.bind(this))
 
// 键盘事件处理
this.bindKeyboardEvents()
}

/**
* 处理节点点击
*/
handleNodeClick({ data, e }) {
const { type, properties } = data
 
// 根据节点类型执行不同操作
switch (type) {
case 'start':
this.handleStartNodeClick(data)
break
case 'task':
this.handleTaskNodeClick(data)
break
case 'gateway':
this.handleGatewayNodeClick(data)
break
case 'end':
this.handleEndNodeClick(data)
break
default:
this.handleDefaultNodeClick(data)
}
}

/**
* 处理连线点击
*/
handleEdgeClick({ data, e }) {
const { properties } = data
 
// 显示连线属性面板
this.showEdgePanel(data)
 
// 高亮相关节点
this.highlightRelatedNodes(data)
}

/**
* 处理开始节点点击
*/
handleStartNodeClick(data) {
console.log('开始节点被点击', data)
 
// 检查是否已有后续节点
const outgoingEdges = this.lf.getNodeOutgoingEdge(data.id)
if (outgoingEdges.length === 0) {
// 提示用户添加后续节点
this.showTip('请添加后续处理节点')
}
}

/**
* 处理任务节点点击
*/
handleTaskNodeClick(data) {
console.log('任务节点被点击', data)
 
// 显示任务配置面板
this.showTaskPanel(data)
}

/**
* 处理网关节点点击
*/
handleGatewayNodeClick(data) {
console.log('网关节点被点击', data)
 
// 检查分支条件
this.checkGatewayConditions(data)
}

/**
* 处理结束节点点击
*/
handleEndNodeClick(data) {
console.log('结束节点被点击', data)
 
// 检查流程完整性
this.checkProcessIntegrity(data)
}

/**
* 绑定键盘事件
*/
bindKeyboardEvents() {
document.addEventListener('keydown', (e) => {
// Ctrl+S 保存
if (e.ctrlKey && e.key === 's') {
e.preventDefault()
this.saveProcess()
}
 
// Delete 删除选中元素
if (e.key === 'Delete') {
this.deleteSelected()
}
 
// Ctrl+Z 撤销
if (e.ctrlKey && e.key === 'z') {
e.preventDefault()
this.undo()
}
 
// Ctrl+Y 重做
if (e.ctrlKey && e.key === 'y') {
e.preventDefault()
this.redo()
}
})
}

/**
* 显示连线属性面板
*/
showEdgePanel(data) {
// 创建属性面板
const panel = document.createElement('div')
panel.className = 'edge-panel'
panel.innerHTML = `
<h3>连线属性</h3>
<div>
<label>连线类型:</label>
<select id="edgeType">
<option value="sequence">顺序流</option>
<option value="conditional">条件流</option>
<option value="default">默认流</option>
</select>
</div>
<div>
<label>条件表达式:</label>
<input type="text" id="condition" placeholder="例如: ${amount > 1000}">
</div>
<div>
<button onclick="this.saveEdgeProperties()">保存</button>
<button onclick="this.closePanel()">取消</button>
</div>
`
 
document.body.appendChild(panel)
}

/**
* 高亮相关节点
*/
highlightRelatedNodes(edgeData) {
const { sourceNodeId, targetNodeId } = edgeData
 
// 高亮源节点和目标节点
this.lf.selectElementById(sourceNodeId, true)
this.lf.selectElementById(targetNodeId, true)
 
// 3秒后取消高亮
setTimeout(() => {
this.lf.clearSelectElements()
}, 3000)
}

/**
* 保存流程
*/
saveProcess() {
const data = this.lf.getGraphData()
 
// 验证流程完整性
if (this.validateProcess(data)) {
// 保存到服务器
this.saveToServer(data)
}
}

/**
* 验证流程完整性
*/
validateProcess(data) {
const { nodes, edges } = data
 
// 检查是否有开始节点
const startNodes = nodes.filter(node => node.type === 'start')
if (startNodes.length === 0) {
this.showError('流程必须有一个开始节点')
return false
}
 
// 检查是否有结束节点
const endNodes = nodes.filter(node => node.type === 'end')
if (endNodes.length === 0) {
this.showError('流程必须有一个结束节点')
return false
}
 
// 检查节点连接
for (const node of nodes) {
if (node.type === 'start') {
const outgoing = edges.filter(edge => edge.sourceNodeId === node.id)
if (outgoing.length === 0) {
this.showError(`开始节点 ${node.text} 必须有输出连线`)
return false
}
}
 
if (node.type === 'end') {
const incoming = edges.filter(edge => edge.targetNodeId === node.id)
if (incoming.length === 0) {
this.showError(`结束节点 ${node.text} 必须有输入连线`)
return false
}
}
}
 
return true
}

/**
* 显示提示信息
*/
showTip(message) {
const tip = document.createElement('div')
tip.className = 'process-tip'
tip.textContent = message
document.body.appendChild(tip)
 
setTimeout(() => {
document.body.removeChild(tip)
}, 3000)
}

/**
* 显示错误信息
*/
showError(message) {
console.error(message)
alert(message) // 实际项目中应该使用更优雅的错误提示
}
}

// 使用自定义事件处理器
const eventHandler = new CustomEventHandler(lf)
```

#### 3.2 条件式事件处理

```javascript
// 基于状态的事件处理
let currentMode = 'view' // view, edit, readonly

lf.on('node:click', ({ data, e }) => {
switch (currentMode) {
case 'view':
// 查看模式:显示节点详情
showNodeDetails(data)
break
 
case 'edit':
// 编辑模式:打开编辑面板
openEditPanel(data)
break
 
case 'readonly':
// 只读模式:不响应点击
e.preventDefault()
break
}
})

// 基于权限的事件处理
const userPermissions = ['read', 'write', 'delete'] // 用户权限

lf.on('node:contextmenu', ({ data, e }) => {
e.preventDefault()
 
const contextMenu = []
 
if (userPermissions.includes('read')) {
contextMenu.push({
text: '查看详情',
handler: () => showNodeDetails(data)
})
}
 
if (userPermissions.includes('write')) {
contextMenu.push({
text: '编辑属性',
handler: () => editNodeProperties(data)
})
}
 
if (userPermissions.includes('delete')) {
contextMenu.push({
text: '删除节点',
handler: () => deleteNode(data)
})
}
 
showContextMenu(e.clientX, e.clientY, contextMenu)
})
```

#### 3.3 异步事件处理

```javascript
// 异步节点验证
lf.on('node:add', async ({ data }) => {
try {
// 显示加载状态
showLoading(`验证节点 ${data.text}...`)
 
// 异步验证节点
const isValid = await validateNodeAsync(data)
 
if (!isValid) {
// 验证失败,删除节点
lf.deleteNode(data.id)
showError('节点验证失败,已自动删除')
} else {
showSuccess('节点验证成功')
}
} catch (error) {
console.error('节点验证出错:', error)
showError('节点验证出错')
} finally {
hideLoading()
}
})

// 异步保存
lf.on('node:drop', async ({ data }) => {
try {
// 节点拖拽结束后自动保存位置
await saveNodePosition(data.id, { x: data.x, y: data.y })
} catch (error) {
console.error('保存节点位置失败:', error)
}
})

// 节点验证函数
async function validateNodeAsync(nodeData) {
return new Promise((resolve) => {
// 模拟异步验证
setTimeout(() => {
// 验证逻辑
const isValid = nodeData.text && nodeData.text.length > 0
resolve(isValid)
}, 1000)
})
}
```

### 4. 事件委托和性能优化

```javascript
class OptimizedEventManager {
constructor(lf) {
this.lf = lf
this.eventQueue = []
this.isProcessing = false
this.debounceTimers = new Map()
 
this.init()
}

init() {
// 使用事件委托减少事件监听器数量
this.setupEventDelegation()
 
// 设置批量处理
this.setupBatchProcessing()
}

/**
* 设置事件委托
*/
setupEventDelegation() {
// 统一的点击事件处理
this.lf.on('node:click', this.handleElementClick.bind(this, 'node'))
this.lf.on('edge:click', this.handleElementClick.bind(this, 'edge'))
}

/**
* 处理元素点击(委托处理)
*/
handleElementClick(elementType, { data, e }) {
const handlers = this.getHandlersForElement(elementType, data.type)
 
for (const handler of handlers) {
try {
handler.call(this, data, e)
} catch (error) {
console.error(`事件处理器执行失败:`, error)
}
}
}

/**
* 获取元素的处理器
*/
getHandlersForElement(elementType, subType) {
const handlerMap = {
node: {
start: [this.handleStartNode],
task: [this.handleTaskNode],
gateway: [this.handleGatewayNode],
end: [this.handleEndNode],
},
edge: {
sequence: [this.handleSequenceEdge],
conditional: [this.handleConditionalEdge],
}
}
 
return handlerMap[elementType]?.[subType] || []
}

/**
* 防抖处理
*/
debounce(key, callback, delay = 300) {
if (this.debounceTimers.has(key)) {
clearTimeout(this.debounceTimers.get(key))
}
 
const timer = setTimeout(() => {
callback()
this.debounceTimers.delete(key)
}, delay)
 
this.debounceTimers.set(key, timer)
}

/**
* 批量处理事件
*/
setupBatchProcessing() {
this.lf.on('node:drag', ({ data }) => {
// 将拖拽事件加入队列
this.eventQueue.push({
type: 'node:position:change',
data: { id: data.id, x: data.x, y: data.y },
timestamp: Date.now()
})
 
// 防抖处理批量更新
this.debounce('batch-update', () => {
this.processBatchEvents()
}, 500)
})
}

/**
* 处理批量事件
*/
async processBatchEvents() {
if (this.isProcessing || this.eventQueue.length === 0) {
return
}
 
this.isProcessing = true
 
try {
// 按类型分组事件
const groupedEvents = this.groupEventsByType(this.eventQueue)
 
// 批量处理每种类型的事件
for (const [type, events] of groupedEvents) {
await this.processBatchEventsByType(type, events)
}
 
// 清空队列
this.eventQueue = []
} catch (error) {
console.error('批量事件处理失败:', error)
} finally {
this.isProcessing = false
}
}

/**
* 按类型分组事件
*/
groupEventsByType(events) {
const grouped = new Map()
 
for (const event of events) {
if (!grouped.has(event.type)) {
grouped.set(event.type, [])
}
grouped.get(event.type).push(event)
}
 
return grouped
}

/**
* 按类型批量处理事件
*/
async processBatchEventsByType(type, events) {
switch (type) {
case 'node:position:change':
await this.batchUpdateNodePositions(events)
break
 
default:
console.warn(`未知的批量事件类型: ${type}`)
}
}

/**
* 批量更新节点位置
*/
async batchUpdateNodePositions(events) {
const positions = events.map(event => ({
id: event.data.id,
x: event.data.x,
y: event.data.y
}))
 
try {
await fetch('/api/nodes/positions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ positions })
})
 
console.log(`批量更新了 ${positions.length} 个节点位置`)
} catch (error) {
console.error('批量更新节点位置失败:', error)
}
}
}

// 使用优化的事件管理器
const eventManager = new OptimizedEventManager(lf)
```

## 🎨 样式和动画

### 1. CSS 样式定义

```css
/* 连线动画 */
@keyframes flow {
0% {
stroke-dashoffset: 10;
}
100% {
stroke-dashoffset: 0;
}
}

.flow-animation {
animation: flow 2s linear infinite;
}

/* 连线悬停效果 */
.lf-edge-path:hover {
stroke-width: 3;
filter: drop-shadow(003pxrgba(24,144,255,0.5));
}

/* 选中状态 */
.lf-edge-path.lf-edge-selected {
stroke: #1890ff;
stroke-width: 3;
}

/* 自定义标记 */
.corner-marker {
cursor: pointer;
transition: all 0.2s;
}

.corner-marker:hover {
r: 5;
fill: #40a9ff;
}

/* 控制点 */
.control-point {
cursor: grab;
transition: all 0.2s;
}

.control-point:hover {
r: 6;
filter: drop-shadow(003pxrgba(24,144,255,0.8));
}

/* 事件提示 */
.process-tip {
position: fixed;
top: 20px;
right: 20px;
background: #52c41a;
color: white;
padding: 10px15px;
border-radius: 4px;
z-index: 1000;
animation: slideIn 0.3s ease;
}

@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}

/* 右键菜单 */
.context-menu {
position: fixed;
background: white;
border: 1px solid #d9d9d9;
border-radius: 4px;
box-shadow: 02px8pxrgba(0,0,0,0.15);
z-index: 1000;
min-width: 120px;
}

.context-menu-item {
padding: 8px12px;
cursor: pointer;
transition: background-color 0.2s;
}

.context-menu-item:hover {
background-color: #f5f5f5;
}

/* 属性面板 */
.edge-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
background: white;
border: 1px solid #d9d9d9;
border-radius: 6px;
padding: 20px;
box-shadow: 04px12pxrgba(0,0,0,0.15);
z-index: 1000;
min-width: 300px;
}

.edge-panel h3 {
margin: 0015px0;
color: #262626;
}

.edge-panel div {
margin-bottom: 10px;
}

.edge-panel label {
display: inline-block;
width: 80px;
font-weight: 500;
}

.edge-panel input,
.edge-panel select {
width: 200px;
padding: 4px8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
}

.edge-panel button {
margin-right: 10px;
padding: 6px15px;
border: none;
border-radius: 4px;
cursor: pointer;
}

.edge-panel button:first-of-type {
background: #1890ff;
color: white;
}

.edge-panel button:last-of-type {
background: #f5f5f5;
color: #595959;
}
```

### 2. 动态样式控制

```javascript
// 动态更改连线样式
function updateEdgeStyle(edgeId, newStyle) {
const edgeModel = lf.getEdgeModelById(edgeId)
if (edgeModel) {
// 更新模型属性
Object.assign(edgeModel, newStyle)
 
// 触发重新渲染
lf.graphModel.eventCenter.emit('model:style:update', {
id: edgeId,
type: 'edge'
})
}
}

// 批量更新样式
function batchUpdateStyles(updates) {
updates.forEach(({ id, type, style }) => {
if (type === 'edge') {
updateEdgeStyle(id, style)
} else if (type === 'node') {
updateNodeStyle(id, style)
}
})
}

// 基于条件的样式更新
function updateStylesByCondition(condition) {
const graphData = lf.getGraphData()
 
graphData.edges.forEach(edge => {
if (condition(edge)) {
updateEdgeStyle(edge.id, {
stroke: '#ff4d4f',
strokeWidth: 3
})
}
})
}
```

## 📚 完整示例

### 业务流程编辑器示例

```html
<!DOCTYPEhtml>
<html>
<head>
<title>LogicFlow 业务流程编辑器</title>
<style>
/* 引入上述CSS样式 */
</style>
</head>
<body>
<divid="container"></div>
 
<scripttype="module">
import LogicFlow from '@logicflow/core'
 
// 初始化编辑器
const lf = new LogicFlow({
container: document.getElementById('container'),
width: 1000,
height: 800,
grid: {
size: 10,
visible: true,
},
})
 
// 注册自定义连线
lf.register({
type: 'business-edge',
view: CustomLineView,
model: CustomLineModel,
})
 
// 设置默认连线类型
lf.setDefaultEdgeType('business-edge')
 
// 创建事件处理器
const eventHandler = new CustomEventHandler(lf)
 
// 渲染初始数据
lf.render({
nodes: [
{
id: 'start_1',
type: 'start',
x: 100,
y: 200,
text: '开始'
},
{
id: 'task_1',
type: 'task',
x: 300,
y: 200,
text: '审核申请',
properties: {
assignee: 'manager',
dueDate: '3days'
}
},
{
id: 'gateway_1',
type: 'gateway',
x: 500,
y: 200,
text: '审核结果'
},
{
id: 'task_2',
type: 'task',
x: 700,
y: 150,
text: '通过处理'
},
{
id: 'task_3',
type: 'task',
x: 700,
y: 250,
text: '拒绝处理'
},
{
id: 'end_1',
type: 'end',
x: 900,
y: 200,
text: '结束'
}
],
edges: [
{
id: 'edge_1',
type: 'business-edge',
sourceNodeId: 'start_1',
targetNodeId: 'task_1',
text: '提交申请'
},
{
id: 'edge_2',
type: 'business-edge',
sourceNodeId: 'task_1',
targetNodeId: 'gateway_1',
text: '审核完成'
},
{
id: 'edge_3',
type: 'business-edge',
sourceNodeId: 'gateway_1',
targetNodeId: 'task_2',
text: '通过',
properties: {
condition: '${approved == true}',
status: 'success'
}
},
{
id: 'edge_4',
type: 'business-edge',
sourceNodeId: 'gateway_1',
targetNodeId: 'task_3',
text: '拒绝',
properties: {
condition: '${approved == false}',
status: 'error'
}
},
{
id: 'edge_5',
type: 'business-edge',
sourceNodeId: 'task_2',
targetNodeId: 'end_1',
text: '完成'
},
{
id: 'edge_6',
type: 'business-edge',
sourceNodeId: 'task_3',
targetNodeId: 'end_1',
text: '完成'
}
]
})
</script>
</body>
</html>
```

## 🔧 调试和性能优化

### 1. 调试技巧

```javascript
// 开启调试模式
lf.openSelectionSelect() // 开启框选
lf.openPaintBucket() // 开启画笔工具

// 监听所有事件(调试用)
const allEvents = [
'node:click', 'node:dblclick', 'node:mousedown', 'node:mouseup',
'edge:click', 'edge:dblclick', 'edge:add', 'edge:delete',
'blank:click', 'selection:selected'
]

allEvents.forEach(eventName => {
lf.on(eventName, (data) => {
console.log(`[LogicFlow Event] ${eventName}:`, data)
})
})

// 性能监控
const performanceMonitor = {
startTime: 0,
 
start(operation) {
this.startTime = performance.now()
console.log(`[Performance] Starting ${operation}`)
},
 
end(operation) {
const duration = performance.now() - this.startTime
console.log(`[Performance] ${operation} completed in ${duration.toFixed(2)}ms`)
}
}

// 使用示例
performanceMonitor.start('Render Graph')
lf.render(graphData)
performanceMonitor.end('Render Graph')
```

### 2. 性能优化建议

```javascript
// 1. 大量数据时使用虚拟滚动
const lf = new LogicFlow({
container: document.getElementById('container'),
// ... 其他配置
virtual: true, // 开启虚拟滚动
})

// 2. 减少不必要的重绘
lf.on('node:drag', debounce(({ data }) => {
// 节流处理拖拽事件
updateNodePosition(data)
}, 100))

// 3. 批量操作
function batchAddNodes(nodes) {
lf.graphModel.batchUpdate(() => {
nodes.forEach(node => {
lf.addNode(node)
})
})
}

// 4. 懒加载
function loadGraphLazily(graphData) {
// 先加载核心节点
const coreNodes = graphData.nodes.filter(node => node.core)
lf.render({ nodes: coreNodes, edges: [] })
 
// 延迟加载其他节点
setTimeout(() => {
const otherNodes = graphData.nodes.filter(node => !node.core)
otherNodes.forEach(node => lf.addNode(node))
 
// 最后加载连线
graphData.edges.forEach(edge => lf.addEdge(edge))
}, 100)
}
```

---

*本文档基于LogicFlow官网最新版本编写,涵盖了自定义连线和事件处理的完整实现方案。适用于各种复杂的业务场景开发。*
http://www.hskmm.com/?act=detail&tid=12444

相关文章:

  • CF2086D Even String
  • logicflow___文档3
  • 2025年运营商API安全建设最佳实践:某头部省级电信案例解析与方案推荐
  • 软件工程第二次作业-第一次个人编程作业
  • 面向对象入门2与类的识别
  • 202508_天山固网_to
  • jmeter分布式压测
  • 怎么屏蔽 ahref.com 上你不想看到的网站链接(垃圾外链)
  • 浅谈字典树
  • go-mapus为局域网地图协作而生
  • 《手搓动态顺序表:从数组到自动扩容的华丽转身》 - 详解
  • 板子大全
  • 通过人大金仓数据库的逻辑备份与还原功能实现数据迁移
  • 第十二节:订单普通下单、支付回调、退款、退款回调详解
  • 《原子习惯》-读书笔记7
  • 第3周预习作业
  • 《原子习惯》-读书笔记6
  • Java LTS版本进化秀:从8到21的欢乐升级之旅
  • 201912_EASER
  • 搜索百科(3):Elasticsearch — 搜索界的“流量明星”
  • 打印机漏洞、匿名协议与AWS安全:一周技术热点解析
  • 从零开始训练推理模型:GRPO+Unsloth改造Qwen实战指南
  • ALLinSSL,开源免费的SSL证书自动化管理平台
  • 《原子习惯》-读书笔记5
  • 03-袁东申论-概括原因
  • 包和final
  • 实现双向循环链表 - 详解
  • 2025-09-21 网站前几分钟还运行的好好地,几分钟后查看居然显示文件无法加载,访问首页提示无法访问此网站??!==ssl证书过期+域名解析失效
  • 20231321王曦轶《密码系统设计》第二周
  • 爱锋拍照工具 - 隐私政策