From df405cc076294ecb5bb6778318c30a68658ee20e Mon Sep 17 00:00:00 2001 From: stqfdyr <89149493+stqfdyr@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:26:18 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=81=A2=E5=A4=8D=E5=AE=9E=E6=97=B6?= =?UTF-8?q?=E6=95=B0=E5=AD=97=E7=9A=84=E5=AE=9A=E5=AE=BD=E6=A7=BD=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #7 在改到期天数时连带删掉了 #4 的 `Num` 槽位:摘要行、表格网速列、展开详情的网速行重新按最宽形态预留宽度,数字变化时两侧不再跳动。到期天数仍由 `expiresIn` 给出。 --- src/components/ServerTable.tsx | 66 +++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/src/components/ServerTable.tsx b/src/components/ServerTable.tsx index 1ecd696..061b856 100644 --- a/src/components/ServerTable.tsx +++ b/src/components/ServerTable.tsx @@ -105,6 +105,37 @@ function Bar({ pct, label }: { pct: number | null; label?: string }) { ) } +// The widest form each formatter writes, in `ch`, measured with tabular figures. +// M is the widest unit letter, so each is measured in megabytes rather than the +// gigabytes a reading is more often in: `compact` spans "0B" to "1023M", that is +// 2.2 to 5.49; `bytes` reaches 7.19 at "1023 MB"; `rate` 10.09 at "1023.0 MB/s". +// Rounded up, since other UI fonts are a few percent wider than the one these +// were measured in. +const SLOT = { compact: 5.6, bytes: 7.3, rate: 10.2 } + +/** + * A figure that changes on every push, held in a slot wide enough for the widest + * form it can take, so a node moving from 19K/s to 8.19K/s leaves the rest of the + * line where it was. Right-aligned, so the unit keeps its place and the digits + * grow towards the arrow instead. + * + * `ch` is the width of a digit under the tabular figures this selects, so a slot + * follows whatever size it is drawn at, down to the 10px the table uses on a + * phone. The width travels as a custom property rather than `min-width` itself, + * which is what allows a caller to drop the reservation where the column is too + * narrow to hold it: an inline style would outrank the class that does so. + */ +function Num({ ch, className, children }: { ch: number; className?: string; children: ReactNode }) { + return ( + + {children} + + ) +} + function Expiry({ node }: { node: Node }) { const days = expiresIn(node) if (days === null) return {FOREVER} @@ -181,7 +212,15 @@ function Details({ node }: { node: Node }) { {m ? m.load.map((n) => n.toFixed(2)).join(" / ") : "—"} {m ? `${m.procs} · TCP ${m.tcp} · UDP ${m.udp}` : "—"} - {m ? `↓ ${rate(m.net_rx)} · ↑ ${rate(m.net_tx)}` : "—"} + + {m ? ( + <> + ↓ {rate(m.net_rx)} · ↑ {rate(m.net_tx)} + + ) : ( + "—" + )} + {flow(node.day_rx, node.day_tx)} {flow(node.month_rx, node.month_tx)} @@ -249,7 +288,19 @@ function Row({ node, index }: { node: Node; index: number }) { {m ? duration(m.uptime) : "—"} {m ? m.load[0].toFixed(2) : "—"} - {m ? `${compact(m.net_rx)} | ${compact(m.net_tx)}` : "— | —"} + {/* No reservation on a phone: the column is 21% of the panel, 60px at + 320px, against the 70px two slots and their separator need, and the + overflow disappears under the bar beside it. */} + + {m ? ( + <> + {compact(m.net_rx)} |{" "} + {compact(m.net_tx)} + + ) : ( + "— | —" + )} + @@ -291,10 +342,15 @@ export function ServerTable({ nodes }: { nodes: Node[] }) {

服务器

- 在线 {nodes.filter((n) => n.online).length} / {nodes.length} · ↓ {compact(sum((n) => n.metrics!.net_rx))}/s · ↑{" "} - {compact(sum((n) => n.metrics!.net_tx))}/s + {/* The online count is reserved for as many digits as the total has, + since it cannot exceed it. */} + 在线 {nodes.filter((n) => n.online).length} / {nodes.length} · ↓{" "} + {compact(sum((n) => n.metrics!.net_rx))}/s · ↑{" "} + {compact(sum((n) => n.metrics!.net_tx))}/s + + + 总流量 ↓ {bytes(totalRx)} · ↑ {bytes(totalTx)} - 总流量 ↓ {bytes(totalRx)} · ↑ {bytes(totalTx)}