vue3写一个简单的时间轴组件

插件版本:

"element-plus": "^2.3.12"

"vue": "^3.0.0"

代码示例:

样式文件style.less:

改变el-tooltip样式,可以复制代码到公共样式文件

.el-popper.o-el-tooltip-popper-class {max-width: 300px;white-space: pre-wrap;}

MyTimeLineCol组件:

<template><div class="o-timeline-area" :style="`width: ${width}px;`"><div class="o-timeline"><divv-for="(item, index) in timelineDesc":key="index":class="['o-timeline-item', { last: index === timelineDesc.length - 1 }]"><div class="o-tail" /><divclass="o-finish":style="{'--rate': item.ratePercent,'--borderColor': item.rate == 100 ? GREEN_COLOR : BLUE_COLOR}"/><div class="o-dot"><spanclass="solid-dot":class="item.rate == 100 ? 'green' : item.rate == 0 ? 'gray' : 'blue'"></span></div><el-tooltippopper-class="o-el-tooltip-popper-class"effect="dark":content="`${item.title1 || ''} ${item.title2 || ''} ${item.title3 || '--'}${'\n'}${item.title} ${item.text}`"placement="top"><div class="o-content"><div:class="[display === 'right-only'? 'o-content-right': index % 2 === 1? 'o-content-left': 'o-content-right',item.rate == 100 ? 'green' : item.rate == 0 ? 'gray' : 'blue']":style="{'--width': display === 'right-only' ? '100%' : '50%'}"><div:class="[display === 'right-only'? 'o-ang-left': index % 2 === 0? 'o-ang-left': 'o-ang-right',item.rate == 100 ? 'green' : item.rate == 0 ? 'gray' : 'blue']"></div><div class="time"><span class="time-title1">{{ item.title1 || '-' }}</span><span class="time-title2">{{ item.title2 }}</span><span class="time-title3">{{ item.title3 || '--' }}</span></div><div class="o-content-text"><span>{{ item.title || '--' }}</span><span>{{ item.text || '--' }}</span></div></div></div></el-tooltip></div></div></div>
</template>
<script lang="ts" setup>
import { computed, defineProps, toRefs } from 'vue'
import { ElTooltip } from 'element-plus'const BLUE_COLOR = '#1890ff'
const GREEN_COLOR = '#52c41a'
interface listItem {title1?: stringtitle2: stringtitle3?: stringtitle: stringtext: stringratePercent: stringrate: number
}
interface IProps {width?: numberdisplay: 'left-and-right' | 'right-only'timelineDesc: listItem[]
}
const props = defineProps<IProps>()
const { timelineDesc, display } = toRefs(props)
const dotLeft = computed(() => {if (display.value === 'left-and-right') {return '50%'}return '0%'
})
</script>
<style lang="less" scoped>
@blue: #1890ff;
@green: #52c41a;
@gray: #aaa;
@white: #fff;
@black: #333;
@dotWidth: 10px;
@dotHeight: 10px;
@dotLeft: v-bind(dotLeft);
@tailBorderWidth: 3px;
@itemHeight: 80px;
@angBorderWidth: 5px;
.o-timeline-area {margin: 0 auto;margin-top: @itemHeight / 2;.o-timeline {.o-timeline-item {position: relative;height: @itemHeight;.o-tail {position: absolute;top: @dotHeight;left: calc(@dotLeft - @tailBorderWidth / 2);height: calc(100% - @dotHeight);border-left: @tailBorderWidth solid @gray;}.o-finish {position: absolute;top: @dotHeight;left: calc(@dotLeft - @tailBorderWidth / 2);height: calc((100% - @dotHeight) * var(--rate));border-left: @tailBorderWidth solid var(--borderColor);}.o-dot {position: absolute;width: @dotWidth;height: @dotHeight;left: calc(@dotLeft - @dotWidth / 2);display: flex;justify-content: center;align-items: center;}.solid-dot {width: 100%;height: 100%;border-radius: 50%;background-color: @gray;&.green {background-color: @green;}&.blue {background-color: @blue;}&.gray {background-color: @gray;}}.o-content {height: 100%;display: flex;align-items: center;}.o-content-left,.o-content-right {position: relative;top: calc(@dotHeight / 2 - 50%);margin-left: 0px;word-break: break-all;word-wrap: break-word;font-size: 14px;font-weight: 400;background-color: rgba(@gray, 0.5);border-radius: 5px;padding: 5px 16px 10px;box-sizing: border-box;color: @gray;&.blue {background-color: rgba(@blue, 0.5);.time {color: @white;&-title1 {color: @black;}}}&.green {background-color: rgba(@green, 0.5);.time {color: @white;&-title1 {color: @black;}}}.time {display: inline-block;font-size: 14px;font-weight: 400;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;width: 100%;&-title1 {margin-right: 5px;}&-title2 {margin-right: 5px;}&-title3 {margin-right: 0px;}}&-text {display: flex;justify-content: space-between;}}.o-content-left {width: calc(var(--width) - @dotWidth - 10px);margin-right: 10px;}.o-content-right {width: calc(var(--width) - @dotWidth - 10px);margin-left: calc(@dotLeft + @dotWidth + 10px);}.o-ang-left,.o-ang-right {position: absolute;display: block;width: 0;height: 0;border-width: 5px;border-style: solid;}.o-ang-left {left: calc(0px - @angBorderWidth * 2);top: calc(50% - @angBorderWidth);border-color: transparent rgba(@gray, 0.5) transparent transparent;&.blue {border-color: transparent rgba(@blue, 0.5) transparent transparent;}&.green {border-color: transparent rgba(@green, 0.5) transparent transparent;}}.o-ang-right {right: calc(0px - @angBorderWidth * 2);top: calc(50% - @angBorderWidth);border-color: transparent transparent transparent rgba(@gray, 0.5);&.blue {border-color: transparent transparent transparent rgba(@blue, 0.5);}&.green {border-color: transparent transparent transparent rgba(@green, 0.5);}}}.last {.o-tail,.o-finish {display: none;}}}
}
</style>

TCard组件

<template><div class="t-card"><div class="t-card-view-header"><span class="left-card-line"></span><span class="card-title">{{title}}</span><button @click="gotoDetail">跳转详情</button></div><div class="t-card-box"><slot name="content"></slot></div></div>
</template>
<script lang="ts" setup>
defineProps<{title: string
}>()
const emit = defineEmits(['gotoDetail'])
const gotoDetail = () => {emit('gotoDetail')
}
</script>
<style lang="less" scoped>
.t-card {width: 100%;background-color: #fff;padding: 15px 16px 15px 16px;margin-bottom: 10px;border-radius: 10px;min-height: 215px;
}
.t-card-box {width: 100%;font-size: 14px;
}
.t-card-view-header {width: 100%;display: flex;align-items: center;
}
.left-card-line {display: inline-block;width: 5px;height: 30px;background-color: blue;margin-right: 10px;
}
.card-title {font-size: 20px;font-weight: 700;color: #333;
}
.card-title {flex: 1 1 0%;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;
}
.right-arrow-class {font-size: 20px;color: #aaa;font-weight: 700;
}
</style>

调用该时间轴组件(描述内容只在右边显示):

<template><TCard title="xxx" @gotoDetail="gotoDetail"><template #content><div class="o-main-box"><MyTimeLineCol :timelineDesc="timelineDesc" display="right-only" /></div><div v-if="list.length > VISIBLE_NUM" class="operate" @click="handleClick">{{ flag == 'fold' ? '展开' : '收起' }}<van-icon v-if="flag == 'fold'" name="arrow-down" /><van-icon v-else-if="flag == 'open'" name="arrow-up" /></div></template></TCard>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import TCard from './TCard.vue'
import MyTimeLineCol from './MyTimeLineCol.vue'
const VISIBLE_NUM = 2;
const flag = ref('fold')
const visibleNum = ref<number>(VISIBLE_NUM)const list = ref([{title: '启动',title1: '标题1',title2: '2019-01-01~2019-12-30',title3: '已完成',rate: 100,currentRate: 0,text: '100%',ratePercent: 1,},{title: '需求确认',title1: '标题2',title2: '2020-01-01~2020-12-30',rate: 60,currentRate: 0,text: '60%',ratePercent: 0.6,title3: '需求确认中'},{title: '项目开发',title1: '标题3',title2: '2021-01-01~2021-12-30',rate: 0,currentRate: 0,text: '0%',ratePercent: 0.0,title3: '未开始'},{title: '功能测试',title1: '标题4',title2: '2022-01-01~2022-12-30', rate: 0,currentRate: 0,text: '0%',ratePercent: 0,title3: '未开始'},{title: '上线',title1: '标题5',title2: '2023-01-01~2023-12-30', rate: 0,currentRate: 0,text: '0%',ratePercent: 0,title3: '未开始'}
])
const timelineDesc = computed(() => {return list.value.slice(0, visibleNum.value)
})
const handleClick = () => {if (flag.value === 'fold') {flag.value = 'open'visibleNum.value = list.value.length} else {flag.value = 'fold'visibleNum.value = VISIBLE_NUM}
}
const gotoDetail = () => {alert('跳转详情')
}
</script>
<style lang="less" scoped>
.o-main-box {overflow-x: auto;
}
.operate {color: #9096a5;font-size: 12px;line-height: 20px;height: 20px;margin-top: 18px;text-align: center;cursor: pointer;
}
</style>

调用该时间轴组件(描述内容左右边岔开显示):

<template><TCard title="xxx" @gotoDetail="gotoDetail"><template #content><div class="o-main-box"><MyTimeLineCol :timelineDesc="timelineDesc" display="left-and-right" /></div><div v-if="list.length > VISIBLE_NUM" class="operate" @click="handleClick">{{ flag == 'fold' ? '展开' : '收起' }}<van-icon v-if="flag == 'fold'" name="arrow-down" /><van-icon v-else-if="flag == 'open'" name="arrow-up" /></div></template></TCard>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import TCard from './TCard.vue'
import MyTimeLineCol from './MyTimeLineCol.vue'
const VISIBLE_NUM = 2;
const flag = ref('fold')
const visibleNum = ref<number>(VISIBLE_NUM)const list = ref([{title: '启动',title1: '标题1',title2: '2019-01-01~2019-12-30',title3: '已完成',rate: 100,currentRate: 0,text: '100%',ratePercent: 1,},{title: '需求确认',title1: '标题2',title2: '2020-01-01~2020-12-30',rate: 60,currentRate: 0,text: '60%',ratePercent: 0.6,title3: '需求确认中'},{title: '项目开发',title1: '标题3',title2: '2021-01-01~2021-12-30',rate: 0,currentRate: 0,text: '0%',ratePercent: 0.0,title3: '未开始'},{title: '功能测试',title1: '标题4',title2: '2022-01-01~2022-12-30', rate: 0,currentRate: 0,text: '0%',ratePercent: 0,title3: '未开始'},{title: '上线',title1: '标题5',title2: '2023-01-01~2023-12-30', rate: 0,currentRate: 0,text: '0%',ratePercent: 0,title3: '未开始'}
])
const timelineDesc = computed(() => {return list.value.slice(0, visibleNum.value)
})
const handleClick = () => {if (flag.value === 'fold') {flag.value = 'open'visibleNum.value = list.value.length} else {flag.value = 'fold'visibleNum.value = VISIBLE_NUM}
}
const gotoDetail = () => {alert('跳转详情')
}
</script>
<style lang="less" scoped>
.o-main-box {overflow-x: auto;
}
.operate {color: #9096a5;font-size: 12px;line-height: 20px;height: 20px;margin-top: 18px;text-align: center;cursor: pointer;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/97071.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97071.shtml

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

相关文章

Linex系统网络管理(二)

二、网络连接查看1. netstat作用查看本地服务的网络监听状态查看客户端连接到本地服务的连接状态语法&#xff1a;netstat 选项 &#xff08;-anptu&#xff09;选项作用-n&#xff0c; --numeric显示数字形式地址而不是去解析主机、端口或用户名-a, --all显示所有的监听或连接…

Unity MQTT通讯

首先明确概念&#xff0c;什么是MQTT&#xff1f; MQTT是一种轻量级、基于发布 / 订阅&#xff08;Publish/Subscribe&#xff09;模式的物联网&#xff08;IoT&#xff09;通信协议&#xff0c;在带宽有限、网络不稳定的环境下&#xff0c;实现低功耗、低延迟的设备间通信&am…

JavaSE:类和对象2

一、封装封装的概念面向对象程序三大特性&#xff1a;封装、继承、多态。而类和对象阶段&#xff0c;主要研究的就是封装特性。何为封装呢&#xff1f;简单来说 就是套壳屏蔽细节。例如手机&#xff0c;你看不到任何的内部实现细节&#xff0c;只留下一些公开的接口给你使用&am…

RandAR训练自己的数据集

论文题目:RandAR: Decoder-only Autoregressive Visual Generation in Random Orders(随机顺序下仅解码器的自回归视觉生成) 会议:CVPR2025 摘要:我们介绍了RandAR,一种仅解码器的视觉自回归(AR)模型,能够以任意令牌顺序生成图像。与之前依赖于预定义生成顺序的纯解码器…

基于PHP服装租赁管理系统/基于php的服装管理系统的设计与实现

基于PHP服装租赁管理系统/基于php的服装管理系统的设计与实现

高并发内存池(12)-ThreadCache回收内存

高并发内存池&#xff08;12&#xff09;-ThreadCache回收内存 代码如下&#xff1a; // 释放对象时&#xff0c;链表过长时&#xff0c;回收内存回到中心缓存 void ThreadCache::ListTooLong(FreeList& list, size_t size) {void* start nullptr;void* end nullptr;list…

读大语言模型09超级智能

1. 超级智能1.1. 如果人工智能超越人类智能&#xff0c;可能会成为人类存在的一个重大威胁1.1.1. 对超级人工智能潜在危险最为担忧的群体中&#xff0c;恰恰包括那些否认大语言模型具备真正智能的人1.2. 计算机科学已经成为所有科学领域中不可或缺的重要组成部1.3. GPT具备编写…

阿里云拉取dockers镜像

假如你已经在云服务器上安装了docker需要配置下docker镜像加速代理就行了找到自己的加速网址&#xff1a;然后在云服务器上&#xff0c;修改docker 配置文件&#xff0c;vi /etc/docker/daemon.json没有这个文件的话&#xff0c;需要创建一个。{"default-address-pools&qu…

python自学笔记14 NumPy 线性代数

在Numpy库中有专门的linalg 模块用来做线性代数相关的运算。 本文中线性代数的一般概念不会解释 拆解矩阵 鸢尾花数据矩阵结构如下&#xff08;150 4&#xff09;&#xff1a;取其中的行向量和列向量&#xff1a; # 导入包 import numpy as np from sklearn.datasets import l…

ubuntu20搭建MQTT

sudo apt update sudo apt install mosquitto mosquitto-clients sudo mosquitto_passwd -c /etc/mosquitto/passwd myuser sudo nano /etc/mosquitto/mosquitto.conf# 允许匿名用户连接&#xff08;默认为 true&#xff0c;我们先关闭它&#xff09; allow_anonymous false# 指…

云服务器的主要用途都有哪些?

企业可以利用云服务器构建官方网站&#xff0c;企业官网需要稳定的运行环境来展示产品、服务、公司动态等信息&#xff0c;云服务器提供的高可用性和可扩展性&#xff0c;能保障大量用户同时访问时网站的稳定运行。移动应用的后端服务可以部署在云服务器上&#xff0c;如社交类…

IntelliJ IDEA Debug 模式功能指南

文章目录前言&#x1f4a1; 1. 断点类型与设置&#x1f680; 2. 启动 Debug 模式⚙️ 3. 调试控制按钮详解&#x1f440; 4. 查看与监控变量&#x1f9f0; 5. 高级调试技巧&#x1f48e; 总结前言 作为一名 Java 开发者&#xff0c;熟练掌握调试技巧是提高开发效率的关键。Int…

在pycharmIDE中如何快速掌握一个新模块的使用方法

一、文档使用悬停文档&#xff1a;鼠标悬停在模块/函数上显示文档摘要 (⭐最常用)快速文档&#xff1a;选中标识符按 CtrlQ (Windows/Linux) 或 F1 (Mac)跳转定义&#xff1a;Ctrl左键单击 直接跳转到源码定义处 (⭐最权威)参数提示&#xff1a;输入函数名时自动显示参数列表&a…

win11自定义停止更新方法

一、打开运行窗口&#xff08;winr&#xff09;输入regedit打开注册表编辑器。按照如下路径寻找。计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings二、在Settings页面下右击——>新建——>DWORD(32位)值(D)&#xff0c;并重命名为粉色框中的名字…

Unity委托、匿名方法与事件深度解析:从理论到实战

Unity委托、匿名方法与事件深度解析&#xff1a;从理论到实战 摘要&#xff1a;本文深入剖析Unity中委托、匿名方法与事件的核心机制&#xff0c;结合理论框架与实战案例&#xff0c;帮助开发者掌握高效的事件驱动编程技巧。全文包含12个代码片段及6个核心原理图示框架&#x…

大脑的藏宝图——神经科学如何为自然语言处理(NLP)的深度语义理解绘制新航线

摘要&#xff1a; 截至2025年&#xff0c;大型语言模型&#xff08;LLM&#xff09;已展现出惊人的能力&#xff0c;但其内在的“黑箱”特性和对深层语义理解的局限性也日益凸显。本报告旨在深入探讨一个充满潜力的前沿交叉领域&#xff1a;借鉴地球上最古老、最精密的语言处理…

记录使用ruoyi-flowable开发部署中出现的问题以及解决方法(二)

1.vform的使用与传值 使用动态表单&#xff0c;把当前的用户名传值进动态表单&#xff0c;另外动态表单的上传组件成功后传值会父组件。 在父组件的加载函数中增加&#xff1a; mounted(){this.$refs.vFormRef.addEC("getuploadfile",this);},该方法为给表单加载外…

Apifox 8 月更新|新增测试用例、支持自定义请求示例代码、提升导入/导出 OpenAPI/Swagger 数据的兼容性

Apifox 作为全能 API 工具&#xff0c;正以迅猛之势革新开发者的工作方式&#xff01;想象一下&#xff0c;您正为测试用例编写头疼&#xff0c;或因 OpenAPI 文件导入失败而延误项目&#xff0c;而 Apifox 8 月更新却带来“救命稻草”&#xff1a;新增测试用例功能、自定义请求…

多机多卡微调流程

多机多卡&#xff08;Distributed Training&#xff09;微调大模型是一项复杂但非常高效的任务。它允许你利用多台机器的计算资源来训练一个模型&#xff0c;从而显著缩短训练时间。 多机多卡微调核心流程 整个流程可以概括为以下几个核心步骤&#xff1a; 环境准备与硬件配置 …

Redis(23) RDB和AOF有什么区别?

Redis 的 RDB&#xff08;Redis Database&#xff09;和 AOF&#xff08;Append-Only File&#xff09;是两种主要的持久化机制。每种机制都有其独特的工作方式、优缺点和适用场景。以下是两者的详细比较&#xff0c;并结合代码示例进行解释。 RDB&#xff08;Redis Database&a…