对接 Traffic7 重大活动接口,修复图表 symbol 图片加载与生产环境 API 配置。
Co-authored-by: Cursor <cursoragent@cursor.com>main
parent
35440a9493
commit
ea43f6261a
|
|
@ -1,2 +1,2 @@
|
|||
# 生产环境配置
|
||||
VITE_API_BASE_URL=http://10.104.10.151:8888
|
||||
# 生产环境配置 目前是配置代理的方式
|
||||
VITE_API_BASE_URL=/juntech-ysdp
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const props = defineProps({
|
|||
// 光晕图片路径
|
||||
glowImage: {
|
||||
type: String,
|
||||
default: '/imgs/guangyun.png'
|
||||
default: './imgs/guangyun.png'
|
||||
},
|
||||
// 是否显示虚线光圈
|
||||
showRing: {
|
||||
|
|
|
|||
|
|
@ -188,6 +188,14 @@ var server = {
|
|||
return ysdpPost('/api/traffic/getStationsByLine/v1', { lineId })
|
||||
},
|
||||
|
||||
getBigActivityList({ metroLine, metroStation, activityMonth }) {
|
||||
return ysdpPost('/api/traffic/getBigActivityList/v1', {
|
||||
metroLine,
|
||||
metroStation,
|
||||
activityMonth
|
||||
})
|
||||
},
|
||||
|
||||
cleanTrafficCache() {
|
||||
return ysdpPost('/api/traffic/cleanCache')
|
||||
},
|
||||
|
|
|
|||
|
|
@ -47,6 +47,39 @@ export function toWanRen(value) {
|
|||
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 所需结构 */
|
||||
export function mapStationRankList(data, type = '进出站') {
|
||||
if (!Array.isArray(data) || !data.length) return []
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import axios from 'axios'
|
||||
|
||||
// 开发环境留空时默认 /juntech-ysdp(走 vite proxy);生产环境 VITE_API_BASE_URL 已含此前缀,不再重复拼接
|
||||
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
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
<!-- <div class="module-title">模块一</div> -->
|
||||
<div class="module-body">
|
||||
<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"
|
||||
@error="onPyramidError" />
|
||||
</div>
|
||||
<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"
|
||||
@error="onPyramidError" />
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -37,9 +37,10 @@ import 'odometer/themes/odometer-theme-default.css'
|
|||
import { METRO_LINES } from '@/utils/const'
|
||||
import server from '@/utils/service'
|
||||
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 pointGlowSymbol = 'image:///imgs/traffic/4-item.png'
|
||||
const pointGlowSymbol = `image://${pointGlowSymbolBase64}`
|
||||
|
||||
const lineCards = ref([])
|
||||
const compareDate = ref('')
|
||||
|
|
|
|||
|
|
@ -14,13 +14,15 @@ import VChart from 'vue-echarts'
|
|||
import * as echarts from 'echarts'
|
||||
import server from '@/utils/service'
|
||||
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 selectedStation = ref('0313')
|
||||
|
||||
const hours = ['5', '7', '9', '11', '13', '15', '17', '19', '21', '23']
|
||||
const pointGlowSymbol = 'image:///imgs/traffic/4-item.png'
|
||||
const pointGlowSymbol1 = 'image:///imgs/traffic/4-item1.png'
|
||||
const pointGlowSymbol = `image://${pointGlowSymbolBase64}`
|
||||
const pointGlowSymbol1 = `image://${pointGlowSymbol1Base64}`
|
||||
|
||||
const topActual = ref([])
|
||||
const topCompare = ref([])
|
||||
|
|
|
|||
|
|
@ -1,223 +1,226 @@
|
|||
<template>
|
||||
<section class="traffic-7">
|
||||
<StationTimeFilter
|
||||
class="traffic-7__filter"
|
||||
v-model:line="selectedLine"
|
||||
v-model:station="selectedStation"
|
||||
v-model:month="selectedMonth"
|
||||
/>
|
||||
|
||||
<div class="activity-list">
|
||||
|
||||
<article v-for="activity in activityList" :key="activity.name" class="activity-card">
|
||||
<div class="activity-status" :class="`activity-status--${activity.statusType}`">
|
||||
{{ activity.status }}
|
||||
</div>
|
||||
|
||||
<h3 class="activity-name">{{ activity.name }}</h3>
|
||||
|
||||
<div class="activity-meta">
|
||||
<span class="activity-label">时间:</span>
|
||||
<span>{{ activity.time }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="activity.venue" class="activity-meta activity-meta--venue">
|
||||
<span class="activity-label">举办场馆:</span>
|
||||
<span>{{ activity.venue }}</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import StationTimeFilter from './StationTimeFilter.vue'
|
||||
|
||||
const selectedLine = ref('7')
|
||||
const selectedStation = ref('0753')
|
||||
const selectedMonth = ref('2026-5')
|
||||
|
||||
const activityList = [
|
||||
{
|
||||
name: 'CBE中国美容博览会',
|
||||
time: '2026/05/12--2026/05/14',
|
||||
venue: '新国际博览中心',
|
||||
status: '进行中',
|
||||
statusType: 'active'
|
||||
},
|
||||
{
|
||||
name: '上海插画艺术节',
|
||||
time: '2026/05/01--2026/05/03',
|
||||
status: '已结束',
|
||||
statusType: 'ended'
|
||||
},
|
||||
{
|
||||
name: '中国国际自行车展览会/中国国际电动车及零配件展览会/中国国际摩托车及零配件展览会/上海国际户外骑行装备展览会',
|
||||
time: '2026/05/05--2026/05/08',
|
||||
status: '已结束',
|
||||
statusType: 'ended'
|
||||
},
|
||||
{
|
||||
name: 'SIAL 西雅国际食品展览会(上海)',
|
||||
time: '2026/05/18--2026/05/20',
|
||||
status: '未开始',
|
||||
statusType: 'pending'
|
||||
},
|
||||
{
|
||||
name: '亚洲国际有机产品博览会',
|
||||
time: '2026/05/18--2026/05/20',
|
||||
status: '未开始',
|
||||
statusType: 'pending'
|
||||
},
|
||||
{
|
||||
name: '上海网络安全博览会暨高峰论坛',
|
||||
time: '2026/05/26--2026/05/28',
|
||||
status: '未开始',
|
||||
statusType: 'pending'
|
||||
},
|
||||
{
|
||||
name: '中国国际厨房、卫浴设施展览会',
|
||||
time: '2026/05/26--2026/05/29',
|
||||
status: '未开始',
|
||||
statusType: 'pending'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.traffic-7 {
|
||||
position: absolute;
|
||||
left: 2948px;
|
||||
top: 707px;
|
||||
width: 892px;
|
||||
height: 880px;
|
||||
background: url('/imgs/traffic/7-bg.png') no-repeat center center;
|
||||
background-size: cover;
|
||||
|
||||
.traffic-7__filter {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 30px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.activity-list {
|
||||
position: absolute;
|
||||
top: 75px;
|
||||
right: 12px;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding-right: 6px;
|
||||
|
||||
}
|
||||
|
||||
.activity-card {
|
||||
position: relative;
|
||||
min-height: 89px;
|
||||
box-sizing: border-box;
|
||||
padding: 12px 104px 10px 31px;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
letter-spacing: 0.03em;
|
||||
flex-shrink: 0;
|
||||
background: rgba(44, 121, 236, 0.35);
|
||||
}
|
||||
|
||||
.activity-empty {
|
||||
height: 100%;
|
||||
|
||||
: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;
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.activity-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// gap: 37px;
|
||||
margin-top: 7px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
|
||||
white-space: nowrap;
|
||||
&--venue {
|
||||
margin-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.activity-label {
|
||||
color: rgba(228, 243, 255, 0.74);
|
||||
flex-shrink: 0;
|
||||
width: 94px;
|
||||
}
|
||||
|
||||
.activity-status {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
width: 91px;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -20px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 35px solid var(--status-bg-start);
|
||||
border-left: 20px solid transparent;
|
||||
}
|
||||
|
||||
&--active {
|
||||
--status-bg-start: #078b66;
|
||||
--status-bg-end: #08b279;
|
||||
color: #02FF5F;
|
||||
background: linear-gradient(90deg, var(--status-bg-start) 0%, var(--status-bg-end) 100%);
|
||||
}
|
||||
|
||||
&--ended {
|
||||
--status-bg-start: #9b7f7f;
|
||||
--status-bg-end: #bba1a1;
|
||||
color: #cfcfcf;
|
||||
background: linear-gradient(90deg, var(--status-bg-start) 0%, var(--status-bg-end) 100%);
|
||||
}
|
||||
|
||||
&--pending {
|
||||
--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>
|
||||
<template>
|
||||
<section class="traffic-7">
|
||||
<StationTimeFilter
|
||||
class="traffic-7__filter"
|
||||
v-model:line="selectedLine"
|
||||
v-model:station="selectedStation"
|
||||
v-model:month="selectedMonth"
|
||||
/>
|
||||
|
||||
<div class="activity-list">
|
||||
<article v-for="activity in activityList" :key="activity.id" class="activity-card">
|
||||
<div class="activity-status" :class="`activity-status--${activity.statusType}`">
|
||||
{{ activity.status }}
|
||||
</div>
|
||||
|
||||
<h3 class="activity-name">{{ activity.name }}</h3>
|
||||
|
||||
<div class="activity-meta">
|
||||
<span class="activity-label">时间:</span>
|
||||
<span>{{ activity.time }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="activity.venue" class="activity-meta activity-meta--venue">
|
||||
<span class="activity-label">举办场馆:</span>
|
||||
<span>{{ activity.venue }}</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import StationTimeFilter from './StationTimeFilter.vue'
|
||||
import server from '@/utils/service'
|
||||
import { formatActivityMonth, processBigActivityList } from '@/utils/traffic'
|
||||
|
||||
const selectedLine = ref('7')
|
||||
const selectedStation = ref('0753')
|
||||
const selectedMonth = ref(dayjs().format('YYYY-M'))
|
||||
const activityList = ref([])
|
||||
const lineApiIdMap = ref({})
|
||||
|
||||
const loadLineApiIdMap = async () => {
|
||||
try {
|
||||
const response = await server.getLineList()
|
||||
if (response.data.success && response.data.data?.length) {
|
||||
lineApiIdMap.value = Object.fromEntries(
|
||||
response.data.data.map(item => [String(item.lineId), String(item.id)])
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取线路列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchActivityList = async () => {
|
||||
const metroLine = lineApiIdMap.value[selectedLine.value]
|
||||
if (!metroLine || !selectedStation.value || !selectedMonth.value) {
|
||||
activityList.value = []
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await server.getBigActivityList({
|
||||
metroLine,
|
||||
metroStation: selectedStation.value,
|
||||
activityMonth: formatActivityMonth(selectedMonth.value)
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
activityList.value = processBigActivityList(response.data.data)
|
||||
} else {
|
||||
activityList.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取重大活动清单失败:', error)
|
||||
activityList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
watch([selectedLine, selectedStation, selectedMonth], fetchActivityList)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadLineApiIdMap()
|
||||
await fetchActivityList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.traffic-7 {
|
||||
position: absolute;
|
||||
left: 2948px;
|
||||
top: 707px;
|
||||
width: 892px;
|
||||
height: 880px;
|
||||
background: url('/imgs/traffic/7-bg.png') no-repeat center center;
|
||||
background-size: cover;
|
||||
|
||||
.traffic-7__filter {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 30px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.activity-list {
|
||||
position: absolute;
|
||||
top: 75px;
|
||||
right: 12px;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding-right: 6px;
|
||||
|
||||
}
|
||||
|
||||
.activity-card {
|
||||
position: relative;
|
||||
min-height: 89px;
|
||||
box-sizing: border-box;
|
||||
padding: 12px 104px 10px 31px;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
letter-spacing: 0.03em;
|
||||
flex-shrink: 0;
|
||||
background: rgba(44, 121, 236, 0.35);
|
||||
}
|
||||
|
||||
.activity-empty {
|
||||
height: 100%;
|
||||
|
||||
: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;
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.activity-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// gap: 37px;
|
||||
margin-top: 7px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
|
||||
white-space: nowrap;
|
||||
&--venue {
|
||||
margin-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.activity-label {
|
||||
color: rgba(228, 243, 255, 0.74);
|
||||
flex-shrink: 0;
|
||||
width: 94px;
|
||||
}
|
||||
|
||||
.activity-status {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
width: 91px;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -20px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 35px solid var(--status-bg-start);
|
||||
border-left: 20px solid transparent;
|
||||
}
|
||||
|
||||
&--active {
|
||||
--status-bg-start: #078b66;
|
||||
--status-bg-end: #08b279;
|
||||
color: #02FF5F;
|
||||
background: linear-gradient(90deg, var(--status-bg-start) 0%, var(--status-bg-end) 100%);
|
||||
}
|
||||
|
||||
&--ended {
|
||||
--status-bg-start: #9b7f7f;
|
||||
--status-bg-end: #bba1a1;
|
||||
color: #cfcfcf;
|
||||
background: linear-gradient(90deg, var(--status-bg-start) 0%, var(--status-bg-end) 100%);
|
||||
}
|
||||
|
||||
&--pending {
|
||||
--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>
|
||||
|
|
|
|||
|
|
@ -58,8 +58,7 @@ 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 }
|
||||
{ rank: 4, lineId: 11, count: 11 }
|
||||
]
|
||||
|
||||
const getLineTheme = lineId => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue