前言
在项目开发中,我需要实现一个支持自定义图标、节点颜色和辅助线的时间轴组件。经过多方搜索,发现现有方案都无法完全满足需求,于是决定自行开发。现将实现过程记录下来,希望能为遇到类似需求的开发者提供参考。
效果预览(以工作经历&教育经历为例)
核心特性
- 完全可定制:图标、节点颜色、连接线独立配置
- 响应式布局:完美适配不同内容长度
组件使用指南
<template>
<stepBar type="work" :stepList="handelData(infoDetail.resumeWorkExpList, 'work')" />
<stepBar class="mt-8" type="education" :stepList="handelData(infoDetail.resumeEduExpList, 'education')" />
</template>
import educationIcon from '@/assets/img/ai/education_icon.png'
import companyIcon from '@/assets/img/ai/company_icon.png'
import { stepBar } from './Components'
const educationIconImg = educationIcon
const companyIconImg = companyIcon
const handelData = (data, type) => {
let stepList = []
if (type === 'work') {
data.forEach((item) => {
const timeStr = [];
if (item.workStartDate) timeStr.push(item.workStartDate);
if (item.workEndDate) timeStr.push(item.workEndDate);
const parts = [];
if (item.workCompany) parts.push(item.workCompany);
if (item.workJobName) parts.push(item.workJobName);
stepList.push({
icon: companyIconImg,
time: timeStr.join(' - '),
desc: parts.join(' · '),
linColor: '#7AC3FF',
dotColor: '#006EF0'
})
})
} else {
data.forEach((item) => {
const timeStr = [];
if (item.eduStartDate) timeStr.push(item.eduStartDate);
if (item.eduEndDate) timeStr.push(item.eduEndDate);
const parts = [];
if (item.schoolName) parts.push(item.schoolName);
if (item.speciality) parts.push(item.speciality);
const educationLabel = translateDict(item.education, dictStore.dicts['topEduDegree']);
parts.push(educationLabel);
stepList.push({
icon: educationIconImg,
time: timeStr.join(' - '),
desc: parts.join(' · '),
linColor: '#B9E4D6',
dotColor: '#19B383'
})
})
}
return stepList
}
组件实现解析
核心代码结构
<template>
<div class="timeline-container">
<div v-for="(item, index) in stepList" :key="index" class="timeline-item"
:class="{ 'last-item': index === stepList.length - 1 }">
<img :src="item.icon" v-if="index === 0" class="current-icon"/>
<div v-else class="timeline-dot" :style="{ background:item.dotColor}"></div>
<div class="text-content ml-12">
<span class="time-label">{{ item.time }}</span>
<span class="desc-text">{{ item.desc }}</span>
</div>
<div v-if="index !== stepList.length - 1" class="timeline-line" :style="{ background:item.linColor}"></div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
type: {
type: String,
default: 'work'
},
stepList: {
type: Array,
default: () => []
}
})
</script>
<style lang="scss" scoped>
.timeline-container {
width: 100%;
padding-left: 3px;
box-sizing: border-box;
}
.timeline-item {
position: relative;
margin-bottom: 8px;
min-height: 20px;
/* background: red; */
}
.text-content{
display: flex;
}
.time-label {
font-size: 14px;
font-weight: normal;
line-height: 20px;
letter-spacing: 0px;
color: #606266;
flex-shrink: 0;
min-width: 118px;
}
.desc-text {
font-size: 14px;
font-weight: normal;
line-height: 20px;
letter-spacing: 0px;
color: #303133;
}
.timeline-dot {
position: absolute;
left: -3px;
top: 5px;
width: 6px;
height: 6px;
border-radius: 50%;
z-index: 2;
}
.current-icon{
position: absolute;
left: -8px;
top: -2px;
width: 16px;
height: 15px;
}
.timeline-line {
position: absolute;
left: -1px;
top: 11px;
bottom: -15px;
width: 2px;
}
.last-item {
margin-bottom: 0;
}
</style>