fix SCUM player online freshness display
This commit is contained in:
@@ -181,16 +181,17 @@ function renderSurfaceBody(e: ReactLike["createElement"], pageKey: string, data:
|
||||
function playersSurface(e: ReactLike["createElement"], data: SCUMSurfaceData, input: SCUMPageContext, view: ViewState) {
|
||||
const search = view.playerSearch.trim().toLowerCase();
|
||||
const players = data.players.filter((player) => matchesText(player, search, "displayName", "playerName", "gamePlayerId", "playerId", "steamId", "squadName") && (view.playerStatus === "all" || (view.playerStatus === "online") === playerOnline(player)));
|
||||
const onlineCount = data.players.filter(playerOnline).length;
|
||||
const selectedPlayer = data.players.find((player) => playerKey(player) === view.playerPanel.playerId);
|
||||
return e("div", { className: "console-record-list scum-surface" },
|
||||
e("div", { className: "resource-filter-bar scum-filter-bar" },
|
||||
labeledField(e, "搜索用户", e("input", { type: "search", value: view.playerSearch, "aria-label": "搜索用户", placeholder: "名称 / Steam ID / 队伍", onChange: (event: InputEvent) => view.setPlayerSearch(inputValue(event)) })),
|
||||
labeledField(e, "在线状态", e("select", { value: view.playerStatus, "aria-label": "在线状态", onChange: (event: InputEvent) => view.setPlayerStatus(inputValue(event)) }, e("option", { value: "all" }, "全部状态"), e("option", { value: "online" }, "在线"), e("option", { value: "offline" }, "离线/未知"))),
|
||||
e("span", { className: "page-status" }, `${players.length} / ${data.players.length} 用户`)
|
||||
e("span", { className: "scum-online-summary" }, "在线用户 ", e("strong", null, `${onlineCount} / ${data.players.length}`)),
|
||||
players.length !== data.players.length ? e("span", { className: "page-status" }, `当前显示 ${players.length} 人`) : null
|
||||
),
|
||||
e("div", { className: "provider-table-wrap scum-table-wrap" },
|
||||
e("table", { className: "resource-table scum-user-table" },
|
||||
e("caption", { className: "provider-id" }, "SCUM 用户真实记录"),
|
||||
e("thead", null, e("tr", null, ["用户名", "队伍", "状态", "最后活动", "操作"].map((label) => e("th", { key: label, scope: "col" }, label)))),
|
||||
e("tbody", null, players.length ? players.map((player, index) => playerTableRow(e, player, index, data, input, view)) : e("tr", null, e("td", { colSpan: 5 }, e("p", { className: "page-status" }, "没有符合筛选条件的真实用户记录。"))))
|
||||
)
|
||||
@@ -578,7 +579,9 @@ function field(row: RecordMap | undefined, ...keys: string[]): unknown { if (!ro
|
||||
function normalizeKey(value: string): string { return value.replace(/[_-]/g, "").toLowerCase(); }
|
||||
function textField(row: RecordMap | undefined, ...keys: string[]): string { const value = field(row, ...keys); return value === undefined || value === null ? "" : String(value); }
|
||||
function boolField(row: RecordMap, ...keys: string[]): boolean { const value = field(row, ...keys); return value === true || value === 1 || value === "1" || value === "true"; }
|
||||
function playerOnline(player: RecordMap): boolean { const status = textField(player, "status").toLowerCase(); return boolField(player, "online") || ["online", "active", "connected"].includes(status); }
|
||||
const playerActivityKeys = ["lastSeenAt", "lastLoginObservedAt", "onlineObservedAt", "updatedAt"];
|
||||
const playerOnlineWindowMs = 5 * 60 * 1000;
|
||||
function playerOnline(player: RecordMap): boolean { const status = textField(player, "status").toLowerCase(); const active = boolField(player, "online") || ["online", "active", "connected"].includes(status); if (!active) return false; const lastActivity = field(player, ...playerActivityKeys); if (lastActivity === undefined || lastActivity === null || lastActivity === "") return true; const timestamp = Date.parse(String(lastActivity)); return Number.isNaN(timestamp) || Date.now() - timestamp <= playerOnlineWindowMs; }
|
||||
function numField(row: RecordMap, ...keys: string[]): string { const value = field(row, ...keys); return value === undefined || value === null || value === "" ? "--" : String(value); }
|
||||
function idOf(row: RecordMap | undefined, fallback: string): string { return textField(row, "_recordKey", "id", "gamePlayerId", "squadId", "vehicleId", "flagId", "eventId", "code") || fallback; }
|
||||
function freshness(row: RecordMap): string { const fresh = field(row, "freshness"); return isRecord(fresh) ? textField(fresh, "status") || "unknown" : textField(row, "freshnessStatus", "updatedAt") || "unknown"; }
|
||||
|
||||
@@ -175,7 +175,8 @@ describe("SCUM plugin feature module", () => {
|
||||
it("renders searchable user management from real collection values", () => {
|
||||
const view = renderAndCollect();
|
||||
expect(view.nodes).toContain("section:用户管理");
|
||||
expect(view.texts).toContain("SCUM 用户真实记录");
|
||||
expect(view.texts).not.toContain("SCUM 用户真实记录");
|
||||
expect(view.texts).toEqual(expect.arrayContaining(["在线用户 ", "1 / 1"]));
|
||||
expect(view.texts).toEqual(expect.arrayContaining(["用户名", "队伍", "状态", "最后活动", "操作"]));
|
||||
expect(view.texts).not.toEqual(expect.arrayContaining(["Steam", "上次登录", "登录 IP", "概况"]));
|
||||
expect(view.texts).not.toContain("筛选结果");
|
||||
@@ -191,6 +192,13 @@ describe("SCUM plugin feature module", () => {
|
||||
expect(view.texts.join("\n")).not.toContain("Fame 42");
|
||||
});
|
||||
|
||||
it("expires online users after five minutes without activity", () => {
|
||||
const staleAt = new Date(Date.now() - 5 * 60 * 1000 - 1).toISOString();
|
||||
const view = renderAndCollect({ data: { ...surfaceData, players: [{ ...surfaceData.players[0], lastSeenAt: staleAt }] } });
|
||||
expect(view.texts).toEqual(expect.arrayContaining(["在线用户 ", "0 / 1"]));
|
||||
expect(view.texts).toContain("离线/未知");
|
||||
});
|
||||
|
||||
it("prepares player attribute SQL execution through platform-to-Run remote access", async () => {
|
||||
const player = { steamId: "76561198000000001", displayName: "Mira", stateVersion: "state-1", stamina: 12, dexterity: 4, intelligence: 8 };
|
||||
const drafts = playerAttributeDrafts(player).map((draft) => draft.fieldKey === "stamina" ? { ...draft, after: "855" } : draft);
|
||||
|
||||
Reference in New Issue
Block a user