feat(datagrid): 增加分页跳页并适配窄屏

- 分页条新增跳页输入与提交按钮,支持回车和点击跳转

- 跳页页码自动限制在有效页码范围内,避免越界分页请求

- 为 v2 状态栏增加容器级响应式规则,适配 AI 面板打开后的窄宽场景

- 分页区域增加横向滚动兜底,避免小尺寸屏幕下控件被挤压变形

- 补充 DataGrid 布局回归测试,覆盖跳页控件和窄屏样式规则
This commit is contained in:
Syngnat
2026-06-01 11:56:56 +08:00
parent b85e7491a9
commit 09139c2553
4 changed files with 256 additions and 5 deletions

View File

@@ -104,6 +104,9 @@ describe('DataGrid layout', () => {
expect(markup).toContain('data-grid-v2-page-chip="true"');
expect(markup).toContain('data-grid-v2-pagination-prev="true"');
expect(markup).toContain('data-grid-v2-pagination-next="true"');
expect(markup).toContain('data-grid-pagination-jump="true"');
expect(markup).toContain('跳页');
expect(markup).toContain('跳转页码');
expect(markup).not.toContain('class="ant-pagination');
expect(markup).not.toContain('class="data-grid-pagination-kicker"');
expect(markup).toContain('当前页查找...');
@@ -545,9 +548,19 @@ describe('DataGrid layout', () => {
expect(secondaryActionsSource.indexOf('{pageFindContent}')).toBeLessThan(secondaryActionsSource.indexOf('gn-v2-data-grid-status-center'));
expect(css).toContain('width: 66px !important;');
expect(css).toContain('grid-template-columns: 160px 26px 26px !important;');
expect(css).toContain('container-name: gn-v2-data-grid-statusbar;');
expect(css).toContain('body[data-ui-version="v2"] .gn-v2-data-grid-status-right::-webkit-scrollbar');
expect(css).toContain('body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap::-webkit-scrollbar');
expect(css).toContain('@container gn-v2-data-grid-statusbar (max-width: 960px)');
expect(css).toContain('@container gn-v2-data-grid-statusbar (max-width: 760px)');
expect(css).toContain('.data-grid-pagination-size-select.ant-select-focused .ant-select-selector');
expect(css).toContain('overflow-x: auto;');
expect(paginationBarSource).toContain("label: `${value}/页`");
expect(paginationBarSource).toContain('const maxJumpPage = Math.max(1, paginationTotalPages);');
expect(paginationBarSource).toContain('Math.min(maxJumpPage, Math.max(1, Math.trunc(Number(jumpPage))))');
expect(paginationBarSource).toContain('onPressEnter={submitJumpPage}');
expect(paginationBarSource).toContain('data-grid-pagination-jump="true"');
expect(css).toContain('.data-grid-pagination-jump-input.ant-input-number-focused');
expect(css).toContain('background: transparent !important;');
});

View File

@@ -2913,6 +2913,57 @@ const DataGrid: React.FC<DataGridProps> = ({
width: 100%;
height: 100%;
}
.${gridId} .data-grid-pagination-jump {
display: inline-flex;
align-items: center;
gap: 6px;
height: 34px;
color: ${paginationSecondaryTextColor};
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.${gridId} .data-grid-pagination-jump-label {
color: ${paginationSecondaryTextColor};
font-variant-numeric: tabular-nums;
}
.${gridId} .data-grid-pagination-jump-input,
.${gridId} .data-grid-pagination-jump-input.ant-input-number {
width: 64px;
min-width: 64px;
height: 34px;
display: inline-flex;
align-items: stretch;
}
.${gridId} .data-grid-pagination-jump-input .ant-input-number-input-wrap,
.${gridId} .data-grid-pagination-jump-input .ant-input-number-input {
height: 100%;
}
.${gridId} .data-grid-pagination-jump-input .ant-input-number-input {
padding: 0 10px;
text-align: center;
color: ${paginationPrimaryTextColor};
font-weight: 600;
font-variant-numeric: tabular-nums;
line-height: 34px;
}
.${gridId} .data-grid-pagination-jump-input.ant-input-number {
border-radius: 12px;
border: 1px solid ${paginationChipBorderColor};
background: ${paginationChipBg};
box-shadow: none;
}
.${gridId} .data-grid-pagination-jump-button.ant-btn {
height: 34px;
min-width: 34px;
padding: 0 10px;
border-radius: 12px;
border-color: ${paginationChipBorderColor};
background: ${paginationChipBg};
color: ${paginationPrimaryTextColor};
font-weight: 700;
box-shadow: none;
}
.${gridId} .data-grid-pagination-size-select {
width: 72px;
min-width: 72px;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Button, Pagination, Select } from 'antd';
import { Button, InputNumber, Pagination, Select } from 'antd';
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
interface DataGridPaginationState {
@@ -38,10 +38,53 @@ const DataGridPaginationBar: React.FC<DataGridPaginationBarProps> = ({
onPageSizeChange,
onV2PageStep,
}) => {
const [jumpPage, setJumpPage] = React.useState<number | null>(pagination?.current ?? null);
React.useEffect(() => {
setJumpPage(pagination?.current ?? null);
}, [pagination?.current]);
if (!pagination) {
return null;
}
const maxJumpPage = Math.max(1, paginationTotalPages);
const normalizedJumpPage = Number.isFinite(Number(jumpPage)) && Number(jumpPage) > 0
? Math.min(maxJumpPage, Math.max(1, Math.trunc(Number(jumpPage))))
: null;
const jumpDisabled = !onPageChange || normalizedJumpPage === null || normalizedJumpPage === pagination.current;
const submitJumpPage = () => {
if (!onPageChange || normalizedJumpPage === null) return;
if (normalizedJumpPage === pagination.current) return;
onPageChange(normalizedJumpPage, pagination.pageSize);
};
const jumpPageControl = (
<div className="data-grid-pagination-jump" data-grid-pagination-jump="true">
<span className="data-grid-pagination-jump-label"></span>
<InputNumber
size="small"
min={1}
max={maxJumpPage}
precision={0}
controls={false}
value={jumpPage}
onChange={(value) => setJumpPage(typeof value === 'number' && Number.isFinite(value) ? value : null)}
onPressEnter={submitJumpPage}
className="data-grid-pagination-jump-input"
aria-label="跳转页码"
disabled={!onPageChange}
/>
<Button
size="small"
className="data-grid-pagination-jump-button"
disabled={jumpDisabled}
onClick={submitJumpPage}
>
</Button>
</div>
);
return (
<div
className={`${isV2Ui ? 'gn-v2-data-grid-pagination-wrap ' : ''}data-grid-pagination-wrap`}
@@ -71,6 +114,7 @@ const DataGridPaginationBar: React.FC<DataGridPaginationBarProps> = ({
disabled={!onPageChange || pagination.current >= paginationTotalPages}
onClick={() => onV2PageStep('next')}
/>
{jumpPageControl}
<Select
size="small"
popupMatchSelectWidth={false}
@@ -105,6 +149,7 @@ const DataGridPaginationBar: React.FC<DataGridPaginationBarProps> = ({
return originalElement;
}}
/>
{jumpPageControl}
<Select
size="small"
popupMatchSelectWidth={false}

View File

@@ -3291,6 +3291,8 @@ body[data-ui-version="v2"] .gn-v2-data-grid .data-grid-virtual-inline-editing .a
body[data-ui-version="v2"] .gn-v2-data-grid-statusbar {
--gn-v2-statusbar-control-height: 26px;
container-type: inline-size;
container-name: gn-v2-data-grid-statusbar;
height: calc(36px * var(--gn-ui-scale, 1));
min-height: calc(36px * var(--gn-ui-scale, 1));
display: flex;
@@ -3325,9 +3327,17 @@ body[data-ui-version="v2"] .gn-v2-data-grid-status-right {
display: inline-flex;
align-items: center;
justify-content: flex-end;
flex: 0 0 auto;
min-width: fit-content;
flex: 0 1 auto;
min-width: 0;
max-width: 100%;
margin-left: auto;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
}
body[data-ui-version="v2"] .gn-v2-data-grid-status-right::-webkit-scrollbar {
display: none;
}
@media (max-width: 1180px) {
@@ -3749,17 +3759,27 @@ body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap {
height: var(--gn-v2-statusbar-control-height);
min-height: var(--gn-v2-statusbar-control-height);
gap: 4px;
flex: 0 0 auto;
flex: 0 1 auto;
min-width: 0;
max-width: 100%;
margin-left: 0;
padding: 0 !important;
border-top: none !important;
background: transparent;
justify-content: flex-end !important;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap::-webkit-scrollbar {
display: none;
}
body[data-ui-version="v2"] .gn-v2-data-grid .data-grid-pagination-shell {
gap: 4px;
flex: 0 0 auto;
flex: 0 0 max-content;
min-width: max-content;
align-items: center !important;
align-self: center;
justify-content: flex-start !important;
@@ -3896,6 +3916,128 @@ body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-paginatio
color: var(--gn-fg-1);
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump {
display: inline-flex;
align-items: center;
gap: 3px;
height: 26px;
min-height: 26px;
color: var(--gn-fg-4);
font-family: var(--gn-font-mono);
font-size: 11px;
line-height: 26px;
white-space: nowrap;
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-label {
color: var(--gn-fg-4);
font-size: 11px;
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-input,
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-input.ant-input-number,
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-button.ant-btn {
height: 26px !important;
min-height: 26px !important;
border-width: 0.5px !important;
border-style: solid !important;
border-color: var(--gn-br-2) !important;
border-radius: 5px !important;
background: var(--gn-bg-input) !important;
box-shadow: none !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-input,
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-input.ant-input-number {
display: inline-flex !important;
align-items: stretch !important;
width: 44px !important;
min-width: 44px !important;
max-width: 44px !important;
line-height: 26px !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-input .ant-input-number-input-wrap,
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-input .ant-input-number-input {
height: 26px !important;
line-height: 26px !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-input .ant-input-number-input {
padding: 0 4px !important;
color: var(--gn-fg-1) !important;
font-family: var(--gn-font-mono);
font-size: 11px !important;
font-weight: 700;
text-align: center;
background: transparent !important;
box-shadow: none !important;
font-variant-numeric: tabular-nums;
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-input.ant-input-number-focused,
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-input.ant-input-number:focus-within {
border-color: color-mix(in srgb, var(--gn-info) 38%, var(--gn-br-2)) !important;
box-shadow: none !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-button.ant-btn {
width: 26px !important;
min-width: 26px !important;
padding: 0 !important;
color: var(--gn-fg-2) !important;
font-size: 11px !important;
font-weight: 700;
}
@container gn-v2-data-grid-statusbar (max-width: 1120px) {
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-summary {
display: none;
}
}
@container gn-v2-data-grid-statusbar (max-width: 960px) {
body[data-ui-version="v2"] .gn-v2-data-grid-page-find {
max-width: 154px;
}
body[data-ui-version="v2"] .gn-v2-data-grid-page-find .ant-input-affix-wrapper {
width: 112px !important;
min-width: 112px;
max-width: 112px !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-label {
display: none;
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-input,
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-jump-input.ant-input-number {
width: 38px !important;
min-width: 38px !important;
max-width: 38px !important;
}
}
@container gn-v2-data-grid-statusbar (max-width: 760px) {
body[data-ui-version="v2"] .gn-v2-data-grid-result-switcher > span {
display: none;
}
body[data-ui-version="v2"] .gn-v2-data-grid-column-quick-find,
body[data-ui-version="v2"] .gn-v2-data-grid-column-quick-find .ant-select,
body[data-ui-version="v2"] .gn-v2-data-grid-column-quick-find .ant-input-affix-wrapper,
body[data-ui-version="v2"] .gn-v2-data-grid-column-quick-find .ant-select-selector {
width: 140px !important;
min-width: 140px !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid-column-quick-find {
grid-template-columns: 140px 28px !important;
max-width: 180px;
}
}
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-size-select,
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-size-select.ant-select,
body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-pagination-size-select.ant-select-single,