bill 1 year ago
parent
commit
ecc9918114

+ 2 - 0
src/util/index.ts

@@ -269,9 +269,11 @@ export const getTextBound = (
 
   ctx.font = font;
   const textMetrics = ctx.measureText(text);
+
   const width = textMetrics.width + (padding[1] + margin[1]) * 2;
   const fontHeight =
     textMetrics.fontBoundingBoxAscent + textMetrics.fontBoundingBoxDescent;
+  console.log(fontHeight);
   const height = fontHeight + (padding[0] + margin[0]) * 2;
 
   return { width, height };

+ 105 - 71
src/view/step-tree/StepTree.vue

@@ -9,6 +9,7 @@
       v-for="step in steps"
       :key="step.id"
       v-bind="getStepAttrib(step)"
+      :treeBoxMargin="treeBoxMargin"
       @click="emit('stepClick', step)"
       @click-host="(data) => emit('stepHostClick', data)"
     />
@@ -16,22 +17,29 @@
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue'
-import { flatSteps, attachBoundAttrib, NStep, getTextBound } from './helper'
-import Step from './step.vue'
+import { computed } from "vue";
+import {
+  flatSteps,
+  attachBoundAttrib,
+  NStep,
+  getTextBound,
+  attachStepTreesBoundAttrib,
+} from "./helper";
+import Step from "./step.vue";
 
 const props = withDefaults(
   defineProps<{
-    margin?: number[]
-    padding?: number[]
-    fontSize?: number
-    data: any
-    fontFamily?: string
-    hostFontSize?: number
-    hostMargin?: number[]
-    hostPadding?: number[]
-    customStepStyle?: (step: any) => {}
-    lineGap: number
+    margin?: number[];
+    padding?: number[];
+    fontSize?: number;
+    treeBoxMargin: number[];
+    data: any;
+    fontFamily?: string;
+    hostFontSize?: number;
+    hostMargin?: number[];
+    hostPadding?: number[];
+    customStepStyle?: (step: any) => {};
+    lineGap: number;
   }>(),
   {
     margin: () => [10, 10],
@@ -41,14 +49,14 @@ const props = withDefaults(
     hostFontSize: 10,
     lineGap: 5,
     fontSize: 14,
-    fontFamily: 'sans-serif',
+    fontFamily: "sans-serif",
   }
-)
+);
 
 const emit = defineEmits<{
-  (e: 'stepClick', data: any): void
-  (e: 'stepHostClick', host: any): void
-}>()
+  (e: "stepClick", data: any): void;
+  (e: "stepHostClick", host: any): void;
+}>();
 
 const getStepSize = (step: any) => {
   const size = getTextBound(
@@ -56,17 +64,17 @@ const getStepSize = (step: any) => {
     props.padding,
     props.margin,
     `${props.fontSize}px normal ${props.fontFamily}`
-  )
+  );
 
   if (step.hosts?.length) {
-    const hostsGroup = []
-    const numGroup = 2
+    const hostsGroup = [];
+    const numGroup = 2;
     for (let i = 0; i < step.hosts.length; i += numGroup) {
-      hostsGroup.push(step.hosts.slice(i, i + numGroup))
+      hostsGroup.push(step.hosts.slice(i, i + numGroup));
     }
-    let top = 0
+    let top = 0;
     const hostSizeGroup = hostsGroup.map((hosts) => {
-      let left = 0
+      let left = 0;
       const hostSize = hosts.reduce(
         (t: any, host: any) => {
           const size = getTextBound(
@@ -74,85 +82,112 @@ const getStepSize = (step: any) => {
             props.hostPadding,
             props.hostMargin,
             `${props.hostFontSize}px normal ${props.fontFamily}`
-          )
-          t.width += size.width
-          t.height = Math.max(t.height, size.height)
+          );
+          t.width += size.width;
+          t.height = Math.max(t.height, size.height);
           host.bound = {
             ...size,
             left,
             top,
-          }
-          left += size.width
-          return t
+          };
+          left += size.width;
+          return t;
         },
         { width: 0, height: 0 }
-      )
-      top += hostSize.height
-      return hostSize
-    })
+      );
+      top += hostSize.height;
+      return hostSize;
+    });
 
     const hostSize = hostSizeGroup.reduceRight(
       (t, hostSize) => {
-        t.width = Math.max(hostSize.width, t.width)
-        t.height += hostSize.height
-        return t
+        t.width = Math.max(hostSize.width, t.width);
+        t.height += hostSize.height;
+        return t;
       },
       { width: 0, height: 0 }
-    )
-    step.hostSize = hostSize
-    size.width = Math.max(size.width, hostSize.width + (props.padding[1] + props.margin[1]) * 2)
-    size.height += hostSize.height
+    );
+    step.hostSize = hostSize;
+    size.width = Math.max(
+      size.width,
+      hostSize.width + (props.padding[1] + props.margin[1]) * 2
+    );
+    size.height += hostSize.height;
   }
 
-  return size
-}
+  return size;
+};
+
+const steps = computed(() => {
+  const steps = flatSteps(props.data);
+  return steps;
+});
+const bound = computed(() => {
+  const pageBound = attachBoundAttrib(steps.value, getStepSize);
+  attachStepTreesBoundAttrib(steps.value, props.data);
+  console.log(steps.value);
+  return {
+    ...pageBound,
+    left: pageBound.left - 10,
+    right: pageBound.right + 10,
+    top: pageBound.top - 10,
+    bottom: pageBound.bottom + 10,
+  };
+});
 
-const steps = computed(() => flatSteps(props.data))
-const bound = computed(() => attachBoundAttrib(steps.value, getStepSize))
-console.log('3323', bound.value)
 const svgAttrib = computed(() => {
-  if (!bound.value) return null
-  const { left, right, top, bottom } = bound.value
+  if (!bound.value) return null;
+  const { left, right, top, bottom } = bound.value;
   return {
     viewBox: [left, top, right - left, bottom - top],
-  }
-})
+  };
+});
 
-const lineGap = computed(() => Math.min(props.lineGap, props.margin[0]))
+const lineGap = computed(() => Math.min(props.lineGap, props.margin[0]));
 
 const getStepLines = (step: NStep) => {
-  if (!step.parentIds.length) return []
-  const start = [step.bound.left + step.bound.width / 2, step.bound.top + props.margin[0]]
-  const points = []
+  if (!step.parentIds.length) return [];
+  const start = [
+    step.bound.left + step.bound.width / 2,
+    step.bound.top + props.margin[0],
+  ];
+  const points = [];
   for (let parentId of step.parentIds) {
-    const parent = steps.value.find((step) => step.id === parentId)!
+    const parent = steps.value.find((step) => step.id === parentId)!;
     const end = [
       parent.bound.left + parent.bound.width / 2,
       parent.bound.top + parent.bound.height - props.margin[0],
-    ]
-    const startLevelHeight = bound.value.levelHeights[step.level]
+    ];
+    const startLevelHeight = bound.value.levelHeights[step.level];
     // const parentLevelHeight = bound.value.levelHeights[parent.level];
-    const offset = lineGap.value + (startLevelHeight - step.bound.height) / 2
+    const offset = lineGap.value + (startLevelHeight - step.bound.height) / 2;
 
-    points.push([...start, start[0], start[1] - offset, end[0], start[1] - offset, ...end])
+    points.push([
+      ...start,
+      start[0],
+      start[1] - offset,
+      end[0],
+      start[1] - offset,
+      ...end,
+    ]);
   }
-  return points
-}
+  return points;
+};
 
 const defaultStyle = {
-  lineColor: '#000',
+  lineColor: "#000",
   lineWidth: 1,
-  textColor: '#000',
-  rectBorderColor: '#000',
-  rectBgColor: '#ffff',
+  textColor: "#000",
+  rectBorderColor: "#000",
+  rectBgColor: "#ffff",
   rectRadius: 2,
   rectBorderWidth: 1,
-}
+};
 
 const getStepAttrib = (step: NStep) => {
-  let style = defaultStyle
+  let style = defaultStyle;
   if (props.customStepStyle) {
-    style = { ...defaultStyle, ...props.customStepStyle(step.raw) }
+    style = { ...defaultStyle, ...props.customStepStyle(step.raw) };
   }
   return {
     style,
@@ -165,7 +200,6 @@ const getStepAttrib = (step: NStep) => {
     hostFontSize: props.hostFontSize,
     fontFamily: props.fontFamily,
     lines: getStepLines(step),
-  }
-}
+  };
+};
 </script>
-

+ 1 - 1
src/view/step-tree/example/example.vue

@@ -12,6 +12,7 @@
       :data="treeData"
       :margin="[15, 15]"
       :padding="[10, 10]"
+      :tree-box-margin="[5, 5]"
       :font-size="16"
       :hostFontSize="16"
       :hostMargin="[10, 5]"
@@ -41,7 +42,6 @@ const treeData = [
 
 // 每个step的样式
 const customStepStyle = (step) => {
-  console.log("xxxxx", step);
   // 等待中,开始状态
   if (step.status === "waiting") {
     return {

+ 42 - 1
src/view/step-tree/helper.ts

@@ -1,6 +1,7 @@
 export type NStep = {
   id: number;
   parentIds: number[];
+  treeBound: { width: number; height: number; left: number; top: number };
   displayName: string;
   bound: { width: number; height: number; left: number; top: number };
   prevId: number;
@@ -265,6 +266,7 @@ export const attachBoundAttrib = (
     left = Math.min(steps[i].bound.left, left);
     right = Math.max(steps[i].bound.left + steps[i].bound.width, right);
   }
+
   return { left, right, top: 0, bottom: top, levelHeights };
 };
 
@@ -274,6 +276,46 @@ export const flatSteps = (data: any) => {
   return nsteps;
 };
 
+const getStepTreeBoundAttrib = (nsteps: NStep[], item: any) => {
+  const itemStep = nsteps.find(({ raw }) => raw === item);
+  if (!item.steps || item.steps.length === 0) {
+    itemStep.treeBound = itemStep.bound;
+    return;
+  }
+
+  const bounds = [itemStep.bound];
+  for (let child of item.steps) {
+    bounds.push(nsteps.find(({ raw }) => raw === child).treeBound);
+  }
+
+  let maxX = -Number.MAX_VALUE,
+    maxY = -Number.MAX_VALUE,
+    minX = Number.MAX_VALUE,
+    minY = Number.MAX_VALUE;
+  for (const bound of bounds) {
+    minX = Math.min(bound.left, minX);
+    minY = Math.min(bound.top, minY);
+    maxX = Math.max(bound.left + bound.width, maxX);
+    maxY = Math.max(bound.top + bound.height, maxY);
+  }
+  itemStep.treeBound = {
+    left: minX,
+    top: minY,
+    width: maxX - minX,
+    height: maxY - minY,
+  };
+};
+
+export const attachStepTreesBoundAttrib = (nsteps: NStep[], data: any) => {
+  for (const item of data) {
+    if (item.steps && item.steps.length > 0) {
+      attachStepTreesBoundAttrib(nsteps, item.steps);
+    }
+    getStepTreeBoundAttrib(nsteps, item);
+  }
+  return nsteps;
+};
+
 const ctx = document.createElement("canvas").getContext("2d")!;
 export const getTextBound = (
   text: string,
@@ -287,6 +329,5 @@ export const getTextBound = (
   const fontHeight =
     textMetrics.fontBoundingBoxAscent + textMetrics.fontBoundingBoxDescent;
   const height = fontHeight + (padding[0] + margin[0]) * 2;
-
   return { width, height };
 };

+ 56 - 44
src/view/step-tree/step.vue

@@ -11,6 +11,27 @@
   >
   </rect>
 
+  <template v-if="lines.length">
+    <polyline
+      v-for="line in lines"
+      :points="line.join(',')"
+      fill="none"
+      :stroke="style.lineColor"
+      :stroke-width="style.lineWidth"
+    />
+  </template>
+  <rect
+    v-if="step.treeBound !== step.bound"
+    :x="step.treeBound.left + margin[0] - treeBoxMargin[0]"
+    :y="step.treeBound.top + margin[1] - treeBoxMargin[1]"
+    :width="step.treeBound.width - 2 * margin[0] + 2 * treeBoxMargin[0]"
+    :height="step.treeBound.height - 2 * margin[1] + 2 * treeBoxMargin[1]"
+    stroke="rgba(0, 0, 0, 0.5)"
+    stroke-dasharray="5, 5"
+    fill="rgba(0, 0, 0, 0)"
+  >
+  </rect>
+
   <template v-if="step.raw.hosts">
     <rect
       v-for="(_, index) in step.raw.hosts"
@@ -64,79 +85,70 @@
   >
     {{ step.displayName }}
   </text>
-
-  <template v-if="lines.length">
-    <polyline
-      v-for="line in lines"
-      :points="line.join(',')"
-      fill="none"
-      :stroke="style.lineColor"
-      :stroke-width="style.lineWidth"
-    />
-  </template>
 </template>
 
 <script setup lang="ts">
-import { computed } from 'vue'
-import { NStep } from './helper'
+import { computed } from "vue";
+import { NStep } from "./helper";
 
 const props = defineProps<{
-  step: NStep
-  margin: number[]
-  padding: number[]
-  fontSize: number
-  fontFamily: string
-  lines: number[][]
-  hostMargin: number[]
-  hostPadding: number[]
-  hostFontSize: number
+  step: NStep;
+  margin: number[];
+  padding: number[];
+  fontSize: number;
+  fontFamily: string;
+  lines: number[][];
+  hostMargin: number[];
+  hostPadding: number[];
+  treeBoxMargin: number[];
+  hostFontSize: number;
   style: {
-    lineColor: string
-    lineWidth: number
-    textColor: string
-    rectBorderColor: string
-    rectBorderWidth: number
-    rectBgColor: string
-    rectRadius: number
-  }
-}>()
+    lineColor: string;
+    lineWidth: number;
+    textColor: string;
+    rectBorderColor: string;
+    rectBorderWidth: number;
+    rectBgColor: string;
+    rectRadius: number;
+  };
+}>();
 
-const emit = defineEmits<{ (e: 'click'): void; (e: 'clickHost', host: any): void }>()
+const emit = defineEmits<{ (e: "click"): void; (e: "clickHost", host: any): void }>();
 
 const rectBound = computed(() => ({
   x: props.step.bound.left + props.margin[1],
   y: props.step.bound.top + props.margin[0],
   width: props.step.bound.width - props.margin[1] * 2,
   height: props.step.bound.height - props.margin[0] * 2,
-}))
+}));
 const textAttrib = computed(() => ({
   x: rectBound.value.x + rectBound.value.width / 2,
-  y: rectBound.value.y + props.padding[0] + props.fontSize / 2,
-}))
+  y: rectBound.value.y + props.padding[0] + props.fontSize / 1.2,
+}));
 
 const hostBounds = computed(() => {
   let left =
     rectBound.value.x +
     props.padding[1] +
-    (rectBound.value.width - props.padding[1] * 2 - props.step.raw.hostSize.width) / 2
-  let top = rectBound.value.y + rectBound.value.height - props.step.raw.hostSize.height
+    (rectBound.value.width - props.padding[1] * 2 - props.step.raw.hostSize.width) / 2;
+  let top = rectBound.value.y + rectBound.value.height - props.step.raw.hostSize.height;
 
-  const hosts = props.step.raw.hosts
+  const hosts = props.step.raw.hosts;
   return hosts.map((host: any) => {
-    const x = left + host.bound.left + props.hostMargin[1]
-    const y = top + props.hostMargin[0] + host.bound.top
+    const x = left + host.bound.left + props.hostMargin[1];
+    const y = top + props.hostMargin[0] + host.bound.top;
     return {
       x,
       y,
       width: host.bound.width - props.hostMargin[1] * 2,
       height: host.bound.height - props.hostMargin[0] * 2,
-    }
-  })
-})
+    };
+  });
+});
 const hostTextAttribs = computed(() =>
   hostBounds.value.map((hostBound: any) => ({
     x: hostBound.x + hostBound.width / 2,
-    y: hostBound.y + props.hostPadding[0] + props.hostFontSize / 2,
+    y: hostBound.y + props.hostPadding[0] + props.hostFontSize / 1.2,
   }))
-)
+);
 </script>

+ 64 - 65
src/view/step-tree/views/flowChart.vue

@@ -1,4 +1,3 @@
-
 <template>
   <div class="status-box flex">
     <!-- <span class="defualt">运行中</span> -->
@@ -27,129 +26,129 @@
 </template>
 
 <script setup>
-import data from './data'
-import StepTree from './StepTree.vue'
+import data from "./data";
+import StepTree from "./StepTree.vue";
 
-import { ref, nextTick, toRefs } from 'vue'
-import { useRouter } from 'vue-router'
-const treeWrapRef = ref()
-const $router = useRouter()
+import { ref, nextTick, toRefs } from "vue";
+import { useRouter } from "vue-router";
+const treeWrapRef = ref();
+const $router = useRouter();
 const treeData = [
-  { displayName: '开始', type: 'startEnd' },
+  { displayName: "开始", type: "startEnd" },
   ...data[0].steps,
-  { displayName: '结束', type: 'startEnd' },
-]
+  { displayName: "结束", type: "startEnd" },
+];
 
 // 每个step的样式
 const customStepStyle = (step) => {
-  console.log('xxxxx', step)
   // 等待中,开始状态
-  if (step.status === 'waiting') {
+  if (step.status === "waiting") {
     return {
-      lineColor: '#89beb2',
+      lineColor: "#89beb2",
       lineWidth: 1,
-      textColor: '#333',
-      rectBorderColor: '#333',
-      rectBgColor: '#89beb2',
+      textColor: "#333",
+      rectBorderColor: "#333",
+      rectBgColor: "#89beb2",
       rectRadius: 2,
       rectBorderWidth: 1,
-    }
+    };
   }
   // 失败
-  else if (step.status === 'error') {
+  else if (step.status === "error") {
     return {
-      lineColor: '#89beb2',
+      lineColor: "#89beb2",
       lineWidth: 1,
-      textColor: '#333',
-      rectBorderColor: '#333',
-      rectBgColor: 'red',
+      textColor: "#333",
+      rectBorderColor: "#333",
+      rectBgColor: "red",
       rectRadius: 2,
       rectBorderWidth: 1,
-    }
+    };
   }
   // 成功
-  else if (step.status === 'success') {
+  else if (step.status === "success") {
     return {
-      lineColor: '#89beb2',
+      lineColor: "#89beb2",
       lineWidth: 1,
-      textColor: '#333',
-      rectBorderColor: '#333',
-      rectBgColor: '#30d567',
+      textColor: "#333",
+      rectBorderColor: "#333",
+      rectBgColor: "#30d567",
       rectRadius: 2,
       rectBorderWidth: 1,
-    }
+    };
   }
   // 部分成功
-  else if (step.status === 'partsuccess') {
+  else if (step.status === "partsuccess") {
     return {
-      lineColor: '#89beb2',
+      lineColor: "#89beb2",
       lineWidth: 1,
-      textColor: '#333',
-      rectBorderColor: '#333',
-      rectBgColor: '#d4f8c3',
+      textColor: "#333",
+      rectBorderColor: "#333",
+      rectBgColor: "#d4f8c3",
       rectRadius: 2,
       rectBorderWidth: 1,
-    }
-  } else if (step.status === 'running') {
+    };
+  } else if (step.status === "running") {
     return {
-      lineColor: '#89beb2',
+      lineColor: "#89beb2",
       lineWidth: 1,
-      textColor: '#333',
-      rectBorderColor: '#333',
-      rectBgColor: '#ecf752',
+      textColor: "#333",
+      rectBorderColor: "#333",
+      rectBgColor: "#ecf752",
       rectRadius: 2,
       rectBorderWidth: 1,
-    }
+    };
   }
   {
     return {
-      lineColor: '#89beb2',
+      lineColor: "#89beb2",
       lineWidth: 1,
-      textColor: '#333',
-      rectBorderColor: 'coral',
-      rectBgColor: '#ffff',
+      textColor: "#333",
+      rectBorderColor: "coral",
+      rectBgColor: "#ffff",
       rectRadius: 2,
       rectBorderWidth: 1,
-    }
+    };
   }
-}
+};
 
 // hosts
 const customHostStyle = (step) => {
   if (step.hosts?.length) {
     step.hosts.forEach((item, idx) => {
-      if (item.status === 'success') {
+      if (item.status === "success") {
         return {
-          lineColor: '#40dbd9',
+          lineColor: "#40dbd9",
           lineWidth: 1,
-          textColor: '#ccc',
-          rectBorderColor: '#40dbd9',
-          rectBgColor: '#ffff',
+          textColor: "#ccc",
+          rectBorderColor: "#40dbd9",
+          rectBgColor: "#ffff",
           rectRadius: 2,
           rectBorderWidth: 1,
-        }
+        };
       }
-    })
+    });
   }
-}
+};
 
 const stepClickHandler = (step) => {
-  console.log(step)
-  if (step.raw.type === 'startEnd') return
+  console.log(step);
+  if (step.raw.type === "startEnd") return;
   let url = $router.resolve({
-    path: '/stepLogs',
+    path: "/stepLogs",
     params: {
       key: 1,
     },
-  }).href
-  window.open(url, '_blank')
-}
+  }).href;
+  window.open(url, "_blank");
+};
 const stepHostClickHandler = (host) => {
-  console.log(host)
-}
+  console.log(host);
+};
 nextTick(() => {
-  treeWrapRef.value.scrollLeft = (treeWrapRef.value.scrollWidth - treeWrapRef.value.clientWidth) / 2
-})
+  treeWrapRef.value.scrollLeft =
+    (treeWrapRef.value.scrollWidth - treeWrapRef.value.clientWidth) / 2;
+});
 </script>
 
 <style scoped>