{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;