Skip to content

Table 表格

功能强大的数据表格组件,支持分页、排序、筛选、多选等功能。

基础用法

html
<div id="demo-table" style="height: 250px;"></div>

<script>
  const table = new SaTable('#demo-table', {
    columns: [
      { prop: 'name', label: '姓名', width: 120 },
      { prop: 'age', label: '年龄', width: 100 },
      { prop: 'address', label: '地址' }
    ],
    data: [
      { name: '张三', age: 25, address: '北京市朝阳区' },
      { name: '李四', age: 30, address: '上海市浦东新区' },
      { name: '王五', age: 28, address: '广州市天河区' }
    ]
  });
</script>
加载 SanoUI 组件中...
            
          

带分页的表格

通过 dataFetcher 配置数据获取函数,实现服务端分页。

html
<div id="demo-table-pagination" style="height: 300px;"></div>

<script>
  const table = new SaTable('#demo-table-pagination', {
    columns: [
      { prop: 'name', label: '姓名', width: 120 },
      { prop: 'age', label: '年龄', width: 100 },
      { prop: 'address', label: '地址' }
    ],
    pagination: {
      pageSize: 10,
      showControls: true
    },
    dataFetcher: async (params) => {
      // params: { page, pageSize, sort, filters }
      const response = await fetch(`/api/users?page=${params.page}&pageSize=${params.pageSize}`);
      const result = await response.json();
      return {
        data: result.data,
        total: result.total
      };
    }
  });
</script>
加载 SanoUI 组件中...
            
          

多选表格

通过 selection.type: "multiple" 启用多选功能。

html
<div id="demo-table-selection" style="height: 250px;"></div>

<script>
  const table = new SaTable('#demo-table-selection', {
    columns: [
      { prop: 'name', label: '姓名', width: 120 },
      { prop: 'age', label: '年龄', width: 100 },
      { prop: 'address', label: '地址' }
    ],
    data: [
      { id: 1, name: '张三', age: 25, address: '北京市' },
      { id: 2, name: '李四', age: 30, address: '上海市' },
      { id: 3, name: '王五', age: 28, address: '广州市' }
    ],
    selection: {
      type: 'multiple'
    },
    rowKey: 'id'
  });
  
  // 获取选中的行数据
  const selectedRows = table.getSelectedRows();
  
  // 获取选中的行键值
  const selectedKeys = table.getSelectedKeys();
</script>
加载 SanoUI 组件中...
            
          

带边框表格

通过 bordered: true 启用单元格边框显示。

html
<div id="demo-table-bordered" style="height: 250px;"></div>

<script>
  const table = new SaTable('#demo-table-bordered', {
    columns: [
      { prop: 'name', label: '姓名', width: 120 },
      { prop: 'age', label: '年龄', width: 100 },
      { prop: 'address', label: '地址' }
    ],
    data: [
      { name: '张三', age: 25, address: '北京市' },
      { name: '李四', age: 30, address: '上海市' }
    ],
    bordered: true
  });
</script>
加载 SanoUI 组件中...
            
          

列对齐方式

通过 align 配置列的对齐方式。

html
<div id="demo-table-align" style="height: 250px;"></div>

<script>
  const table = new SaTable('#demo-table-align', {
    columns: [
      { prop: 'name', label: '姓名(左对齐)', align: 'left', width: 150 },
      { prop: 'age', label: '年龄(居中)', align: 'center', width: 100 },
      { prop: 'salary', label: '薪资(右对齐)', align: 'right', width: 150 }
    ],
    data: [
      { name: '张三', age: 25, salary: '10000' },
      { name: '李四', age: 30, salary: '15000' }
    ]
  });
</script>
加载 SanoUI 组件中...
            
          

API

构造函数

javascript
new SaTable(container, options)

参数

参数说明类型默认值
container容器选择器或元素string | HTMLElement-
options配置选项SaTableOptions{}

静态方法

方法名说明参数返回值
create(rootSelector, options)创建表格实例rootSelector: string | HTMLElement
options: SaTableOptions
SaTable

配置选项

基础配置

参数说明类型默认值
columns列配置数组Array<SaTableColumn>[]
data表格数据(静态数据)Array<any>[]
dataFetcher数据获取函数(用于服务端分页/排序/筛选)function(params): Promise<{rows: Array, total: number}>null
rowKey行数据的唯一标识字段string'id'
stripe是否显示斑马纹booleantrue
bordered是否显示单元格边框booleanfalse
density表格密度'compact' | 'normal' | 'comfortable''normal'
locale语言设置string'zh-CN'

分页配置 (pagination)

参数说明类型默认值
pageSize每页显示条数number20
pageSizeOptions每页条数选项Array<number>[10, 20, 50, 100]
showControls是否显示分页控制(上一页、下一页等)booleanfalse

选择配置 (selection)

参数说明类型默认值
type选择类型'none' | 'single' | 'multiple''none'
showCheckboxInHeader多选时是否在表头显示全选复选框booleantrue

功能配置 (features)

参数说明类型默认值
columnResize是否允许调整列宽booleantrue
columnReorder是否允许拖拽调整列顺序booleantrue
stickyHeader是否固定表头booleantrue
virtualScroll是否启用虚拟滚动booleanfalse
inlineEdit是否启用行内编辑booleanfalse

列配置 (SaTableColumn)

参数说明类型默认值
field字段名(对应数据对象的属性)string-
prop字段名(field 的别名)string-
label列标题string-
width列宽度number | string-
minWidth最小列宽number-
align对齐方式'left' | 'center' | 'right''left'
sortable是否可排序booleanfalse
sortComparator自定义排序函数function(a, b): number-
fixed固定列'left' | 'right'-
hidden是否隐藏booleanfalse
formatter格式化函数function(value, row, column): string-
render自定义渲染函数function(value, row, column): HTMLElement | string-
type列类型'rowNumber' | 'selection' | 'action'-

事件回调

参数说明类型默认值
onSelect行选择变化回调function(rows: Array, keys: Array): voidnull
onFilter筛选按钮点击回调function(filters: Object, setFilters: Function): voidnull
onRowClick行点击回调function(row: Object, index: number): voidnull
onRowDblClick行双击回调function(row: Object, index: number): voidnull

方法

方法名说明参数返回值
loadData(extraParams?)加载数据(异步)extraParams?: Object - 额外参数Promise<void>
updateData(rows, total?)更新表格数据rows: Array - 行数据
total?: number - 总数
void
refresh()刷新表格(重新获取数据)-void
getSelectedRows()获取选中的行数据-Array<Object>
getSelectedRowKeys()获取选中的行键值数组-Array<string | number>
clearSelection()清空选择-void
setDensity(density)设置表格密度density: 'compact' | 'normal' | 'comfortable'void
toggleColumnVisibility(field)切换列的显示/隐藏field: string - 列字段名void
setColumnOrder(newOrder)设置列顺序newOrder: Array<string> - 字段名数组void
setFilters(filters)设置筛选条件filters: Object - 筛选条件对象void
destroy()销毁实例-void

dataFetcher 函数参数

当使用服务端数据时,dataFetcher 函数会接收以下参数:

javascript
{
  page: number,           // 当前页码
  pageSize: number,      // 每页条数
  sortBy: string,        // 排序字段
  sortOrder: 'asc' | 'desc', // 排序方向
  filters: Object        // 筛选条件
}

返回值:

javascript
{
  rows: Array,    // 行数据数组
  total: number   // 总记录数
}

自动初始化属性

属性说明类型默认值
class="sa-table-container"启用自动初始化--
data-table启用自动初始化标记boolean-
data-columns列配置(JSON 字符串)string[]
data-data表格数据(JSON 字符串)string[]
data-stripe是否显示斑马纹booleantrue
data-border是否显示边框booleanfalse

示例代码

手动创建实例

javascript
const table = new SaTable('#my-table', {
  columns: [
    { prop: 'name', label: '姓名', width: 120 },
    { prop: 'age', label: '年龄', width: 100 },
    { prop: 'address', label: '地址' }
  ],
  data: [
    { id: 1, name: '张三', age: 25, address: '北京市' },
    { id: 2, name: '李四', age: 30, address: '上海市' }
  ],
  selection: {
    type: 'multiple'
  },
  pagination: {
    pageSize: 10,
    showControls: true
  },
  onSelect: (rows, keys) => {
    console.log('选中的行:', rows);
    console.log('选中的键值:', keys);
  }
});

// 刷新数据
table.refresh();

// 获取选中的行
const selectedRows = table.getSelectedRows();

自动初始化

html
<div class="sa-table-container" 
     data-table
     data-columns='[{"prop":"name","label":"姓名"},{"prop":"age","label":"年龄"}]'
     data-data='[{"name":"张三","age":25},{"name":"李四","age":30}]'>
</div>

<script>
  SA.init('body');
</script>
加载 SanoUI 组件中...
            
          

实际使用场景

场景 1:用户管理表格

完整的用户管理表格,包含搜索、筛选、批量操作等功能。

html
<div style="margin-bottom: 1rem;">
  <div class="sa-input" 
       id="search-input"
       data-placeholder="搜索用户名、邮箱..." 
       data-prefix-icon="search"
       data-clearable="true"
       style="width: 300px; margin-right: 1rem; display: inline-block;">
  </div>
  <button class="sa-button sa-button--primary" data-icon="search" onclick="handleSearch()">
    搜索
  </button>
  <button class="sa-button" onclick="handleReset()" style="margin-left: 0.5rem;">
    重置
  </button>
</div>

<div id="user-table" style="height: 400px;"></div>

<div style="margin-top: 1rem;">
  <button 
    class="sa-button sa-button--primary" 
    id="batch-edit-btn"
    data-icon="edit"
    disabled
    onclick="handleBatchEdit()"
  >
    批量编辑
  </button>
  <button 
    class="sa-button sa-button--danger" 
    id="batch-delete-btn"
    data-icon="delete"
    disabled
    onclick="handleBatchDelete()"
    style="margin-left: 0.5rem;"
  >
    批量删除
  </button>
  <span id="selected-count" style="margin-left: 1rem; color: #666;">已选择 0 项</span>
</div>

<script>
  SA.init('body');
  
  // 模拟用户数据
  const mockUsers = [
    { id: 1, name: '张三', email: 'zhangsan@example.com', role: '管理员', status: '正常', createTime: '2024-01-01' },
    { id: 2, name: '李四', email: 'lisi@example.com', role: '编辑', status: '正常', createTime: '2024-01-02' },
    { id: 3, name: '王五', email: 'wangwu@example.com', role: '用户', status: '禁用', createTime: '2024-01-03' },
    { id: 4, name: '赵六', email: 'zhaoliu@example.com', role: '用户', status: '正常', createTime: '2024-01-04' },
    { id: 5, name: '钱七', email: 'qianqi@example.com', role: '编辑', status: '正常', createTime: '2024-01-05' }
  ];
  
  const table = new SaTable('#user-table', {
    columns: [
      { field: 'name', label: '姓名', width: 120, sortable: true },
      { field: 'email', label: '邮箱', width: 200, sortable: true },
      { 
        field: 'role', 
        label: '角色', 
        width: 100,
        formatter: (value) => {
          const roleMap = { '管理员': 'danger', '编辑': 'warning', '用户': 'info' };
          return `<span class="sa-tag sa-tag--${roleMap[value] || 'info'}">${value}</span>`;
        }
      },
      { 
        field: 'status', 
        label: '状态', 
        width: 100,
        formatter: (value) => {
          return value === '正常' 
            ? '<span style="color: #67c23a;">●</span> 正常'
            : '<span style="color: #f56c6c;">●</span> 禁用';
        }
      },
      { field: 'createTime', label: '创建时间', width: 150, sortable: true },
      {
        field: 'action',
        label: '操作',
        width: 150,
        fixed: 'right',
        render: (value, row) => {
          return `
            <button class="sa-button sa-button--text" onclick="handleEdit(${row.id})">编辑</button>
            <button class="sa-button sa-button--text sa-button--danger" onclick="handleDelete(${row.id})">删除</button>
          `;
        }
      }
    ],
    data: mockUsers,
    selection: {
      type: 'multiple'
    },
    rowKey: 'id',
    pagination: {
      pageSize: 10,
      showControls: true
    },
    onSelect: (rows, keys) => {
      updateSelectionUI(rows.length);
    }
  });
  
  const searchInput = document.getElementById('search-input');
  const batchEditBtn = document.getElementById('batch-edit-btn');
  const batchDeleteBtn = document.getElementById('batch-delete-btn');
  const selectedCountSpan = document.getElementById('selected-count');
  
  function updateSelectionUI(count) {
    selectedCountSpan.textContent = `已选择 ${count} 项`;
    batchEditBtn.disabled = count === 0;
    batchDeleteBtn.disabled = count === 0;
  }
  
  function handleSearch() {
    const keyword = searchInput._saInputInstance?.getValue() || '';
    if (keyword) {
      const filtered = mockUsers.filter(user => 
        user.name.includes(keyword) || user.email.includes(keyword)
      );
      table.updateData(filtered, filtered.length);
    } else {
      table.updateData(mockUsers, mockUsers.length);
    }
  }
  
  function handleReset() {
    searchInput._saInputInstance?.setValue('');
    table.updateData(mockUsers, mockUsers.length);
  }
  
  function handleBatchEdit() {
    const selected = table.getSelectedRows();
    alert(`批量编辑 ${selected.length} 项`);
  }
  
  function handleBatchDelete() {
    const selected = table.getSelectedRows();
    if (confirm(`确定要删除 ${selected.length} 项吗?`)) {
      alert('删除成功');
      table.clearSelection();
      updateSelectionUI(0);
    }
  }
  
  function handleEdit(id) {
    alert(`编辑用户 ID: ${id}`);
  }
  
  function handleDelete(id) {
    if (confirm('确定要删除吗?')) {
      alert(`删除用户 ID: ${id}`);
    }
  }
</script>
加载 SanoUI 组件中...
            
          

场景 2:服务端分页表格

使用服务端数据获取函数,实现真正的服务端分页、排序和筛选。

html
<div id="server-table" style="height: 400px;"></div>

<script>
  const table = new SaTable('#server-table', {
    columns: [
      { field: 'id', label: 'ID', width: 80, sortable: true },
      { field: 'title', label: '标题', width: 200, sortable: true },
      { field: 'author', label: '作者', width: 120 },
      { field: 'views', label: '浏览量', width: 100, align: 'right', sortable: true },
      { field: 'createTime', label: '创建时间', width: 180, sortable: true }
    ],
    pagination: {
      pageSize: 10,
      showControls: true
    },
    dataFetcher: async (params) => {
      // 模拟 API 调用
      console.log('请求参数:', params);
      
      // 模拟延迟
      await new Promise(resolve => setTimeout(resolve, 500));
      
      // 模拟数据
      const allData = Array.from({ length: 100 }, (_, i) => ({
        id: i + 1,
        title: `文章标题 ${i + 1}`,
        author: `作者 ${(i % 5) + 1}`,
        views: Math.floor(Math.random() * 10000),
        createTime: `2024-01-${String((i % 28) + 1).padStart(2, '0')} 10:00:00`
      }));
      
      // 模拟排序
      let filtered = [...allData];
      if (params.sortBy) {
        filtered.sort((a, b) => {
          const aVal = a[params.sortBy];
          const bVal = b[params.sortBy];
          const order = params.sortOrder === 'asc' ? 1 : -1;
          return aVal > bVal ? order : aVal < bVal ? -order : 0;
        });
      }
      
      // 模拟分页
      const start = (params.page - 1) * params.pageSize;
      const end = start + params.pageSize;
      const rows = filtered.slice(start, end);
      
      return {
        rows,
        total: filtered.length
      };
    }
  });
</script>
加载 SanoUI 组件中...
            
          

场景 3:带筛选的表格

表格支持多条件筛选,包括状态筛选、角色筛选等。

html
<div style="margin-bottom: 1rem;">
  <select id="status-filter" onchange="applyFilters()" style="padding: 0.5rem; margin-right: 1rem; border: 1px solid #ddd; border-radius: 4px;">
    <option value="">全部状态</option>
    <option value="正常">正常</option>
    <option value="禁用">禁用</option>
  </select>
  
  <select id="role-filter" onchange="applyFilters()" style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
    <option value="">全部角色</option>
    <option value="管理员">管理员</option>
    <option value="编辑">编辑</option>
    <option value="用户">用户</option>
  </select>
</div>

<div id="filter-table" style="height: 350px;"></div>

<script>
  const allData = [
    { id: 1, name: '张三', role: '管理员', status: '正常', email: 'zhangsan@example.com' },
    { id: 2, name: '李四', role: '编辑', status: '正常', email: 'lisi@example.com' },
    { id: 3, name: '王五', role: '用户', status: '禁用', email: 'wangwu@example.com' },
    { id: 4, name: '赵六', role: '用户', status: '正常', email: 'zhaoliu@example.com' },
    { id: 5, name: '钱七', role: '编辑', status: '正常', email: 'qianqi@example.com' },
    { id: 6, name: '孙八', role: '管理员', status: '禁用', email: 'sunba@example.com' }
  ];
  
  const filterTable = new SaTable('#filter-table', {
    columns: [
      { field: 'name', label: '姓名', width: 120 },
      { field: 'role', label: '角色', width: 100 },
      { field: 'status', label: '状态', width: 100 },
      { field: 'email', label: '邮箱', width: 200 }
    ],
    data: allData,
    bordered: true
  });
  
  function applyFilters() {
    const statusFilter = document.getElementById('status-filter').value;
    const roleFilter = document.getElementById('role-filter').value;
    
    let filtered = allData;
    
    if (statusFilter) {
      filtered = filtered.filter(item => item.status === statusFilter);
    }
    
    if (roleFilter) {
      filtered = filtered.filter(item => item.role === roleFilter);
    }
    
    filterTable.updateData(filtered, filtered.length);
  }
</script>
加载 SanoUI 组件中...
            
          

场景 4:固定列表格

表格支持固定左侧列和右侧列,适合列数较多的场景。

html
<div id="fixed-table" style="height: 300px;"></div>

<script>
  const fixedTable = new SaTable('#fixed-table', {
    columns: [
      { field: 'id', label: 'ID', width: 80, fixed: 'left' },
      { field: 'name', label: '姓名', width: 120, fixed: 'left' },
      { field: 'col1', label: '列1', width: 150 },
      { field: 'col2', label: '列2', width: 150 },
      { field: 'col3', label: '列3', width: 150 },
      { field: 'col4', label: '列4', width: 150 },
      { field: 'col5', label: '列5', width: 150 },
      { field: 'action', label: '操作', width: 150, fixed: 'right' }
    ],
    data: Array.from({ length: 20 }, (_, i) => ({
      id: i + 1,
      name: `用户 ${i + 1}`,
      col1: `数据 ${i + 1}-1`,
      col2: `数据 ${i + 1}-2`,
      col3: `数据 ${i + 1}-3`,
      col4: `数据 ${i + 1}-4`,
      col5: `数据 ${i + 1}-5`,
      action: '操作'
    })),
    bordered: true
  });
</script>
加载 SanoUI 组件中...
            
          

场景 5:表格密度切换

允许用户切换表格的显示密度(紧凑、正常、舒适)。

html
<div style="margin-bottom: 1rem;">
  <label>表格密度:</label>
  <button class="sa-button sa-button--small" onclick="setDensity('compact')">紧凑</button>
  <button class="sa-button sa-button--small" onclick="setDensity('normal')">正常</button>
  <button class="sa-button sa-button--small" onclick="setDensity('comfortable')">舒适</button>
</div>

<div id="density-table" style="height: 300px;"></div>

<script>
  const densityTable = new SaTable('#density-table', {
    columns: [
      { field: 'name', label: '姓名', width: 120 },
      { field: 'age', label: '年龄', width: 100 },
      { field: 'email', label: '邮箱', width: 200 },
      { field: 'address', label: '地址', width: 200 }
    ],
    data: Array.from({ length: 10 }, (_, i) => ({
      id: i + 1,
      name: `用户 ${i + 1}`,
      age: 20 + i,
      email: `user${i + 1}@example.com`,
      address: `地址 ${i + 1}`
    })),
    density: 'normal'
  });
  
  function setDensity(density) {
    densityTable.setDensity(density);
  }
</script>
加载 SanoUI 组件中...
            
          

注意事项

  1. 数据格式:表格数据必须是对象数组,每个对象代表一行数据
  2. rowKey:如果使用选择功能,必须设置 rowKey 指定唯一标识字段
  3. 服务端分页:使用 dataFetcher 时,函数必须返回 { rows, total } 格式
  4. 列配置fieldprop 都可以指定列字段,field 优先级更高
  5. 性能优化:大数据量时建议启用 virtualScroll 虚拟滚动
  6. 固定列:固定列会创建额外的 DOM 结构,建议只在必要时使用

常见问题

Q: 如何实现服务端分页?

A: 使用 dataFetcher 函数:

javascript
const table = new SaTable('#table', {
  dataFetcher: async (params) => {
    const response = await fetch(`/api/data?page=${params.page}&pageSize=${params.pageSize}`);
    const result = await response.json();
    return {
      rows: result.data,
      total: result.total
    };
  }
});

Q: 如何获取选中的行数据?

A: 使用 getSelectedRows() 方法:

javascript
const selectedRows = table.getSelectedRows();
console.log('选中的行:', selectedRows);

Q: 如何动态更新表格数据?

A: 使用 updateData() 方法:

javascript
table.updateData(newData, newTotal);

Q: 如何实现列的自定义渲染?

A: 使用列的 render 函数:

javascript
{
  field: 'status',
  label: '状态',
  render: (value, row, column) => {
    return `<span class="badge">${value}</span>`;
  }
}

Q: 如何实现表格排序?

A: 设置列的 sortable: true,表格会自动处理排序:

javascript
{
  field: 'name',
  label: '姓名',
  sortable: true
}

Q: 如何实现自定义排序逻辑?

A: 使用列的 sortComparator 函数:

javascript
{
  field: 'date',
  label: '日期',
  sortable: true,
  sortComparator: (a, b) => {
    return new Date(a.date) - new Date(b.date);
  }
}

Released under the MIT License.