|
|
|
<template>
|
|
|
|
<div :class='["TimeLine1", { "auto-size": autoSize }]' ref="TimeLine1Ref">
|
|
|
|
<!-- 节点 -->
|
|
|
|
<div class="node" v-for="(item, index) in data" :key="index" ref="nodeRefs">
|
|
|
|
<span class="top-label keep-ratio-bottom">
|
|
|
|
<slot name="bottom-label" :data="item">
|
|
|
|
{{ item.time }}
|
|
|
|
</slot>
|
|
|
|
</span>
|
|
|
|
<div class="center">
|
|
|
|
<div class="line" v-if="!index" :style="{ width: `${nodeLinesWidth[-1]}px` }" />
|
|
|
|
<div class="circle keep-ratio" :style="{ '--active-color': !item.isActive ? normalColor : activeColor }">
|
|
|
|
</div>
|
|
|
|
<div class="line" :style="{
|
|
|
|
width: `${nodeLinesWidth[index]}px`,
|
|
|
|
borderImage: getBorderImageStyle(item),
|
|
|
|
}" />
|
|
|
|
</div>
|
|
|
|
<slot name="bottom-label" :data="item">
|
|
|
|
<div class="bottom keep-ratio"
|
|
|
|
:style="{ backgroundImage: `url(${require(`./images/bg${item.isActive ? '-active' : ''}.svg`)})` }">
|
|
|
|
{{ item.label }}
|
|
|
|
</div>
|
|
|
|
</slot>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
|
|
function getPositionAtCenter(element) {
|
|
|
|
const { top, left } = element.getBoundingClientRect();
|
|
|
|
|
|
|
|
return {
|
|
|
|
x: left,
|
|
|
|
y: top
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDistanceBetweenElements(domA, domB) {
|
|
|
|
const domAPosition = getPositionAtCenter(domA);
|
|
|
|
const domBPosition = getPositionAtCenter(domB);
|
|
|
|
|
|
|
|
return Math.hypot(domAPosition.x - domBPosition.x, domAPosition.y - domBPosition.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default {
|
|
|
|
name: 'TimeLine1',
|
|
|
|
props: {
|
|
|
|
data: {
|
|
|
|
type: Array,
|
|
|
|
default: () => Array.from({ length: 15 }).map(() => ({
|
|
|
|
time: "16.36",
|
|
|
|
label: "阿发",
|
|
|
|
isActive: false
|
|
|
|
}))
|
|
|
|
},
|
|
|
|
activeColor: {
|
|
|
|
type: String,
|
|
|
|
default: "#39D5BF"
|
|
|
|
},
|
|
|
|
normalColor: {
|
|
|
|
type: String,
|
|
|
|
default: "#ccc"
|
|
|
|
},
|
|
|
|
lineActiveColor: {
|
|
|
|
type: String,
|
|
|
|
default: "linear-gradient(90deg, rgba(59, 216, 188, 1), rgba(29, 171, 215, 1)}) 2 2"
|
|
|
|
},
|
|
|
|
lineNormalColor: {
|
|
|
|
type: String,
|
|
|
|
default: null
|
|
|
|
},
|
|
|
|
// 自动适配宽度
|
|
|
|
autoSize: {
|
|
|
|
type: Boolean,
|
|
|
|
default: true
|
|
|
|
},
|
|
|
|
filterDistance: {
|
|
|
|
type: Function,
|
|
|
|
default: null
|
|
|
|
}
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
nodeLinesWidth: {}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
data: {
|
|
|
|
handler(data) {
|
|
|
|
let needFilter = false
|
|
|
|
const nodeLinesWidth = [];
|
|
|
|
|
|
|
|
const filterDistance = num => needFilter ? this.filterDistance(num) : num;
|
|
|
|
|
|
|
|
const removeDistance = 20 + 4 * 2;
|
|
|
|
|
|
|
|
function getDistance(index) {
|
|
|
|
return filterDistance(getDistanceBetweenElements(
|
|
|
|
this.$refs.nodeRefs[index].querySelector('.center'),
|
|
|
|
this.$refs.nodeRefs[index + 1].querySelector('.center'))
|
|
|
|
) - removeDistance
|
|
|
|
};
|
|
|
|
|
|
|
|
function getSpecialDistance(index) {
|
|
|
|
return filterDistance(getDistanceBetweenElements(
|
|
|
|
this.$refs.nodeRefs[index].parentElement,
|
|
|
|
this.$refs.nodeRefs[index].querySelector('.center'))
|
|
|
|
) - removeDistance
|
|
|
|
}
|
|
|
|
|
|
|
|
const getLineWidths = () => {
|
|
|
|
nodeLinesWidth.length = 0;
|
|
|
|
|
|
|
|
data.forEach((_, index) => {
|
|
|
|
if (index === data.length - 1) {
|
|
|
|
if (this.autoSize) {
|
|
|
|
nodeLinesWidth[-1] = getSpecialDistance.call(this, 0)
|
|
|
|
nodeLinesWidth[data.length - 1] = getSpecialDistance.call(this, data.length - 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
};
|
|
|
|
|
|
|
|
nodeLinesWidth[index] = getDistance.call(this, index);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.nodeLinesWidth = nodeLinesWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
const timeLine1RefDom = this.$refs.TimeLine1Ref;
|
|
|
|
if (timeLine1RefDom.clientWidth != timeLine1RefDom.getBoundingClientRect().width) {
|
|
|
|
needFilter = true
|
|
|
|
}
|
|
|
|
getLineWidths();
|
|
|
|
})
|
|
|
|
},
|
|
|
|
immediate: true
|
|
|
|
}
|
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
// console.log()
|
|
|
|
console.log(this.$refs.nodeRefs[0].getBoundingClientRect())
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
getBorderImageStyle(item) {
|
|
|
|
const linearColor = item.isActive ? (this.lineActiveColor || `${this.activeColor}, ${this.activeColor}`) : (this.lineNormalColor || `${this.normalColor}, ${this.normalColor}`)
|
|
|
|
|
|
|
|
return `linear-gradient(90deg, ${linearColor}) 2 2`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang='scss' scoped>
|
|
|
|
div.auto-size {
|
|
|
|
justify-content: space-between;
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
.node {
|
|
|
|
flex: 1;
|
|
|
|
min-width: auto !important;
|
|
|
|
|
|
|
|
&:first-child,
|
|
|
|
&:last-child {
|
|
|
|
.line {
|
|
|
|
width: auto;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.TimeLine1 {
|
|
|
|
color: #fff;
|
|
|
|
width: auto;
|
|
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
font-family: PingFang SC, PingFang SC;
|
|
|
|
font-weight: 400;
|
|
|
|
color: #FFFFFF;
|
|
|
|
display: flex;
|
|
|
|
flex-wrap: nowrap;
|
|
|
|
overflow: auto;
|
|
|
|
gap: 15px;
|
|
|
|
padding-left: 15px;
|
|
|
|
|
|
|
|
.node {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
// justify-content: center;
|
|
|
|
flex-direction: column;
|
|
|
|
gap: 3px;
|
|
|
|
min-width: 90px;
|
|
|
|
|
|
|
|
.top-label {
|
|
|
|
line-height: 16px;
|
|
|
|
height: 24px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.center {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
gap: 6px;
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
.circle {
|
|
|
|
border: 1px solid var(--active-color, #39D5BF);
|
|
|
|
border-radius: 50%;
|
|
|
|
width: 15px;
|
|
|
|
height: 15px;
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
&::before {
|
|
|
|
content: "";
|
|
|
|
position: absolute;
|
|
|
|
width: 72%;
|
|
|
|
height: 72%;
|
|
|
|
border-radius: 50%;
|
|
|
|
left: 50%;
|
|
|
|
top: 50%;
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
background-color: var(--active-color, #39D5BF);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.line {
|
|
|
|
position: absolute;
|
|
|
|
height: 0px;
|
|
|
|
opacity: 1;
|
|
|
|
border: 2px solid;
|
|
|
|
border-image: linear-gradient(90deg, rgba(59, 216, 188, 1), rgba(29, 171, 215, 1)) 2 2;
|
|
|
|
|
|
|
|
&:first-child {
|
|
|
|
right: calc(50% + 8px + 6px);
|
|
|
|
}
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
left: calc(50% + 8px + 6px);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&:first-child,
|
|
|
|
&:last-child {
|
|
|
|
.line {
|
|
|
|
width: 60px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.bottom {
|
|
|
|
line-height: 16px;
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
background-size: 100% 100%;
|
|
|
|
min-width: 90px;
|
|
|
|
text-align: center;
|
|
|
|
padding: 6px 12px;
|
|
|
|
height: 27px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|