fix: batch start/stop selected counts

This commit is contained in:
jialin
2025-02-18 19:51:22 +08:00
parent 6ea8cc9986
commit 3a021e7879
9 changed files with 346 additions and 207 deletions
+3
View File
@@ -32,6 +32,9 @@ export default {
colorBgSpotlight: '#3e3e3e' colorBgSpotlight: '#3e3e3e'
// sizePopupArrow: 0 // sizePopupArrow: 0
}, },
Cascader: {
dropdownHeight: 240
},
Slider: { Slider: {
handleSize: 8, handleSize: 8,
handleSizeHover: 8, handleSizeHover: 8,
@@ -0,0 +1,50 @@
import { RightOutlined } from '@ant-design/icons';
import { Button, Checkbox } from 'antd';
import _ from 'lodash';
import React from 'react';
interface HeaderPrefixProps {
expandable?: boolean | React.ReactNode;
enableSelection?: boolean;
onSelectAll?: (e: any) => void;
indeterminate?: boolean;
selectAll?: boolean;
}
const HeaderPrefix: React.FC<HeaderPrefixProps> = (props) => {
const { expandable, enableSelection, onSelectAll, indeterminate, selectAll } =
props;
if (expandable && enableSelection) {
return (
<div className="header-row-prefix-wrapper">
<span style={{ marginRight: 5, padding: '0 14px' }}></span>
<Checkbox
onChange={onSelectAll}
indeterminate={indeterminate}
checked={selectAll}
></Checkbox>
</div>
);
}
if (expandable) {
return (
<div className="header-row-prefix-wrapper">
{_.isBoolean(expandable) ? (
<Button type="text" size="small">
<RightOutlined />
</Button>
) : (
expandable
)}
</div>
);
}
if (enableSelection) {
return (
<div className="header-row-prefix-wrapper">{<Checkbox></Checkbox>}</div>
);
}
return null;
};
export default React.memo(HeaderPrefix);
@@ -2,18 +2,122 @@ import { CheckOutlined, FormOutlined, UndoOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button, Input, InputNumber, Tooltip } from 'antd'; import { Button, Input, InputNumber, Tooltip } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import _ from 'lodash';
import React, { useContext, useEffect } from 'react'; import React, { useContext, useEffect } from 'react';
import RowContext from '../row-context'; import RowContext from '../row-context';
import '../styles/cell.less'; import '../styles/cell.less';
import { SealColumnProps } from '../types'; import { SealColumnProps } from '../types';
const SealColumn: React.FC<SealColumnProps> = (props) => { interface EditButtonsProps {
isEditing: boolean;
editable?: any;
handleSubmit: () => void;
handleUndo: () => void;
handleEdit: () => void;
}
interface CellContentProps {
isEditing: boolean;
current: any;
editable: any;
onChange: (val: any) => void;
row?: any;
render?: (text: any, record: any) => React.ReactNode;
}
const EditButtons: React.FC<EditButtonsProps> = (props) => {
const intl = useIntl(); const intl = useIntl();
const { isEditing, editable, handleSubmit, handleUndo, handleEdit } = props;
if (!editable) {
return null;
}
if (isEditing) {
return (
<span className="flex-column">
<Tooltip
key="confirm"
title={intl.formatMessage({ id: 'common.button.confirm' })}
>
<Button
type="text"
size="small"
className="m-l-10"
onClick={handleSubmit}
>
<CheckOutlined />
</Button>
</Tooltip>
<Tooltip
title={intl.formatMessage({ id: 'common.button.cancel' })}
key="undo"
>
<Button
type="text"
size="small"
className="m-l-10"
onClick={handleUndo}
>
<UndoOutlined />
</Button>
</Tooltip>
</span>
);
}
return (
<span className="flex-column">
<Tooltip
key="edit"
title={
_.isBoolean(editable) ? (
intl.formatMessage({ id: 'common.button.edit' })
) : (
<span>{editable.title || ''}</span>
)
}
>
<Button
type="text"
size="small"
className="m-l-10"
onClick={handleEdit}
>
<FormOutlined />
</Button>
</Tooltip>
</span>
);
};
const CellContent: React.FC<CellContentProps> = (props) => {
const { editable, current, isEditing, row, render, onChange } = props;
if (isEditing && editable) {
const isNumType =
typeof editable === 'object' && editable?.valueType === 'number';
return isNumType ? (
<InputNumber
style={{ width: '80px' }}
min={0}
value={current}
onChange={onChange}
/>
) : (
<Input value={current} onChange={(e) => onChange(e.target.value)} />
);
}
if (render) {
return render(current, row);
}
return current;
};
const SealColumn: React.FC<SealColumnProps> = (props) => {
const { row, onCell } = useContext(RowContext); const { row, onCell } = useContext(RowContext);
const { dataIndex, render, align, editable } = props; const { dataIndex, render, align, editable } = props;
const [isEditing, setIsEditing] = React.useState(false); const [isEditing, setIsEditing] = React.useState(false);
const [current, setCurrent] = React.useState(row[dataIndex]); const [current, setCurrent] = React.useState(row[dataIndex]);
const cachedValue = React.useRef(row[dataIndex]); const cachedValue = React.useRef(null);
const handleEdit = () => { const handleEdit = () => {
setIsEditing(true); setIsEditing(true);
@@ -35,40 +139,16 @@ const SealColumn: React.FC<SealColumnProps> = (props) => {
setCurrent(cachedValue.current); setCurrent(cachedValue.current);
setIsEditing(false); setIsEditing(false);
}; };
useEffect(() => {
if (editable) {
cachedValue.current = row[dataIndex];
}
setCurrent(row[dataIndex]);
}, [row[dataIndex], editable]);
const renderContent = () => { const handleValueChange = (val: any) => {
if (isEditing && editable) { setCurrent(val);
const isNumType =
typeof editable === 'object' && editable.valueType === 'number';
return isNumType ? (
<InputNumber
style={{ width: '80px' }}
min={0}
value={current}
onChange={(val) => {
setCurrent(val as any);
}}
/>
) : (
<Input
value={current}
onChange={(e) => {
setCurrent(e.target.value);
}}
/>
);
}
if (render) {
return render(current, row);
}
return current;
}; };
useEffect(() => {
cachedValue.current = row[dataIndex];
setCurrent(row[dataIndex]);
}, [row[dataIndex]]);
return ( return (
<div <div
className={classNames('cell', { className={classNames('cell', {
@@ -78,55 +158,24 @@ const SealColumn: React.FC<SealColumnProps> = (props) => {
})} })}
> >
<span className="cell-content flex-center"> <span className="cell-content flex-center">
{renderContent()} <CellContent
{editable && ( onChange={handleValueChange}
<span className="flex-column"> isEditing={isEditing}
{isEditing ? ( editable={editable}
<> current={current}
<Tooltip row={row}
key="confirm" render={render}
title={intl.formatMessage({ id: 'common.button.confirm' })} ></CellContent>
> <EditButtons
<Button editable={editable}
type="text" isEditing={isEditing}
size="small" handleEdit={handleEdit}
className="m-l-10" handleSubmit={handleSubmit}
onClick={handleSubmit} handleUndo={handleUndo}
> ></EditButtons>
<CheckOutlined />
</Button>
</Tooltip>
<Tooltip
title={intl.formatMessage({ id: 'common.button.cancel' })}
key="undo"
>
<Button
type="text"
size="small"
className="m-l-10"
onClick={handleUndo}
>
<UndoOutlined />
</Button>
</Tooltip>
</>
) : (
<Tooltip key="edit" title={<span>{editable?.title}</span>}>
<Button
type="text"
size="small"
className="m-l-10"
onClick={handleEdit}
>
<FormOutlined />
</Button>
</Tooltip>
)}
</span>
)}
</span> </span>
</div> </div>
); );
}; };
export default React.memo(SealColumn); export default SealColumn;
@@ -0,0 +1,72 @@
import { Empty } from 'antd';
import React from 'react';
import TableRow from './table-row';
interface TableBodyProps {
dataSource: any[];
columns: React.ReactNode[];
rowKey: string;
rowSelection?: any;
expandable?: any;
expandedRowKeys?: any;
onExpand?: any;
childParentKey?: any;
pollingChildren?: any;
watchChildren?: any;
renderChildren?: any;
loadChildren?: any;
loadChildrenAPI?: any;
onCell?: any;
}
const TableBody: React.FC<TableBodyProps> = ({
dataSource,
rowKey,
rowSelection,
expandable,
expandedRowKeys,
onExpand,
childParentKey,
pollingChildren,
watchChildren,
renderChildren,
loadChildren,
loadChildrenAPI,
columns,
onCell
}) => {
if (!dataSource.length) {
return (
<div className="empty-wrapper">
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
</div>
);
}
return (
<div className="seal-table-content">
{dataSource.map((item, index) => (
<TableRow
key={item[rowKey]}
record={item}
rowIndex={index}
columns={columns}
rowSelection={rowSelection}
expandable={expandable}
rowKey={rowKey}
childParentKey={childParentKey}
pollingChildren={pollingChildren}
watchChildren={watchChildren}
renderChildren={renderChildren}
loadChildren={loadChildren}
loadChildrenAPI={loadChildrenAPI}
onCell={onCell}
onExpand={onExpand}
expandedRowKeys={expandedRowKeys}
/>
))}
</div>
);
};
export default TableBody;
+74 -117
View File
@@ -1,16 +1,9 @@
import { RightOutlined } from '@ant-design/icons'; import { Pagination, Spin, type PaginationProps } from 'antd';
import {
Button,
Checkbox,
Empty,
Pagination,
Spin,
type PaginationProps
} from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useState } from 'react';
import Header from './components/header'; import Header from './components/header';
import TableRow from './components/table-row'; import HeaderPrefix from './components/header-prefix';
import TableBody from './components/table-body';
import './styles/index.less'; import './styles/index.less';
import { SealTableProps } from './types'; import { SealTableProps } from './types';
@@ -36,42 +29,58 @@ const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
loadChildren, loadChildren,
loadChildrenAPI loadChildrenAPI
} = props; } = props;
console.log('sealtable===='); const [selectState, setSelectState] = useState({
const [selectAll, setSelectAll] = useState(false); selectAll: false,
const [indeterminate, setIndeterminate] = useState(false); indeterminate: false
const tableContent = useRef(null); });
useEffect(() => { useEffect(() => {
if (rowSelection) { if (rowSelection) {
const { selectedRowKeys } = rowSelection; const { selectedRowKeys } = rowSelection;
if (selectedRowKeys?.length === 0) { const selectedKeys = new Set(rowSelection.selectedRowKeys);
setSelectAll(false); const allRowKeys = props.dataSource.map((record) => record[rowKey]);
setIndeterminate(false); if (!selectedRowKeys?.length) {
} else if ( setSelectState({
selectedRowKeys?.length === props.dataSource.length && selectAll: false,
selectedRowKeys.length > 0 indeterminate: false
) { });
setSelectAll(true); } else if (allRowKeys.every((key) => selectedKeys.has(key))) {
setIndeterminate(false); setSelectState({
selectAll: true,
indeterminate: false
});
} else { } else {
setSelectAll(false); setSelectState({
setIndeterminate(true); selectAll: false,
indeterminate: true
});
} }
} }
}, [rowSelection, props.dataSource]); }, [rowSelection?.selectedRowKeys, props.dataSource, rowKey]);
const selectAllRows = () => {
const allKeys = new Set([
...props.dataSource.map((record) => record[rowKey]),
...(rowSelection?.selectedRowKeys || [])
]);
const allDatas = _.uniqBy(
[...props.dataSource, ...(rowSelection?.selectedRows || [])],
(record: any) => record[rowKey]
);
rowSelection?.onChange([...allKeys], allDatas);
};
const deselectAllRows = () => {
const currentKeys = props.dataSource.map((record) => record[rowKey]);
rowSelection?.removeSelectedKeys?.(currentKeys);
};
const handleSelectAllChange = (e: any) => { const handleSelectAllChange = (e: any) => {
if (e.target.checked) { if (e.target.checked) {
rowSelection?.onChange( selectAllRows();
props.dataSource.map((record) => record[rowKey]),
props.dataSource
);
setSelectAll(true);
setIndeterminate(false);
} else { } else {
rowSelection?.onChange([], []); deselectAllRows();
setSelectAll(false);
setIndeterminate(false);
} }
}; };
@@ -83,89 +92,37 @@ const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
pagination?.onShowSizeChange?.(current, size); pagination?.onShowSizeChange?.(current, size);
}; };
const renderHeaderPrefix = useMemo(() => {
if (expandable && rowSelection) {
return (
<div className="header-row-prefix-wrapper">
<span style={{ marginRight: 5, padding: '0 14px' }}></span>
<Checkbox
onChange={handleSelectAllChange}
indeterminate={indeterminate}
checked={selectAll}
></Checkbox>
</div>
);
}
if (expandable) {
return (
<div className="header-row-prefix-wrapper">
{_.isBoolean(expandable) ? (
<Button type="text" size="small">
<RightOutlined />
</Button>
) : (
expandable
)}
</div>
);
}
if (rowSelection) {
return (
<div className="header-row-prefix-wrapper">{<Checkbox></Checkbox>}</div>
);
}
return null;
}, [expandable, rowSelection, selectAll, indeterminate]);
const renderContent = useMemo(() => {
if (!props.dataSource.length) {
return (
<div className="empty-wrapper">
<Empty
image={loadend ? Empty.PRESENTED_IMAGE_SIMPLE : null}
description={loadend ? undefined : null}
></Empty>
</div>
);
}
return (
<div className="seal-table-content" ref={tableContent}>
{props.dataSource.map((item, index) => {
return (
<TableRow
record={item}
rowIndex={index}
columns={children}
key={item[rowKey]}
rowSelection={rowSelection}
expandable={expandable}
rowKey={rowKey}
childParentKey={childParentKey}
pollingChildren={pollingChildren}
watchChildren={watchChildren}
renderChildren={renderChildren}
loadChildren={loadChildren}
loadChildrenAPI={loadChildrenAPI}
onCell={onCell}
onExpand={onExpand}
expandedRowKeys={expandedRowKeys}
></TableRow>
);
})}
</div>
);
}, [props.dataSource, expandedRowKeys, rowSelection, children]);
return ( return (
<> <>
<div className="seal-table-container"> <div className="seal-table-container">
{ <div className="header-row-wrapper">
<div className="header-row-wrapper"> <HeaderPrefix
{renderHeaderPrefix} selectAll={selectState.selectAll}
<Header onSort={onSort}>{children}</Header> indeterminate={selectState.indeterminate}
</div> onSelectAll={handleSelectAllChange}
} expandable={expandable}
enableSelection={rowSelection?.enableSelection}
<Spin spinning={loading}>{renderContent}</Spin> ></HeaderPrefix>
<Header onSort={onSort}>{children}</Header>
</div>
<Spin spinning={loading}>
<TableBody
dataSource={props.dataSource}
columns={children}
rowSelection={rowSelection}
expandable={expandable}
rowKey={rowKey}
childParentKey={childParentKey}
pollingChildren={pollingChildren}
watchChildren={watchChildren}
renderChildren={renderChildren}
loadChildren={loadChildren}
loadChildrenAPI={loadChildrenAPI}
onCell={onCell}
onExpand={onExpand}
expandedRowKeys={expandedRowKeys}
/>
</Spin>
</div> </div>
{pagination && ( {pagination && (
<div className="pagination-wrapper"> <div className="pagination-wrapper">
@@ -180,4 +137,4 @@ const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
); );
}; };
export default React.memo(SealTable); export default SealTable;
+4 -2
View File
@@ -14,7 +14,7 @@ export interface SealColumnProps {
| boolean | boolean
| { | {
valueType?: 'text' | 'number' | 'date' | 'datetime' | 'time'; valueType?: 'text' | 'number' | 'date' | 'datetime' | 'time';
title?: string; title?: React.ReactNode;
}; };
valueType?: 'text' | 'number' | 'date' | 'datetime' | 'time'; valueType?: 'text' | 'number' | 'date' | 'datetime' | 'time';
sortOrder?: 'ascend' | 'descend' | null; sortOrder?: 'ascend' | 'descend' | null;
@@ -27,7 +27,7 @@ export interface TableHeaderProps {
dataIndex: string; dataIndex: string;
onSort?: (dataIndex: string, order: 'ascend' | 'descend') => void; onSort?: (dataIndex: string, order: 'ascend' | 'descend') => void;
children?: React.ReactNode; children?: React.ReactNode;
title: string; title: React.ReactNode;
style?: React.CSSProperties; style?: React.CSSProperties;
firstCell?: boolean; firstCell?: boolean;
lastCell?: boolean; lastCell?: boolean;
@@ -37,6 +37,8 @@ export interface TableHeaderProps {
export interface RowSelectionProps { export interface RowSelectionProps {
selectedRowKeys: React.Key[]; selectedRowKeys: React.Key[];
selectedRows: any[]; selectedRows: any[];
enableSelection: boolean;
removeSelectedKeys: (rowKeys: React.Key[]) => void;
onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => void; onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => void;
} }
export interface SealTableProps { export interface SealTableProps {
+10 -4
View File
@@ -19,22 +19,28 @@ export default function useTableRowSelection() {
const removeSelectedKey = (rowKey: React.Key | React.Key[]) => { const removeSelectedKey = (rowKey: React.Key | React.Key[]) => {
if (Array.isArray(rowKey)) { if (Array.isArray(rowKey)) {
setSelectedRowKeys((keys) => keys.filter((key) => !rowKey.includes(key))); const keys = selectedRowKeys.filter((key) => !rowKey.includes(key));
setSelectedRowKeys(keys);
setSelectedRows((rows) => rows.filter((row) => !rowKey.includes(row.id))); setSelectedRows((rows) => rows.filter((row) => !rowKey.includes(row.id)));
return; return keys;
} }
setSelectedRowKeys((keys) => keys.filter((key) => key !== rowKey)); const keys = selectedRowKeys.filter((key) => key !== rowKey);
setSelectedRowKeys(keys);
setSelectedRows((rows) => rows.filter((row) => row.id !== rowKey)); setSelectedRows((rows) => rows.filter((row) => row.id !== rowKey));
return keys;
}; };
const removeSelectedKeys = (rowKeys: React.Key[]) => { const removeSelectedKeys = (rowKeys: React.Key[]) => {
setSelectedRowKeys((keys) => keys.filter((key) => !rowKeys.includes(key))); const keys = selectedRowKeys.filter((key) => !rowKeys.includes(key));
setSelectedRowKeys(keys);
setSelectedRows((rows) => rows.filter((row) => !rowKeys.includes(row.id))); setSelectedRows((rows) => rows.filter((row) => !rowKeys.includes(row.id)));
return keys;
}; };
const rowSelection = { const rowSelection = {
selectedRowKeys, selectedRowKeys,
selectedRows, selectedRows,
enableSelection: true,
clearSelections, clearSelections,
onChange: onSelectChange, onChange: onSelectChange,
removeSelectedKeys, removeSelectedKeys,
+3 -3
View File
@@ -321,7 +321,6 @@ const Models: React.FC<ModelsProps> = ({
replicas: 1 replicas: 1
} }
}); });
message.success(intl.formatMessage({ id: 'common.message.success' }));
updateExpandedRowKeys([row.id, ...expandedRowKeys]); updateExpandedRowKeys([row.id, ...expandedRowKeys]);
} catch (error) { } catch (error) {
// ingore // ingore
@@ -531,7 +530,7 @@ const Models: React.FC<ModelsProps> = ({
}; };
const handleSelect = useCallback( const handleSelect = useCallback(
(val: any, row: ListItem) => { async (val: any, row: ListItem) => {
if (val === 'edit') { if (val === 'edit') {
handleEdit(row); handleEdit(row);
} }
@@ -542,7 +541,8 @@ const Models: React.FC<ModelsProps> = ({
handleDelete(row); handleDelete(row);
} }
if (val === 'start') { if (val === 'start') {
handleStartModel(row); await handleStartModel(row);
message.success(intl.formatMessage({ id: 'common.message.success' }));
} }
if (val === 'stop') { if (val === 'stop') {