import { Eye, FileText, RefreshCcw, Upload, X } from 'lucide-react'; import type { ReactNode } from 'react'; import { useEffect, useRef, useState } from 'react'; import { getAdminEditorShowcaseCampaign, listAdminEditorShowcaseAssets, reviewAdminEditorShowcaseAsset, updateAdminEditorShowcaseDisplay, uploadAdminEditorShowcaseCampaignImage, upsertAdminEditorShowcaseCampaign, } from '../api/adminApiClient'; import type { AdminEditorShowcaseAssetPayload, AdminEditorShowcaseCampaignPayload, AdminEditorShowcaseListQuery, } from '../api/adminApiTypes'; import { AdminEditorAssetPreviewDialog, AdminEditorAssetThumbnail, } from '../components/AdminEditorAssetMedia'; import { AdminUserReferenceButton } from '../components/AdminUserReferenceButton'; import { handlePageError } from './pageUtils'; interface AdminEditorShowcaseReviewPageProps { token: string; onUnauthorized: (message?: string) => void; } const showcaseCategoryOptions = [ { value: 'characters', label: '角色' }, { value: 'ui', label: 'UI' }, { value: 'music', label: '音乐' }, { value: 'marketing', label: '美宣' }, ]; const reviewStatusOptions = [ { value: '', label: '全部' }, { value: 'pending', label: '待审核' }, { value: 'approved', label: '已通过' }, { value: 'rejected', label: '已拒绝' }, ]; export function AdminEditorShowcaseReviewPage({ token, onUnauthorized, }: AdminEditorShowcaseReviewPageProps) { const [entries, setEntries] = useState([]); const [reviewStatus, setReviewStatus] = useState('pending'); const [ownerUserId, setOwnerUserId] = useState(''); const [submittedAfter, setSubmittedAfter] = useState(''); const [submittedBefore, setSubmittedBefore] = useState(''); const [nextCursor, setNextCursor] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const [reviewNotes, setReviewNotes] = useState>({}); const [detailEntry, setDetailEntry] = useState(null); const [previewEntry, setPreviewEntry] = useState(null); const [promptPreview, setPromptPreview] = useState<{ title: string; prompt: string; } | null>(null); const [campaignDraft, setCampaignDraft] = useState({ enabled: false, title: '', imageSrc: '', prompt: '', author: '', costText: '', imageObjectKey: null, imageWidth: null, imageHeight: null, }); const [isSavingCampaign, setIsSavingCampaign] = useState(false); const [isUploadingCampaignImage, setIsUploadingCampaignImage] = useState(false); const campaignImageInputRef = useRef(null); useEffect(() => { void refreshPage(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [token, reviewStatus, ownerUserId, submittedAfter, submittedBefore]); useEffect(() => { void getAdminEditorShowcaseCampaign(token) .then((response) => { if (response.campaign) { setCampaignDraft(response.campaign); } }) .catch((error: unknown) => handlePageError(error, onUnauthorized, setErrorMessage), ); }, [token, onUnauthorized]); async function refreshPage() { setIsLoading(true); setErrorMessage(''); try { const response = await listAdminEditorShowcaseAssets( token, buildListQuery(), ); setEntries(response.entries); setNextCursor(response.nextCursor ?? null); } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } finally { setIsLoading(false); } } async function loadMore() { if (!nextCursor || isLoadingMore) { return; } setIsLoadingMore(true); setErrorMessage(''); try { const response = await listAdminEditorShowcaseAssets(token, { ...buildListQuery(), cursor: nextCursor, }); setEntries((current) => mergeShowcaseEntries(current, response.entries)); setNextCursor(response.nextCursor ?? null); } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } finally { setIsLoadingMore(false); } } function buildListQuery(): AdminEditorShowcaseListQuery { return { ownerUserId: ownerUserId || null, reviewStatus: reviewStatus || null, submittedAfter: dateInputToStartRfc3339(submittedAfter), submittedBefore: dateInputToEndRfc3339(submittedBefore), limit: 80, }; } async function submitReview( entry: AdminEditorShowcaseAssetPayload, nextStatus: 'approved' | 'rejected', ) { setErrorMessage(''); try { const response = await reviewAdminEditorShowcaseAsset(token, { showcaseId: entry.showcaseId, reviewStatus: nextStatus, reviewNote: reviewNotes[entry.showcaseId]?.trim() || null, }); replaceEntry(response.entry); setReviewNotes((current) => ({ ...current, [entry.showcaseId]: '', })); } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } } async function toggleDisplay(entry: AdminEditorShowcaseAssetPayload) { setErrorMessage(''); try { const response = await updateAdminEditorShowcaseDisplay(token, { showcaseId: entry.showcaseId, displayEnabled: !entry.displayEnabled, showcaseCategory: entry.showcaseCategory ?? null, }); replaceEntry(response.entry); } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } } async function updateCategory( entry: AdminEditorShowcaseAssetPayload, showcaseCategory: string, ) { setErrorMessage(''); const nextCategory = showcaseCategory.trim(); try { const response = await updateAdminEditorShowcaseDisplay(token, { showcaseId: entry.showcaseId, displayEnabled: entry.displayEnabled, showcaseCategory: nextCategory, }); replaceEntry(response.entry); } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } } async function saveCampaign() { setIsSavingCampaign(true); setErrorMessage(''); try { const response = await upsertAdminEditorShowcaseCampaign(token, { enabled: campaignDraft.enabled, title: campaignDraft.title, imageSrc: campaignDraft.imageSrc, imageObjectKey: campaignDraft.imageObjectKey ?? null, imageWidth: campaignDraft.imageWidth ?? null, imageHeight: campaignDraft.imageHeight ?? null, prompt: campaignDraft.prompt, author: campaignDraft.author, costText: campaignDraft.costText, }); if (response.campaign) { setCampaignDraft(response.campaign); } } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } finally { setIsSavingCampaign(false); } } async function handleCampaignImageFile(file: File | null | undefined) { if (!file) { return; } setIsUploadingCampaignImage(true); setErrorMessage(''); try { const upload = await uploadAdminEditorShowcaseCampaignImage(token, file); setCampaignDraft((current) => ({ ...current, imageSrc: upload.imageSrc, imageObjectKey: upload.imageObjectKey, imageWidth: upload.imageWidth, imageHeight: upload.imageHeight, })); } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } finally { setIsUploadingCampaignImage(false); if (campaignImageInputRef.current) { campaignImageInputRef.current.value = ''; } } } function replaceEntry(entry: AdminEditorShowcaseAssetPayload) { setEntries((current) => current.map((item) => item.showcaseId === entry.showcaseId ? entry : item, ), ); setDetailEntry((current) => current?.showcaseId === entry.showcaseId ? entry : current, ); } return (

精选审核

{errorMessage ? (
{errorMessage}
) : null}
{entries.map((entry) => { const promptText = entry.prompt || entry.actualPrompt || '-'; return ( ); })}
资源图 提交时间 作者 分类 状态 提示词 成本 / 返还 审核
{entry.label || '-'} {formatDateTime(entry.submittedAt)}
{authorDisplayName(entry)} {entry.authorPublicUserCode?.trim() || '-'}
{entry.reviewStatus === 'approved' ? ( ) : ( '-' )} {reviewStatusLabel(entry.reviewStatus)} {entry.reviewStatus === 'approved' ? ( {entry.displayEnabled ? '展示中' : '未展示'} ) : null} {entry.generationCostMudPoints} 泥点 {entry.refundMudPoints} 泥点
{entry.reviewStatus === 'pending' ? ( <> setReviewNotes((current) => ({ ...current, [entry.showcaseId]: event.target.value, })) } /> ) : null} {entry.reviewStatus === 'approved' ? ( ) : null}
{!isLoading && entries.length === 0 ? (
暂无精选审核素材
) : null} {nextCursor ? ( ) : null}

精选活动卡