类似“贪吃蛇”类的流程图

52 阅读2分钟

1. 最简单的手写方式(使用 html + css)

image.png

// parent component
<template>
  <div class="wrapper">
    <span v-for="(item, index) in innerAndOuterflow" :key="item.id + index">
      <ProcessNode
        :title="item.statusName"
        :bgc="'#67c23a'"
        :arrow-flag="index !== innerAndOuterflow.length - 1 ? true : false"
        :arrow-direction="getArrowDirection(index, innerAndOuterflow)"
      />
    </span>
  </div>
</template>

<script setup lang="ts">
import { defineProps, ref, watch } from 'vue'
import { IFlowChart, IInnerAndOuterFlowChart } from '@/axios/api/types'
import ProcessNode from './ProcessNode.vue'

interface IProps {
  flowData: IInnerAndOuterFlowChart
}

const props = defineProps<IProps>()

const innerAndOuterflow = ref<IFlowChart[]>([])

const getArrowDirection = (index: number, totalArr: IFlowChart[]) => {
  let division
  division = Math.floor(index / 6)
  if (
    // such as 5, 6
    ((index + 1) % 6 === 0 && Math.floor((index + 1) / 6) % 2 === 1) ||
    (index % 6 === 0 && division % 2 === 1 && totalArr.length >= (division + 1) * 6)
  ) {
    return 'down'
  }
  return division % 2 === 0 ? 'right' : 'left'
}

// the flow chart is sorted as 'greedy-snake', we need to change our array
const transformToGreedySnakeShape = (array: IFlowChart[]) => {
  const result: IFlowChart[] = []
  const groupMap = new Map()
  array.map((item, index) => {
    const mod = Math.floor(index / 6)
    if (!groupMap.get(mod)) {
      groupMap.set(mod, [item])
    } else {
      const previousSameModIndex = groupMap.get(mod)
      groupMap.set(mod, [...previousSameModIndex, item])
    }
  })
  for (const entry of groupMap.entries()) {
    const [key, value] = entry
    if (key % 2 === 0) {
      result.push(...value)
    } else {
      const reversedArray = value.toReversed()
      result.push(...reversedArray)
    }
  }
  return result
}

watch(
  () => props.flowData,
  (newValue) => {
    if (newValue.auditFlows.length) {
      innerAndOuterflow.value = []
      const reversedAudits = newValue.auditFlows.toReversed()
      const reversedAgents = newValue.agentFlows.toReversed()
      const waitingToTransformedArray = [...reversedAudits, ...reversedAgents]
      innerAndOuterflow.value = transformToGreedySnakeShape(waitingToTransformedArray)
    }
  },
  { deep: true }
)
</script>

<style lang="scss" scoped>
.wrapper {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-row-gap: 120px;
  padding-top: 20px;
  padding-left: 20px;
}
</style>

// child component -> ProcessNode.vue
<template>
  <div class="process-node" :style="{ width: boxWidth, height: boxWidth, backgroundColor: bgc }">
    <div class="inner-content">
      <span class="title">{{ title }}</span>
    </div>
    <div
      v-if="arrowFlag"
      :class="{
        'line-left': arrowDirection === 'left',
        'line-right': arrowDirection === 'right',
        'line-down': arrowDirection === 'down'
      }"
    ></div>
  </div>
</template>

<script setup lang="ts">
import { defineProps, withDefaults } from 'vue'

interface IProps {
  title: string
  bgc: string
  boxWidth?: string
  arrowFlag: boolean
  arrowDirection?: string
}
withDefaults(defineProps<IProps>(), {
  boxWidth: '80px',
  arrowDirection: 'right'
})
</script>

<style lang="scss" scoped>
.process-node {
  display: flex;
  justify-content: center;
  align-items: center;
  transform: rotate(45deg);
  position: relative;
}

.inner-content {
  text-align: center;
  transform: rotate(-45deg);
}
.line-left {
  position: absolute;
  right: 56px;
  bottom: -55px;
  width: 159px;
  border-top: 1px solid #8c8c8c;
  transform: rotate(-45deg);
}
.line-left::after {
  content: '<';
  position: absolute;
  top: -11px;
  left: 0;
  color: #8c8c8c;
}
.line-right {
  position: absolute;
  left: 56px;
  top: -55px;
  width: 159px;
  border-top: 1px solid #8c8c8c;
  transform: rotate(-45deg);
}
.line-right::after {
  content: '>';
  position: absolute;
  top: -11px;
  right: 0;
  color: #8c8c8c;
}

.line-down {
  position: absolute;
  bottom: -30px;
  left: 66px;
  width: 88px;
  border-top: 1px solid #8c8c8c;
  transform: rotate(-135deg);
}
.line-down::after {
  content: '<';
  position: absolute;
  top: -11px;
  left: -4px;
  color: #8c8c8c;
}
</style>

PS: 这种写法有个弊端,就是当宽度改变的时候需要重新修改线条的位置。