diff --git a/.env.development b/.env.development
index fd7f64f..eab18b8 100644
--- a/.env.development
+++ b/.env.development
@@ -1,3 +1,3 @@
# 开发环境配置
-# 使用相对路径,通过 vite proxy 代理到目标服务器
+# 留空则通过 vite proxy 代理到 http://101.133.172.2:8082
VITE_API_BASE_URL=
diff --git a/.gitignore b/.gitignore
index 76add87..83e8060 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,53 @@
+# 依赖
node_modules
-dist
\ No newline at end of file
+
+# 构建产物
+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-*
diff --git a/src/composables/useTrafficLineStation.js b/src/composables/useTrafficLineStation.js
new file mode 100644
index 0000000..8cfa4c7
--- /dev/null
+++ b/src/composables/useTrafficLineStation.js
@@ -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 }
+}
diff --git a/src/utils/service.js b/src/utils/service.js
index 21732d5..3bc572e 100644
--- a/src/utils/service.js
+++ b/src/utils/service.js
@@ -1,10 +1,9 @@
import { request } from './request'
import { encrypt } from './decrypt'
-import axios from 'axios'
+import { ysdpPost } from './ysdpRequest'
var ipRoot = "http://140.206.138.190:8900"
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 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 getHotEventList = ipRoot + '/api/xckf/getHotEventList/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 xcImgGd = ip + '/lineCrossImg/1734.jpg'
@@ -142,35 +133,67 @@ var server = {
},
getOperateInfo() {
- return axios.post(getOperateInfo, {})
+ return ysdpPost('/api/getOperateInfo/v1')
},
getCommonMonitor() {
- return axios.post(getCommonMonitor, {})
+ return ysdpPost('/api/getCommonMonitor/v1')
},
getDutyList() {
- return axios.post(getDutyList, {})
+ return ysdpPost('/api/getDutyList/v1')
},
getStationMaxList() {
- return axios.post(getStationMaxList, {})
+ return ysdpPost('/api/getStationMaxList/v1')
},
getIndicator() {
- return axios.post(getIndicator, {})
+ return ysdpPost('/api/getIndicator/v1')
},
getQxjGdybHour() {
- return axios.post(getQxjGdybHour, {})
+ return ysdpPost('/api/getQxjGdybHour/v1')
},
getYunyingInfo() {
- return axios.post(getYunyingInfo, {})
+ return ysdpPost('/api/getYunyingInfo/v1')
},
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')
}
}
diff --git a/src/utils/traffic.js b/src/utils/traffic.js
new file mode 100644
index 0000000..548282d
--- /dev/null
+++ b/src/utils/traffic.js
@@ -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
+ }
+ })
+}
diff --git a/src/utils/ysdpRequest.js b/src/utils/ysdpRequest.js
new file mode 100644
index 0000000..4c0c313
--- /dev/null
+++ b/src/utils/ysdpRequest.js
@@ -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
diff --git a/src/utils/yunying.js b/src/utils/yunying.js
new file mode 100644
index 0000000..d503da9
--- /dev/null
+++ b/src/utils/yunying.js
@@ -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
+ }
+ })
+}
diff --git a/src/views/home/components/ModuleOne.vue b/src/views/home/components/ModuleOne.vue
index ed927d1..b0d2fbf 100644
--- a/src/views/home/components/ModuleOne.vue
+++ b/src/views/home/components/ModuleOne.vue
@@ -48,7 +48,8 @@ const fetchOperateInfo = async () => {
const response = await server.getOperateInfo()
if (response.data.success) {
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 begin = new Date(begindate)
diff --git a/src/views/home/components/ModuleSix.vue b/src/views/home/components/ModuleSix.vue
index 47363c8..bbbc26e 100644
--- a/src/views/home/components/ModuleSix.vue
+++ b/src/views/home/components/ModuleSix.vue
@@ -39,6 +39,7 @@
import { ref, onMounted, nextTick } from 'vue'
import * as echarts from 'echarts'
import server from '@/utils/service'
+import { processYunyingInfo } from '@/utils/yunying'
const chartRefs = ref([])
@@ -55,8 +56,6 @@ const charts = ref(chartDataList.value)
const chartInstances = ref([])
-const timeKeys = ['time1', 'time2', 'time3', 'time4', 'time5', 'time6', 'time7', 'time8', 'time9', 'time10']
-
const getNumberBoxColor = (number) => {
const colors = {
3: '#FBD61D',
@@ -99,60 +98,18 @@ const getBaifenbiColorStyle = (chart) => {
}
}
const processData = (dataList) => {
- // 根据当前系统时间计算对应的 time 索引
- const getCurrentTimeIndex = () => {
- const now = new Date()
- const hour = now.getHours()
- const minutes = now.getMinutes()
- // 5 点和 6 点对应 time1,7 点和 8 点对应 time2,以此类推
- // 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,
- maxDate: item.maxDay?.maxDate || '',
- maxValue: maxValue,
- compareMaxValue: compareMaxValue,
- totalSum: totalSum
- }
- })
+ return processYunyingInfo(dataList).map(item => ({
+ barData: item.barData,
+ yellowData: item.yellowData,
+ redData: item.redData,
+ number: item.line,
+ digits: item.digits.length > 0 ? item.digits : [0],
+ compareDate: item.compareDate,
+ maxDate: item.maxDate,
+ maxValue: item.maxValue,
+ compareMaxValue: item.compareMaxValue,
+ totalSum: item.totalSum
+ }))
}
const fetchYunyingInfo = async () => {
diff --git a/src/views/traffic/components/StationFilter.vue b/src/views/traffic/components/StationFilter.vue
index 38de5e4..11f3ea6 100644
--- a/src/views/traffic/components/StationFilter.vue
+++ b/src/views/traffic/components/StationFilter.vue
@@ -18,10 +18,9 @@
\ No newline at end of file
+
diff --git a/src/views/traffic/components/StationTimeFilter.vue b/src/views/traffic/components/StationTimeFilter.vue
index 12572fb..7343a9a 100644
--- a/src/views/traffic/components/StationTimeFilter.vue
+++ b/src/views/traffic/components/StationTimeFilter.vue
@@ -26,12 +26,12 @@
\ No newline at end of file
+
diff --git a/src/views/traffic/components/Traffic1.vue b/src/views/traffic/components/Traffic1.vue
index 8e218e1..e1d6edb 100644
--- a/src/views/traffic/components/Traffic1.vue
+++ b/src/views/traffic/components/Traffic1.vue
@@ -7,21 +7,27 @@