Files
Agent-System/web/src/components/layout/AppShell.tsx
T

183 lines
5.4 KiB
TypeScript

import React, { useState } from 'react';
import { Topbar } from './Topbar';
import { LeftRail } from './LeftRail';
import { ResizeHandle } from './ResizeHandle';
import { ChatColumn } from './ChatColumn';
import { RightDocRail } from './RightDocRail';
import { ProjectSession } from '../../types/workbench';
import { UserRole, UserProfile } from '../../types/docgov';
interface AppShellProps {
sessions: ProjectSession[];
activeSessionId: string;
onSelectSession: (id: string) => void;
onNewSession: () => void;
canCreateProject?: boolean;
currentUser: UserProfile;
currentRole: UserRole;
currentUserName: string;
onChangeRole: (role: UserRole, name: string) => void;
onOpenRightsModal: () => void;
onOpenUserSettings: () => void;
onOpenAdminSettings?: () => void;
onOpenProjectMembers?: () => void;
chatContent: React.ReactNode;
docContent: React.ReactNode;
activeWorkflowKey?: string;
workflowVersion?: number;
isCreator?: boolean;
onWorkflowChanged?: (newKey: string, newVersion: number) => void;
workflowBanner?: React.ReactNode;
}
export const AppShell: React.FC<AppShellProps> = ({
sessions,
activeSessionId,
onSelectSession,
onNewSession,
canCreateProject = true,
currentUser,
currentRole,
currentUserName: _currentUserName,
onChangeRole: _onChangeRole,
onOpenRightsModal,
onOpenUserSettings,
onOpenAdminSettings,
onOpenProjectMembers,
chatContent,
docContent,
activeWorkflowKey,
workflowVersion,
isCreator,
onWorkflowChanged,
workflowBanner,
}) => {
const [theme, setTheme] = useState<'dark' | 'light'>('dark');
const [leftWidth, setLeftWidth] = useState<number>(280);
const [leftCollapsed, setLeftCollapsed] = useState<boolean>(false);
const [rightWidth, setRightWidth] = useState<number>(500);
const [rightCollapsed, setRightCollapsed] = useState<boolean>(false);
const currentSession =
sessions.find((s) => s.sessionId === activeSessionId) || sessions[0] || {
sessionId: 'sess-default',
userId: 0,
projectId: 1,
title: '未命名立项项目',
deviceType: '医疗器械',
stage: 'V0.1',
stageLabel: 'V0.1 需求摸底',
messageCount: 0,
updatedAt: '刚刚',
};
const handleToggleTheme = () => {
const next = theme === 'dark' ? 'light' : 'dark';
setTheme(next);
document.documentElement.setAttribute('data-theme', next);
};
const handleResizeLeft = (newWidth: number) => {
setLeftWidth(newWidth);
};
const handleResizeRight = (newWidth: number) => {
setRightWidth(newWidth);
};
// 动态视口安全最大宽度计算(保障中间对话栏至少保留 380px)
const windowWidth = typeof window !== 'undefined' ? window.innerWidth : 1440;
const maxLeftWidth = Math.min(420, Math.max(200, windowWidth - (rightCollapsed ? 0 : rightWidth) - 380));
const maxRightWidth = Math.min(850, Math.max(360, windowWidth - (leftCollapsed ? 0 : leftWidth) - 380));
return (
<div
style={{
width: '100vw',
height: '100vh',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
background: 'var(--bg-app)',
}}
>
<Topbar
currentSession={currentSession}
theme={theme}
onToggleTheme={handleToggleTheme}
leftCollapsed={leftCollapsed}
onToggleLeft={() => setLeftCollapsed(!leftCollapsed)}
rightCollapsed={rightCollapsed}
onToggleRight={() => setRightCollapsed(!rightCollapsed)}
activeWorkflowKey={activeWorkflowKey}
workflowVersion={workflowVersion}
isCreator={isCreator}
onWorkflowChanged={onWorkflowChanged}
/>
<div
style={{
flex: 1,
display: 'flex',
overflow: 'hidden',
width: '100%',
height: 'calc(100vh - 52px)',
}}
>
{/* 左侧栏 */}
<LeftRail
width={leftWidth}
collapsed={leftCollapsed}
sessions={sessions}
activeSessionId={activeSessionId}
onSelectSession={onSelectSession}
onNewSession={onNewSession}
canCreateProject={canCreateProject}
currentUser={currentUser}
currentRole={currentRole}
onOpenRightsModal={onOpenRightsModal}
onOpenUserSettings={onOpenUserSettings}
onOpenAdminSettings={onOpenAdminSettings}
onOpenProjectMembers={onOpenProjectMembers}
isAdmin={currentUser.roleId === 'role-admin' || currentUser.name.includes('管理员')}
/>
{!leftCollapsed && (
<ResizeHandle
side="left"
currentWidth={leftWidth}
minWidth={200}
maxWidth={maxLeftWidth}
defaultWidth={280}
onResize={handleResizeLeft}
onReset={() => setLeftWidth(280)}
/>
)}
{/* 中间栏 */}
<ChatColumn currentSession={currentSession}>
{workflowBanner}
{chatContent}
</ChatColumn>
{!rightCollapsed && (
<ResizeHandle
side="right"
currentWidth={rightWidth}
minWidth={360}
maxWidth={maxRightWidth}
defaultWidth={500}
onResize={handleResizeRight}
onReset={() => setRightWidth(500)}
/>
)}
{/* 右侧栏 */}
<RightDocRail width={rightWidth} collapsed={rightCollapsed}>
{docContent}
</RightDocRail>
</div>
</div>
);
};