- 新增审计报告 docs/architecture/audit/announcements-messages-audit-report.md - 新增中英双语 i18n 字典 announcements.json / messages.json(11/13 个命名空间) - 重构所有 announcements 和 messaging 组件接入 next-intl(useTranslations) - 所有页面 page.tsx 使用 generateMetadata + getTranslations 替代硬编码 metadata - 新增 7 个 error.tsx 错误边界(4 公告 + 3 消息),统一 EmptyState + i18n + 重试 - a11y 改进:announcement-card / message-list / notification-dropdown 添加 aria-label - 同步架构图 004 和 005:i18n.messages 清单 + 已知问题修复记录
65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
"use client"
|
|
|
|
import Link from "next/link"
|
|
import { useTranslations } from "next-intl"
|
|
|
|
import { Badge } from "@/shared/components/ui/badge"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
|
import { formatDate } from "@/shared/lib/utils"
|
|
import type { Announcement } from "../types"
|
|
|
|
export function AnnouncementCard({
|
|
announcement,
|
|
href,
|
|
}: {
|
|
announcement: Announcement
|
|
href?: string
|
|
}) {
|
|
const t = useTranslations("announcements")
|
|
|
|
const statusVariant: Record<Announcement["status"], "default" | "secondary" | "outline"> = {
|
|
draft: "secondary",
|
|
published: "default",
|
|
archived: "outline",
|
|
}
|
|
|
|
const card = (
|
|
<Card className="h-full transition-colors hover:bg-accent/50">
|
|
<CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0">
|
|
<CardTitle className="line-clamp-2 text-base">{announcement.title}</CardTitle>
|
|
<Badge variant={statusVariant[announcement.status]} className="shrink-0">
|
|
{t(`status.${announcement.status}`)}
|
|
</Badge>
|
|
</CardHeader>
|
|
<CardContent className="space-y-2">
|
|
<p className="line-clamp-3 text-sm text-muted-foreground whitespace-pre-wrap">
|
|
{announcement.content}
|
|
</p>
|
|
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
|
<Badge variant="outline" className="capitalize">
|
|
{t(`type.${announcement.type}`)}
|
|
</Badge>
|
|
<span>
|
|
{announcement.publishedAt
|
|
? t("meta.publishedAt", { date: formatDate(announcement.publishedAt) })
|
|
: t("meta.updatedAt", { date: formatDate(announcement.updatedAt) })}
|
|
</span>
|
|
{announcement.authorName ? (
|
|
<span className="ml-auto">{t("meta.author", { name: announcement.authorName })}</span>
|
|
) : null}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
|
|
if (href) {
|
|
return (
|
|
<Link href={href} className="block h-full" aria-label={announcement.title}>
|
|
{card}
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
return card
|
|
}
|