vue3写一个简单的时间轴组件
插件版本:
"element-plus": "^2.3.12"
"vue": "^3.0.0"
代码示例:
样式文件style.less:
.el-popper.o-el-tooltip-popper-class {max-width: 300px;white-space: pre-wrap;}
MyTimeLineCol组件:
<template><div class="o-timeline-area" :style="`width: ${width}px`"><div class="o-timeline"><divv-for="(item, index) in timelineDesc":key="index":class="['o-timeline-item', { last: index === timelineDesc.length - 1 }]"><div class="o-tail" /><divclass="o-finish":style="{'--rate': item.ratePercent,'--borderColor': item.rate == 100 ? GREEN_COLOR : BLUE_COLOR}"/><div class="o-dot"><spanclass="solid-dot":class="item.rate == 100 ? 'green' : item.rate == 0 ? 'gray' : 'blue'"></span></div><el-tooltippopper-class="o-el-tooltip-popper-class"effect="dark":content="`${item.leftTitle || ''} ${item.planTime}~${item.finishTime} ${item.desc || '--'}${'\n'}${item.title} ${item.text}`"placement="top"><div class="o-content"><div:class="[display === 'right-only'? 'o-ang-left': index % 2 === 0? 'o-ang-left': 'o-ang-right',item.rate == 100 ? 'green' : item.rate == 0 ? 'gray' : 'blue']"></div><div:class="[display === 'right-only'? 'o-content-right': index % 2 === 1? 'o-content-left': 'o-content-right',item.rate == 100 ? 'green' : item.rate == 0 ? 'gray' : 'blue']":style="{'--width': display === 'right-only' ? '100%' : '50%'}"><div class="time"><span class="time-title">{{ item.leftTitle || '-' }}</span><span>{{ item.planTime }}~{{ item.finishTime }}</span><span>{{ item.desc || '--' }}</span></div><div class="o-content-text"><span>{{ item.title || '--' }}</span><span>{{ item.text || '--' }}</span></div></div></div></el-tooltip></div></div></div>
</template>
<script lang="ts" setup>
import { defineProps, toRefs } from 'vue'
import { ElTooltip } from 'element-plus'
import './style.less'const BLUE_COLOR = '#1890ff'
const GREEN_COLOR = '#52c41a'
interface listItem {leftTitle?: stringplanTime: stringfinishTime: stringdesc?: stringtitle: stringtext: stringratePercent: stringrate: number
}
interface IProps {width?: numberdotLeft: '50%' | '0%'display: 'left-and-right' | 'right-only'timelineDesc: listItem[]
}
const props = defineProps<IProps>()
const { timelineDesc, dotLeft } = toRefs(props)
</script>
<style lang="less" scoped>
@blue: #1890ff;
@green: #52c41a;
@gray: #aaa;
@white: #fff;
@black: #333;
@dotWidth: 10px;
@dotHeight: 10px;
@dotLeft: v-bind(dotLeft);
@tailBorderWidth: 3px;
@itemHeight: 80px;
.o-timeline-area {margin: 0 auto;.o-timeline {.o-timeline-item {position: relative;padding-bottom: 10px;height: @itemHeight;.o-tail {position: absolute;top: @dotHeight;left: calc(@dotLeft - @tailBorderWidth / 2);height: calc(100% - @dotHeight);border-left: @tailBorderWidth solid @gray;}.o-finish {position: absolute;top: @dotHeight;left: calc(@dotLeft - @tailBorderWidth / 2);height: calc((100% - @dotHeight) * var(--rate));border-left: @tailBorderWidth solid var(--borderColor);}.o-dot {position: absolute;width: @dotWidth;height: @dotHeight;left: calc(@dotLeft - @dotWidth / 2);display: flex;justify-content: center;align-items: center;}.solid-dot {width: 100%;height: 100%;border-radius: 50%;background-color: @gray;&.green {background-color: @green;}&.blue {background-color: @blue;}&.gray {background-color: @gray;}}.o-content {height: 100%;position: relative;top: -50%;left: calc(0px - @dotWidth / 2);display: flex;align-items: center;transform: translateY(calc(0px + @dotHeight / 2));}.o-content-left,.o-content-right {width: calc(var(--width) - @dotWidth);top: 8px;margin-left: 0px;word-break: break-all;word-wrap: break-word;font-size: 14px;font-weight: 400;background-color: rgba(@gray, 0.5);border-radius: 5px;padding: 5px 16px 10px;box-sizing: border-box;color: @gray;&.blue {background-color: rgba(@blue, 0.5);.time {color: @white;&-title {color: @black;}}}&.green {background-color: rgba(@green, 0.5);.time {color: @white;&-title {color: @black;}}}.time {display: inline-block;font-size: 14px;font-weight: 400;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;width: 100%;&-title {margin-right: 5px;}}&-text {display: flex;justify-content: space-between;}}.o-content-right {margin-left: calc(@dotLeft + @dotWidth);}.o-ang-left,.o-ang-right {display: block;width: 0;height: 0;border-width: 5px;border-style: solid;}.o-ang-left {position: relative;left: calc(@dotLeft + @dotWidth);border-color: transparent rgba(@gray, 0.5) transparent transparent;&.blue {border-color: transparent rgba(@blue, 0.5) transparent transparent;}&.green {border-color: transparent rgba(@green, 0.5) transparent transparent;}}.o-ang-right {position: relative;left: calc(@dotLeft - @dotWidth / 2);border-color: transparent transparent transparent rgba(@gray, 0.5);&.blue {border-color: transparent transparent transparent rgba(@blue, 0.5);}&.green {border-color: transparent transparent transparent rgba(@green, 0.5);}}}.last {.o-tail,.o-finish {display: none;}}}
}
</style>
调用该时间轴组件(描述内容只在右边显示):
<template><div class="o-main-box"><MyTimeLineCol :timelineDesc="timelineDesc" display="right-only" dotLeft="0%" /></div><div v-if="list.length > VISIBLE_NUM" class="operate" @click="handleClick">{{ flag == 'fold' ? '展开' : '收起' }}<van-icon v-if="flag == 'fold'" name="arrow-down" /><van-icon v-else-if="flag == 'open'" name="arrow-up" /></div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import MyTimeLineCol from './MyTimeLineCol.vue'
const VISIBLE_NUM = 2
const flag = ref('fold')
const visibleNum = ref<number>(VISIBLE_NUM)
const list = ref([{title: '启动',leftTitle: '标题1',planTime: '2019-01-01',finishTime: '2019-12-30',rate: 100,currentRate: 0,text: '100%',ratePercent: 1,desc: '已完成'},{title: '需求确认',leftTitle: '标题2',planTime: '2020-01-01',finishTime: '2020-12-30',rate: 60,currentRate: 0,text: '60%',ratePercent: 0.6,desc: '需求确认中'},{title: '项目开发',leftTitle: '标题3',planTime: '2021-01-01',finishTime: '2021-12-30',rate: 0,currentRate: 0,text: '0%',ratePercent: 0.0,desc: '未开始'},{title: '功能测试',leftTitle: '标题4',planTime: '2022-01-01',finishTime: '2022-12-30',rate: 0,currentRate: 0,text: '0%',ratePercent: 0,desc: '未开始'},{title: '上线',leftTitle: '标题5',planTime: '2023-01-01',finishTime: '2023-12-30',rate: 0,currentRate: 0,text: '0%',ratePercent: 0,desc: '未开始'}
])
const timelineDesc = computed(() => {return list.value.slice(0, visibleNum.value)
})
const handleClick = () => {if (flag.value === 'fold') {flag.value = 'open'visibleNum.value = list.value.length} else {flag.value = 'fold'visibleNum.value = VISIBLE_NUM}
}
</script>
<style lang="less" scoped>
.o-main-box {margin-top: 26px;
}
.operate {color: #9096a5;font-size: 12px;line-height: 20px;height: 20px;margin-top: 18px;text-align: center;cursor: pointer;
}
</style>
调用该时间轴组件(描述内容左右边岔开显示):
<template><div class="o-main-box"><MyTimeLineCol :timelineDesc="timelineDesc" display="left-and-right" dotLeft="50%" /></div><div v-if="list.length > VISIBLE_NUM" class="operate" @click="handleClick">{{ flag == 'fold' ? '展开' : '收起' }}<van-icon v-if="flag == 'fold'" name="arrow-down" /><van-icon v-else-if="flag == 'open'" name="arrow-up" /></div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import MyTimeLineCol from './MyTimeLineCol.vue'
const VISIBLE_NUM = 2
const flag = ref('fold')
const visibleNum = ref<number>(VISIBLE_NUM)
const list = ref([{title: '启动',leftTitle: '标题1',planTime: '2019-01-01',finishTime: '2019-12-30',rate: 100,currentRate: 0,text: '100%',ratePercent: 1,desc: '已完成'},{title: '需求确认',leftTitle: '标题2',planTime: '2020-01-01',finishTime: '2020-12-30',rate: 60,currentRate: 0,text: '60%',ratePercent: 0.6,desc: '需求确认中'},{title: '项目开发',leftTitle: '标题3',planTime: '2021-01-01',finishTime: '2021-12-30',rate: 0,currentRate: 0,text: '0%',ratePercent: 0.0,desc: '未开始'},{title: '功能测试',leftTitle: '标题4',planTime: '2022-01-01',finishTime: '2022-12-30',rate: 0,currentRate: 0,text: '0%',ratePercent: 0,desc: '未开始'},{title: '上线',leftTitle: '标题5',planTime: '2023-01-01',finishTime: '2023-12-30',rate: 0,currentRate: 0,text: '0%',ratePercent: 0,desc: '未开始'}
])
const timelineDesc = computed(() => {return list.value.slice(0, visibleNum.value)
})
const handleClick = () => {if (flag.value === 'fold') {flag.value = 'open'visibleNum.value = list.value.length} else {flag.value = 'fold'visibleNum.value = VISIBLE_NUM}
}
</script>
<style lang="less" scoped>
.o-main-box {margin-top: 26px;
}
.operate {color: #9096a5;font-size: 12px;line-height: 20px;height: 20px;margin-top: 18px;text-align: center;cursor: pointer;
}
</style>