import { Plus, RefreshCcw, Save, Trash2 } from 'lucide-react'; import { FormEvent, useEffect, useState } from 'react'; import { getAdminCreationEntryConfig, upsertAdminCreationEntryBanners, upsertAdminCreationEntryConfig, } from '../api/adminApiClient'; import type { AdminCreationEntryEventBannerPayload, AdminCreationEntryTypeConfigPayload, UnifiedCreationFieldPayload, UnifiedCreationSpecPayload, } from '../api/adminApiTypes'; import { useAdminWriteConfirm } from '../components/useAdminWriteConfirm'; import { handlePageError } from './pageUtils'; /** 创作入口后台页面参数;公告模式只展示底部加号入口公告表单。 */ interface AdminCreationEntrySwitchPageProps { token: string; onUnauthorized: (message?: string) => void; mode?: 'switches' | 'announcements'; } /** 后台公告表单的一行编辑态,保存时会统一序列化为后端传输字段。 */ type AnnouncementFormItem = { id: string; title: string; htmlCode: string; }; /** 公告表单保存前的校验与序列化结果。 */ type AnnouncementFormBuildResult = | { ok: true; json: string } | { ok: false; message: string }; let announcementFormItemSequence = 0; export function AdminCreationEntrySwitchPage({ token, onUnauthorized, mode = 'switches', }: AdminCreationEntrySwitchPageProps) { const [entries, setEntries] = useState< AdminCreationEntryTypeConfigPayload[] >([]); const [selectedId, setSelectedId] = useState('puzzle'); const [title, setTitle] = useState(''); const [subtitle, setSubtitle] = useState(''); const [badge, setBadge] = useState(''); const [imageSrc, setImageSrc] = useState(''); const [visible, setVisible] = useState(true); const [open, setOpen] = useState(true); const [sortOrder, setSortOrder] = useState('30'); const [categoryId, setCategoryId] = useState('recommended'); const [categoryLabel, setCategoryLabel] = useState('热门推荐'); const [categorySortOrder, setCategorySortOrder] = useState('20'); const [unifiedCreationSpecJson, setUnifiedCreationSpecJson] = useState(''); const [announcementItems, setAnnouncementItems] = useState< AnnouncementFormItem[] >([]); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); const [isSavingBanners, setIsSavingBanners] = useState(false); const [listErrorMessage, setListErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState(''); const [bannerErrorMessage, setBannerErrorMessage] = useState(''); const { confirmWrite, confirmDialog } = useAdminWriteConfirm(); const isAnnouncementMode = mode === 'announcements'; useEffect(() => { void refreshEntries(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [token]); async function refreshEntries() { setIsLoading(true); setListErrorMessage(''); try { const response = await getAdminCreationEntryConfig(token); const nextEntries = sortEntries(response.entries); setEntries(nextEntries); setAnnouncementItems(formatEventBannersFormItems(response.eventBanners)); fillForm( nextEntries.find((entry) => entry.id === selectedId) ?? nextEntries[0] ?? null, ); } catch (error: unknown) { handlePageError(error, onUnauthorized, setListErrorMessage); } finally { setIsLoading(false); } } async function handleSave(event: FormEvent) { event.preventDefault(); if (isSaving) { return; } const targetId = selectedId.trim(); setErrorMessage(''); const unifiedCreationSpecResult = parseUnifiedCreationSpecJson( targetId, unifiedCreationSpecJson, ); if (!unifiedCreationSpecResult.ok) { setErrorMessage(unifiedCreationSpecResult.message); return; } const confirmed = await confirmWrite({ action: '保存创作入口开关', target: targetId, }); if (!confirmed) { return; } setIsSaving(true); try { const response = await upsertAdminCreationEntryConfig(token, { id: targetId, title: title.trim(), subtitle: subtitle.trim(), badge: badge.trim(), imageSrc: imageSrc.trim(), visible, open, sortOrder: parseInteger(sortOrder), categoryId: categoryId.trim(), categoryLabel: categoryLabel.trim(), categorySortOrder: parseInteger(categorySortOrder), unifiedCreationSpec: unifiedCreationSpecResult.spec, }); const nextEntries = sortEntries(response.entries); setEntries(nextEntries); setAnnouncementItems(formatEventBannersFormItems(response.eventBanners)); fillForm(nextEntries.find((entry) => entry.id === targetId) ?? null); } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } finally { setIsSaving(false); } } /** 保存底部加号创作入口页的多公告表单配置。 */ async function handleSaveBanners() { if (isSavingBanners) { return; } setBannerErrorMessage(''); const bannerJsonResult = buildEventBannersJsonFromForm(announcementItems); if (!bannerJsonResult.ok) { setBannerErrorMessage(bannerJsonResult.message); return; } const confirmed = await confirmWrite({ action: '保存创作入口公告', target: 'creation-entry-announcements', }); if (!confirmed) { return; } setIsSavingBanners(true); try { const response = await upsertAdminCreationEntryBanners(token, { eventBannersJson: bannerJsonResult.json, }); setEntries(sortEntries(response.entries)); setAnnouncementItems(formatEventBannersFormItems(response.eventBanners)); } catch (error: unknown) { handlePageError(error, onUnauthorized, setBannerErrorMessage); } finally { setIsSavingBanners(false); } } function fillForm(entry: AdminCreationEntryTypeConfigPayload | null) { if (!entry) { return; } setSelectedId(entry.id); setTitle(entry.title); setSubtitle(entry.subtitle); setBadge(entry.badge); setImageSrc(entry.imageSrc); setVisible(entry.visible); setOpen(entry.open); setSortOrder(String(entry.sortOrder)); setCategoryId(entry.categoryId); setCategoryLabel(entry.categoryLabel); setCategorySortOrder(String(entry.categorySortOrder)); setUnifiedCreationSpecJson( formatUnifiedCreationSpecJson(entry.unifiedCreationSpec), ); } /** 更新单条公告表单字段,避免后台页面直接暴露 JSON 编辑。 */ function updateAnnouncementItem( index: number, patch: Partial>, ) { setAnnouncementItems((currentItems) => currentItems.map((item, itemIndex) => itemIndex === index ? { ...item, ...patch } : item, ), ); } /** 新增一条空公告表单行。 */ function addAnnouncementItem() { setAnnouncementItems((currentItems) => [ ...currentItems, createAnnouncementFormItem('', ''), ]); } /** 删除指定公告表单行,至少保留一条空行供继续编辑。 */ function removeAnnouncementItem(index: number) { setAnnouncementItems((currentItems) => { const nextItems = currentItems.filter( (_, itemIndex) => itemIndex !== index, ); return nextItems.length > 0 ? nextItems : [createAnnouncementFormItem('', '')]; }); } return (

{isAnnouncementMode ? '创作入口公告' : '创作入口开关'}

{isAnnouncementMode ? '配置底部加号创作入口页的公告轮播' : '控制创作中心入口展示与运行态路由可用性'}

{listErrorMessage ? (
{listErrorMessage}
) : null} {isAnnouncementMode ? (

创作入口公告

{announcementItems.length > 0 ? '已配置' : '未配置'}
{announcementItems.map((item, index) => (
{`公告 ${index + 1}`}