对齐规则列表页交互样式
This commit is contained in:
@@ -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<typeof loader>() as LoaderData;
|
||||
const { rows, filters, options, totalCount, currentPage, pageSize } = useLoaderData<typeof loader>() as LoaderData;
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const updateParams = (updates: Record<string, string | number | undefined>, 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<HTMLSelectElement>) => {
|
||||
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 (
|
||||
<div className="rules-test-page rules-page">
|
||||
<div className="page-shell">
|
||||
<div className="rules-test-list-shell">
|
||||
<div className="rules-list-header">
|
||||
<div className="rules-list-title">
|
||||
<h2>评查点管理</h2>
|
||||
<div className="rules-list-total">
|
||||
<i className="ri-file-list-3-line"></i>
|
||||
<span>总评查点数:</span>
|
||||
<strong>{totalCount}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FilterPanel
|
||||
className="rules-test-filter-panel px-3 py-3"
|
||||
noActionDivider={true}
|
||||
actions={
|
||||
<Button type="default" icon="ri-refresh-line" onClick={handleReset} className="mr-2 hover:!border-gray-300">
|
||||
重置
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<FilterSelect
|
||||
label="所属规则组"
|
||||
name="ruleGroup"
|
||||
value={filters.ruleGroup}
|
||||
options={options.ruleGroups.map(group => ({ value: group, label: group }))}
|
||||
onChange={handleFilterChange}
|
||||
className="mr-3 w-[22%]"
|
||||
/>
|
||||
|
||||
<FilterSelect
|
||||
label="子类型"
|
||||
name="subtype"
|
||||
value={filters.subtype}
|
||||
options={options.subtypes.map(subtype => ({ value: subtype, label: subtype }))}
|
||||
onChange={handleFilterChange}
|
||||
className="mr-3 w-[18%]"
|
||||
/>
|
||||
|
||||
<SearchFilter
|
||||
key={filters.keyword}
|
||||
label="搜索"
|
||||
placeholder="输入评查点名称或编码"
|
||||
value={filters.keyword}
|
||||
buttonText="搜索"
|
||||
onSearch={handleSearch}
|
||||
className="min-w-[200px] flex-1"
|
||||
/>
|
||||
</FilterPanel>
|
||||
|
||||
<Card className="ant-card">
|
||||
<Table
|
||||
className="rules-test-table rules-table"
|
||||
columns={columns}
|
||||
dataSource={rows}
|
||||
rowKey="rowId"
|
||||
scroll={{ y: 700 }}
|
||||
emptyText={<div className="empty-state">暂无规则。当前类型的规则列表流程已保留,可进入配置页查看空 YAML 模板。</div>}
|
||||
/>
|
||||
{totalCount > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
total={totalCount}
|
||||
pageSize={pageSize}
|
||||
onChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
showTotal={true}
|
||||
showPageSizeChanger={true}
|
||||
pageSizeOptions={[10, 20, 30, 50]}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user