Split React client pages into modules
This commit is contained in:
87
apps/senlinai-acro-react/src/pages/login.tsx
Normal file
87
apps/senlinai-acro-react/src/pages/login.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { ReactElement } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Button, Card, Form, Input, Space, Typography } from '@arco-design/web-react'
|
||||
import { IconBook, IconCheckCircleFill, IconLaunch, IconSafe, IconUserGroup } from '@arco-design/web-react/icon'
|
||||
|
||||
const { Title, Text, Paragraph } = Typography
|
||||
|
||||
export function LoginPage({ onLogin }: { onLogin: () => void }) {
|
||||
const [server, setServer] = useState('https://10.0.0.15:8080')
|
||||
|
||||
return (
|
||||
<section className="login-screen">
|
||||
<Card className="login-card" bordered>
|
||||
<div className="login-brand-panel">
|
||||
<div className="brand-lockup large">
|
||||
<span className="brand-symbol">S</span>
|
||||
<Title heading={2}>SenlinAI</Title>
|
||||
</div>
|
||||
<Text className="login-slogan">私有部署 · 安全可控的智能协作平台</Text>
|
||||
<Text type="secondary">知识沉淀 · 团队协作 · AI 助理</Text>
|
||||
<Space className="login-footer-links" size={24}>
|
||||
<Text type="secondary">v1.0.0</Text>
|
||||
<Text type="secondary">隐私政策</Text>
|
||||
<Text type="secondary">服务协议</Text>
|
||||
<Text type="secondary">关于 SenlinAI</Text>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div className="login-form-panel">
|
||||
<Title heading={4}>登录到您的 SenlinAI</Title>
|
||||
<Text type="secondary">连接私有工作台</Text>
|
||||
<Form layout="vertical" className="login-form">
|
||||
<Form.Item label="服务器地址(IP 或域名优先)">
|
||||
<Input value={server} onChange={setServer} placeholder="例如:https://10.0.0.15:8080" suffix={<IconLaunch />} />
|
||||
</Form.Item>
|
||||
<Form.Item label="账号">
|
||||
<Input placeholder="请输入用户名或邮箱" />
|
||||
</Form.Item>
|
||||
<Form.Item label="密码">
|
||||
<Input.Password placeholder="请输入密码" />
|
||||
</Form.Item>
|
||||
<div className="login-row">
|
||||
<Space size={8}>
|
||||
<span className="check-dot" />
|
||||
<Text>记住服务器地址</Text>
|
||||
</Space>
|
||||
<Button type="text" size="mini">无法连接?</Button>
|
||||
</div>
|
||||
<Button type="primary" long size="large" onClick={onLogin}>
|
||||
登录
|
||||
</Button>
|
||||
<div className="connection-card">
|
||||
<IconCheckCircleFill />
|
||||
<div>
|
||||
<Text className="connection-title">连接正常</Text>
|
||||
<Text type="secondary">已连接到 {server}</Text>
|
||||
</div>
|
||||
<Button type="text" size="mini">更换服务器</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
<div className="login-value-panel">
|
||||
<Title heading={3}>安全 · 专注 · 高效</Title>
|
||||
<Paragraph>
|
||||
SenlinAI 是面向知识工作者与内部团队的私有部署工作台,帮助团队在统一空间中收集信息、整理知识、协同决策。
|
||||
</Paragraph>
|
||||
<ValuePoint icon={<IconSafe />} title="私有部署,数据自有" desc="所有数据与模型运行在您的环境中,安全可控。" />
|
||||
<ValuePoint icon={<IconBook />} title="知识沉淀,结构清晰" desc="从收集到整理,形成可复用的团队知识库。" />
|
||||
<ValuePoint icon={<IconUserGroup />} title="协同高效,聚焦成果" desc="围绕项目与任务协同,减少沟通成本,专注交付。" />
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function ValuePoint({ icon, title, desc }: { icon: ReactElement; title: string; desc: string }) {
|
||||
return (
|
||||
<div className="value-point">
|
||||
<span>{icon}</span>
|
||||
<div>
|
||||
<Text className="value-title">{title}</Text>
|
||||
<Text type="secondary">{desc}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
132
apps/senlinai-acro-react/src/pages/overview.tsx
Normal file
132
apps/senlinai-acro-react/src/pages/overview.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import { useMemo } from 'react'
|
||||
import { Button, Card, Grid, Space, Tag, Typography } from '@arco-design/web-react'
|
||||
import { IconCalendar, IconCheckCircleFill, IconClockCircle, IconEmail, IconFile, IconMore, IconRobot } from '@arco-design/web-react/icon'
|
||||
import { aiSessions, inbox, notes, tasks } from '../features/projects/project-data'
|
||||
|
||||
const { Row, Col } = Grid
|
||||
const { Title, Text } = Typography
|
||||
|
||||
export function OverviewPage({ onSelectItem }: { onSelectItem: (title: string) => void }) {
|
||||
const metrics = useMemo(() => [
|
||||
{ title: 'Inbox 待处理', value: 36, delta: '较昨日 -8', icon: <IconEmail />, color: 'arcoblue' },
|
||||
{ title: '进行中的任务', value: 12, delta: '较昨日 -2', icon: <IconCheckCircleFill />, color: 'green' },
|
||||
{ title: 'AI 会话活跃', value: 4, delta: '较昨日 +1', icon: <IconRobot />, color: 'purple' },
|
||||
{ title: '笔记资料总数', value: 343, delta: '较昨日 +5', icon: <IconFile />, color: 'cyan' },
|
||||
{ title: '计划任务', value: 45, delta: '较昨日 +0', icon: <IconClockCircle />, color: 'orange' },
|
||||
], [])
|
||||
|
||||
return (
|
||||
<div className="overview-page">
|
||||
<div className="overview-head">
|
||||
<div>
|
||||
<Text type="secondary">项目整体快照,帮助你快速了解项目动态</Text>
|
||||
<Title heading={4}>概况</Title>
|
||||
</div>
|
||||
<Space>
|
||||
<Button icon={<IconCalendar />}>2026-07-20</Button>
|
||||
<Button icon={<IconMore />} />
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Row gutter={12} className="metric-row">
|
||||
{metrics.map((metric) => (
|
||||
<Col span={24 / metrics.length} key={metric.title}>
|
||||
<Card className="metric-card" bordered>
|
||||
<Space align="start">
|
||||
<Tag color={metric.color}>{metric.icon}</Tag>
|
||||
<div>
|
||||
<Text type="secondary">{metric.title}</Text>
|
||||
<Title heading={3}>{metric.value}</Title>
|
||||
<Text className="metric-delta">{metric.delta}</Text>
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
<QueueSection title="Inbox 待处理(36)" items={inbox} onSelectItem={onSelectItem} />
|
||||
<TaskSection onSelectItem={onSelectItem} />
|
||||
<AISessionSection onSelectItem={onSelectItem} />
|
||||
<NoteSection onSelectItem={onSelectItem} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function QueueSection({ title, items, onSelectItem }: { title: string; items: typeof inbox; onSelectItem: (title: string) => void }) {
|
||||
return (
|
||||
<Card className="queue-section" bordered>
|
||||
<div className="section-header">
|
||||
<Title heading={6}>{title}</Title>
|
||||
<Button type="text" size="mini">查看全部</Button>
|
||||
</div>
|
||||
{items.map((item) => (
|
||||
<button key={item.title} className="data-row" onClick={() => onSelectItem(item.title)}>
|
||||
<span className="row-title"><IconFile /> {item.title}</span>
|
||||
<span>{item.meta}</span>
|
||||
<Tag>{item.tag}</Tag>
|
||||
<span>{item.owner}</span>
|
||||
<time>{item.time}</time>
|
||||
</button>
|
||||
))}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function TaskSection({ onSelectItem }: { onSelectItem: (title: string) => void }) {
|
||||
return (
|
||||
<Card className="queue-section" bordered>
|
||||
<div className="section-header">
|
||||
<Title heading={6}>进行中的任务(12)</Title>
|
||||
<Button type="text" size="mini">查看全部</Button>
|
||||
</div>
|
||||
{tasks.map((task) => (
|
||||
<button key={task.title} className="data-row task-row" onClick={() => onSelectItem(task.title)}>
|
||||
<span className="row-title"><IconCheckCircleFill /> {task.title}</span>
|
||||
<Tag color="arcoblue">{task.tag}</Tag>
|
||||
<span>进度 {task.progress}</span>
|
||||
<span>负责人 {task.owner}</span>
|
||||
<time>截止 {task.due}</time>
|
||||
</button>
|
||||
))}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function AISessionSection({ onSelectItem }: { onSelectItem: (title: string) => void }) {
|
||||
return (
|
||||
<Card className="queue-section" bordered>
|
||||
<div className="section-header">
|
||||
<Title heading={6}>AI 会话(4)</Title>
|
||||
<Button type="text" size="mini">查看全部</Button>
|
||||
</div>
|
||||
{aiSessions.map((session) => (
|
||||
<button key={session.title} className="data-row ai-row" onClick={() => onSelectItem(session.title)}>
|
||||
<span className="row-title"><IconRobot /> {session.title}</span>
|
||||
<span>{session.summary}</span>
|
||||
<span>{session.owner}</span>
|
||||
<time>{session.time}</time>
|
||||
</button>
|
||||
))}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function NoteSection({ onSelectItem }: { onSelectItem: (title: string) => void }) {
|
||||
return (
|
||||
<Card className="queue-section" bordered>
|
||||
<div className="section-header">
|
||||
<Title heading={6}>最近笔记资料(5)</Title>
|
||||
<Button type="text" size="mini">查看全部</Button>
|
||||
</div>
|
||||
{notes.map((note) => (
|
||||
<button key={note.title} className="data-row note-row" onClick={() => onSelectItem(note.title)}>
|
||||
<span className="row-title"><IconFile /> {note.title}</span>
|
||||
<Tag>{note.type}</Tag>
|
||||
<span>更新于 {note.updated}</span>
|
||||
<span>{note.owner}</span>
|
||||
</button>
|
||||
))}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
55
apps/senlinai-acro-react/src/pages/project.tsx
Normal file
55
apps/senlinai-acro-react/src/pages/project.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Layout } from '@arco-design/web-react'
|
||||
import { OverviewPage } from './overview'
|
||||
import { ProjectInspector } from '../features/projects/project-inspector'
|
||||
import { ProjectRail } from '../features/projects/project-rail'
|
||||
import { ProjectSidebar } from '../features/projects/project-sidebar'
|
||||
import { ProjectStatusbar } from '../features/projects/project-statusbar'
|
||||
import { ProjectTopbar } from '../features/projects/project-topbar'
|
||||
import type { ChannelKey, Project, Theme } from '../features/projects/project-types'
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
export function ProjectPage({
|
||||
activeProject,
|
||||
activeChannel,
|
||||
selectedItem,
|
||||
theme,
|
||||
onSelectProject,
|
||||
onSelectChannel,
|
||||
onSelectItem,
|
||||
onToggleTheme,
|
||||
}: {
|
||||
activeProject: Project
|
||||
activeChannel: ChannelKey
|
||||
selectedItem: string
|
||||
theme: Theme
|
||||
onSelectProject: (project: Project) => void
|
||||
onSelectChannel: (key: ChannelKey) => void
|
||||
onSelectItem: (title: string) => void
|
||||
onToggleTheme: () => void
|
||||
}) {
|
||||
return (
|
||||
<Layout className="workbench-shell">
|
||||
<ProjectTopbar theme={theme} onToggleTheme={onToggleTheme} />
|
||||
|
||||
<Layout className="workbench-main">
|
||||
<ProjectRail activeProject={activeProject} onSelectProject={onSelectProject} />
|
||||
|
||||
<ProjectSidebar
|
||||
activeProject={activeProject}
|
||||
activeChannel={activeChannel}
|
||||
onSelectChannel={onSelectChannel}
|
||||
onSelectItem={onSelectItem}
|
||||
/>
|
||||
|
||||
<Content className="stage">
|
||||
<OverviewPage onSelectItem={onSelectItem} />
|
||||
</Content>
|
||||
|
||||
<ProjectInspector activeProject={activeProject} activeChannel={activeChannel} selectedItem={selectedItem} />
|
||||
</Layout>
|
||||
|
||||
<ProjectStatusbar />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user