![]()
<template>
<div class="custom-timeline-container">
<div class="custom-timeline">
<div
v-for="(item, index) in timelineData"
:key="index"
class="custom-timeline-item"
:class="{'custom-top': index % 2 === 0, 'custom-bottom': index % 2 !== 0}"
>
<div class="custom-timeline-node">
<div class="node-circle"></div>
</div>
<div class="custom-timeline-wrapper">
<div class="custom-timeline-content">
<div class="custom-timestamp">{{ item.time }}</div>
<el-card shadow="hover" class="custom-card">
<h4>{{ item.title }}</h4>
<p>{{ item.content }}</p>
<el-tag v-if="item.tag" :type="item.tagType">{{ item.tag }}</el-tag>
</el-card>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const timelineData = ref([
{
time: '2023 Q1',
title: '项目启动',
content: '完成需求分析和团队组建',
tag: '已完成',
tagType: 'success'
},
{
time: '2023 Q2',
title: '原型设计',
content: '完成产品原型和UI设计',
tag: '已完成',
tagType: 'success'
},
{
time: '2023 Q3',
title: '开发阶段',
content: '核心功能开发与单元测试',
tag: '进行中',
tagType: 'primary'
},
{
time: '2023 Q4',
title: '测试阶段',
content: '系统集成测试和性能优化',
tag: '待开始',
tagType: 'info'
},
{
time: '2024 Q1',
title: '产品发布',
content: '正式版本发布和市场推广',
tag: '规划中',
tagType: 'warning'
}
]);
</script>
<style scoped>
.custom-timeline-container {
width: 100%;
overflow-x: auto;
padding: 60px 0;
}
.custom-timeline {
display: flex;
justify-content: center;
position: relative;
min-width: 1000px;
padding: 0 50px;
}
.custom-timeline::before {
content: '';
position: absolute;
left: 0;
right: 0;
top: 50%;
height: 2px;
background: linear-gradient(90deg, #409eff, #67c23a);
z-index: 0;
}
.custom-timeline-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
padding: 0 10px;
}
.custom-top {
align-items: flex-start;
}
.custom-bottom {
align-items: flex-end;
}
.custom-timeline-node {
display: flex;
justify-content: center;
position: relative;
z-index: 1;
margin-bottom: 15px;
}
.custom-bottom .custom-timeline-node {
margin-top: 15px;
margin-bottom: 0;
}
.node-circle {
width: 18px;
height: 18px;
border: 3px solid #409eff;
border-radius: 50%;
background: white;
box-shadow: 0 0 0 4px rgba(64, 158, 255, 0.2);
transition: all 0.3s;
}
.custom-timeline-item:hover .node-circle {
transform: scale(1.2);
box-shadow: 0 0 0 6px rgba(64, 158, 255, 0.3);
}
.custom-timeline-wrapper {
width: 200px;
}
.custom-timeline-content {
display: flex;
flex-direction: column;
align-items: center;
}
.custom-top .custom-timeline-content {
margin-bottom: 20px;
}
.custom-bottom .custom-timeline-content {
margin-top: 20px;
}
.custom-timestamp {
font-weight: bold;
color: #409eff;
margin-bottom: 10px;
font-size: 15px;
}
.custom-card {
border-radius: 8px;
padding: 15px;
text-align: center;
transition: transform 0.3s;
width: 100%;
}
.custom-card:hover {
transform: translateY(-5px);
}
.custom-card h4 {
margin: 0 0 10px 0;
font-size: 16px;
color: #303133;
}
.custom-card p {
margin: 0 0 10px 0;
font-size: 14px;
color: #606266;
}
.custom-timeline-item::after {
content: '';
position: absolute;
top: 50%;
left: calc(100% - 10px);
width: 20px;
height: 2px;
background: #e4e7ed;
z-index: 0;
}
.custom-timeline-item:last-child::after {
display: none;
}
/* 响应式设计 */
@media (max-width: 992px) {
.custom-timeline {
flex-direction: column;
min-width: auto;
padding: 0 20px;
}
.custom-timeline::before {
left: 20px;
top: 0;
bottom: 0;
width: 2px;
height: auto;
}
.custom-timeline-item {
flex-direction: row;
padding: 20px 0;
align-items: flex-start !important;
}
.custom-timeline-node {
margin: 0 15px 0 0 !important;
}
.custom-timeline-wrapper {
width: calc(100% - 50px);
text-align: left;
}
.custom-timeline-content {
align-items: flex-start;
margin: 0 !important;
}
.custom-card {
text-align: left;
}
.custom-timeline-item::after {
display: none;
}
}
</style>