refactor(web): 优化侧边栏与邮件展示交互
- 关闭移动端侧边栏在路由切换后的残留打开状态。 - 补充 `SheetContent` 的可访问性描述配置,避免无效告警。 - 统一邮件收件人渲染的空值处理,防止数组为空时异常。 - 渲染纯文本邮件内容时转义 HTML,提升展示安全性。
This commit is contained in:
@@ -133,6 +133,16 @@ function ProtectedContent() {
|
|||||||
|
|
||||||
function AdminSectionItems({ activeSection }: { activeSection: string }) {
|
function AdminSectionItems({ activeSection }: { activeSection: string }) {
|
||||||
const { isMobile, setOpenMobile } = useSidebar()
|
const { isMobile, setOpenMobile } = useSidebar()
|
||||||
|
const location = useLocation()
|
||||||
|
const locationKey = `${location.pathname}${location.search}`
|
||||||
|
const previousLocationKey = React.useRef(locationKey)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const previous = previousLocationKey.current
|
||||||
|
previousLocationKey.current = locationKey
|
||||||
|
if (isMobile && previous !== locationKey) setOpenMobile(false)
|
||||||
|
}, [isMobile, locationKey, setOpenMobile])
|
||||||
|
|
||||||
function closeMobile() {
|
function closeMobile() {
|
||||||
if (isMobile) setOpenMobile(false)
|
if (isMobile) setOpenMobile(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ const Sidebar = React.forwardRef<
|
|||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
side={side}
|
side={side}
|
||||||
|
aria-describedby={undefined}
|
||||||
>
|
>
|
||||||
<SheetHeader className="sr-only">
|
<SheetHeader className="sr-only">
|
||||||
<SheetTitle>导航</SheetTitle>
|
<SheetTitle>导航</SheetTitle>
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ function AdminMessagesSection({ mailboxes }: { mailboxes: MailboxType[] }) {
|
|||||||
<div className="mt-3 space-y-2 text-sm">
|
<div className="mt-3 space-y-2 text-sm">
|
||||||
<div className="truncate text-muted-foreground">邮箱:{message.mailboxAddress || message.recipientAddress || "-"}</div>
|
<div className="truncate text-muted-foreground">邮箱:{message.mailboxAddress || message.recipientAddress || "-"}</div>
|
||||||
<div className="truncate text-muted-foreground">发件人:{adminSenderDisplayName(message)}</div>
|
<div className="truncate text-muted-foreground">发件人:{adminSenderDisplayName(message)}</div>
|
||||||
<div className="truncate text-muted-foreground">收件人:{message.recipientAddress || message.to.join(", ")}</div>
|
<div className="truncate text-muted-foreground">收件人:{message.recipientAddress || message.to?.join(", ") || ""}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 flex flex-wrap gap-2">
|
<div className="mt-3 flex flex-wrap gap-2">
|
||||||
<Badge variant="secondary">{folderName(message.folder)}</Badge>
|
<Badge variant="secondary">{folderName(message.folder)}</Badge>
|
||||||
@@ -506,7 +506,7 @@ function AdminMessagesSection({ mailboxes }: { mailboxes: MailboxType[] }) {
|
|||||||
{message.ownerEmail && <div className="text-xs text-muted-foreground">{message.ownerEmail}</div>}
|
{message.ownerEmail && <div className="text-xs text-muted-foreground">{message.ownerEmail}</div>}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="max-w-[220px] truncate" title={adminSenderTitle(message)}>{adminSenderDisplayName(message)}</TableCell>
|
<TableCell className="max-w-[220px] truncate" title={adminSenderTitle(message)}>{adminSenderDisplayName(message)}</TableCell>
|
||||||
<TableCell className="max-w-[220px] truncate">{message.recipientAddress || message.to.join(", ")}</TableCell>
|
<TableCell className="max-w-[220px] truncate">{message.recipientAddress || message.to?.join(", ") || ""}</TableCell>
|
||||||
<TableCell><Badge variant="secondary">{folderName(message.folder)}</Badge></TableCell>
|
<TableCell><Badge variant="secondary">{folderName(message.folder)}</Badge></TableCell>
|
||||||
<TableCell className="text-muted-foreground">{formatDate(message.receivedAt)}</TableCell>
|
<TableCell className="text-muted-foreground">{formatDate(message.receivedAt)}</TableCell>
|
||||||
<TableCell><Button variant="ghost" size="sm" onClick={() => setSelectedId(message.id)}>查看</Button></TableCell>
|
<TableCell><Button variant="ghost" size="sm" onClick={() => setSelectedId(message.id)}>查看</Button></TableCell>
|
||||||
@@ -848,7 +848,7 @@ function AdminMessageDialog({ message, loading, open, onOpenChange }: { message?
|
|||||||
<MessageMeta label="所属邮箱" value={message.mailboxAddress || message.recipientAddress || ""} />
|
<MessageMeta label="所属邮箱" value={message.mailboxAddress || message.recipientAddress || ""} />
|
||||||
<MessageMeta label="所属用户" value={message.ownerEmail || ""} />
|
<MessageMeta label="所属用户" value={message.ownerEmail || ""} />
|
||||||
<MessageMeta label="发件人" value={adminSenderTitle(message)} />
|
<MessageMeta label="发件人" value={adminSenderTitle(message)} />
|
||||||
<MessageMeta label="收件人" value={message.recipientAddress || message.to.join(", ")} />
|
<MessageMeta label="收件人" value={message.recipientAddress || message.to?.join(", ") || ""} />
|
||||||
<MessageMeta label="文件夹" value={folderName(message.folder)} />
|
<MessageMeta label="文件夹" value={folderName(message.folder)} />
|
||||||
<MessageMeta label="时间" value={formatDate(message.receivedAt)} />
|
<MessageMeta label="时间" value={formatDate(message.receivedAt)} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -737,7 +737,7 @@ export function MailPage() {
|
|||||||
</div>
|
</div>
|
||||||
<ScrollArea className="min-h-0 flex-1">
|
<ScrollArea className="min-h-0 flex-1">
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="mail-html prose max-w-none text-sm leading-7" dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(selected.bodyHtml || `<pre>${selected.bodyText || ""}</pre>`) }} />
|
<div className="mail-html prose max-w-none text-sm leading-7" dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(selected.bodyHtml || `<pre>${escapeHtml(selected.bodyText || "")}</pre>`) }} />
|
||||||
{selected.attachments && selected.attachments.length > 0 && <div className="mt-8 rounded-lg border p-4"><div className="mb-3 font-medium">附件</div><div className="space-y-2">{selected.attachments.map((a) => <a className="flex items-center justify-between rounded-md border p-3 text-sm hover:bg-accent" href={`/api/mail/attachments/${a.id}`} key={a.id}><span className="flex items-center gap-2"><Paperclip className="h-4 w-4" />{a.filename}</span><span className="text-muted-foreground">{formatBytes(a.sizeBytes)}</span></a>)}</div></div>}
|
{selected.attachments && selected.attachments.length > 0 && <div className="mt-8 rounded-lg border p-4"><div className="mb-3 font-medium">附件</div><div className="space-y-2">{selected.attachments.map((a) => <a className="flex items-center justify-between rounded-md border p-3 text-sm hover:bg-accent" href={`/api/mail/attachments/${a.id}`} key={a.id}><span className="flex items-center gap-2"><Paperclip className="h-4 w-4" />{a.filename}</span><span className="text-muted-foreground">{formatBytes(a.sizeBytes)}</span></a>)}</div></div>}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
@@ -758,7 +758,7 @@ export function MailPage() {
|
|||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button size="icon" variant="ghost" aria-label="打开导航"><PanelLeftOpen className="h-4 w-4" /></Button>
|
<Button size="icon" variant="ghost" aria-label="打开导航"><PanelLeftOpen className="h-4 w-4" /></Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent side="left" className="w-[86vw] max-w-80 p-0 [&>button]:hidden">
|
<SheetContent side="left" className="w-[86vw] max-w-80 p-0 [&>button]:hidden" aria-describedby={undefined}>
|
||||||
<SheetTitle className="sr-only">邮箱导航</SheetTitle>
|
<SheetTitle className="sr-only">邮箱导航</SheetTitle>
|
||||||
<div className="h-[100dvh]">{sidebarContent}</div>
|
<div className="h-[100dvh]">{sidebarContent}</div>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ export function ProfilePage() {
|
|||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button size="icon" variant="ghost" aria-label="打开导航"><PanelLeftOpen className="h-4 w-4" /></Button>
|
<Button size="icon" variant="ghost" aria-label="打开导航"><PanelLeftOpen className="h-4 w-4" /></Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent side="left" className="w-[86vw] max-w-80 p-0 [&>button]:hidden">
|
<SheetContent side="left" className="w-[86vw] max-w-80 p-0 [&>button]:hidden" aria-describedby={undefined}>
|
||||||
<SheetTitle className="sr-only">个人中心导航</SheetTitle>
|
<SheetTitle className="sr-only">个人中心导航</SheetTitle>
|
||||||
<div className="h-[100dvh]">{sidebarContent}</div>
|
<div className="h-[100dvh]">{sidebarContent}</div>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user