Files
chat-one-web/src/pages/index.tsx
2026-04-03 21:17:42 +08:00

187 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useMemo, useState } from "react";
import { Button, Drawer, Input, Layout, Space, Typography } from "antd";
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
MessageOutlined,
SettingOutlined,
UserOutlined,
} from "@ant-design/icons";
const { Header, Sider, Content } = Layout;
const MOBILE_WIDTH = 750;
const mockMessages = [
{ role: "assistant", content: "你好,我是 ChatOne 助手,有什么可以帮你?" },
{ role: "user", content: "请帮我总结今天的工作内容。" },
{ role: "assistant", content: "当然可以,请先告诉我今天完成了哪些任务。" },
];
export default function HomePage() {
// 桌面端侧边栏折叠状态(移动端不使用该状态)
const [collapsed, setCollapsed] = useState(false);
// 移动端抽屉侧边栏显示状态
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
// 当前视口宽度(用于调试响应式是否生效)
const [viewportWidth, setViewportWidth] = useState(0);
// 是否为移动端(小于 750px
const [isMobile, setIsMobile] = useState(false);
const [inputValue, setInputValue] = useState("");
const menuItems = useMemo(
() => [
{ icon: <MessageOutlined />, label: "新建会话" },
{ icon: <UserOutlined />, label: "我的对话" },
{ icon: <SettingOutlined />, label: "设置" },
],
[],
);
useEffect(() => {
const updateIsMobile = () => {
// 兼容不同浏览器/缩放场景下的视口宽度读取
const viewportWidth = Math.min(
window.innerWidth,
document.documentElement.clientWidth || window.innerWidth,
);
setViewportWidth(viewportWidth);
const mobile = viewportWidth < MOBILE_WIDTH;
setIsMobile(mobile);
if (!mobile) {
// 切回桌面端时关闭移动抽屉,避免状态残留
setMobileSidebarOpen(false);
}
};
updateIsMobile();
window.addEventListener("resize", updateIsMobile);
window.addEventListener("orientationchange", updateIsMobile);
return () => {
window.removeEventListener("resize", updateIsMobile);
window.removeEventListener("orientationchange", updateIsMobile);
};
}, []);
const sidebarContent = (
<>
{/* 侧边栏头部:桌面折叠时显示缩写,其他情况显示完整标题 */}
<div className="h-14 border-b border-gray-100 px-4 flex items-center">
<Typography.Title level={5} style={{ margin: 0 }}>
{collapsed && !isMobile ? "CO" : "ChatOne"}
</Typography.Title>
</div>
<div className="px-2 py-3">
<Space orientation="vertical" className="w-full">
{menuItems.map((item) => (
<Button key={item.label} type="text" className="w-full text-left!">
<Space>
{item.icon}
{/* 桌面折叠时隐藏文字;移动端抽屉内始终显示文字 */}
{(!collapsed || isMobile) && <span>{item.label}</span>}
</Space>
</Button>
))}
</Space>
</div>
</>
);
return (
<Layout style={{ minHeight: "100vh" }}>
{/* 桌面端固定侧边栏;移动端改为 Drawer 抽屉 */}
{!isMobile && (
<Sider trigger={null} collapsible collapsed={collapsed} theme="light">
{sidebarContent}
</Sider>
)}
<Layout>
<Header className="bg-white! px-4! border-b! border-gray-100! flex items-center justify-between">
<Button
type="text"
icon={
isMobile ? (
<MenuUnfoldOutlined />
) : collapsed ? (
<MenuUnfoldOutlined />
) : (
<MenuFoldOutlined />
)
}
onClick={() => {
if (isMobile) {
// 移动端点击按钮:打开左侧抽屉
setMobileSidebarOpen(true);
} else {
// 桌面端点击按钮:切换侧边栏折叠
setCollapsed((v) => !v);
}
}}
/>
<Space size={12}>
<Typography.Text type="secondary">ChatOne Web</Typography.Text>
<Typography.Text type="secondary">
{`W:${viewportWidth}px / ${isMobile ? "mobile" : "desktop"}`}
</Typography.Text>
</Space>
</Header>
<Content className="bg-gray-50 p-6">
{/* 聊天窗口:消息列表 + 底部输入区 */}
<div className="h-[calc(100vh-112px)] rounded-lg bg-white border border-gray-100 flex flex-col">
<div className="border-b border-gray-100 px-5 py-3">
<Typography.Title level={5} style={{ margin: 0 }}>
</Typography.Title>
</div>
<div className="flex-1 overflow-auto px-5 py-4 flex flex-col gap-2">
{mockMessages.map((item, index) => (
<div
key={index}
className={`flex ${item.role === "user" ? "justify-end" : "justify-start"}`}
>
<div
className={`max-w-[80%] rounded-lg px-3 py-2 ${
item.role === "user"
? "bg-blue-50 border border-blue-100"
: "bg-gray-50 border border-gray-100"
}`}
>
<Typography.Text>{item.content}</Typography.Text>
</div>
</div>
))}
</div>
<div className="border-t border-gray-100 p-4">
<Space.Compact className="w-full">
<Input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="输入消息..."
/>
<Button type="primary"></Button>
</Space.Compact>
</div>
</div>
</Content>
</Layout>
<Drawer
title="ChatOne"
placement="left"
closable
onClose={() => setMobileSidebarOpen(false)}
open={isMobile && mobileSidebarOpen}
size={260}
>
{sidebarContent}
{sidebarContent}
</Drawer>
</Layout>
);
}