last logicflow

<template>
  <div class="logicflow-page">
    <div class="sidebar">
      <div class="palette-title">组件面板</div>
      <div class="palette-item" @mousedown="startDrag('custom-rect', '矩形')">矩形</div>
      <div class="palette-item" @mousedown="startDrag('circle', '圆形')">圆形</div>
      <div class="palette-item" @mousedown="startDrag('diamond', '菱形')">菱形</div>
      <div class="palette-item" @mousedown="startDrag('text', '文本')">文本</div>

      <div class="palette-title" style="margin-top: 20px;">关联关系</div>
      <div class="edge-type-selector">
        <div 
          class="edge-type-item" 
          :class="{ active: selectedEdgeType === 'solid' }"
          @click="selectEdgeType('solid')"
        >
          <div class="edge-preview solid-line"></div>
          <span>包含关系</span>
        </div>
        <div 
          class="edge-type-item" 
          :class="{ active: selectedEdgeType === 'dashed' }"
          @click="selectEdgeType('dashed')"
        >
          <div class="edge-preview dashed-line"></div>
          <span>关联关系</span>
        </div>
        <div 
          class="edge-type-item" 
          :class="{ active: selectedEdgeType === 'dotted' }"
          @click="selectEdgeType('dotted')"
        >
          <div class="edge-preview dotted-line"></div>
          <span>使用关系</span>
        </div>
      </div>

      <div class="palette-title" style="margin-top: 20px;">操作说明</div>
      <div class="help-text">
        <p>• 点击节点/连接线:编辑文字</p>
        <p>• 右键节点/连接线:删除</p>
        <p>• Delete/Backspace:删除选中元素</p>
        <p>• Ctrl+A:全选</p>
        <p>• Ctrl+C:复制</p>
        <p>• Ctrl+V:粘贴</p>
      </div>
    </div>
    <div class="canvas-wrap">
      <div ref="lfContainerRef" class="lf-container"></div>
    </div>

    <el-dialog v-model="nodeDialogVisible" title="节点配置" width="600px" append-to-body :close-on-click-modal="false">
      <el-form label-width="100px">
        <el-form-item label="节点ID">
          <el-input v-model="selectedNodeId" disabled />
        </el-form-item>
        <el-form-item label="显示文本">
          <el-input v-model="selectedNodeText" placeholder="请输入节点文本" />
        </el-form-item>
        <el-form-item label="节点类型">
          <el-input v-model="selectedNodeType" placeholder="请输入节点类型" />
        </el-form-item>
        <el-form-item label="节点描述">
          <el-input 
            v-model="selectedNodeDescription" 
            type="textarea" 
            :rows="2"
            placeholder="请输入节点描述" 
          />
        </el-form-item>
        
        <!-- 关联关系配置 -->
        <el-divider content-position="left">关联关系配置</el-divider>
        
        <el-form-item label="关联节点">
          <div class="relation-config">
            <div class="relation-list">
              <div 
                v-for="(relation, index) in selectedNodeRelations" 
                :key="index"
                class="relation-item"
              >
                <el-select 
                  v-model="relation.targetNodeId" 
                  placeholder="选择关联节点"
                  style="width: 150px; margin-right: 10px;"
                >
                  <el-option 
                    v-for="node in getAvailableNodes(selectedNodeId)"
                    :key="node.id"
                    :label="node.text?.value || node.id"
                    :value="node.id"
                  />
                </el-select>
                <el-select 
                  v-model="relation.relationType" 
                  placeholder="关系类型"
                  style="width: 120px; margin-right: 10px;"
                >
                  <el-option label="包含" value="contains" />
                  <el-option label="关联" value="associates" />
                  <el-option label="使用" value="uses" />
                </el-select>
                <el-button 
                  type="danger" 
                  icon="Delete" 
                  size="small"
                  @click="removeRelation(index)"
                />
              </div>
            </div>
            <el-button 
              type="primary" 
              icon="Plus" 
              size="small"
              @click="addRelation"
              style="margin-top: 10px;"
            >
              添加关联
            </el-button>
          </div>
        </el-form-item>
        
        <!-- 现有关联关系显示 -->
        <el-form-item label="现有关联" v-if="getNodeRelationsSummary(selectedNodeId).length > 0">
          <div class="existing-relations">
            <el-tag 
              v-for="(summary, index) in getNodeRelationsSummary(selectedNodeId)"
              :key="index"
              type="info"
              size="small"
              style="margin-right: 8px; margin-bottom: 4px;"
            >
              {{ summary }}
            </el-tag>
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="nodeDialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="applyNodeChange">保 存</el-button>
          <el-button type="info" @click="viewNodeRelations">查看关系图</el-button>
        </div>
      </template>
    </el-dialog>

    <el-dialog v-model="edgeDialogVisible" title="连接线配置" width="500px" append-to-body :close-on-click-modal="false">
      <el-form label-width="100px">
        <el-form-item label="连接线ID">
          <el-input v-model="selectedEdgeId" disabled />
        </el-form-item>
        <el-form-item label="显示文本">
          <el-input v-model="selectedEdgeText" placeholder="请输入连接线文本" />
        </el-form-item>
        <el-form-item label="线条类型">
          <el-select v-model="selectedEdgeLineType" placeholder="选择线条类型">
            <el-option label="实线" value="solid" />
            <el-option label="虚线" value="dashed" />
            <el-option label="点线" value="dotted" />
          </el-select>
        </el-form-item>
        <el-form-item label="文字颜色">
          <div class="color-picker-container">
            <el-color-picker 
              v-model="selectedEdgeTextColor" 
              :predefine="predefinedColors"
              show-alpha
            />
            <span class="color-preview" :style="{ color: selectedEdgeTextColor }">
              预览文字效果
            </span>
          </div>
        </el-form-item>
        <el-form-item label="文字大小">
          <el-slider 
            v-model="selectedEdgeTextSize" 
            :min="10" 
            :max="24" 
            :step="1"
            show-input
            input-size="small"
          />
        </el-form-item>
        <el-form-item label="箭头样式">
          <el-select v-model="selectedArrowType" placeholder="选择箭头样式">
            <el-option label="默认箭头" value="default" />
            <el-option label="实心箭头" value="filled" />
            <el-option label="空心箭头" value="hollow" />
            <el-option label="菱形箭头" value="diamond" />
            <el-option label="圆形箭头" value="circle" />
          </el-select>
        </el-form-item>
        <el-form-item label="起始点标记">
          <el-select v-model="selectedStartMarker" placeholder="选择起始点标记">
            <el-option label="无标记" value="none" />
            <el-option label="实心圆" value="filled-circle" />
            <el-option label="空心圆" value="hollow-circle" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="edgeDialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="applyEdgeChange">保 存</el-button>
        </div>
      </template>
    </el-dialog>

    <!-- 关系图查看对话框 -->
    <el-dialog v-model="relationViewDialogVisible" title="节点关系图" width="800px" append-to-body>
      <div class="relation-view">
        <div class="relation-header">
          <h4>节点:{{ selectedNodeText }} ({{ selectedNodeId }})</h4>
        </div>
        
        <!-- 关系树状图 -->
        <div class="relation-tree">
          <el-tree
            :data="relationTreeData"
            :props="{ children: 'children', label: 'label' }"
            default-expand-all
            node-key="id"
            class="relation-tree-view"
          >
            <template #default="{ node, data }">
              <span class="relation-node">
                <el-icon v-if="data.type === 'node'"><Box /></el-icon>
                <el-icon v-else-if="data.type === 'relation'"><Connection /></el-icon>
                <span>{{ data.label }}</span>
                <el-tag v-if="data.relationType" size="small" type="primary">
                  {{ getRelationTypeLabel(data.relationType) }}
                </el-tag>
              </span>
            </template>
          </el-tree>
        </div>
        
        <!-- 关系表格 -->
        <el-divider>关系详情</el-divider>
        <el-table :data="relationTableData" style="width: 100%" size="small">
          <el-table-column prop="sourceNode" label="源节点" width="150" />
          <el-table-column prop="relationType" label="关系类型" width="120">
            <template #default="scope">
              <el-tag size="small" :type="getRelationTagType(scope.row.relationType)">
                {{ getRelationTypeLabel(scope.row.relationType) }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="targetNode" label="目标节点" width="150" />
          <el-table-column prop="description" label="描述" />
          <el-table-column label="操作" width="120">
            <template #default="scope">
              <el-button 
                type="primary" 
                size="small" 
                @click="highlightRelation(scope.row)"
              >
                高亮
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="relationViewDialogVisible = false">关 闭</el-button>
          <el-button type="primary" @click="exportRelations">导出关系</el-button>
        </div>
      </template>
    </el-dialog>
  </div>

</template>

<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue'
import { ElMessage } from 'element-plus'
import LogicFlow from '@logicflow/core'
import { Menu, SelectionSelect, Control, MiniMap, Snapshot } from '@logicflow/extension'
import { RectNode, RectNodeModel, LineEdge, LineEdgeModel, h } from '@logicflow/core'
import '@logicflow/core/dist/index.css'
import '@logicflow/extension/dist/index.css'

const lfContainerRef = ref(null)
let lf = null

const nodeDialogVisible = ref(false)
const selectedNodeId = ref('')
const selectedNodeText = ref('')
const selectedNodeType = ref('')
const selectedNodeDescription = ref('')
const selectedNodeRelations = ref([])

// 关系图查看对话框
const relationViewDialogVisible = ref(false)
const relationTreeData = ref([])
const relationTableData = ref([])

// 全局关联关系存储
const nodeRelations = ref(new Map())

const edgeDialogVisible = ref(false)
const selectedEdgeId = ref('')
const selectedEdgeText = ref('')
const selectedEdgeType = ref('solid') // 当前选中的连线类型
const selectedEdgeLineType = ref('solid') // 编辑对话框中的线条类型
const selectedEdgeTextColor = ref('#374151') // 编辑对话框中的文字颜色
const selectedEdgeTextSize = ref(12) // 编辑对话框中的文字大小
const selectedArrowType = ref('default') // 编辑对话框中的箭头类型
const selectedStartMarker = ref('none') // 编辑对话框中的起始点标记

// 预定义颜色选项
const predefinedColors = [
  '#374151', '#ef4444', '#f59e0b', '#10b981', 
  '#3b82f6', '#8b5cf6', '#ec4899', '#6b7280'
]

// 自定义矩形节点
class CustomRectNode extends RectNode {}
class CustomRectNodeModel extends RectNodeModel {
  constructor(data, graphModel) {
    super(data, graphModel)
    // 设置默认尺寸
    this.width = 180
    this.height = 40
  }

  getNodeStyle() {
    const style = super.getNodeStyle()
    return {
      ...style,
      fill: '#f0f9ff',  // 自定义填充色
      stroke: '#3b82f6',  // 自定义边框色
      strokeWidth: 2,  // 自定义边框宽度
      borderRadius: 8,  // 自定义圆角
    }
  }

  // 重写获取节点尺寸的方法
  getWidth() {
    return this.width
  }

  getHeight() {
    return this.height
  }

  getTextStyle() {
    const style = super.getTextStyle()
    return {
      ...style,
      fontSize: 14,
      fontWeight: 'bold',
      fill: '#1e40af',
    }
  }
}

// 备用方案:如果自定义getText不行,就用这个简单的类
class SimpleCustomEdge extends LineEdge {
  // 什么都不重写,让LogicFlow完全处理
}

// 完全重写的自定义连接线 - 支持起始点标记和文字居中
class CustomEdge extends LineEdge {
  getShape() {
    const { model } = this.props
    const { startMarker } = model.properties || {}
    
    // 获取默认的连线形状
    const shape = super.getShape()
    
    // 如果有起始点标记,添加起始点圆形
    if (startMarker && startMarker !== 'none') {
      const startPoint = this.getStartPoint()
      const lineType = model.properties?.lineType || 'solid'
      
      const colors = {
        solid: '#3b82f6',
        dashed: '#10b981',
        dotted: '#f59e0b'
      }
      
      const circle = h('circle', {
        cx: startPoint.x,
        cy: startPoint.y,
        r: 4,
        fill: startMarker === 'filled-circle' ? colors[lineType] : 'white',
        stroke: colors[lineType],
        strokeWidth: 2,
        className: 'lf-edge-start-marker'
      })
      
      // 将起始点圆形添加到形状中
      if (shape.children) {
        shape.children.push(circle)
      } else {
        return h('g', {}, [shape, circle])
      }
    }
    
    return shape
  }
  
  getStartPoint() {
    const { model } = this.props
    
    // 尝试多种方式获取起始点
    if (model.startPoint) {
      return model.startPoint
    } else if (model.x1 !== undefined) {
      return { x: model.x1, y: model.y1 }
    } else {
      const sourceNode = model.graphModel.getNodeModelById(model.sourceNodeId)
      return sourceNode ? { x: sourceNode.x, y: sourceNode.y } : { x: 0, y: 0 }
    }
  }
  getText() {
    const { model } = this.props
    const text = model.text?.value || ''
    
    if (!text.trim()) return null

    // 调试:输出模型的所有属性
    console.log('模型属性:', model)
    console.log('startPoint:', model.startPoint)
    console.log('endPoint:', model.endPoint)
    console.log('x1,y1,x2,y2:', model.x1, model.y1, model.x2, model.y2)
    
    // 尝试多种方式获取连线坐标
    let startX, startY, endX, endY
    
    // 方法1: 直接从model获取
    if (model.x1 !== undefined && model.x2 !== undefined) {
      startX = model.x1
      startY = model.y1
      endX = model.x2
      endY = model.y2
    }
    // 方法2: 从startPoint和endPoint获取
    else if (model.startPoint && model.endPoint) {
      startX = model.startPoint.x
      startY = model.startPoint.y
      endX = model.endPoint.x
      endY = model.endPoint.y
    }
    // 方法3: 从源节点和目标节点获取
    else {
      const sourceNode = model.graphModel.getNodeModelById(model.sourceNodeId)
      const targetNode = model.graphModel.getNodeModelById(model.targetNodeId)
      if (sourceNode && targetNode) {
        startX = sourceNode.x
        startY = sourceNode.y
        endX = targetNode.x
        endY = targetNode.y
      } else {
        console.error('无法获取连线坐标')
        return null
      }
    }
    
    // 计算中心点
    const centerX = (startX + endX) / 2
    const centerY = (startY + endY) / 2
    
    // 获取文字样式
    const textColor = model.properties?.textColor || '#374151'
    const textSize = model.properties?.textSize || 12
    
    console.log(`连线坐标: (${startX},${startY}) -> (${endX},${endY})`)
    console.log(`文字渲染: "${text}" 中心位置: (${centerX}, ${centerY})`)
    
    // 直接使用绝对坐标,不使用transform
    return h('text', {
      x: centerX,
      y: centerY,
      textAnchor: 'middle',
      dominantBaseline: 'central',
      fontSize: textSize,
      fill: textColor,
      fontFamily: 'Arial, sans-serif',
      fontWeight: '500'
    }, text)
  }
}

// 基础自定义边模型
class CustomEdgeModel extends LineEdgeModel {
  getEdgeStyle() {
    const style = super.getEdgeStyle()
    const lineType = this.properties?.lineType || 'solid'
    
    // 根据线条类型设置不同样式
    const lineStyles = {
      solid: {
        stroke: '#3b82f6',
        strokeWidth: 2,
        strokeDasharray: '0',
      },
      dashed: {
        stroke: '#10b981',
        strokeWidth: 2,
        strokeDasharray: '8,4',
      },
      dotted: {
        stroke: '#f59e0b',
        strokeWidth: 2,
        strokeDasharray: '2,2',
      }
    }
    
    return {
      ...style,
      ...lineStyles[lineType]
    }
  }
  
  getArrowStyle() {
    const style = super.getArrowStyle()
    const lineType = this.properties?.lineType || 'solid'
    const arrowType = this.properties?.arrowType || 'default'
    
    const arrowColors = {
      solid: '#3b82f6',
      dashed: '#10b981',
      dotted: '#f59e0b'
    }
    
    // 根据箭头类型设置不同的样式
    const baseStyle = {
      fill: arrowColors[lineType],
      stroke: arrowColors[lineType],
      strokeWidth: 1,
    }
    
    switch (arrowType) {
      case 'filled': // 实心箭头
        return {
          ...style,
          ...baseStyle,
          fill: arrowColors[lineType],
          stroke: arrowColors[lineType]
        }
      case 'hollow': // 空心箭头
        return {
          ...style,
          ...baseStyle,
          fill: 'white',
          stroke: arrowColors[lineType],
          strokeWidth: 2
        }
      case 'diamond': // 菱形箭头
        return {
          ...style,
          ...baseStyle,
          d: 'M 0 0 L 8 4 L 0 8 L -8 4 Z' // 菱形路径
        }
      case 'circle': // 圆形箭头
        return {
          ...style,
          ...baseStyle,
          r: 4 // 圆形半径
        }
      default: // 默认箭头
        return {
          ...style,
          ...baseStyle
        }
    }
  }
  
  // 配置起始点标记
  getStartArrowStyle() {
    const startMarker = this.properties?.startMarker || 'none'
    const lineType = this.properties?.lineType || 'solid'
    
    const colors = {
      solid: '#3b82f6',
      dashed: '#10b981',
      dotted: '#f59e0b'
    }
    
    if (startMarker === 'none') return null
    
    return {
      fill: startMarker === 'filled-circle' ? colors[lineType] : 'white',
      stroke: colors[lineType],
      strokeWidth: 2,
      r: 4
    }
  }
  
  // 配置终点标记
  getEndArrowStyle() {
    return this.getArrowStyle()
  }
  
  // 备用的文字样式方法,如果CustomEdge的getText有问题就用这个
  getTextStyle() {
    const style = super.getTextStyle()
    const textColor = this.properties?.textColor || '#374151'
    const textSize = this.properties?.textSize || 12
    
    return {
      ...style,
      fontSize: textSize,
      fill: textColor,
      fontWeight: '500',
      fontFamily: 'Arial, sans-serif',
      textAnchor: 'middle',
      dominantBaseline: 'central',
      background: {
        fill: 'transparent',
        stroke: 'transparent',
        strokeWidth: 0
      }
    }
  }
}

function startDrag(type, text) {
  if (!lf) return
  lf.dnd.startDrag({ type, text })
}

// 选择边类型
function selectEdgeType(edgeType) {
  selectedEdgeType.value = edgeType
  // 设置默认的边类型,影响后续创建的连线
  if (lf) {
    lf.setDefaultEdgeType('custom-edge')
  }
}

// 根据线型获取对应的关联关系
function getRelationTypeByLineType(lineType) {
  const mapping = {
    'solid': 'contains',      // 实线 -> 包含关系
    'dashed': 'associates',   // 虚线 -> 关联关系
    'dotted': 'uses'          // 点线 -> 使用关系
  }
  return mapping[lineType] || 'contains'
}

// 根据关联关系获取对应的线型
function getLineTypeByRelationType(relationType) {
  const mapping = {
    'contains': 'solid',      // 包含关系 -> 实线
    'associates': 'dashed',   // 关联关系 -> 虚线
    'uses': 'dotted'          // 使用关系 -> 点线
  }
  return mapping[relationType] || 'solid'
}

// 将连线关系同步到源节点的关联关系配置
function syncEdgeToNodeRelation(sourceNodeId, targetNodeId, relationType) {
  // 获取源节点现有的关联关系
  const existingRelations = nodeRelations.value.get(sourceNodeId) || []
  
  // 检查是否已存在相同的关系(避免重复)
  const isDuplicate = existingRelations.some(relation => 
    relation.targetNodeId === targetNodeId && relation.relationType === relationType
  )
  
  if (!isDuplicate) {
    // 添加新的关联关系
    const newRelation = {
      targetNodeId: targetNodeId,
      relationType: relationType,
      customType: '',
      description: `通过连线自动创建的${getRelationTypeLabel(relationType)}关系`
    }
    
    existingRelations.push(newRelation)
    nodeRelations.value.set(sourceNodeId, existingRelations)
    
    console.log(`已自动添加关联关系: ${sourceNodeId} -> ${targetNodeId} (${relationType})`)
  }
}

// 从节点关联关系中移除指定的关系
function removeEdgeFromNodeRelation(sourceNodeId, targetNodeId, relationType) {
  const existingRelations = nodeRelations.value.get(sourceNodeId) || []
  
  const filteredRelations = existingRelations.filter(relation => 
    !(relation.targetNodeId === targetNodeId && relation.relationType === relationType)
  )
  
  if (filteredRelations.length !== existingRelations.length) {
    nodeRelations.value.set(sourceNodeId, filteredRelations)
    console.log(`已移除关联关系: ${sourceNodeId} -> ${targetNodeId} (${relationType})`)
  }
}

function applyNodeChange() {
  if (!selectedNodeId.value) return
  
  // 更新节点基本信息
  lf.setProperties(selectedNodeId.value, { 
    text: selectedNodeText.value,
    nodeType: selectedNodeType.value,
    description: selectedNodeDescription.value
  })
  // 同步 label 文本
  lf.updateText(selectedNodeId.value, selectedNodeText.value)
  
  // 保存关联关系
  saveNodeRelations(selectedNodeId.value, selectedNodeRelations.value)
  
  nodeDialogVisible.value = false
  ElMessage.success('已更新节点配置')
}

// 添加关联关系
function addRelation() {
  selectedNodeRelations.value.push({
    targetNodeId: '',
    relationType: '',
    customType: '',
    description: ''
  })
}

// 移除关联关系
function removeRelation(index) {
  selectedNodeRelations.value.splice(index, 1)
}

// 保存节点关联关系
function saveNodeRelations(nodeId, relations) {
  const validRelations = relations.filter(r => r.targetNodeId && r.relationType)
  const oldRelations = nodeRelations.value.get(nodeId) || []
  
  // 找出被删除的关系,删除对应的自动创建的连线
  oldRelations.forEach((oldRelation, index) => {
    const stillExists = validRelations.some(newRelation => 
      newRelation.targetNodeId === oldRelation.targetNodeId && 
      newRelation.relationType === oldRelation.relationType
    )
    
    if (!stillExists) {
      // 删除对应的自动创建的连线
      const edgeId = `relation_${nodeId}_${oldRelation.targetNodeId}_${index}`
      try {
        const edgeModel = lf.getEdgeModelById(edgeId)
        if (edgeModel) {
          lf.deleteEdge(edgeId)
          console.log(`已删除关联关系对应的连线: ${edgeId}`)
        }
      } catch (error) {
        console.warn('删除关联连线失败:', error)
      }
    }
  })
  
  nodeRelations.value.set(nodeId, validRelations)
  
  // 创建自动连线(可选)
  createRelationEdges(nodeId, validRelations)
}

// 创建关系连线
function createRelationEdges(sourceNodeId, relations) {
  relations.forEach((relation, index) => {
    if (!relation.targetNodeId || !relation.relationType) return
    
    const edgeId = `relation_${sourceNodeId}_${relation.targetNodeId}_${index}`
    const relationTypeLabel = getRelationTypeLabel(relation.relationType)
    
    // 检查是否已存在连线
    const existingEdge = lf.getEdgeModelById(edgeId)
    if (existingEdge) {
      // 更新现有连线
      lf.setProperties(edgeId, {
        relationType: relation.relationType,
        relationLabel: relationTypeLabel
      })
      lf.updateText(edgeId, relationTypeLabel)
    } else {
      // 创建新连线
      try {
        lf.addEdge({
          id: edgeId,
          sourceNodeId: sourceNodeId,
          targetNodeId: relation.targetNodeId,
          text: relationTypeLabel,
          type: 'custom-edge',
          properties: {
            lineType: getRelationLineType(relation.relationType),
            relationType: relation.relationType,
            relationLabel: relationTypeLabel,
            textColor: getRelationColor(relation.relationType),
            textSize: 12,
            arrowType: 'filled'
          }
        })
      } catch (error) {
        console.warn('创建关系连线失败:', error)
      }
    }
  })
}

// 获取可用节点列表
function getAvailableNodes(currentNodeId) {
  if (!lf) return []
  const graphData = lf.getGraphData()
  return graphData.nodes.filter(node => node.id !== currentNodeId)
}

// 获取节点关系摘要
function getNodeRelationsSummary(nodeId) {
  const relations = nodeRelations.value.get(nodeId) || []
  const summaries = []
  
  relations.forEach(relation => {
    if (relation.targetNodeId && relation.relationType) {
      const targetNode = getNodeById(relation.targetNodeId)
      const targetName = targetNode?.text?.value || relation.targetNodeId
      const relationLabel = getRelationTypeLabel(relation.relationType)
      summaries.push(`${relationLabel} → ${targetName}`)
    }
  })
  
  // 查找指向当前节点的关系
  nodeRelations.value.forEach((relations, sourceNodeId) => {
    relations.forEach(relation => {
      if (relation.targetNodeId === nodeId) {
        const sourceNode = getNodeById(sourceNodeId)
        const sourceName = sourceNode?.text?.value || sourceNodeId
        const relationLabel = getRelationTypeLabel(relation.relationType)
        summaries.push(`${sourceName} → ${relationLabel}`)
      }
    })
  })
  
  return summaries
}

// 查看节点关系
function viewNodeRelations() {
  buildRelationTreeData(selectedNodeId.value)
  buildRelationTableData(selectedNodeId.value)
  relationViewDialogVisible.value = true
}

// 构建关系树数据
function buildRelationTreeData(nodeId) {
  const currentNode = getNodeById(nodeId)
  const currentNodeName = currentNode?.text?.value || nodeId
  
  const treeNode = {
    id: nodeId,
    label: currentNodeName,
    type: 'node',
    children: []
  }
  
  // 添加出去的关系
  const outgoingRelations = nodeRelations.value.get(nodeId) || []
  outgoingRelations.forEach((relation, index) => {
    if (relation.targetNodeId && relation.relationType) {
      const targetNode = getNodeById(relation.targetNodeId)
      const targetName = targetNode?.text?.value || relation.targetNodeId
      const relationLabel = getRelationTypeLabel(relation.relationType)
      
      treeNode.children.push({
        id: `${nodeId}_out_${index}`,
        label: `${relationLabel} → ${targetName}`,
        type: 'relation',
        relationType: relation.relationType
      })
    }
  })
  
  // 添加进来的关系
  nodeRelations.value.forEach((relations, sourceNodeId) => {
    relations.forEach((relation, index) => {
      if (relation.targetNodeId === nodeId) {
        const sourceNode = getNodeById(sourceNodeId)
        const sourceName = sourceNode?.text?.value || sourceNodeId
        const relationLabel = getRelationTypeLabel(relation.relationType)
        
        treeNode.children.push({
          id: `${sourceNodeId}_in_${index}`,
          label: `${sourceName} → ${relationLabel}`,
          type: 'relation',
          relationType: relation.relationType
        })
      }
    })
  })
  
  relationTreeData.value = [treeNode]
}

// 构建关系表格数据
function buildRelationTableData(nodeId) {
  const tableData = []
  
  // 出去的关系
  const outgoingRelations = nodeRelations.value.get(nodeId) || []
  outgoingRelations.forEach(relation => {
    if (relation.targetNodeId && relation.relationType) {
      const targetNode = getNodeById(relation.targetNodeId)
      const targetName = targetNode?.text?.value || relation.targetNodeId
      const currentNode = getNodeById(nodeId)
      const currentName = currentNode?.text?.value || nodeId
      
      tableData.push({
        sourceNode: currentName,
        relationType: relation.relationType,
        targetNode: targetName,
        description: relation.description || `${currentName} ${getRelationTypeLabel(relation.relationType)} ${targetName}`,
        direction: 'outgoing',
        sourceNodeId: nodeId,
        targetNodeId: relation.targetNodeId
      })
    }
  })
  
  // 进来的关系
  nodeRelations.value.forEach((relations, sourceNodeId) => {
    relations.forEach(relation => {
      if (relation.targetNodeId === nodeId) {
        const sourceNode = getNodeById(sourceNodeId)
        const sourceName = sourceNode?.text?.value || sourceNodeId
        const currentNode = getNodeById(nodeId)
        const currentName = currentNode?.text?.value || nodeId
        
        tableData.push({
          sourceNode: sourceName,
          relationType: relation.relationType,
          targetNode: currentName,
          description: relation.description || `${sourceName} ${getRelationTypeLabel(relation.relationType)} ${currentName}`,
          direction: 'incoming',
          sourceNodeId: sourceNodeId,
          targetNodeId: nodeId
        })
      }
    })
  })
  
  relationTableData.value = tableData
}

// 获取节点by ID
function getNodeById(nodeId) {
  if (!lf) return null
  const graphData = lf.getGraphData()
  return graphData.nodes.find(node => node.id === nodeId)
}

// 获取关系类型标签
function getRelationTypeLabel(relationType) {
  const labels = {
    'contains': '包含',
    'associates': '关联',
    'uses': '使用'
  }
  return labels[relationType] || relationType
}

// 获取关系标签类型
function getRelationTagType(relationType) {
  const types = {
    'contains': 'success',
    'associates': 'primary',
    'uses': 'warning'
  }
  return types[relationType] || 'info'
}

// 获取关系线条类型
function getRelationLineType(relationType) {
  const lineTypes = {
    'contains': 'solid',      // 包含关系用实线
    'associates': 'dashed',   // 关联关系用虚线
    'uses': 'dotted'          // 使用关系用点线
  }
  return lineTypes[relationType] || 'solid'
}

// 获取关系颜色
function getRelationColor(relationType) {
  const colors = {
    'contains': '#3b82f6',    // 包含关系用蓝色(对应实线)
    'associates': '#10b981',  // 关联关系用绿色(对应虚线)
    'uses': '#f59e0b'         // 使用关系用橙色(对应点线)
  }
  return colors[relationType] || '#374151'
}

// 高亮关系
function highlightRelation(relationData) {
  // 高亮相关节点
  lf.selectElementById(relationData.sourceNodeId, true)
  lf.selectElementById(relationData.targetNodeId, true)
  
  ElMessage.success(`已高亮关系:${relationData.sourceNode} → ${relationData.targetNode}`)
}

// 导出关系
function exportRelations() {
  const allRelations = []
  nodeRelations.value.forEach((relations, sourceNodeId) => {
    const sourceNode = getNodeById(sourceNodeId)
    const sourceName = sourceNode?.text?.value || sourceNodeId
    
    relations.forEach(relation => {
      if (relation.targetNodeId && relation.relationType) {
        const targetNode = getNodeById(relation.targetNodeId)
        const targetName = targetNode?.text?.value || relation.targetNodeId
        
        allRelations.push({
          源节点: sourceName,
          源节点ID: sourceNodeId,
          关系类型: getRelationTypeLabel(relation.relationType),
          目标节点: targetName,
          目标节点ID: relation.targetNodeId,
          自定义类型: relation.customType || '',
          描述: relation.description || ''
        })
      }
    })
  })
  
  // 导出为JSON
  const dataStr = JSON.stringify(allRelations, null, 2)
  const dataBlob = new Blob([dataStr], {type: 'application/json'})
  const url = URL.createObjectURL(dataBlob)
  const link = document.createElement('a')
  link.href = url
  link.download = 'node-relations.json'
  link.click()
  
  ElMessage.success('关系数据已导出')
}

function applyEdgeChange() {
  if (!selectedEdgeId.value) return
  
  // 获取连线数据
  const edgeModel = lf.getEdgeModelById(selectedEdgeId.value)
  if (!edgeModel) return
  
  const oldRelationType = edgeModel.properties?.relationType
  const newRelationType = getRelationTypeByLineType(selectedEdgeLineType.value)
  
  // 如果关联关系类型发生变化,需要同步更新节点关联关系
  if (oldRelationType && oldRelationType !== newRelationType) {
    // 先移除旧的关联关系
    removeEdgeFromNodeRelation(edgeModel.sourceNodeId, edgeModel.targetNodeId, oldRelationType)
    // 再添加新的关联关系
    syncEdgeToNodeRelation(edgeModel.sourceNodeId, edgeModel.targetNodeId, newRelationType)
  }
  
  const relationLabel = getRelationTypeLabel(newRelationType)
  
  lf.setProperties(selectedEdgeId.value, {
    text: selectedEdgeText.value || relationLabel, // 如果没有自定义文本,使用关系标签
    lineType: selectedEdgeLineType.value,
    relationType: newRelationType,
    relationLabel: relationLabel,
    textColor: selectedEdgeTextColor.value,
    textSize: selectedEdgeTextSize.value,
    arrowType: selectedArrowType.value,
    startMarker: selectedStartMarker.value
  })
  
  // 同步连接线文本
  const displayText = selectedEdgeText.value || relationLabel
  lf.updateText(selectedEdgeId.value, displayText)
  
  edgeDialogVisible.value = false
  ElMessage.success('已更新连接线样式和关联关系')
}

onMounted(() => {
  lf = new LogicFlow({
    container: lfContainerRef.value,
    grid: true,
    keyboard: {
      enabled: true
    },
    snapline: true,
    edgeType: 'custom-edge',  // 使用自定义连接线
    plugins: [Menu, SelectionSelect, Control, MiniMap, Snapshot]
  })

  // 注册自定义矩形节点
  lf.register({
    type: 'custom-rect',
    view: CustomRectNode,
    model: CustomRectNodeModel,
  })

  // 注册自定义连接线 - 如果CustomEdge有问题,可以切换到SimpleCustomEdge
  lf.register({
    type: 'custom-edge',
    view: CustomEdge, // 如果有问题,改为 SimpleCustomEdge
    model: CustomEdgeModel,
  })

  console.log(lf, '__+++')

  // 监听点击节点,打开编辑弹窗
  lf.on('node:click', ({ data }) => {
    selectedNodeId.value = data.id
    selectedNodeText.value = data.text?.value || ''
    selectedNodeType.value = data.properties?.nodeType || ''
    selectedNodeDescription.value = data.properties?.description || ''
    
    // 加载现有关联关系
    const existingRelations = nodeRelations.value.get(data.id) || []
    selectedNodeRelations.value = existingRelations.map(r => ({...r})) // 深拷贝
    
    nodeDialogVisible.value = true
  })

  // 监听点击连接线,打开编辑弹窗
  lf.on('edge:click', ({ data }) => {
    selectedEdgeId.value = data.id
    selectedEdgeText.value = data.text?.value || ''
    selectedEdgeLineType.value = data.properties?.lineType || 'solid'
    selectedEdgeTextColor.value = data.properties?.textColor || '#374151'
    selectedEdgeTextSize.value = data.properties?.textSize || 12
    selectedArrowType.value = data.properties?.arrowType || 'default'
    selectedStartMarker.value = data.properties?.startMarker || 'none'
    edgeDialogVisible.value = true
  })

  // 双击连接线添加文本
  lf.on('edge:dbclick', ({ data }) => {
    selectedEdgeId.value = data.id
    selectedEdgeText.value = data.text?.value || ''
    selectedEdgeLineType.value = data.properties?.lineType || 'solid'
    selectedEdgeTextColor.value = data.properties?.textColor || '#374151'
    selectedEdgeTextSize.value = data.properties?.textSize || 12
    selectedArrowType.value = data.properties?.arrowType || 'default'
    selectedStartMarker.value = data.properties?.startMarker || 'none'
    edgeDialogVisible.value = true
  })

  // 监听边创建,自动设置当前选择的线条类型和关联关系
  lf.on('edge:add', ({ data }) => {
    const lineType = selectedEdgeType.value
    const relationType = getRelationTypeByLineType(lineType)
    const relationLabel = getRelationTypeLabel(relationType)
    
    // 为新创建的边设置线条类型、关联关系和样式
    lf.setProperties(data.id, {
      lineType: lineType,
      relationType: relationType,
      relationLabel: relationLabel,
      textColor: getRelationColor(relationType),
      textSize: 12,
      arrowType: 'filled'
    })
    
    // 设置连线文本为关联关系标签
    lf.updateText(data.id, relationLabel)
    
    // 自动同步到源节点的关联关系配置
    syncEdgeToNodeRelation(data.sourceNodeId, data.targetNodeId, relationType)
  })

  // 监听边删除事件,同步删除节点关联关系
  lf.on('edge:delete', ({ data }) => {
    const properties = data.properties || {}
    const relationType = properties.relationType
    
    if (relationType && data.sourceNodeId && data.targetNodeId) {
      removeEdgeFromNodeRelation(data.sourceNodeId, data.targetNodeId, relationType)
    }
  })

  // 键盘删除事件
  lf.on('keydown', ({ data }) => {
    if (data.key === 'Delete' || data.key === 'Backspace') {
      const selectedElements = lf.getSelectElements()
      if (selectedElements.nodes.length > 0 || selectedElements.edges.length > 0) {
        lf.deleteSelectElements()
        ElMessage.success('已删除选中元素')
      }
    }
  })

  // 右键菜单删除
  lf.on('edge:contextmenu', ({ data }) => {
    lf.showContextMenu({
      type: 'edge',
      data: data,
      callback: (type, data) => {
        if (type === 'delete') {
          lf.deleteEdge(data.id)
          ElMessage.success('已删除连接线')
        }
      }
    })
  })

  lf.on('node:contextmenu', ({ data }) => {
    lf.showContextMenu({
      type: 'node',
      data: data,
      callback: (type, data) => {
        if (type === 'delete') {
          lf.deleteNode(data.id)
          ElMessage.success('已删除节点')
        }
      }
    })
  })

  // 初始化一个示例
  lf.render({
    nodes: [
      { id: 'n1', type: 'custom-rect', x: 200, y: 80, text: '开始' },
      { id: 'n2', type: 'custom-rect', x: 420, y: 80, text: '处理' },
      { id: 'n3', type: 'custom-rect', x: 640, y: 80, text: '结束' },
      { id: 'n4', type: 'custom-rect', x: 420, y: 180, text: '工具' },
    ],
    edges: [
      { 
        id: 'e1', 
        sourceNodeId: 'n1', 
        targetNodeId: 'n2', 
        text: '包含',
        properties: {
          lineType: 'solid',
          relationType: 'contains',
          relationLabel: '包含',
          textColor: '#3b82f6',
          textSize: 14,
          arrowType: 'filled',
          startMarker: 'filled-circle'
        }
      },
      { 
        id: 'e2', 
        sourceNodeId: 'n2', 
        targetNodeId: 'n3', 
        text: '关联',
        properties: {
          lineType: 'dashed',
          relationType: 'associates',
          relationLabel: '关联',
          textColor: '#10b981',
          textSize: 14,
          arrowType: 'filled',
          startMarker: 'hollow-circle'
        }
      },
      { 
        id: 'e3', 
        sourceNodeId: 'n2', 
        targetNodeId: 'n4', 
        text: '使用',
        properties: {
          lineType: 'dotted',
          relationType: 'uses',
          relationLabel: '使用',
          textColor: '#f59e0b',
          textSize: 14,
          arrowType: 'filled',
          startMarker: 'none'
        }
      }
    ]
  })
})

onBeforeUnmount(() => {
  if (lf) {
    lf.destroy()
    lf = null
  }
})
</script>

<style scoped>
.logicflow-page {
  display: flex;
  height: calc(100vh - 120px);
  padding: 12px;
  box-sizing: border-box;
}
.sidebar {
  width: 200px;
  border-right: 1px solid #eee;
  padding-right: 12px;
}
.palette-title {
  font-weight: 600;
  margin-bottom: 8px;
}
.palette-item {
  background: #f6f7fb;
  border: 1px dashed #c0c4cc;
  padding: 8px 10px;
  margin-bottom: 8px;
  cursor: grab;
  border-radius: 4px;
  user-select: none;
}

.help-text {
  font-size: 12px;
  color: #666;
  line-height: 1.5;
}

.help-text p {
  margin: 4px 0;
}
.canvas-wrap {
  flex: 1;
  padding-left: 12px;
}
.lf-container {
  width: 100%;
  height: 100%;
  background: #fff;
  border: 1px solid #eee;
  border-radius: 4px;
}

/* 边类型选择器样式 */
.edge-type-selector {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 12px;
}

.edge-type-item {
  display: flex;
  align-items: center;
  padding: 8px 10px;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s;
  background: #f9fafb;
}

.edge-type-item:hover {
  border-color: #3b82f6;
  background: #eff6ff;
}

.edge-type-item.active {
  border-color: #3b82f6;
  background: #dbeafe;
}

.edge-preview {
  width: 30px;
  height: 2px;
  margin-right: 10px;
  position: relative;
}

.solid-line {
  background: #3b82f6;
}

.dashed-line {
  background: linear-gradient(to right, #10b981 0%, #10b981 50%, transparent 50%, transparent 100%);
  background-size: 8px 2px;
}

.dotted-line {
  background: linear-gradient(to right, #f59e0b 0%, #f59e0b 25%, transparent 25%, transparent 50%, #f59e0b 50%, #f59e0b 75%, transparent 75%, transparent 100%);
  background-size: 4px 2px;
}

/* 颜色选择器样式 */
.color-picker-container {
  display: flex;
  align-items: center;
  gap: 12px;
}

.color-preview {
  font-size: 14px;
  font-weight: 500;
  padding: 4px 8px;
  border-radius: 4px;
  background: #f5f5f5;
}

/* LogicFlow 连线文字样式优化 */
:deep(.lf-edge-text) {
  pointer-events: none;
}

/* 隐藏默认的文字背景矩形 */
:deep(.lf-edge-text rect),
:deep(.lf-edge-text-bg),
:deep(.lf-edge .lf-edge-text-bg) {
  fill: transparent !important;
  stroke: transparent !important;
  opacity: 0 !important;
  display: none !important;
}

/* 确保自定义文字样式 */
:deep(.lf-edge text) {
  text-anchor: middle !important;
  dominant-baseline: central !important;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}

/* 增强文字可读性 */
:deep(.lf-edge text) {
  filter: drop-shadow(1px 1px 1px rgba(255, 255, 255, 0.8)) 
          drop-shadow(-1px -1px 1px rgba(255, 255, 255, 0.8));
}

/* 关联关系配置样式 */
.relation-config {
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  padding: 12px;
  background-color: #fafbfc;
}

.relation-list {
  max-height: 200px;
  overflow-y: auto;
}

.relation-item {
  display: flex;
  align-items: center;
  margin-bottom: 8px;
  padding: 8px;
  background: white;
  border-radius: 4px;
  border: 1px solid #e5e7eb;
}

.relation-item:last-child {
  margin-bottom: 0;
}

.existing-relations {
  max-height: 100px;
  overflow-y: auto;
  padding: 8px;
  background-color: #f9fafb;
  border-radius: 4px;
  border: 1px solid #e5e7eb;
}

/* 关系查看对话框样式 */
.relation-view {
  padding: 8px;
}

.relation-header h4 {
  margin: 0 0 16px 0;
  color: #374151;
  font-weight: 600;
}

.relation-tree {
  margin-bottom: 20px;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  padding: 12px;
  background-color: #fafbfc;
}

.relation-tree-view {
  background: transparent;
}

.relation-node {
  display: flex;
  align-items: center;
  gap: 8px;
}

.relation-node .el-icon {
  color: #6b7280;
}

.relation-node .el-tag {
  margin-left: 8px;
}

/* 关系连线的不同颜色样式 */
:deep(.lf-edge[data-relation-type="depends"]) .lf-edge-path {
  stroke: #f59e0b;
}

:deep(.lf-edge[data-relation-type="contains"]) .lf-edge-path {
  stroke: #10b981;
}

:deep(.lf-edge[data-relation-type="triggers"]) .lf-edge-path {
  stroke: #ef4444;
}

:deep(.lf-edge[data-relation-type="inherits"]) .lf-edge-path {
  stroke: #3b82f6;
}

:deep(.lf-edge[data-relation-type="references"]) .lf-edge-path {
  stroke: #8b5cf6;
}

:deep(.lf-edge[data-relation-type="calls"]) .lf-edge-path {
  stroke: #374151;
}
</style>

  

posted @ 2025-09-15 13:56  Action_swt  阅读(6)  评论(0)    收藏  举报