页面完成
|
|
@ -15,6 +15,7 @@
|
|||
"element-plus": "^2.6.1",
|
||||
"hls.js": "^1.6.15",
|
||||
"nprogress": "^0.2.0",
|
||||
"odometer": "^0.4.8",
|
||||
"pinia": "^2.1.7",
|
||||
"three": "^0.183.2",
|
||||
"v3-scroll": "^1.0.4",
|
||||
|
|
@ -2037,6 +2038,12 @@
|
|||
"integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/odometer": {
|
||||
"version": "0.4.8",
|
||||
"resolved": "https://registry.npmjs.org/odometer/-/odometer-0.4.8.tgz",
|
||||
"integrity": "sha512-bfKuAhWE/qMCiX9bwX90c5bTpt7MDVeq1e6YsOHQrYokNhv9jhS5JbR9kg6i+FeWmDgalf3VUtwNx1aVaZ8cgg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"element-plus": "^2.6.1",
|
||||
"hls.js": "^1.6.15",
|
||||
"nprogress": "^0.2.0",
|
||||
"odometer": "^0.4.8",
|
||||
"pinia": "^2.1.7",
|
||||
"three": "^0.183.2",
|
||||
"v3-scroll": "^1.0.4",
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 964 B |
|
After Width: | Height: | Size: 559 B |
|
After Width: | Height: | Size: 468 KiB |
|
After Width: | Height: | Size: 477 KiB |
|
After Width: | Height: | Size: 459 KiB |
|
After Width: | Height: | Size: 468 KiB |
|
|
@ -168,7 +168,7 @@ watch(stationOptions, options => {
|
|||
|
||||
:deep(.el-input__wrapper) {
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
border: none !important;
|
||||
box-shadow: none;
|
||||
background: transparent !important;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,29 @@
|
|||
<template>
|
||||
<section class="traffic-1">1</section>
|
||||
<section class="traffic-1">
|
||||
<div class="rank-list">
|
||||
<TrafficRankRow v-for="item in rankList" :key="item.rank" :item="item" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import TrafficRankRow from './TrafficRankRow.vue'
|
||||
|
||||
const rankList = ref([
|
||||
{ rank: 1, lines: '1,3,7,15', station: '上海火车站', type: '进出站', today: '8.40', compare: '6.20', percent: 92 },
|
||||
{ rank: 2, lines: '1,3,7,15', station: '上海体育场', type: '进出站', today: '8.20', compare: '5.20', percent: 80 },
|
||||
{ rank: 3, lines: '1,3,7,15', station: '延安西路', type: '进出站', today: '6.40', compare: '3.20', percent: 70 },
|
||||
{ rank: 4, lines: '1,3,7,15', station: '上大路', type: '进出站', today: '6.20', compare: '6.20', percent: 50 },
|
||||
{ rank: 5, lines: '1,3,7,15', station: '上海体育馆', type: '进出站', today: '6.20', compare: '6.20', percent: 40 },
|
||||
{ rank: 6, lines: '1,3,7,15', station: '上海南站', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
||||
{ rank: 7, lines: '1,3,7,15', station: '大渡河路', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
||||
{ rank: 8, lines: '1,3,7,15', station: '大渡河路', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
||||
{ rank: 9, lines: '1,3,7,15', station: '红宝石路', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
||||
{ rank: 10, lines: '1,3,7,15', station: '吴中路', type: '进出站', today: '6.20', compare: '6.20', percent: 10 }
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.traffic-1 {
|
||||
position: absolute;
|
||||
|
|
@ -17,5 +39,18 @@
|
|||
background: url('/imgs/traffic/1-bg.png') no-repeat center center;
|
||||
background-size: cover;
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
.rank-list {
|
||||
position: absolute;
|
||||
top: 140px;
|
||||
right: 10px;
|
||||
bottom: 30px;
|
||||
left: 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,162 +1,26 @@
|
|||
<template>
|
||||
<div class="traffic-2">
|
||||
<!-- <div class="module-title">模块四</div> -->
|
||||
<div class="module-body">
|
||||
<div class="scroll-wrapper">
|
||||
<div class="scroll-list">
|
||||
<div class="list-item" v-for="(item, index) in stationData" :key="item.id + '-' + index">
|
||||
<div class="cell-bg">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: item.progress + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rank" :class="'rank-' + ((index % stationData.length) + 1)">{{ String(item.rank).padStart(2,
|
||||
'0')
|
||||
}}</div>
|
||||
<div class="line-numbers">
|
||||
<span v-for="lineId in getLineIdList(item.lineId)" :key="lineId" class="line-number-badge"
|
||||
:style="getLineNumberStyle(lineId)">
|
||||
{{ lineId }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="station-name">{{ item.stationName }}</div>
|
||||
<div class="action-type">{{ item.actionType }}</div>
|
||||
<!-- <div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: item.progress + '%' }"></div>
|
||||
</div> -->
|
||||
<div class="in-count">
|
||||
<span class="highlight">{{ item.inCount }}</span>
|
||||
<span class="unit">人</span>
|
||||
</div>
|
||||
<div class="out-count">
|
||||
<span :style="getOutCountColorStyle(item)">{{ getOutCountPercentage(item) }}</span>
|
||||
<span class="unit">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rank-list">
|
||||
<TrafficRankRow v-for="item in rankList" :key="item.rank" :item="item" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import server from '@/utils/service'
|
||||
import { ref } from 'vue'
|
||||
import TrafficRankRow from './TrafficRankRow.vue'
|
||||
|
||||
const stationData = ref([])
|
||||
|
||||
const fetchStationMaxList = async () => {
|
||||
try {
|
||||
const response = await server.getStationMaxList()
|
||||
if (response.data.success) {
|
||||
// console.log('原始最大客流列表:', response.data.data)
|
||||
const maxTotal = Math.max(...response.data.data.map(item => parseFloat(item.total)))
|
||||
stationData.value = response.data.data.map((item, index) => ({
|
||||
id: item.stationId,
|
||||
rank: index + 1,
|
||||
stationName: item.stationName,
|
||||
actionType: '进出站',
|
||||
progress: Math.round((parseFloat(item.total) / maxTotal) * 100),
|
||||
inCount: item.total,
|
||||
outCount: item.compareTotal,
|
||||
lineId: item.lineId
|
||||
}))
|
||||
console.log('站点最大客流列表:', stationData.value)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取站点最大客流列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const currentIndex = ref(0)
|
||||
const transitionState = ref(true)
|
||||
let timer = null
|
||||
const itemHeight = 70
|
||||
const visibleCount = 3
|
||||
|
||||
const displayList = computed(() => {
|
||||
return stationData.value
|
||||
})
|
||||
|
||||
const getLineIdList = (lineId) => {
|
||||
if (!lineId) {
|
||||
return []
|
||||
}
|
||||
return String(lineId).split(',').map(id => id.trim())
|
||||
}
|
||||
|
||||
const getLineNumberStyle = (lineId) => {
|
||||
const lineNum = parseInt(lineId)
|
||||
const colors = {
|
||||
1: '#E6002A',
|
||||
2: '#8CC220',
|
||||
3: '#FBD61D',
|
||||
4: '#451E83',
|
||||
5: '#944D9A',
|
||||
6: '#D40068',
|
||||
7: '#ED7010',
|
||||
8: '#0095D9',
|
||||
9: '#87CAED',
|
||||
10: '#C6AFD4',
|
||||
11: '#871C2B',
|
||||
12: '#007A60',
|
||||
13: '#E999C0',
|
||||
14: '#9A982F',
|
||||
15: '#C4AF8C',
|
||||
16: '#98D1C0',
|
||||
17: '#B6766C',
|
||||
18: '#C09453'
|
||||
}
|
||||
const textColors = {
|
||||
4: '#ffffff'
|
||||
}
|
||||
const bgColor = colors[lineNum] || '#ffcc00'
|
||||
const textColor = textColors[lineNum] || '#000000'
|
||||
return {
|
||||
backgroundColor: bgColor,
|
||||
color: textColor
|
||||
}
|
||||
}
|
||||
|
||||
const getOutCountPercentage = (item) => {
|
||||
if (!item.outCount || item.outCount === 0) {
|
||||
return '0.00'
|
||||
}
|
||||
const percentage = ((item.inCount - item.outCount) / item.inCount) * 100
|
||||
// return Math.abs(percentage).toFixed(2)
|
||||
return percentage.toFixed(2)
|
||||
}
|
||||
|
||||
const getOutCountColorStyle = (item) => {
|
||||
if (!item.outCount || item.outCount === 0) {
|
||||
return { color: '#ffffff', fontWeight: 'bold' }
|
||||
}
|
||||
const percentage = (item.inCount - item.outCount) / item.inCount
|
||||
if (percentage > 0) {
|
||||
return { color: '#ff3333', fontWeight: 'bold' }
|
||||
} else if (percentage < 0) {
|
||||
return { color: '#00ff00', fontWeight: 'bold' }
|
||||
} else {
|
||||
return { color: '#ffffff', fontWeight: 'bold' }
|
||||
}
|
||||
}
|
||||
|
||||
const startScroll = () => {
|
||||
}
|
||||
|
||||
const handleScrollEnd = (index) => {
|
||||
console.log('滚动到第', index, '项')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchStationMaxList()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
})
|
||||
const rankList = ref([
|
||||
{ rank: 1, lines: '1,3,7,15', station: '上海火车站', type: '进出站', today: '18.40', compare: '6.20', percent: 92 },
|
||||
{ rank: 2, lines: '3,7', station: '上海体育场', type: '进出站', today: '18.20', compare: '5.20', percent: 80 },
|
||||
{ rank: 3, lines: '1,3,7,15', station: '延安西路', type: '进出站', today: '16.40', compare: '3.20', percent: 70 },
|
||||
{ rank: 4, lines: '1,3,7', station: '上大路', type: '进出站', today: '6.20', compare: '6.20', percent: 50 },
|
||||
{ rank: 5, lines: '1,15', station: '上海体育馆', type: '进出站', today: '6.20', compare: '6.20', percent: 40 },
|
||||
{ rank: 6, lines: '1,3,7,15', station: '上海南站', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
||||
{ rank: 7, lines: '1,3,15', station: '大渡河路', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
||||
{ rank: 8, lines: '1,3,7,15', station: '大渡河路', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
||||
{ rank: 9, lines: '1,3', station: '红宝石路', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
||||
{ rank: 10, lines: '15', station: '吴中路', type: '进出站', today: '6.20', compare: '6.20', percent: 10 }
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
@ -174,256 +38,22 @@ onUnmounted(() => {
|
|||
background-size: cover;
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
.module-title {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding: 0 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #00d4ff;
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
border-bottom: 1px solid rgba(0, 212, 255, 0.2);
|
||||
|
||||
:deep(.traffic-rank-row) {
|
||||
grid-template-columns: 66fr 110fr 210fr 120fr 150fr 140fr;
|
||||
}
|
||||
|
||||
.module-body {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
.rank-list {
|
||||
position: absolute;
|
||||
top: 140px;
|
||||
right: 22px;
|
||||
bottom: 30px;
|
||||
left: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.scroll-wrapper {
|
||||
overflow: visible;
|
||||
margin-top: 100px;
|
||||
// background: yellow;
|
||||
|
||||
.scroll-list {
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 70px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.cell-bg {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 742.6px;
|
||||
height: 44px;
|
||||
background: url('/imgs/cell.png') no-repeat center center;
|
||||
background-size: 100% 100%;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.rank {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 40px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
|
||||
// 10个等级
|
||||
&.rank-1 {
|
||||
background: linear-gradient(90deg, #00FFFF 0%, #00FFFF 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
&.rank-2 {
|
||||
background: linear-gradient(90deg, #00FFFF 0%, #00FFFF 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
&.rank-3 {
|
||||
background: linear-gradient(90deg, #00FFFF 0%, #00FFFF 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
&.rank-4 {
|
||||
background: linear-gradient(90deg, #fff 0%, #fff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
&.rank-5 {
|
||||
background: linear-gradient(90deg, #fff 0%, #fff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
&.rank-6 {
|
||||
background: linear-gradient(90deg, #fff 0%, #fff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
&.rank-7 {
|
||||
background: linear-gradient(90deg, #fff 0%, #fff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
&.rank-8 {
|
||||
background: linear-gradient(90deg, #fff 0%, #fff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
&.rank-9 {
|
||||
background: linear-gradient(90deg, #fff 0%, #fff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
&.rank-10 {
|
||||
background: linear-gradient(90deg, #fff 0%, #fff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.line-numbers {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
width: 70px;
|
||||
// margin-left: 10px;
|
||||
flex-shrink: 0;
|
||||
// background-color: yellow;
|
||||
|
||||
.line-number-badge {
|
||||
width: 20px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.station-name {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 140px;
|
||||
font-size: 23px;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
// margin-left: 20px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.action-type {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 90px;
|
||||
font-size: 23px;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
margin-left: 76px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 3px;
|
||||
margin-left: 60px;
|
||||
margin-top: 37px;
|
||||
overflow: hidden;
|
||||
width: 500px;
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(270deg, #00FFFF 0%, rgba(0, 183, 255, 0.18) 100%);
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.in-count {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 150px;
|
||||
text-align: left;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #47ffff;
|
||||
flex-shrink: 0;
|
||||
margin-left: 50px;
|
||||
padding-bottom: 8px;
|
||||
// background-color: yellow;
|
||||
|
||||
.highlight {
|
||||
color: #47ffff;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-size: 10px;
|
||||
color: #47ffff;
|
||||
margin-left: 2px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.out-count {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 120px;
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
flex-shrink: 0;
|
||||
margin-left: 0px;
|
||||
padding-bottom: 8px;
|
||||
|
||||
.unit {
|
||||
font-size: 10px;
|
||||
color: #FFFFFF;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,453 @@
|
|||
<template>
|
||||
<section class="traffic-3">3</section>
|
||||
<section class="traffic-3">
|
||||
<div class="chart-legend">
|
||||
<span class="legend-item legend-item--actual">实际</span>
|
||||
<span class="legend-item legend-item--compare">对比日(2026-03-05)</span>
|
||||
<span class="legend-item legend-item--history">历史峰值</span>
|
||||
<span class="legend-unit">单位:万人次</span>
|
||||
</div>
|
||||
|
||||
<div class="line-chart-grid">
|
||||
<article v-for="(item, index) in chartCards" :key="item.lineId" class="line-chart-card">
|
||||
<div class="line-chart-header">
|
||||
<span class="line-badge" :style="getLineStyle(item.lineId)">{{ item.lineId }}</span>
|
||||
<span class="line-title">累计<br>客流</span>
|
||||
<div class="total-digits">
|
||||
<span v-for="(_, digitIndex) in targetTotals[index]" :key="`${item.lineId}-${digitIndex}`" class="digit-box">
|
||||
<span
|
||||
:ref="el => setDigitRef(el, index, digitIndex)"
|
||||
class="digit-odometer"
|
||||
>0</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-chart class="line-chart" :option="item.option" autoresize />
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.traffic-3 {
|
||||
position: absolute;
|
||||
left: 1689px;
|
||||
top: 128px;
|
||||
width: 1250px;
|
||||
height: 895px;
|
||||
background: url('/imgs/traffic/3-bg.png') no-repeat center center;
|
||||
background-size: cover;
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import VChart from 'vue-echarts'
|
||||
import * as echarts from 'echarts'
|
||||
import Odometer from 'odometer'
|
||||
import 'odometer/themes/odometer-theme-default.css'
|
||||
import { METRO_LINES } from '@/utils/const'
|
||||
|
||||
const hours = ['5', '7', '9', '11', '13', '15', '17', '19', '21', '23']
|
||||
const pointGlowSymbol = 'image:///imgs/traffic/4-item.png'
|
||||
|
||||
const lineCards = ref([
|
||||
{
|
||||
lineId: 3,
|
||||
total: '2746782',
|
||||
actual: [2600, 4700, 2400, 3900, 2300, 4100, 2600, 3300, 1700, 2200],
|
||||
compare: [4850, 4550, 4650, 4300, 4300, 4550, 4700, 3900, 3900, 3000],
|
||||
history: [5000, 4600, 4700, 4200, 4200, 4450, 4600, 4300, 3900, 3600]
|
||||
},
|
||||
{
|
||||
lineId: 4,
|
||||
total: '2746782',
|
||||
actual: [700, 950, 1550, 4200, 1700, 1350, 1600, 1550, 2500, 1400],
|
||||
compare: [1150, 1450, 2200, 3900, 2500, 1550, 1700, 1650, 2950, 2050],
|
||||
history: [1350, 2200, 3100, 4450, 3000, 2650, 2550, 2750, 4200, 4550]
|
||||
},
|
||||
{
|
||||
lineId: 7,
|
||||
total: '2746782',
|
||||
actual: [2900, 3300, 5000, 2300, 2000, 1900, 2600, 4050, 1700, 1700],
|
||||
compare: [3200, 3700, 5200, 2500, 2400, 2500, 3100, 4700, 2400, 2400],
|
||||
history: [3300, 4100, 5300, 2700, 2650, 2900, 3500, 4950, 3300, 2700]
|
||||
},
|
||||
{
|
||||
lineId: 15,
|
||||
total: '2746782',
|
||||
actual: [3000, 3600, 4550, 2400, 3900, 2200, 3400, 4050, 2800, 3300],
|
||||
compare: [4700, 4300, 4500, 4100, 4100, 4300, 4400, 4550, 4000, 3900],
|
||||
history: [4850, 4350, 4550, 4100, 4100, 4300, 4450, 4500, 3950, 3600]
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
lineCards.value[0].total = '8846081'
|
||||
}, 8000);
|
||||
|
||||
const chartCards = computed(() => {
|
||||
return lineCards.value.map(item => ({
|
||||
...item,
|
||||
option: createChartOption(item)
|
||||
}))
|
||||
})
|
||||
|
||||
const targetTotals = computed(() => lineCards.value.map(item => item.total.split('')))
|
||||
const digitEls = []
|
||||
const odometerInstances = []
|
||||
|
||||
const setDigitRef = (el, cardIndex, digitIndex) => {
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
|
||||
digitEls[cardIndex] ||= []
|
||||
digitEls[cardIndex][digitIndex] = el
|
||||
}
|
||||
|
||||
const initOdometers = () => {
|
||||
targetTotals.value.forEach((digits, cardIndex) => {
|
||||
odometerInstances[cardIndex] ||= []
|
||||
|
||||
digits.forEach((_, digitIndex) => {
|
||||
const el = digitEls[cardIndex]?.[digitIndex]
|
||||
|
||||
if (!el || odometerInstances[cardIndex][digitIndex]) {
|
||||
return
|
||||
}
|
||||
|
||||
odometerInstances[cardIndex][digitIndex] = new Odometer({
|
||||
el,
|
||||
value: 0,
|
||||
format: 'd',
|
||||
duration: 1200,
|
||||
theme: 'default'
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const updateOdometers = () => {
|
||||
targetTotals.value.forEach((digits, cardIndex) => {
|
||||
digits.forEach((digit, digitIndex) => {
|
||||
odometerInstances[cardIndex]?.[digitIndex]?.update(Number(digit))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
watch(targetTotals, async () => {
|
||||
await nextTick()
|
||||
initOdometers()
|
||||
updateOdometers()
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
initOdometers()
|
||||
requestAnimationFrame(updateOdometers)
|
||||
})
|
||||
|
||||
const getLineStyle = lineId => {
|
||||
const theme = METRO_LINES[lineId] || {
|
||||
bg: '#2bd5ff',
|
||||
color: '#00194a'
|
||||
}
|
||||
|
||||
return {
|
||||
backgroundColor: theme.bg,
|
||||
color: theme.color
|
||||
}
|
||||
}
|
||||
|
||||
const createChartOption = item => ({
|
||||
backgroundColor: 'transparent',
|
||||
animationDuration: 900,
|
||||
grid: {
|
||||
left: 6,
|
||||
right: 34,
|
||||
top: 16,
|
||||
bottom: 14,
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: hours,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(167, 205, 255, 0.45)'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(226, 239, 255, 0.96)',
|
||||
fontSize: 14,
|
||||
fontWeight: 600,
|
||||
margin: 10
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 5000,
|
||||
interval: 1000,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(226, 239, 255, 0.96)',
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
margin: 10
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(219, 235, 255, 0.32)',
|
||||
type: 'dashed',
|
||||
width: 1.5
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'scatter',
|
||||
data: hours.map((hour, index) => [hour, item.actual[index]]),
|
||||
symbol: pointGlowSymbol,
|
||||
symbolSize: [38, 88],
|
||||
symbolOffset: [3, 0],
|
||||
symbolKeepAspect: false,
|
||||
silent: true,
|
||||
z: 3,
|
||||
tooltip: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '实际',
|
||||
type: 'line',
|
||||
data: item.actual,
|
||||
smooth: false,
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
z: 5,
|
||||
lineStyle: {
|
||||
color: '#20cfff',
|
||||
width: 3,
|
||||
shadowBlur: 8,
|
||||
shadowColor: 'rgba(32, 207, 255, 0.55)'
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#dffbff',
|
||||
borderColor: '#20cfff',
|
||||
borderWidth: 2
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(27, 218, 255, 0.86)' },
|
||||
{ offset: 0.58, color: 'rgba(21, 144, 226, 0.48)' },
|
||||
{ offset: 1, color: 'rgba(13, 74, 166, 0.16)' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '对比日(2026-03-05)',
|
||||
type: 'line',
|
||||
data: item.compare,
|
||||
smooth: false,
|
||||
symbol: 'none',
|
||||
z: 3,
|
||||
lineStyle: {
|
||||
color: '#f5c400',
|
||||
width: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '历史峰值',
|
||||
type: 'line',
|
||||
data: item.history,
|
||||
smooth: false,
|
||||
symbol: 'none',
|
||||
z: 3,
|
||||
lineStyle: {
|
||||
color: '#ff1d1d',
|
||||
width: 2
|
||||
}
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0, 18, 55, 0.92)',
|
||||
borderColor: 'rgba(31, 205, 255, 0.45)',
|
||||
textStyle: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.traffic-3 {
|
||||
position: absolute;
|
||||
left: 1689px;
|
||||
top: 128px;
|
||||
width: 1250px;
|
||||
height: 895px;
|
||||
background: url('/imgs/traffic/3-bg.png') no-repeat center center;
|
||||
background-size: cover;
|
||||
|
||||
.chart-legend {
|
||||
position: absolute;
|
||||
top: 64px;
|
||||
left: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
color: #ffffff;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
position: relative;
|
||||
padding-left: 18px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
&--actual::before {
|
||||
background: #20d8ff;
|
||||
}
|
||||
|
||||
&--compare::before {
|
||||
background: #f5c400;
|
||||
}
|
||||
|
||||
&--history::before {
|
||||
background: #ff1d1d;
|
||||
}
|
||||
}
|
||||
|
||||
.legend-unit {
|
||||
margin-left: -12px;
|
||||
}
|
||||
|
||||
.line-chart-grid {
|
||||
position: absolute;
|
||||
top: 96px;
|
||||
right: 23px;
|
||||
bottom: 22px;
|
||||
left: 23px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-template-rows: repeat(2, minmax(0, 1fr));
|
||||
gap: 27px 26px;
|
||||
}
|
||||
|
||||
.line-chart-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: rgba(27, 83, 165, 0.25);
|
||||
|
||||
}
|
||||
|
||||
.line-chart-header {
|
||||
position: absolute;
|
||||
top: 36px;
|
||||
left: 20px;
|
||||
right: 22px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.line-badge {
|
||||
width: 39px;
|
||||
height: 39px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.line-title {
|
||||
margin-left: 21px;
|
||||
color: #ffffff;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.total-digits {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-left: 47px;
|
||||
}
|
||||
|
||||
.digit-box {
|
||||
width: 39px;
|
||||
height: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
background: url('/imgs/traffic/3-item.png') no-repeat center center;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.digit-odometer {
|
||||
width: 39px;
|
||||
height: 44px;
|
||||
display: block;
|
||||
color: #ffffff;
|
||||
font-family: inherit;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 44px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.digit-odometer :deep(.odometer-digit),
|
||||
.digit-odometer :deep(.odometer-digit-spacer) {
|
||||
width: 39px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
.digit-odometer :deep(.odometer-digit-inner) {
|
||||
width: 39px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
.digit-odometer :deep(.odometer-value) {
|
||||
width: 39px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.line-chart {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 14px;
|
||||
left: 10px;
|
||||
height: 238px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -159,8 +159,8 @@ const chartOption = computed(() => ({
|
|||
symbol: pointGlowSymbol,
|
||||
symbolSize: [47, 109],
|
||||
symbolOffset: [4, 0],
|
||||
symbolKeepAspect: true,
|
||||
silent: true,
|
||||
symbolKeepAspect: false,
|
||||
silent: false,
|
||||
z: 4,
|
||||
tooltip: {
|
||||
show: false
|
||||
|
|
|
|||
|
|
@ -167,12 +167,13 @@ const summaryCards = ref([
|
|||
.rank-no {
|
||||
min-width: 74px;
|
||||
font-size: 26px;
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0 0 12px rgba(0, 210, 255, 0.15);
|
||||
|
||||
@each $rank, $color in $rank-colors {
|
||||
&--#{$rank} {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,21 @@
|
|||
<template>
|
||||
<section class="traffic-6">6</section>
|
||||
<section class="traffic-6">
|
||||
<div class="traffic-image-list">
|
||||
<img
|
||||
v-for="item in imageList"
|
||||
:key="item"
|
||||
class="traffic-image"
|
||||
:src="`/imgs/traffic/${item}`"
|
||||
alt=""
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const imageList = ['image1.png', 'image2.png', 'image3.png', 'image4.png']
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.traffic-6 {
|
||||
position: absolute;
|
||||
|
|
@ -10,8 +24,29 @@
|
|||
width: 2923px;
|
||||
height: 535px;
|
||||
opacity: 1;
|
||||
background: url('/imgs/traffic/6-bg.png') no-repeat center center;
|
||||
background: url('/imgs/traffic/6-bg.png') no-repeat center center;
|
||||
background-size: cover;
|
||||
box-sizing: border-box;
|
||||
|
||||
.traffic-image-list {
|
||||
position: absolute;
|
||||
top: 58px;
|
||||
left: 24px;
|
||||
right: 24px;
|
||||
display: grid;
|
||||
grid-template-columns: 702px 689px 702px 702px;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-image {
|
||||
width: 100%;
|
||||
height: 419px;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
border: 4px solid rgba(0, 102, 255, 0.72);
|
||||
box-sizing: border-box;
|
||||
box-shadow: inset 0 0 18px rgba(0, 180, 255, 0.36), 0 0 10px rgba(0, 90, 255, 0.32);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,162 @@
|
|||
<template>
|
||||
<section class="traffic-8"></section>
|
||||
<template>
|
||||
<section class="traffic-8">
|
||||
<div class="ranking-panel">
|
||||
|
||||
|
||||
<div class="ranking-list">
|
||||
<article v-for="item in rankingList" :key="item.rank" class="ranking-item">
|
||||
<span class="ranking-index" :class="'ranking-index--r' + item.rank">
|
||||
NO.{{ item.rank }}
|
||||
</span>
|
||||
<span class="line-badge" :style="getLineStyle(item.lineId)">
|
||||
<template v-if="getLineNameParts(item.lineId).isNumberLine">
|
||||
<strong>{{ getLineNameParts(item.lineId).number }}</strong>
|
||||
<small>{{ getLineNameParts(item.lineId).suffix }}</small>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ getLineName(item.lineId) }}
|
||||
</template>
|
||||
</span>
|
||||
<div>{{ item.count }}条</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comment-panel">
|
||||
|
||||
|
||||
<label class="search-box">
|
||||
<input v-model="keyword" type="text" aria-label="鎼滅储璇勮" />
|
||||
<span class="search-icon"></span>
|
||||
</label>
|
||||
|
||||
<div class="comment-list">
|
||||
<article v-for="comment in filteredComments" :key="comment.id" class="comment-card">
|
||||
<header>
|
||||
<span class="comment-user">
|
||||
<i class="user-icon"></i>
|
||||
{{ comment.user }}
|
||||
</span>
|
||||
<time>{{ comment.date }} {{ comment.time }}</time>
|
||||
<i class="status-icon"></i>
|
||||
</header>
|
||||
|
||||
<p>{{ comment.content }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { METRO_LINES } from '@/utils/const'
|
||||
|
||||
const keyword = ref('')
|
||||
|
||||
const rankingList = [
|
||||
{ rank: 1, lineId: 3, count: 20 },
|
||||
{ rank: 2, lineId: 4, count: 2 },
|
||||
{ rank: 3, lineId: 7, count: 11 },
|
||||
{ rank: 4, lineId: 41, count: 11 },
|
||||
{ rank: 5, lineId: 51, count: 11 }
|
||||
]
|
||||
|
||||
const getLineTheme = lineId => {
|
||||
return METRO_LINES[Number(lineId)] || {
|
||||
bg: '#2bd5ff',
|
||||
color: '#00194a',
|
||||
name: `${lineId}号线`
|
||||
}
|
||||
}
|
||||
|
||||
const getLineName = lineId => getLineTheme(lineId).name
|
||||
|
||||
const getLineNameParts = lineId => {
|
||||
const name = getLineName(lineId)
|
||||
const match = String(name).match(/^(\d+)(.*)$/)
|
||||
|
||||
if (!match) {
|
||||
return {
|
||||
isNumberLine: false,
|
||||
text: name
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isNumberLine: true,
|
||||
number: match[1],
|
||||
suffix: match[2]
|
||||
}
|
||||
}
|
||||
|
||||
const getLineStyle = lineId => {
|
||||
const theme = getLineTheme(lineId)
|
||||
|
||||
return {
|
||||
color: theme.color,
|
||||
backgroundColor: theme.bg
|
||||
}
|
||||
}
|
||||
|
||||
const comments = [
|
||||
{
|
||||
id: 1,
|
||||
user: '莲花楼楼主_CC',
|
||||
date: '03-28',
|
||||
time: '16:21',
|
||||
content: '把15号线,改为,青浦线,或者,华为专线,有这么难吗。'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
user: '莲花楼楼主_CC',
|
||||
date: '03-28',
|
||||
time: '16:21',
|
||||
content: '把15号线,改为,青浦线,或者,华为专线,有这么难吗。'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
user: '莲花楼楼主_CC',
|
||||
date: '03-28',
|
||||
time: '16:21',
|
||||
content: '把15号线,改为,青浦线,或者,华为专线,有这么难吗。'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
user: '莲花楼楼主_CC',
|
||||
date: '03-28',
|
||||
time: '16:21',
|
||||
content: '把15号线,改为,青浦线,或者,华为专线,有这么难吗。'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
user: '莲花楼楼主_CC',
|
||||
date: '03-28',
|
||||
time: '16:21',
|
||||
content: '把15号线,改为,青浦线,或者,华为专线,有这么难吗。'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
user: '莲花楼楼主_CC',
|
||||
date: '03-28',
|
||||
time: '16:21',
|
||||
content: '把15号线,改为,青浦线,或者,华为专线,有这么难吗。'
|
||||
}
|
||||
]
|
||||
|
||||
const filteredComments = computed(() => {
|
||||
const text = keyword.value.trim()
|
||||
|
||||
if (!text) {
|
||||
return comments
|
||||
}
|
||||
|
||||
return comments.filter(item => {
|
||||
return item.user.includes(text) || item.content.includes(text)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.traffic-8 {
|
||||
position: absolute;
|
||||
|
|
@ -13,5 +168,239 @@
|
|||
|
||||
background: url('/imgs/traffic/8-bg.png') no-repeat center center;
|
||||
background-size: cover;
|
||||
|
||||
.ranking-panel,
|
||||
.comment-panel {
|
||||
position: absolute;
|
||||
top: 120px;
|
||||
bottom: 18px;
|
||||
}
|
||||
|
||||
.ranking-panel {
|
||||
left: 29px;
|
||||
width: 426px;
|
||||
}
|
||||
|
||||
.comment-panel {
|
||||
right: 24px;
|
||||
width: 430px;
|
||||
}
|
||||
|
||||
.ranking-list {
|
||||
height: calc(100% - 62px);
|
||||
margin-top: 12px;
|
||||
padding-right: 8px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
height: 81px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: 112px 1fr 86px;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding: 0 25px 0 24px;
|
||||
|
||||
background: rgba(44, 121, 236, 0.25);
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
|
||||
div {
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
|
||||
.ranking-index {
|
||||
font-family: YouSheBiaoTiHei;
|
||||
color: #2bd5ff;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
|
||||
|
||||
&--r1 {
|
||||
color: #ff1010;
|
||||
}
|
||||
|
||||
&--r2 {
|
||||
color: #EEB30F;
|
||||
}
|
||||
}
|
||||
|
||||
.line-badge {
|
||||
justify-self: center;
|
||||
min-width: 72px;
|
||||
height: 30px;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
margin-right: 10px;
|
||||
|
||||
strong,
|
||||
small {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
small {
|
||||
margin-left: 1px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 22px;
|
||||
width: 385px;
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 38px 0 14px;
|
||||
border: 2px solid rgba(37, 216, 255, 0.76);
|
||||
border-radius: 18px;
|
||||
background: rgba(16, 65, 143, 0.38);
|
||||
box-shadow: inset 0 0 12px rgba(31, 211, 255, 0.18);
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: #ffffff;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 13px;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
border: 2px solid #28d9ff;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
bottom: -4px;
|
||||
width: 8px;
|
||||
height: 2px;
|
||||
transform: rotate(45deg);
|
||||
border-radius: 2px;
|
||||
background: #28d9ff;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-list {
|
||||
position: absolute;
|
||||
top: 56px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 13px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.comment-card {
|
||||
min-height: 104px;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
padding: 15px 16px 14px 16px;
|
||||
|
||||
background: rgba(44, 121, 236, 0.25);
|
||||
color: #ffffff;
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
time {
|
||||
margin-left: auto;
|
||||
color: rgba(235, 248, 255, 0.92);
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 12px 19px 0 34px;
|
||||
color: rgba(230, 230, 230, 1);
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 26px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
max-width: 224px;
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.user-icon,
|
||||
.status-icon {
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.user-icon {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
margin-right: 9px;
|
||||
background-image: url('/imgs/traffic/8-item.png');
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-left: 7px;
|
||||
background-image: url('/imgs/traffic/8-item1.png');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,255 @@
|
|||
<template>
|
||||
<article class="traffic-rank-row" :class="`traffic-rank-row--${fillType}`">
|
||||
<div class="row-fill" :style="{ width: `${item.percent}%` }"></div>
|
||||
|
||||
<div class="rank-hex" :class="{ 'rank-hex--highlight': item.rank < 4 }">{{ rankText }}</div>
|
||||
|
||||
<div class="tag-cell">
|
||||
<div class="line-list">
|
||||
<span v-for="lineId in lineIds" :key="lineId" class="line-tag" :style="getLineStyle(lineId)">
|
||||
{{ lineId }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="station-cell">
|
||||
<span class="station-name">{{ item.station }}</span>
|
||||
</div>
|
||||
|
||||
<div class="type-cell">{{ item.type }}</div>
|
||||
|
||||
<div class="flow-cell flow-cell--today" :class="{ 'flow-cell--highlight': item.rank < 4 }">
|
||||
<strong>{{ item.today }}</strong>
|
||||
<span>万人</span>
|
||||
</div>
|
||||
|
||||
<div class="flow-cell flow-cell--compare">
|
||||
<strong>{{ item.compare }}</strong>
|
||||
<span>万人</span>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { METRO_LINES } from '@/utils/const'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const rankText = computed(() => String(props.item.rank).padStart(2, '0'))
|
||||
|
||||
const fillType = computed(() => {
|
||||
const rank = Number(props.item.rank)
|
||||
|
||||
if (rank === 1) {
|
||||
return 'first'
|
||||
}
|
||||
|
||||
if (rank === 2) {
|
||||
return 'second'
|
||||
}
|
||||
|
||||
if (rank === 3) {
|
||||
return 'third'
|
||||
}
|
||||
|
||||
return 'normal'
|
||||
})
|
||||
|
||||
const lineIds = computed(() => {
|
||||
return String(props.item.lines || '')
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean)
|
||||
})
|
||||
|
||||
const getLineStyle = lineId => {
|
||||
const theme = METRO_LINES[Number(lineId)] || {
|
||||
bg: '#2bd5ff',
|
||||
color: '#00194a'
|
||||
}
|
||||
|
||||
return {
|
||||
backgroundColor: theme.bg + 'aa',
|
||||
border:`1px solid ${theme.bg}`,
|
||||
color: theme.color
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.traffic-rank-row {
|
||||
position: relative;
|
||||
height: 44px;
|
||||
display: grid;
|
||||
grid-template-columns: 66px 120px 200px 130px 160px 146px;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(90deg, #0085ff00 0%, #00a3ff32 7%, #00a3ff33 46%, #0075ff0a 100%);
|
||||
|
||||
&--first {
|
||||
--fill-start: #0085ff00;
|
||||
--fill-center1: #00ffff33;
|
||||
--fill-center2: #00ffff33;
|
||||
--fill-end: #0075ff14;
|
||||
--bar-start: #00b7ff2e;
|
||||
--bar-end: #00FFFF;
|
||||
|
||||
}
|
||||
|
||||
&--second {
|
||||
--fill-start: #00d9ff00;
|
||||
--fill-center1: #00ffdd33;
|
||||
--fill-center2: #00ffdd33;
|
||||
--fill-end: #00d9ff14;
|
||||
--bar-start: #00b7ff2e;
|
||||
--bar-end: #00ffdd;
|
||||
}
|
||||
|
||||
&--third {
|
||||
--fill-start: #0085ff00;
|
||||
--fill-center1: #0099ff52;
|
||||
--fill-center2: #00b7ff52;
|
||||
--fill-end: #0075ff14 ;
|
||||
--bar-start: #00b7ff2e;
|
||||
--bar-end: #00b7ff;
|
||||
|
||||
}
|
||||
|
||||
&--normal {
|
||||
--fill-start: #ffffff00;
|
||||
--fill-center1: #f5f5f533;
|
||||
--fill-center2: #f1f1f133;
|
||||
--fill-end: #ffffff14 ;
|
||||
--bar-start: #c6c6c62e;
|
||||
--bar-end: #ffffffcc;
|
||||
}
|
||||
}
|
||||
|
||||
.row-fill {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 66px;
|
||||
z-index: 0;
|
||||
background: linear-gradient(90deg, var(--fill-start) 0%,var(--fill-center1) 42%,var(--fill-center2) 80%, var(--fill-end) 100%);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 6px;
|
||||
background: linear-gradient(90deg, var(--bar-start) 0%, var(--bar-end) 100%);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.rank-hex,
|
||||
.station-cell,
|
||||
.tag-cell,
|
||||
.type-cell,
|
||||
.flow-cell {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
}
|
||||
|
||||
.rank-hex {
|
||||
justify-self: center;
|
||||
width: 53px;
|
||||
height: 43px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
background: url('/imgs/traffic/1-item.png') no-repeat center center;
|
||||
background-size: contain;
|
||||
|
||||
&--highlight {
|
||||
color: #17f7ff;
|
||||
text-shadow: 0 0 7px rgba(21, 247, 255, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.station-cell {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.tag-cell {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.line-list {
|
||||
width: 93px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.line-tag {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 1px;
|
||||
font-size: 17px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.station-name,
|
||||
.type-cell {
|
||||
overflow: hidden;
|
||||
color: #ffffff;
|
||||
font-size: 23px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.type-cell {
|
||||
text-align: center;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.flow-cell {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
margin-left: 20px;
|
||||
|
||||
strong {
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 2px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&--highlight {
|
||||
color: #12f8ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||