nex_design/docs/components/ListTable.md

16 KiB
Raw Blame History

ListTable 组件

组件说明

列表表格组件,基于 Ant Design Table 组件封装,提供统一的表格样式、行选择、分页、滚动和行点击等功能。支持跨页全选功能

组件位置

src/components/ListTable/ListTable.jsx
src/components/ListTable/ListTable.css

参数说明

参数名 类型 必填 默认值 说明
columns Array - 表格列配置数组
dataSource Array - 表格数据源
rowKey string 'id' 行数据的唯一标识字段名
selectedRowKeys Array<string|number> [] 选中行的 key 数组
onSelectionChange function(keys: Array) - 行选择变化回调
isAllPagesSelected boolean false 是否跨页全选所有数据
totalCount number - 总数据量(用于跨页全选显示)
onSelectAllPages function() - 选择所有页回调
onClearSelection function() - 清除选择回调
pagination Object|false 默认配置 分页配置false 表示不分页
scroll Object { x: 1200 } 表格滚动配置
onRowClick function(record: Object) - 行点击回调
selectedRow Object - 当前选中的行数据对象
loading boolean false 表格加载状态
className string '' 自定义类名

ColumnConfig 列配置

继承自 Ant Design Table 的列配置,常用属性:

属性名 类型 说明
title string|ReactNode 列标题
dataIndex string 数据字段名
key string 列唯一标识
width number 列宽度
align 'left'|'center'|'right' 对齐方式
fixed 'left'|'right' 固定列
render function(value, record, index) 自定义渲染函数

默认分页配置

{
  pageSize: 10,
  showSizeChanger: true,
  showQuickJumper: true,
}

注意:当传入 onSelectAllPagesonClearSelection 时,组件会自动在 showTotal 位置显示选择信息和跨页全选操作:

  • 无选中:已选择 0 项
  • 部分选中:已选择 3 项 [选择全部 100 项] [清除]
  • 跨页全选:已选择 100 项 [清除选择]

使用示例

基础用法

import ListTable from '../components/ListTable/ListTable'

function MyPage() {
  const columns = [
    {
      title: '序号',
      dataIndex: 'id',
      key: 'id',
      width: 80,
      align: 'center',
    },
    {
      title: '用户名',
      dataIndex: 'userName',
      key: 'userName',
      width: 150,
    },
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name',
      width: 120,
    },
    {
      title: '状态',
      dataIndex: 'status',
      key: 'status',
      width: 100,
      render: (status) => (
        <Tag color={status === 'enabled' ? 'green' : 'default'}>
          {status === 'enabled' ? '启用' : '停用'}
        </Tag>
      ),
    },
  ]

  const dataSource = [
    { id: 1, userName: 'admin', name: '管理员', status: 'enabled' },
    { id: 2, userName: 'user', name: '张三', status: 'disabled' },
  ]

  return (
    <ListTable
      columns={columns}
      dataSource={dataSource}
    />
  )
}

带行选择

import { useState } from 'react'
import ListTable from '../components/ListTable/ListTable'

function UserListPage() {
  const [selectedRowKeys, setSelectedRowKeys] = useState([])

  return (
    <div>
      {/* 显示选中的数量 */}
      <div>已选择 {selectedRowKeys.length} </div>

      <ListTable
        columns={columns}
        dataSource={dataSource}
        selectedRowKeys={selectedRowKeys}
        onSelectionChange={setSelectedRowKeys}
      />
    </div>
  )
}

带行点击和高亮

import { useState } from 'react'
import ListTable from '../components/ListTable/ListTable'

function UserListPage() {
  const [selectedUser, setSelectedUser] = useState(null)

  const handleRowClick = (record) => {
    setSelectedUser(record)
    // 打开详情抽屉等操作
    setShowDetailDrawer(true)
  }

  return (
    <ListTable
      columns={columns}
      dataSource={dataSource}
      onRowClick={handleRowClick}
      selectedRow={selectedUser}
    />
  )
}

自定义分页

<ListTable
  columns={columns}
  dataSource={dataSource}
  pagination={{
    pageSize: 20,
    showSizeChanger: true,
    showQuickJumper: true,
    showTotal: (total) => `总计 ${total} 条记录`,
    pageSizeOptions: ['10', '20', '50', '100'],
  }}
/>

禁用分页

<ListTable
  columns={columns}
  dataSource={dataSource}
  pagination={false}
/>

带加载状态

import { useState, useEffect } from 'react'

function UserListPage() {
  const [loading, setLoading] = useState(false)
  const [dataSource, setDataSource] = useState([])

  const fetchData = async () => {
    setLoading(true)
    try {
      const data = await api.fetchUsers()
      setDataSource(data)
    } finally {
      setLoading(false)
    }
  }

  useEffect(() => {
    fetchData()
  }, [])

  return (
    <ListTable
      columns={columns}
      dataSource={dataSource}
      loading={loading}
    />
  )
}

横向滚动(列较多时)

<ListTable
  columns={columns}
  dataSource={dataSource}
  scroll={{ x: 1600 }}  // 内容宽度超过容器时出现横向滚动
/>

固定列

const columns = [
  {
    title: '序号',
    dataIndex: 'id',
    key: 'id',
    width: 80,
    fixed: 'left',  // 固定在左侧
  },
  // ... 其他列
  {
    title: '操作',
    key: 'action',
    width: 200,
    fixed: 'right',  // 固定在右侧
    render: (_, record) => (
      <Space>
        <Button type="link" size="small">编辑</Button>
        <Button type="link" size="small" danger>删除</Button>
      </Space>
    ),
  },
]

<ListTable
  columns={columns}
  dataSource={dataSource}
  scroll={{ x: 1600 }}
/>

跨页全选(重要功能)

支持选择所有页的数据,而不仅仅是当前页。

import { useState, useMemo } from 'react'
import ListTable from '../components/ListTable/ListTable'

function UserListPage() {
  const [allData] = useState([...]) // 所有数据
  const [currentPage, setCurrentPage] = useState(1)
  const [pageSize, setPageSize] = useState(10)

  // 选择状态
  const [selectedRowKeys, setSelectedRowKeys] = useState([])
  const [isAllPagesSelected, setIsAllPagesSelected] = useState(false)

  // 当前页数据
  const currentPageData = useMemo(() => {
    const start = (currentPage - 1) * pageSize
    const end = start + pageSize
    return allData.slice(start, end)
  }, [allData, currentPage, pageSize])

  // 选择变化处理(重要:支持跨页选择)
  const handleSelectionChange = (keys) => {
    // keys 是当前页面操作后的选中项
    // 需要合并其他页的选中项,以保持跨页选择状态

    const currentPageKeys = currentPageData.map(item => item.id)
    const otherPagesSelectedKeys = selectedRowKeys.filter(
      key => !currentPageKeys.includes(key)
    )
    const newSelectedKeys = [...otherPagesSelectedKeys, ...keys]

    setSelectedRowKeys(newSelectedKeys)
    setIsAllPagesSelected(false)
  }

  // 选择所有页
  const handleSelectAllPages = () => {
    setIsAllPagesSelected(true)
    setSelectedRowKeys(allData.map(item => item.id))
  }

  // 清除选择
  const handleClearSelection = () => {
    setSelectedRowKeys([])
    setIsAllPagesSelected(false)
  }

  return (
    <ListTable
      columns={columns}
      dataSource={currentPageData}
      selectedRowKeys={selectedRowKeys}
      onSelectionChange={handleSelectionChange}

      // 跨页全选相关 props
      isAllPagesSelected={isAllPagesSelected}
      totalCount={allData.length}
      onSelectAllPages={handleSelectAllPages}
      onClearSelection={handleClearSelection}

      pagination={{
        current: currentPage,
        pageSize: pageSize,
        total: allData.length,
        onChange: (page, size) => {
          setCurrentPage(page)
          setPageSize(size)
        },
      }}
    />
  )
}

跨页选择的关键点

  1. 状态管理:需要维护 selectedRowKeysisAllPagesSelected 两个状态
  2. 选择合并:在 onSelectionChange 中合并其他页的选择
  3. 全选操作:将所有数据的 key 设置到 selectedRowKeys
  4. 传递回调:传入 onSelectAllPagesonClearSelection

工作原理

// 第 1 页选中 row1, row2
selectedRowKeys = ['row1', 'row2']

// 切换到第 2 页,选中 row11
// handleSelectionChange 自动合并
selectedRowKeys = ['row1', 'row2', 'row11']

// 点击"选择全部"
isAllPagesSelected = true
selectedRowKeys = ['row1', 'row2', ..., 'row100']

完整示例(包含跨页全选)

import { useState, useMemo } from 'react'
import { Button, Modal, message } from 'antd'
import ListTable from '../components/ListTable/ListTable'
import ListActionBar from '../components/ListActionBar/ListActionBar'
import { DeleteOutlined, ExportOutlined } from '@ant-design/icons'

function UserListPage() {
  const [allUsers] = useState([...]) // 所有用户数据
  const [currentPage, setCurrentPage] = useState(1)
  const [pageSize, setPageSize] = useState(10)
  const [selectedRowKeys, setSelectedRowKeys] = useState([])
  const [isAllPagesSelected, setIsAllPagesSelected] = useState(false)

  const currentPageData = useMemo(() => {
    const start = (currentPage - 1) * pageSize
    return allUsers.slice(start, start + pageSize)
  }, [allUsers, currentPage, pageSize])

  const handleSelectionChange = (keys) => {
    const currentPageKeys = currentPageData.map(item => item.id)
    const otherPagesSelectedKeys = selectedRowKeys.filter(
      key => !currentPageKeys.includes(key)
    )
    setSelectedRowKeys([...otherPagesSelectedKeys, ...keys])
    setIsAllPagesSelected(false)
  }

  const handleSelectAllPages = () => {
    setIsAllPagesSelected(true)
    setSelectedRowKeys(allUsers.map(item => item.id))
    message.success(`已选择全部 ${allUsers.length} 条数据`)
  }

  const handleClearSelection = () => {
    setSelectedRowKeys([])
    setIsAllPagesSelected(false)
  }

  const handleBatchDelete = () => {
    Modal.confirm({
      title: '确认删除',
      content: isAllPagesSelected
        ? `确定要删除全部 ${allUsers.length} 条数据吗?`
        : `确定要删除选中的 ${selectedRowKeys.length} 条数据吗?`,
      onOk: () => {
        // 执行删除
        message.success('删除成功')
        handleClearSelection()
      },
    })
  }

  return (
    <div>
      {/* 操作栏 */}
      <ListActionBar
        actions={[...]}
        batchActions={[
          {
            key: 'delete',
            label: '批量删除',
            icon: <DeleteOutlined />,
            danger: true,
            disabled: selectedRowKeys.length === 0,
            onClick: handleBatchDelete,
          },
        ]}
        search={...}
      />

      {/* 表格 */}
      <ListTable
        columns={columns}
        dataSource={currentPageData}
        selectedRowKeys={selectedRowKeys}
        onSelectionChange={handleSelectionChange}
        isAllPagesSelected={isAllPagesSelected}
        totalCount={allUsers.length}
        onSelectAllPages={handleSelectAllPages}
        onClearSelection={handleClearSelection}
        pagination={{
          current: currentPage,
          pageSize: pageSize,
          total: allUsers.length,
          onChange: (page, size) => {
            setCurrentPage(page)
            setPageSize(size)
          },
        }}
      />
    </div>
  )
}

完整示例(不使用跨页全选)

import { useState } from 'react'
import ListTable from '../components/ListTable/ListTable'
import { Tag, Button, Space } from 'antd'
import { EditOutlined, DeleteOutlined } from '@ant-design/icons'

function UserListPage() {
  const [selectedRowKeys, setSelectedRowKeys] = useState([])
  const [selectedUser, setSelectedUser] = useState(null)
  const [showDetailDrawer, setShowDetailDrawer] = useState(false)

  const columns = [
    {
      title: '序号',
      dataIndex: 'id',
      key: 'id',
      width: 80,
      align: 'center',
    },
    {
      title: '用户名',
      dataIndex: 'userName',
      key: 'userName',
      width: 150,
    },
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name',
      width: 120,
    },
    {
      title: '用户分组',
      dataIndex: 'group',
      key: 'group',
      width: 150,
      render: (text) => <Tag color="blue">{text}</Tag>,
    },
    {
      title: '状态',
      dataIndex: 'status',
      key: 'status',
      width: 100,
      align: 'center',
      render: (status) => (
        <Tag color={status === 'enabled' ? 'green' : 'default'}>
          {status === 'enabled' ? '启用' : '停用'}
        </Tag>
      ),
    },
    {
      title: '操作',
      key: 'action',
      width: 180,
      fixed: 'right',
      render: (_, record) => (
        <Space size="small" onClick={(e) => e.stopPropagation()}>
          <Button
            type="link"
            size="small"
            icon={<EditOutlined />}
            onClick={() => handleEdit(record)}
          >
            编辑
          </Button>
          <Button
            type="link"
            size="small"
            icon={<DeleteOutlined />}
            danger
            onClick={() => handleDelete(record)}
          >
            删除
          </Button>
        </Space>
      ),
    },
  ]

  const handleRowClick = (record) => {
    setSelectedUser(record)
    setShowDetailDrawer(true)
  }

  return (
    <ListTable
      columns={columns}
      dataSource={filteredUsers}
      selectedRowKeys={selectedRowKeys}
      onSelectionChange={setSelectedRowKeys}
      onRowClick={handleRowClick}
      selectedRow={selectedUser}
      scroll={{ x: 1400 }}
    />
  )
}

样式定制

组件提供以下 CSS 类名供自定义样式:

  • .list-table-container - 表格容器
  • .row-selected - 选中行的类名
  • .table-selection-info - 分页器中的选择信息容器
  • .selection-count - 选择数量文本
  • .count-highlight - 高亮数字
  • .selection-action - 操作链接(选择全部、清除)

固定高度设计

ListTable 组件采用固定表格体高度设计,确保页面布局的稳定性:

  • 尺寸:使用 Ant Design size="middle" 属性
  • 行高47pxAnt Design middle 尺寸默认值)
  • 表格体高度470px固定高度内部滚动
  • 显示行数10 行470px ÷ 47px = 10
  • 分页器高度56pxAnt Design 默认)
  • 容器内边距16px × 2

设计说明

  • 组件使用 Ant Design 的标准 size="middle" 属性,不自定义行高
  • 表格体固定 470px 高度,恰好显示 10 行数据
  • 超过 10 行的内容通过表格体内部滚动查看
  • 当数据不足 10 行时,表格体仍保持 470px 高度,确保布局稳定
  • 分页器上边距 16px与表格体保持适当间距

使用场景

  1. 用户列表 - 显示和管理用户数据
  2. 设备列表 - 显示和管理设备信息
  3. 订单列表 - 显示订单数据
  4. 任何需要表格展示的数据列表

注意事项

  1. columns 配置中的 key 必须唯一
  2. dataSource 中的每条数据必须有 rowKey 指定的唯一标识字段(默认为 id
  3. 操作列中的点击事件需要使用 e.stopPropagation() 阻止事件冒泡,避免触发行点击
  4. 当列数较多时,建议设置合适的 scroll.x 值并固定首尾列
  5. selectedRow 用于高亮显示,selectedRowKeys 用于多选
  6. 使用 render 函数时,要注意性能,避免在渲染函数中进行复杂计算
  7. 跨页全选:必须在 onSelectionChange 中合并其他页的选择,否则切换页面会丢失选择状态
  8. 跨页全选:传入 onSelectAllPagesonClearSelection 后,组件会自动显示选择信息和全选操作
  9. 跨页全选totalCount 用于显示总数量,如果不传则使用 pagination.total

相关组件


更新日志

  • v1.1.0 (2025-11-18)

    • 新增跨页全选功能支持
    • 自动在分页器显示选择信息和全选操作
    • 📝 完善文档和使用示例
  • v1.0.0 (2025-11-15)

    • 🎉 初始版本
    • 基础表格功能
    • 行选择、分页、滚动支持