fix(web): 重构线路图渲染算法,实现平行水平走线与胶囊式垂直换乘
This commit is contained in:
@@ -16,8 +16,8 @@
|
|||||||
"mtimeMs": 1783602619602.658
|
"mtimeMs": 1783602619602.658
|
||||||
},
|
},
|
||||||
"ticket-order.js": {
|
"ticket-order.js": {
|
||||||
"size": 33248,
|
"size": 32045,
|
||||||
"mtimeMs": 1783780167713.1963
|
"mtimeMs": 1783782910999.121
|
||||||
},
|
},
|
||||||
"ticket-search.js": {
|
"ticket-search.js": {
|
||||||
"size": 17269,
|
"size": 17269,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"blog.css": 2,
|
"blog.css": 2,
|
||||||
"custom-dialog.js": 11,
|
"custom-dialog.js": 11,
|
||||||
"public-status.js": 14,
|
"public-status.js": 14,
|
||||||
"ticket-order.js": 19,
|
"ticket-order.js": 20,
|
||||||
"ticket-search.js": 12,
|
"ticket-search.js": 12,
|
||||||
"ticket-route.js": 3,
|
"ticket-route.js": 3,
|
||||||
"index.js": 5,
|
"index.js": 5,
|
||||||
|
|||||||
@@ -158,7 +158,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/custom-dialog.js?v=11"></script>
|
<script src="/custom-dialog.js?v=11"></script>
|
||||||
<script src="/ticket-order.js?v=19"></script>
|
<script src="/ticket-order.js?v=20"></script>
|
||||||
<script src="/public-status.js?v=14"></script>
|
<script src="/public-status.js?v=14"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|||||||
+59
-72
@@ -221,39 +221,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
stationYByCanonical = {};
|
stationYByCanonical = {};
|
||||||
const ysByCanonical = {};
|
for (const li of lineStops) {
|
||||||
for (const c of Object.keys(primaryLineByStation)) {
|
const yNode = baseY + li.idx * lineGapY;
|
||||||
const li = primaryLineByStation[c];
|
for (const c of li.stops) {
|
||||||
if (li == null) continue;
|
if (stationYByCanonical[c] == null) {
|
||||||
const canonical = stationCanonicalByCode[c] || c;
|
stationYByCanonical[c] = yNode;
|
||||||
if (!ysByCanonical[canonical]) ysByCanonical[canonical] = [];
|
}
|
||||||
ysByCanonical[canonical].push(baseY + li * lineGapY);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const canonicalY = {};
|
|
||||||
for (const canonical of Object.keys(ysByCanonical)) {
|
|
||||||
const ys = ysByCanonical[canonical];
|
|
||||||
canonicalY[canonical] = ys.reduce((a,b) => a+b, 0) / ys.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const c of Object.keys(primaryLineByStation)) {
|
|
||||||
const canonical = stationCanonicalByCode[c] || c;
|
|
||||||
stationYByCanonical[c] = canonicalY[canonical];
|
|
||||||
}
|
|
||||||
|
|
||||||
const allStations = Object.keys(primaryLineByStation);
|
|
||||||
if (allStations.length === 0) {
|
|
||||||
mapContainer.innerHTML = '<div class="empty-state jr-empty-state"><p>线路数据为空,请稍后再试。</p></div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const labelShownForCanonical = new Set();
|
|
||||||
const transferColorsByStation = {};
|
|
||||||
const primaryColorByStation = {};
|
|
||||||
for (const c of allStations) {
|
|
||||||
const lineIdxs = Array.from(new Set(linesByStation.get(c) || [])).sort((a,b)=>a-b);
|
|
||||||
const colors = lineIdxs.map(i => lineColors[i]).filter(Boolean);
|
|
||||||
transferColorsByStation[c] = Array.from(new Set(colors));
|
|
||||||
primaryColorByStation[c] = colors[0] || '#93a2b7';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let svgContent = '';
|
let svgContent = '';
|
||||||
@@ -273,7 +247,7 @@
|
|||||||
const canonical = stationCanonicalByCode[code] || code;
|
const canonical = stationCanonicalByCode[code] || code;
|
||||||
return {
|
return {
|
||||||
x: stationXByCanonical[canonical] || 0,
|
x: stationXByCanonical[canonical] || 0,
|
||||||
y: stationYByCanonical[code] ?? yLine
|
y: yLine
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -285,54 +259,67 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const canonical of Object.keys(stationCodesByCanonical)) {
|
const canonicalYs = {};
|
||||||
const codes = (stationCodesByCanonical[canonical] || []).filter(code => Number.isFinite(stationYByCanonical[code]));
|
for (const li of lineStops) {
|
||||||
if (codes.length < 2) continue;
|
const yNode = baseY + li.idx * lineGapY;
|
||||||
|
for (const c of li.stops) {
|
||||||
|
const canonical = stationCanonicalByCode[c] || c;
|
||||||
|
if (!canonicalYs[canonical]) canonicalYs[canonical] = [];
|
||||||
|
// Only add if we haven't added this Y for this canonical yet
|
||||||
|
if (!canonicalYs[canonical].some(n => n.y === yNode)) {
|
||||||
|
canonicalYs[canonical].push({ y: yNode, color: li.color, code: c });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const canonical of Object.keys(canonicalYs)) {
|
||||||
const x = stationXByCanonical[canonical];
|
const x = stationXByCanonical[canonical];
|
||||||
|
const nodes = canonicalYs[canonical];
|
||||||
const ys = codes.map(c => stationYByCanonical[c]);
|
const ys = nodes.map(n => n.y);
|
||||||
const minY = Math.min(...ys);
|
const minY = Math.min(...ys);
|
||||||
const maxY = Math.max(...ys);
|
const maxY = Math.max(...ys);
|
||||||
|
const isTransfer = nodes.length >= 2 || stationCodesByCanonical[canonical]?.length >= 2;
|
||||||
|
|
||||||
// Draw vertical pill connecting the transfer stations
|
// Vertical pill
|
||||||
if (maxY > minY) {
|
if (maxY > minY) {
|
||||||
svgContent += `<rect x="${x - 8}" y="${minY}" width="16" height="${maxY - minY}" rx="8" fill="#ffffff" stroke="#111827" stroke-width="3" />`;
|
svgContent += `<rect x="${x - 8}" y="${minY}" width="16" height="${maxY - minY}" rx="8" fill="#ffffff" stroke="#111827" stroke-width="3" />`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const y = minY;
|
const primaryCode = nodes[0].code;
|
||||||
const labelY = y - 34;
|
const name = stationNameByCode[primaryCode] || primaryCode;
|
||||||
// Remove the standalone circle for the transfer hub, just rely on the nodes themselves and the vertical pill
|
|
||||||
// svgContent += `<circle cx="${x}" cy="${y}" r="7" fill="#ffffff" stroke="#111827" stroke-width="3" />`;
|
|
||||||
svgContent += `<rect x="${x - 42}" y="${labelY - 16}" width="84" height="24" rx="12" fill="rgba(241,245,249,0.92)" stroke="#cbd5e1" stroke-width="1.5" />`;
|
|
||||||
svgContent += `<text x="${x}" y="${labelY}" text-anchor="middle" fill="#334155" font-size="13" font-weight="800">${getStationDisplayName(canonical)}</text>`;
|
|
||||||
labelShownForCanonical.add(canonical);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const c of allStations) {
|
// Nodes
|
||||||
const canonical = stationCanonicalByCode[c] || c;
|
let nodesSvg = '';
|
||||||
const x = stationXByCanonical[canonical];
|
for (const node of nodes) {
|
||||||
const yNode = stationYByCanonical[c] ?? baseY;
|
const rOuter = isTransfer ? 13 : 12;
|
||||||
const name = stationNameByCode[c] || c;
|
const outer = `<circle cx="${x}" cy="${node.y}" r="${rOuter}" fill="#ffffff" stroke="${node.color}" stroke-width="5" />`;
|
||||||
const isTransfer = stationTransfer.has(c);
|
const transferFill = isTransfer ? `<circle cx="${x}" cy="${node.y}" r="7" fill="#ffffff" stroke="#111827" stroke-width="3" />` : '';
|
||||||
const cls = isTransfer ? 'map-station transfer' : 'map-station';
|
nodesSvg += `
|
||||||
const ringSvg = '';
|
${outer}
|
||||||
const primaryColor = primaryColorByStation[c] || '#93a2b7';
|
${transferFill}
|
||||||
const outer = isTransfer
|
<circle class="node-core" cx="${x}" cy="${node.y}" r="8" fill="#ffffff" stroke="#111827" stroke-width="3" />
|
||||||
? `<circle cx="${x}" cy="${yNode}" r="13" fill="#ffffff" stroke="${primaryColor}" stroke-width="5" />`
|
`;
|
||||||
: `<circle cx="${x}" cy="${yNode}" r="12" fill="#ffffff" stroke="${primaryColor}" stroke-width="5" />`;
|
}
|
||||||
const transferFill = isTransfer ? `<circle cx="${x}" cy="${yNode}" r="7" fill="#ffffff" stroke="#111827" stroke-width="3" />` : '';
|
|
||||||
const coreFill = isTransfer ? '#ffffff' : '#ffffff';
|
// Label
|
||||||
const showLabel = !isTransfer || !labelShownForCanonical.has(canonical);
|
let labelSvg = '';
|
||||||
const textSvg = showLabel
|
if (isTransfer) {
|
||||||
? `<text x="${x}" y="${yNode+28}" text-anchor="middle" fill="#e5e7eb" font-size="14" font-weight="700">${name}</text>`
|
const labelY = minY - 34; // above the top node
|
||||||
: '';
|
labelSvg += `<rect x="${x - 42}" y="${labelY - 16}" width="84" height="24" rx="12" fill="rgba(241,245,249,0.92)" stroke="#cbd5e1" stroke-width="1.5" />`;
|
||||||
|
labelSvg += `<text x="${x}" y="${labelY}" text-anchor="middle" fill="#334155" font-size="13" font-weight="800">${getStationDisplayName(canonical)}</text>`;
|
||||||
|
} else {
|
||||||
|
const labelY = maxY + 28; // below the single node
|
||||||
|
labelSvg += `<text x="${x}" y="${labelY}" text-anchor="middle" fill="#e5e7eb" font-size="14" font-weight="700">${name}</text>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hitY = (minY + maxY) / 2;
|
||||||
|
const hitHeight = Math.max(30, maxY - minY + 30);
|
||||||
|
|
||||||
svgContent += `
|
svgContent += `
|
||||||
<g class="${cls}" data-code="${c}" onclick="handleStationClick('${c}')" style="cursor:pointer">
|
<g class="map-station ${isTransfer ? 'transfer' : ''}" data-code="${primaryCode}" onclick="handleStationClick('${primaryCode}')" style="cursor:pointer">
|
||||||
${ringSvg}
|
<rect x="${x - 20}" y="${hitY - hitHeight/2}" width="40" height="${hitHeight}" fill="transparent" />
|
||||||
${outer}
|
${nodesSvg}
|
||||||
${transferFill}
|
${labelSvg}
|
||||||
<circle class="node-core" cx="${x}" cy="${yNode}" r="8" fill="${coreFill}" stroke="#111827" stroke-width="3" />
|
|
||||||
${textSvg}
|
|
||||||
</g>
|
</g>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user