vue2+html+css实现动态鱼骨图

简介

最近博主接到需求,画一个鱼骨图,网上找了下,没有现成的,于是博主自己写了个,可以给大家提供一个思路

效果图

在这里插入图片描述
博主是根据客户那边的要求进行一步一步调整的,样式比较丑,用的时候可以根据自己的需要修改样式,博主更多的是提供一个思路

首先新建一个fishboneDiagram.vue文件,然后将下面的代码复制进去

html

<template>
  <div class="fishboneDiagram">
    <div class="fishbone">
      <div class="content" ref="content">
        <!-- 大骨 -->
        <el-row type="flex" class="top-bone" align="bottom">
          <div
            class="item-bone"
            v-for="(item, index) in fishboneData.childrenList"
            :key="index"
          >
            <div class="item-bone-children" v-if="index % 2 == 0">
              <!-- 中骨 -->
              <div class="item_bone_children_item left_item_bone_children">
                <div
                  v-for="(metatarsus, i) in item.childrenList"
                  class="children-item"
                  :key="i"
                >
                  <div class="metatarsus left_metatarsus" v-if="i % 2 == 0">
                    <!-- 小骨 -->
                    <div class="metatarsus_content left_content top_content">
                      <div
                        v-for="(ossicle, ossindex) in metatarsus.childrenList"
                        :key="ossindex"
                        class="metatarsus_content_item"
                      >
                        <div
                          class="metatarsus_content_item_text"
                          v-if="ossindex % 2 == 0"
                        >
                          {{ ossicle.content }}
                        </div>
                      </div>
                    </div>
                    <!-- 中骨 -->
                    <div class="metatarsus_main">
                      <div class="metatarsus_title">
                        {{ metatarsus.content }}
                      </div>
                      <div class="metatarsus_line"></div>
                    </div>
                    <!-- 小骨 -->
                    <div class="metatarsus_content left_content bottom_content">
                      <div
                        v-for="(ossicle, ossindex) in metatarsus.childrenList"
                        :key="ossindex"
                        class="metatarsus_content_item"
                      >
                        <div
                          class="metatarsus_content_item_text"
                          v-if="ossindex % 2 != 0"
                        >
                          {{ ossicle.content }}
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div class="item_bone_children_bigBone">
                <div class="item_bone_children_bigBone_title">
                  {{ item.content }}
                </div>
                <div class="item_bone_children_bigBone_line"></div>
              </div>
              <!-- 中骨 -->
              <div class="item_bone_children_item right_item_bone_children">
                <div
                  v-for="(metatarsus, i) in item.childrenList"
                  class="children-item right_children_item"
                  :key="i"
                >
                  <div class="metatarsus" v-if="i % 2 != 0">
                    <!-- 小骨 -->
                    <div class="metatarsus_content right_content top_content">
                      <div
                        v-for="(ossicle, ossindex) in metatarsus.childrenList"
                        :key="ossindex"
                        class="metatarsus_content_item"
                      >
                        <div
                          class="metatarsus_content_item_text"
                          v-if="ossindex % 2 == 0"
                        >
                          {{ ossicle.content }}
                        </div>
                      </div>
                    </div>
                    <!-- 中骨 -->
                    <div class="metatarsus_main">
                      <div class="metatarsus_line"></div>
                      <div class="metatarsus_title">
                        {{ metatarsus.content }}
                      </div>
                    </div>
                    <!-- 小骨 -->
                    <div
                      class="metatarsus_content right_content bottom_content"
                    >
                      <div
                        v-for="(ossicle, ossindex) in metatarsus.childrenList"
                        :key="ossindex"
                        class="metatarsus_content_item"
                      >
                        <div
                          class="metatarsus_content_item_text"
                          v-if="ossindex % 2 != 0"
                        >
                          {{ ossicle.content }}
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </el-row>
        <!-- 主骨 -->
        <div class="center_cardinalBone">
          <div class="center_cardinalBone_line"></div>
          <div class="center_cardinalBone_title">
            {{ fishboneData.content }}
          </div>
        </div>
        <!-- 大骨 -->
        <el-row type="flex" class="bottom-bone">
          <div
            class="item-bone"
            v-for="(item, index) in fishboneData.childrenList"
            :key="index"
          >
            <div class="item-bone-children" v-if="index % 2 != 0">
              <!-- 中骨 -->
              <div class="item_bone_children_item left_item_bone_children">
                <div
                  v-for="(metatarsus, i) in item.childrenList"
                  class="children-item"
                  :key="i"
                >
                  <div class="metatarsus left_metatarsus" v-if="i % 2 == 0">
                    <div
                      class="metatarsus_content left_content bottom_left_content top_content"
                    >
                      <div
                        v-for="(ossicle, ossindex) in metatarsus.childrenList"
                        :key="ossindex"
                        class="metatarsus_content_item"
                      >
                        <div
                          class="metatarsus_content_item_text bottom_text"
                          v-if="ossindex % 2 == 0"
                        >
                          {{ ossicle.content }}
                        </div>
                      </div>
                    </div>
                    <div class="metatarsus_main">
                      <div class="metatarsus_title bottom_text">
                        {{ metatarsus.content }}
                      </div>
                      <div class="metatarsus_line"></div>
                    </div>
                    <div
                      class="metatarsus_content left_content bottom_left_content bottom_content"
                    >
                      <div
                        v-for="(ossicle, ossindex) in metatarsus.childrenList"
                        :key="ossindex"
                        class="metatarsus_content_item"
                      >
                        <div
                          class="metatarsus_content_item_text bottom_text"
                          v-if="ossindex % 2 != 0"
                        >
                          {{ ossicle.content }}
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div class="item_bone_children_bigBone">
                <div class="item_bone_children_bigBone_line"></div>
                <div class="item_bone_children_bigBone_title bottom_text">
                  {{ item.content }}
                </div>
              </div>
              <!-- 中骨 -->
              <div class="item_bone_children_item right_item_bone_children">
                <div
                  v-for="(metatarsus, i) in item.childrenList"
                  class="children-item right_children_item"
                  :key="i"
                >
                  <div class="metatarsus" v-if="i % 2 != 0">
                    <!-- 小骨 -->
                    <div
                      class="metatarsus_content right_content bottom_right_content top_content"
                    >
                      <div
                        v-for="(ossicle, ossindex) in metatarsus.childrenList"
                        :key="ossindex"
                        class="metatarsus_content_item"
                      >
                        <div
                          class="metatarsus_content_item_text bottom_text"
                          v-if="ossindex % 2 == 0"
                        >
                          {{ ossicle.content }}
                        </div>
                      </div>
                    </div>
                    <!-- 中骨 -->
                    <div class="metatarsus_main">
                      <div class="metatarsus_line"></div>
                      <div class="metatarsus_title bottom_text">
                        {{ metatarsus.content }}
                      </div>
                    </div>
                    <!-- 小骨 -->
                    <div
                      class="metatarsus_content right_content bottom_content"
                    >
                      <div
                        v-for="(ossicle, ossindex) in metatarsus.childrenList"
                        :key="ossindex"
                        class="metatarsus_content_item"
                      >
                        <div
                          class="metatarsus_content_item_text bottom_text"
                          v-if="ossindex % 2 != 0"
                        >
                          {{ ossicle.content }}
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </el-row>
      </div>
    </div>
  </div>
</template>

js部分

fishboneData是父组件传的数据,树结构的数据,子节点为childrenList,显示的文字参数为content

<script>
export default {
  name: "fishboneDiagram",
  props: {
    fishboneData: {
      type: Object,
      default: () => {},
    },
  },
  data() {
    return {};
  },
  created() {},
  mounted() {
  	// 动态计算
    this.$nextTick(() => {
      // 小骨与中骨位置间隔
      const metatarsusTitle =
        document.getElementsByClassName("metatarsus_title");
      const topContent = document.getElementsByClassName("top_content");
      const bottomContent = document.getElementsByClassName("bottom_content");
      for (let index = 0; index < metatarsusTitle.length; index++) {
        const topHeight = metatarsusTitle[index].clientHeight * -0.5;
        topContent[index].style.marginBottom = `${topHeight}px`;
        bottomContent[index].style.marginTop = `${topHeight}px`;
      }
      // 中骨与大骨位置间隔
      const itemBoneChildrenBigBoneTitle = document.getElementsByClassName(
        "item_bone_children_bigBone_title"
      );
      const leftItemBoneChildren = document.getElementsByClassName(
        "left_item_bone_children"
      );
      const rightItemBoneChildren = document.getElementsByClassName(
        "right_item_bone_children"
      );
      for (
        let index = 0;
        index < itemBoneChildrenBigBoneTitle.length;
        index++
      ) {
        const topHeight =
          itemBoneChildrenBigBoneTitle[index].clientWidth * -0.5;
        leftItemBoneChildren[index].style.marginRight = `${topHeight}px`;
        rightItemBoneChildren[index].style.marginLeft = `${topHeight}px`;
      }
      // 大骨与主骨位置间隔
      const centerCardinalBoneTitle = document.getElementsByClassName(
        "center_cardinalBone_title"
      );
      const topBone = document.getElementsByClassName("top-bone");
      const bottomBone = document.getElementsByClassName("bottom-bone");
      for (let index = 0; index < centerCardinalBoneTitle.length; index++) {
        const topHeight = centerCardinalBoneTitle[index].clientHeight * -0.5;
        topBone[index].style.marginBottom = `${topHeight}px`;
        bottomBone[index].style.marginTop = `${topHeight}px`;
      }
      // 中骨
      const metatarsusContent =
        document.getElementsByClassName("metatarsus_content");
      let metatarsusLine = document.getElementsByClassName("metatarsus_line");
      for (let index = 0; index < metatarsusLine.length; index++) {
        if (
          metatarsusContent[index * 2].clientWidth >
          metatarsusContent[index * 2 + 1].clientWidth
        ) {
          metatarsusLine[index].style.width = `${
            metatarsusContent[index * 2].clientWidth * 1.2
          }px`;
        } else {
          metatarsusLine[index].style.width = `${
            metatarsusContent[index * 2 + 1].clientWidth * 1.2
          }px`;
        }
      }
      // 大骨
      const elementItem = document.getElementsByClassName(
        "item_bone_children_item"
      );
      let element1 = document.getElementsByClassName(
        "item_bone_children_bigBone_line"
      );
      for (let index = 0; index < element1.length; index++) {
        // element1[index].style.height = `${element[index].clientHeight * 1}px`;
        if (
          elementItem[index * 2].clientHeight >
          elementItem[index * 2 + 1].clientHeight
        ) {
          element1[index].style.height = `${
            elementItem[index * 2].clientHeight * 1.2
          }px`;
        } else {
          element1[index].style.height = `${
            elementItem[index * 2 + 1].clientHeight * 1.2
          }px`;
        }
      }
    });
  },
  methods: {},
};
</script>

css部分

<style lang="less" scoped>
@bnoe-color: #002766;

.children-item {
  display: flex;
  justify-content: flex-end;
  align-items: center;
}

.right_children_item {
  justify-content: flex-start !important;
}
.fishboneDiagram {
  width: 100%;
  user-select: none;
}

.fishbone {
  min-height: 300px;
  height: 100%;
  position: relative;

  rgb(5, 10, 17) .content {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  }

  .center_cardinalBone {
    width: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    .center_cardinalBone_line {
      flex: 1;
      height: 2px;
      background-color: @bnoe-color;
    }

    .center_cardinalBone_title {
      width: fit-content;
      font-size: 16px;
      font-weight: bold;
      max-width: 100px;
    }
  }

  .top-bone {
    margin-bottom: -20px;
  }

  .bottom-bone {
    margin-top: -20px;
  }

  .top-bone,
  .bottom-bone {
    width: 100%;
    padding-right: 100px;

    .item-bone {
      float: left;
      margin: 0 10px;
    }

    .item-bone-children {
      // transform: skewX(45deg);
      margin: 0 20px;
      display: flex;
      justify-content: center;
      align-items: flex-end;
    }
  }

  .bottom-bone {
    bottom: 0;

    .item-bone-children {
      align-items: flex-start !important;
    }
  }

  .item_bone_children_bigBone {
    height: 100%;
    display: flex;
    align-items: center;
    flex-direction: column;
    .item_bone_children_bigBone_line {
      width: 2px;
      background-color: @bnoe-color;
    }

    .item_bone_children_bigBone_title {
      font-size: 14px;
      font-weight: bold;
      max-height: 100px;
      writing-mode: vertical-rl;
      text-orientation: upright;
      overflow-wrap: break-word;
    }
  }
}

::v-deep .el-row--flex {
  justify-content: center;
}

.metatarsus {
  width: fit-content;
  display: flex;
  align-items: flex-start;
  flex-direction: column;
  justify-content: space-between;
  margin: 10px 0;
  .metatarsus_content {
    width: fit-content;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 5px 0;
  }
  .metatarsus_main {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    .metatarsus_line {
      flex: 1;
      height: 2px;
      background-color: @bnoe-color;
    }

    .metatarsus_title {
      width: fit-content;
      font-size: 14px;
      font-weight: bold;
      max-width: 100px;
    }
  }
}

.left_metatarsus {
  align-items: flex-end !important;
}

.metatarsus_content_item {
  width: 100%;
  .metatarsus_content_item_text {
    margin: 0 5px;
    font-weight: bold;
    border-left: 2px dashed @bnoe-color;
    max-height: 100px;
    writing-mode: vertical-rl;
    text-orientation: upright;
    overflow-wrap: break-word;
  }
}

.right_content {
  margin-left: 0 !important;
}

.left_content {
  margin-right: 10px !important;
}

.top_content {
  align-items: flex-end !important;
}

.bottom_content {
  align-items: flex-start !important;
}
</style>
  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值