🐛 fix(data-grid): 修复当前页查找高亮残留并压缩旧版结果工具栏

- 当前页查找改为即时响应,清空或按 Esc 后立即取消高亮
- 查找渲染版本元数据改为可透传,避免高亮状态残留
- 旧版结果工具栏调整为紧凑单行布局并移除重复分页信息
- JSON 和文本视图隐藏当前页查找入口
This commit is contained in:
Syngnat
2026-05-31 13:33:46 +08:00
parent bea16b72df
commit e5fb03bbcd
7 changed files with 228 additions and 86 deletions

View File

@@ -10,6 +10,7 @@ import DataGrid, {
resolveDefaultGridFilterOperator,
resolveNextGridFilterOperatorForColumnChange,
} from './DataGrid';
import DataGridPaginationBar from './DataGridPaginationBar';
import { cloneShortcutOptions, DEFAULT_SHORTCUT_OPTIONS } from '../utils/shortcuts';
vi.mock('../store', () => ({
@@ -108,6 +109,85 @@ describe('DataGrid layout', () => {
expect(markup).toContain('当前页查找...');
});
it('hides current-page find in JSON and text record views', () => {
const source = readFileSync(new URL('./DataGrid.tsx', import.meta.url), 'utf8');
expect(source).toContain("const visiblePageFindContent = viewMode === 'table' ? pageFindContent : null;");
expect(source).toContain('pageFindContent={visiblePageFindContent}');
});
it('keeps legacy secondary actions aligned on a shared search-row baseline', () => {
const source = readFileSync(new URL('./DataGridSecondaryActions.tsx', import.meta.url), 'utf8');
const columnQuickFindSource = readFileSync(new URL('./DataGridColumnQuickFind.tsx', import.meta.url), 'utf8');
const pageFindSource = readFileSync(new URL('./DataGridPageFind.tsx', import.meta.url), 'utf8');
const dataGridSource = readFileSync(new URL('./DataGrid.tsx', import.meta.url), 'utf8');
const paginationSource = readFileSync(new URL('./DataGridPaginationBar.tsx', import.meta.url), 'utf8');
expect(source).toContain('data-grid-legacy-secondary-actions="true"');
expect(source).toContain('data-grid-legacy-secondary-row="primary"');
expect(source).toContain('data-grid-legacy-secondary-row="search"');
expect(source).toContain('data-grid-legacy-result-view-switcher="true"');
expect(source).toContain('data-grid-legacy-column-quick-find="true"');
expect(source).toContain('data-grid-legacy-page-find="true"');
expect(source).toContain('data-grid-legacy-pagination="true"');
expect(source).toContain("justifyContent: 'flex-start'");
expect(source).toContain('minHeight: 32');
expect(source).toContain("style={{ display: 'flex', minWidth: 0, marginLeft: 'auto' }}");
expect(source).toContain("flex: '0 1 240px'");
expect(source).toContain("flex: '0 1 auto'");
expect(columnQuickFindSource).not.toContain('定位字段列');
expect(columnQuickFindSource).toContain("flexWrap: 'nowrap'");
expect(columnQuickFindSource).toContain("width: 168");
expect(columnQuickFindSource).toContain('height: 32');
expect(columnQuickFindSource).toContain('const legacyDropdownOpen =');
expect(columnQuickFindSource).toContain('open={isV2Ui ? undefined : legacyDropdownOpen}');
expect(columnQuickFindSource).toContain('onSubmit: (value?: string) => void;');
expect(columnQuickFindSource).toContain('onSubmit(nextValue);');
expect(columnQuickFindSource).toContain('onPressEnter={() => onSubmit(value)}');
expect(columnQuickFindSource).not.toContain('data-grid-column-quick-find-submit=');
expect(columnQuickFindSource).not.toContain(" '跳转'");
expect(pageFindSource).toContain("gap: 8");
expect(pageFindSource).toContain("flexWrap: 'nowrap'");
expect(pageFindSource).toContain('height: 32');
expect(pageFindSource).not.toContain("flexDirection: 'column'");
expect(pageFindSource).not.toContain(" '上一个'");
expect(pageFindSource).not.toContain(" '下一个'");
expect(pageFindSource).toContain("paddingInline: 8");
expect(pageFindSource).toContain("whiteSpace: 'nowrap'");
expect(pageFindSource).toContain("onCancel: () => void;");
expect(pageFindSource).toContain("if (event.key === 'Escape')");
expect(pageFindSource).toContain('onCancel();');
expect(pageFindSource).toContain("textAlign: 'left'");
expect(dataGridSource).toContain("const normalizedPageFindText = useMemo(() => normalizeDataGridFindQuery(pageFindText), [pageFindText]);");
expect(dataGridSource).not.toContain("const normalizedPageFindText = useMemo(() => normalizeDataGridFindQuery(deferredPageFindText), [deferredPageFindText]);");
expect(paginationSource).toContain("padding: 0");
expect(paginationSource).toContain("justifyContent: 'flex-start'");
});
it('avoids duplicating legacy pagination page text beside the pager', () => {
const markup = renderToStaticMarkup(
<DataGridPaginationBar
isV2Ui={false}
pagination={{
current: 1,
pageSize: 100,
total: 24,
}}
paginationV2SummaryText="24 行"
paginationSummaryText="当前 24 条 / 共 24 条"
paginationControlTotal={24}
paginationTotalPages={1}
paginationPageSizeOptions={['100', '200']}
onPageChange={() => {}}
onPageSizeChange={() => {}}
onV2PageStep={() => {}}
/>,
);
expect(markup).toContain('class="ant-pagination');
expect(markup).not.toContain('第 1 / 1 页');
});
it('renders the v2 DataGrid toolbar using the redesigned topbar hooks', () => {
const markup = renderToStaticMarkup(
<DataGrid
@@ -375,7 +455,11 @@ describe('DataGrid layout', () => {
const css = readFileSync(new URL('../v2-theme.css', import.meta.url), 'utf8');
expect(source).toContain('virtualHorizontalElementsRef');
expect(source).toContain('const handleSubmitColumnQuickFind = useCallback(() => {');
expect(source).toContain('const handleSubmitColumnQuickFind = useCallback((submittedValue?: string) => {');
expect(source).toContain('const effectiveQuery = String(submittedValue ?? columnQuickFindText);');
expect(source).toContain('resolveDataGridColumnQuickFindTarget(displayColumnNames, query)');
expect(source).toContain("onCancel={() => setPageFindText('')}");
expect(source).toContain('enumerable: true');
expect(source).toContain('resolveDataGridColumnQuickFindScrollLeft({');
expect(source).toContain('const applied = applyVirtualHorizontalOffset(tableContainer, nextScrollLeft);');
expect(source).toContain('syncExternalScrollFromTargets();');

View File

@@ -36,7 +36,7 @@ import {
resolveDataTableColumnWidth,
resolveDataTableVerticalBorderColor,
} from '../utils/dataGridDisplay';
import { resolvePaginationPageText, resolvePaginationSummaryText, resolvePaginationTotalForControl } from '../utils/dataGridPagination';
import { resolvePaginationSummaryText, resolvePaginationTotalForControl } from '../utils/dataGridPagination';
import { resolveGridSortInfoFromTableSorter } from '../utils/dataGridSort';
import {
calculateExternalHorizontalScrollInnerWidth,
@@ -393,7 +393,7 @@ export const attachDataGridVirtualEditRenderVersion = <T extends Item>(
const nextRow = { ...(row as object) } as T;
Object.defineProperty(nextRow, DATA_GRID_VIRTUAL_EDIT_RENDER_VERSION, {
value: `${editingCell.rowKey}${CELL_KEY_SEP}${editingCell.dataIndex}`,
enumerable: false,
enumerable: true,
});
return nextRow;
});
@@ -410,7 +410,7 @@ export const attachDataGridDisplayRenderVersion = <T extends Item>(
const nextRow = { ...(row as object) } as T;
Object.defineProperty(nextRow, DATA_GRID_DISPLAY_RENDER_VERSION, {
value: renderVersion,
enumerable: false,
enumerable: true,
});
return nextRow;
});
@@ -1568,9 +1568,9 @@ const DataGrid: React.FC<DataGridProps> = ({
const [pageFindText, setPageFindText] = useState('');
const [activePageFindMatchIndex, setActivePageFindMatchIndex] = useState(-1);
const columnQuickFindHighlightTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const deferredPageFindText = useDeferredValue(pageFindText);
const deferredColumnQuickFindText = useDeferredValue(columnQuickFindText);
const normalizedPageFindText = useMemo(() => normalizeDataGridFindQuery(deferredPageFindText), [deferredPageFindText]);
// 当前页查找需要即时反馈;否则清空输入框后高亮会继续停留一拍。
const normalizedPageFindText = useMemo(() => normalizeDataGridFindQuery(pageFindText), [pageFindText]);
const normalizedColumnQuickFindText = useMemo(
() => normalizeDataGridFindQuery(deferredColumnQuickFindText),
[deferredColumnQuickFindText],
@@ -6531,8 +6531,8 @@ const DataGrid: React.FC<DataGridProps> = ({
const effectiveQuery = String(submittedValue ?? columnQuickFindText);
const targetColumnName = resolveColumnQuickFindTarget(effectiveQuery);
if (!targetColumnName) {
if (columnQuickFindText.trim()) {
void message.warning(`未找到字段列:${columnQuickFindText.trim()}`);
if (effectiveQuery.trim()) {
void message.warning(`未找到字段列:${effectiveQuery.trim()}`);
}
return;
}
@@ -6870,14 +6870,6 @@ const DataGrid: React.FC<DataGridProps> = ({
});
}, [pagination, prefersManualTotalCount, supportsApproximateTableCount]);
const paginationPageText = useMemo(() => {
if (!pagination) return '';
return resolvePaginationPageText({
pagination,
supportsApproximateTotalPages,
});
}, [pagination, supportsApproximateTotalPages]);
const paginationControlTotal = useMemo(() => {
if (!pagination) return 0;
return resolvePaginationTotalForControl({
@@ -7024,10 +7016,12 @@ const DataGrid: React.FC<DataGridProps> = ({
occurrenceCount={pageFindSummary.occurrenceCount}
matchedCellCount={pageFindSummary.matchedCellCount}
onPageFindTextChange={setPageFindText}
onCancel={() => setPageFindText('')}
onNavigatePrevious={() => handleNavigatePageFind('previous')}
onNavigateNext={() => handleNavigatePageFind('next')}
/>
);
const visiblePageFindContent = viewMode === 'table' ? pageFindContent : null;
const columnQuickFindContent = isTableSurfaceActive ? (
<DataGridColumnQuickFind
isV2Ui={isV2Ui}
@@ -7054,7 +7048,6 @@ const DataGrid: React.FC<DataGridProps> = ({
pagination={pagination}
paginationV2SummaryText={paginationV2SummaryText}
paginationSummaryText={paginationSummaryText}
paginationPageText={paginationPageText}
paginationControlTotal={paginationControlTotal}
paginationTotalPages={paginationTotalPages}
paginationPageSizeOptions={paginationPageSizeOptions}
@@ -7513,7 +7506,7 @@ const DataGrid: React.FC<DataGridProps> = ({
resultViewSwitcher={resultViewSwitcher}
columnInfoSettingContent={columnInfoSettingContent}
columnQuickFindContent={columnQuickFindContent}
pageFindContent={pageFindContent}
pageFindContent={visiblePageFindContent}
paginationContent={paginationContent}
onViewModeChange={handleViewModeChange}
dataPanelOpen={dataPanelOpen}

View File

@@ -14,6 +14,7 @@ export interface DataGridPageFindProps {
occurrenceCount: number;
matchedCellCount: number;
onPageFindTextChange: (value: string) => void;
onCancel: () => void;
onNavigatePrevious: () => void;
onNavigateNext: () => void;
}
@@ -30,6 +31,7 @@ const DataGridPageFind: React.FC<DataGridPageFindProps> = ({
occurrenceCount,
matchedCellCount,
onPageFindTextChange,
onCancel,
onNavigatePrevious,
onNavigateNext,
}) => (
@@ -37,44 +39,57 @@ const DataGridPageFind: React.FC<DataGridPageFindProps> = ({
<div
data-grid-page-find="true"
className={isV2Ui ? 'gn-v2-data-grid-page-find' : undefined}
style={isV2Ui ? undefined : { display: 'flex', alignItems: 'center', gap: 6 }}
style={isV2Ui ? undefined : { display: 'flex', alignItems: 'center', gap: 8, minWidth: 0, flexWrap: 'nowrap', height: 32 }}
>
<div className={isV2Ui ? 'gn-v2-data-grid-page-find-row' : undefined}>
<Input
className={isV2Ui ? 'gn-v2-data-grid-page-find-input' : undefined}
{...inputProps}
allowClear
size="small"
variant="borderless"
prefix={<SearchOutlined />}
placeholder="当前页查找..."
value={pageFindText}
onChange={(event) => onPageFindTextChange(event.target.value)}
style={isV2Ui ? undefined : { width: 220 }}
/>
<Button
data-grid-page-find-prev="true"
className={isV2Ui ? 'gn-v2-data-grid-page-find-prev' : undefined}
size="small"
icon={<LeftOutlined />}
disabled={!hasMatches}
onClick={onNavigatePrevious}
>
{isV2Ui ? null : '上一个'}
</Button>
<Button
data-grid-page-find-next="true"
className={isV2Ui ? 'gn-v2-data-grid-page-find-next' : undefined}
size="small"
icon={<RightOutlined />}
disabled={!hasMatches}
onClick={onNavigateNext}
>
{isV2Ui ? null : '下一个'}
</Button>
</div>
<Input
className={isV2Ui ? 'gn-v2-data-grid-page-find-input' : undefined}
{...inputProps}
allowClear
size="small"
variant="borderless"
prefix={<SearchOutlined />}
placeholder="当前页查找..."
value={pageFindText}
onChange={(event) => onPageFindTextChange(event.target.value)}
onKeyDown={(event) => {
if (event.key === 'Escape') {
event.preventDefault();
event.stopPropagation();
onCancel();
}
}}
style={isV2Ui ? undefined : { width: 168, height: 32 }}
/>
<Button
data-grid-page-find-prev="true"
className={isV2Ui ? 'gn-v2-data-grid-page-find-prev' : undefined}
size="small"
icon={<LeftOutlined />}
disabled={!hasMatches}
onClick={onNavigatePrevious}
style={isV2Ui ? undefined : { height: 32, minWidth: 32, paddingInline: 8 }}
/>
<Button
data-grid-page-find-next="true"
className={isV2Ui ? 'gn-v2-data-grid-page-find-next' : undefined}
size="small"
icon={<RightOutlined />}
disabled={!hasMatches}
onClick={onNavigateNext}
style={isV2Ui ? undefined : { height: 32, minWidth: 32, paddingInline: 8 }}
/>
{normalizedPageFindText && (
<span aria-live="polite" style={isV2Ui ? undefined : { fontSize: 12, color: darkMode ? '#999' : '#666', whiteSpace: 'nowrap' }}>
<span
aria-live="polite"
style={isV2Ui ? undefined : {
fontSize: 12,
color: darkMode ? '#999' : '#666',
lineHeight: 1.4,
whiteSpace: 'nowrap',
textAlign: 'left',
flex: '0 1 auto',
}}
>
{hasMatches ? `${activePageFindPosition} / ${matchCount} · ` : ''} {occurrenceCount} / {matchedCellCount}
</span>
)}

View File

@@ -18,7 +18,6 @@ export interface DataGridPaginationBarProps {
pagination?: DataGridPaginationState;
paginationV2SummaryText: string;
paginationSummaryText: string;
paginationPageText: string;
paginationControlTotal: number;
paginationTotalPages: number;
paginationPageSizeOptions: string[];
@@ -32,7 +31,6 @@ const DataGridPaginationBar: React.FC<DataGridPaginationBarProps> = ({
pagination,
paginationV2SummaryText,
paginationSummaryText,
paginationPageText,
paginationControlTotal,
paginationTotalPages,
paginationPageSizeOptions,
@@ -47,7 +45,7 @@ const DataGridPaginationBar: React.FC<DataGridPaginationBarProps> = ({
return (
<div
className={`${isV2Ui ? 'gn-v2-data-grid-pagination-wrap ' : ''}data-grid-pagination-wrap`}
style={isV2Ui ? undefined : { padding: '12px 0 0', borderTop: 'none', display: 'flex', justifyContent: 'flex-end' }}
style={isV2Ui ? undefined : { padding: 0, borderTop: 'none', display: 'flex', justifyContent: 'flex-start' }}
>
{isV2Ui ? (
<div className="data-grid-pagination-shell" data-grid-v2-pagination="true">
@@ -89,7 +87,6 @@ const DataGridPaginationBar: React.FC<DataGridPaginationBarProps> = ({
<span className="data-grid-pagination-kicker"></span>
<span className="data-grid-pagination-summary-value">{paginationSummaryText}</span>
</div>
<div className="data-grid-pagination-page-chip">{paginationPageText}</div>
<Pagination
current={pagination.current}
pageSize={pagination.pageSize}

View File

@@ -125,43 +125,87 @@ const DataGridSecondaryActions: React.FC<DataGridSecondaryActionsProps> = ({
<>
<div
data-grid-secondary-actions="true"
data-grid-legacy-secondary-actions="true"
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: 10,
flexWrap: 'wrap',
flexDirection: 'column',
gap: 4,
padding: '4px 0 0',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<Button
icon={<EditOutlined />}
type={dataPanelOpen ? 'primary' : 'default'}
disabled={!isTableSurfaceActive}
onClick={onToggleDataPanel}
>
</Button>
<Popover trigger="click" placement="bottomRight" content={columnInfoSettingContent}>
<Button data-grid-column-display-action="true" icon={<FileTextOutlined />}></Button>
</Popover>
{columnQuickFindContent}
{canViewDdl && (
<div
data-grid-legacy-secondary-row="primary"
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
flexWrap: 'wrap',
justifyContent: 'flex-start',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', flex: '0 1 auto', minWidth: 0 }}>
<Button
data-grid-ddl-action="true"
icon={<FileTextOutlined />}
loading={ddlLoading}
onClick={onOpenTableDdl}
icon={<EditOutlined />}
type={dataPanelOpen ? 'primary' : 'default'}
disabled={!isTableSurfaceActive}
onClick={onToggleDataPanel}
>
DDL
</Button>
)}
{pageFindContent}
<Popover trigger="click" placement="bottomRight" content={columnInfoSettingContent}>
<Button data-grid-column-display-action="true" icon={<FileTextOutlined />}></Button>
</Popover>
{canViewDdl && (
<Button
data-grid-ddl-action="true"
icon={<FileTextOutlined />}
loading={ddlLoading}
onClick={onOpenTableDdl}
>
DDL
</Button>
)}
</div>
<div
data-grid-legacy-result-view-switcher="true"
style={{ display: 'flex', alignItems: 'center', minWidth: 0 }}
>
{resultViewSwitcher}
</div>
</div>
<div
data-grid-legacy-secondary-row="search"
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
flexWrap: 'wrap',
justifyContent: 'flex-start',
minHeight: 32,
}}
>
{columnQuickFindContent ? (
<div
data-grid-legacy-column-quick-find="true"
style={{ display: 'flex', flex: '0 1 240px', minWidth: 0 }}
>
{columnQuickFindContent}
</div>
) : null}
<div
data-grid-legacy-page-find="true"
style={{ display: 'flex', flex: '0 1 auto', minWidth: 0 }}
>
{pageFindContent}
</div>
<div
data-grid-legacy-pagination="true"
style={{ display: 'flex', minWidth: 0, marginLeft: 'auto' }}
>
{paginationContent}
</div>
</div>
{resultViewSwitcher}
</div>
{paginationContent}
</>
);
};

View File

@@ -114,4 +114,13 @@ describe('dataGridFind', () => {
expect(hasDataGridFindRenderVersionChanged(betaRows[0], alphaRows[0])).toBe(true);
expect(hasDataGridFindRenderVersionChanged(rows[0], alphaRows[0])).toBe(true);
});
it('keeps find render metadata on symbol keys while allowing wrapped rows to preserve it', () => {
const rows = [{ id: 1, name: 'Alpha' }];
const alphaRows = attachDataGridFindRenderVersion(rows, 'alpha');
expect(Object.keys(alphaRows[0])).toEqual(['id', 'name']);
expect(Object.getOwnPropertySymbols(alphaRows[0]).length).toBeGreaterThan(0);
expect(hasDataGridFindRenderVersionChanged(alphaRows[0], rows[0])).toBe(true);
});
});

View File

@@ -55,7 +55,7 @@ export const attachDataGridFindRenderVersion = <T>(rows: T[], query: string): T[
const nextRow = { ...(row as object) } as T;
Object.defineProperty(nextRow, DATA_GRID_FIND_RENDER_VERSION, {
value: normalizedQuery,
enumerable: false,
enumerable: true,
});
return nextRow;
});