feat(ticket-order): 优化单线线路展示与同线区间高亮
This commit is contained in:
+61
-37
@@ -106,6 +106,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nodeSlotWidth = window.innerWidth <= 768 ? 58 : 70;
|
||||||
|
const trackPadding = Math.round(nodeSlotWidth / 2);
|
||||||
|
|
||||||
// 1. Render Line Tabs
|
// 1. Render Line Tabs
|
||||||
let tabsHtml = `<div class="tvm-lines-nav" style="display: flex; gap: 8px; overflow-x: auto; padding: 12px; background: var(--surface-color, #f8fafc); border-bottom: 1px solid var(--border-color, #e2e8f0); border-radius: 12px 12px 0 0; -webkit-overflow-scrolling: touch; scrollbar-width: none;">`;
|
let tabsHtml = `<div class="tvm-lines-nav" style="display: flex; gap: 8px; overflow-x: auto; padding: 12px; background: var(--surface-color, #f8fafc); border-bottom: 1px solid var(--border-color, #e2e8f0); border-radius: 12px 12px 0 0; -webkit-overflow-scrolling: touch; scrollbar-width: none;">`;
|
||||||
allLinesData.forEach(line => {
|
allLinesData.forEach(line => {
|
||||||
@@ -143,44 +146,64 @@
|
|||||||
const activeLine = allLinesData.find(l => String(l.id) === String(activeLineId)) || allLinesData[0];
|
const activeLine = allLinesData.find(l => String(l.id) === String(activeLineId)) || allLinesData[0];
|
||||||
const stopsRaw = Array.isArray(activeLine.stops) ? activeLine.stops : (Array.isArray(activeLine.站点列表) ? activeLine.站点列表 : []);
|
const stopsRaw = Array.isArray(activeLine.stops) ? activeLine.stops : (Array.isArray(activeLine.站点列表) ? activeLine.站点列表 : []);
|
||||||
const stops = stopsRaw.map(c => String(c || '').trim()).filter(Boolean);
|
const stops = stopsRaw.map(c => String(c || '').trim()).filter(Boolean);
|
||||||
|
const visibleStops = stops.filter(code => code !== '-');
|
||||||
|
|
||||||
let stationsHtml = `<div class="tvm-stations-line-container" style="overflow-x: auto; padding: 40px 20px 20px 20px; background: var(--bg-color, #ffffff); border-radius: 0 0 12px 12px; -webkit-overflow-scrolling: touch; scrollbar-width: thin;">`;
|
let stationsHtml = `<div class="tvm-stations-line-container" style="overflow-x: auto; padding: 40px 20px 20px 20px; background: var(--bg-color, #ffffff); border-radius: 0 0 12px 12px; -webkit-overflow-scrolling: touch; scrollbar-width: thin;">`;
|
||||||
|
|
||||||
stationsHtml += `<div style="display: flex; position: relative; min-width: max-content; align-items: flex-start;">`;
|
stationsHtml += `<div style="display: flex; position: relative; min-width: max-content; align-items: flex-start;">`;
|
||||||
|
|
||||||
stationsHtml += `<div style="position: absolute; top: 14px; left: 35px; right: 35px; height: 8px; background-color: ${activeLine.color || '#94a3b8'}; border-radius: 4px; z-index: 1;"></div>`;
|
stationsHtml += `<div style="position: absolute; top: 14px; left: ${trackPadding}px; right: ${trackPadding}px; height: 8px; background-color: ${activeLine.color || '#94a3b8'}; border-radius: 4px; z-index: 1;"></div>`;
|
||||||
|
|
||||||
// Draw highlighted route line if start and end are on the same line
|
const getVisibleStopIndex = (targetCode) => {
|
||||||
let routeStartIndex = -1;
|
const targetCanonical = stationCanonicalByCode[String(targetCode || '').trim()] || String(targetCode || '').trim();
|
||||||
let routeEndIndex = -1;
|
return visibleStops.findIndex(code => (stationCanonicalByCode[code] || code) === targetCanonical);
|
||||||
|
};
|
||||||
if (selection[0] && selection[1] && Array.isArray(currentRoute) && currentRoute.length > 0) {
|
|
||||||
routeStartIndex = stops.indexOf(selection[0]);
|
const fromIndex = getVisibleStopIndex(selection[0]);
|
||||||
routeEndIndex = stops.indexOf(selection[1]);
|
const toIndex = getVisibleStopIndex(selection[1]);
|
||||||
|
const sameLineSelection = fromIndex >= 0
|
||||||
if (routeStartIndex !== -1 && routeEndIndex !== -1) {
|
&& toIndex >= 0
|
||||||
const startPos = Math.min(routeStartIndex, routeEndIndex);
|
&& selection[0]
|
||||||
const endPos = Math.max(routeStartIndex, routeEndIndex);
|
&& selection[1]
|
||||||
const leftOffset = 35 + startPos * 70; // 70px is the width of each station node
|
&& (!Array.isArray(currentRouteTransfers) || currentRouteTransfers.length === 0);
|
||||||
const lineWidth = (endPos - startPos) * 70;
|
|
||||||
|
if (sameLineSelection) {
|
||||||
// Base yellow highlight line
|
const startIndex = Math.min(fromIndex, toIndex);
|
||||||
stationsHtml += `<div style="position: absolute; top: 12px; left: ${leftOffset}px; width: ${lineWidth}px; height: 12px; background-color: var(--primary, #facc15); border-radius: 6px; z-index: 2; box-shadow: 0 0 8px rgba(250, 204, 21, 0.6);"></div>`;
|
const endIndex = Math.max(fromIndex, toIndex);
|
||||||
|
const left = (startIndex * nodeSlotWidth) + trackPadding;
|
||||||
// Animated arrows
|
const width = Math.max((endIndex - startIndex) * nodeSlotWidth, 0);
|
||||||
const isForward = routeStartIndex < routeEndIndex;
|
const arrowCount = Math.max(1, Math.floor(width / 40));
|
||||||
const arrowCount = Math.max(1, Math.floor(lineWidth / 40));
|
let arrowsHtml = '';
|
||||||
|
for (let i = 0; i < arrowCount; i++) {
|
||||||
stationsHtml += `<div style="position: absolute; top: 12px; left: ${leftOffset}px; width: ${lineWidth}px; height: 12px; z-index: 3; display: flex; align-items: center; justify-content: space-evenly; overflow: hidden; pointer-events: none;">`;
|
arrowsHtml += `<span style="font-size: 16px; line-height: 1; color: #854d0e; font-weight: 800;">›</span>`;
|
||||||
for (let i = 0; i < arrowCount; i++) {
|
|
||||||
stationsHtml += `<i class="fas fa-chevron-${isForward ? 'right' : 'left'}" style="color: #854d0e; font-size: 10px; animation: tvmArrowMove 1.5s infinite linear; opacity: 0.8;"></i>`;
|
|
||||||
}
|
|
||||||
stationsHtml += `</div>`;
|
|
||||||
}
|
}
|
||||||
|
stationsHtml += `
|
||||||
|
<div style="
|
||||||
|
position: absolute;
|
||||||
|
top: 14px;
|
||||||
|
left: ${left}px;
|
||||||
|
width: ${width}px;
|
||||||
|
height: 8px;
|
||||||
|
background: linear-gradient(90deg, rgba(250,204,21,0.92) 0%, #facc15 100%);
|
||||||
|
border-radius: 999px;
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: 0 0 0 2px rgba(255,255,255,0.85), 0 4px 12px rgba(250, 204, 21, 0.35);
|
||||||
|
overflow: visible;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
position: absolute;
|
||||||
|
inset: -7px 8px auto 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
pointer-events: none;
|
||||||
|
">${arrowsHtml}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasStations = false;
|
let hasStations = false;
|
||||||
stops.forEach((code, index) => {
|
stops.forEach(code => {
|
||||||
if (code === '-') return;
|
if (code === '-') return;
|
||||||
hasStations = true;
|
hasStations = true;
|
||||||
|
|
||||||
@@ -209,28 +232,24 @@
|
|||||||
let nodeSize = isTransfer ? '20px' : '14px';
|
let nodeSize = isTransfer ? '20px' : '14px';
|
||||||
let nodeBorder = isTransfer ? '4px' : '3px';
|
let nodeBorder = isTransfer ? '4px' : '3px';
|
||||||
let badgeHtml = '';
|
let badgeHtml = '';
|
||||||
let nodeZIndex = 4;
|
|
||||||
|
|
||||||
if (isSelectedFrom) {
|
if (isSelectedFrom) {
|
||||||
nodeColor = 'var(--success, #16a34a)';
|
nodeColor = 'var(--success, #16a34a)';
|
||||||
nodeSize = '24px';
|
nodeSize = '24px';
|
||||||
nodeZIndex = 5;
|
|
||||||
badgeHtml = `<div style="position: absolute; top: -32px; background: var(--success, #16a34a); color: white; font-size: 11px; padding: 2px 6px; border-radius: 4px; font-weight: bold; white-space: nowrap; box-shadow: 0 2px 4px rgba(0,0,0,0.2); z-index: 10;">起点</div>`;
|
badgeHtml = `<div style="position: absolute; top: -32px; background: var(--success, #16a34a); color: white; font-size: 11px; padding: 2px 6px; border-radius: 4px; font-weight: bold; white-space: nowrap; box-shadow: 0 2px 4px rgba(0,0,0,0.2); z-index: 10;">起点</div>`;
|
||||||
} else if (isSelectedTo) {
|
} else if (isSelectedTo) {
|
||||||
nodeColor = 'var(--danger, #dc2626)';
|
nodeColor = 'var(--danger, #dc2626)';
|
||||||
nodeSize = '24px';
|
nodeSize = '24px';
|
||||||
nodeZIndex = 5;
|
|
||||||
badgeHtml = `<div style="position: absolute; top: -32px; background: var(--danger, #dc2626); color: white; font-size: 11px; padding: 2px 6px; border-radius: 4px; font-weight: bold; white-space: nowrap; box-shadow: 0 2px 4px rgba(0,0,0,0.2); z-index: 10;">终点</div>`;
|
badgeHtml = `<div style="position: absolute; top: -32px; background: var(--danger, #dc2626); color: white; font-size: 11px; padding: 2px 6px; border-radius: 4px; font-weight: bold; white-space: nowrap; box-shadow: 0 2px 4px rgba(0,0,0,0.2); z-index: 10;">终点</div>`;
|
||||||
} else if (inRoute) {
|
} else if (inRoute) {
|
||||||
nodeColor = 'var(--primary, #facc15)';
|
nodeColor = 'var(--primary, #facc15)';
|
||||||
nodeZIndex = 5;
|
|
||||||
if (!isTransfer) nodeSize = '18px';
|
if (!isTransfer) nodeSize = '18px';
|
||||||
else nodeSize = '24px';
|
else nodeSize = '24px';
|
||||||
}
|
}
|
||||||
|
|
||||||
let transferDotsHtml = '';
|
let transferDotsHtml = '';
|
||||||
if (isTransfer) {
|
if (isTransfer) {
|
||||||
transferDotsHtml = `<div style="display: flex; gap: 3px; position: absolute; top: -14px; z-index: 6;">`;
|
transferDotsHtml = `<div style="display: flex; gap: 3px; position: absolute; top: -14px;">`;
|
||||||
transferLines.slice(0, 3).forEach(l => {
|
transferLines.slice(0, 3).forEach(l => {
|
||||||
transferDotsHtml += `<div title="可换乘 ${l.name}" style="width: 8px; height: 8px; border-radius: 50%; background: ${l.color || '#999'}; border: 1px solid #fff; box-shadow: 0 1px 2px rgba(0,0,0,0.2);"></div>`;
|
transferDotsHtml += `<div title="可换乘 ${l.name}" style="width: 8px; height: 8px; border-radius: 50%; background: ${l.color || '#999'}; border: 1px solid #fff; box-shadow: 0 1px 2px rgba(0,0,0,0.2);"></div>`;
|
||||||
});
|
});
|
||||||
@@ -240,14 +259,14 @@
|
|||||||
stationsHtml += `
|
stationsHtml += `
|
||||||
<div class="tvm-station-node" onclick="window.handleStationClick('${code}')" style="
|
<div class="tvm-station-node" onclick="window.handleStationClick('${code}')" style="
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: ${nodeZIndex};
|
z-index: 2;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 70px;
|
width: ${nodeSlotWidth}px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
">
|
">
|
||||||
|
|
||||||
<!-- Fixed height container for vertical alignment of the track nodes -->
|
<!-- Fixed height container for vertical alignment of the track nodes -->
|
||||||
<div style="height: 36px; display: flex; align-items: center; justify-content: center; position: relative; width: 100%;">
|
<div style="height: 36px; display: flex; align-items: center; justify-content: center; position: relative; width: 100%;">
|
||||||
@@ -268,7 +287,7 @@
|
|||||||
<!-- Labels -->
|
<!-- Labels -->
|
||||||
<div style="text-align: center; margin-top: 8px; width: 100%; display: flex; flex-direction: column; align-items: center;">
|
<div style="text-align: center; margin-top: 8px; width: 100%; display: flex; flex-direction: column; align-items: center;">
|
||||||
<div style="font-size: 10px; color: var(--text-muted, #64748b); font-weight: 700; margin-bottom: 2px;">${code}</div>
|
<div style="font-size: 10px; color: var(--text-muted, #64748b); font-weight: 700; margin-bottom: 2px;">${code}</div>
|
||||||
<div style="font-size: 14px; font-weight: 800; color: var(--text-color, #0f172a); line-height: 1.2; word-break: break-all; max-width: 60px;">${name}</div>
|
<div style="font-size: ${window.innerWidth <= 768 ? '13px' : '14px'}; font-weight: 800; color: var(--text-color, #0f172a); line-height: 1.2; word-break: break-all; max-width: ${Math.max(nodeSlotWidth - 10, 46)}px;">${name}</div>
|
||||||
${enName ? `<div style="font-size: 9px; color: var(--text-muted, #94a3b8); font-weight: 600; margin-top: 2px; transform: scale(0.9);">${enName}</div>` : ''}
|
${enName ? `<div style="font-size: 9px; color: var(--text-muted, #94a3b8); font-weight: 600; margin-top: 2px; transform: scale(0.9);">${enName}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -441,6 +460,11 @@
|
|||||||
// Load Map on Start
|
// Load Map on Start
|
||||||
renderSystemMap();
|
renderSystemMap();
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
if (!allLinesData || allLinesData.length === 0) return;
|
||||||
|
renderTvmUI();
|
||||||
|
});
|
||||||
|
|
||||||
async function previewPrice(force = false){
|
async function previewPrice(force = false){
|
||||||
const seq = ++previewSeq;
|
const seq = ++previewSeq;
|
||||||
const type = document.querySelector('input[name="trainType"]:checked').value;
|
const type = document.querySelector('input[name="trainType"]:checked').value;
|
||||||
|
|||||||
Reference in New Issue
Block a user