javascript
import React, { useState } from "react";
import { Flex, Space, Table, Tag, Button, Modal } from 'antd';
import type { TableProps } from 'antd';
import { ExclamationCircleFilled } from '@ant-design/icons';
// 解构出 Modal 的 confirm 方法,用于显示确认对话框
const { confirm } = Modal;
// 定义表格数据的类型接口
interface DataType {
key: string;
name: string;
age: number;
address: string;
tags: string[];
isPublished: boolean; // 是否发布状态
}
// 定义行选择的类型,便于 TypeScript 类型推断
type TableRowSelection = TableProps<DataType>['rowSelection'];
/**
* 表格列配置工厂函数
* 接收 onDelete 回调作为参数,实现列内的操作交互
*/
const columns = (onDelete: (key: string) => void): TableProps<DataType>['columns'] => [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
render: (text) => <a>{text}</a>,
},
{
title: '是否发布',
dataIndex: 'isPublished',
// 注意:这里没有写 dataIndex,使用 render 的第一个参数直接接收该行的 isPublished 值
render: (isPublished: boolean) => (
<Tag color={isPublished ? 'green' : 'red'}>
{isPublished ? '已发布' : '未发布'}
</Tag>
),
},
{
title: '答卷',
dataIndex: 'age',
key: 'age',
},
{
title: '创建时间',
dataIndex: 'address',
key: 'address',
},
{
title: 'Tags',
key: 'tags',
dataIndex: 'tags',
render: (_, { tags }) => (
<Flex gap="small" align="center" wrap>
{tags.map((tag) => {
// 根据 tag 的内容动态设置颜色
let color = tag.length > 5 ? 'geekblue' : 'green';
if (tag === 'loser') {
color = 'volcano';
}
return (
<Tag color={color} key={tag}>
{tag.toUpperCase()}
</Tag>
);
})}
</Flex>
),
},
{
title: 'Action',
key: 'action',
render: (_, record) => (
<Space size="middle">
<a>Invite {record.name}</a>
{/* 点击删除触发回调,传入当前行的 key */}
<a onClick={() => onDelete(record.key)}>Delete</a>
</Space>
),
},
];
// 初始数据源
const initialData: DataType[] = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
isPublished: true,
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
isPublished: false,
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sydney No. 1 Lake Park',
tags: ['cool', 'teacher'],
isPublished: true,
},
{
key: '4',
name: 'Alice Smith',
age: 28,
address: 'Paris No. 5 River Street',
tags: ['creative', 'designer'],
isPublished: false,
},
];
const Stat: React.FC = () => {
// 1. 状态定义
// 选中的行 keys (受控组件)
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
// 表格数据源 (用于动态删除更新)
const [tableData, setTableData] = useState<DataType[]>(initialData);
/**
* 2. 处理行选择变化
* 当用户勾选/取消勾选复选框时,更新 selectedRowKeys 状态
*/
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
console.log('selectedRowKeys changed: ', newSelectedRowKeys);
setSelectedRowKeys(newSelectedRowKeys);
};
// 3. rowSelection 配置对象
const rowSelection: TableRowSelection = {
selectedRowKeys, // 指定选中项的 key 数组
onChange: onSelectChange, // 选中项发生变化时的回调
};
// 辅助变量:判断是否有选中项
const hasSelected = selectedRowKeys.length > 0;
/**
* 4. 单行删除处理函数
* @param key - 要删除的行的唯一 key
*/
const handleDelete = (key: string) => {
showDeleteConfirm(() => {
// 过滤掉指定 key 的数据
const newData = tableData.filter(item => item.key !== key);
setTableData(newData);
// 修复:如果被删除的行正处于选中状态,需要从选中列表中移除,避免数据不一致
const newSelectedRowKeys = selectedRowKeys.filter(selectedKey => selectedKey !== key);
setSelectedRowKeys(newSelectedRowKeys);
});
};
/**
* 5. 批量删除处理函数
* 删除所有选中的行
*/
const handleBulkDelete = () => {
showDeleteConfirm(() => {
// 过滤掉所有在 selectedRowKeys 中的数据
const newData = tableData.filter(item => !selectedRowKeys.includes(item.key));
setTableData(newData);
// 清空选中状态
setSelectedRowKeys([]);
});
};
/**
* 6. 通用的确认弹窗
* @param onOk - 用户点击确认后的回调函数
*/
const showDeleteConfirm = (onOk: () => void) => {
confirm({
title: '确认删除',
icon: <ExclamationCircleFilled />,
content: '确定要删除选中的项目吗?此操作不可恢复',
okText: '确认',
okType: 'danger', // 确认按钮显示为危险(红色)样式
cancelText: '取消',
onOk() {
onOk(); // 执行传入的删除逻辑
},
onCancel() {
console.log('取消删除');
},
});
};
return (
<>
{/* 顶部操作栏 */}
<div style={{ marginBottom: 16 }}>
{/* 批量删除按钮:当没有选中项时禁用 */}
<Button
type="primary"
disabled={!hasSelected}
onClick={handleBulkDelete}
style={{ marginRight: 8 }}
>
批量删除
</Button>
{/* 显示选中数量的提示 */}
{hasSelected && <span>已选中 {selectedRowKeys.length} 项</span>}
</div>
{/* 表格主体 */}
<Table<DataType>
rowSelection={rowSelection} // 注入复选框配置
columns={columns(handleDelete)} // 注入列配置,并传递删除回调
dataSource={tableData} // 使用 state 管理的数据源
pagination={{ pageSize: 10 }} // 分页配置
/>
</>
);
};
export default Stat;
📖 代码逻辑解读
这段代码实现了一个具备数据交互能力的表格,核心逻辑可以分为以下 4 层:
1. 数据层
initialData: 定义了表格的初始数据。
tableData: 使用 useState 将初始数据变为可变状态。这是为了支持删除操作后,界面能实时更新。
selectedRowKeys: 专门用于存储当前被勾选的行的 key。Ant Design 的表格是受控组件,必须通过这个 state 来控制复选框的勾选状态。
- 视图层
columns: 这是一个函数,接收 onDelete 回调。这样做的好处是让列配置(Column)能够触发外部的函数(解耦)。
rowSelection: 配置了复选框的行为。它绑定了 selectedRowKeys 和 onSelectChange,实现了"勾选更新状态,状态更新视图"的闭环。
- 交互层 (核心)
单行删除 (handleDelete):
点击 "Delete" 链接。
触发 showDeleteConfirm。
确认后,通过 filter 过滤掉对应数据,并更新 tableData。
关键点:如果删除的是已选中的行,同步更新 selectedRowKeys,防止状态残留。
批量删除 (handleBulkDelete):
点击顶部的 "批量删除" 按钮。
利用 selectedRowKeys 数组,过滤掉所有选中的数据。
执行删除后,清空 selectedRowKeys。
- 体验层
Modal.confirm: 使用了 Ant Design 的确认框,防止用户误删数据。
hasSelected: 控制 "批量删除" 按钮的可用状态。如果没有勾选任何行,按钮置灰且无法点击,提升了用户体验。