ux: 对话历史页手机端优化(布局/按钮/卡片/气泡宽度)
CI / 后端测试 (push) Has been cancelled
CI / 前端构建 (push) Has been cancelled

This commit is contained in:
selfrelease
2026-08-31 19:51:22 +08:00
parent 9e79e17a35
commit 86f15bc79c
2 changed files with 22 additions and 23 deletions
+3 -3
View File
@@ -11,9 +11,9 @@ interface PageLayoutProps {
export function PageLayout({ title, description, actions, children, className }: PageLayoutProps) { export function PageLayout({ title, description, actions, children, className }: PageLayoutProps) {
return ( return (
<div className={cn('space-y-6', className)}> <div className={cn('space-y-4 md:space-y-6', className)}>
<div className="flex items-start justify-between gap-4"> <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-4">
<div> <div className="min-w-0">
<h2 className="text-xl font-bold text-foreground md:text-2xl">{title}</h2> <h2 className="text-xl font-bold text-foreground md:text-2xl">{title}</h2>
{description && <p className="mt-1 text-sm text-muted-foreground">{description}</p>} {description && <p className="mt-1 text-sm text-muted-foreground">{description}</p>}
</div> </div>
+19 -20
View File
@@ -183,8 +183,8 @@ export function ChatHistory() {
actions={ actions={
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button variant="outline" size="sm" onClick={handleBack}> <Button variant="outline" size="sm" onClick={handleBack}>
<ArrowLeft className="mr-1 h-4 w-4" /> <ArrowLeft className="h-4 w-4 sm:mr-1" />
返回列表 <span className="hidden sm:inline">返回列表</span>
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
@@ -192,19 +192,19 @@ export function ChatHistory() {
className="text-destructive hover:text-destructive" className="text-destructive hover:text-destructive"
onClick={() => setDeleteTarget(currentSession)} onClick={() => setDeleteTarget(currentSession)}
> >
<Trash2 className="mr-1 h-4 w-4" /> <Trash2 className="h-4 w-4 sm:mr-1" />
删除 <span className="hidden sm:inline">删除</span>
</Button> </Button>
</div> </div>
} }
> >
<Card> <Card>
<CardContent className="p-4"> <CardContent className="p-4">
<div className="mb-4 flex items-center gap-3 border-b pb-3"> <div className="mb-4 flex items-center gap-2 border-b pb-3 sm:gap-3">
<modeCfg.icon className="h-5 w-5 text-muted-foreground" /> <modeCfg.icon className="h-4 w-4 shrink-0 text-muted-foreground sm:h-5 sm:w-5" />
<div className="flex-1"> <div className="min-w-0 flex-1">
<p className="font-medium">{currentSession.title || '未命名对话'}</p> <p className="truncate font-medium">{currentSession.title || '未命名对话'}</p>
<div className="mt-0.5 flex flex-wrap items-center gap-2"> <div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1">
<span className="text-xs text-muted-foreground">{modeCfg.label}</span> <span className="text-xs text-muted-foreground">{modeCfg.label}</span>
{currentSession.scene && ( {currentSession.scene && (
<Badge variant="secondary" className="text-xs"> <Badge variant="secondary" className="text-xs">
@@ -212,8 +212,7 @@ export function ChatHistory() {
</Badge> </Badge>
)} )}
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
{formatTime(currentSession.first_message_at)} ~ {formatTime(currentSession.last_message_at)} {currentSession.message_count} 条 · {formatTime(currentSession.last_message_at)}
{' · '}{currentSession.message_count} 条消息
</span> </span>
</div> </div>
</div> </div>
@@ -291,13 +290,13 @@ export function ChatHistory() {
title="对话历史" title="对话历史"
description="普通模式可直接查看,保密模式需密码验证,阅后即焚不落库" description="普通模式可直接查看,保密模式需密码验证,阅后即焚不落库"
actions={ actions={
<div className="flex flex-wrap items-center gap-1 rounded-md border bg-card p-1"> <div className="flex items-center gap-1 self-start rounded-md border bg-card p-1 sm:self-auto">
{(['all', 'normal', 'confidential'] as const).map((m) => ( {(['all', 'normal', 'confidential'] as const).map((m) => (
<Button <Button
key={m} key={m}
variant={modeFilter === m ? 'default' : 'ghost'} variant={modeFilter === m ? 'default' : 'ghost'}
size="sm" size="sm"
className="h-7 text-xs" className="h-7 px-2.5 text-xs"
onClick={() => setModeFilter(m)} onClick={() => setModeFilter(m)}
> >
{m === 'all' ? '全部' : m === 'normal' ? '普通' : '保密'} {m === 'all' ? '全部' : m === 'normal' ? '普通' : '保密'}
@@ -354,13 +353,13 @@ export function ChatHistory() {
className="cursor-pointer transition-colors hover:bg-accent/50" className="cursor-pointer transition-colors hover:bg-accent/50"
onClick={() => handleView(session)} onClick={() => handleView(session)}
> >
<CardContent className="flex items-center gap-3 p-4"> <CardContent className="flex items-center gap-2 p-3 sm:gap-3 sm:p-4">
<div className={cn('flex h-10 w-10 items-center justify-center rounded-lg', modeCfg.color)}> <div className={cn('flex h-9 w-9 shrink-0 items-center justify-center rounded-lg sm:h-10 sm:w-10', modeCfg.color)}>
<modeCfg.icon className="h-5 w-5" /> <modeCfg.icon className="h-4 w-4 sm:h-5 sm:w-5" />
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{session.title || '未命名对话'}</p> <p className="truncate text-sm font-medium">{session.title || '未命名对话'}</p>
<div className="mt-0.5 flex flex-wrap items-center gap-2"> <div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5">
<span className="text-xs text-muted-foreground">{modeCfg.label}</span> <span className="text-xs text-muted-foreground">{modeCfg.label}</span>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
{session.message_count} 条 · {formatTime(session.last_message_at)} {session.message_count} 条 · {formatTime(session.last_message_at)}
@@ -374,7 +373,7 @@ export function ChatHistory() {
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" />
</button> </button>
<ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground" /> <ChevronRight className="hidden h-4 w-4 shrink-0 text-muted-foreground sm:block" />
</CardContent> </CardContent>
</Card> </Card>
) )
@@ -402,7 +401,7 @@ function MessageBubble({ message }: { message: HistoryMessage }) {
const riskLevel = message.risk?.level const riskLevel = message.risk?.level
return ( return (
<div className={cn('flex', isUser ? 'justify-end' : 'justify-start')}> <div className={cn('flex', isUser ? 'justify-end' : 'justify-start')}>
<div className={cn('max-w-[80%] rounded-lg p-3', isUser ? 'bg-primary text-primary-foreground' : 'bg-muted')}> <div className={cn('max-w-[92%] rounded-lg p-3 sm:max-w-[80%]', isUser ? 'bg-primary text-primary-foreground' : 'bg-muted')}>
{/* 消息正文:用户消息纯文本,AI 消息 Markdown 渲染 */} {/* 消息正文:用户消息纯文本,AI 消息 Markdown 渲染 */}
{isUser ? ( {isUser ? (
<p className="whitespace-pre-wrap text-sm">{message.content}</p> <p className="whitespace-pre-wrap text-sm">{message.content}</p>
@@ -440,7 +439,7 @@ function MessageBubble({ message }: { message: HistoryMessage }) {
{!isUser && ( {!isUser && (
<div className="mt-2 border-t border-border/30 pt-2"> <div className="mt-2 border-t border-border/30 pt-2">
<div className="mb-1 flex items-center gap-1 text-[10px] text-muted-foreground"> <div className="mb-1 flex items-center gap-1 text-[10px] text-muted-foreground">
<BookOpen className="h-3 w-3" /> 参考来源 <BookOpen className="h-3 w-3 shrink-0" /> 参考来源
</div> </div>
{message.meta?.rag_context && message.meta.rag_context.length > 0 ? ( {message.meta?.rag_context && message.meta.rag_context.length > 0 ? (
<div className="space-y-1"> <div className="space-y-1">