前端绘制道路鱼骨图

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

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.pswp.cn/news/908888.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

selinux firewalld

一、selinux 1.说明 SELinux 是 Security-Enhanced Linux 的缩写,意思是安全强化的 linux; SELinux 主要由美国国家安全局(NSA)开发,当初开发的目的是为了避免资源的误用 DAC(Discretionary Access Control)自主访问控制系统MAC(Mandatory Access Control)强制访问控…

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…

企业数字化转型实战:某行业研究院如何通过SD-WAN技术优化网络架构?

一、引言 随着企业数字化转型的深入推进&#xff0c;传统网络架构在灵活性、可靠性和管理效率方面逐渐暴露不足。SD-WAN&#xff08;软件定义广域网&#xff09;技术凭借其智能化、自动化和高效的特点&#xff0c;逐渐成为企业网络架构优化的首选方案。本文以某研究院数字化基…

数字证书_CA_详解

目录 一、数字证书简介 二、 CA&#xff08;证书颁发机构&#xff09; (一) 证书链&#xff08;信任链&#xff09; 1. 根证书 2. 中间证书 3. 网站证书 (二) 抓包软件的证书链与信任机制 1. 抓包通信流程 2. 证书链伪造与信任验证流程 (三) 关于移动设备的CA 一、数…

Android协程学习

目录 Android上的Kotlin协程介绍基本概念与简单使用示例协程的高级用法 结构化并发线程调度器(Dispatchers)自定义调度器并发:同步 vs 异步 异步并发(async 并行执行)同步顺序执行协程取消与超时 取消机制超时控制异步数据流 Flow协程间通信 使用 Channel使用 StateFlow /…

统计学(第8版)——假设检验学习笔记(考试用)

一、假设检验核心框架 &#xff08;一&#xff09;解决的核心问题 判断样本与总体 / 样本与样本的差异是由抽样误差还是本质差异引起 典型场景&#xff1a; 产品合格率是否达标&#xff08;比例检验&#xff09;工艺改进后均值是否显著变化&#xff08;均值检验&#xff09…

Java求职者面试:微服务技术与源码原理深度解析

Java求职者面试&#xff1a;微服务技术与源码原理深度解析 第一轮&#xff1a;基础概念问题 1. 请解释什么是微服务架构&#xff0c;并说明其优势和挑战。 微服务架构是一种将单体应用拆分为多个小型、独立的服务的软件开发方法。每个服务都运行在自己的进程中&#xff0c;并…

c# 局部函数 定义、功能与示例

C# 局部函数&#xff1a;定义、功能与示例 1. 定义与功能 局部函数&#xff08;Local Function&#xff09;是嵌套在另一个方法内部的私有方法&#xff0c;仅在包含它的方法内可见。 • 作用&#xff1a;封装仅用于当前方法的逻辑&#xff0c;避免污染类作用域&#xff0c;提升…

ava多线程实现HTTP断点续传:原理、设计与代码实现

一、引言 在当今互联网环境下&#xff0c;大文件下载需求日益增长。传统单线程下载方式效率低下&#xff0c;且一旦下载中断&#xff0c;需要重新开始。断点续传技术通过将文件分块并利用多线程并行下载&#xff0c;显著提升了下载效率&#xff0c;同时支持中断后继续下载。本…

vla学习 富

# 基于diffusion # π0 ## 架构 其核心思想是在预训练好的视觉语言模型&#xff08;VLM&#xff09;基础上添加一个“动作专家”&#xff08;action expert&#xff09;&#xff0c;通过流匹配&#xff08;flow matching&#xff09;的方式生成连续的高频控制指令。整个架构可以…

降雨预测系统(机器学习)

这是一个基于Python开发的降雨预测系统,使用机器学习算法对指定月份的降雨概率进行预测。该系统提供了友好的图形用户界面(GUI),支持数据可视化和交互式操作。 ## 功能特点 - 📊 生成历史降雨数据(2015-2024年) - 🤖 使用逻辑回归模型进行降雨预测 - 📈 可视化…

逻辑回归与Softmax

Softmax函数是一种将一个含任意实数的K维向量转化为另一个K维向量的函数,这个输出向量的每个元素都在(0, 1)区间内,并且所有元素之和等于1。 因此,它可以被看作是某种概率分布,常用于多分类问题中作为输出层的激活函数。这里我们以拓展逻辑回归解决多分类的角度对Softmax函…

基于PSO与BP神经网络分类模型的特征选择实战(Python实现)

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在机器学习建模过程中&#xff0c;特征选择是提升模型性能、降低计算复杂度的重要环节。尤其在高维数据场景下&…

深度学习之模型压缩三驾马车:基于ResNet18的模型剪枝实战(1)

一、背景&#xff1a;为什么需要模型剪枝&#xff1f; 随着深度学习的发展&#xff0c;模型参数量和计算量呈指数级增长。以ResNet18为例&#xff0c;其在ImageNet上的参数量约为1100万&#xff0c;虽然在服务器端运行流畅&#xff0c;但在移动端或嵌入式设备上部署时&#xf…

uni-app学习笔记二十四--showLoading和showModal的用法

showLoading(OBJECT) 显示 loading 提示框, 需主动调用 uni.hideLoading 才能关闭提示框。 OBJECT参数说明 参数类型必填说明平台差异说明titleString是提示的文字内容&#xff0c;显示在loading的下方maskBoolean否是否显示透明蒙层&#xff0c;防止触摸穿透&#xff0c;默…

【大模型RAG】六大 LangChain 支持向量库详细对比

摘要 向量数据库已经成为检索增强生成&#xff08;RAG&#xff09;、推荐系统和多模态检索的核心基础设施。本文从 Chroma、Elasticsearch、Milvus、Redis、FAISS、Pinecone 六款 LangChain 官方支持的 VectorStore 出发&#xff0c;梳理它们的特性、典型应用场景与性能边界&a…

【MySQL】数据库三大范式

目录 一. 什么是范式 二. 第一范式 三. 第二范式 不满足第二范式时可能出现的问题 四. 第三范式 一. 什么是范式 在数据库中范式其实就是一组规则&#xff0c;在我们设计数据库的时候&#xff0c;需要遵守不同的规则要求&#xff0c;设计出合理的关系型数据库&#xff0c;…

Coze工作流-语音故事创作-文本转语音的应用

教程简介 本教程将带着大家去了解怎么样把文本转换成语音&#xff0c;例如说我们要做一些有声故事&#xff0c;我们可能会用上一些语音的技术&#xff0c;来把你创作的故事朗读出来 首先我们创建一个工作流 对各个模块进行编辑&#xff0c;如果觉得系统提示词写的不好&#xf…

5.子网划分及分片相关计算

某公司网络使用 IP 地址空间 192.168.2.0/24&#xff0c;现需将其均分给 市场部 和 研发部 两个子网。已知&#xff1a; &#x1f3e2; 市场部子网 &#x1f5a5;️ 已分配 IP 地址范围&#xff1a;192.168.2.1 ~ 192.168.2.30&#x1f310; 路由器接口 IP&#xff1a;192.16…