保存规则库 YAML 维护改造进展

This commit is contained in:
2026-04-28 22:00:00 +08:00
parent 7b86293263
commit dce5ac0c9a
96 changed files with 36801 additions and 615 deletions
+156 -5
View File
@@ -26,6 +26,30 @@ interface Match {
data: unknown;
}
type RulesTestDetailData = {
pack?: {
documentType?: string;
mainType?: string;
fields?: unknown[];
subDocuments?: unknown[];
visualElements?: unknown[];
};
};
type RulesTestListData = {
filters?: {
documentType?: string;
mainType?: string;
subtype?: string;
ruleGroup?: string;
keyword?: string;
};
options?: {
subtypes?: string[];
ruleGroups?: string[];
};
};
export function Layout({ children, userRole = 'developer' as UserRole, frontendJWT = '', isMobile = false }: LayoutProps) {
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [effectiveUserRole, setEffectiveUserRole] = useState<UserRole>(userRole);
@@ -121,7 +145,31 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ
if (shouldHideSidebar) {
return <>{children}</>;
}
const isRulesTestList = location.pathname.startsWith('/rulesTest/list');
const isRulesTestDetail = location.pathname.startsWith('/rulesTest/detail');
const isRulesTestTopbarPage = isRulesTestList || isRulesTestDetail;
const rulesTestListData = matches.find(match => match.pathname.startsWith('/rulesTest/list'))?.data as RulesTestListData | undefined;
const rulesTestDetailData = matches.find(match => match.pathname.startsWith('/rulesTest/detail'))?.data as RulesTestDetailData | undefined;
const listFilters = rulesTestListData?.filters || {};
const listOptions = rulesTestListData?.options || {};
const detailPack = rulesTestDetailData?.pack;
const isContractDetail = !!detailPack?.documentType?.includes('合同');
const isCaseFileDetail = !!detailPack?.documentType?.includes('案卷');
const showFieldNav = isContractDetail && (detailPack?.fields?.length || 0) > 0;
const showSubDocumentNav = isCaseFileDetail && (detailPack?.subDocuments?.length || 0) > 0;
const showVisualNav = (detailPack?.visualElements?.length || 0) > 0;
const rulesListHref = detailPack?.documentType
? `/rulesTest/list?documentType=${encodeURIComponent(detailPack.documentType)}${detailPack.mainType ? `&mainType=${encodeURIComponent(detailPack.mainType)}` : ''}`
: '/rulesTest/list';
const listScopeText = [
listFilters.documentType,
listFilters.mainType && listFilters.mainType !== listFilters.documentType ? listFilters.mainType : ''
].filter(Boolean).join(' / ');
const submitTopbarFilter = (event: React.ChangeEvent<HTMLSelectElement>) => {
event.currentTarget.form?.requestSubmit();
};
return (
<div className="layout-container">
{/* 侧边栏始终保留,不再使用条件渲染 */}
@@ -131,8 +179,111 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ
userRole={effectiveUserRole}
frontendJWT={effectiveFrontendJWT}
/>
<div className={`main-content ${sidebarCollapsed ? 'sidebar-collapsed' : ''}`}>
{/* 规则列表页顶部栏 */}
{isRulesTestList && (
<div className={`page-topbar rules-list-topbar ${sidebarCollapsed ? 'sidebar-collapsed' : ''}`}>
<div className="topbar-content">
<div className="topbar-left">
<span className="topbar-icon" aria-hidden="true">
<i className="ri-list-settings-line"></i>
</span>
<div className="topbar-heading">
<h2 className="topbar-title"></h2>
<span className="topbar-breadcrumb">
<span></span>
{listScopeText && (
<>
<span className="separator">/</span>
<span>{listScopeText}</span>
</>
)}
</span>
</div>
</div>
<div className="topbar-right">
<a className="topbar-action secondary" href="/rules/list">
<i className="ri-history-line"></i>
<span></span>
</a>
</div>
</div>
<form method="get" action="/rulesTest/list" className="topbar-filter-strip">
<input type="hidden" name="documentType" defaultValue={listFilters.documentType || ''} />
{listFilters.mainType && <input type="hidden" name="mainType" defaultValue={listFilters.mainType} />}
<label className="topbar-filter-field">
<span></span>
<select name="subtype" value={listFilters.subtype || ''} onChange={submitTopbarFilter}>
<option value=""></option>
{(listOptions.subtypes || []).map(option => (
<option key={option} value={option}>{option}</option>
))}
</select>
</label>
<label className="topbar-filter-field">
<span></span>
<select name="ruleGroup" value={listFilters.ruleGroup || ''} onChange={submitTopbarFilter}>
<option value=""></option>
{(listOptions.ruleGroups || []).map(group => (
<option key={group} value={group}>{group}</option>
))}
</select>
</label>
<label className="topbar-filter-field topbar-filter-field-search">
<span></span>
<input
key={listFilters.keyword || 'empty-keyword'}
name="keyword"
defaultValue={listFilters.keyword || ''}
placeholder="规则名称 / 编码 / 规则组"
/>
</label>
<button className="topbar-action" type="submit">
<i className="ri-search-line"></i>
<span></span>
</button>
</form>
</div>
)}
{/* 规则详情页顶部栏 */}
{isRulesTestDetail && (
<div className={`page-topbar ${sidebarCollapsed ? 'sidebar-collapsed' : ''}`}>
<div className="topbar-content">
<div className="topbar-left">
<span className="topbar-icon" aria-hidden="true">
<i className="ri-file-settings-line"></i>
</span>
<div className="topbar-heading">
<h2 className="topbar-title"></h2>
<span className="topbar-breadcrumb">
<a href={rulesListHref}></a>
<span className="separator">/</span>
<span></span>
</span>
</div>
</div>
<div className="topbar-right">
<a className="topbar-action" href={rulesListHref}>
<i className="ri-arrow-left-line"></i>
<span></span>
</a>
<a className="topbar-action secondary" href="/rules/list">
<i className="ri-history-line"></i>
<span></span>
</a>
</div>
</div>
<div className="topbar-nav">
{showFieldNav && <a className="topbar-nav-link" href="#fields"><i className="ri-input-field"></i></a>}
{showSubDocumentNav && <a className="topbar-nav-link" href="#sub-documents"><i className="ri-file-list-3-line"></i></a>}
{showVisualNav && <a className="topbar-nav-link" href="#visual-elements"><i className="ri-stamp-line"></i></a>}
<a className="topbar-nav-link" href="#rules"><i className="ri-list-check-3"></i></a>
</div>
</div>
)}
<div className={`main-content ${sidebarCollapsed ? 'sidebar-collapsed' : ''} ${isRulesTestDetail ? 'rules-detail-main' : ''} ${isRulesTestList ? 'rules-list-main' : ''}`}>
{/* 应用模块选择器 */}
{/* <div className="app-module-selector py-2 px-4 border-b border-gray-100 flex items-center">
{APP_MODULES.map(app => (
@@ -150,10 +301,10 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ
</div> */}
<div className={`content-container${shouldNoPadding ? ' !p-0' : ''}`}>
{!shouldHideBreadcrumb && <Breadcrumb />}
{!shouldHideBreadcrumb && !isRulesTestTopbarPage && <Breadcrumb />}
{children}
</div>
</div>
</div>
);
}
}
+93 -20
View File
@@ -169,9 +169,18 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: Sid
}));
};
const isActive = (path: string) => {
return location.pathname === path || location.pathname.startsWith(`${path}/`);
};
const isActive = (path: string) => {
const target = new URL(path, 'http://sidebar.local');
if (target.search) {
const currentParams = new URLSearchParams(location.search);
return location.pathname === target.pathname && Array.from(target.searchParams.entries()).every(
([key, value]) => currentParams.get(key) === value
);
}
return location.pathname === target.pathname || location.pathname.startsWith(`${target.pathname}/`);
};
// 处理侧边栏切换事件
const handleToggleSidebar = (e: React.MouseEvent) => {
@@ -181,17 +190,81 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: Sid
onToggle();
};
// 处理子菜单项点击事件
const handleSubMenuClick = (child: MenuItem, e: React.MouseEvent) => {
// 只需要阻止冒泡,不阻止默认行为
e.stopPropagation();
// console.log('子菜单点击:', child.title, '路径:', child.path);
};
// const isPort51707 = typeof window !== 'undefined' && window.location.port === '51707'
// 处理菜单项:清理子菜单结构
const processedMenuItems: MenuItem[] = menuItems.filter(item =>{
// 处理子菜单项点击事件
const handleSubMenuClick = (child: MenuItem, e: React.MouseEvent) => {
// 只需要阻止冒泡,不阻止默认行为
e.stopPropagation();
// console.log('子菜单点击:', child.title, '路径:', child.path);
};
const isRuleManagementMenu = (item: MenuItem) =>
item.id === 'rule-management' ||
item.path === '/rules' ||
item.title === '评查规则库' ||
!!item.children?.some(child => child.id === 'rules-list' || child.path === '/rules/list');
const isCaseFileModule = selectedModuleName.includes('案卷') || selectedModuleName.includes('卷宗');
const buildRulesTestListPath = (mainType?: string) => {
const params = new URLSearchParams();
if (isCaseFileModule) {
params.set('documentType', '案卷');
if (mainType) params.set('mainType', mainType);
} else if (selectedModuleName.includes('合同')) {
params.set('documentType', '合同');
params.set('mainType', '合同');
} else if (selectedModuleName.includes('公文')) {
params.set('documentType', '内部公文');
params.set('mainType', '内部公文');
} else if (selectedModuleName) {
params.set('documentType', selectedModuleName);
params.set('mainType', selectedModuleName);
}
const query = params.toString();
return query ? `/rulesTest/list?${query}` : '/rulesTest/list';
};
const normalizeRuleManagementMenu = (item: MenuItem): MenuItem => {
if (!isRuleManagementMenu(item)) {
return item;
}
if (isCaseFileModule) {
return {
...item,
children: [
{
id: 'rules-admin-penalty',
title: '行政处罚',
path: buildRulesTestListPath('行政处罚'),
icon: 'ri-list-check-3',
order: 1
},
{
id: 'rules-admin-license',
title: '行政许可',
path: buildRulesTestListPath('行政许可'),
icon: 'ri-list-check-3',
order: 2
}
]
};
}
return {
...item,
children: item.children?.map(child => (
child.id === 'rules-list' || child.path === '/rules' || child.path === '/rules/list'
? { ...child, path: buildRulesTestListPath() }
: child
))
};
};
// const isPort51707 = typeof window !== 'undefined' && window.location.port === '51707'
// 处理菜单项:清理子菜单结构
const processedMenuItems: MenuItem[] = menuItems.filter(item =>{
// console.log('菜单项:', item.title, 'Icon:', item.icon)
// 🔑 优先检查:如果处于系统设置模式,只显示 /settings 及其子路由
@@ -255,11 +328,11 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: Sid
if (item.path === '/contract-template' || item.path?.startsWith('/contract-template/')) {
return false;
}
// 保留其他菜单
return true;
}).map((item): MenuItem => {
// 保留其他菜单
return true;
}).map(normalizeRuleManagementMenu).map((item): MenuItem => {
// 处理子菜单:过滤隐藏的子菜单
if (item.children && item.children.length > 0) {
// 过滤掉 hideBreadcrumb=true 的子菜单(这些通常是隐藏菜单)
@@ -449,4 +522,4 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: Sid
</div>
</>
);
}
}