进度条及tab框修改

This commit is contained in:
duanhao 2025-07-18 14:29:29 +08:00
parent 5d2ab9b9e4
commit 4f1dccacc8
4 changed files with 313 additions and 144 deletions

View File

@ -54,7 +54,7 @@
<div class="timeline-container"> <div class="timeline-container">
<!-- 时间开始标签 --> <!-- 时间开始标签 -->
<span class="time-label">2023.10.07 00:00:00</span> <span class="time-label">2023.10.07 00:00:00</span>
<div class="timeline-track"> <div class="timeline-track" :style="trackStyle">
<div <div
v-for="point in store.timePoints" v-for="point in store.timePoints"
:key="point.id" :key="point.id"
@ -100,7 +100,7 @@
import { ref, defineExpose } from 'vue'; import { ref, defineExpose } from 'vue';
import { useKeyNodeStore1 } from '@/store/keyNodeStore1'; import { useKeyNodeStore1 } from '@/store/keyNodeStore1';
import DynamicGraph from "./graph/dynamicGraph.vue"; import DynamicGraph from "./graph/dynamicGraph.vue";
import { watch } from 'vue' import { watch, computed } from 'vue'
const store = useKeyNodeStore1(); const store = useKeyNodeStore1();
const leaderGraphRef = ref(null); const leaderGraphRef = ref(null);
@ -171,6 +171,15 @@ watch(
{ immediate: true, deep: true } { immediate: true, deep: true }
); );
//
const trackStyle = computed(() => {
if (!store.activeTimePoint) return {};
const activePosition = pointPositions.value[store.activeTimePoint] || 0;
return {
background: `linear-gradient(90deg, #3B7699 0%, #00F3FF ${activePosition}%, #3B7699 ${activePosition}%, #3B7699 100%)`
};
});
defineExpose({ highlightNode }); defineExpose({ highlightNode });
</script> </script>
@ -227,7 +236,8 @@ defineExpose({ highlightNode });
.timeline-track { .timeline-track {
flex-grow: 1; flex-grow: 1;
height: 4px; height: 4px;
background: linear-gradient(90deg, #1b62a9, #3aa1f8, #1b62a9); /* background: linear-gradient(90deg, #1b62a9, #3aa1f8, #1b62a9); */
/* background-color: #3B7699; */
margin: 0 15px; margin: 0 15px;
position: relative; position: relative;
display: flex; display: flex;

View File

@ -3,34 +3,45 @@
<img <img
src="../../../assets/images/leaderTitle.png" src="../../../assets/images/leaderTitle.png"
alt="" alt=""
class="headerImage"
style="margin-top: -22px; margin-left: -15px" style="margin-top: -22px; margin-left: -15px"
/> />
<div class="tabs"> <div class="tabs">
<button <div class="tabs-switch">
v-for="tab in tabs" <div
:key="tab" class="switch-item"
:class="{ active: activeTab === tab }" v-for="tab in tabs"
@click="activeTab = tab" :key="tab"
> @click="activeTab = tab"
{{ tab }} :class="{ 'tabsSwich-active': activeTab === tab }"
</button> >
{{ tab }}
</div>
</div>
</div> </div>
<div class="leader-list" ref="leaderListRef"> <div class="leader-list" ref="leaderListRef">
<div <div
v-for="leader in filteredVisibleLeaders" v-for="(leader, index) in filteredVisibleLeaders"
:key="leader.id" :key="leader.id"
class="leader-item" class="leader-item"
@click="emit('selectLeader', leader)" @click="emit('selectLeader', leader)"
> >
<div class="order">{{ index + 1 }}</div>
<img :src="leader.avatar" :alt="leader.name" class="avatar" /> <img :src="leader.avatar" :alt="leader.name" class="avatar" />
<div class="info"> <div class="user-info">
<div class="name"> <div class="username">
<span class="en-name">{{ leader.name }}</span> <span class="en-name">{{ leader.name }}</span>
<span v-if="leader.chineseName" class="cn-name">{{ leader.chineseName }}</span> <span v-if="leader.chineseName" class="cn-name">({{ leader.chineseName }})</span>
</div> </div>
<div class="stats"> <div class="userState">
<span>粉丝数量: {{ leader.followers }}</span> <div class="userState-fancy">
<span>发帖总数: {{ leader.posts }}</span> 粉丝数:
<p>{{ leader.followers }}</p>
</div>
<div class="userState-monitor-count">
发帖数:
<p>{{ leader.posts }}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -67,7 +78,7 @@ watch(filteredVisibleLeaders, async () => {
}, { deep: true }); }, { deep: true });
</script> </script>
<style scoped> <style scoped lang="less">
.left-panel { .left-panel {
width: 350px; width: 350px;
flex-shrink: 0; flex-shrink: 0;
@ -76,89 +87,117 @@ watch(filteredVisibleLeaders, async () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 15px; padding: 15px;
height: 100%;
} }
.tabs { .tabs {
display: flex; padding: 10px 20px;
margin-bottom: 15px;
border-bottom: 2px solid #1a5a9c;
} }
.tabs button { .tabs-switch {
background: none; margin: 0 auto;
border: none; display: flex;
color: #a9c2e0; justify-content: space-between;
padding: 8px 16px; }
.switch-item {
flex: 1;
padding: 4px 0px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #20406e;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; color: #cccccc9d;
position: relative;
} }
.tabs button.active { .switch-item:first-child {
color: #fff; border-radius: 5px 0 0 5px;
font-weight: bold;
} }
.tabs button.active::after { .switch-item:last-child {
content: ""; border-radius: 0px 5px 5px 0px;
position: absolute; }
bottom: -2px; .tabsSwich-active {
left: 0;
width: 100%; width: 100%;
height: 2px; height: 100%;
background-color: #3aa1f8; color: #fff;
opacity: 1;
background-color: #236291;
border: 1px solid #3fa9f5;
} }
.leader-list { .leader-list {
width: 100%; width: 100%;
height: 410px; height: 430px;
flex-grow: 1; margin-top: 10px;
padding-right: 5px;
overflow: auto; overflow: auto;
scrollbar-width: none;
color: #fff; color: #fff;
} }
.leader-list::-webkit-scrollbar { .leader-list::-webkit-scrollbar {
width: 4px; width: 3px;
} height: 5px;
.leader-list::-webkit-scrollbar-track {
background: transparent;
} }
.leader-list::-webkit-scrollbar-thumb { .leader-list::-webkit-scrollbar-thumb {
background: #3aa1f8; background: rgba(147, 210, 255, 0.3);
border-radius: 2px; border-radius: 4px;
}
.leader-list::-webkit-scrollbar-thumb:hover {
background: rgba(147, 210, 255, 0.5);
} }
.leader-item { .leader-item {
width: 100%;
height: 80px;
padding: 10px 0px;
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 5px;
border-bottom: 1px solid rgba(58, 161, 248, 0.2);
cursor: pointer; cursor: pointer;
border-bottom: 0.5px solid rgba(0, 113, 188, 0.5);
}
.order {
color: #fff;
font-size: 16px;
font-style: normal;
font-weight: 400;
margin-right: 15px;
} }
.avatar { .avatar {
width: 50px; width: 48px;
height: 50px; height: 48px;
border-radius: 50%; border-radius: 5px;
margin-right: 15px;
flex-shrink: 0; flex-shrink: 0;
} }
.info { .user-info {
flex: 1;
padding-left: 15px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; justify-content: space-between;
} }
.name { .username {
display: flex; color: #fff;
align-items: baseline;
gap: 8px;
}
.en-name {
font-size: 16px; font-size: 16px;
font-weight: bold; font-family: "微软雅黑";
} }
.cn-name { .cn-name {
font-size: 14px; font-size: 14px;
color: #a9c2e0; color: #a9c2e0;
margin-left: 5px;
} }
.stats { .userState {
font-size: 12px;
color: #a9c2e0;
display: flex; display: flex;
gap: 20px; justify-content: space-between;
margin-top: 8px;
font-size: 13px;
color: #cccccc9d;
div {
display: flex;
p {
color: #fff;
margin-left: 5px;
}
}
}
.userState-monitor-count {
width: 100px; /* 固定宽度确保对齐 */
justify-content: flex-start; /* 内容靠左对齐 */
} }
</style> </style>

View File

@ -53,11 +53,12 @@
</div> </div>
<div class="timeline-container"> <div class="timeline-container">
<span class="time-label">2023.10.07 00:00:00</span> <span class="time-label">2023.10.07 00:00:00</span>
<div class="timeline-track"> <div class="timeline-track" :style="trackStyle">
<div <div
v-for="point in store.timePoints" v-for="point in store.timePoints"
:key="point.id" :key="point.id"
class="timeline-point-wrapper" class="timeline-point-wrapper"
:style="{ left: `${pointPositions[point.id]}%` }"
@click="store.setActiveTimePoint(point.id)" @click="store.setActiveTimePoint(point.id)"
> >
<el-tooltip <el-tooltip
@ -92,7 +93,7 @@
</template> </template>
<script setup> <script setup>
import { ref, defineExpose } from 'vue'; import { ref, defineExpose, watch, computed } from 'vue';
import { useKeyNodeStore2 } from '@/store/keyNodeStore2'; import { useKeyNodeStore2 } from '@/store/keyNodeStore2';
import DynamicGraph from "./graph/dynamicGraph.vue"; import DynamicGraph from "./graph/dynamicGraph.vue";
@ -108,6 +109,72 @@ const highlightNode = (leaderId) => {
leaderGraphRef.value.highlightNode(leaderId); leaderGraphRef.value.highlightNode(leaderId);
} }
}; };
//
const calculatePositions = (timestamp) => {
// : 2023-10-07T00:00:00 2023-10-15T00:00:00
const startTime = new Date('2023-10-07T00:00:00').getTime()
const endTime = new Date('2023-10-15T00:00:00').getTime()
const timeRange = endTime - startTime;
//
const sortedPoints = [...store.timePoints].sort((a, b) => {
return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
});
//
const pointPositions = {};
sortedPoints.forEach(point => {
const pointTime = new Date(point.timestamp).getTime();
const position = ((pointTime - startTime) / timeRange) * 100;
pointPositions[point.id] = Math.max(0, Math.min(100, position));
});
// (20px)
const pointWidthPercentage = 5; //
const minSpacing = pointWidthPercentage; //
//
for (let i = 1; i < sortedPoints.length; i++) {
const prevPoint = sortedPoints[i - 1];
const currPoint = sortedPoints[i];
const prevPosition = pointPositions[prevPoint.id];
const currPosition = pointPositions[currPoint.id];
//
if (currPosition - prevPosition < minSpacing) {
//
pointPositions[currPoint.id] = prevPosition + minSpacing;
//
if (pointPositions[currPoint.id] > 100) {
pointPositions[currPoint.id] = 100;
}
}
}
return pointPositions;
}
//
const pointPositions = ref({});
//
watch(
() => store.timePoints,
() => {
pointPositions.value = calculatePositions();
},
{ immediate: true, deep: true }
);
//
const trackStyle = computed(() => {
if (!store.activeTimePoint) return {};
const activePosition = pointPositions.value[store.activeTimePoint] || 0;
return {
background: `linear-gradient(90deg, #3B7699 0%, #00F3FF ${activePosition}%, #3B7699 ${activePosition}%, #3B7699 100%)`
};
});
defineExpose({ highlightNode }); defineExpose({ highlightNode });
</script> </script>
@ -164,11 +231,11 @@ defineExpose({ highlightNode });
.timeline-track { .timeline-track {
flex-grow: 1; flex-grow: 1;
height: 4px; height: 4px;
background: linear-gradient(90deg, #1b62a9, #3aa1f8, #1b62a9); /* background: linear-gradient(90deg, #1b62a9, #3aa1f8, #1b62a9); */
margin: 0 15px; margin: 0 15px;
position: relative; position: relative;
display: flex; display: flex;
justify-content: space-between; /* justify-content: space-between; */
align-items: center; align-items: center;
} }
.timeline-point-wrapper { .timeline-point-wrapper {
@ -177,22 +244,37 @@ defineExpose({ highlightNode });
justify-content: center; justify-content: center;
height: 20px; height: 20px;
cursor: pointer; cursor: pointer;
position: absolute;
transform: translateX(-50%);
} }
.timeline-point { .timeline-point {
width: 10px; width: 18px;
height: 10px; height: 18px;
background-color: #8dc5ff; background-color: transparent;
border-radius: 50%; border-radius: 50%;
border: 1px solid #cce7ff; border: 1.6px solid #FFE5A4;
transition: transform 0.3s ease; transition: transform 0.3s ease;
position: relative; position: relative;
display: flex;
align-items: center;
justify-content: center;
} }
.timeline-point::after {
content: '';
width: 10px;
height: 10px;
background-color: #F9BD25;
border-radius: 50%;
position: absolute;
}
.timeline-point-wrapper:hover .timeline-point { .timeline-point-wrapper:hover .timeline-point {
transform: scale(1.5); transform: scale(1.5);
} }
.timeline-point.active { .timeline-point.active {
background-color: #ffc94d; background-color: transparent;
border-color: #fff; border-color: #FFE5A4;
transform: scale(1.3); transform: scale(1.3);
} }
.active-pin { .active-pin {
@ -200,8 +282,7 @@ defineExpose({ highlightNode });
height: 34px; height: 34px;
background-image: url("@/assets/images/point.png"); background-image: url("@/assets/images/point.png");
background-size: cover; background-size: cover;
bottom: 5px; bottom:6px;
left: -11px;
position: absolute; position: absolute;
} }
</style> </style>

View File

@ -1,36 +1,47 @@
<template> <template>
<div class="left-panel"> <div class="left-panel">
<img <img
src="@/assets/images/chuanbo-show-title.png" src="../../../assets/images/leaderTitle.png"
alt="" alt=""
class="headerImage"
style="margin-top: -22px; margin-left: -15px" style="margin-top: -22px; margin-left: -15px"
/> />
<div class="tabs"> <div class="tabs">
<button <div class="tabs-switch">
v-for="tab in tabs" <div
:key="tab" class="switch-item"
:class="{ active: activeTab === tab }" v-for="tab in tabs"
@click="activeTab = tab" :key="tab"
> @click="activeTab = tab"
{{ tab }} :class="{ 'tabsSwich-active': activeTab === tab }"
</button> >
{{ tab }}
</div>
</div>
</div> </div>
<div class="leader-list" ref="leaderListRef"> <div class="leader-list" ref="leaderListRef">
<div <div
v-for="leader in filteredVisibleLeaders" v-for="(leader, index) in filteredVisibleLeaders"
:key="leader.id" :key="leader.id"
class="leader-item" class="leader-item"
@click="emit('selectLeader', leader)" @click="emit('selectLeader', leader)"
> >
<div class="order">{{ index + 1 }}</div>
<img :src="leader.avatar" :alt="leader.name" class="avatar" /> <img :src="leader.avatar" :alt="leader.name" class="avatar" />
<div class="info"> <div class="user-info">
<div class="name"> <div class="username">
<span class="en-name">{{ leader.name }}</span> <span class="en-name">{{ leader.name }}</span>
<span v-if="leader.chineseName" class="cn-name">{{ leader.chineseName }}</span> <span v-if="leader.chineseName" class="cn-name">({{ leader.chineseName }})</span>
</div> </div>
<div class="stats"> <div class="userState">
<span>粉丝数量: {{ leader.followers }}</span> <div class="userState-fancy">
<span>发帖总数: {{ leader.posts }}</span> 粉丝数:
<p>{{ leader.followers }}</p>
</div>
<div class="userState-monitor-count">
发帖数:
<p>{{ leader.posts }}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -67,7 +78,7 @@ watch(filteredVisibleLeaders, async () => {
}, { deep: true }); }, { deep: true });
</script> </script>
<style scoped> <style scoped lang="less">
.left-panel { .left-panel {
width: 350px; width: 350px;
flex-shrink: 0; flex-shrink: 0;
@ -76,89 +87,117 @@ watch(filteredVisibleLeaders, async () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 15px; padding: 15px;
height: 100%;
} }
.tabs { .tabs {
display: flex; padding: 10px 20px;
margin-bottom: 15px;
border-bottom: 2px solid #1a5a9c;
} }
.tabs button { .tabs-switch {
background: none; margin: 0 auto;
border: none; display: flex;
color: #a9c2e0; justify-content: space-between;
padding: 8px 16px; }
.switch-item {
flex: 1;
padding: 4px 0px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #20406e;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; color: #cccccc9d;
position: relative;
} }
.tabs button.active { .switch-item:first-child {
color: #fff; border-radius: 5px 0 0 5px;
font-weight: bold;
} }
.tabs button.active::after { .switch-item:last-child {
content: ""; border-radius: 0px 5px 5px 0px;
position: absolute; }
bottom: -2px; .tabsSwich-active {
left: 0;
width: 100%; width: 100%;
height: 2px; height: 100%;
background-color: #3aa1f8; color: #fff;
opacity: 1;
background-color: #236291;
border: 1px solid #3fa9f5;
} }
.leader-list { .leader-list {
width: 100%; width: 100%;
height: 410px; height: 430px;
flex-grow: 1; margin-top: 10px;
padding-right: 5px;
overflow: auto; overflow: auto;
scrollbar-width: none;
color: #fff; color: #fff;
} }
.leader-list::-webkit-scrollbar { .leader-list::-webkit-scrollbar {
width: 4px; width: 3px;
} height: 5px;
.leader-list::-webkit-scrollbar-track {
background: transparent;
} }
.leader-list::-webkit-scrollbar-thumb { .leader-list::-webkit-scrollbar-thumb {
background: #3aa1f8; background: rgba(147, 210, 255, 0.3);
border-radius: 2px; border-radius: 4px;
}
.leader-list::-webkit-scrollbar-thumb:hover {
background: rgba(147, 210, 255, 0.5);
} }
.leader-item { .leader-item {
width: 100%;
height: 80px;
padding: 10px 0px;
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 5px;
border-bottom: 1px solid rgba(58, 161, 248, 0.2);
cursor: pointer; cursor: pointer;
border-bottom: 0.5px solid rgba(0, 113, 188, 0.5);
}
.order {
color: #fff;
font-size: 16px;
font-style: normal;
font-weight: 400;
margin-right: 15px;
} }
.avatar { .avatar {
width: 50px; width: 48px;
height: 50px; height: 48px;
border-radius: 50%; border-radius: 5px;
margin-right: 15px;
flex-shrink: 0; flex-shrink: 0;
} }
.info { .user-info {
flex: 1;
padding-left: 15px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; justify-content: space-between;
} }
.name { .username {
display: flex; color: #fff;
align-items: baseline;
gap: 8px;
}
.en-name {
font-size: 16px; font-size: 16px;
font-weight: bold; font-family: "微软雅黑";
} }
.cn-name { .cn-name {
font-size: 14px; font-size: 14px;
color: #a9c2e0; color: #a9c2e0;
margin-left: 5px;
} }
.stats { .userState {
font-size: 12px;
color: #a9c2e0;
display: flex; display: flex;
gap: 20px; justify-content: space-between;
margin-top: 8px;
font-size: 13px;
color: #cccccc9d;
div {
display: flex;
p {
color: #fff;
margin-left: 5px;
}
}
}
.userState-monitor-count {
width: 100px; /* 固定宽度确保对齐 */
justify-content: flex-start; /* 内容靠左对齐 */
} }
</style> </style>