feat: 用自定义侧边栏替换聊天室 Conversations 组件,提取公共 SidebarGroup 和 date-group

This commit is contained in:
2026-06-04 00:46:57 +08:00
parent dc7d9e83b8
commit f67cfa84ef
18 changed files with 1042 additions and 262 deletions

View File

@@ -3,8 +3,6 @@ import { Button, Flex, Popconfirm, Tag, Typography } from "antd";
import type { Material, MaterialStatus } from "../types";
import { formatDateLabel } from "../../../shared/utils/time";
interface MaterialCardProps {
material: Material;
onDelete: () => void;
@@ -12,11 +10,6 @@ interface MaterialCardProps {
selected: boolean;
}
function formatAssociatedDate(date: string): string {
if (!date) return "—";
return formatDateLabel(date);
}
const STATUS_MAP: Record<MaterialStatus, { color: string; label: string }> = {
approved: { color: "green", label: "已通过" },
discarded: { color: "red", label: "已放弃" },
@@ -29,15 +22,13 @@ export function MaterialCard({ material, onDelete, onSelect, selected }: Materia
return (
<Flex align="center" className={className} gap="small" justify="space-between" onClick={onSelect}>
<div style={{ flex: 1, minWidth: 0 }}>
<Typography.Text ellipsis strong={selected}>
{material.description}
</Typography.Text>
<br />
<Typography.Text className="material-item-time" type="secondary">
{formatAssociatedDate(material.associatedDate)}
</Typography.Text>
</div>
<Typography.Paragraph
className="material-item-desc"
ellipsis={{ rows: 2 }}
style={{ flex: 1, margin: 0, minWidth: 0 }}
>
{material.description}
</Typography.Paragraph>
<div className="material-item-right">
<span className="material-item-tag">
{statusInfo && <Tag color={statusInfo.color}>{statusInfo.label}</Tag>}

View File

@@ -1,28 +0,0 @@
import { CaretDownOutlined, CaretRightOutlined } from "@ant-design/icons";
import { Typography } from "antd";
import { type ReactNode, useState } from "react";
interface MaterialGroupProps {
children: ReactNode;
count: number;
label: string;
}
export function MaterialGroup({ children, count, label }: MaterialGroupProps) {
const [collapsed, setCollapsed] = useState(false);
return (
<div className="app-inbox-group">
<div className="app-inbox-group-header" onClick={() => setCollapsed(!collapsed)}>
<span className="app-inbox-group-arrow">{collapsed ? <CaretRightOutlined /> : <CaretDownOutlined />}</span>
<Typography.Text className="app-inbox-group-label" type="secondary">
{label}
</Typography.Text>
<Typography.Text className="app-inbox-group-count" type="secondary">
({count})
</Typography.Text>
</div>
{!collapsed && <div className="app-inbox-group-content">{children}</div>}
</div>
);
}

View File

@@ -12,11 +12,10 @@ import { useMemo, useState } from "react";
import type { Material } from "../types";
import { SidebarGroup } from "../../../shared/components/SidebarGroup";
import { useIsDark } from "../../../shared/hooks/use-is-dark";
import { GROUP_LABELS, groupByDate } from "../../../shared/utils/date-group";
import { MaterialCard } from "./MaterialCard";
import { MaterialGroup } from "./MaterialGroup";
type DateGroup = "earlier" | "thisMonth" | "thisWeek" | "today" | "yesterday";
interface MaterialListProps {
loading: boolean;
@@ -27,55 +26,6 @@ interface MaterialListProps {
selectedId: null | string;
}
const GROUP_LABELS: Record<DateGroup, string> = {
earlier: "更早",
thisMonth: "本月",
thisWeek: "本周",
today: "今天",
yesterday: "昨天",
};
const GROUP_ORDER: readonly DateGroup[] = ["today", "yesterday", "thisWeek", "thisMonth", "earlier"];
interface MaterialGroupData {
items: Material[];
key: DateGroup;
}
function getDateGroup(dateStr: string, now: Date): DateGroup {
const date = new Date(dateStr);
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const yesterday = new Date(today.getTime() - 86_400_000);
const dateDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
if (dateDay.getTime() >= today.getTime()) return "today";
if (dateDay.getTime() >= yesterday.getTime()) return "yesterday";
const dayOfWeek = today.getDay();
const mondayOffset = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
const monday = new Date(today.getTime() - mondayOffset * 86_400_000);
if (dateDay.getTime() >= monday.getTime()) return "thisWeek";
if (dateDay.getFullYear() === today.getFullYear() && dateDay.getMonth() === today.getMonth()) {
return "thisMonth";
}
return "earlier";
}
function groupMaterialsByDate(materials: readonly Material[]): MaterialGroupData[] {
const now = new Date();
const groups = new Map<DateGroup, Material[]>();
for (const m of materials) {
const group = getDateGroup(m.createdAt, now);
if (!groups.has(group)) groups.set(group, []);
groups.get(group)!.push(m);
}
return GROUP_ORDER.map((key) => ({ items: groups.get(key) ?? [], key }));
}
const STATUS_FILTER_OPTIONS = [
{ icon: <AppstoreOutlined />, label: "全部", value: "all" },
{ color: "#faad14", icon: <ClockCircleOutlined />, label: "待审核", value: "pending" },
@@ -94,7 +44,7 @@ export function MaterialList({ loading, materials, onAddClick, onDelete, onSelec
return materials.filter((m) => m.status === filterStatus);
}, [materials, filterStatus]);
const groupedMaterials = useMemo(() => groupMaterialsByDate(filteredMaterials), [filteredMaterials]);
const groupedMaterials = useMemo(() => groupByDate(filteredMaterials, "createdAt"), [filteredMaterials]);
const segmentedOptions = useMemo(
() =>
@@ -111,15 +61,15 @@ export function MaterialList({ loading, materials, onAddClick, onDelete, onSelec
);
return (
<div className="app-inbox-sidebar">
<div className="app-inbox-sidebar-header">
<div className="app-sidebar-list" style={{ width: 260 }}>
<div className="app-sidebar-list-header">
<Button block icon={<PlusOutlined />} onClick={onAddClick} type="primary">
</Button>
<Segmented block onChange={(value) => setFilterStatus(value)} options={segmentedOptions} value={filterStatus} />
</div>
<OverlayScrollbarsComponent
className="app-inbox-list"
className="app-sidebar-list-body"
options={{
overflow: { x: "hidden", y: "scroll" },
scrollbars: {
@@ -138,7 +88,7 @@ export function MaterialList({ loading, materials, onAddClick, onDelete, onSelec
groupedMaterials.map((group) => {
if (group.items.length === 0) return null;
return (
<MaterialGroup count={group.items.length} key={group.key} label={GROUP_LABELS[group.key]}>
<SidebarGroup count={group.items.length} key={group.key} label={GROUP_LABELS[group.key]}>
{group.items.map((material) => (
<MaterialCard
key={material.id}
@@ -148,7 +98,7 @@ export function MaterialList({ loading, materials, onAddClick, onDelete, onSelec
selected={material.id === selectedId}
/>
))}
</MaterialGroup>
</SidebarGroup>
);
})
)}