2 Commits

Author SHA1 Message Date
ABAPPLO
b882a5c7d7 merge: 合入 test(KOC资源列表分页展示) 2026-08-27 19:18:00 +08:00
ABAPPLO
8e4e3de974 feat: KOC资源列表分页展示(每页15个) 2026-08-27 19:17:53 +08:00
2 changed files with 121 additions and 7 deletions

View File

@@ -3171,6 +3171,22 @@ function ScreenshotTaskTable({ distributions }: { distributions: Distribution[]
); );
} }
const RESOURCE_PAGE_SIZE = 15;
function pageList(current: number, total: number): Array<number | "gap"> {
if (total <= 7) {
return Array.from({ length: total }, (_, index) => index + 1);
}
const items: Array<number | "gap"> = [1];
const start = Math.max(2, current - 1);
const end = Math.min(total - 1, current + 1);
if (start > 2) items.push("gap");
for (let page = start; page <= end; page += 1) items.push(page);
if (end < total - 1) items.push("gap");
items.push(total);
return items;
}
function ResourcesPage({ function ResourcesPage({
accounts, accounts,
distributions, distributions,
@@ -3188,6 +3204,7 @@ function ResourcesPage({
const [platformFilter, setPlatformFilter] = useState("全部平台"); const [platformFilter, setPlatformFilter] = useState("全部平台");
const [ipFilter, setIpFilter] = useState(""); const [ipFilter, setIpFilter] = useState("");
const [sourceFilter, setSourceFilter] = useState(""); const [sourceFilter, setSourceFilter] = useState("");
const [page, setPage] = useState(1);
const [importOpen, setImportOpen] = useState(false); const [importOpen, setImportOpen] = useState(false);
const [importMode, setImportMode] = useState<"bulk" | "single">("bulk"); const [importMode, setImportMode] = useState<"bulk" | "single">("bulk");
const [importFile, setImportFile] = useState<File | null>(null); const [importFile, setImportFile] = useState<File | null>(null);
@@ -3307,11 +3324,21 @@ function ResourcesPage({
platformFilter !== "全部平台" || platformFilter !== "全部平台" ||
Boolean(ipFilter.trim()) || Boolean(ipFilter.trim()) ||
Boolean(sourceFilter.trim()); Boolean(sourceFilter.trim());
const totalPages = Math.max(
1,
Math.ceil(filteredAccounts.length / RESOURCE_PAGE_SIZE),
);
const currentPage = Math.min(page, totalPages);
const pagedAccounts = filteredAccounts.slice(
(currentPage - 1) * RESOURCE_PAGE_SIZE,
currentPage * RESOURCE_PAGE_SIZE,
);
const clearFilters = () => { const clearFilters = () => {
setQuery(""); setQuery("");
setPlatformFilter("全部平台"); setPlatformFilter("全部平台");
setIpFilter(""); setIpFilter("");
setSourceFilter(""); setSourceFilter("");
setPage(1);
}; };
const closeImport = () => { const closeImport = () => {
if (importWorking || singleWorking) return; if (importWorking || singleWorking) return;
@@ -3569,7 +3596,10 @@ function ResourcesPage({
<div className="filter-chips"> <div className="filter-chips">
<button <button
className={platformFilter === "全部平台" ? "active" : ""} className={platformFilter === "全部平台" ? "active" : ""}
onClick={() => setPlatformFilter("全部平台")} onClick={() => {
setPlatformFilter("全部平台");
setPage(1);
}}
> >
全部平台 全部平台
</button> </button>
@@ -3577,7 +3607,10 @@ function ResourcesPage({
<button <button
className={platformFilter === platform ? "active" : ""} className={platformFilter === platform ? "active" : ""}
key={platform} key={platform}
onClick={() => setPlatformFilter(platform)} onClick={() => {
setPlatformFilter(platform);
setPage(1);
}}
> >
<PlatformBadge platform={platform} compact /> <PlatformBadge platform={platform} compact />
</button> </button>
@@ -3590,7 +3623,10 @@ function ResourcesPage({
<span aria-hidden="true">⌕</span> <span aria-hidden="true">⌕</span>
<input <input
aria-label="搜索账号名称、账号ID或当前联系人" aria-label="搜索账号名称、账号ID或当前联系人"
onChange={(event) => setQuery(event.target.value)} onChange={(event) => {
setQuery(event.target.value);
setPage(1);
}}
placeholder="搜索账号名称 / 账号ID / 当前联系人 / 标签" placeholder="搜索账号名称 / 账号ID / 当前联系人 / 标签"
type="search" type="search"
value={query} value={query}
@@ -3601,7 +3637,10 @@ function ResourcesPage({
<input <input
aria-label="模糊搜索IP地区" aria-label="模糊搜索IP地区"
list="resource-ip-options" list="resource-ip-options"
onChange={(event) => setIpFilter(event.target.value)} onChange={(event) => {
setIpFilter(event.target.value);
setPage(1);
}}
placeholder="搜索IP地区" placeholder="搜索IP地区"
type="search" type="search"
value={ipFilter} value={ipFilter}
@@ -3615,7 +3654,10 @@ function ResourcesPage({
<input <input
aria-label="模糊搜索合作来源" aria-label="模糊搜索合作来源"
list="resource-source-options" list="resource-source-options"
onChange={(event) => setSourceFilter(event.target.value)} onChange={(event) => {
setSourceFilter(event.target.value);
setPage(1);
}}
placeholder="搜索合作来源" placeholder="搜索合作来源"
type="search" type="search"
value={sourceFilter} value={sourceFilter}
@@ -3634,7 +3676,7 @@ function ResourcesPage({
清空筛选 清空筛选
</button> </button>
<div className="resource-toolbar-summary"> <div className="resource-toolbar-summary">
<span>共 <b>{filteredAccounts.length}</b> 个结果</span> <span>共 <b>{filteredAccounts.length}</b> 个结果{totalPages > 1 ? ` · 第 ${currentPage}/${totalPages} 页` : ""}</span>
<button <button
className="export-data-button" className="export-data-button"
disabled={runningIdRefresh || idRefreshStarting} disabled={runningIdRefresh || idRefreshStarting}
@@ -3660,7 +3702,7 @@ function ResourcesPage({
{idRefreshError && <div className="form-error">{idRefreshError}</div>} {idRefreshError && <div className="form-error">{idRefreshError}</div>}
</div> </div>
<div className="resource-grid"> <div className="resource-grid">
{filteredAccounts.map(({ account, sources, partnerManagedOnly }) => { {pagedAccounts.map(({ account, sources, partnerManagedOnly }) => {
const profileLink = resourceProfileLink(account); const profileLink = resourceProfileLink(account);
const accountTags = (account.tags || "") const accountTags = (account.tags || "")
.split(/[,,、;;|]/) .split(/[,,、;;|]/)
@@ -3762,6 +3804,35 @@ function ResourcesPage({
</button> </button>
</div> </div>
)} )}
{totalPages > 1 && (
<div className="resource-pagination">
<button
disabled={currentPage === 1}
onClick={() => setPage(currentPage - 1)}
>
上一页
</button>
{pageList(currentPage, totalPages).map((item, index) =>
item === "gap" ? (
<span className="gap" key={`gap-${index}`}>…</span>
) : (
<button
className={item === currentPage ? "active" : ""}
key={item}
onClick={() => setPage(item)}
>
{item}
</button>
),
)}
<button
disabled={currentPage === totalPages}
onClick={() => setPage(currentPage + 1)}
>
下一页
</button>
</div>
)}
</section> </section>
{editAccount && ( {editAccount && (
<div className="modal-backdrop" role="presentation" onMouseDown={closeAccountEdit}> <div className="modal-backdrop" role="presentation" onMouseDown={closeAccountEdit}>

View File

@@ -2577,6 +2577,49 @@ a {
font-size: 11px; font-size: 11px;
} }
.resource-pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding-top: 18px;
}
.resource-pagination button {
min-width: 30px;
height: 30px;
padding: 0 10px;
border: 1px solid transparent;
border-radius: 8px;
color: #66736f;
background: transparent;
font-size: 11px;
font-weight: 600;
cursor: pointer;
}
.resource-pagination button:hover:not(:disabled) {
background: #f0f4f2;
}
.resource-pagination button.active {
color: #34443f;
background: white;
border-color: #dce2de;
box-shadow: 0 1px 3px rgb(18 31 28 / 0.08);
}
.resource-pagination button:disabled {
color: #b7c1bd;
cursor: default;
}
.resource-pagination .gap {
color: #9aa4a0;
font-size: 11px;
padding: 0 2px;
}
.resource-import-modal { .resource-import-modal {
width: min(780px, calc(100vw - 32px)); width: min(780px, calc(100vw - 32px));
max-height: min(86vh, 820px); max-height: min(86vh, 820px);