对接运三大屏开发环境接口,完善客运页数据展示
统一 ysdp 请求封装并接入首页/客运页接口,新增线路与单站客流对接,补充 gitignore 与开发环境配置说明。 Co-authored-by: Cursor <cursoragent@cursor.com>main
parent
763956d7ed
commit
35440a9493
|
|
@ -1,3 +1,3 @@
|
||||||
# 开发环境配置
|
# 开发环境配置
|
||||||
# 使用相对路径,通过 vite proxy 代理到目标服务器
|
# 留空则通过 vite proxy 代理到 http://101.133.172.2:8082
|
||||||
VITE_API_BASE_URL=
|
VITE_API_BASE_URL=
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,53 @@
|
||||||
|
# 依赖
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
# 构建产物
|
||||||
dist
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# 项目文档(本地维护,不纳入版本库)
|
||||||
|
doc/
|
||||||
|
|
||||||
|
# 环境变量(保留 .env.development / .env.production,忽略本地覆盖)
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# 日志
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# 编辑器 / IDE
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# 操作系统
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# 测试 / 覆盖率
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# 缓存 / 临时文件
|
||||||
|
.cache
|
||||||
|
.temp
|
||||||
|
.tmp
|
||||||
|
*.tsbuildinfo
|
||||||
|
.eslintcache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { ref, watch, onMounted } from 'vue'
|
||||||
|
import server from '@/utils/service'
|
||||||
|
import metroMenuData from '@/utils/lineStationList.json'
|
||||||
|
|
||||||
|
const localLineStationList = metroMenuData.lineStationList || []
|
||||||
|
|
||||||
|
function mapLocalLines() {
|
||||||
|
return localLineStationList.map(item => ({
|
||||||
|
label: item.nameCn,
|
||||||
|
value: item.lineId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapLocalStations(lineId) {
|
||||||
|
const currentLine = localLineStationList.find(item => item.lineId === lineId)
|
||||||
|
return (currentLine?.stationList || []).map(item => ({
|
||||||
|
label: item.nameCn,
|
||||||
|
value: item.statId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 客运页线路/站点下拉:优先接口,空数据时回退本地 JSON */
|
||||||
|
export function useTrafficLineStation(getLine, getStation, emit) {
|
||||||
|
const lineOptions = ref([])
|
||||||
|
const stationOptions = ref([])
|
||||||
|
|
||||||
|
const loadLines = async () => {
|
||||||
|
try {
|
||||||
|
const response = await server.getLineList()
|
||||||
|
if (response.data.success && response.data.data?.length) {
|
||||||
|
lineOptions.value = response.data.data.map(item => ({
|
||||||
|
label: item.name,
|
||||||
|
value: String(item.lineId)
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取线路列表失败:', error)
|
||||||
|
}
|
||||||
|
lineOptions.value = mapLocalLines()
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadStations = async lineId => {
|
||||||
|
if (!lineId) {
|
||||||
|
stationOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await server.getStationsByLine(lineId)
|
||||||
|
if (response.data.success && response.data.data?.length) {
|
||||||
|
stationOptions.value = response.data.data.map(item => ({
|
||||||
|
label: item.stationName || item.name || item.nameCn,
|
||||||
|
value: item.stationId || item.statId
|
||||||
|
}))
|
||||||
|
syncStationSelection()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取站点列表失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
stationOptions.value = mapLocalStations(lineId)
|
||||||
|
syncStationSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncStationSelection = () => {
|
||||||
|
const options = stationOptions.value
|
||||||
|
if (!options.length) {
|
||||||
|
emit('update:station', '')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const currentStation = getStation()
|
||||||
|
const hasCurrent = options.some(item => item.value === currentStation)
|
||||||
|
if (!hasCurrent) {
|
||||||
|
emit('update:station', options[0].value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initLineSelection = () => {
|
||||||
|
if (!lineOptions.value.length) return
|
||||||
|
const currentLine = getLine()
|
||||||
|
const hasCurrent = lineOptions.value.some(item => item.value === currentLine)
|
||||||
|
if (!hasCurrent) {
|
||||||
|
emit('update:line', lineOptions.value[0].value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => getLine(),
|
||||||
|
lineId => loadStations(lineId),
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadLines()
|
||||||
|
initLineSelection()
|
||||||
|
await loadStations(getLine())
|
||||||
|
})
|
||||||
|
|
||||||
|
return { lineOptions, stationOptions }
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import { request } from './request'
|
import { request } from './request'
|
||||||
import { encrypt } from './decrypt'
|
import { encrypt } from './decrypt'
|
||||||
import axios from 'axios'
|
import { ysdpPost } from './ysdpRequest'
|
||||||
|
|
||||||
var ipRoot = "http://140.206.138.190:8900"
|
var ipRoot = "http://140.206.138.190:8900"
|
||||||
var ip = "http://140.206.138.190:8905"
|
var ip = "http://140.206.138.190:8905"
|
||||||
var ysdpmIp = import.meta.env.VITE_API_BASE_URL || "http://192.168.16.193:8888"
|
|
||||||
|
|
||||||
var getToken = "/app/getToken"
|
var getToken = "/app/getToken"
|
||||||
var getTblMetroInout = ipRoot + '/api/xckf/getTblMetroInout/v1'
|
var getTblMetroInout = ipRoot + '/api/xckf/getTblMetroInout/v1'
|
||||||
|
|
@ -22,14 +21,6 @@ var getBusInfo = ipRoot + '/api/xckf/getBusInfo/v1'
|
||||||
var updayeBusEntrance = ipRoot + '/api/xckf/updayeBusEntrance/v1'
|
var updayeBusEntrance = ipRoot + '/api/xckf/updayeBusEntrance/v1'
|
||||||
var getHotEventList = ipRoot + '/api/xckf/getHotEventList/v1'
|
var getHotEventList = ipRoot + '/api/xckf/getHotEventList/v1'
|
||||||
var updateHotEvent = ipRoot + '/api/xckf/updateHotEvent/v1'
|
var updateHotEvent = ipRoot + '/api/xckf/updateHotEvent/v1'
|
||||||
var getOperateInfo = (import.meta.env.DEV ? '' : import.meta.env.VITE_API_BASE_URL) + '/juntech-ysdp/api/getOperateInfo/v1'
|
|
||||||
var getCommonMonitor = (import.meta.env.DEV ? '' : import.meta.env.VITE_API_BASE_URL) + '/juntech-ysdp/api/getCommonMonitor/v1'
|
|
||||||
var getDutyList = (import.meta.env.DEV ? '' : import.meta.env.VITE_API_BASE_URL) + '/juntech-ysdp/api/getDutyList/v1'
|
|
||||||
var getStationMaxList = (import.meta.env.DEV ? '' : import.meta.env.VITE_API_BASE_URL) + '/juntech-ysdp/api/getStationMaxList/v1'
|
|
||||||
var getIndicator = (import.meta.env.DEV ? '' : import.meta.env.VITE_API_BASE_URL) + '/juntech-ysdp/api/getIndicator/v1'
|
|
||||||
var getYunyingInfo = (import.meta.env.DEV ? '' : import.meta.env.VITE_API_BASE_URL) + '/juntech-ysdp/api/getYunyingInfo/v1'
|
|
||||||
var getQxjGdybHour = (import.meta.env.DEV ? '' : import.meta.env.VITE_API_BASE_URL) + '/juntech-ysdp/api/getQxjGdybHour/v1'
|
|
||||||
var getTaskNotice = (import.meta.env.DEV ? '' : import.meta.env.VITE_API_BASE_URL) + '/juntech-ysdp/api/getTaskNotice/v1'
|
|
||||||
var xcImg = ip + '/1734.jpg'
|
var xcImg = ip + '/1734.jpg'
|
||||||
var xcImgGd = ip + '/lineCrossImg/1734.jpg'
|
var xcImgGd = ip + '/lineCrossImg/1734.jpg'
|
||||||
|
|
||||||
|
|
@ -142,35 +133,67 @@ var server = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getOperateInfo() {
|
getOperateInfo() {
|
||||||
return axios.post(getOperateInfo, {})
|
return ysdpPost('/api/getOperateInfo/v1')
|
||||||
},
|
},
|
||||||
|
|
||||||
getCommonMonitor() {
|
getCommonMonitor() {
|
||||||
return axios.post(getCommonMonitor, {})
|
return ysdpPost('/api/getCommonMonitor/v1')
|
||||||
},
|
},
|
||||||
|
|
||||||
getDutyList() {
|
getDutyList() {
|
||||||
return axios.post(getDutyList, {})
|
return ysdpPost('/api/getDutyList/v1')
|
||||||
},
|
},
|
||||||
|
|
||||||
getStationMaxList() {
|
getStationMaxList() {
|
||||||
return axios.post(getStationMaxList, {})
|
return ysdpPost('/api/getStationMaxList/v1')
|
||||||
},
|
},
|
||||||
|
|
||||||
getIndicator() {
|
getIndicator() {
|
||||||
return axios.post(getIndicator, {})
|
return ysdpPost('/api/getIndicator/v1')
|
||||||
},
|
},
|
||||||
|
|
||||||
getQxjGdybHour() {
|
getQxjGdybHour() {
|
||||||
return axios.post(getQxjGdybHour, {})
|
return ysdpPost('/api/getQxjGdybHour/v1')
|
||||||
},
|
},
|
||||||
|
|
||||||
getYunyingInfo() {
|
getYunyingInfo() {
|
||||||
return axios.post(getYunyingInfo, {})
|
return ysdpPost('/api/getYunyingInfo/v1')
|
||||||
},
|
},
|
||||||
|
|
||||||
getTaskNotice() {
|
getTaskNotice() {
|
||||||
return axios.post(getTaskNotice, {})
|
return ysdpPost('/api/getTaskNotice/v1')
|
||||||
|
},
|
||||||
|
|
||||||
|
getInOutStationRank() {
|
||||||
|
return ysdpPost('/api/traffic/getInOutStationRank/v1')
|
||||||
|
},
|
||||||
|
|
||||||
|
getTransferStationRank() {
|
||||||
|
return ysdpPost('/api/traffic/getTransferStationRank/v1')
|
||||||
|
},
|
||||||
|
|
||||||
|
getLineList() {
|
||||||
|
return ysdpPost('/api/traffic/getLineList/v1')
|
||||||
|
},
|
||||||
|
|
||||||
|
getLinePassengerFlow() {
|
||||||
|
return ysdpPost('/api/traffic/getLinePassengerFlow/v1')
|
||||||
|
},
|
||||||
|
|
||||||
|
getStationPassengerFlow(stationId) {
|
||||||
|
return ysdpPost('/api/traffic/getStationPassengerFlow/v1', { stationId })
|
||||||
|
},
|
||||||
|
|
||||||
|
getStationsByLine(lineId) {
|
||||||
|
return ysdpPost('/api/traffic/getStationsByLine/v1', { lineId })
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanTrafficCache() {
|
||||||
|
return ysdpPost('/api/traffic/cleanCache')
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanApiCache() {
|
||||||
|
return ysdpPost('/api/cleanApiCache')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
const TIME_KEYS = ['time1', 'time2', 'time3', 'time4', 'time5', 'time6', 'time7', 'time8', 'time9', 'time10']
|
||||||
|
|
||||||
|
/** 从 time1~time10 对象提取时序数值 */
|
||||||
|
export function extractTimeValues(dataObj) {
|
||||||
|
return TIME_KEYS.map(key => {
|
||||||
|
const value = dataObj?.[key]
|
||||||
|
return value ? parseFloat(value) : 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将单站客流接口数据转为 Traffic4 图表结构 */
|
||||||
|
export function processStationPassengerFlow(data) {
|
||||||
|
if (!data) return null
|
||||||
|
|
||||||
|
return {
|
||||||
|
compareDate: data.compareDate || '',
|
||||||
|
inOut: {
|
||||||
|
actual: extractTimeValues(data.inOut?.today),
|
||||||
|
compare: extractTimeValues(data.inOut?.compareDay)
|
||||||
|
},
|
||||||
|
transfer: {
|
||||||
|
actual: extractTimeValues(data.transfer?.today),
|
||||||
|
compare: extractTimeValues(data.transfer?.compareDay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 计算图表 Y 轴最大值(按万取整) */
|
||||||
|
export function calcChartYAxisMax(...seriesList) {
|
||||||
|
const maxValue = Math.max(...seriesList.flat(), 0)
|
||||||
|
return Math.ceil(maxValue / 10000) * 10000 || 10000
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将接口 lineId(如 07、04,07)转为页面展示格式(7、4,7) */
|
||||||
|
export function normalizeLineIds(lineId) {
|
||||||
|
if (!lineId) return ''
|
||||||
|
return String(lineId)
|
||||||
|
.split(',')
|
||||||
|
.map(id => String(parseInt(id.trim(), 10)))
|
||||||
|
.filter(id => id && id !== 'NaN')
|
||||||
|
.join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 客流数值转万人(保留两位小数) */
|
||||||
|
export function toWanRen(value) {
|
||||||
|
const num = parseFloat(value) || 0
|
||||||
|
return (num / 10000).toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将站点排名接口数据映射为 TrafficRankRow 所需结构 */
|
||||||
|
export function mapStationRankList(data, type = '进出站') {
|
||||||
|
if (!Array.isArray(data) || !data.length) return []
|
||||||
|
|
||||||
|
const maxTotal = Math.max(...data.map(item => parseFloat(item.total) || 0))
|
||||||
|
|
||||||
|
return data.map((item, index) => {
|
||||||
|
const total = parseFloat(item.total) || 0
|
||||||
|
const compare = parseFloat(item.compareTotal) || 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
rank: index + 1,
|
||||||
|
lines: normalizeLineIds(item.lineId),
|
||||||
|
station: item.stationName || '',
|
||||||
|
type,
|
||||||
|
today: toWanRen(total),
|
||||||
|
compare: toWanRen(compare),
|
||||||
|
percent: maxTotal > 0 ? Math.round((total / maxTotal) * 100) : 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const ysdpRequest = axios.create({
|
||||||
|
baseURL: `${import.meta.env.VITE_API_BASE_URL || ''}/juntech-ysdp`,
|
||||||
|
timeout: 30000
|
||||||
|
})
|
||||||
|
|
||||||
|
/** POST 请求,参数以 application/x-www-form-urlencoded 提交 */
|
||||||
|
export function ysdpPost(path, data = {}) {
|
||||||
|
const body = new URLSearchParams()
|
||||||
|
Object.entries(data).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined && value !== null && value !== '') {
|
||||||
|
body.append(key, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return ysdpRequest.post(path, body, {
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ysdpRequest
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
const TIME_KEYS = ['time1', 'time2', 'time3', 'time4', 'time5', 'time6', 'time7', 'time8', 'time9', 'time10']
|
||||||
|
|
||||||
|
/** 根据当前时刻计算对应的 time 索引(time1~time10) */
|
||||||
|
export function getCurrentTimeIndex() {
|
||||||
|
const hour = new Date().getHours()
|
||||||
|
if (hour < 5) return 0
|
||||||
|
return Math.min(Math.ceil((hour - 5) / 2), 9)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimeValues(dataObj) {
|
||||||
|
return TIME_KEYS.map(key => {
|
||||||
|
const value = dataObj?.[key]
|
||||||
|
return value ? parseFloat(value) : 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将 getYunyingInfo 接口数据转为图表组件所需结构 */
|
||||||
|
export function processYunyingInfo(dataList) {
|
||||||
|
if (!Array.isArray(dataList)) return []
|
||||||
|
|
||||||
|
const currentTimeIndex = getCurrentTimeIndex()
|
||||||
|
|
||||||
|
return dataList.map(item => {
|
||||||
|
const barData = getTimeValues(item.today)
|
||||||
|
const yellowData = getTimeValues(item.compareDay)
|
||||||
|
const redData = getTimeValues(item.maxDay)
|
||||||
|
|
||||||
|
const maxValue = redData[currentTimeIndex]
|
||||||
|
const compareMaxValue = yellowData[currentTimeIndex]
|
||||||
|
const totalSum = item.today?.totalSum ? parseFloat(item.today.totalSum) : 0
|
||||||
|
const total = barData.reduce((sum, val) => sum + val, 0)
|
||||||
|
const totalStr = Math.floor(total).toString()
|
||||||
|
|
||||||
|
return {
|
||||||
|
lineId: parseInt(item.line, 10),
|
||||||
|
line: parseInt(item.line, 10),
|
||||||
|
total: totalStr,
|
||||||
|
actual: barData,
|
||||||
|
compare: yellowData,
|
||||||
|
history: redData,
|
||||||
|
barData,
|
||||||
|
yellowData,
|
||||||
|
redData,
|
||||||
|
digits: totalStr.split('').map(Number),
|
||||||
|
compareDate: item.compareDate || '',
|
||||||
|
maxDate: item.maxDay?.maxDate || '',
|
||||||
|
maxValue,
|
||||||
|
compareMaxValue,
|
||||||
|
totalSum
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -48,7 +48,8 @@ const fetchOperateInfo = async () => {
|
||||||
const response = await server.getOperateInfo()
|
const response = await server.getOperateInfo()
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
const { level: levelData, begindate } = response.data.data
|
const { level: levelData, begindate } = response.data.data
|
||||||
level.value = levelData
|
const levelMatch = String(levelData).match(/\d+/)
|
||||||
|
level.value = levelMatch ? levelMatch[0] : levelData
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const begin = new Date(begindate)
|
const begin = new Date(begindate)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
import { ref, onMounted, nextTick } from 'vue'
|
import { ref, onMounted, nextTick } from 'vue'
|
||||||
import * as echarts from 'echarts'
|
import * as echarts from 'echarts'
|
||||||
import server from '@/utils/service'
|
import server from '@/utils/service'
|
||||||
|
import { processYunyingInfo } from '@/utils/yunying'
|
||||||
|
|
||||||
const chartRefs = ref([])
|
const chartRefs = ref([])
|
||||||
|
|
||||||
|
|
@ -55,8 +56,6 @@ const charts = ref(chartDataList.value)
|
||||||
|
|
||||||
const chartInstances = ref([])
|
const chartInstances = ref([])
|
||||||
|
|
||||||
const timeKeys = ['time1', 'time2', 'time3', 'time4', 'time5', 'time6', 'time7', 'time8', 'time9', 'time10']
|
|
||||||
|
|
||||||
const getNumberBoxColor = (number) => {
|
const getNumberBoxColor = (number) => {
|
||||||
const colors = {
|
const colors = {
|
||||||
3: '#FBD61D',
|
3: '#FBD61D',
|
||||||
|
|
@ -99,60 +98,18 @@ const getBaifenbiColorStyle = (chart) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const processData = (dataList) => {
|
const processData = (dataList) => {
|
||||||
// 根据当前系统时间计算对应的 time 索引
|
return processYunyingInfo(dataList).map(item => ({
|
||||||
const getCurrentTimeIndex = () => {
|
barData: item.barData,
|
||||||
const now = new Date()
|
yellowData: item.yellowData,
|
||||||
const hour = now.getHours()
|
redData: item.redData,
|
||||||
const minutes = now.getMinutes()
|
number: item.line,
|
||||||
// 5 点和 6 点对应 time1,7 点和 8 点对应 time2,以此类推
|
digits: item.digits.length > 0 ? item.digits : [0],
|
||||||
// time1: 5-6, time2: 7-8, time3: 9-10, time4: 11-12, time5: 13-14
|
|
||||||
// time6: 15-16, time7: 17-18, time8: 19-20, time9: 21-22, time10: 23-24
|
|
||||||
if (hour < 5) return 0
|
|
||||||
// 如果当前小时是偶数且分钟大于 0,或者当前小时是奇数,都算下一个时间段
|
|
||||||
// 例如:10:30,10 是偶数,但已经过了 10 点,应该算 time4
|
|
||||||
// 11:00-11:59 也算 time4,12:00-12:59 也算 time4
|
|
||||||
const timeIndex = Math.ceil((hour - 5) / 2)
|
|
||||||
const result = Math.min(timeIndex, 9) // 最大到 time10 (索引 9)
|
|
||||||
console.log(`当前时间:${hour}:${minutes},计算得到的 time 索引:${result}, 对应 ${timeKeys[result]}`)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentTimeIndex = getCurrentTimeIndex()
|
|
||||||
|
|
||||||
return dataList.map(item => {
|
|
||||||
const getValues = (dataObj) => {
|
|
||||||
return timeKeys.map(key => {
|
|
||||||
const value = dataObj[key]
|
|
||||||
return value ? parseFloat(value) : 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const barData = getValues(item.today)
|
|
||||||
const yellowData = getValues(item.compareDay)
|
|
||||||
const redData = getValues(item.maxDay)
|
|
||||||
|
|
||||||
// 根据当前时间获取对应的 maxDay 值和 compareDay 值
|
|
||||||
const maxValue = redData[currentTimeIndex]
|
|
||||||
const compareMaxValue = yellowData[currentTimeIndex]
|
|
||||||
const totalSum = item.today?.totalSum ? parseFloat(item.today.totalSum) : 0
|
|
||||||
|
|
||||||
const total = barData.reduce((sum, val) => sum + val, 0)
|
|
||||||
const totalStr = Math.floor(total).toString()
|
|
||||||
const digits = totalStr.split('').map(Number)
|
|
||||||
|
|
||||||
return {
|
|
||||||
barData,
|
|
||||||
yellowData,
|
|
||||||
redData,
|
|
||||||
number: parseInt(item.line),
|
|
||||||
digits: digits.length > 0 ? digits : [0],
|
|
||||||
compareDate: item.compareDate,
|
compareDate: item.compareDate,
|
||||||
maxDate: item.maxDay?.maxDate || '',
|
maxDate: item.maxDate,
|
||||||
maxValue: maxValue,
|
maxValue: item.maxValue,
|
||||||
compareMaxValue: compareMaxValue,
|
compareMaxValue: item.compareMaxValue,
|
||||||
totalSum: totalSum
|
totalSum: item.totalSum
|
||||||
}
|
}))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchYunyingInfo = async () => {
|
const fetchYunyingInfo = async () => {
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, watch } from 'vue'
|
import { computed } from 'vue'
|
||||||
import metroMenuData from '@/utils/lineStationList.json'
|
|
||||||
import { METRO_LINES } from '@/utils/const'
|
import { METRO_LINES } from '@/utils/const'
|
||||||
|
import { useTrafficLineStation } from '@/composables/useTrafficLineStation'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
line: {
|
line: {
|
||||||
|
|
@ -31,12 +30,10 @@ const props = defineProps({
|
||||||
station: {
|
station: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:line', 'update:station'])
|
const emit = defineEmits(['update:line', 'update:station'])
|
||||||
const lineStationList = metroMenuData.lineStationList || []
|
|
||||||
|
|
||||||
const selectedLine = computed({
|
const selectedLine = computed({
|
||||||
get: () => props.line,
|
get: () => props.line,
|
||||||
|
|
@ -48,25 +45,11 @@ const selectedStation = computed({
|
||||||
set: value => emit('update:station', value)
|
set: value => emit('update:station', value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { lineOptions, stationOptions } = useTrafficLineStation(
|
||||||
|
() => props.line,
|
||||||
const lineOptions = computed(() => {
|
() => props.station,
|
||||||
return lineStationList.map(item => ({
|
emit
|
||||||
label: item.nameCn,
|
)
|
||||||
value: item.lineId
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
const stationOptions = computed(() => {
|
|
||||||
const currentLine = lineStationList.find(item => item.lineId === selectedLine.value)
|
|
||||||
const stationList = currentLine?.stationList || []
|
|
||||||
|
|
||||||
return stationList.map(item => ({
|
|
||||||
label: item.nameCn,
|
|
||||||
value: item.statId,
|
|
||||||
line: item.line
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
const lineDisplayText = computed(() => {
|
const lineDisplayText = computed(() => {
|
||||||
const currentLine = lineOptions.value.find(item => item.value === props.line)
|
const currentLine = lineOptions.value.find(item => item.value === props.line)
|
||||||
|
|
@ -78,8 +61,6 @@ const stationDisplayText = computed(() => {
|
||||||
return String(currentStation?.label || stationOptions.value[0]?.label || '')
|
return String(currentStation?.label || stationOptions.value[0]?.label || '')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const lineTheme = computed(() => {
|
const lineTheme = computed(() => {
|
||||||
return METRO_LINES[Number(selectedLine.value)] || {
|
return METRO_LINES[Number(selectedLine.value)] || {
|
||||||
bg: '#ED6E00',
|
bg: '#ED6E00',
|
||||||
|
|
@ -89,7 +70,7 @@ const lineTheme = computed(() => {
|
||||||
|
|
||||||
const themeStyle = computed(() => ({
|
const themeStyle = computed(() => ({
|
||||||
'--metro-bg': lineTheme.value.bg,
|
'--metro-bg': lineTheme.value.bg,
|
||||||
'--metro-text': lineTheme.value.color,
|
'--metro-text': lineTheme.value.color
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const getFilterItemStyle = (text, type) => {
|
const getFilterItemStyle = (text, type) => {
|
||||||
|
|
@ -105,18 +86,6 @@ const getFilterItemStyle = (text, type) => {
|
||||||
width: `${Math.max(rule.padding + contentLength * rule.charWidth)}px`
|
width: `${Math.max(rule.padding + contentLength * rule.charWidth)}px`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(stationOptions, options => {
|
|
||||||
if (!options.length) {
|
|
||||||
emit('update:station', '')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasCurrentStation = options.some(item => item.value === props.station)
|
|
||||||
if (!hasCurrentStation) {
|
|
||||||
emit('update:station', options[0].value)
|
|
||||||
}
|
|
||||||
}, { immediate: true })
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, watch } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { Calendar } from '@element-plus/icons-vue'
|
import { Calendar } from '@element-plus/icons-vue'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||||
import metroMenuData from '@/utils/lineStationList.json'
|
|
||||||
import { METRO_LINES } from '@/utils/const'
|
import { METRO_LINES } from '@/utils/const'
|
||||||
|
import { useTrafficLineStation } from '@/composables/useTrafficLineStation'
|
||||||
|
|
||||||
import 'dayjs/locale/zh-cn'
|
import 'dayjs/locale/zh-cn'
|
||||||
|
|
||||||
|
|
@ -53,7 +53,6 @@ const props = defineProps({
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:line', 'update:station', 'update:month'])
|
const emit = defineEmits(['update:line', 'update:station', 'update:month'])
|
||||||
const lineStationList = metroMenuData.lineStationList || []
|
|
||||||
|
|
||||||
const selectedLine = computed({
|
const selectedLine = computed({
|
||||||
get: () => props.line,
|
get: () => props.line,
|
||||||
|
|
@ -70,23 +69,11 @@ const selectedMonth = computed({
|
||||||
set: value => emit('update:month', value)
|
set: value => emit('update:month', value)
|
||||||
})
|
})
|
||||||
|
|
||||||
const lineOptions = computed(() => {
|
const { lineOptions, stationOptions } = useTrafficLineStation(
|
||||||
return lineStationList.map(item => ({
|
() => props.line,
|
||||||
label: item.nameCn,
|
() => props.station,
|
||||||
value: item.lineId
|
emit
|
||||||
}))
|
)
|
||||||
})
|
|
||||||
|
|
||||||
const stationOptions = computed(() => {
|
|
||||||
const currentLine = lineStationList.find(item => item.lineId === selectedLine.value)
|
|
||||||
const stationList = currentLine?.stationList || []
|
|
||||||
|
|
||||||
return stationList.map(item => ({
|
|
||||||
label: item.nameCn,
|
|
||||||
value: item.statId,
|
|
||||||
line: item.line
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
const lineDisplayText = computed(() => {
|
const lineDisplayText = computed(() => {
|
||||||
const currentLine = lineOptions.value.find(item => item.value === props.line)
|
const currentLine = lineOptions.value.find(item => item.value === props.line)
|
||||||
|
|
@ -109,7 +96,7 @@ const lineTheme = computed(() => {
|
||||||
|
|
||||||
const themeStyle = computed(() => ({
|
const themeStyle = computed(() => ({
|
||||||
'--metro-bg': lineTheme.value.bg,
|
'--metro-bg': lineTheme.value.bg,
|
||||||
'--metro-text': lineTheme.value.color,
|
'--metro-text': lineTheme.value.color
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const getFilterItemStyle = (text, type) => {
|
const getFilterItemStyle = (text, type) => {
|
||||||
|
|
@ -125,18 +112,6 @@ const getFilterItemStyle = (text, type) => {
|
||||||
width: `${Math.max(rule.padding + contentLength * rule.charWidth)}px`
|
width: `${Math.max(rule.padding + contentLength * rule.charWidth)}px`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(stationOptions, options => {
|
|
||||||
if (!options.length) {
|
|
||||||
emit('update:station', '')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasCurrentStation = options.some(item => item.value === props.station)
|
|
||||||
if (!hasCurrentStation) {
|
|
||||||
emit('update:station', options[0].value)
|
|
||||||
}
|
|
||||||
}, { immediate: true })
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -7,21 +7,27 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import TrafficRankRow from './TrafficRankRow.vue'
|
import TrafficRankRow from './TrafficRankRow.vue'
|
||||||
|
import server from '@/utils/service'
|
||||||
|
import { mapStationRankList } from '@/utils/traffic'
|
||||||
|
|
||||||
const rankList = ref([
|
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 },
|
const fetchRankList = async () => {
|
||||||
{ rank: 3, lines: '1,3,7,15', station: '延安西路', type: '进出站', today: '6.40', compare: '3.20', percent: 70 },
|
try {
|
||||||
{ rank: 4, lines: '1,3,7,15', station: '上大路', type: '进出站', today: '6.20', compare: '6.20', percent: 50 },
|
const response = await server.getInOutStationRank()
|
||||||
{ rank: 5, lines: '1,3,7,15', station: '上海体育馆', type: '进出站', today: '6.20', compare: '6.20', percent: 40 },
|
if (response.data.success) {
|
||||||
{ rank: 6, lines: '1,3,7,15', station: '上海南站', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
rankList.value = mapStationRankList(response.data.data, '进出站')
|
||||||
{ 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 },
|
} catch (error) {
|
||||||
{ rank: 9, lines: '1,3,7,15', station: '红宝石路', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
console.error('获取进出站客流排名失败:', error)
|
||||||
{ rank: 10, lines: '1,3,7,15', station: '吴中路', type: '进出站', today: '6.20', compare: '6.20', percent: 10 }
|
}
|
||||||
])
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchRankList()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,27 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import TrafficRankRow from './TrafficRankRow.vue'
|
import TrafficRankRow from './TrafficRankRow.vue'
|
||||||
|
import server from '@/utils/service'
|
||||||
|
import { mapStationRankList } from '@/utils/traffic'
|
||||||
|
|
||||||
const rankList = ref([
|
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 },
|
const fetchRankList = async () => {
|
||||||
{ rank: 3, lines: '1,3,7,15', station: '延安西路', type: '进出站', today: '16.40', compare: '3.20', percent: 70 },
|
try {
|
||||||
{ rank: 4, lines: '1,3,7', station: '上大路', type: '进出站', today: '6.20', compare: '6.20', percent: 50 },
|
const response = await server.getTransferStationRank()
|
||||||
{ rank: 5, lines: '1,15', station: '上海体育馆', type: '进出站', today: '6.20', compare: '6.20', percent: 40 },
|
if (response.data.success) {
|
||||||
{ rank: 6, lines: '1,3,7,15', station: '上海南站', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
rankList.value = mapStationRankList(response.data.data, '换乘站')
|
||||||
{ 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 },
|
} catch (error) {
|
||||||
{ rank: 9, lines: '1,3', station: '红宝石路', type: '进出站', today: '6.20', compare: '6.20', percent: 38 },
|
console.error('获取换乘站客流排名失败:', error)
|
||||||
{ rank: 10, lines: '15', station: '吴中路', type: '进出站', today: '6.20', compare: '6.20', percent: 10 }
|
}
|
||||||
])
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchRankList()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<section class="traffic-3">
|
<section class="traffic-3">
|
||||||
<div class="chart-legend">
|
<div class="chart-legend">
|
||||||
<span class="legend-item legend-item--actual">实际</span>
|
<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--compare">对比日{{ compareDateLabel }}</span>
|
||||||
<span class="legend-item legend-item--history">历史峰值</span>
|
<span class="legend-item legend-item--history">历史峰值</span>
|
||||||
<span class="legend-unit">单位:万人次</span>
|
<span class="legend-unit">单位:万人次</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -35,45 +35,37 @@ import * as echarts from 'echarts'
|
||||||
import Odometer from 'odometer'
|
import Odometer from 'odometer'
|
||||||
import 'odometer/themes/odometer-theme-default.css'
|
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 { processYunyingInfo } from '@/utils/yunying'
|
||||||
|
|
||||||
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:///imgs/traffic/4-item.png'
|
||||||
|
|
||||||
const lineCards = ref([
|
const lineCards = ref([])
|
||||||
{
|
const compareDate = ref('')
|
||||||
lineId: 3,
|
|
||||||
total: '2746782',
|
const compareDateLabel = computed(() => {
|
||||||
actual: [2600, 4700, 2400, 3900, 2300, 4100, 2600, 3300, 1700, 2200],
|
return compareDate.value ? `(${compareDate.value})` : ''
|
||||||
compare: [4850, 4550, 4650, 4300, 4300, 4550, 4700, 3900, 3900, 3000],
|
})
|
||||||
history: [5000, 4600, 4700, 4200, 4200, 4450, 4600, 4300, 3900, 3600]
|
|
||||||
},
|
const fetchLinePassengerFlow = async () => {
|
||||||
{
|
try {
|
||||||
lineId: 4,
|
const response = await server.getLinePassengerFlow()
|
||||||
total: '2746782',
|
if (response.data.success) {
|
||||||
actual: [700, 950, 1550, 4200, 1700, 1350, 1600, 1550, 2500, 1400],
|
const processed = processYunyingInfo(response.data.data)
|
||||||
compare: [1150, 1450, 2200, 3900, 2500, 1550, 1700, 1650, 2950, 2050],
|
compareDate.value = processed[0]?.compareDate || ''
|
||||||
history: [1350, 2200, 3100, 4450, 3000, 2650, 2550, 2750, 4200, 4550]
|
lineCards.value = processed.map(item => ({
|
||||||
},
|
lineId: item.lineId,
|
||||||
{
|
total: String(Math.floor(item.totalSum || 0)),
|
||||||
lineId: 7,
|
actual: item.actual,
|
||||||
total: '2746782',
|
compare: item.compare,
|
||||||
actual: [2900, 3300, 5000, 2300, 2000, 1900, 2600, 4050, 1700, 1700],
|
history: item.history
|
||||||
compare: [3200, 3700, 5200, 2500, 2400, 2500, 3100, 4700, 2400, 2400],
|
}))
|
||||||
history: [3300, 4100, 5300, 2700, 2650, 2900, 3500, 4950, 3300, 2700]
|
}
|
||||||
},
|
} catch (error) {
|
||||||
{
|
console.error('获取线路客流信息失败:', error)
|
||||||
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(() => {
|
const chartCards = computed(() => {
|
||||||
return lineCards.value.map(item => ({
|
return lineCards.value.map(item => ({
|
||||||
|
|
@ -82,7 +74,7 @@ const chartCards = computed(() => {
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
const targetTotals = computed(() => lineCards.value.map(item => item.total.split('')))
|
const targetTotals = computed(() => lineCards.value.map(item => String(item.total || '0').split('')))
|
||||||
const digitEls = []
|
const digitEls = []
|
||||||
const odometerInstances = []
|
const odometerInstances = []
|
||||||
|
|
||||||
|
|
@ -132,6 +124,7 @@ watch(targetTotals, async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
await fetchLinePassengerFlow()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
initOdometers()
|
initOdometers()
|
||||||
requestAnimationFrame(updateOdometers)
|
requestAnimationFrame(updateOdometers)
|
||||||
|
|
@ -149,7 +142,11 @@ const getLineStyle = lineId => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createChartOption = item => ({
|
const createChartOption = item => {
|
||||||
|
const maxValue = Math.max(...item.actual, ...item.compare, ...item.history, 0)
|
||||||
|
const yAxisMax = Math.ceil(maxValue / 10000) * 10000 || 10000
|
||||||
|
|
||||||
|
return {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
animationDuration: 900,
|
animationDuration: 900,
|
||||||
grid: {
|
grid: {
|
||||||
|
|
@ -184,8 +181,8 @@ const createChartOption = item => ({
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 5000,
|
max: yAxisMax,
|
||||||
interval: 1000,
|
interval: yAxisMax / 5,
|
||||||
axisLine: {
|
axisLine: {
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
|
|
@ -196,7 +193,8 @@ const createChartOption = item => ({
|
||||||
color: 'rgba(226, 239, 255, 0.96)',
|
color: 'rgba(226, 239, 255, 0.96)',
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
margin: 10
|
margin: 10,
|
||||||
|
formatter: value => (value / 10000).toFixed(0)
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
|
|
@ -248,7 +246,7 @@ const createChartOption = item => ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '对比日(2026-03-05)',
|
name: compareDateLabel.value || '对比日',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: item.compare,
|
data: item.compare,
|
||||||
smooth: false,
|
smooth: false,
|
||||||
|
|
@ -281,7 +279,8 @@ const createChartOption = item => ({
|
||||||
fontSize: 12
|
fontSize: 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<section class="traffic-4">
|
<section class="traffic-4">
|
||||||
<StationFilter class="traffic-4__filter" v-model:line="selectedLine" v-model:station="selectedStation"
|
<StationFilter class="traffic-4__filter" v-model:line="selectedLine" v-model:station="selectedStation" />
|
||||||
/>
|
|
||||||
<div class="chart-shell">
|
<div class="chart-shell">
|
||||||
<v-chart class="traffic-4__chart" :option="chartOption" autoresize />
|
<v-chart class="traffic-4__chart" :option="chartOption" autoresize />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -9,31 +8,54 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed ,ref} from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import StationFilter from './StationFilter.vue'
|
import StationFilter from './StationFilter.vue'
|
||||||
import VChart from 'vue-echarts'
|
import VChart from 'vue-echarts'
|
||||||
import * as echarts from 'echarts'
|
import * as echarts from 'echarts'
|
||||||
|
import server from '@/utils/service'
|
||||||
|
import { processStationPassengerFlow, calcChartYAxisMax } from '@/utils/traffic'
|
||||||
|
|
||||||
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 topActual = [800, 900, 2100, 4050, 1500, 1450, 1200, 2300, 900, 900]
|
|
||||||
const topCompare = [1400, 1800, 3400, 4500, 2950, 2600, 2800, 4300, 4300, 4850]
|
|
||||||
|
|
||||||
const bottomActual = [4800, 4400, 3000, 1600, 3150, 3500, 3350, 1850, 1450, 1650]
|
|
||||||
const bottomCompare = [5400, 5300, 4900, 2200, 4700, 5000, 3850, 4700, 5350, 5350 ]
|
|
||||||
|
|
||||||
const pointGlowSymbol = 'image:///imgs/traffic/4-item.png'
|
const pointGlowSymbol = 'image:///imgs/traffic/4-item.png'
|
||||||
const pointGlowSymbol1 = 'image:///imgs/traffic/4-item1.png'
|
const pointGlowSymbol1 = 'image:///imgs/traffic/4-item1.png'
|
||||||
const topPointGlow = hours.map((hour, index) => [hour, topActual[index]])
|
|
||||||
const bottomPointGlow = hours.map((hour, index) => [hour, bottomActual[index]])
|
|
||||||
|
|
||||||
|
const topActual = ref([])
|
||||||
|
const topCompare = ref([])
|
||||||
|
const bottomActual = ref([])
|
||||||
|
const bottomCompare = ref([])
|
||||||
|
|
||||||
|
const fetchStationFlow = async () => {
|
||||||
|
if (!selectedStation.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await server.getStationPassengerFlow(selectedStation.value)
|
||||||
|
if (response.data.success) {
|
||||||
|
const processed = processStationPassengerFlow(response.data.data)
|
||||||
|
if (!processed) return
|
||||||
|
topActual.value = processed.inOut.actual
|
||||||
|
topCompare.value = processed.inOut.compare
|
||||||
|
bottomActual.value = processed.transfer.actual
|
||||||
|
bottomCompare.value = processed.transfer.compare
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取车站客流详情失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(selectedStation, () => {
|
||||||
|
fetchStationFlow()
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
const topPointGlow = computed(() => hours.map((hour, index) => [hour, topActual.value[index] || 0]))
|
||||||
|
const bottomPointGlow = computed(() => hours.map((hour, index) => [hour, bottomActual.value[index] || 0]))
|
||||||
|
|
||||||
|
const yAxisMaxTop = computed(() => calcChartYAxisMax(topActual.value, topCompare.value))
|
||||||
|
const yAxisMaxBottom = computed(() => calcChartYAxisMax(bottomActual.value, bottomCompare.value))
|
||||||
|
const yAxisIntervalTop = computed(() => yAxisMaxTop.value / 5)
|
||||||
|
const yAxisIntervalBottom = computed(() => yAxisMaxBottom.value / 5)
|
||||||
|
|
||||||
const chartOption = computed(() => ({
|
const chartOption = computed(() => ({
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
|
|
@ -63,21 +85,15 @@ const chartOption = computed(() => ({
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: hours,
|
data: hours,
|
||||||
gridIndex: 0,
|
gridIndex: 0,
|
||||||
axisLine: {
|
axisLine: { show: true },
|
||||||
show: true
|
axisTick: { show: false },
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: 'rgba(226, 239, 255, 0.95)',
|
color: 'rgba(226, 239, 255, 0.95)',
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
margin: 12
|
margin: 12
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: { show: false }
|
||||||
show: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'category',
|
type: 'category',
|
||||||
|
|
@ -85,35 +101,26 @@ const chartOption = computed(() => ({
|
||||||
data: hours,
|
data: hours,
|
||||||
gridIndex: 1,
|
gridIndex: 1,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
axisLine: {
|
axisLine: { show: true },
|
||||||
show: true
|
axisTick: { show: false },
|
||||||
},
|
axisLabel: { show: false },
|
||||||
axisTick: {
|
splitLine: { show: false }
|
||||||
show: false
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
yAxis: [
|
yAxis: [
|
||||||
{
|
{
|
||||||
type: 'value',
|
type: 'value',
|
||||||
gridIndex: 0,
|
gridIndex: 0,
|
||||||
interval: 1000,
|
min: 0,
|
||||||
axisLine: {
|
max: yAxisMaxTop.value,
|
||||||
show: false
|
interval: yAxisIntervalTop.value,
|
||||||
},
|
axisLine: { show: false },
|
||||||
axisTick: {
|
axisTick: { show: false },
|
||||||
show: false
|
|
||||||
},
|
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: 'rgba(222, 236, 255, 0.9)',
|
color: 'rgba(222, 236, 255, 0.9)',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
margin: 10
|
margin: 10,
|
||||||
|
formatter: value => (value / 10000).toFixed(0)
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
|
|
@ -127,18 +134,17 @@ const chartOption = computed(() => ({
|
||||||
{
|
{
|
||||||
type: 'value',
|
type: 'value',
|
||||||
gridIndex: 1,
|
gridIndex: 1,
|
||||||
interval: 1000,
|
min: 0,
|
||||||
|
max: yAxisMaxBottom.value,
|
||||||
|
interval: yAxisIntervalBottom.value,
|
||||||
inverse: true,
|
inverse: true,
|
||||||
axisLine: {
|
axisLine: { show: false },
|
||||||
show: false
|
axisTick: { show: false },
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: 'rgba(222, 236, 255, 0.9)',
|
color: 'rgba(222, 236, 255, 0.9)',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
margin: 10
|
margin: 10,
|
||||||
|
formatter: value => (value / 10000).toFixed(0)
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
|
|
@ -155,38 +161,34 @@ const chartOption = computed(() => ({
|
||||||
type: 'scatter',
|
type: 'scatter',
|
||||||
xAxisIndex: 0,
|
xAxisIndex: 0,
|
||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
data: topPointGlow,
|
data: topPointGlow.value,
|
||||||
symbol: pointGlowSymbol,
|
symbol: pointGlowSymbol,
|
||||||
symbolSize: [47, 109],
|
symbolSize: [47, 109],
|
||||||
symbolOffset: [4, 0],
|
symbolOffset: [4, 0],
|
||||||
symbolKeepAspect: false,
|
symbolKeepAspect: false,
|
||||||
silent: false,
|
silent: false,
|
||||||
z: 4,
|
z: 4,
|
||||||
tooltip: {
|
tooltip: { show: false }
|
||||||
show: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'scatter',
|
type: 'scatter',
|
||||||
xAxisIndex: 1,
|
xAxisIndex: 1,
|
||||||
yAxisIndex: 1,
|
yAxisIndex: 1,
|
||||||
data: bottomPointGlow,
|
data: bottomPointGlow.value,
|
||||||
symbol: pointGlowSymbol1,
|
symbol: pointGlowSymbol1,
|
||||||
symbolSize: [47, 109],
|
symbolSize: [47, 109],
|
||||||
symbolOffset: [4, 0],
|
symbolOffset: [4, 0],
|
||||||
symbolKeepAspect: true,
|
symbolKeepAspect: true,
|
||||||
silent: true,
|
silent: true,
|
||||||
z: 4,
|
z: 4,
|
||||||
tooltip: {
|
tooltip: { show: false }
|
||||||
show: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '进出站',
|
name: '进出站',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
xAxisIndex: 0,
|
xAxisIndex: 0,
|
||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
data: topActual,
|
data: topActual.value,
|
||||||
smooth: false,
|
smooth: false,
|
||||||
symbol: 'circle',
|
symbol: 'circle',
|
||||||
symbolSize: 6,
|
symbolSize: 6,
|
||||||
|
|
@ -219,7 +221,7 @@ const chartOption = computed(() => ({
|
||||||
type: 'line',
|
type: 'line',
|
||||||
xAxisIndex: 0,
|
xAxisIndex: 0,
|
||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
data: topCompare,
|
data: topCompare.value,
|
||||||
smooth: false,
|
smooth: false,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
z: 4,
|
z: 4,
|
||||||
|
|
@ -235,7 +237,7 @@ const chartOption = computed(() => ({
|
||||||
type: 'line',
|
type: 'line',
|
||||||
xAxisIndex: 1,
|
xAxisIndex: 1,
|
||||||
yAxisIndex: 1,
|
yAxisIndex: 1,
|
||||||
data: bottomActual,
|
data: bottomActual.value,
|
||||||
smooth: false,
|
smooth: false,
|
||||||
symbol: 'circle',
|
symbol: 'circle',
|
||||||
symbolSize: 6,
|
symbolSize: 6,
|
||||||
|
|
@ -268,7 +270,7 @@ const chartOption = computed(() => ({
|
||||||
type: 'line',
|
type: 'line',
|
||||||
xAxisIndex: 1,
|
xAxisIndex: 1,
|
||||||
yAxisIndex: 1,
|
yAxisIndex: 1,
|
||||||
data: bottomCompare,
|
data: bottomCompare.value,
|
||||||
smooth: false,
|
smooth: false,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
z: 4,
|
z: 4,
|
||||||
|
|
@ -289,7 +291,7 @@ const chartOption = computed(() => ({
|
||||||
},
|
},
|
||||||
formatter: params => {
|
formatter: params => {
|
||||||
const { name, seriesName, value } = params[0]
|
const { name, seriesName, value } = params[0]
|
||||||
return `${name}时<br>${seriesName}: ${value}`
|
return `${name}时<br>${seriesName}: ${(value / 10000).toFixed(2)}万`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
@ -305,8 +307,6 @@ const chartOption = computed(() => ({
|
||||||
background: url('/imgs/traffic/4-bg.png') no-repeat center center;
|
background: url('/imgs/traffic/4-bg.png') no-repeat center center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.traffic-4__filter {
|
.traffic-4__filter {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue