添加合同和卷宗数据隔离

This commit is contained in:
2025-06-03 12:16:31 +08:00
parent b02978508d
commit 0397139ad8
20 changed files with 1190 additions and 437 deletions
+48 -2
View File
@@ -5,6 +5,16 @@ import { Breadcrumb } from './Breadcrumb';
import { useMatches, useLocation } from '@remix-run/react';
import type { UserRole } from '~/root';
// 定义应用模块类型
type AppModule = 'contract' | 'record' | 'model';
// 应用模块与reviewType的映射
const REVIEW_TYPE_TO_APP: Record<string, AppModule> = {
'contract': 'contract', // 合同管理
'record': 'record', // 案卷智能评查
'model': 'model' // 智慧法务大模型
};
interface LayoutProps {
children: React.ReactNode;
userRole?: UserRole;
@@ -24,6 +34,7 @@ interface Match {
export function Layout({ children, userRole = 'developer' }: LayoutProps) {
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [selectedApp, setSelectedApp] = useState<AppModule>('contract');
const matches = useMatches() as Match[];
const location = useLocation();
@@ -36,12 +47,25 @@ export function Layout({ children, userRole = 'developer' }: LayoutProps) {
match.handle && match.handle.hideBreadcrumb === true
);
// 从本地存储中获取侧边栏状态
// 从sessionStorage中获取侧边栏状态和reviewType
useEffect(() => {
// 从localStorage获取侧边栏状态
const savedState = localStorage.getItem('sidebarCollapsed');
if (savedState) {
setSidebarCollapsed(savedState === 'true');
}
// 从sessionStorage获取reviewType并设置对应的应用模块
if (typeof window !== 'undefined') {
try {
const reviewType = sessionStorage.getItem('reviewType');
if (reviewType && REVIEW_TYPE_TO_APP[reviewType]) {
setSelectedApp(REVIEW_TYPE_TO_APP[reviewType]);
}
} catch (error) {
console.error('获取reviewType失败:', error);
}
}
}, []);
const toggleSidebar = () => {
@@ -50,6 +74,12 @@ export function Layout({ children, userRole = 'developer' }: LayoutProps) {
localStorage.setItem('sidebarCollapsed', String(newState));
};
// 切换应用模块
// const changeAppModule = (appId: AppModule) => {
// setSelectedApp(appId);
// localStorage.setItem('selectedApp', appId);
// };
// 如果是无布局页面,只渲染内容
if (shouldHideSidebar) {
return <>{children}</>;
@@ -61,10 +91,26 @@ export function Layout({ children, userRole = 'developer' }: LayoutProps) {
collapsed={sidebarCollapsed}
onToggle={toggleSidebar}
userRole={userRole}
selectedApp={selectedApp}
/>
<div className={`main-content ${sidebarCollapsed ? 'sidebar-collapsed' : ''}`}>
{/* <Header username="系统管理员" /> */}
{/* 应用模块选择器 */}
{/* <div className="app-module-selector py-2 px-4 border-b border-gray-100 flex items-center">
{APP_MODULES.map(app => (
<button
key={app.id}
onClick={() => changeAppModule(app.id as AppModule)}
className={`app-module-btn mr-4 py-2 px-4 rounded-md flex items-center ${
selectedApp === app.id ? 'bg-green-50 text-green-700 border border-green-200' : 'hover:bg-gray-50'
}`}
>
<i className={`${app.icon} mr-2`}></i>
<span>{app.name}</span>
</button>
))}
</div> */}
<div className="content-container">
{!shouldHideBreadcrumb && <Breadcrumb />}
{children}
+100 -10
View File
@@ -16,11 +16,92 @@ interface SidebarProps {
onToggle: () => void;
collapsed: boolean;
userRole: UserRole;
selectedApp?: string; // 添加所选应用模块参数
}
export function Sidebar({ onToggle, collapsed, userRole }: SidebarProps) {
// 定义不同应用模块下显示的菜单项ID
const APP_MENU_MAP = {
'contract': ['home', 'contract-template', 'file-management', 'rule-management', 'system-settings'],
'record': ['home', 'file-management', 'rule-management', 'system-settings'],
'model': ['home']
};
// 应用模块名称映射
const APP_NAME_MAP: Record<string, string> = {
'contract': '合同管理',
'record': '案卷智能评查',
'model': '智慧法务大模型'
};
// 应用模块图标映射
const APP_ICON_MAP: Record<string, string> = {
'contract': 'ri-file-list-2-fill',
'record': 'ri-folder-shared-fill',
'model': 'ri-robot-2-fill'
};
export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract' }: SidebarProps) {
const location = useLocation();
const [expandedMenus, setExpandedMenus] = useState<Record<string, boolean>>({});
const [currentApp, setCurrentApp] = useState<string>(selectedApp);
// 从 sessionStorage 获取 reviewType 并设置当前应用模块
useEffect(() => {
// 初始加载时获取 reviewType
const updateReviewType = () => {
if (typeof window !== 'undefined') {
const reviewType = sessionStorage.getItem('reviewType');
if (reviewType) {
setCurrentApp(reviewType);
}
}
};
// 首次执行
updateReviewType();
// 设置轮询,每秒检查一次 reviewType 变化
const intervalId = setInterval(updateReviewType, 1000);
// 添加自定义事件监听
const handleReviewTypeChange = () => {
updateReviewType();
};
// 监听 sessionStorage 变化
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'reviewType' && e.newValue) {
setCurrentApp(e.newValue);
}
};
// 添加事件监听器
window.addEventListener('reviewTypeChange', handleReviewTypeChange);
window.addEventListener('storage', handleStorageChange);
return () => {
clearInterval(intervalId);
window.removeEventListener('reviewTypeChange', handleReviewTypeChange);
window.removeEventListener('storage', handleStorageChange);
};
}, []);
// 监听路由变化,重新检查 reviewType
useEffect(() => {
if (typeof window !== 'undefined') {
const reviewType = sessionStorage.getItem('reviewType');
if (reviewType) {
setCurrentApp(reviewType);
}
}
}, [location.pathname]);
// 监听 selectedApp 属性变化
useEffect(() => {
if (selectedApp) {
setCurrentApp(selectedApp);
}
}, [selectedApp]);
const menuItems: MenuItem[] = [
{
@@ -187,12 +268,21 @@ export function Sidebar({ onToggle, collapsed, userRole }: SidebarProps) {
// console.log('子菜单点击:', child.title, '路径:', child.path);
};
// 根据用户角色过滤菜单项
// 获取当前应用模式下应显示的菜单ID列表
const visibleMenuIds = APP_MENU_MAP[currentApp as keyof typeof APP_MENU_MAP] || APP_MENU_MAP['contract'];
// 根据用户角色和当前应用模式过滤菜单项
const filteredMenuItems = menuItems.filter(item => {
// 如果菜单项需要特定角色但用户没有
if (item.requiredRole && item.requiredRole !== userRole) {
return false;
}
// 检查当前菜单是否在所选应用模式中显示
if (!visibleMenuIds.includes(item.id)) {
return false;
}
return true;
});
@@ -213,17 +303,17 @@ export function Sidebar({ onToggle, collapsed, userRole }: SidebarProps) {
</button>
</div>
{/* {!collapsed && (
<div className="user-profile p-4 border-b border-gray-100 flex items-center">
<div className="avatar w-10 h-10 rounded-full bg-primary text-white flex items-center justify-center">
<span>管</span>
{!collapsed && (
<div className="px-4 py-3 border-b border-gray-100">
<div className="flex items-center text-green-700">
<i className={`${APP_ICON_MAP[currentApp] || 'ri-file-list-2-fill'} mr-2 text-xl`}></i>
<span className="font-medium">{APP_NAME_MAP[currentApp] || '合同管理'}</span>
</div>
<div className="ml-3">
<p className="text-sm font-medium">系统管理员</p>
<p className="text-xs text-gray-500">超级管理员</p>
<div className="text-xs text-gray-500 mt-1">
: {APP_NAME_MAP[currentApp] || '合同管理'}
</div>
</div>
)} */}
)}
<div className="py-4 px-[10px]">
{filteredMenuItems.map((item) => (