| import { |
| BugOutlined, |
| DatabaseOutlined, |
| DashboardOutlined, |
| FileTextOutlined, |
| LineChartOutlined, |
| RobotOutlined, |
| SnippetsOutlined, |
| UnorderedListOutlined, |
| MessageOutlined, |
| SafetyCertificateOutlined, |
| PhoneOutlined, |
| } from '@ant-design/icons' |
| import { Layout, Menu, Typography, theme } from 'antd' |
| import type { MenuProps } from 'antd' |
| import { useMemo, useState } from 'react' |
| import { Link, Outlet, useLocation } from 'react-router-dom' |
|
|
| const { Header, Content, Sider } = Layout |
|
|
| const menuItems: MenuProps['items'] = [ |
| { |
| key: '/dashboard', |
| icon: <DashboardOutlined />, |
| label: <Link to="/dashboard">健康概览</Link>, |
| }, |
| { |
| key: '/tasks', |
| icon: <UnorderedListOutlined />, |
| label: <Link to="/tasks">任务中心</Link>, |
| }, |
| { |
| key: '/resources', |
| icon: <DatabaseOutlined />, |
| label: '资源池', |
| children: [ |
| { |
| key: '/resources/accounts', |
| label: <Link to="/resources/accounts">账号池</Link>, |
| }, |
| { |
| key: '/resources/sessions', |
| label: <Link to="/resources/sessions">会话池</Link>, |
| }, |
| { |
| key: '/resources/proxies', |
| label: <Link to="/resources/proxies">代理池</Link>, |
| }, |
| ], |
| }, |
| { |
| key: '/errors', |
| icon: <BugOutlined />, |
| label: <Link to="/errors">错误中心</Link>, |
| }, |
| { |
| key: '/content/raw-notes', |
| icon: <FileTextOutlined />, |
| label: <Link to="/content/raw-notes">原始快照</Link>, |
| }, |
| { |
| key: '/content/cleaned-notes', |
| icon: <SnippetsOutlined />, |
| label: <Link to="/content/cleaned-notes">清洗笔记库</Link>, |
| }, |
| { |
| key: '/ai/generation', |
| icon: <MessageOutlined />, |
| label: <Link to="/ai/generation">AI 生产内容</Link>, |
| }, |
| { |
| key: '/compliance/review', |
| icon: <SafetyCertificateOutlined />, |
| label: <Link to="/compliance/review">合规检测</Link>, |
| }, |
| { |
| key: '/leads', |
| icon: <PhoneOutlined />, |
| label: <Link to="/leads">线索转化池</Link>, |
| }, |
| { |
| key: '/rpa', |
| icon: <RobotOutlined />, |
| label: <Link to="/rpa">RPA 兜底</Link>, |
| }, |
| { |
| key: '/metrics', |
| icon: <LineChartOutlined />, |
| label: <Link to="/metrics">监控指标</Link>, |
| }, |
| ] |
|
|
| export default function AppLayout() { |
| const location = useLocation() |
| const { |
| token: { colorBgContainer, borderRadiusLG, colorBorderSecondary }, |
| } = theme.useToken() |
|
|
| const selectedKeys = useMemo(() => { |
| if (location.pathname.startsWith('/tasks')) return ['/tasks'] |
| if (location.pathname.startsWith('/errors')) return ['/errors'] |
| return [location.pathname] |
| }, [location.pathname]) |
|
|
| const defaultOpenKeys = useMemo(() => { |
| if (location.pathname.startsWith('/resources')) return ['/resources'] |
| return [] |
| }, [location.pathname]) |
|
|
| const [openKeys, setOpenKeys] = useState(defaultOpenKeys) |
|
|
| const onOpenChange = (keys: string[]) => { |
| setOpenKeys(keys) |
| } |
|
|
| return ( |
| <Layout style={{ minHeight: '100vh' }}> |
| <Sider |
| breakpoint="lg" |
| collapsedWidth={56} |
| style={{ borderRight: `1px solid ${colorBorderSecondary}` }} |
| > |
| <div |
| style={{ |
| height: 56, |
| display: 'flex', |
| alignItems: 'center', |
| paddingInline: 16, |
| }} |
| > |
| <Typography.Text strong style={{ color: '#fff' }}> |
| Spider XHS |
| </Typography.Text> |
| </div> |
| <Menu |
| theme="dark" |
| mode="inline" |
| selectedKeys={selectedKeys} |
| openKeys={openKeys} |
| onOpenChange={onOpenChange} |
| items={menuItems} |
| /> |
| </Sider> |
| <Layout> |
| <Header |
| style={{ |
| paddingInline: 16, |
| display: 'flex', |
| alignItems: 'center', |
| background: colorBgContainer, |
| borderBottom: `1px solid ${colorBorderSecondary}`, |
| }} |
| > |
| <Typography.Text>Ops Console</Typography.Text> |
| </Header> |
| <Content style={{ padding: 16 }}> |
| <div |
| style={{ |
| background: colorBgContainer, |
| padding: 16, |
| minHeight: 280, |
| borderRadius: borderRadiusLG, |
| }} |
| > |
| <Outlet /> |
| </div> |
| </Content> |
| </Layout> |
| </Layout> |
| ) |
| } |
|
|