Quellcode durchsuchen

竖型排列后重新sortNode,解决蛇形排列问题。pannel添加保存按钮。右侧信息编辑。

jiaxiaoqiang vor 3 Monaten
Ursprung
Commit
f349f5f476

+ 1 - 1
index.html

@@ -12,7 +12,7 @@
       name="keywords"
       content="vue,element-plus,typescript,vue-element-admin,vue3-element-admin"
     />
-    <title>综合管控采集平台</title>
+    <title>自动化测试管理系统</title>
   </head>
 
   <body>

+ 1 - 1
package.json

@@ -1,5 +1,5 @@
 {
-  "name": "成飞自动测试系统",
+  "name": "自动测试管理系统",
   "version": "1.0.0",
   "private": true,
   "type": "module",

+ 6 - 0
src/assets/icons/save.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
+  <path fill-rule="evenodd" d="M1 2.9C1 1.85 1.85 1 2.9 1h10.2c1.05 0 1.9.85 1.9 1.9v10.2a1.9 1.9 0 0 1-1.9 1.9H2.9A1.9 1.9 0 0 1 1 13.1V2.9ZM3 3v10h10V3H3Z" clip-rule="evenodd"/>
+  <path fill-rule="evenodd" d="M4 2a1 1 0 0 1 1-1h5.667a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V2Zm2 1v4h3.667V3H6Z" clip-rule="evenodd"/>
+  <path fill-rule="evenodd" d="M8.667 3.333a1 1 0 0 1 1 1v1.334a1 1 0 0 1-2 0V4.333a1 1 0 0 1 1-1Z" clip-rule="evenodd"/>
+  <path d="M3.664 2h8.334"/>
+</svg>

+ 126 - 96
src/components/hjflow/src/hjflow/index.vue

@@ -1,5 +1,14 @@
 <script setup lang="ts">
-import {ref, onMounted, InjectionKey, Ref, provide, inject, nextTick, markRaw} from 'vue';
+import {
+  ref,
+  onMounted,
+  InjectionKey,
+  Ref,
+  provide,
+  inject,
+  nextTick,
+  markRaw,
+} from "vue";
 import {
   HJMethodName,
   HJNodeData,
@@ -7,185 +16,206 @@ import {
   nodeTypes,
   edgeTypes,
   HJMethodProvideName,
-  CurrentHeaderOperationProvideName, CurrentSelectedEdgeProvideName, HJFlowProps
+  CurrentHeaderOperationProvideName,
+  CurrentSelectedEdgeProvideName,
+  HJFlowProps,
 } from "../types/comTypes";
 import Panel from "../panel/index.vue";
-import CommonEdge from '../edges/commonEdge.vue'
+import CommonEdge from "../edges/commonEdge.vue";
 import DropzoneBackground from "../background/DropzoneBackground.vue";
-import {useVueFlow, VueFlow, Edge} from "@vue-flow/core";
+import { useVueFlow, VueFlow, Edge } from "@vue-flow/core";
 import useDragAndDrop from "../hooks/useDnD";
-import {useLayout} from '../utils/useLayout'
-import {useSnakeLayoutHook} from "../utils/useSnake";
-import {ElMessage} from "element-plus";
-
+import { sortNodesByPosition, useLayout } from "../utils/useLayout";
+import { useSnakeLayoutHook } from "../hooks/snakeHooks";
+import { ElMessage } from "element-plus";
 
 defineOptions({
-  name: 'HJFlow'
-})
+  name: "HJFlow",
+});
 
-const {onConnect, addEdges, fitView, updateNode, findNode} = useVueFlow()
-const {onDragOver, onDrop, onDragLeave, isDragOver} = useDragAndDrop()
-
-onConnect(addEdges)
+const { onConnect, addEdges, fitView, updateNode, findNode } = useVueFlow();
+const { onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop();
 
+onConnect(addEdges);
 
 // ========== 外面传进来的参数  ===========
-const nodes = defineModel<HJNodeData[]>('nodesData')
-const edges = defineModel('edgesData')
-const props = defineProps<HJFlowProps>()
-
+const nodes = defineModel<HJNodeData[]>("nodesData");
+const edges = defineModel("edgesData");
+const props = defineProps<HJFlowProps>();
 
 // ==========vueflow 本身的方法操作 ===========
-const doubleClick = ({node}) => {
+const doubleClick = ({ node }) => {
   // 如果通过双击弹出额外信息编辑是可以拿到jsonData的,但是如果在自定义node中是拿不到的,非要在自定义中拿就得把额外的信息放到data中。
   // 还要看 是否要根据 dragData中的数据生成具体 avue页面。 之后可以尝试通过useVueFlow的find能否找到
-  console.log('doubleClick', node);
-}
-
-const currentClickEdgeData = ref<Edge | null>(null)
-provide(CurrentSelectedEdgeProvideName, currentClickEdgeData)
-const onClickEdge = ({edge}) => {
-  currentClickEdgeData.value = JSON.parse(JSON.stringify(edge))
-}
-
-
+  console.log("doubleClick", node);
+};
 
+const currentClickEdgeData = ref<Edge | null>(null);
+provide(CurrentSelectedEdgeProvideName, currentClickEdgeData);
+const onClickEdge = ({ edge }) => {
+  currentClickEdgeData.value = JSON.parse(JSON.stringify(edge));
+};
 
 // ======= Emits =======
 const emits = defineEmits<{
-  hjMethod: [name: HJMethodName, node: HJNodeData]
-}>()
+  hjMethod: [name: HJMethodName, node: HJNodeData];
+}>();
 
 // 提供一个孙子组件里面(比如自定义node里面) 调用本组件的方法
 // ======= Node操作 =======
-const currentHeaderOperationNodeData = ref<HJNodeData | null >(null) // 当前选中的header操作节点
-provide(CurrentHeaderOperationProvideName, currentHeaderOperationNodeData)
-provide(HJMethodProvideName, ( name: HJMethodName,node: HJNodeData) => {
-  currentHeaderOperationNodeData.value = JSON.parse(JSON.stringify(node)) // 复制一份数据
-  console.log('provide', name, node);
-  emits('hjMethod', name, node)
-})
-
-
+const currentHeaderOperationNodeData = ref<HJNodeData | null>(null); // 当前选中的header操作节点
+provide(CurrentHeaderOperationProvideName, currentHeaderOperationNodeData);
+provide(HJMethodProvideName, (name: HJMethodName, node: HJNodeData) => {
+  currentHeaderOperationNodeData.value = JSON.parse(JSON.stringify(node)); // 复制一份数据
+  console.log("provide", name, node);
+  emits("hjMethod", name, node);
+});
 
 ///// === 数据历史纪录 用于回退或者重置按钮 === /////
-let originData: any = null
-let historyList: any[]  =  []
-
+let originData: any = null;
+let historyList: any[] = [];
 
 ///// === 所有的node 和edge change 调用的地方 === /////
 const onFlowNodesChange = (data: any) => {
   if (
-      data.length > 0 &&
-      data[0].type &&
-      (data[0].type === "add" || data[0].type === "remove")
+    data.length > 0 &&
+    data[0].type &&
+    (data[0].type === "add" || data[0].type === "remove")
   ) {
-
-    historyList.unshift(JSON.stringify({nodes: markRaw(nodes.value), edges: markRaw(edges.value)}))
-    console.log('onFlowNodesChange historyList', historyList)
+    historyList.unshift(
+      JSON.stringify({
+        nodes: markRaw(nodes.value),
+        edges: markRaw(edges.value),
+      })
+    );
+    console.log("onFlowNodesChange historyList", historyList);
   }
-}
+};
 const onFlowEdgesChange = (data: any) => {
   if (
-      data.length > 0 &&
-      data[0].type &&
-      (data[0].type === "add" || data[0].type === "remove")
+    data.length > 0 &&
+    data[0].type &&
+    (data[0].type === "add" || data[0].type === "remove")
   ) {
-    historyList.unshift(JSON.stringify({nodes: markRaw(nodes.value), edges: markRaw(edges.value)}))
+    historyList.unshift(
+      JSON.stringify({
+        nodes: markRaw(nodes.value),
+        edges: markRaw(edges.value),
+      })
+    );
   }
-}
+};
 ///// === 所有的node 和edge change 调用的地方 === /////
 
 ///// === 初始化   === /////
 const initFlow = (nodes: HJNodeData[], edges: any[]) => {
-  originData = JSON.stringify({nodes, edges}) // 复制一份数据
-  historyList = [] // 初始化历史纪录
-}
+  originData = JSON.stringify({ nodes, edges }); // 复制一份数据
+  historyList = []; // 初始化历史纪录
+};
 
 // 为父组件提供更新node中的data数据的操作 一般只更新information就行了 更新一个node的值
 const updateNodeData = (node: HJNodeData) => {
-  console.log('updateNodeData', node)
+  console.log("updateNodeData", node);
 
   if (node.id) {
-    const findNodeData = findNode(node.id) as HJNodeData
-    findNodeData.data.information = node.data.information
-    console.log('findNodeData', findNodeData)
+    const findNodeData = findNode(node.id) as HJNodeData;
+    findNodeData.data.information = node.data.information;
+    console.log("findNodeData", findNodeData);
     // updateNode(node.id, node)
   }
-}
+};
 
 defineExpose({
   updateNodeData,
-  initFlow
-})
+  initFlow,
+});
 
 // ======= Panel =======
 // 测试layout  点击切换布局时候的操作
-const {layout} = useLayout()
+const { layout } = useLayout();
 
-async function layoutGraph(direction: 'LR' | 'TB') {
-  nodes.value = layout(nodes.value, edges.value, direction)
+async function layoutGraph(direction: "LR" | "TB") {
+  nodes.value = layout(nodes.value, edges.value, direction);
+
+  console.log("排序完", nodes.value);
 
   nextTick(() => {
-    fitView()
-  })
+    fitView();
+    nodes.value = sortNodesByPosition(nodes.value);
+  });
 }
 
 const pannelOperations = {
   addNode: (nodeType: HJNodeType) => {
-    console.log('addNode', nodeType);
+    console.log("addNode", nodeType);
   },
-  layout: (direction: 'LR' | 'TB') => {
-    layoutGraph(direction)
+  layout: (direction: "LR" | "TB") => {
+    layoutGraph(direction);
   },
   toSnake: () => {
-    console.log('toSnake');
-    nodes.value = useSnakeLayoutHook(nodes.value, edges.value)
+    console.log("toSnake");
+    nodes.value = useSnakeLayoutHook(nodes.value, edges.value);
+    nextTick(() => {
+      fitView();
+    });
   },
   reset: () => {
-    console.log('reset');
+    console.log("reset");
     if (originData && JSON.parse(originData)) {
-      let data = JSON.parse(originData)
+      let data = JSON.parse(originData);
       nodes.value = data.nodes;
       edges.value = data.edges;
-      historyList = []
+      historyList = [];
     }
   },
   back: () => {
-    console.log('back');
+    console.log("back");
     let firstHistory = historyList.shift();
     if (firstHistory) {
-      let historyData = JSON.parse(firstHistory)
+      let historyData = JSON.parse(firstHistory);
       nodes.value = historyData.nodes;
       edges.value = historyData.edges;
     } else {
       ElMessage.warning("已是初始线路");
     }
-  }
-}
+  },
+  saveTemplate: () => {
+    emits("hjMethod", HJMethodName.SaveTemplate, nodes.value[0]);
+  },
+};
 // ======= Panel ======= ↑↑↑↑↑↑↑↑↑↑↑↑↑↑
-
 </script>
 
 <template>
-  <VueFlow  v-model:nodes="nodes" v-model:edges="edges" :node-types="nodeTypes" :edgeTypes="edgeTypes"
-           @dragover="onDragOver" @dragleave="onDragLeave" @drop="onDrop" @nodeDoubleClick="doubleClick" @edge-click="onClickEdge" @nodes-change="onFlowNodesChange" @edges-change="onFlowEdgesChange">
+  <VueFlow
+    v-model:nodes="nodes"
+    v-model:edges="edges"
+    :node-types="nodeTypes"
+    :edgeTypes="edgeTypes"
+    @dragover="onDragOver"
+    @dragleave="onDragLeave"
+    @drop="onDrop"
+    @nodeDoubleClick="doubleClick"
+    @edge-click="onClickEdge"
+    @nodes-change="onFlowNodesChange"
+    @edges-change="onFlowEdgesChange"
+  >
     <DropzoneBackground
-        :style="{
-            backgroundColor: isDragOver ? '#e7f3ff' : 'transparent',
-            transition: 'background-color 0.2s ease',
-          }"
+      :style="{
+        backgroundColor: isDragOver ? '#e7f3ff' : 'transparent',
+        transition: 'background-color 0.2s ease',
+      }"
     >
-<!--      <p v-if="isDragOver">Drop here</p>-->
+      <!--      <p v-if="isDragOver">Drop here</p>-->
     </DropzoneBackground>
 
     <!--        右面上工具栏操作 -->
-    <Panel v-if="showPanel" v-on="pannelOperations" :fun-names="panelBtns"></Panel>
-
-
+    <Panel
+      v-if="showPanel"
+      v-on="pannelOperations"
+      :fun-names="panelBtns"
+    ></Panel>
   </VueFlow>
 </template>
 
-<style scoped lang="less">
-
-</style>
+<style scoped lang="less"></style>

+ 113 - 0
src/components/hjflow/src/hooks/snakeHooks.ts

@@ -0,0 +1,113 @@
+export const isStraightFlow = (nodes: any[], edges: any[]) => {
+  let isSerial = false;
+  let message = "非标准直线型流程图";
+
+  // 如果nodes的数量小于1 说明没有节点 直接返回false
+  if (nodes && nodes.length < 2) {
+    return [isSerial, "节点数量少于2"];
+  }
+
+  // 如果edges的数量小于1 说明没有边 直接返回false
+  if (edges && edges.length < 1) {
+    return [isSerial, "边数量少于1"];
+  }
+
+  // 如果nodes的数量不等于edges的数量+1 说明没有完全连线或者是不是直线
+  if (nodes?.length !== edges?.length + 1) {
+    return [isSerial, message];
+  }
+
+  // 每一个边的源节点id组成一个set,如果set的数量和edges的数量不相等,说明不是标准直线型流程图
+  let sourceNodeIdsSet = new Set(
+    edges.map((edge) => {
+      return edge.source;
+    })
+  );
+  let targetNodeIdsSet = new Set(
+    edges.map((edge) => {
+      return edge.target;
+    })
+  );
+  if (
+    sourceNodeIdsSet.size !== edges.length ||
+    targetNodeIdsSet.size !== edges.length
+  ) {
+    return [isSerial, message];
+  }
+
+  // 能到这里说明是直线
+  return [true, "是直线"];
+};
+
+// 按照edges的顺序重新给nodes排序
+export const sortNodesByEdges = (nodes: any[], edges: any[]) => {
+  // 先获取所有边的node的id
+  let edgeIds = [edges[0].sourceNode.id];
+  for (let i = 1; i < edges.length; i++) {
+    edgeIds.push(edges[i].targetNode.id);
+  }
+
+  // 创建一个ids到对应的索引的映射
+  const idIndexMap = edgeIds.reduce((map, id, index) => {
+    map[id] = index;
+    return map;
+  }, {});
+
+  // 根据ids数组中的顺序对对象数组进行排序
+  return nodes.sort((a, b) => {
+    return idIndexMap[a.id] - idIndexMap[b.id];
+  });
+};
+
+export const useSnakeLayoutHook = (
+  nodes: any[],
+  edges: any[],
+  spacingY: number = 150,
+  spacingX: number = 230
+) => {
+  let [isStraight, msg] = isStraightFlow(nodes, edges);
+  if (!isStraight) {
+    ElMessage.warning(msg);
+    return nodes; //这里要返回nodes, 因为外面要用到
+  }
+
+  console.log("dddd", nodes, edges);
+
+  // nodes = sortNodesByEdges(nodes, edges);
+
+  const snakeNodes = [];
+
+  // 内边距
+  const paddingTop = 35;
+  const paddingLeft = 15;
+
+  // 这是每一个个列的数量  列数量
+  const nodesPerColumn = 6; //Math.floor(canvasHeight / spacingY);
+
+  // console.log(canvasHeight, canvasHeight / spacingY);
+
+  nodes.forEach((node, index) => {
+    const col = Math.floor(index / nodesPerColumn);
+    const row = index % nodesPerColumn;
+
+    // 根据行号计算位置,奇数列从下到上,偶数列从上到下
+    const adjustedRow = row;
+    // const adjustedRow = col % 2 === 0 ? row : nodesPerColumn - 1 - row;
+
+    node.position = {
+      x: col * spacingX + paddingLeft,
+      y: adjustedRow * spacingY + paddingTop,
+    };
+
+    // 更新奇数列的 handle 处理
+    // if (col % 2 !== 0) {
+    //   // 奇数列的节点调整 handle 位置
+    //   node.data.handleDirection = 'toTop'; //如果是第二列 也就是奇数列 调整箭头向上
+    // } else {
+    //   node.data.handleDirection = 'toDown';
+    // }
+
+    snakeNodes.push(node);
+  });
+  return snakeNodes;
+};

+ 46 - 17
src/components/hjflow/src/panel/index.vue

@@ -1,33 +1,62 @@
 <script setup lang="ts">
-import { Panel } from '@vue-flow/core';
-import Back from './btns/Back.vue';
-import AddNode from '../panel/btns/add-node.vue';
-import LayoutLR from './btns/layout-LR.vue'
-import LayoutTB from './btns/layout-TB.vue'
-import SnakeBtn from './btns/snake.vue'
-import ResetBtn from './btns/reset.vue'
-import {HJPanelFunName} from "../types/comTypes";
+import { Panel } from "@vue-flow/core";
+import Back from "./btns/Back.vue";
+import AddNode from "../panel/btns/add-node.vue";
+import LayoutLR from "./btns/layout-LR.vue";
+import LayoutTB from "./btns/layout-TB.vue";
+import SnakeBtn from "./btns/snake.vue";
+import ResetBtn from "./btns/reset.vue";
+import { HJPanelFunName } from "../types/comTypes";
 
 // 定义一个props,参数是数组,而且数组里面的值只能是 HJPannelFunName 的值
 const props = defineProps<{
   funNames: HJPanelFunName[];
-}>()
+}>();
 
-const emits = defineEmits(['addNode', 'reset', 'layout', 'toSnake', 'back']);
+const emits = defineEmits([
+  "addNode",
+  "reset",
+  "layout",
+  "toSnake",
+  "back",
+  "saveTemplate",
+]);
 
 const onAddNode = (type: string) => {
-  emits('addNode', type);
+  emits("addNode", type);
 };
 </script>
 
 <template>
   <Panel position="top-right" class="process-panel">
-    <Back @click="() => emits('back')"  v-if="props.funNames.includes('back')"/>
-    <add-node @addNode="onAddNode" v-if="props.funNames.includes('addNode')"></add-node>
-    <LayoutLR @click="() => emits('layout', 'LR')" v-if="props.funNames.includes('layoutLR')"/>
-    <LayoutTB @click="() => emits('layout', 'TB')" v-if="props.funNames.includes('layoutTB')"/>
-    <SnakeBtn @click="() => emits('toSnake')" v-if="props.funNames.includes('toSnake')"/>
-    <ResetBtn @click="() => emits('reset')" v-if="props.funNames.includes('reset')"/>
+    <el-button
+      type="primary"
+      style="width: 80px"
+      @click="() => emits('saveTemplate')"
+      v-if="props.funNames.includes('saveTemplate')"
+      >保存模版</el-button
+    >
+    <Back @click="() => emits('back')" v-if="props.funNames.includes('back')" />
+    <add-node
+      @addNode="onAddNode"
+      v-if="props.funNames.includes('addNode')"
+    ></add-node>
+    <LayoutLR
+      @click="() => emits('layout', 'LR')"
+      v-if="props.funNames.includes('layoutLR')"
+    />
+    <LayoutTB
+      @click="() => emits('layout', 'TB')"
+      v-if="props.funNames.includes('layoutTB')"
+    />
+    <SnakeBtn
+      @click="() => emits('toSnake')"
+      v-if="props.funNames.includes('toSnake')"
+    />
+    <ResetBtn
+      @click="() => emits('reset')"
+      v-if="props.funNames.includes('reset')"
+    />
   </Panel>
 </template>
 

+ 56 - 41
src/components/hjflow/src/types/comTypes.ts

@@ -1,31 +1,30 @@
 // 这里是 vue-flow 自定义的组件的types集合
-import { markRaw } from 'vue';
-import type { Node, Edge } from '@vue-flow/core';
+import { markRaw } from "vue";
+import type { Node, Edge } from "@vue-flow/core";
 
-import CommonEdge from '../edges/commonEdge.vue'
+import CommonEdge from "../edges/commonEdge.vue";
 
-import CommonNode from '../nodes/CommonNode.vue';
-import NestNode from '../nodes/NestNode.vue';
-import NumberInputNode from '../nodes/NumberInputNode.vue';
-import NumberFunctionNode from '../nodes/NumberFunNode.vue';
-import NumberResultNode from '../nodes/NumberResultNode.vue';
+import CommonNode from "../nodes/CommonNode.vue";
+import NestNode from "../nodes/NestNode.vue";
+import NumberInputNode from "../nodes/NumberInputNode.vue";
+import NumberFunctionNode from "../nodes/NumberFunNode.vue";
+import NumberResultNode from "../nodes/NumberResultNode.vue";
 
-import UniversalNode from '../nodes/universal/UniversalNode.vue';
+import UniversalNode from "../nodes/universal/UniversalNode.vue";
 // 工作流相关
-import WorkFlowNode from '../nodes/workflow/WorkFlowNode.vue';
+import WorkFlowNode from "../nodes/workflow/WorkFlowNode.vue";
 
-
-export  enum HJNodeType {
-  custom = 'custom',
-  nest = 'nest',
+export enum HJNodeType {
+  custom = "custom",
+  nest = "nest",
   //   算数相关
-  numberInput = 'number-input',
-  numberFunction = 'number-function',
-  numberResult = 'number-result',
-//   工作流相关
-  workFlow = 'work-flow',
-//   公共 通用 上面是header 下面是content
-  universal = 'universal',
+  numberInput = "number-input",
+  numberFunction = "number-function",
+  numberResult = "number-result",
+  //   工作流相关
+  workFlow = "work-flow",
+  //   公共 通用 上面是header 下面是content
+  universal = "universal",
 }
 
 export const nodeTypes = {
@@ -42,61 +41,77 @@ export const nodeTypes = {
 
 export const edgeTypes = {
   default: markRaw(CommonEdge),
-}
+};
 
 // 用于Handle
-export  enum HJPosition {
-  Left = 'left',
-  Top = 'top',
-  Right = 'right',
-  Bottom = 'bottom',
+export enum HJPosition {
+  Left = "left",
+  Top = "top",
+  Right = "right",
+  Bottom = "bottom",
 }
 
 export interface HJHandle {
-  type: 'source' | 'target';
+  type: "source" | "target";
   position: HJPosition;
   style?: any; //如果同一侧有多个handle,可以用style调整位置
   other?: string; //预留字段,以后看可能不同的node类型需要不同的handle样式,通过这个引入不同的Handlevue 文件
 }
 
 // 用于Node 类型中的data,方便拓展选中 拖拽等数据
-export  interface HJNodeDataModel {
+export interface HJNodeDataModel {
   label: string;
   isSelected?: boolean;
   isDragging?: boolean;
   handles?: HJHandle[];
-  information?: any  //点击node节点 弹出drawer编辑的数据
+  information?: any; //点击node节点 弹出drawer编辑的数据
 }
 
 // 用于<VueFlow>组件的nodes属性的类型定义 从外部导入数据需要使用
-export  interface HJNodeData extends Partial<Node> {
+export interface HJNodeData extends Partial<Node> {
   jsonData?: string; //经实验发现nodeTypes这种方法无法传递 Node之外的key,给props扩展也没有用, 也就是自定义的nodes里面拿不到这个key
   data: HJNodeDataModel;
 }
 
 // 这个类型用于自定义node节点内部的数据结构,忽略一些数据能提升渲染时候的性能,
-export  type HJInterNodeData = Omit<HJNodeData, 'jsonData'>;
-
+export type HJInterNodeData = Omit<HJNodeData, "jsonData">;
 
 // 在顶层组件订一个方法,通过provide方法提供给其他组件使用
-export const HJMethodProvideName = 'hjMethodProvideName'
-export const CurrentHeaderOperationProvideName = 'hjCurrentHeaderOperationProvideName'
-export const CurrentSelectedEdgeProvideName = 'hjCurrentSelectedEdgeProvideName'
+export const HJMethodProvideName = "hjMethodProvideName";
+export const CurrentHeaderOperationProvideName =
+  "hjCurrentHeaderOperationProvideName";
+export const CurrentSelectedEdgeProvideName =
+  "hjCurrentSelectedEdgeProvideName";
 export class HJMethodName {
-  static AddNode = 'hjAddNode'
-  static EditNode = 'hjEditNode'
+  static AddNode = "hjAddNode";
+  static EditNode = "hjEditNode";
+  static SaveTemplate = "hjSaveTemplate";
 }
 export type GrandparentMethod = (name: HJMethodName, param: HJNodeData) => void;
 
 // Panel相关 HJPanelFunName添加一个  就在数组种写一个
-export type HJPanelFunName = 'addNode' | 'reset' | 'layoutLR' | 'layoutTB' | 'toSnake' | 'back';
-export const HJPanelFunNameArray = ['addNode','reset', 'layoutLR', 'layoutTB', 'toSnake', 'back'];
+export type HJPanelFunName =
+  | "addNode"
+  | "reset"
+  | "layoutLR"
+  | "layoutTB"
+  | "toSnake"
+  | "back"
+  | "saveTemplate";
+export const HJPanelFunNameArray = [
+  "addNode",
+  "reset",
+  "layoutLR",
+  "layoutTB",
+  "toSnake",
+  "back",
+];
 
 // 外面会传给HJFlow的props
 export interface HJFlowProps {
   // Pannel相关
   panelBtns?: HJPanelFunName[]; //自定义面板按钮 默认空
-  showPanel?: boolean;  // 默认为false,不显示面板
+  showPanel?: boolean; // 默认为false,不显示面板
 }
 
 export interface HJFlowInstance {

+ 81 - 63
src/components/hjflow/src/utils/useLayout.ts

@@ -1,72 +1,90 @@
-import dagre from '@dagrejs/dagre'
-import { Position, useVueFlow, Node, Edge } from '@vue-flow/core'
-import { ref } from 'vue'
-import {HJNodeData} from "@/packages/components/flow/src/types/comTypes";
-
-
+import dagre from "@dagrejs/dagre";
+import { Position, useVueFlow, Node, Edge } from "@vue-flow/core";
+import { ref } from "vue";
+import { HJNodeData } from "@/packages/components/flow/src/types/comTypes";
 
 /**
  * Composable to run the layout algorithm on the graph.
  * It uses the `dagre` library to calculate the layout of the nodes and edges.
  */
 export function useLayout() {
-    const { findNode } = useVueFlow()
-
-    const graph = ref(new dagre.graphlib.Graph())
-
-    const previousDirection = ref('LR')
-
-    function layout(nodes: HJNodeData[], edges: Edge[], direction: 'LR' | 'TB' = 'LR') {
-        // we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there
-        const dagreGraph = new dagre.graphlib.Graph()
-
-        graph.value = dagreGraph
-
-        dagreGraph.setDefaultEdgeLabel(() => ({}))
-
-        const isHorizontal = direction === 'LR'
-        dagreGraph.setGraph({ rankdir: direction })
-
-        previousDirection.value = direction
-
-        for (const node of nodes) {
-            // if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type)
-            const graphNode: any = findNode(node.id)
-
-            dagreGraph.setNode(node?.id ?? "", { width: graphNode.dimensions.width || 150, height: graphNode.dimensions.height || 50 })
-        }
-
-        for (const edge of edges) {
-            dagreGraph.setEdge(edge.source, edge.target)
-        }
-
-        dagre.layout(dagreGraph)
-
-        // set nodes with updated positions
-        return nodes.map((node) => {
-            const nodeWithPosition = dagreGraph.node(node?.id ?? "")
-
-            return {
-                ...node,
-                targetPosition: isHorizontal ? Position.Left : Position.Top,
-                sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
-                position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
-                data: {
-                    ...node.data,
-                    handles: [
-                        {
-                            type: 'target',
-                            position: isHorizontal ? Position.Left : Position.Top,
-                        },
-                        {
-                            type:'source',
-                            position: isHorizontal ? Position.Right : Position.Bottom,
-                        }
-                    ]
-                }
-            }
-        })
+  const { findNode } = useVueFlow();
+
+  const graph = ref(new dagre.graphlib.Graph());
+
+  const previousDirection = ref("LR");
+
+  function layout(
+    nodes: HJNodeData[],
+    edges: Edge[],
+    direction: "LR" | "TB" = "LR"
+  ) {
+    // we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there
+    const dagreGraph = new dagre.graphlib.Graph();
+
+    graph.value = dagreGraph;
+
+    dagreGraph.setDefaultEdgeLabel(() => ({}));
+
+    const isHorizontal = direction === "LR";
+    dagreGraph.setGraph({ rankdir: direction });
+
+    previousDirection.value = direction;
+
+    for (const node of nodes) {
+      // if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type)
+      const graphNode: any = findNode(node.id);
+
+      dagreGraph.setNode(node?.id ?? "", {
+        width: graphNode.dimensions.width || 150,
+        height: graphNode.dimensions.height || 50,
+      });
+    }
+
+    for (const edge of edges) {
+      dagreGraph.setEdge(edge.source, edge.target);
     }
 
-    return { graph, layout, previousDirection }
+    dagre.layout(dagreGraph);
+
+    // set nodes with updated positions
+    return nodes.map((node) => {
+      const nodeWithPosition = dagreGraph.node(node?.id ?? "");
+
+      return {
+        ...node,
+        targetPosition: isHorizontal ? Position.Left : Position.Top,
+        sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
+        position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
+        data: {
+          ...node.data,
+          handles: [
+            {
+              type: "target",
+              position: isHorizontal ? Position.Left : Position.Top,
+            },
+            {
+              type: "source",
+              position: isHorizontal ? Position.Right : Position.Bottom,
+            },
+          ],
+        },
+      };
+    });
+  }
+
+  return { graph, layout, previousDirection };
+}
+
+// 竖形排列完之后, 根据node节点的上下位置进行重新排序
+export function sortNodesByPosition(nodes: HJNodeData[]): HJNodeData[] {
+  return nodes.sort((a, b) => {
+    if (a.position.y < b.position.y) {
+      return -1;
+    } else if (a.position.y > b.position.y) {
+      return 1;
+    } else {
+      return 0;
+    }
+  });
 }

+ 3 - 1
src/views/modules/project-config/com/function-col.vue

@@ -54,6 +54,7 @@ const test = (data) => {
 <style scoped lang="scss">
 .function-col {
   width: 200px;
+  flex-shrink: 0;
   height: calc(100vh - $main-header-height);
   background-color: $hj-black-2;
 }
@@ -66,8 +67,9 @@ const test = (data) => {
   flex-direction: column;
   justify-content: start;
   align-items: center;
-
+  max-height: 500px;
   width: 100%;
+  overflow-y: auto;
   background-color: $hj-black-2;
   padding: 12px 11px;
   .drag-item {

+ 1 - 0
src/views/modules/project-config/com/project-message.vue

@@ -66,6 +66,7 @@ const goGlobalConfig = () => {
 <style scoped lang="scss">
 .project-message-col {
   width: 200px;
+  flex-shrink: 0;
   height: calc(100vh - $main-header-height);
   background-color: $hj-black-2;
   border-right: 1px solid $hj-white-4;

+ 1 - 0
src/views/modules/project-config/com/titleHeader.vue

@@ -15,5 +15,6 @@
   color: $hj-white-1;
   line-height: 65px;
   text-align: left;
+  background-color: $hj-black-2;
 }
 </style>

+ 7 - 6
src/views/modules/project-config/configs/properites.ts

@@ -1,7 +1,4 @@
 import { Node } from "@vue-flow/core";
-import { HJNodeDataModel } from "@/components/hjflow/src/types/comTypes";
-
-// 功能块名称	属性名称	初始值	数值类型	下限	上限	输入/输出
 
 // 功能块类别
 interface FunctionTypeModel {
@@ -16,6 +13,7 @@ export enum ExcelTypeName {
 }
 // 功能块模型
 interface InformationModel {
+  nodeName?: string; // 节点名称  lingzong说要有
   // 保留类型的信息以便展示
   functionType: ExcelTypeName;
   functionTypeId: string;
@@ -24,7 +22,7 @@ interface InformationModel {
   properties?: InforPropertyModel[]; //编辑node节点的时候根据这个数组展示右侧的信息
 }
 // 功能块模型有哪些属性对应的类 属性名称	初始值	数值类型	下限	上限	输入/输出
-interface InforPropertyModel {
+export interface InforPropertyModel {
   proName: string;
   defaultValue?: string;
   proType?: "text" | "enum" | "number"; //text enum  number
@@ -34,9 +32,12 @@ interface InforPropertyModel {
   bindValue?: string; //这个是el-form 会绑定的值
 }
 
-type HJNodeData2 = Omit<HJNodeDataModel, "information"> & {
+interface HJNodeData2 {
+  label: string;
+  isSelected?: boolean;
+  isDragging?: boolean;
   information: InformationModel;
-};
+}
 
 // 这个类型里面的label 展示的是对应的FunctionTypeModel的名称, 以便在Node.vue中的header中展示
 export interface AutoTestNodeData extends Partial<Node> {

+ 93 - 15
src/views/modules/project-config/project-config.vue

@@ -1,6 +1,4 @@
 <script setup lang="ts">
-import ProjectMessage from "./com/project-message.vue";
-import FunctionModule from "./com/function-col.vue";
 import HJFlow from "@/components/hjflow/src/hjflow/index.vue";
 import {
   HJFlowInstance,
@@ -9,28 +7,36 @@ import {
 } from "@/components/hjflow/src/types/comTypes";
 import { Edge } from "@vue-flow/core";
 
+import ProjectMessage from "./com/project-message.vue";
+import FunctionModule from "./com/function-col.vue";
+import TitleHeader from "@/views/modules/project-config/com/titleHeader.vue";
+import { AutoTestNodeData, InforPropertyModel } from "./configs/properites";
+import type { FormInstance } from "element-plus";
+
 const flowRef = ref<HJFlowInstance>();
 const nodes = ref<HJNodeData[]>([]);
 const edges = ref<Edge[]>([]);
 // vue 的相关操作
 const onNodeOperation = (name: HJMethodName, node: HJNodeData): void => {
-  console.log("onNodeOperation", name, node);
-  // selectedNode.value = null;
-  // ruleFormRef.value && ruleFormRef.value.resetFields();
-  // selectedNode.value = JSON.parse(JSON.stringify(node));
-  console.log("selectedNode", selectedNode.value);
-  // if (selectedNode.value && selectedNode.value.data.information) {
-  //   formData.value = JSON.parse(
-  //     JSON.stringify(selectedNode.value.data.information)
-  //   );
-  // }
+  if (name === HJMethodName.EditNode) {
+    selectedNode.value = null;
+    selectedNode.value = JSON.parse(JSON.stringify(node));
+    infoVisible.value = true;
+  } else if (name === HJMethodName.SaveTemplate) {
+    console.log("保存模版数据");
+  }
 };
 
 // 右侧信息
 // ======= 信息展示 =======
 const infoVisible = ref(false);
-const selectedNode = ref<HJNodeData | null>(null); //双击的节点数据
-const closeInfo = () => {
+const selectedNode = ref<AutoTestNodeData | null>(null); //双击的节点数据
+const onSaveConfigs = () => {
+  console.log("onSaveConfigs", selectedNode.value);
+  flowRef.value &&
+    flowRef.value.updateNodeData(
+      JSON.parse(JSON.stringify(selectedNode.value))
+    );
   infoVisible.value = false;
   selectedNode.value = null;
 };
@@ -50,8 +56,35 @@ onMounted(() => {
       ref="flowRef"
       v-model:nodes-data="nodes"
       v-model:edges-data="edges"
+      :show-panel="true"
+      :panel-btns="['toSnake', 'layoutTB', 'saveTemplate']"
       @hjMethod="onNodeOperation"
     />
+    <div class="right-message-box">
+      <TitleHeader>属性配置</TitleHeader>
+      <div class="form-box" v-if="infoVisible">
+        <el-form rer="formRef" label-position="top">
+          <el-form-item label="节点名称">
+            <el-input
+              v-model="selectedNode!.data.information.nodeName"
+            ></el-input>
+          </el-form-item>
+          <el-form-item
+            v-for="property in selectedNode?.data?.information?.properties"
+            :label="property.proName"
+            :key="property.proName"
+          >
+            <el-input v-model="property.bindValue"></el-input>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="bottom-box">
+        <div class="save-btn" @click="onSaveConfigs">
+          <svg-icon icon-class="save" />
+          保存节点信息
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -62,7 +95,52 @@ onMounted(() => {
   height: calc(100vh - $main-header-height);
 }
 .hjflow-box {
-  width: 800px;
+  //width: 100%;
+  flex: 1;
   height: 100%;
 }
+
+.right-message-box {
+  width: 296px;
+  flex-shrink: 0;
+  background-color: rgba(80, 80, 80, 1);
+  height: calc(100vh - $main-header-height);
+  position: relative;
+
+  .form-box {
+    width: 100%;
+    padding: 24px;
+  }
+
+  .bottom-box {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    //height: 80px;
+    padding: 10px 0;
+    background-color: $hj-black-2;
+
+    .save-btn {
+      width: 130px;
+      height: 36px;
+      line-height: 36px;
+      margin: 0 auto;
+      background: var(--fc-color-7);
+      color: var(--hj-white-1);
+      text-align: center;
+      border-radius: 4px 4px 4px 4px;
+      cursor: pointer;
+    }
+  }
+}
+
+:deep(.el-form-item--label-top .el-form-item__label) {
+  height: 18px;
+  font-weight: 400;
+  font-size: 14px;
+  color: #ffffff;
+  line-height: 16px;
+  text-align: left;
+}
 </style>