当前位置: 首页 > backend >正文

前端绘制道路鱼骨图

项目背景:需要实现道路情况鱼骨图,根据上下行道路分别显示对应的道路情况和沿路设施状况,箭头根据所示方向平滑移动

1.封装组件,创建FishboneDiagram.vue文件

<template><div class="fishedOneBox flex items-center"><div @click="scrollContent('left')" class="left cursor-pointer leftButtonBox pl-20px box-border w-60px p-15px box-border h-165px flex justify-center items-center"><button class="text-(16px #7BA9FA) font-bold leading-20px">哈密方向</button></div><div class="content" ref="scrollContainers"><div class="upList mb-6px"><!-- 上行公路 --><div class="road"><div class="upRoadItem relative" v-for="(item, index) in upList" :key="index":style="{ width: sectionWidth, background: item.status === 1 ? '#4ACF50' : '#D6D6D6', borderLeft: getLeftBorder(index, upList), borderRight: getRightBorder(index, upList) }"><img class="arrows arrow-lefts" src="/@/assets/images/iconLook/left_arrow.png" alt=""><img class="arrow arrow-left" src="/@/assets/images/iconLook/left_arrow.png" alt=""><!-- 桩号 --><div class="text-(12px #999999) absolute right--30px top--18px">{{ item.id }}</div><!-- 路侧设备(服务区,互通) --><div class="absolute top--51px" v-if="item.staketype !== 4 && item.staketype !== 5"><div class="flex rounded-16px pl-7px pr-7px pt-3px pb-3px" :class="item.staketype === 2 ? 'bg-#E4FEE0' : 'bg-#E4EEFF'"><img class="w-20px h-20px" :src="getImageUrl(item.staketype === 1 ? 'tollStation':item.staketype === 2 ? 'service':item.staketype === 3 ? 'interFlow' : '' , 'fishBoneIcon')" alt=""><span class="text-(14px #333333) ml-2px">{{ item.stakename }}</span></div><div class="line w-full flex justify-center"><img class="w-5px h-24px" :src="getImageUrl(`${item.staketype === 2 ? 'line_up_green' : 'line_up_blue'}`, 'fishBoneIcon')" alt=""></div></div></div></div></div><div class="downList"><!-- 下行公路 --><div class="road"><div class="upRoadItem relative" v-for="(item, index) in downList" :key="index":style="{ width: downSectionWidth, background: item.status === 1 ? '#4ACF50' : '#D6D6D6', borderLeft: getLeftBorder(index, downList), borderRight: getRightBorder(index, downList) }"><img class="downArrow arrow-right" src="/@/assets/images/iconLook/right_arrow.png" alt=""><img class="downArrows arrow-rights" src="/@/assets/images/iconLook/right_arrow.png" alt=""><!-- 桩号 --><div class="text-(12px #999999) absolute right--30px bottom--20px">{{ item.id }}</div><!-- 下行路侧设备(服务区,互通) --><div class="absolute bottom--51px" v-if="item.staketype !== 4 && item.staketype !== 5"><div class="line w-full flex justify-center"><img class="w-5px h-24px" :src="getImageUrl(`${item.staketype === 2 ? 'line_down_green' : 'line_down_blue'}`, 'fishBoneIcon')" alt=""></div><div class="flex rounded-16px pl-7px pr-7px pt-3px pb-3px" :class="item.staketype === 2 ? 'bg-#E4FEE0' : 'bg-#E4EEFF'"><img class="w-20px h-20px" :src="getImageUrl(item.staketype === 1 ? 'tollStation':item.staketype === 2 ? 'service':item.staketype === 3 ? 'interFlow' : '' , 'fishBoneIcon')" alt=""><span class="text-(14px #333333) ml-2px">{{ item.stakename }}</span></div></div></div></div></div></div><div @click="scrollContent('right')" class="right cursor-pointer rightButtonBox pr-20px box-border w-60px h-165px flex justify-center p-15px box-border items-center"><button class="text-(16px #7BA9FA) font-bold leading-20px" >星星峡方向</button></div></div>
</template><script setup lang="ts">
import { onMounted, ref, computed } from "vue"
import { getImageUrl } from '/@/utils'
const myTimeout = ref()
const scrollTimeout = ref()
const scrollContainers = ref()
const maxScroll = ref()const props = defineProps({value: {type: Object,default: {}}
});// 上行
const upList = computed(() => {return props.value.upList
})// 下行
const downList = computed(() => {return props.value.downList
})
// 根据路段设施数量确定区间宽度
const sectionWidth = computed(() => {const widthShow = scrollContainers.value?.offsetWidth >= upList.value.length * 120let sectionWidth: any = "120px"if(upList.value.length >= downList.value.length) {if (widthShow) {sectionWidth = (scrollContainers.value?.offsetWidth / upList.value.length) + 'px'} else {sectionWidth = "120px"}} else {const width = downList.value.length * 120sectionWidth = (width / upList.value.length) + 'px'}return sectionWidth
})
const downSectionWidth = computed(() => {const downWidthShow = scrollContainers.value?.offsetWidth >= downList.value.length * 120let downSectionWidth: any = "120px"if(downList.value.length >= upList.value.length) {if (downWidthShow) {downSectionWidth = (scrollContainers.value?.offsetWidth / downList.value.length) + 'px'} else {downSectionWidth = "120px"}} else {const width = upList.value.length * 120downSectionWidth = (width / downList.value.length) + 'px'}return downSectionWidth
})
// 边框逻辑
const getLeftBorder = (index: number, list: any[]) => {if (index === 0) return ''; // 第一个元素始终显示左边框const prevItem = list[index - 1];const current = list[index];if (prevItem.end === 1 && current.start === 1) {return ''; // 当前元素的左边框不显示} else if (current.start === 1) {return '2px solid #ffffff';}return '';
};const getRightBorder = (index: number, list: any[]) => {if (index === list.length - 1) return ''; // 最后一个元素始终显示右边框const current = list[index];const nextItem = list[index + 1];if (current.end === 1 && nextItem.start === 1) {return ''; // 当前元素的右边框不显示} else if (current.end === 1) {return '2px solid #ffffff';}return '';
};
const scrollContent = (direction: any) => {// 清除之前的防抖计时器(如果存在)clearTimeout(scrollTimeout.value);scrollTimeout.value = setTimeout(() => {clearTimeout(myTimeout.value)const scrollContainer = scrollContainers.value;const scrollStep = 40; // 每次滚动的步长const scrollInterval = setInterval(() => {if (direction === 'left') {if (scrollContainer?.scrollLeft > 0) {scrollContainer.scrollLeft -= scrollStep;} else {clearInterval(scrollInterval);}} else {if (scrollContainer?.scrollLeft < maxScroll.value) {scrollContainer.scrollLeft += scrollStep;} else {clearInterval(scrollInterval);}}}, 20); // 滚动间隔时间,数值越小滚动越快myTimeout.value = setTimeout(() => {clearInterval(scrollInterval)}, 200)}, 200)
}
const updateScrollRange = () => {const scrollContainer = scrollContainers.value;// maxScroll.value = scrollContainer?.scrollWidth - scrollContainer?.clientWidth;maxScroll.value = scrollContainer?.scrollWidth
}
onMounted(() => {updateScrollRange()
})
</script><style lang="scss" scoped>
.fishedOneBox {width: 100%;height: 200px;
}
.leftButtonBox{background: url('/@/assets/images/leftButtonBox.png') no-repeat left center;background-size: 60% 100%;
}
.rightButtonBox{background: url('/@/assets/images/rightButtonBox.png') no-repeat right center;background-size: 60% 100%;
}
.content {display: flex;height: 186px;flex-direction: column;justify-content: center;width: calc(100% - 120px);overflow-x: scroll;overflow-y: hidden;.upList,.downList {display: flex;align-items: center;justify-content: flex-start; /* 确保子项目从左到右排列 */position: relative;.road {display: flex;background: #D6D6D6;.upRoadItem {background: #4ACF50;height: 30px;display: flex;align-items: center;//border-left: 1px solid #ffffff;//border-right: 1px solid #ffffff;.arrow {display: inline-block;position: relative;pointer-events: none;}.arrows {display: inline-block;position: relative;pointer-events: none;}.downArrow {display: inline-block;position: relative;pointer-events: none;}.downArrows {display: inline-block;position: relative;pointer-events: none;}}}}
}
.arrow-right {animation: moveRight 2.5s infinite linear forwards;
}
.arrow-rights {animation: moveRights 2.5s infinite linear forwards;
}
.arrow-left {animation: moveLeft 2.5s infinite linear forwards;
}
.arrow-lefts {animation: moveLefts 2.5s infinite linear forwards;
}
@keyframes moveRight {0% {left: -55%;}100% {left: 45%;}
}
@keyframes moveRights {0% {left: -5%;}100% {left: 95%;}
}
@keyframes moveLeft {0% {left: 40%;}100% {left: -60%;}
}
@keyframes moveLefts {0% {left: 95%;}100% {left: -5%;}
}
</style>

2. 引用组件

<template>
<Fishbone :value="dataInfoList" />
</template><script setup lang="ts">
import Fishbone from "./Fishbone.vue"// 鱼骨图上行下行数据
const dataInfoList: any = ref({upList: [],downList: []
})
</script>

小结:

1. 根据上行下行数据画出上行下行路段

2. 再根据每段路中是否存在一些设备设施,通过v-if渲染出来

3. 道路状况也可以根据当前此段道路的拥堵情况渲染不同的颜色

4. 路段动画根据图标方向对图标做left或者right的平移动画

根据自身业务情况适当修改,鱼骨图可以根据业务方向继续延伸,希望大家能有一点思路,我也是从毫无头绪慢慢画出来,又get到一个新的知识点,希望大家多多指正,一起加油!

http://www.xdnf.cn/news/13228.html

相关文章:

  • 502的普通频谱参数设置
  • 红外测温传感器如何提升智能制造水平?
  • 学习时困了怎么办
  • 2020年IS SCI2区,多样本和遗忘能力粒子群算法XPSO,深度解析+性能实测
  • Python打卡day49!!!
  • 【精彩回顾.上海交通大学专场】---大模型推理需求下的计算生态链变革
  • “概率鹦鹉”难解语义等价验证的NPC难题: 从技术本质看LLM在SQL优化任务中的致命缺陷
  • 高并发内存池的轻量级模拟-细节处理与优化部分
  • 多协议诱骗电压芯片优势,如何防止负载太大而导致充电器复位重启
  • DisplayPort 2.0协议介绍(2)
  • JavaScript 标签加载
  • AI知识库调用全攻略:四种实战方法与技术实现
  • c++第七天 继承与派生2
  • 安全编程期末复习12(红色重点向下兼容)
  • 河南建筑安全员C证考试常见题及答案解析
  • 2.7 判断.lib和.a是静态库 还是动态库的导入库
  • 基于Docker部署MYSQL主从复制
  • RT_Thread——线程管理(下)
  • 数学公式中latex的粗体问题
  • vSphere环境ubuntu24.04虚拟机从BIOS切换为EFI模式启动
  • 链表反转示例代码
  • 每日算法刷题Day27 6.9:leetcode二分答案2道题,用时1h20min
  • 论文解析:一文弄懂U-Net(图像分割)!
  • WEB3全栈开发——面试专业技能点P5中间件
  • 华为智选携手IAM:突破技术边界,重塑智慧健康家居新时代
  • 苍穹外卖|学习笔记|day07
  • C#学习第29天:表达式树(Expression Trees)
  • 俩人相向而行,何时相遇问题思考。
  • 《创始人IP打造:知识变现的高效路径》
  • EXCEL 实现“点击跳转到指定 Sheet”的方法