进度条及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">
<!-- 时间开始标签 -->
<span class="time-label">2023.10.07 00:00:00</span>
<div class="timeline-track">
<div class="timeline-track" :style="trackStyle">
<div
v-for="point in store.timePoints"
:key="point.id"
@ -100,7 +100,7 @@
import { ref, defineExpose } from 'vue';
import { useKeyNodeStore1 } from '@/store/keyNodeStore1';
import DynamicGraph from "./graph/dynamicGraph.vue";
import { watch } from 'vue'
import { watch, computed } from 'vue'
const store = useKeyNodeStore1();
const leaderGraphRef = ref(null);
@ -171,6 +171,15 @@ watch(
{ 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 });
</script>
@ -227,7 +236,8 @@ defineExpose({ highlightNode });
.timeline-track {
flex-grow: 1;
height: 4px;
background: linear-gradient(90deg, #1b62a9, #3aa1f8, #1b62a9);
/* background: linear-gradient(90deg, #1b62a9, #3aa1f8, #1b62a9); */
/* background-color: #3B7699; */
margin: 0 15px;
position: relative;
display: flex;

View File

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

View File

@ -53,11 +53,12 @@
</div>
<div class="timeline-container">
<span class="time-label">2023.10.07 00:00:00</span>
<div class="timeline-track">
<div class="timeline-track" :style="trackStyle">
<div
v-for="point in store.timePoints"
:key="point.id"
class="timeline-point-wrapper"
:style="{ left: `${pointPositions[point.id]}%` }"
@click="store.setActiveTimePoint(point.id)"
>
<el-tooltip
@ -92,7 +93,7 @@
</template>
<script setup>
import { ref, defineExpose } from 'vue';
import { ref, defineExpose, watch, computed } from 'vue';
import { useKeyNodeStore2 } from '@/store/keyNodeStore2';
import DynamicGraph from "./graph/dynamicGraph.vue";
@ -108,6 +109,72 @@ const 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 });
</script>
@ -164,11 +231,11 @@ defineExpose({ highlightNode });
.timeline-track {
flex-grow: 1;
height: 4px;
background: linear-gradient(90deg, #1b62a9, #3aa1f8, #1b62a9);
/* background: linear-gradient(90deg, #1b62a9, #3aa1f8, #1b62a9); */
margin: 0 15px;
position: relative;
display: flex;
justify-content: space-between;
/* justify-content: space-between; */
align-items: center;
}
.timeline-point-wrapper {
@ -177,22 +244,37 @@ defineExpose({ highlightNode });
justify-content: center;
height: 20px;
cursor: pointer;
position: absolute;
transform: translateX(-50%);
}
.timeline-point {
width: 10px;
height: 10px;
background-color: #8dc5ff;
width: 18px;
height: 18px;
background-color: transparent;
border-radius: 50%;
border: 1px solid #cce7ff;
border: 1.6px solid #FFE5A4;
transition: transform 0.3s ease;
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 {
transform: scale(1.5);
}
.timeline-point.active {
background-color: #ffc94d;
border-color: #fff;
background-color: transparent;
border-color: #FFE5A4;
transform: scale(1.3);
}
.active-pin {
@ -200,8 +282,7 @@ defineExpose({ highlightNode });
height: 34px;
background-image: url("@/assets/images/point.png");
background-size: cover;
bottom: 5px;
left: -11px;
bottom:6px;
position: absolute;
}
</style>

View File

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