vue2+html+css实现动态鱼骨图_vue 鱼骨图-爱代码爱编程
简介
最近博主接到需求,画一个鱼骨图,网上找了下,没有现成的,于是博主自己写了个,可以给大家提供一个思路
效果图
博主是根据客户那边的要求进行一步一步调整的,样式比较丑,用的时候可以根据自己的需要修改样式,博主更多的是提供一个思路
首先新建一个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>