style: 样式优化

This commit is contained in:
2026-06-14 16:31:23 +08:00
parent fe053f94dc
commit 7a0443ebcc
8 changed files with 902 additions and 164 deletions

View File

@@ -1,17 +1,18 @@
// ============================================================
// SessionSidebar — 会话历史侧边栏
// 职责:展示会话列表、新建/切换/删除/重命名会话
// SessionSidebar — 会话历史侧边栏Overlay 抽屉式)
// 职责:展示会话列表、搜索、新建/切换/删除/重命名会话
// 设计overlay 覆盖在主界面之上,不挤压主界面空间
// ============================================================
import { useState } from "react";
import { useMemo, useState } from "react";
import { useI18n } from "../../lib/i18n";
import type { SessionSummary } from "../../types";
interface SessionSidebarProps {
sessions: SessionSummary[];
activeSessionId: string | null;
collapsed: boolean;
onToggleCollapse: () => void;
open: boolean;
onToggle: () => void;
onNewSession: () => void;
onSelectSession: (id: string) => void;
onDeleteSession: (id: string) => void;
@@ -35,11 +36,27 @@ function formatRelativeTime(ts: number): string {
return `${d.getMonth() + 1}/${d.getDate()}`;
}
/** 判断两个时间戳是否为同一天 */
function isSameDay(ts: number, ref: Date): boolean {
const d = new Date(ts);
return d.getFullYear() === ref.getFullYear() &&
d.getMonth() === ref.getMonth() &&
d.getDate() === ref.getDate();
}
type TimeGroup = "today" | "yesterday" | "earlier";
function getTimeGroup(ts: number, today: Date, yesterday: Date): TimeGroup {
if (isSameDay(ts, today)) return "today";
if (isSameDay(ts, yesterday)) return "yesterday";
return "earlier";
}
export function SessionSidebar({
sessions,
activeSessionId,
collapsed,
onToggleCollapse,
open,
onToggle,
onNewSession,
onSelectSession,
onDeleteSession,
@@ -48,6 +65,7 @@ export function SessionSidebar({
const { t } = useI18n();
const [editingId, setEditingId] = useState<string | null>(null);
const [editTitle, setEditTitle] = useState("");
const [searchQuery, setSearchQuery] = useState("");
const handleStartRename = (id: string, currentTitle: string) => {
setEditingId(id);
@@ -67,94 +85,151 @@ export function SessionSidebar({
setEditTitle("");
};
if (collapsed) {
return (
<div className="sidebar sidebar--collapsed">
<button className="sidebar__toggle" onClick={onToggleCollapse} title={t("sidebar.expand")}>
</button>
<button className="sidebar__new-btn" onClick={onNewSession} title={t("sidebar.new")}>
</button>
</div>
);
}
const handleSelect = (id: string) => {
onSelectSession(id);
// 选择后自动收起侧边栏
onToggle();
};
// 过滤 + 分组
const grouped = useMemo(() => {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
const filtered = searchQuery.trim()
? sessions.filter((s) =>
s.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
s.preview.toLowerCase().includes(searchQuery.toLowerCase())
)
: sessions;
const groups: Record<TimeGroup, SessionSummary[]> = {
today: [],
yesterday: [],
earlier: [],
};
for (const session of filtered) {
const group = getTimeGroup(session.lastActiveAt, today, yesterday);
groups[group].push(session);
}
return groups;
}, [sessions, searchQuery]);
if (!open) return null;
const groupLabels: Record<TimeGroup, string> = {
today: t("sidebar.today"),
yesterday: t("sidebar.yesterday"),
earlier: t("sidebar.earlier"),
};
return (
<div className="sidebar">
<div className="sidebar__header">
<button className="sidebar__new-btn sidebar__new-btn--full" onClick={onNewSession}>
{t("sidebar.new")}
</button>
<button className="sidebar__toggle" onClick={onToggleCollapse} title={t("sidebar.collapse")}>
</button>
</div>
<>
<div className="sidebar-backdrop" onClick={onToggle} />
<div className="sidebar">
{/* 头部:新建 + 收起 */}
<div className="sidebar__header">
<button className="sidebar__new-btn" onClick={onNewSession}>
{t("sidebar.new")}
</button>
<button className="sidebar__toggle" onClick={onToggle} title={t("sidebar.collapse")}>
</button>
</div>
<div className="sidebar__list">
{sessions.length === 0 ? (
<div className="sidebar__empty">{t("sidebar.empty")}</div>
) : (
sessions.map((session) => (
<div
key={session.id}
className={`sidebar__item ${session.id === activeSessionId ? "sidebar__item--active" : ""}`}
onClick={() => onSelectSession(session.id)}
>
{editingId === session.id ? (
<div className="sidebar__edit" onClick={(e) => e.stopPropagation()}>
<input
className="sidebar__edit-input"
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") handleConfirmRename();
if (e.key === "Escape") handleCancelRename();
}}
autoFocus
/>
<button className="sidebar__edit-btn" onClick={handleConfirmRename}></button>
<button className="sidebar__edit-btn" onClick={handleCancelRename}></button>
</div>
) : (
<>
<div className="sidebar__item-content">
<div className="sidebar__item-title">{session.title}</div>
<div className="sidebar__item-meta">
{session.messageCount > 0 && (
<span>{session.messageCount}{t("sidebar.messages")}</span>
{/* 搜索栏 */}
<div className="sidebar__search">
<input
className="sidebar__search-input"
type="text"
placeholder={t("sidebar.search")}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
{/* 会话列表(按时间分组) */}
<div className="sidebar__list">
{sessions.length === 0 ? (
<div className="sidebar__empty">{t("sidebar.empty")}</div>
) : (
(["today", "yesterday", "earlier"] as TimeGroup[]).map((groupKey) => {
const items = grouped[groupKey];
if (items.length === 0) return null;
return (
<div key={groupKey}>
<div className="sidebar__group">{groupLabels[groupKey]}</div>
{items.map((session) => (
<div
key={session.id}
className={`sidebar__item ${session.id === activeSessionId ? "sidebar__item--active" : ""}`}
onClick={() => handleSelect(session.id)}
>
{editingId === session.id ? (
<div className="sidebar__edit" onClick={(e) => e.stopPropagation()}>
<input
className="sidebar__edit-input"
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") handleConfirmRename();
if (e.key === "Escape") handleCancelRename();
}}
autoFocus
/>
<button className="sidebar__edit-btn" onClick={handleConfirmRename}></button>
<button className="sidebar__edit-btn" onClick={handleCancelRename}></button>
</div>
) : (
<>
{/* 视频标记图标 */}
{session.messageCount > 5 && (
<span className="sidebar__item-icon" title={t("sidebar.video")}>📹</span>
)}
<div className="sidebar__item-content">
<div className="sidebar__item-title">{session.title}</div>
<div className="sidebar__item-meta">
{session.messageCount > 0 && (
<span>{session.messageCount}{t("sidebar.messages")}</span>
)}
<span>{formatRelativeTime(session.lastActiveAt)}</span>
</div>
</div>
<div className="sidebar__item-actions">
<button
className="sidebar__action-btn"
onClick={(e) => {
e.stopPropagation();
handleStartRename(session.id, session.title);
}}
title={t("sidebar.rename")}
>
</button>
<button
className="sidebar__action-btn sidebar__action-btn--danger"
onClick={(e) => {
e.stopPropagation();
onDeleteSession(session.id);
}}
title={t("sidebar.delete")}
>
🗑
</button>
</div>
</>
)}
<span>{formatRelativeTime(session.lastActiveAt)}</span>
</div>
</div>
<div className="sidebar__item-actions">
<button
className="sidebar__action-btn"
onClick={(e) => {
e.stopPropagation();
handleStartRename(session.id, session.title);
}}
title={t("sidebar.rename")}
>
</button>
<button
className="sidebar__action-btn sidebar__action-btn--danger"
onClick={(e) => {
e.stopPropagation();
onDeleteSession(session.id);
}}
title={t("sidebar.delete")}
>
🗑
</button>
</div>
</>
)}
</div>
))
)}
))}
</div>
);
})
)}
</div>
</div>
</div>
</>
);
}