From 84d2e0a1a51149eef77eb5a4400cdcc189e88dd3 Mon Sep 17 00:00:00 2001 From: Jande Jang Date: Wed, 29 Apr 2026 11:22:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E9=BD=90=E8=A7=84=E5=88=99=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E9=A1=B5=E4=BA=A4=E4=BA=92=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/layout/Breadcrumb.tsx | 6 +- app/components/layout/Layout.tsx | 95 +----------------- app/routes/rulesTest.list.tsx | 145 +++++++++++++++++++++++++-- app/styles/pages/rules_test.css | 65 ++++++++++++ 4 files changed, 209 insertions(+), 102 deletions(-) diff --git a/app/components/layout/Breadcrumb.tsx b/app/components/layout/Breadcrumb.tsx index 33cd8dc..454950a 100644 --- a/app/components/layout/Breadcrumb.tsx +++ b/app/components/layout/Breadcrumb.tsx @@ -84,7 +84,7 @@ export function Breadcrumb({ items = [], className = '' }: BreadcrumbProps) { ); -} \ No newline at end of file +} diff --git a/app/components/layout/Layout.tsx b/app/components/layout/Layout.tsx index bb65085..d5c097e 100644 --- a/app/components/layout/Layout.tsx +++ b/app/components/layout/Layout.tsx @@ -36,20 +36,6 @@ type RulesTestDetailData = { }; }; -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); @@ -146,13 +132,9 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ 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 isRulesTestTopbarPage = isRulesTestDetail; 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('案卷'); @@ -162,13 +144,6 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ 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) => { - event.currentTarget.form?.requestSubmit(); - }; return (
@@ -180,72 +155,6 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ frontendJWT={effectiveFrontendJWT} /> - {/* 规则列表页顶部栏 */} - {isRulesTestList && ( -
-
-
- -
-

评查规则列表

- - 评查规则库 - {listScopeText && ( - <> - / - {listScopeText} - - )} - -
-
- -
-
- - {listFilters.mainType && } - - - - -
-
- )} - {/* 规则详情页顶部栏 */} {isRulesTestDetail && (
@@ -283,7 +192,7 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ
)} -
+
{/* 应用模块选择器 */} {/*
{APP_MODULES.map(app => ( diff --git a/app/routes/rulesTest.list.tsx b/app/routes/rulesTest.list.tsx index 6aa032e..4e533f8 100644 --- a/app/routes/rulesTest.list.tsx +++ b/app/routes/rulesTest.list.tsx @@ -1,6 +1,10 @@ import { type LoaderFunctionArgs, type MetaFunction } from '@remix-run/node'; -import { Link, useLoaderData } from '@remix-run/react'; +import { Link, useLoaderData, useSearchParams } from '@remix-run/react'; +import type React from 'react'; +import { Button } from '~/components/ui/Button'; import { Card } from '~/components/ui/Card'; +import { FilterPanel, FilterSelect, SearchFilter } from '~/components/ui/FilterPanel'; +import { Pagination } from '~/components/ui/Pagination'; import { Table } from '~/components/ui/Table'; import { Tag, type TagColor } from '~/components/ui/Tag'; import { loadRuleYamlPacks, type RuleSummary, type RuleYamlPack } from '~/utils/rules-yaml-mock.server'; @@ -14,6 +18,10 @@ export const meta: MetaFunction = () => [ { title: '规则 YAML 列表 - 智慧法务' } ]; +export const handle = { + breadcrumb: '评查点列表' +}; + type RuleRow = RuleSummary & { rowId: string; packId: string; @@ -34,7 +42,12 @@ type LoaderData = { subtype: string; ruleGroup: string; keyword: string; + page: number; + pageSize: number; }; + totalCount: number; + currentPage: number; + pageSize: number; options: { documentTypes: string[]; mainTypes: string[]; @@ -59,12 +72,16 @@ export async function loader({ request }: LoaderFunctionArgs) { const requestedMainType = url.searchParams.get('mainType') || url.searchParams.get('ruleTypeName') || ''; const requestedSubtype = url.searchParams.get('subtype') || url.searchParams.get('documentAttributeType') || ''; const requestedRuleGroup = url.searchParams.get('ruleGroup') || url.searchParams.getAll('ruleGroups')[0] || ''; + const requestedPage = Number(url.searchParams.get('page') || '1'); + const requestedPageSize = Number(url.searchParams.get('pageSize') || '10'); const requestedFilters = { documentType: url.searchParams.get('documentType') || '', mainType: requestedMainType, subtype: requestedSubtype, ruleGroup: requestedRuleGroup, - keyword: url.searchParams.get('keyword') || '' + keyword: url.searchParams.get('keyword') || '', + page: Number.isFinite(requestedPage) && requestedPage > 0 ? requestedPage : 1, + pageSize: [10, 20, 30, 50].includes(requestedPageSize) ? requestedPageSize : 10 }; const packs = await loadRuleYamlPacks(); @@ -103,7 +120,7 @@ export async function loader({ request }: LoaderFunctionArgs) { (!filters.subtype || pack.subtype === filters.subtype) ); - const rows: RuleRow[] = visiblePacks.flatMap((pack): RuleRow[] => { + const filteredRows: RuleRow[] = visiblePacks.flatMap((pack): RuleRow[] => { if (pack.rules.length === 0) { return [{ rowId: `${pack.id}-empty`, @@ -153,11 +170,22 @@ export async function loader({ request }: LoaderFunctionArgs) { return [row.ruleId, row.name, row.group, row.yamlName, row.subtype] .some(value => value.toLowerCase().includes(filters.keyword.toLowerCase())); }); + const totalCount = filteredRows.length; + const totalPages = Math.max(1, Math.ceil(totalCount / filters.pageSize)); + const currentPage = Math.min(filters.page, totalPages); + const startIndex = (currentPage - 1) * filters.pageSize; + const rows = filteredRows.slice(startIndex, startIndex + filters.pageSize); return Response.json({ rows, packs, - filters, + filters: { + ...filters, + page: currentPage + }, + totalCount, + currentPage, + pageSize: filters.pageSize, options: { documentTypes, mainTypes: unique(packs.filter(pack => pack.documentType === filters.documentType).map(pack => pack.mainType)), @@ -171,7 +199,50 @@ export async function loader({ request }: LoaderFunctionArgs) { } export default function RulesTestList() { - const { rows } = useLoaderData() as LoaderData; + const { rows, filters, options, totalCount, currentPage, pageSize } = useLoaderData() as LoaderData; + const [searchParams, setSearchParams] = useSearchParams(); + + const updateParams = (updates: Record, resetPage = true) => { + const nextParams = new URLSearchParams(searchParams); + + Object.entries(updates).forEach(([key, value]) => { + if (value === undefined || value === '') { + nextParams.delete(key); + return; + } + nextParams.set(key, String(value)); + }); + + if (resetPage) { + nextParams.set('page', '1'); + } + + setSearchParams(nextParams); + }; + + const handleFilterChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + updateParams({ [name]: value }); + }; + + const handleSearch = (keyword: string) => { + updateParams({ keyword }); + }; + + const handleReset = () => { + const nextParams = new URLSearchParams(searchParams); + ['ruleGroup', 'subtype', 'documentAttributeType', 'keyword', 'page'].forEach(key => nextParams.delete(key)); + nextParams.set('page', '1'); + setSearchParams(nextParams); + }; + + const handlePageChange = (page: number) => { + updateParams({ page }, false); + }; + + const handlePageSizeChange = (nextPageSize: number) => { + updateParams({ pageSize: nextPageSize, page: 1 }, false); + }; const columns = [ { @@ -244,15 +315,77 @@ export default function RulesTestList() { return (
-
+
+
+
+

评查点管理

+
+ + 总评查点数: + {totalCount} +
+
+
+ + + 重置 + + } + > + ({ value: group, label: group }))} + onChange={handleFilterChange} + className="mr-3 w-[22%]" + /> + + ({ value: subtype, label: subtype }))} + onChange={handleFilterChange} + className="mr-3 w-[18%]" + /> + + + + 暂无规则。当前类型的规则列表流程已保留,可进入配置页查看空 YAML 模板。} /> + {totalCount > 0 && ( + + )} diff --git a/app/styles/pages/rules_test.css b/app/styles/pages/rules_test.css index 2ddebcb..e607d55 100644 --- a/app/styles/pages/rules_test.css +++ b/app/styles/pages/rules_test.css @@ -30,6 +30,71 @@ overflow: visible; } +.rules-test-page .rules-test-list-shell { + display: flex; + flex-direction: column; + gap: 16px; + overflow: visible; +} + +.rules-test-page .rules-list-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 0; +} + +.rules-test-page .rules-list-title { + display: flex; + align-items: center; + gap: 16px; + min-width: 0; +} + +.rules-test-page .rules-list-title h2 { + margin: 0; + color: #17231f; + font-size: 20px; + font-weight: 500; + letter-spacing: 0; +} + +.rules-test-page .rules-list-total { + display: inline-flex; + align-items: center; + gap: 4px; + min-height: 28px; + padding: 4px 12px; + border-radius: 6px; + background: #fff; + color: #6b7b73; + font-size: 14px; +} + +.rules-test-page .rules-list-total i, +.rules-test-page .rules-list-total strong { + color: #00684a; +} + +.rules-test-page .rules-list-total strong { + margin-left: 2px; + font-size: 16px; + font-weight: 400; +} + +.rules-test-page .rules-test-filter-panel { + margin-bottom: 0; +} + +.rules-test-page .rules-test-filter-panel .filter-list { + align-items: flex-end; +} + +.rules-test-page .rules-test-filter-panel .filter-actions { + align-self: flex-end; +} + .rules-test-page .rules-test-hero { border: 1px solid #dfe8e3; background: #fff;