refactor(web): 将换乘站合并为单点并重绘订票线路图
This commit is contained in:
@@ -16,8 +16,8 @@
|
|||||||
"mtimeMs": 1783602619602.658
|
"mtimeMs": 1783602619602.658
|
||||||
},
|
},
|
||||||
"ticket-order.js": {
|
"ticket-order.js": {
|
||||||
"size": 34258,
|
"size": 33737,
|
||||||
"mtimeMs": 1783784148462.1907
|
"mtimeMs": 1783816412239.149
|
||||||
},
|
},
|
||||||
"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": 22,
|
"ticket-order.js": 23,
|
||||||
"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=22"></script>
|
<script src="/ticket-order.js?v=23"></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', () => {
|
||||||
|
|||||||
+38
-60
@@ -236,20 +236,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const primaryLineByStation = {};
|
const hubYByCanonical = {};
|
||||||
for (const [c, arr] of linesByStation.entries()) {
|
for (const canonical of Object.keys(lineIdxsByCanonical)) {
|
||||||
const uniq = Array.from(new Set(arr)).sort((a,b)=>a-b);
|
const idxs = Array.from(lineIdxsByCanonical[canonical] || []).sort((a, b) => a - b);
|
||||||
if (uniq.length === 0) continue;
|
if (!idxs.length) continue;
|
||||||
primaryLineByStation[c] = uniq[0];
|
const ys = idxs.map(idx => baseY + idx * lineGapY);
|
||||||
|
hubYByCanonical[canonical] = ys.reduce((sum, y) => sum + y, 0) / ys.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
stationYByCanonical = {};
|
stationYByCanonical = {};
|
||||||
for (const li of lineStops) {
|
for (const li of lineStops) {
|
||||||
const yNode = baseY + li.idx * lineGapY;
|
const yNode = baseY + li.idx * lineGapY;
|
||||||
for (const c of li.stops) {
|
for (const c of li.stops) {
|
||||||
if (stationYByCanonical[c] == null) {
|
const canonical = stationCanonicalByCode[c] || c;
|
||||||
stationYByCanonical[c] = yNode;
|
stationYByCanonical[c] = hubYByCanonical[canonical] ?? yNode;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +270,7 @@
|
|||||||
const canonical = stationCanonicalByCode[code] || code;
|
const canonical = stationCanonicalByCode[code] || code;
|
||||||
return {
|
return {
|
||||||
x: stationXByCanonical[canonical] || 0,
|
x: stationXByCanonical[canonical] || 0,
|
||||||
y: yLine
|
y: stationYByCanonical[code] ?? yLine
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -282,68 +282,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canonicalYs = {};
|
const sortedStations = Object.keys(stationXByCanonical).sort((a, b) => stationXByCanonical[a] - stationXByCanonical[b]);
|
||||||
for (const li of lineStops) {
|
for (let i = 0; i < sortedStations.length; i++) {
|
||||||
const yNode = baseY + li.idx * lineGapY;
|
const canonical = sortedStations[i];
|
||||||
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 codes = stationCodesByCanonical[canonical] || [canonical];
|
||||||
const ys = nodes.map(n => n.y);
|
const lineIdxs = Array.from(lineIdxsByCanonical[canonical] || []).sort((a, b) => a - b);
|
||||||
const minY = Math.min(...ys);
|
const colors = lineIdxs.map(idx => lineColors[idx]).filter(Boolean);
|
||||||
const maxY = Math.max(...ys);
|
const uniqueColors = Array.from(new Set(colors));
|
||||||
const isTransfer = nodes.length >= 2 || stationCodesByCanonical[canonical]?.length >= 2;
|
const primaryCode = codes[0];
|
||||||
|
const y = hubYByCanonical[canonical] ?? stationYByCanonical[primaryCode] ?? baseY;
|
||||||
|
const isTransfer = uniqueColors.length >= 2 || codes.length >= 2;
|
||||||
|
const name = getStationDisplayName(primaryCode);
|
||||||
|
|
||||||
// Vertical pill
|
let markerSvg = '';
|
||||||
if (maxY > minY) {
|
if (isTransfer) {
|
||||||
svgContent += `<rect x="${x - 6}" y="${minY + 12}" width="12" height="${Math.max(0, maxY - minY - 24)}" rx="6" fill="#f8fafc" stroke="#94a3b8" stroke-width="2" />`;
|
markerSvg += `<circle cx="${x}" cy="${y}" r="15" fill="#ffffff" stroke="#1f2937" stroke-width="2.5" />`;
|
||||||
|
markerSvg += pieSvg(x, y, 11.5, uniqueColors);
|
||||||
|
markerSvg += `<circle class="node-core" cx="${x}" cy="${y}" r="6.5" fill="#ffffff" stroke="#334155" stroke-width="2.25" />`;
|
||||||
|
} else {
|
||||||
|
const color = uniqueColors[0] || '#93a2b7';
|
||||||
|
markerSvg += `<circle cx="${x}" cy="${y}" r="12.5" fill="#ffffff" stroke="${color}" stroke-width="5" />`;
|
||||||
|
markerSvg += `<circle class="node-core" cx="${x}" cy="${y}" r="7" fill="#ffffff" stroke="#334155" stroke-width="2.25" />`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const primaryCode = nodes[0].code;
|
const preferTop = isTransfer || (i % 2 === 0 && y > baseY + 20);
|
||||||
const name = stationNameByCode[primaryCode] || primaryCode;
|
const labelY = preferTop ? (y - 22) : (y + 30);
|
||||||
|
const labelClass = preferTop ? 'station-label-top' : 'station-label-bottom';
|
||||||
// Nodes
|
|
||||||
let nodesSvg = '';
|
|
||||||
for (const node of nodes) {
|
|
||||||
const rOuter = isTransfer ? 13 : 12;
|
|
||||||
const outer = `<circle cx="${x}" cy="${node.y}" r="${rOuter}" fill="#ffffff" stroke="${node.color}" stroke-width="5" />`;
|
|
||||||
const transferFill = isTransfer ? `<circle cx="${x}" cy="${node.y}" r="7" fill="#ffffff" stroke="#475569" stroke-width="2.5" />` : '';
|
|
||||||
nodesSvg += `
|
|
||||||
${outer}
|
|
||||||
${transferFill}
|
|
||||||
<circle class="node-core" cx="${x}" cy="${node.y}" r="8" fill="#ffffff" stroke="#334155" stroke-width="2.5" />
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Label
|
|
||||||
let labelSvg = '';
|
let labelSvg = '';
|
||||||
if (isTransfer) {
|
if (isTransfer) {
|
||||||
const labelY = minY - 34; // above the top node
|
const labelW = Math.max(72, name.length * 14 + 18);
|
||||||
const transferName = getStationDisplayName(canonical);
|
labelSvg += `<rect x="${x - labelW / 2}" y="${labelY - 16}" width="${labelW}" height="24" rx="12" fill="rgba(248,250,252,0.96)" stroke="#cbd5e1" stroke-width="1.5" />`;
|
||||||
const transferLabelW = Math.max(72, transferName.length * 14 + 18);
|
labelSvg += `<text class="${labelClass}" x="${x}" y="${labelY}" text-anchor="middle" fill="#334155" font-size="13" font-weight="800">${name}</text>`;
|
||||||
labelSvg += `<rect x="${x - transferLabelW / 2}" y="${labelY - 16}" width="${transferLabelW}" height="24" rx="12" fill="rgba(248,250,252,0.96)" stroke="#cbd5e1" stroke-width="1.5" />`;
|
|
||||||
labelSvg += `<text x="${x}" y="${labelY}" text-anchor="middle" fill="#334155" font-size="13" font-weight="800">${transferName}</text>`;
|
|
||||||
} else {
|
} else {
|
||||||
const labelY = (nodes[0].y === baseY || nodes[0].y <= baseY + lineGapY) ? maxY + 28 : maxY + 30;
|
labelSvg += `<text class="${labelClass}" x="${x}" y="${labelY}" text-anchor="middle" fill="#334155" font-size="13.5" font-weight="700">${name}</text>`;
|
||||||
labelSvg += `<text x="${x}" y="${labelY}" text-anchor="middle" fill="#334155" 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="map-station ${isTransfer ? 'transfer' : ''}" data-code="${primaryCode}" data-canonical="${canonical}" onclick="handleStationClick('${primaryCode}')" style="cursor:pointer">
|
<g class="map-station ${isTransfer ? 'transfer' : ''}" data-code="${primaryCode}" data-canonical="${canonical}" onclick="handleStationClick('${primaryCode}')" style="cursor:pointer">
|
||||||
<rect x="${x - 20}" y="${hitY - hitHeight/2}" width="40" height="${hitHeight}" fill="transparent" />
|
<rect x="${x - 20}" y="${y - 20}" width="40" height="40" fill="transparent" />
|
||||||
${nodesSvg}
|
${markerSvg}
|
||||||
${labelSvg}
|
${labelSvg}
|
||||||
</g>
|
</g>
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user