后端: - 新增 celery_health 生成前置闸——无 worker 在线时图片/视频生成入口 一律 503,防"提交到 ARK 后无人轮询、结果悬空+额度冻结"的数据丢失 - 拼接导出:帧率跟随源众数、字幕逐句重映射、原声/BGM 混音修复 - 资金核算审计脚本 + genesis 账目回填 + 滞留预留清理命令 - 接入 yunqi provider 与豆包 TTS 模型(catalog/migrations/bootstrap 命令) 前端:全站页面更新(pipeline/library/products/projects/team/account 等), 新增共享 pager 分页组件 QA:刷新 function-audit 全量输出,新增 full-qa 报告 文档:BP 产品介绍资料、design/CLAUDE.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
43 lines
1.9 KiB
TypeScript
43 lines
1.9 KiB
TypeScript
// 通用列表分页器:沿用项目向导 pp-pager / 消费页 bill-pager 的视觉(mono 总数 + 页码窗口 + 每页条数)。
|
||
// 纯前端切片分页:父组件持有 page state,自己 slice;本组件只画控件。
|
||
// total <= pageSize 时不渲染(单页无需分页器)。
|
||
|
||
// 页码窗口:超过 7 页折叠成 1 … cur-1 cur cur+1 … last
|
||
export function pageWindow(current: number, total: number): Array<number | "ellipsis"> {
|
||
if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
|
||
const items: Array<number | "ellipsis"> = [1];
|
||
const start = Math.max(2, current - 1);
|
||
const end = Math.min(total - 1, current + 1);
|
||
if (start > 2) items.push("ellipsis");
|
||
for (let p = start; p <= end; p++) items.push(p);
|
||
if (end < total - 1) items.push("ellipsis");
|
||
items.push(total);
|
||
return items;
|
||
}
|
||
|
||
export function Pager({ page, total, pageSize, onChange }: {
|
||
page: number;
|
||
total: number;
|
||
pageSize: number;
|
||
onChange: (page: number) => void;
|
||
}) {
|
||
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
||
const cur = Math.min(Math.max(1, page), totalPages);
|
||
if (total <= pageSize) return null;
|
||
return (
|
||
<div className="list-pager">
|
||
<span className="total">// 共 {total} 条 · 第 {cur} / {totalPages} 页</span>
|
||
<div className="pages">
|
||
<button type="button" disabled={cur === 1} onClick={() => onChange(cur - 1)} aria-label="上一页">‹</button>
|
||
{pageWindow(cur, totalPages).map((p, i) => (
|
||
p === "ellipsis"
|
||
? <span key={`e${i}`} className="ellipsis">…</span>
|
||
: <button type="button" key={p} className={p === cur ? "active" : ""} onClick={() => onChange(p)}>{p}</button>
|
||
))}
|
||
<button type="button" disabled={cur === totalPages} onClick={() => onChange(cur + 1)} aria-label="下一页">›</button>
|
||
</div>
|
||
<span className="page-size">每页 {pageSize} 条</span>
|
||
</div>
|
||
);
|
||
}
|