feat(events): infinite scroll with page_size=10, replace full re-render
Replace bulk load+re-render with scroll-based pagination: load 10 items on init, append next page when scrolling near the bottom. prependEvent now inserts directly into DOM instead of rebuilding from state.events. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2a247dd70b
commit
bc6e6e889f
|
|
@ -2,56 +2,80 @@ import { apiFetch } from "./api.js";
|
|||
import { dom } from "./dom.js";
|
||||
import { state } from "./state.js";
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
let _page = 1;
|
||||
let _hasMore = false;
|
||||
let _loading = false;
|
||||
|
||||
function formatTime(value) {
|
||||
if (!value) {
|
||||
return "--";
|
||||
}
|
||||
return value;
|
||||
return value || "--";
|
||||
}
|
||||
|
||||
export function renderEvents() {
|
||||
function makeCard(item) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "event-card";
|
||||
row.innerHTML = `<div class="event-meta"><span class="badge">${(item.level || "info").toUpperCase()}</span><span class="muted event-time">${formatTime(item.created_at)}</span><strong class="event-type">${item.event_type}</strong></div><div class="event-message">${item.message}</div>`;
|
||||
return row;
|
||||
}
|
||||
|
||||
async function loadMore() {
|
||||
if (_loading || !_hasMore) return;
|
||||
_loading = true;
|
||||
|
||||
const params = new URLSearchParams({ page: String(_page), page_size: String(PAGE_SIZE) });
|
||||
if (state.selectedUnitId) params.set("unit_id", state.selectedUnitId);
|
||||
|
||||
try {
|
||||
const response = await apiFetch(`/api/event?${params.toString()}`);
|
||||
const items = response.data || [];
|
||||
items.forEach((item) => dom.eventList.appendChild(makeCard(item)));
|
||||
_hasMore = items.length === PAGE_SIZE;
|
||||
_page += 1;
|
||||
} finally {
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadEvents() {
|
||||
_page = 1;
|
||||
_hasMore = false;
|
||||
_loading = false;
|
||||
dom.eventList.innerHTML = "";
|
||||
|
||||
if (!state.events.length) {
|
||||
const params = new URLSearchParams({ page: "1", page_size: String(PAGE_SIZE) });
|
||||
if (state.selectedUnitId) params.set("unit_id", state.selectedUnitId);
|
||||
|
||||
_loading = true;
|
||||
try {
|
||||
const response = await apiFetch(`/api/event?${params.toString()}`);
|
||||
const items = response.data || [];
|
||||
|
||||
if (!items.length) {
|
||||
dom.eventList.innerHTML = '<div class="list-item"><div class="muted">暂无事件</div></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
state.events.forEach((item) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "event-card";
|
||||
row.innerHTML = `<div class="event-meta"><span class="badge">${(item.level || "info").toUpperCase()}</span><span class="muted event-time">${formatTime(item.created_at)}</span><strong class="event-type">${item.event_type}</strong></div><div class="event-message">${item.message}</div>`;
|
||||
dom.eventList.appendChild(row);
|
||||
});
|
||||
items.forEach((item) => dom.eventList.appendChild(makeCard(item)));
|
||||
_hasMore = items.length === PAGE_SIZE;
|
||||
_page = 2;
|
||||
} finally {
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
function matchesCurrentFilter(item) {
|
||||
if (state.selectedUnitId && item.unit_id !== state.selectedUnitId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function prependEvent(item) {
|
||||
if (!matchesCurrentFilter(item)) {
|
||||
return;
|
||||
if (state.selectedUnitId && item.unit_id !== state.selectedUnitId) return;
|
||||
|
||||
const placeholder = dom.eventList.querySelector(".list-item");
|
||||
if (placeholder) placeholder.remove();
|
||||
|
||||
dom.eventList.insertBefore(makeCard(item), dom.eventList.firstChild);
|
||||
}
|
||||
|
||||
state.events = [item, ...state.events.filter((existing) => existing.id !== item.id)].slice(0, 20);
|
||||
renderEvents();
|
||||
dom.eventList.addEventListener("scroll", () => {
|
||||
const el = dom.eventList;
|
||||
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 40) {
|
||||
loadMore();
|
||||
}
|
||||
|
||||
export async function loadEvents() {
|
||||
const params = new URLSearchParams({
|
||||
page: "1",
|
||||
page_size: "20",
|
||||
});
|
||||
|
||||
if (state.selectedUnitId) {
|
||||
params.set("unit_id", state.selectedUnitId);
|
||||
}
|
||||
|
||||
const response = await apiFetch(`/api/event?${params.toString()}`);
|
||||
state.events = response.data || [];
|
||||
renderEvents();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue