前言
时间是人类最忠实的记录者,而在数字世界中,Timeline(时间轴) 则是将抽象的时间流转转化为可视化叙事的重要工具。无论是项目里程碑的追溯、用户动态的串联,还是历史事件的回放,Timeline 组件都能以清晰的逻辑与优雅的交互,让用户直观感知时间的脉络。
本文将简单介绍时间轴组件的基本实现。
开始
需求分析
回顾日常开发场景,Timeline 组件的核心结构往往由三个元素构成:
- 时间节点
- 节点要和内容一一对应
- 轴线
- 贯穿所有节点
- 内容容器
- 展示内容的盒子
普通时间轴一般没有和用户交互的功能,所以这里先不做讨论,仅仅只介绍实现时间轴的方案以及属性,所以接下来要做的就是:
- 确定结构
- 确定属性
确定方案
属性
TimelineItemProps
// type.ts
import TimelineItem from './TimeLineItem.vue'
type TimelineItemType = 'primary' | 'success' | 'warning' | 'danger' | 'info'
export interface TimelineItemProps {
// 时间戳
timestamp?: string
// 是否隐藏时间戳
hideTimestamp?: boolean
// 是否垂直居中
center?: boolean
// 时间戳位置
placement?: 'top' | 'bottom'
// 节点类型
type?: TimelineItemType | string
// 节点颜色
color?: string
// 节点大小
size?: 'normal' | 'large' | string
// 图标
icon?: string
// 是否为空心圆点
hollow?: boolean
}
export type TimelineItemInstance = InstanceType<typeof TimelineItem>
时间轴组件由两部分组成,即Timeline 和 TimelineItem,在使用的时候向TimelineItem传递props即可,所以只需要定义TimelineItemProps的接口。
组件
- Timeline
我看了下Element Plus官方Timeline组件是通过render函数的方式来定义的,并且Elemen UI的时候还有反转节点的功能,但是Element Plus的时候就取消了这个功能,使用渲染函数的时候是为了方便节点的reverse,即slots.reverse() (这里是我的个人见解,如有错误还请大佬评论区批评指正),具体细节可以阅读Element两个组件库的源码,这里不做详细介绍。
我们这里的组件同样也没有反转的功能,也就不使用render函数的方式,直接使用模版来定义组件:
// Timeline.vue
<template>
<ul class="yv-timeline">
<slot />
</ul>
</template>
然后是TimelineItem
// TimelineItem.vue
<template>
<li class="yv-timeline-item" :class="{ 'yv-timeline-item-center': center }">
<!-- 时间线 -->
<div class="yv-timeline-item-tail"></div>
<div
v-if="!$slots.dot"
class="yv-timeline-item-node"
:class="{
[`yv-timeline-item-node--${size}`]: size,
[`yv-timeline-item-node--${type}`]: type,
['yv-timeline-item-node--hollow']: hollow
}"
:style="{
backgroundColor: color
}"
>
<!-- 时间轴节点icon -->
<Icon v-if="icon" class="yv-timeline-item-icon" :icon="icon"></Icon>
</div>
<!-- 时间轴节点内容 -->
<div v-if="$slots.dot" class="yv-timeline-dot">
<slot name="dot" />
</div>
<!-- 时间轴内容 -->
<div class="yv-timeline-item-wrapper">
<!-- 时间戳下方展示 -->
<div
v-if="!hideTimestamp && placement === 'top'"
class="yv-timeline-item-timestamp yv-timeline-item-timestamp-top"
>
{{ timestamp }}
</div>
<!-- 文本内容 -->
<div class="yv-timeline-item-content">
<slot />
</div>
<!-- 时间戳下方展示-->
<div
v-if="!hideTimestamp && placement === 'bottom'"
class="yv-timeline-item-timestamp yv-timeline-item-timestamp-bottom"
>
{{ timestamp }}
</div>
</div>
</li>
</template>
代码实现
<script setup lang="ts">
import { defineOptions, withDefaults, defineProps } from 'vue'
import Icon from '../Icon/Icon.vue'
import type { TimelineItemProps } from './types'
defineOptions({
name: 'YvTimelineItem'
})
withDefaults(defineProps<TimelineItemProps>(), {
timestamp: '',
hideTimestamp: false,
center: false,
placement: 'bottom',
size: 'normal',
hollow: false
})
</script>
由于本组件仅有展示功能, 所以不需要其他逻辑代码,仅定义props默认属性即可。
css样式可以直接参考Element官方的样式,这里同样不做讨论。
测试
deepseek真的超级好用,所以这里就尝试用deepseek来写测试用例:
import { describe, test, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import YvTimelineItem from '../TimelineItem.vue'
import Icon from '../../Icon/Icon.vue'
describe('时间轴条目组件测试', () => {
test('应该渲染组件', () => {
const wrapper = mount(YvTimelineItem)
expect(wrapper.find('.yv-timeline-item')).toBeTruthy()
})
test('应该包含默认属性', () => {
const wrapper = mount(YvTimelineItem)
expect(wrapper.props('timestamp')).toEqual('')
expect(wrapper.props('hideTimestamp')).toEqual(false)
expect(wrapper.props('center')).toEqual(false)
expect(wrapper.props('placement')).toEqual('bottom')
expect(wrapper.props('size')).toEqual('normal')
expect(wrapper.props('hollow')).toEqual(false)
})
test('当不隐藏时间戳且位置在顶部时应该显示时间戳', () => {
const wrapper = mount(YvTimelineItem, {
propsData: {
timestamp: '测试时间',
placement: 'top'
}
})
expect(wrapper.find('.yv-timeline-item-timestamp-top').text()).toEqual(
'测试时间'
)
})
test('当不隐藏时间戳且位置在底部时应该显示时间戳', () => {
const wrapper = mount(YvTimelineItem, {
propsData: {
timestamp: '测试时间',
placement: 'bottom'
}
})
expect(wrapper.find('.yv-timeline-item-timestamp-bottom').text()).toEqual(
'测试时间'
)
})
test('当提供dot插槽时应该渲染自定义内容', () => {
const wrapper = mount(YvTimelineItem, {
slots: {
dot: '<div>测试圆点</div>'
}
})
expect(wrapper.find('.yv-timeline-dot').html()).toContain(
'<div>测试圆点</div>'
)
})
test('当提供图标属性时应该显示图标', () => {
const wrapper = mount(YvTimelineItem, {
propsData: {
icon: '测试图标'
},
global: {
stubs: ['Icon']
}
})
const iconElement = wrapper.findComponent(Icon)
expect(iconElement.exists()).toBeTruthy()
expect(wrapper.find('.yv-timeline-item-icon')).toBeTruthy()
})
})
测试结果:
ai真的好用啊,用的越舒服越感觉自己找不到工作了,越感觉自己要被替代了😭😭😭。
总结
本文主要介绍了Timeline组件的实现,算是组件库里边一个比较简单实现的组件了,写完结构直接套用Element Plus官方大大的样式就行了。
题外话:已经好久没有更新写组件库的文章了,一直也没时间,从去年6月开始第一段实习,9月回学校开始秋招,秋招差点就有收获了,结果因为逆天的,,,,,,,没法出去,详情见这里吧 ,所以秋招等于颗粒无收,后边寒假又找了一个实习。
今天有时间更新这个组件是因为上周的一个需求需要时间轴,我当时没想到自己实现,直接用图片代替(也是服了自己,被自己蠢到了),后边还是mt帮我写的,之后移动端我就会写了,今天是实习lastday,实现一下Timeline组件也算是对mt教给我的东西的一个总结了。
后边就要继续春招了,各位大佬们有没有内推啊,bg双非本+非科班,buff叠满,一段中厂实习+一段小厂实习,求捞捞,然后组件库后边会继续更新的(速度应该会很慢),只不过不知道更新哪一个了,大家可以在评论区留下想看的组件库让俺参考参考参考。