Files
QiuSW 9068712bb1
lint / go (push) Canceled after 0s
lint / go_mod (push) Canceled after 0s
lint / conf (push) Canceled after 0s
lint / docslinks (push) Canceled after 0s
lint / docsorder (push) Canceled after 0s
lint / apidocs (push) Canceled after 0s
lint / other (push) Canceled after 0s
test / test_64 (push) Canceled after 0s
test / test_32 (push) Canceled after 0s
test / test_e2e (push) Canceled after 0s
feat(admin): add complete device pagination (#6)
2026-08-28 16:48:17 +08:00

183 lines
5.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function initMediaMTXAdminList(root, factory) {
"use strict";
const api = factory();
if (typeof module !== "undefined" && module.exports) module.exports = api;
else root.MediaMTXAdminList = api;
}(typeof globalThis !== "undefined" ? globalThis : this, () => {
"use strict";
class PageLoadError extends Error {
constructor(label, page, cause) {
const detail = cause?.message ? `:${cause.message}` : "。";
super(`${label}第 ${page + 1} 页加载失败${detail}`);
this.name = "PageLoadError";
this.status = Number.isInteger(cause?.status) ? cause.status : 0;
this.page = page;
this.cause = cause;
}
}
function limitConcurrency(fetcher, requestedLimit) {
const limit = Math.max(1, Number(requestedLimit) || 1);
let active = 0;
const queue = [];
function runNext() {
while (active < limit && queue.length > 0) {
const task = queue.shift();
active += 1;
Promise.resolve()
.then(() => fetcher(...task.args))
.then(task.resolve, task.reject)
.finally(() => {
active -= 1;
runNext();
});
}
}
return (...args) => new Promise((resolve, reject) => {
queue.push({ args, resolve, reject });
runNext();
});
}
function pageURL(endpoint, page, pageSize) {
const separator = endpoint.includes("?") ? "&" : "?";
const query = new URLSearchParams({
itemsPerPage: String(pageSize),
page: String(page),
});
return `${endpoint}${separator}${query}`;
}
function validatePage(payload, label, page) {
if (!payload || !Array.isArray(payload.items)
|| !Number.isInteger(payload.itemCount) || payload.itemCount < 0
|| !Number.isInteger(payload.pageCount) || payload.pageCount < 0) {
throw new PageLoadError(label, page, new Error("API 返回了无效的分页数据。"));
}
return payload;
}
async function fetchPage(fetcher, endpoint, label, page, pageSize) {
try {
const payload = await fetcher(pageURL(endpoint, page, pageSize));
return validatePage(payload, label, page);
} catch (error) {
if (error instanceof PageLoadError) throw error;
throw new PageLoadError(label, page, error);
}
}
async function fetchAllPages(fetcher, endpoint, options = {}) {
const label = options.label || "列表";
const pageSize = options.pageSize || 100;
const concurrency = Math.max(1, options.concurrency || 4);
const onProgress = typeof options.onProgress === "function" ? options.onProgress : () => {};
const first = await fetchPage(fetcher, endpoint, label, 0, pageSize);
const pageCount = first.pageCount;
const pages = new Array(Math.max(1, pageCount));
pages[0] = first.items;
let loaded = first.items.length;
let pagesLoaded = 1;
let stopped = false;
onProgress({ label, loaded, total: first.itemCount, pagesLoaded, pageCount });
let nextPage = 1;
const workerCount = Math.min(concurrency, Math.max(0, pageCount - 1));
const workers = Array.from({ length: workerCount }, async () => {
while (!stopped && nextPage < pageCount) {
const page = nextPage;
nextPage += 1;
let payload;
try {
payload = await fetchPage(fetcher, endpoint, label, page, pageSize);
} catch (error) {
stopped = true;
throw error;
}
if (stopped) return;
pages[page] = payload.items;
loaded += payload.items.length;
pagesLoaded += 1;
onProgress({ label, loaded, total: first.itemCount, pagesLoaded, pageCount });
}
});
await Promise.all(workers);
return {
items: pages.flat(),
itemCount: first.itemCount,
pageCount,
};
}
function uniqueSortedByName(items) {
const byName = new Map();
for (const item of items) {
const name = String(item?.name || "");
if (name) byName.set(name, item);
}
return Array.from(byName.values()).sort((left, right) => {
const leftName = String(left.name);
const rightName = String(right.name);
if (leftName < rightName) return -1;
if (leftName > rightName) return 1;
return 0;
});
}
function filterConfigs(items, query, statusFilter, getStatus) {
const normalizedQuery = String(query || "").trim().toLocaleLowerCase();
return items.filter((item) => {
const matchesQuery = !normalizedQuery
|| String(item.name).toLocaleLowerCase().includes(normalizedQuery)
|| String(item.sourceMasked).toLocaleLowerCase().includes(normalizedQuery);
const matchesStatus = statusFilter === "all" || getStatus(item) === statusFilter;
return matchesQuery && matchesStatus;
});
}
function countStatuses(items, getStatus) {
const result = { total: items.length, online: 0, offline: 0, idle: 0 };
for (const item of items) {
const status = getStatus(item);
if (Object.prototype.hasOwnProperty.call(result, status)) result[status] += 1;
}
return result;
}
function paginateItems(items, requestedPage, requestedPageSize) {
const pageSize = [25, 50, 100].includes(Number(requestedPageSize))
? Number(requestedPageSize)
: 50;
const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
const page = Math.min(Math.max(1, Number(requestedPage) || 1), totalPages);
const startIndex = (page - 1) * pageSize;
const pageItems = items.slice(startIndex, startIndex + pageSize);
return {
items: pageItems,
page,
pageSize,
totalItems: items.length,
totalPages,
start: pageItems.length === 0 ? 0 : startIndex + 1,
end: pageItems.length === 0 ? 0 : startIndex + pageItems.length,
};
}
return {
PageLoadError,
countStatuses,
fetchAllPages,
filterConfigs,
limitConcurrency,
pageURL,
paginateItems,
uniqueSortedByName,
};
}));