183 lines
5.4 KiB
TypeScript
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>
|
|
);
|
|
};
|