288 lines
9.1 KiB
TypeScript
288 lines
9.1 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Link, useLocation } from '@remix-run/react';
|
|
|
|
interface MenuItem {
|
|
id: string;
|
|
title: string;
|
|
path: string;
|
|
icon: string;
|
|
hideBreadcrumb?: boolean;
|
|
children?: MenuItem[];
|
|
}
|
|
|
|
interface SidebarProps {
|
|
onToggle: () => void;
|
|
collapsed: boolean;
|
|
}
|
|
|
|
export function Sidebar({ onToggle, collapsed }: SidebarProps) {
|
|
const location = useLocation();
|
|
const [expandedMenus, setExpandedMenus] = useState<Record<string, boolean>>({});
|
|
|
|
const menuItems: MenuItem[] = [
|
|
{
|
|
id: 'contract-search',
|
|
title: '智能搜索',
|
|
path: '/contract-search',
|
|
hideBreadcrumb: true,
|
|
icon: 'ri-search-line'
|
|
},
|
|
{
|
|
id: 'contract-template',
|
|
title: '合同模板',
|
|
path: '/contract-template',
|
|
icon: 'ri-file-search-line',
|
|
children: [
|
|
{
|
|
id: 'contract-search-ai',
|
|
title: '智能搜索',
|
|
path: '/contract-template/search',
|
|
icon: 'ri-search-line'
|
|
},
|
|
{
|
|
id: 'contract-list',
|
|
title: '合同列表',
|
|
path: '/contract-template/list',
|
|
icon: 'ri-folder-line'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'home',
|
|
title: '系统概览',
|
|
path: '/home',
|
|
icon: 'ri-home-line'
|
|
},
|
|
{
|
|
id: 'file-management',
|
|
title: '文件管理',
|
|
path: '/files',
|
|
icon: 'ri-folder-line',
|
|
children: [
|
|
{
|
|
id: 'file-upload',
|
|
title: '文件上传',
|
|
path: '/files/upload',
|
|
icon: 'ri-upload-cloud-line'
|
|
},
|
|
{
|
|
id:'documents',
|
|
title:'文档列表',
|
|
path:'/documents',
|
|
icon:'ri-file-list-3-line'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'rule-management',
|
|
title: '评查规则库',
|
|
path: '/rules',
|
|
icon: 'ri-book-3-line',
|
|
children: [
|
|
{
|
|
id: 'rule-groups',
|
|
title: '评查点分组',
|
|
path: '/rule-groups',
|
|
icon: 'ri-folder-open-line'
|
|
},
|
|
{
|
|
id: 'rules-list',
|
|
title: '评查点列表',
|
|
path: '/rules',
|
|
icon: 'ri-list-check-3'
|
|
},
|
|
{
|
|
id: 'rules-file',
|
|
title: '评查文件列表',
|
|
path: '/rules-files',
|
|
icon: 'ri-list-check-2'
|
|
},
|
|
{
|
|
id: 'rule-new',
|
|
title: '新增评查点',
|
|
path: '/rules-new',
|
|
icon: 'ri-add-circle-line'
|
|
},
|
|
// {
|
|
// id: 'review-detail',
|
|
// title: '评查详情',
|
|
// path: '/reviews',
|
|
// icon: 'ri-file-chart-line'
|
|
// }
|
|
]
|
|
},
|
|
{
|
|
id: 'system-settings',
|
|
title: '系统设置',
|
|
path: '/settings',
|
|
icon: 'ri-settings-4-line',
|
|
children: [
|
|
{
|
|
id: 'config-lists',
|
|
title: '配置列表',
|
|
path: '/config-lists',
|
|
icon: 'ri-list-check-3'
|
|
},
|
|
// {
|
|
// id: 'basic-settings',
|
|
// title: '基础设置',
|
|
// path: '/settings',
|
|
// icon: 'ri-equalizer-line'
|
|
// },
|
|
{
|
|
id: 'document-types',
|
|
title: '文档类型',
|
|
path: '/document-types',
|
|
icon: 'ri-file-list-line'
|
|
},
|
|
{
|
|
id: 'prompt-management',
|
|
title: '提示词管理',
|
|
path: '/prompts',
|
|
icon: 'ri-chat-1-line'
|
|
}
|
|
]
|
|
}
|
|
];
|
|
|
|
// 初始化展开状态,默认全部展开
|
|
useEffect(() => {
|
|
const initialExpandedState: Record<string, boolean> = {};
|
|
menuItems.forEach(item => {
|
|
if (item.children) {
|
|
initialExpandedState[item.id] = true;
|
|
}
|
|
});
|
|
setExpandedMenus(initialExpandedState);
|
|
}, []);
|
|
|
|
const toggleMenu = (id: string, e: React.MouseEvent) => {
|
|
// 防止事件冒泡和默认行为
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
// console.log('%c父菜单展开/折叠 ===> ', 'background: #f5222d; color: white; padding: 2px 4px; border-radius: 2px;', id);
|
|
|
|
setExpandedMenus(prev => ({
|
|
...prev,
|
|
[id]: !prev[id]
|
|
}));
|
|
};
|
|
|
|
const isActive = (path: string) => {
|
|
return location.pathname === path || location.pathname.startsWith(`${path}/`);
|
|
};
|
|
|
|
// 处理侧边栏切换事件
|
|
const handleToggleSidebar = (e: React.MouseEvent) => {
|
|
// console.log('%c侧边栏折叠/展开 ===> ', 'background: #1890ff; color: white; padding: 2px 4px; border-radius: 2px;');
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
onToggle();
|
|
};
|
|
|
|
// 处理子菜单项点击事件
|
|
const handleSubMenuClick = (child: MenuItem, e: React.MouseEvent) => {
|
|
// 需要阻止冒泡,否则会触发父级菜单的展开/折叠事件
|
|
e.stopPropagation();
|
|
// console.log('%c子菜单点击 ===> ', 'background: #00684a; color: white; padding: 2px 4px; border-radius: 2px;', child.title, '路径:', child.path);
|
|
};
|
|
|
|
return (
|
|
<div className={`sidebar ${collapsed ? 'collapsed' : ''}`}>
|
|
<div className="py-6 px-4 border-b border-gray-100 flex justify-between items-center">
|
|
<div className="flex items-center">
|
|
<img src="/logo.svg" alt="智慧法务" className="w-12 h-12 mr-2" />
|
|
{!collapsed && <h2 className="text-lg font-medium">智慧法务</h2>}
|
|
</div>
|
|
<button
|
|
className="sidebar-toggle"
|
|
onClick={handleToggleSidebar}
|
|
aria-label={collapsed ? "展开侧边栏" : "折叠侧边栏"}
|
|
type="button"
|
|
>
|
|
<i className={`${collapsed ? 'ri-menu-unfold-line' : 'ri-menu-fold-line'}`}></i>
|
|
</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>
|
|
</div>
|
|
<div className="ml-3">
|
|
<p className="text-sm font-medium">系统管理员</p>
|
|
<p className="text-xs text-gray-500">超级管理员</p>
|
|
</div>
|
|
</div>
|
|
)} */}
|
|
|
|
<div className="py-4 px-[10px]">
|
|
{menuItems.map((item) => (
|
|
<div key={item.id} className={`${collapsed ? 'px-0' : ''}`}>
|
|
{!item.children ? (
|
|
<Link
|
|
to={item.path}
|
|
className={`sidebar-menu-item ${isActive(item.path) ? 'active' : ''} flex items-center ${collapsed ? 'justify-center' : ''}`}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
// console.log('%c单级菜单点击 ===> ', 'background: #52c41a; color: white; padding: 2px 4px; border-radius: 2px;', item.title, '路径:', item.path);
|
|
}}
|
|
>
|
|
<i className={`${item.icon} ${collapsed ? 'text-xl' : 'mr-3'}`}></i>
|
|
{!collapsed && <span>{item.title}</span>}
|
|
</Link>
|
|
) : (
|
|
<>
|
|
<div
|
|
className={`sidebar-menu-item flex items-center ${collapsed ? 'justify-center' : 'justify-between'} cursor-pointer z-10`}
|
|
onClick={(e) => {
|
|
// console.log('%c父菜单点击 ===> ', 'background: #722ed1; color: white; padding: 2px 4px; border-radius: 2px;', item.title);
|
|
toggleMenu(item.id, e);
|
|
}}
|
|
role="button"
|
|
tabIndex={0}
|
|
aria-expanded={expandedMenus[item.id] || false}
|
|
aria-controls={`submenu-${item.id}`}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
toggleMenu(item.id, e as unknown as React.MouseEvent);
|
|
}
|
|
}}
|
|
>
|
|
<div className="flex items-center">
|
|
<i className={`${item.icon} ${collapsed ? 'text-xl' : 'mr-3'}`}></i>
|
|
{!collapsed && <span>{item.title}</span>}
|
|
</div>
|
|
{!collapsed && (
|
|
<i className={`ri-arrow-${expandedMenus[item.id] ? 'down' : 'right'}-s-line`}></i>
|
|
)}
|
|
</div>
|
|
|
|
{(expandedMenus[item.id] || collapsed) && (
|
|
<div
|
|
className={`submenu-container ${collapsed ? 'border-l-0 pl-0' : 'border-l border-gray-100 ml-4 pl-3'} z-20`}
|
|
id={`submenu-${item.id}`}
|
|
>
|
|
{item.children.map((child) => (
|
|
<Link
|
|
key={child.id}
|
|
to={child.path}
|
|
className={`sidebar-menu-item ${isActive(child.path) ? 'active' : ''} flex items-center ${collapsed ? 'justify-center' : ''}`}
|
|
onClick={(e) => handleSubMenuClick(child, e)}
|
|
>
|
|
<i className={`${child.icon} ${collapsed ? 'text-xl' : 'mr-3'}`}></i>
|
|
{!collapsed && <span>{child.title}</span>}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
} |