From d32d72a3da4c67941b8c54fa3efc21d7acbc1b05 Mon Sep 17 00:00:00 2001
From: HenryDu8133 <813367384@qq.com>
Date: Thu, 16 Jul 2026 00:26:18 +0800
Subject: [PATCH] =?UTF-8?q?feat(web):=20=E4=BC=98=E5=8C=96=E5=8C=BA?=
=?UTF-8?q?=E9=97=B4=E9=80=89=E6=8B=A9=E7=95=8C=E9=9D=A2=E7=BA=BF=E8=B7=AF?=
=?UTF-8?q?=E5=9B=BE=E5=B8=83=E5=B1=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. 实现正交布线与圆角拐角连接,减少斜线交叉
2. 动态调整线路间距与节点位置,提高整体紧凑性
3. 优化线路名称与换乘站标签的避让逻辑,避免遮挡
4. 删除不再使用的全局预览页(map-preview.html)
---
.trae/rules/git.md | 2 +-
web/map-preview.html | 63 --------------------
web/ticket-order.js | 139 ++++++++++++++++++++++++++++++++++++++-----
3 files changed, 124 insertions(+), 80 deletions(-)
delete mode 100644 web/map-preview.html
diff --git a/.trae/rules/git.md b/.trae/rules/git.md
index a379908..246112c 100644
--- a/.trae/rules/git.md
+++ b/.trae/rules/git.md
@@ -1,2 +1,2 @@
1. 完成功能制作后自行编辑git commit并推送
-2.
\ No newline at end of file
+2. 提交时请添加功能描述
\ No newline at end of file
diff --git a/web/map-preview.html b/web/map-preview.html
deleted file mode 100644
index 0f5c8c0..0000000
--- a/web/map-preview.html
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
- 全网线路图预览 - FSE铁路运输票务系统
-
-
-
-
-
-
-
-
-
diff --git a/web/ticket-order.js b/web/ticket-order.js
index 90ab193..7ce8333 100644
--- a/web/ticket-order.js
+++ b/web/ticket-order.js
@@ -1,3 +1,11 @@
+/*
+ * @Author: HenryDu8133 813367384@qq.com
+ * @Date: 2025-12-06 23:59:11
+ * @LastEditors: HenryDu8133 813367384@qq.com
+ * @LastEditTime: 2026-07-15 23:25:36
+ * @FilePath: \TicketMachine\web\ticket-order.js
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
(() => {
const $ = (sel) => document.querySelector(sel);
const fromEl = $('#from');
@@ -56,13 +64,101 @@
};
const buildTransitPath = (points, radius = 12) => {
- if (!points || points.length === 0) return '';
- if (points.length === 1) return `M ${points[0].x} ${points[0].y}`;
+ 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}`;
- let d = `M ${points[0].x} ${points[0].y}`;
- for (let i = 1; i < points.length; i++) {
- d += ` L ${points[i].x} ${points[i].y}`;
+ const fmt = (n) => {
+ const v = Number(n);
+ if (!Number.isFinite(v)) return '0';
+ return String(Math.round(v * 10) / 10);
+ };
+ const eq = (a, b) => Math.abs(a.x - b.x) < 0.01 && Math.abs(a.y - b.y) < 0.01;
+ const push = (arr, p) => {
+ const x = Number(p?.x);
+ const y = Number(p?.y);
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return;
+ const item = { x, y, code: p?.code ? String(p.code).trim() : '' };
+ if (arr.length && eq(arr[arr.length - 1], item)) return;
+ arr.push(item);
+ };
+ const isTransfer = (p) => {
+ const code = String(p?.code || '').trim();
+ return !!code && stationTransfer && stationTransfer.has(code);
+ };
+ const simplify = (arr) => {
+ const out = [];
+ for (const p of arr) push(out, p);
+ let changed = true;
+ while (changed) {
+ changed = false;
+ for (let i = 1; i < out.length - 1; i++) {
+ const a = out[i - 1];
+ const b = out[i];
+ const c = out[i + 1];
+ if ((a.x === b.x && b.x === c.x) || (a.y === b.y && b.y === c.y)) {
+ out.splice(i, 1);
+ changed = true;
+ break;
+ }
+ }
+ }
+ return out;
+ };
+ const orth = [];
+ push(orth, src[0]);
+ for (let i = 1; i < src.length; i++) {
+ const prev = orth[orth.length - 1];
+ const next = src[i];
+ const x2 = Number(next?.x);
+ const y2 = Number(next?.y);
+ if (!Number.isFinite(x2) || !Number.isFinite(y2)) continue;
+
+ if (prev.x === x2 || prev.y === y2) {
+ push(orth, next);
+ continue;
+ }
+
+ const aT = isTransfer(prev);
+ const bT = isTransfer(next);
+ 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 });
+ } else {
+ push(orth, { x: x2, y: prev.y, code: next?.code });
+ }
+ push(orth, next);
}
+
+ const pts = simplify(orth);
+ if (pts.length === 0) return '';
+ if (pts.length === 1) return `M ${fmt(pts[0].x)} ${fmt(pts[0].y)}`;
+ if (radius <= 0 || pts.length === 2) {
+ let d = `M ${fmt(pts[0].x)} ${fmt(pts[0].y)}`;
+ for (let i = 1; i < pts.length; i++) d += ` L ${fmt(pts[i].x)} ${fmt(pts[i].y)}`;
+ return d;
+ }
+
+ const rBase = Math.max(0, Number(radius) || 0);
+ let d = `M ${fmt(pts[0].x)} ${fmt(pts[0].y)}`;
+ for (let i = 1; i < pts.length - 1; i++) {
+ const a = pts[i - 1];
+ const b = pts[i];
+ const c = pts[i + 1];
+ const v1x = b.x - a.x;
+ const v1y = b.y - a.y;
+ const v2x = c.x - b.x;
+ const v2y = c.y - b.y;
+ const len1 = Math.hypot(v1x, v1y);
+ const len2 = Math.hypot(v2x, v2y);
+ if (!len1 || !len2) continue;
+ const r = Math.min(rBase, len1 / 2, len2 / 2);
+ const p1 = { x: b.x - (v1x / len1) * r, y: b.y - (v1y / len1) * r };
+ const p2 = { x: b.x + (v2x / len2) * r, y: b.y + (v2y / len2) * r };
+ d += ` L ${fmt(p1.x)} ${fmt(p1.y)} Q ${fmt(b.x)} ${fmt(b.y)} ${fmt(p2.x)} ${fmt(p2.y)}`;
+ }
+ d += ` L ${fmt(pts[pts.length - 1].x)} ${fmt(pts[pts.length - 1].y)}`;
return d;
};
@@ -194,8 +290,17 @@
const legendW = 10;
const startX = 140;
const baseY = 80;
- const lineGapY = 80;
- const minGapX = 80;
+ const totalLines = currentLineIdx;
+ const maxStops = Math.max(2, ...lineStops.map(li => Array.isArray(li.stops) ? li.stops.length : 2));
+ let lineGapY = 80;
+ if (totalLines >= 14) lineGapY = 66;
+ else if (totalLines >= 10) lineGapY = 72;
+ lineGapY = Math.max(60, Math.min(84, lineGapY));
+ let minGapX = 80;
+ if (maxStops >= 24) minGapX = 64;
+ else if (maxStops >= 18) minGapX = 72;
+ minGapX = Math.max(56, Math.min(96, minGapX));
+ const labelStackGap = Math.max(30, Math.round(lineGapY * 0.55));
const occurrences = {};
for (const li of lineStops) {
@@ -272,7 +377,7 @@
const hasEn = !!enName;
const nameLen = Math.max(name.length * 13 + 16, hasEn ? enName.length * 6.5 + 16 : 0);
const h = hasEn ? 36 : 24;
- const yOffset = count * 42 - (h / 2);
+ const yOffset = count * labelStackGap - (h / 2);
let lx, textX;
if (isStart) {
@@ -301,7 +406,8 @@
const canonical = stationCanonicalByCode[code] || code;
return {
x: stationXByCanonical[canonical] || 0,
- y: stationYByCanonical[code] ?? (baseY + li.idx * lineGapY)
+ y: stationYByCanonical[code] ?? (baseY + li.idx * lineGapY),
+ code
};
});
@@ -311,8 +417,9 @@
// Add line name labels at ends
const pStart = pts[0];
const pEnd = pts[pts.length - 1];
- svgContent += placeLabel(pStart.x, pStart.y, true, li.name, li.enName, li.color);
- svgContent += placeLabel(pEnd.x, pEnd.y, false, li.name, li.enName, li.color);
+ const yLabel = baseY + li.idx * lineGapY;
+ svgContent += placeLabel(pStart.x, yLabel, true, li.name, li.enName, li.color);
+ svgContent += placeLabel(pEnd.x, yLabel, false, li.name, li.enName, li.color);
}
}
@@ -355,10 +462,10 @@
const codeW = primaryCode.length * 6 + 10;
if (preferTop) {
- const bottomY = y - 14;
+ const bottomY = y - 22;
const enY = bottomY;
const nameY = showEn ? bottomY - 12 : bottomY;
- const codeY = nameY - 16;
+ const codeY = nameY - 18;
textContent += ``;
textContent += `${primaryCode}`;
@@ -369,9 +476,9 @@
textContent += `${enName}`;
}
} else {
- const topY = y + 24;
+ const topY = y + 34;
const codeY = topY;
- const nameY = topY + 16;
+ const nameY = topY + 18;
const enY = nameY + 12;
textContent += ``;
@@ -537,7 +644,7 @@
const x = stationXByCanonical[canonical];
const y = stationYByCanonical[normalized];
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
- return { x, y };
+ return { x, y, code: normalized };
}
function getMapStationEl(code) {