refactor & feat & fix: 重写SVG生成器,优化前端线路绘制与修复样式

重写服务器端SVG生成逻辑,将原铁路线路图生成功能替换为站点间票价矩阵生成。
优化前端换乘路径绘制:新增线路偏移与弯道处理,避免多条线路路径重叠。
修复站点标记的外层圆圈填充颜色,使其正确使用对应线路的配色。
This commit is contained in:
2026-07-16 03:49:45 +08:00
parent d32d72a3da
commit 7812d47f0c
2 changed files with 207 additions and 711 deletions
+31 -5
View File
@@ -63,10 +63,11 @@
return out;
};
const buildTransitPath = (points, radius = 12) => {
const buildTransitPath = (points, radius = 12, options = {}) => {
const src = Array.isArray(points) ? points.filter(Boolean) : [];
if (src.length === 0) return '';
if (src.length === 1) return `M ${src[0].x} ${src[0].y}`;
const laneOffset = Number(options?.laneOffset) || 0;
const fmt = (n) => {
const v = Number(n);
@@ -86,6 +87,23 @@
const code = String(p?.code || '').trim();
return !!code && stationTransfer && stationTransfer.has(code);
};
const resolveLaneBendX = (from, to) => {
if (!laneOffset) return null;
const fromTransfer = isTransfer(from);
const toTransfer = isTransfer(to);
if (!fromTransfer && !toTransfer) return null;
const minX = Math.min(from.x, to.x);
const maxX = Math.max(from.x, to.x);
if ((maxX - minX) < 28) return null;
const anchorX = toTransfer && !fromTransfer
? to.x
: (fromTransfer && !toTransfer ? from.x : ((from.x + to.x) / 2));
const candidate = Math.max(minX + 12, Math.min(maxX - 12, anchorX + laneOffset));
if (Math.abs(candidate - from.x) < 4 || Math.abs(candidate - to.x) < 4) return null;
return candidate;
};
const simplify = (arr) => {
const out = [];
for (const p of arr) push(out, p);
@@ -121,7 +139,11 @@
const aT = isTransfer(prev);
const bT = isTransfer(next);
if (bT && !aT) {
const bendX = resolveLaneBendX(prev, next);
if (Number.isFinite(bendX)) {
push(orth, { x: bendX, y: prev.y, code: prev?.code || next?.code });
push(orth, { x: bendX, y: y2, code: next?.code || prev?.code });
} else if (bT && !aT) {
push(orth, { x: x2, y: prev.y, code: next?.code });
} else if (aT && !bT) {
push(orth, { x: prev.x, y: y2, code: prev?.code });
@@ -366,6 +388,10 @@
};
const labelPlacements = {};
const getLaneOffset = (lineIdx) => {
const center = (Math.max(totalLines, 1) - 1) / 2;
return Math.max(-18, Math.min(18, Math.round((lineIdx - center) * 4)));
};
const placeLabel = (x, y, isStart, name, enName, color) => {
const key = `${x},${y},${isStart ? 'left' : 'right'}`;
const count = labelPlacements[key] || 0;
@@ -411,7 +437,7 @@
};
});
const d = buildTransitPath(pts, 16);
const d = buildTransitPath(pts, 16, { laneOffset: getLaneOffset(li.idx) });
svgContent += `<path d="${d}" fill="none" stroke="${li.color}" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" />`;
// Add line name labels at ends
@@ -446,8 +472,8 @@
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" />`;
markerSvg += `<circle cx="${x}" cy="${y}" r="12.5" fill="${color}" stroke="${color}" stroke-width="4" />`;
markerSvg += `<circle class="node-core" cx="${x}" cy="${y}" r="7" fill="#ffffff" stroke="${color}" stroke-width="2.25" />`;
}
const preferTop = isTransfer || (i % 2 === 0 && y >= baseY + 20);