完成视频复刻和优化
This commit is contained in:
@@ -162,17 +162,17 @@ export function AdminApp({ section, user, team, navigateAdmin, navigate, logout
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<header className="topbar">
|
||||
<div className="admin-crumb">
|
||||
<button type="button" onClick={() => navigateAdmin("")}>平台后台</button>
|
||||
{active.slug ? <span>{active.label}</span> : null}
|
||||
</div>
|
||||
<div className="right">
|
||||
<span className="admin-mode">超管模式</span>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<Decorations />
|
||||
<header className="topbar">
|
||||
<div className="admin-crumb">
|
||||
<button type="button" onClick={() => navigateAdmin("")}>平台后台</button>
|
||||
{active.slug ? <span>{active.label}</span> : null}
|
||||
</div>
|
||||
<div className="right">
|
||||
<span className="admin-mode">超管模式</span>
|
||||
</div>
|
||||
</header>
|
||||
<div className="content" id="page-content">
|
||||
<CornerMarks />
|
||||
{toast && <ToastLike notice={toast} />}
|
||||
|
||||
@@ -32,48 +32,54 @@ export function AdminReviewsPage({ notify }: { notify: Notify }) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [tab, setTab] = useState("");
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [preview, setPreview] = useState<{ src: string; name: string } | null>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setSelected(new Set());
|
||||
const load = useCallback(async ({ silent = false } = {}) => {
|
||||
if (!silent) setLoading(true);
|
||||
try {
|
||||
const res = await adminApi.assetReviews({ review_status: tab || undefined, page, page_size: PAGE_SIZE });
|
||||
if (res.results.length === 0 && res.count > 0 && page > 1) { setPage((p) => Math.max(1, p - 1)); return; }
|
||||
setAssets(res.results);
|
||||
setCount(res.count);
|
||||
} catch {
|
||||
notify("error", "加载审核队列失败");
|
||||
if (!silent) notify("error", "加载审核队列失败");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (!silent) setLoading(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [tab, page]);
|
||||
|
||||
useEffect(() => { void load(); }, [load]);
|
||||
|
||||
function toggleOne(id: string) {
|
||||
setSelected((s) => {
|
||||
const next = new Set(s);
|
||||
if (next.has(id)) next.delete(id); else next.add(id);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
function toggleAll() {
|
||||
setSelected((s) => (s.size === assets.length ? new Set() : new Set(assets.map((a) => a.id))));
|
||||
}
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
const tick = async () => {
|
||||
if (document.hidden) return;
|
||||
try {
|
||||
await adminApi.pollReviews();
|
||||
if (alive) await load({ silent: true });
|
||||
} catch {
|
||||
/* 自动送审失败不挡列表 */
|
||||
}
|
||||
};
|
||||
void tick();
|
||||
const timer = window.setInterval(tick, 12000);
|
||||
return () => {
|
||||
alive = false;
|
||||
window.clearInterval(timer);
|
||||
};
|
||||
}, [load]);
|
||||
|
||||
async function submit(ids: string[]) {
|
||||
if (busy || ids.length === 0) return;
|
||||
async function retry(id: string) {
|
||||
if (busy) return;
|
||||
setBusy(true);
|
||||
try {
|
||||
const res = await adminApi.submitReviews(ids);
|
||||
notify("success", `已提交 ${res.submitted} 个资产送审`);
|
||||
await load();
|
||||
await adminApi.submitReviews([id]);
|
||||
notify("success", "已重新送审");
|
||||
await load({ silent: true });
|
||||
} catch {
|
||||
notify("error", "送审失败");
|
||||
notify("error", "重试失败");
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
@@ -84,8 +90,17 @@ export function AdminReviewsPage({ notify }: { notify: Notify }) {
|
||||
setBusy(true);
|
||||
try {
|
||||
const res = await adminApi.pollReviews();
|
||||
notify("success", res.polled > 0 ? `已刷新 ${res.polled} 个审核中资产` : "暂无审核中的资产");
|
||||
await load();
|
||||
const submitted = Number(res.submitted || 0);
|
||||
const polled = Number(res.polled || 0);
|
||||
if (submitted || polled) {
|
||||
notify("success", [
|
||||
submitted ? `自动送审 ${submitted} 个` : "",
|
||||
polled ? `刷新 ${polled} 个审核中` : "",
|
||||
].filter(Boolean).join(" · "));
|
||||
} else {
|
||||
notify("success", "暂无待处理审核");
|
||||
}
|
||||
await load({ silent: true });
|
||||
} catch {
|
||||
notify("error", "刷新失败");
|
||||
} finally {
|
||||
@@ -98,7 +113,7 @@ export function AdminReviewsPage({ notify }: { notify: Notify }) {
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1>资产审核</h1>
|
||||
<div className="sub"><span className="mono">{count} 个真人资产</span> · 火山人像素材库绿盾 / 红标</div>
|
||||
<div className="sub"><span className="mono">{count} 个真人资产</span> · 未送审会自动提交火山审核,无需手动点送审</div>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<button className="btn" type="button" disabled={busy} onClick={() => void poll()}>
|
||||
@@ -124,7 +139,6 @@ export function AdminReviewsPage({ notify }: { notify: Notify }) {
|
||||
<table className="t admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="col-check"><input type="checkbox" checked={selected.size === assets.length && assets.length > 0} onChange={toggleAll} aria-label="全选" /></th>
|
||||
<th>预览</th>
|
||||
<th>团队</th>
|
||||
<th>名称</th>
|
||||
@@ -135,7 +149,6 @@ export function AdminReviewsPage({ notify }: { notify: Notify }) {
|
||||
<tbody>
|
||||
{assets.map((a) => (
|
||||
<tr key={a.id}>
|
||||
<td className="col-check"><input type="checkbox" checked={selected.has(a.id)} onChange={() => toggleOne(a.id)} aria-label="选择" /></td>
|
||||
<td>
|
||||
{a.preview_url
|
||||
? (
|
||||
@@ -158,11 +171,11 @@ export function AdminReviewsPage({ notify }: { notify: Notify }) {
|
||||
{a.review_status === "failed" && a.review_error && <span className="admin-review-err mono" title={a.review_error}>!</span>}
|
||||
</td>
|
||||
<td className="col-actions">
|
||||
{(a.review_status === "failed" || a.review_status === "") && (
|
||||
<button className="btn btn-sm btn-ghost" type="button" disabled={busy} onClick={() => void submit([a.id])}>
|
||||
{a.review_status === "failed" ? "重试" : "送审"}
|
||||
{a.review_status === "failed" ? (
|
||||
<button className="btn btn-sm btn-ghost" type="button" disabled={busy} onClick={() => void retry(a.id)}>
|
||||
重试
|
||||
</button>
|
||||
)}
|
||||
) : null}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -172,14 +185,6 @@ export function AdminReviewsPage({ notify }: { notify: Notify }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selected.size > 0 && (
|
||||
<div className="admin-bulk-bar" role="toolbar" aria-label="批量送审">
|
||||
<span className="admin-bulk-count">已选 {selected.size} 个</span>
|
||||
<button className="btn btn-sm" type="button" onClick={() => setSelected(new Set())}>取消</button>
|
||||
<button className="btn btn-sm btn-primary" type="button" disabled={busy} onClick={() => void submit([...selected])}>批量送审</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<MediaLightbox open={!!preview} src={preview?.src || ""} kind="image" name={preview?.name} close={() => setPreview(null)} />
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user