对接 Traffic7 重大活动接口,修复图表 symbol 图片加载与生产环境 API 配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
main
崔方鑫 2026-08-27 18:05:27 +08:00
parent 35440a9493
commit ea43f6261a
12 changed files with 285 additions and 234 deletions

View File

@ -1,2 +1,2 @@
# 生产环境配置 # 生产环境配置 目前是配置代理的方式
VITE_API_BASE_URL=http://10.104.10.151:8888 VITE_API_BASE_URL=/juntech-ysdp

View File

@ -15,7 +15,7 @@ const props = defineProps({
// //
glowImage: { glowImage: {
type: String, type: String,
default: '/imgs/guangyun.png' default: './imgs/guangyun.png'
}, },
// 线 // 线
showRing: { showRing: {

View File

@ -188,6 +188,14 @@ var server = {
return ysdpPost('/api/traffic/getStationsByLine/v1', { lineId }) return ysdpPost('/api/traffic/getStationsByLine/v1', { lineId })
}, },
getBigActivityList({ metroLine, metroStation, activityMonth }) {
return ysdpPost('/api/traffic/getBigActivityList/v1', {
metroLine,
metroStation,
activityMonth
})
},
cleanTrafficCache() { cleanTrafficCache() {
return ysdpPost('/api/traffic/cleanCache') return ysdpPost('/api/traffic/cleanCache')
}, },

View File

@ -47,6 +47,39 @@ export function toWanRen(value) {
return (num / 10000).toFixed(2) return (num / 10000).toFixed(2)
} }
const ACTIVITY_STATUS_TYPE_MAP = {
进行中: 'active',
已结束: 'ended',
未开始: 'pending'
}
/** 活动月份格式化为 YYYY-MM */
export function formatActivityMonth(month) {
if (!month) return ''
const [year, monthValue] = String(month).split('-')
if (!year || !monthValue) return ''
return `${year}-${String(monthValue).padStart(2, '0')}`
}
/** 将重大活动清单接口数据映射为 Traffic7 所需结构 */
export function processBigActivityList(data) {
if (!Array.isArray(data)) return []
return data.map(item => {
const startDate = item.startDate ? item.startDate.replace(/-/g, '/') : ''
const endDate = item.endDate ? item.endDate.replace(/-/g, '/') : ''
return {
id: item.id,
name: item.activityName || '',
time: startDate && endDate ? `${startDate}--${endDate}` : startDate || endDate,
venue: item.venue || '',
status: item.status || '',
statusType: ACTIVITY_STATUS_TYPE_MAP[item.status] || 'pending'
}
})
}
/** 将站点排名接口数据映射为 TrafficRankRow 所需结构 */ /** 将站点排名接口数据映射为 TrafficRankRow 所需结构 */
export function mapStationRankList(data, type = '进出站') { export function mapStationRankList(data, type = '进出站') {
if (!Array.isArray(data) || !data.length) return [] if (!Array.isArray(data) || !data.length) return []

View File

@ -1,7 +1,8 @@
import axios from 'axios' import axios from 'axios'
// 开发环境留空时默认 /juntech-ysdp走 vite proxy生产环境 VITE_API_BASE_URL 已含此前缀,不再重复拼接
const ysdpRequest = axios.create({ const ysdpRequest = axios.create({
baseURL: `${import.meta.env.VITE_API_BASE_URL || ''}/juntech-ysdp`, baseURL: import.meta.env.VITE_API_BASE_URL || '/juntech-ysdp',
timeout: 30000 timeout: 30000
}) })

View File

@ -3,12 +3,12 @@
<!-- <div class="module-title">模块一</div> --> <!-- <div class="module-title">模块一</div> -->
<div class="module-body"> <div class="module-body">
<div class="pyramid-3d-wrapper"> <div class="pyramid-3d-wrapper">
<Pyramid3D :show-glow="true" :show-ring="true" glow-image="/imgs/guangyun.png" color="#00d4ff" :scale="1.77" <Pyramid3D :show-glow="true" :show-ring="true" glow-image="./imgs/guangyun.png" color="#00d4ff" :scale="1.77"
:rotation-speed="0.2" :auto-rotate="true" :width="252" :height="166" @loaded="onPyramidLoaded" :rotation-speed="0.2" :auto-rotate="true" :width="252" :height="166" @loaded="onPyramidLoaded"
@error="onPyramidError" /> @error="onPyramidError" />
</div> </div>
<div class="pyramid-3d-wrapper2"> <div class="pyramid-3d-wrapper2">
<Pyramid3D :show-glow="true" :show-ring="true" glow-image="/imgs/guangyun.png" color="#00d4ff" :scale="1.77" <Pyramid3D :show-glow="true" :show-ring="true" glow-image="./imgs/guangyun.png" color="#00d4ff" :scale="1.77"
:rotation-speed="0.2" :auto-rotate="true" :width="252" :height="166" @loaded="onPyramidLoaded" :rotation-speed="0.2" :auto-rotate="true" :width="252" :height="166" @loaded="onPyramidLoaded"
@error="onPyramidError" /> @error="onPyramidError" />
</div> </div>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -37,9 +37,10 @@ import 'odometer/themes/odometer-theme-default.css'
import { METRO_LINES } from '@/utils/const' import { METRO_LINES } from '@/utils/const'
import server from '@/utils/service' import server from '@/utils/service'
import { processYunyingInfo } from '@/utils/yunying' import { processYunyingInfo } from '@/utils/yunying'
import { pointGlowSymbolBase64 } from '../assets/4-item.base64.js'
const hours = ['5', '7', '9', '11', '13', '15', '17', '19', '21', '23'] const hours = ['5', '7', '9', '11', '13', '15', '17', '19', '21', '23']
const pointGlowSymbol = 'image:///imgs/traffic/4-item.png' const pointGlowSymbol = `image://${pointGlowSymbolBase64}`
const lineCards = ref([]) const lineCards = ref([])
const compareDate = ref('') const compareDate = ref('')

View File

@ -14,13 +14,15 @@ import VChart from 'vue-echarts'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import server from '@/utils/service' import server from '@/utils/service'
import { processStationPassengerFlow, calcChartYAxisMax } from '@/utils/traffic' import { processStationPassengerFlow, calcChartYAxisMax } from '@/utils/traffic'
import { pointGlowSymbolBase64 } from '../assets/4-item.base64.js'
import { pointGlowSymbol1Base64 } from '../assets/4-item1.base64.js'
const selectedLine = ref('3') const selectedLine = ref('3')
const selectedStation = ref('0313') const selectedStation = ref('0313')
const hours = ['5', '7', '9', '11', '13', '15', '17', '19', '21', '23'] const hours = ['5', '7', '9', '11', '13', '15', '17', '19', '21', '23']
const pointGlowSymbol = 'image:///imgs/traffic/4-item.png' const pointGlowSymbol = `image://${pointGlowSymbolBase64}`
const pointGlowSymbol1 = 'image:///imgs/traffic/4-item1.png' const pointGlowSymbol1 = `image://${pointGlowSymbol1Base64}`
const topActual = ref([]) const topActual = ref([])
const topCompare = ref([]) const topCompare = ref([])

View File

@ -1,223 +1,226 @@
<template> <template>
<section class="traffic-7"> <section class="traffic-7">
<StationTimeFilter <StationTimeFilter
class="traffic-7__filter" class="traffic-7__filter"
v-model:line="selectedLine" v-model:line="selectedLine"
v-model:station="selectedStation" v-model:station="selectedStation"
v-model:month="selectedMonth" v-model:month="selectedMonth"
/> />
<div class="activity-list"> <div class="activity-list">
<article v-for="activity in activityList" :key="activity.id" class="activity-card">
<article v-for="activity in activityList" :key="activity.name" class="activity-card"> <div class="activity-status" :class="`activity-status--${activity.statusType}`">
<div class="activity-status" :class="`activity-status--${activity.statusType}`"> {{ activity.status }}
{{ activity.status }} </div>
</div>
<h3 class="activity-name">{{ activity.name }}</h3>
<h3 class="activity-name">{{ activity.name }}</h3>
<div class="activity-meta">
<div class="activity-meta"> <span class="activity-label">时间:</span>
<span class="activity-label">时间:</span> <span>{{ activity.time }}</span>
<span>{{ activity.time }}</span> </div>
</div>
<div v-if="activity.venue" class="activity-meta activity-meta--venue">
<div v-if="activity.venue" class="activity-meta activity-meta--venue"> <span class="activity-label">举办场馆:</span>
<span class="activity-label">举办场馆:</span> <span>{{ activity.venue }}</span>
<span>{{ activity.venue }}</span> </div>
</div> </article>
</article> </div>
</div> </section>
</section> </template>
</template>
<script setup>
<script setup> import { onMounted, ref, watch } from 'vue'
import { ref } from 'vue' import dayjs from 'dayjs'
import StationTimeFilter from './StationTimeFilter.vue' import StationTimeFilter from './StationTimeFilter.vue'
import server from '@/utils/service'
const selectedLine = ref('7') import { formatActivityMonth, processBigActivityList } from '@/utils/traffic'
const selectedStation = ref('0753')
const selectedMonth = ref('2026-5') const selectedLine = ref('7')
const selectedStation = ref('0753')
const activityList = [ const selectedMonth = ref(dayjs().format('YYYY-M'))
{ const activityList = ref([])
name: 'CBE中国美容博览会', const lineApiIdMap = ref({})
time: '2026/05/12--2026/05/14',
venue: '新国际博览中心', const loadLineApiIdMap = async () => {
status: '进行中', try {
statusType: 'active' const response = await server.getLineList()
}, if (response.data.success && response.data.data?.length) {
{ lineApiIdMap.value = Object.fromEntries(
name: '上海插画艺术节', response.data.data.map(item => [String(item.lineId), String(item.id)])
time: '2026/05/01--2026/05/03', )
status: '已结束', }
statusType: 'ended' } catch (error) {
}, console.error('获取线路列表失败:', error)
{ }
name: '中国国际自行车展览会/中国国际电动车及零配件展览会/中国国际摩托车及零配件展览会/上海国际户外骑行装备展览会', }
time: '2026/05/05--2026/05/08',
status: '已结束', const fetchActivityList = async () => {
statusType: 'ended' const metroLine = lineApiIdMap.value[selectedLine.value]
}, if (!metroLine || !selectedStation.value || !selectedMonth.value) {
{ activityList.value = []
name: 'SIAL 西雅国际食品展览会(上海)', return
time: '2026/05/18--2026/05/20', }
status: '未开始',
statusType: 'pending' try {
}, const response = await server.getBigActivityList({
{ metroLine,
name: '亚洲国际有机产品博览会', metroStation: selectedStation.value,
time: '2026/05/18--2026/05/20', activityMonth: formatActivityMonth(selectedMonth.value)
status: '未开始', })
statusType: 'pending'
}, if (response.data.success) {
{ activityList.value = processBigActivityList(response.data.data)
name: '上海网络安全博览会暨高峰论坛', } else {
time: '2026/05/26--2026/05/28', activityList.value = []
status: '未开始', }
statusType: 'pending' } catch (error) {
}, console.error('获取重大活动清单失败:', error)
{ activityList.value = []
name: '中国国际厨房、卫浴设施展览会', }
time: '2026/05/26--2026/05/29', }
status: '未开始',
statusType: 'pending' watch([selectedLine, selectedStation, selectedMonth], fetchActivityList)
}
] onMounted(async () => {
</script> await loadLineApiIdMap()
await fetchActivityList()
<style lang="scss" scoped> })
.traffic-7 { </script>
position: absolute;
left: 2948px; <style lang="scss" scoped>
top: 707px; .traffic-7 {
width: 892px; position: absolute;
height: 880px; left: 2948px;
background: url('/imgs/traffic/7-bg.png') no-repeat center center; top: 707px;
background-size: cover; width: 892px;
height: 880px;
.traffic-7__filter { background: url('/imgs/traffic/7-bg.png') no-repeat center center;
position: absolute; background-size: cover;
top: 12px;
right: 30px; .traffic-7__filter {
z-index: 2; position: absolute;
} top: 12px;
right: 30px;
.activity-list { z-index: 2;
position: absolute; }
top: 75px;
right: 12px; .activity-list {
bottom: 20px; position: absolute;
left: 20px; top: 75px;
display: flex; right: 12px;
flex-direction: column; bottom: 20px;
gap: 15px; left: 20px;
overflow-x: hidden; display: flex;
overflow-y: scroll; flex-direction: column;
padding-right: 6px; gap: 15px;
overflow-x: hidden;
} overflow-y: scroll;
padding-right: 6px;
.activity-card {
position: relative; }
min-height: 89px;
box-sizing: border-box; .activity-card {
padding: 12px 104px 10px 31px; position: relative;
overflow: hidden; min-height: 89px;
border-radius: 5px; box-sizing: border-box;
letter-spacing: 0.03em; padding: 12px 104px 10px 31px;
flex-shrink: 0; overflow: hidden;
background: rgba(44, 121, 236, 0.35); border-radius: 5px;
} letter-spacing: 0.03em;
flex-shrink: 0;
.activity-empty { background: rgba(44, 121, 236, 0.35);
height: 100%; }
:deep(.el-empty__description p) { .activity-empty {
color: rgba(220, 236, 255, 0.78); height: 100%;
font-size: 18px;
font-weight: 700; :deep(.el-empty__description p) {
} color: rgba(220, 236, 255, 0.78);
} font-size: 18px;
font-weight: 700;
.activity-name { }
margin: 0; }
color: #FFFFFF;
font-size: 22px; .activity-name {
font-weight: 500; margin: 0;
line-height: 32px; color: #FFFFFF;
letter-spacing: 0.03em; font-size: 22px;
} font-weight: 500;
line-height: 32px;
.activity-meta { letter-spacing: 0.03em;
display: flex; }
align-items: center;
// gap: 37px; .activity-meta {
margin-top: 7px; display: flex;
color: rgba(255, 255, 255, 0.8); align-items: center;
// gap: 37px;
font-size: 20px; margin-top: 7px;
font-weight: 400; color: rgba(255, 255, 255, 0.8);
line-height: 28px;
font-size: 20px;
white-space: nowrap; font-weight: 400;
&--venue { line-height: 28px;
margin-top: 7px;
} white-space: nowrap;
} &--venue {
margin-top: 7px;
.activity-label { }
color: rgba(228, 243, 255, 0.74); }
flex-shrink: 0;
width: 94px; .activity-label {
} color: rgba(228, 243, 255, 0.74);
flex-shrink: 0;
.activity-status { width: 94px;
position: absolute; }
top: 0;
right: 0; .activity-status {
z-index: 1; position: absolute;
width: 91px; top: 0;
height: 35px; right: 0;
display: flex; z-index: 1;
align-items: center; width: 91px;
justify-content: center; height: 35px;
font-size: 20px; display: flex;
font-weight: 500; align-items: center;
line-height: 1; justify-content: center;
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5); font-size: 20px;
font-weight: 500;
&::before { line-height: 1;
content: ''; text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5);
position: absolute;
top: 0; &::before {
left: -20px; content: '';
width: 0; position: absolute;
height: 0; top: 0;
border-top: 35px solid var(--status-bg-start); left: -20px;
border-left: 20px solid transparent; width: 0;
} height: 0;
border-top: 35px solid var(--status-bg-start);
&--active { border-left: 20px solid transparent;
--status-bg-start: #078b66; }
--status-bg-end: #08b279;
color: #02FF5F; &--active {
background: linear-gradient(90deg, var(--status-bg-start) 0%, var(--status-bg-end) 100%); --status-bg-start: #078b66;
} --status-bg-end: #08b279;
color: #02FF5F;
&--ended { background: linear-gradient(90deg, var(--status-bg-start) 0%, var(--status-bg-end) 100%);
--status-bg-start: #9b7f7f; }
--status-bg-end: #bba1a1;
color: #cfcfcf; &--ended {
background: linear-gradient(90deg, var(--status-bg-start) 0%, var(--status-bg-end) 100%); --status-bg-start: #9b7f7f;
} --status-bg-end: #bba1a1;
color: #cfcfcf;
&--pending { background: linear-gradient(90deg, var(--status-bg-start) 0%, var(--status-bg-end) 100%);
--status-bg-start: #546174; }
--status-bg-end: #6f7c91;
color: #f0f0f0; &--pending {
background: linear-gradient(90deg, var(--status-bg-start) 0%, var(--status-bg-end) 100%); --status-bg-start: #546174;
} --status-bg-end: #6f7c91;
} color: #f0f0f0;
background: linear-gradient(90deg, var(--status-bg-start) 0%, var(--status-bg-end) 100%);
} }
</style> }
}
</style>

View File

@ -58,8 +58,7 @@ const rankingList = [
{ rank: 1, lineId: 3, count: 20 }, { rank: 1, lineId: 3, count: 20 },
{ rank: 2, lineId: 4, count: 2 }, { rank: 2, lineId: 4, count: 2 },
{ rank: 3, lineId: 7, count: 11 }, { rank: 3, lineId: 7, count: 11 },
{ rank: 4, lineId: 41, count: 11 }, { rank: 4, lineId: 11, count: 11 }
{ rank: 5, lineId: 51, count: 11 }
] ]
const getLineTheme = lineId => { const getLineTheme = lineId => {