fix(*): 邮箱与系统公告 - 合并 PR #417 并修复迁移冲突和提醒关闭逻辑

保留历史数据库迁移谱系与公告按需加载,复用邮件配置快照校验注册域名,补充白名单及公告回归测试。
This commit is contained in:
ddCat
2026-09-05 16:39:45 +08:00
18 changed files with 560 additions and 79 deletions

View File

@@ -1,5 +1,5 @@
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { Image as AntImage, Modal } from "antd";
import { Button, Image as AntImage, Modal } from "antd";
import { Bell, BellOff, CircleAlert, Info, Pin, ShieldAlert, Wrench } from "lucide-react";
import { aceternityMotion } from "@/lib/aceternity-motion";
@@ -11,7 +11,10 @@ type AnnouncementTimelineModalProps = {
announcements: SystemAnnouncement[];
loading?: boolean;
error?: string;
automaticPrompt?: boolean;
onClose: () => void;
onDismissOnce?: () => void;
onDismissToday?: () => void;
onRetry?: () => void;
};
@@ -22,7 +25,7 @@ const levelMeta: Record<AnnouncementLevel, { label: string; dot: string; icon: t
critical: { label: "重要通知", dot: "bg-red-500", icon: ShieldAlert },
};
export function AnnouncementTimelineModal({ open, announcements, loading = false, error = "", onClose, onRetry }: AnnouncementTimelineModalProps) {
export function AnnouncementTimelineModal({ open, announcements, loading = false, error = "", automaticPrompt = false, onClose, onDismissOnce, onDismissToday, onRetry }: AnnouncementTimelineModalProps) {
const reducedMotion = useReducedMotion();
return (
@@ -30,7 +33,12 @@ export function AnnouncementTimelineModal({ open, announcements, loading = false
open={open}
width={960}
centered
footer={null}
footer={automaticPrompt ? (
<div className="flex flex-wrap justify-end gap-2">
<Button onClick={onDismissToday}>今日不再提醒</Button>
<Button type="primary" onClick={onDismissOnce}>关闭本次</Button>
</div>
) : null}
onCancel={onClose}
title={
<div className="flex min-w-0 items-center gap-3 pr-8">
@@ -111,7 +119,7 @@ function AnnouncementTimelineItem({ announcement, last, reducedMotion }: { annou
{announcement.pinned ? <span className="inline-flex items-center gap-1 text-[var(--fs-label)] font-medium text-amber-500"><Pin className="size-3" />置顶</span> : null}
</div>
{announcement.imageUrl ? <AntImage src={announcementImageUrl(announcement)} alt={`${announcement.title} 配图`} preview className="mt-3 max-h-36 w-56 max-w-full rounded-lg border border-border/70 bg-muted/20 object-contain p-1" /> : null}
<AnnouncementContent content={announcement.content} className="mt-1 text-sm leading-6 text-foreground/75 sm:text-[var(--fs-body-lg)]" />
{announcement.content ? <AnnouncementContent content={announcement.content} className="mt-1 text-sm leading-6 text-foreground/75 sm:text-[var(--fs-body-lg)]" /> : null}
<time dateTime={announcement.publishedAt} className="mt-2 block text-xs tabular-nums text-foreground/40">{relativeTime(announcement.publishedAt)} · {formatDateTime(announcement.publishedAt)}</time>
</div>
</motion.article>