前端实践:打造高度可定制的Vue3时间线组件——图标、节点与连接线的个性化配置
前言
在项目开发中,我需要实现一个支持自定义图标、节点颜色和辅助线的时间轴组件。经过多方搜索,发现现有方案都无法完全满足需求,于是决定自行开发。现将实现过程记录下来,希望能为遇到类似需求的开发者提供参考。
效果预览(以工作经历&教育经历为例)
核心特性
- 完全可定制:图标、节点颜色、连接线独立配置
- 响应式布局:完美适配不同内容长度
组件使用指南
<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>