feat(web,server): 修复模板语法错误并新增多项体验优化
修复多处JS文件中的模板字符串转义错误,将错误的\\`替换为标准反引号 为IC卡搜索页面新增中文事件类型匹配,支持充值、进站、出站场景 重构票务订单页面的线路绘制逻辑,添加站点交替波浪偏移并使用贝塞尔曲线优化线路显示 优化SVG票价表格渲染,新增不可达站点的红色叉号提示,重构代码提升可读性
This commit is contained in:
Binary file not shown.
@@ -160,36 +160,42 @@ function generate(stations, lines, fares, nameByCode, transfers) {
|
||||
if (i === j) {
|
||||
svg += `<text x="${x0 + cellW / 2}" y="${y0 + 22}" font-size="12" fill="#6b7280" text-anchor="middle">-</text>`;
|
||||
} else {
|
||||
const val = Math.round(Number(distAll[code][codes[j]] || 0));
|
||||
const intensity = maxFare > 0 ? Math.min(1, val / maxFare) : 0;
|
||||
|
||||
// Get route colors
|
||||
const routeColors = colorsForRoute(code, codes[j]);
|
||||
|
||||
// Draw background strips for multiple lines
|
||||
if (routeColors.length > 0) {
|
||||
const stripW = cellW / routeColors.length;
|
||||
for (let k = 0; k < routeColors.length; k++) {
|
||||
const c = routeColors[k] || '#93a2b7';
|
||||
const stripX = x0 + k * stripW;
|
||||
// Use fill-opacity for robustness against any color format
|
||||
svg += `<rect x="${stripX}" y="${y0}" width="${stripW}" height="${cellH}" fill="${escapeHTML(c)}" fill-opacity="0.15" stroke="none"/>`;
|
||||
}
|
||||
const distVal = distAll[code][codes[j]];
|
||||
if (distVal === Infinity || !Number.isFinite(distVal)) {
|
||||
// Unreachable station
|
||||
svg += `<text x="${x0 + cellW / 2}" y="${y0 + 22}" font-size="14" font-weight="800" fill="#ef4444" text-anchor="middle">×</text>`;
|
||||
} else {
|
||||
svg += `<rect x="${x0}" y="${y0}" width="${cellW}" height="${cellH}" fill="#000000" fill-opacity="0.03" stroke="none"/>`;
|
||||
}
|
||||
|
||||
// Add cell border
|
||||
svg += `<rect x="${x0}" y="${y0}" width="${cellW}" height="${cellH}" fill="none" stroke="#e5e7eb" stroke-width="0.5"/>`;
|
||||
|
||||
svg += `<text x="${x0 + cellW / 2}" y="${y0 + 22}" font-size="12" font-weight="800" fill="#000000" text-anchor="middle">${escapeHTML(String(val))}</text>`;
|
||||
|
||||
// Show small color indicators at top left
|
||||
const dots = routeColors.slice(0, 4);
|
||||
for (let k = 0; k < dots.length; k++) {
|
||||
const cx = x0 + 8 + k * 10; const cy = y0 + 10;
|
||||
const dotColor = dots[k] || '#93a2b7';
|
||||
svg += `<circle cx="${cx}" cy="${cy}" r="3.5" fill="${escapeHTML(dotColor)}" stroke="white" stroke-width="1"/>`;
|
||||
const val = Math.round(Number(distVal || 0));
|
||||
const intensity = maxFare > 0 ? Math.min(1, val / maxFare) : 0;
|
||||
|
||||
// Get route colors
|
||||
const routeColors = colorsForRoute(code, codes[j]);
|
||||
|
||||
// Draw background strips for multiple lines
|
||||
if (routeColors.length > 0) {
|
||||
const stripW = cellW / routeColors.length;
|
||||
for (let k = 0; k < routeColors.length; k++) {
|
||||
const c = routeColors[k] || '#93a2b7';
|
||||
const stripX = x0 + k * stripW;
|
||||
// Use fill-opacity for robustness against any color format
|
||||
svg += `<rect x="${stripX}" y="${y0}" width="${stripW}" height="${cellH}" fill="${escapeHTML(c)}" fill-opacity="0.15" stroke="none"/>`;
|
||||
}
|
||||
} else {
|
||||
svg += `<rect x="${x0}" y="${y0}" width="${cellW}" height="${cellH}" fill="#000000" fill-opacity="0.03" stroke="none"/>`;
|
||||
}
|
||||
|
||||
// Add cell border
|
||||
svg += `<rect x="${x0}" y="${y0}" width="${cellW}" height="${cellH}" fill="none" stroke="#e5e7eb" stroke-width="0.5"/>`;
|
||||
|
||||
svg += `<text x="${x0 + cellW / 2}" y="${y0 + 22}" font-size="12" font-weight="800" fill="#000000" text-anchor="middle">${escapeHTML(String(val))}</text>`;
|
||||
|
||||
// Show small color indicators at top left
|
||||
const dots = routeColors.slice(0, 4);
|
||||
for (let k = 0; k < dots.length; k++) {
|
||||
const cx = x0 + 8 + k * 10; const cy = y0 + 10;
|
||||
const dotColor = dots[k] || '#93a2b7';
|
||||
svg += `<circle cx="${cx}" cy="${cy}" r="3.5" fill="${escapeHTML(dotColor)}" stroke="white" stroke-width="1"/>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,25 +96,25 @@
|
||||
|
||||
if (type === 'create' || type === 'order_created' || type === 'activated') {
|
||||
const cardType = ev?.detail?.card_type || ev?.card_type || ev?.["类型"] || '储值卡';
|
||||
return \`[${cardType}];余额: \${balance};时间: \${timeStr}\`;
|
||||
return `[${cardType}];余额: ${balance};时间: ${timeStr}`;
|
||||
}
|
||||
if (type === 'topup' || type === 'refill' || action === 'topup') {
|
||||
if (type === 'topup' || type === 'refill' || action === 'topup' || type === '充值') {
|
||||
const amount = ev?.amount ?? ev?.["充值数额"] ?? 0;
|
||||
return \`机器ID: \${deviceId};充值数额: \${amount};余额: \${balance};时间: \${timeStr}\`;
|
||||
return `机器ID: ${deviceId};充值数额: ${amount};余额: ${balance};时间: ${timeStr}`;
|
||||
}
|
||||
if (action === 'entry' || type === 'entry') {
|
||||
if (action === 'entry' || type === 'entry' || type === '进站') {
|
||||
const code = ev?.station_code || ev?.["站点编号"] || '---';
|
||||
const name = ev?.station_name || ev?.["站点中文"] || ev?.["发生站"] || '---';
|
||||
return \`机器ID: \${deviceId};站点编号: \${code};站点中文: \${name};余额: \${balance};时间: \${timeStr}\`;
|
||||
return `机器ID: ${deviceId};站点编号: ${code};站点中文: ${name};余额: ${balance};时间: ${timeStr}`;
|
||||
}
|
||||
if (action === 'exit' || type === 'exit') {
|
||||
if (action === 'exit' || type === 'exit' || type === '出站') {
|
||||
const code = ev?.station_code || ev?.["站点编号"] || '---';
|
||||
const name = ev?.station_name || ev?.["站点中文"] || ev?.["发生站"] || '---';
|
||||
return \`机器ID: \${deviceId};站点编号: \${code};站点中文: \${name};余额: \${balance};时间: \${timeStr}\`;
|
||||
return `机器ID: ${deviceId};站点编号: ${code};站点中文: ${name};余额: ${balance};时间: ${timeStr}`;
|
||||
}
|
||||
|
||||
// fallback
|
||||
return \`时间: \${timeStr};\${typeof ev?.detail === 'string' ? ev.detail : JSON.stringify(ev?.detail || {}, null, 2)}\`;
|
||||
return `时间: ${timeStr};${typeof ev?.detail === 'string' ? ev.detail : JSON.stringify(ev?.detail || {}, null, 2)}`;
|
||||
};
|
||||
|
||||
const buildEventsHtml = (events) => {
|
||||
|
||||
+5
-5
@@ -457,25 +457,25 @@ createApp({
|
||||
|
||||
if (type === 'create' || type === 'order_created' || type === 'activated') {
|
||||
const cardType = ev?.detail?.card_type || ev?.card_type || ev?.["类型"] || '储值卡';
|
||||
return \`[${cardType}];余额: \${balance};时间: \${timeStr}\`;
|
||||
return `[${cardType}];余额: ${balance};时间: ${timeStr}`;
|
||||
}
|
||||
if (type === 'topup' || type === 'refill' || action === 'topup') {
|
||||
const amount = ev?.amount ?? ev?.["充值数额"] ?? 0;
|
||||
return \`机器ID: \${deviceId};充值数额: \${amount};余额: \${balance};时间: \${timeStr}\`;
|
||||
return `机器ID: ${deviceId};充值数额: ${amount};余额: ${balance};时间: ${timeStr}`;
|
||||
}
|
||||
if (action === 'entry' || type === 'entry') {
|
||||
const code = ev?.station_code || ev?.["站点编号"] || '---';
|
||||
const name = ev?.station_name || ev?.["站点中文"] || ev?.["发生站"] || '---';
|
||||
return \`机器ID: \${deviceId};站点编号: \${code};站点中文: \${name};余额: \${balance};时间: \${timeStr}\`;
|
||||
return `机器ID: ${deviceId};站点编号: ${code};站点中文: ${name};余额: ${balance};时间: ${timeStr}`;
|
||||
}
|
||||
if (action === 'exit' || type === 'exit') {
|
||||
const code = ev?.station_code || ev?.["站点编号"] || '---';
|
||||
const name = ev?.station_name || ev?.["站点中文"] || ev?.["发生站"] || '---';
|
||||
return \`机器ID: \${deviceId};站点编号: \${code};站点中文: \${name};余额: \${balance};时间: \${timeStr}\`;
|
||||
return `机器ID: ${deviceId};站点编号: ${code};站点中文: ${name};余额: ${balance};时间: ${timeStr}`;
|
||||
}
|
||||
|
||||
// fallback
|
||||
return \`时间: \${timeStr};\${typeof ev?.detail === 'string' ? ev.detail : JSON.stringify(ev?.detail || {}, null, 2)}\`;
|
||||
return `时间: ${timeStr};${typeof ev?.detail === 'string' ? ev.detail : JSON.stringify(ev?.detail || {}, null, 2)}`;
|
||||
};
|
||||
|
||||
const getStationName = (code) => {
|
||||
|
||||
+18
-6
@@ -190,10 +190,13 @@
|
||||
}
|
||||
|
||||
stationYByCanonical = {};
|
||||
let stationIndex = 0;
|
||||
for (const c of Object.keys(primaryLineByStation)) {
|
||||
const li = primaryLineByStation[c];
|
||||
if (li == null) continue;
|
||||
stationYByCanonical[c] = baseY + li * lineGapY;
|
||||
const waveOffset = (stationIndex % 2 === 0) ? -12 : 12;
|
||||
stationYByCanonical[c] = baseY + li * lineGapY + waveOffset;
|
||||
stationIndex++;
|
||||
}
|
||||
|
||||
const allStations = Object.keys(primaryLineByStation);
|
||||
@@ -219,11 +222,20 @@
|
||||
svgContent += `<text x="${legendTextX}" y="${yLine-1}" fill="#e5e7eb" font-size="16" font-weight="800">${li.name}</text>`;
|
||||
|
||||
if (li.stops.length >= 2) {
|
||||
const firstX = stationXByCanonical[stationCanonicalByCode[li.stops[0]] || li.stops[0]];
|
||||
let d = `M ${firstX} ${yLine}`;
|
||||
for (let i = 1; i < li.stops.length; i++) {
|
||||
const x = stationXByCanonical[stationCanonicalByCode[li.stops[i]] || li.stops[i]];
|
||||
d += ` L ${x} ${yLine}`;
|
||||
const pts = li.stops.map(code => {
|
||||
const canonical = stationCanonicalByCode[code] || code;
|
||||
return {
|
||||
x: stationXByCanonical[canonical] || 0,
|
||||
y: stationYByCanonical[code] ?? yLine
|
||||
};
|
||||
});
|
||||
|
||||
let d = `M ${pts[0].x} ${pts[0].y}`;
|
||||
for (let i = 1; i < pts.length; i++) {
|
||||
const prev = pts[i-1];
|
||||
const curr = pts[i];
|
||||
const midX = (prev.x + curr.x) / 2;
|
||||
d += ` C ${midX} ${prev.y}, ${midX} ${curr.y}, ${curr.x} ${curr.y}`;
|
||||
}
|
||||
svgContent += `<path d="${d}" fill="none" stroke="${li.color}" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" />`;
|
||||
}
|
||||
|
||||
@@ -115,23 +115,23 @@
|
||||
|
||||
if (type === 'sale' || type === '售票') {
|
||||
const cost = ev.cost ?? ev.price ?? ev.amount ?? ev["票价"] ?? ev["售票额"] ?? '0';
|
||||
return \`机器ID: \${deviceId};票价: \${cost};时间: \${timeStr}\`;
|
||||
return `机器ID: ${deviceId};票价: ${cost};时间: ${timeStr}`;
|
||||
}
|
||||
if (type === 'entry' || action === 'entry') {
|
||||
const code = ev.station_code || ev["站点编号"] || '---';
|
||||
const name = ev.station_name || ev["站点中文"] || ev["发生站"] || '---';
|
||||
return \`机器ID: \${deviceId};站点编号: \${code};站点中文: \${name};时间: \${timeStr}\`;
|
||||
return `机器ID: ${deviceId};站点编号: ${code};站点中文: ${name};时间: ${timeStr}`;
|
||||
}
|
||||
if (type === 'exit' || action === 'exit') {
|
||||
const code = ev.station_code || ev["站点编号"] || '---';
|
||||
const name = ev.station_name || ev["站点中文"] || ev["发生站"] || '---';
|
||||
return \`机器ID: \${deviceId};站点编号: \${code};站点中文: \${name};时间: \${timeStr}\`;
|
||||
return `机器ID: ${deviceId};站点编号: ${code};站点中文: ${name};时间: ${timeStr}`;
|
||||
}
|
||||
|
||||
// fallback
|
||||
const code = ev.station_code || ev["站点编号"] || '---';
|
||||
const name = ev.station_name || ev["站点中文"] || ev["发生站"] || '---';
|
||||
return \`机器ID: \${deviceId};站点编号: \${code};站点中文: \${name};时间: \${timeStr}\`;
|
||||
return `机器ID: ${deviceId};站点编号: ${code};站点中文: ${name};时间: ${timeStr}`;
|
||||
};
|
||||
|
||||
function renderList(items) {
|
||||
|
||||
Reference in New Issue
Block a user