文件
hyb-oa/client/src/pages/PortalAnnouncementsPage.tsx
T
wangsli 9905c1c79d feat(P25-2/P25-3/P26-2/P28-1/P24-2/P29-3): 落地内部OA够用版前端入口与运营页面
关联 Issue: P25-2, P25-3, P25-4, P26-2, P26-4, P28-1, P28-2, P28-3, P28-4, P24-2, P29-3
2026-03-21 18:03:48 +08:00

85 行
4.2 KiB
TypeScript

import { useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { archiveAnnouncement, createAnnouncement, fetchAnnouncements, updateAnnouncement } from "../api/announcements";
import { PageHeader } from "../components/common/PageHeader";
import { Button } from "../components/ui/button";
import { Input } from "../components/ui/input";
import { Label } from "../components/ui/label";
import { toast } from "sonner";
export function PortalAnnouncementsPage() {
const queryClient = useQueryClient();
const [title, setTitle] = useState("");
const [summary, setSummary] = useState("");
const [linkUrl, setLinkUrl] = useState("");
const [priority, setPriority] = useState("100");
const listQuery = useQuery({ queryKey: ["announcements", "manage"], queryFn: () => fetchAnnouncements(true) });
const refresh = () => queryClient.invalidateQueries({ queryKey: ["announcements"] });
const createMutation = useMutation({
mutationFn: () => createAnnouncement({ title, summary, linkUrl: linkUrl || null, priority: Number(priority || 100) }),
onSuccess: async () => {
setTitle("");
setSummary("");
setLinkUrl("");
setPriority("100");
await refresh();
toast.success("公告已创建");
},
});
const archiveMutation = useMutation({
mutationFn: archiveAnnouncement,
onSuccess: async () => {
await refresh();
toast.success("公告已归档");
},
});
const toggleMutation = useMutation({
mutationFn: ({ id, enabled }: { id: string; enabled: boolean }) => updateAnnouncement(id, { enabled }),
onSuccess: async () => {
await refresh();
toast.success("公告状态已更新");
},
});
return (
<div className="space-y-6">
<PageHeader title="公告管理" subtitle="轻量发布公告、制度链接和 FAQ 入口。" />
<div className="grid gap-6 xl:grid-cols-[360px,1fr]">
<div className="card-std space-y-4 p-6">
<h3 className="text-base font-semibold text-zinc-900">新建公告</h3>
<div className="space-y-2"><Label>标题</Label><Input value={title} onChange={(event) => setTitle(event.target.value)} /></div>
<div className="space-y-2"><Label>摘要</Label><textarea className="min-h-28 w-full rounded-lg border border-zinc-300 px-3 py-2 text-sm" value={summary} onChange={(event) => setSummary(event.target.value)} /></div>
<div className="space-y-2"><Label>链接</Label><Input value={linkUrl} onChange={(event) => setLinkUrl(event.target.value)} /></div>
<div className="space-y-2"><Label>优先级</Label><Input value={priority} onChange={(event) => setPriority(event.target.value)} /></div>
<Button type="button" disabled={createMutation.isPending} onClick={() => createMutation.mutate()}>{createMutation.isPending ? "创建中..." : "创建公告"}</Button>
</div>
<div className="card-std space-y-4 p-6">
<h3 className="text-base font-semibold text-zinc-900">公告列表</h3>
<div className="space-y-3">
{(listQuery.data?.items ?? []).map((item) => (
<div key={item.id} className="rounded-lg border border-zinc-200 p-4">
<div className="flex items-start justify-between gap-3">
<div>
<div className="font-medium text-zinc-900">{item.title}</div>
<div className="mt-1 text-sm text-zinc-600">{item.summary}</div>
<div className="mt-1 text-xs text-zinc-500">priority={item.priority} · {item.enabled ? "enabled" : "disabled"}</div>
</div>
<div className="flex gap-2">
<Button type="button" variant="secondary" onClick={() => toggleMutation.mutate({ id: item.id, enabled: !item.enabled })}>{item.enabled ? "停用" : "启用"}</Button>
<Button type="button" variant="secondary" onClick={() => archiveMutation.mutate(item.id)}>归档</Button>
</div>
</div>
</div>
))}
{!listQuery.data?.items?.length ? <div className="text-sm text-zinc-500">暂无公告</div> : null}
</div>
</div>
</div>
</div>
);
}