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/.env.production b/.env.production index 7196be4..b193b5a 100644 --- a/.env.production +++ b/.env.production @@ -1,2 +1,2 @@ -# 生产环境配置 -VITE_API_BASE_URL=http://10.104.10.151:8888 +# 生产环境配置 目前是配置代理的方式 +VITE_API_BASE_URL=/juntech-ysdp 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/public/imgs/driving/1-bg.png b/public/imgs/driving/1-bg.png new file mode 100644 index 0000000..3e9ba61 Binary files /dev/null and b/public/imgs/driving/1-bg.png differ diff --git a/public/imgs/driving/1-item-1.png b/public/imgs/driving/1-item-1.png new file mode 100644 index 0000000..765782d Binary files /dev/null and b/public/imgs/driving/1-item-1.png differ diff --git a/public/imgs/driving/1-item-2.png b/public/imgs/driving/1-item-2.png new file mode 100644 index 0000000..dc0bcbf Binary files /dev/null and b/public/imgs/driving/1-item-2.png differ diff --git a/public/imgs/driving/2-bg.png b/public/imgs/driving/2-bg.png new file mode 100644 index 0000000..a8e85c6 Binary files /dev/null and b/public/imgs/driving/2-bg.png differ diff --git a/public/imgs/driving/3-bg.png b/public/imgs/driving/3-bg.png new file mode 100644 index 0000000..1f7cb16 Binary files /dev/null and b/public/imgs/driving/3-bg.png differ diff --git a/public/imgs/driving/3-item-1.png b/public/imgs/driving/3-item-1.png new file mode 100644 index 0000000..96f84fe Binary files /dev/null and b/public/imgs/driving/3-item-1.png differ diff --git a/public/imgs/driving/4-bg.png b/public/imgs/driving/4-bg.png new file mode 100644 index 0000000..e78bf15 Binary files /dev/null and b/public/imgs/driving/4-bg.png differ diff --git a/public/imgs/driving/4-item-1.png b/public/imgs/driving/4-item-1.png new file mode 100644 index 0000000..bb39ef2 Binary files /dev/null and b/public/imgs/driving/4-item-1.png differ diff --git a/public/imgs/driving/5-bg.png b/public/imgs/driving/5-bg.png new file mode 100644 index 0000000..1b7b338 Binary files /dev/null and b/public/imgs/driving/5-bg.png differ diff --git a/public/imgs/driving/5-item-1.png b/public/imgs/driving/5-item-1.png new file mode 100644 index 0000000..6264646 Binary files /dev/null and b/public/imgs/driving/5-item-1.png differ diff --git a/public/imgs/driving/5-item-2.png b/public/imgs/driving/5-item-2.png new file mode 100644 index 0000000..576c2fe Binary files /dev/null and b/public/imgs/driving/5-item-2.png differ diff --git a/public/imgs/driving/6-bg.png b/public/imgs/driving/6-bg.png new file mode 100644 index 0000000..33b83f5 Binary files /dev/null and b/public/imgs/driving/6-bg.png differ diff --git a/public/imgs/driving/7-bg.png b/public/imgs/driving/7-bg.png new file mode 100644 index 0000000..9f55412 Binary files /dev/null and b/public/imgs/driving/7-bg.png differ diff --git a/public/imgs/driving/8-bg.png b/public/imgs/driving/8-bg.png new file mode 100644 index 0000000..fa376f5 Binary files /dev/null and b/public/imgs/driving/8-bg.png differ diff --git a/public/imgs/driving/arrow.png b/public/imgs/driving/arrow.png new file mode 100644 index 0000000..293f10b Binary files /dev/null and b/public/imgs/driving/arrow.png differ diff --git a/public/imgs/layout/menu-bg-active.png b/public/imgs/layout/menu-bg-active.png new file mode 100644 index 0000000..0108733 Binary files /dev/null and b/public/imgs/layout/menu-bg-active.png differ diff --git a/public/imgs/layout/menu-bg.png b/public/imgs/layout/menu-bg.png new file mode 100644 index 0000000..9a38941 Binary files /dev/null and b/public/imgs/layout/menu-bg.png differ diff --git a/public/imgs/top.png b/public/imgs/top.png index 58e88a8..0cd5267 100644 Binary files a/public/imgs/top.png and b/public/imgs/top.png differ diff --git a/src/components/Pyramid3D/index.vue b/src/components/Pyramid3D/index.vue index 33c09f4..1886a69 100644 --- a/src/components/Pyramid3D/index.vue +++ b/src/components/Pyramid3D/index.vue @@ -15,7 +15,7 @@ const props = defineProps({ // 光晕图片路径 glowImage: { type: String, - default: '/imgs/guangyun.png' + default: './imgs/guangyun.png' }, // 是否显示虚线光圈 showRing: { 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/router/index.js b/src/router/index.js index b967b1c..d3637a8 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -33,6 +33,18 @@ const routes = [ name: 'Traffic', component: () => import('@/views/traffic/index.vue'), meta: { title: '客运', requiresAuth: true } + }, + { + path: 'driving', + name: 'Driving', + component: () => import('@/views/driving/index.vue'), + meta: { title: '行车', requiresAuth: true } + }, + { + path: 'construction', + name: 'Construction', + component: () => import('@/views/construction/index.vue'), + meta: { title: '施工', requiresAuth: true } } ], meta: { requiresAuth: true } diff --git a/src/utils/service.js b/src/utils/service.js index 21732d5..08b6c60 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,75 @@ 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 }) + }, + + getBigActivityList({ metroLine, metroStation, activityMonth }) { + return ysdpPost('/api/traffic/getBigActivityList/v1', { + metroLine, + metroStation, + activityMonth + }) + }, + + 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..2f04e8e --- /dev/null +++ b/src/utils/traffic.js @@ -0,0 +1,103 @@ +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) +} + +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 [] + + 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..e3e42a4 --- /dev/null +++ b/src/utils/ysdpRequest.js @@ -0,0 +1,22 @@ +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', + 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/construction/index.vue b/src/views/construction/index.vue new file mode 100644 index 0000000..49f1304 --- /dev/null +++ b/src/views/construction/index.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/views/driving/components/Driving1.vue b/src/views/driving/components/Driving1.vue new file mode 100644 index 0000000..c67dd4d --- /dev/null +++ b/src/views/driving/components/Driving1.vue @@ -0,0 +1,312 @@ + + + + + + + diff --git a/src/views/driving/components/Driving2.vue b/src/views/driving/components/Driving2.vue new file mode 100644 index 0000000..b217b25 --- /dev/null +++ b/src/views/driving/components/Driving2.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/src/views/driving/components/Driving3.vue b/src/views/driving/components/Driving3.vue new file mode 100644 index 0000000..7e50006 --- /dev/null +++ b/src/views/driving/components/Driving3.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/src/views/driving/components/Driving4.vue b/src/views/driving/components/Driving4.vue new file mode 100644 index 0000000..61c4af6 --- /dev/null +++ b/src/views/driving/components/Driving4.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/views/driving/components/Driving5.vue b/src/views/driving/components/Driving5.vue new file mode 100644 index 0000000..2ff3120 --- /dev/null +++ b/src/views/driving/components/Driving5.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/src/views/driving/components/Driving6.vue b/src/views/driving/components/Driving6.vue new file mode 100644 index 0000000..26b2883 --- /dev/null +++ b/src/views/driving/components/Driving6.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/views/driving/components/Driving7.vue b/src/views/driving/components/Driving7.vue new file mode 100644 index 0000000..600a02f --- /dev/null +++ b/src/views/driving/components/Driving7.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/src/views/driving/components/Driving8.vue b/src/views/driving/components/Driving8.vue new file mode 100644 index 0000000..ef3cc09 --- /dev/null +++ b/src/views/driving/components/Driving8.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/src/views/driving/components/DrivingChartPanel.vue b/src/views/driving/components/DrivingChartPanel.vue new file mode 100644 index 0000000..8308e22 --- /dev/null +++ b/src/views/driving/components/DrivingChartPanel.vue @@ -0,0 +1,413 @@ + + + + + diff --git a/src/views/driving/components/MetroLineSelect.vue b/src/views/driving/components/MetroLineSelect.vue new file mode 100644 index 0000000..52c1484 --- /dev/null +++ b/src/views/driving/components/MetroLineSelect.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/src/views/driving/index.vue b/src/views/driving/index.vue new file mode 100644 index 0000000..cc8546d --- /dev/null +++ b/src/views/driving/index.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/views/home/components/DashboardHeader.vue b/src/views/home/components/DashboardHeader.vue index daaf808..f5b1ac5 100644 --- a/src/views/home/components/DashboardHeader.vue +++ b/src/views/home/components/DashboardHeader.vue @@ -1,38 +1,18 @@ \ 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 @@ + + + + + diff --git a/src/views/traffic/components/Traffic8.vue b/src/views/traffic/components/Traffic8.vue index 54d9f44..aa5d837 100644 --- a/src/views/traffic/components/Traffic8.vue +++ b/src/views/traffic/components/Traffic8.vue @@ -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 => {