可自定义设置以下属性:
时间轴内容数组(timelineData),类型:Array<{desc: string, color?: string}>,默认 []
时间轴区域总宽度(width),类型:number|string,单位 px,默认 '100%'
时间线样式(lineStyle),类型: 'solid' | 'dashed' | 'dotted',默认 'solid'
通过设置 mode 可以改变时间轴和内容的相对位置(mode),类型:'left' | 'center' | 'right',默认 'left
当 mode 为 center 时,内容交替展现,内容从左边(left)开始或者右边(right)开始展现(position),类型:'left' | 'right',默认:'left'
时间轴内容数组(TimelineData):
desc:文字描述,类型:string | slot,默认 undefined
color?:圆圈颜色,类型:'blue' | 'green' | 'red' | 'gray' | string,默认 'blue'
效果如下图:在线预览
①创建时间轴组件Timeline.vue:
<script setup lang="ts">
import { ref, computed, watchEffect } from 'vue'
interface Data {
desc: string // 文字描述 string | slot
color?: 'blue' | 'green' | 'red' | 'gray' | string // 圆圈颜色,默认值 blue
}
interface Props {
timelineData?: Data[] // 时间轴内容数组
width?: number | string // 时间轴区域总宽度,单位 px
lineStyle?: 'solid' | 'dashed' | 'dotted' // 时间线样式
mode?: 'left' | 'center' | 'right' // 通过设置 mode 可以改变时间轴和内容的相对位置
position?: 'left' | 'right' // 当 mode 为 center 时,内容交替展现,内容从左边(left)开始或者右边(right)开始展现
}
const props = withDefaults(defineProps<Props>(), {
timelineData: () => [],
width: '100%',
lineStyle: 'solid',
mode: 'left',
position: 'left'
})
enum ColorStyle { // 颜色主题对象
blue = '#1677ff',
green = '#52c41a',
red = '#ff4d4f',
gray = '#00000040'
}
const desc = ref()
const dotsHeight = ref<string[]>([])
const totalWidth = computed(() => {
if (typeof props.width === 'number') {
return props.width + 'px'
} else {
return props.width
}
})
const len = computed(() => {
return props.timelineData.length
})
function getDotsHeight() {
for (let n = 0; n < len.value; n++) {
dotsHeight.value[n] = getComputedStyle(desc.value[n].firstElementChild || desc.value[n], null).getPropertyValue(
'line-height'
)
}
}
watchEffect(
() => {
getDotsHeight()
},
{ flush: 'post' }
)
watchEffect(
() => {
if (props.mode === 'center') {
for (let n = 0; n < len.value; n++) {
if ((n + 1) % 2) {
// odd
if (props.position === 'left') {
desc.value[n].classList.add('desc-alternate-left')
} else {
desc.value[n].classList.add('desc-alternate-right')
}
} else {
// even
if (props.position === 'left') {
desc.value[n].classList.add('desc-alternate-right')
} else {
desc.value[n].classList.add('desc-alternate-left')
}
}
}
}
},
{ flush: 'post' }
)
</script>
<template>
<div class="m-timeline-wrap" :style="`width: ${totalWidth};`">
<div class="m-timeline">
<div
:class="['timeline-item', { 'item-last': index === timelineData.length - 1 }]"
v-for="(data, index) in timelineData"
:key="index"
>
<span class="timeline-tail" :class="`tail-${mode}`" :style="`border-left-style: ${lineStyle};`"></span>
<div class="timeline-dot" :class="`dot-${mode}`" :style="`height: ${dotsHeight[index]}`">
<slot name="dot" :index="index">
<span class="dot-item" v-if="data.color === 'red'" :style="{ borderColor: ColorStyle.red }"></span>
<span class="dot-item" v-else-if="data.color === 'gray'" :style="{ borderColor: ColorStyle.gray }"></span>
<span class="dot-item" v-else-if="data.color === 'green'" :style="{ borderColor: ColorStyle.green }"></span>
<span class="dot-item" v-else-if="data.color === 'blue'" :style="{ borderColor: ColorStyle.blue }"></span>
<span class="dot-item" v-else :style="{ borderColor: data.color || ColorStyle.blue }"></span>
</slot>
</div>
<div ref="desc" :class="`timeline-desc desc-${mode}`">
<slot name="desc" :index="index">{
{ data.desc || '--' }}</slot>
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.m-timeline-wrap {
.m-timeline {
.timeline-item {
position: relative;
padding-bottom: 30px;
.timeline-tail {
position: absolute;
top: 12px;
width: 0;
height: 100%;
border-left-width: 2px;
border-left-color: #e8e8e8;
}
.tail-left {
left: 5px;
}
.tail-center {
left: 0;
right: 0;
margin: 0 auto;
}
.tail-right {
right: 5px;
}
.timeline-dot {
position: absolute;
display: flex;
align-items: center;
.dot-item {
display: inline-block;
width: 12px;
height: 12px;
border-width: 2px;
border-style: solid;
border-radius: 50%;
background: #fff;
}
}
.dot-left {
left: 6px;
transform: translateX(-50%);
}
.dot-center {
left: 50%;
transform: translateX(-50%);
}
.dot-right {
right: 6px;
transform: translateX(50%);
}
.timeline-desc {
font-size: 14px;
line-height: 1.5714285714285714;
word-break: break-all;
}
.desc-left {
margin-left: 25px;
}
.desc-center {
width: calc(50% - 12px);
}
.desc-alternate-left {
text-align: end;
}
.desc-alternate-right {
margin-left: calc(50% + 12px);
}
.desc-right {
margin-right: 25px;
text-align: end;
}
}
.item-last {
.timeline-tail {
display: none;
}
}
}
}
</style>
②在要使用的页面引入:
<script setup lang="ts">
import Timeline from './Timeline.vue'
import { ref } from 'vue'
const timelineData = ref([
{
desc: 'Create a services site 2023-05-24',
color: 'green'
},
{
desc: 'Solve initial network problems 1 Solve initial network problems 2 2023-05-24',
color: 'red'
},
{
desc: 'Technical testing 2023-05-24',
color: 'blue'
},
{
desc: 'Network problems being solved 2023-05-24'
},
{
desc: 'Network problems being solved 2',
color: 'gray'
}
])
</script>
<template>
<div>
<h1>Timeline 时间轴</h1>
<h2 class="mt30 mb10">基本使用</h2>
<Timeline :timeline-data="timelineData" />
<h2 class="mb10">自定义样式</h2>
<Timeline :timeline-data="timelineData">
<template #dot="{ index }">
<span class="big-dot" v-if="index===2"></span>
<svg focusable="false" v-if="index===3" class="u-icon" data-icon="clock-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path><path d="M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z"></path></svg>
</template>
<template #desc="{ index }">
<p class="desc" v-if="index===2">Create a services site</p>
</template>
</Timeline>
<h2 class="mb10">使用虚线</h2>
<Timeline :timeline-data="timelineData" line-style="dashed" />
<h2 class="mb10">右侧时间轴点</h2>
<Timeline :timeline-data="timelineData" mode="right" :width="500" />
<h2 class="mb10">中间时间轴点</h2>
<h3 class="mb10">内容从左边开始交替展现</h3>
<Timeline :timeline-data="timelineData" mode="center" :width="500">
<template #dot="{ index }">
<span class="big-dot" v-if="index===2"></span>
</template>
</Timeline>
<h3 class="mb10">内容从右边开始交替展现</h3>
<Timeline :timeline-data="timelineData" mode="center" position="right" :width="500">
<template #dot="{ index }">
<span class="big-dot" v-if="index===2"></span>
</template>
</Timeline>
</div>
</template>
<style lang="less" scoped>
.big-dot {
display: inline-block;
width: 18px;
height: 18px;
border: 4px solid #1677ff;
border-radius: 50%;
background: #FFF;
}
.u-icon {
fill: #1668dc;
background: #fff;
border-radius: 50%;
}
.desc {
font-size: 16px;
font-weight: 500;
}
</style>