refactor(web): 优化侧边栏与邮件展示交互

- 关闭移动端侧边栏在路由切换后的残留打开状态。
- 补充 `SheetContent` 的可访问性描述配置,避免无效告警。
- 统一邮件收件人渲染的空值处理,防止数组为空时异常。
- 渲染纯文本邮件内容时转义 HTML,提升展示安全性。
This commit is contained in:
LanQin
2026-06-20 23:52:59 +08:00
parent cee9f7aaa0
commit a8513120e5
5 changed files with 17 additions and 6 deletions
@@ -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)
} }
+1
View File
@@ -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>
+3 -3
View File
@@ -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>
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -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>