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 | HTMLElementoptions: SaTableOptions | SaTable |
配置选项
基础配置
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
columns | 列配置数组 | Array<SaTableColumn> | [] |
data | 表格数据(静态数据) | Array<any> | [] |
dataFetcher | 数据获取函数(用于服务端分页/排序/筛选) | function(params): Promise<{rows: Array, total: number}> | null |
rowKey | 行数据的唯一标识字段 | string | 'id' |
stripe | 是否显示斑马纹 | boolean | true |
bordered | 是否显示单元格边框 | boolean | false |
density | 表格密度 | 'compact' | 'normal' | 'comfortable' | 'normal' |
locale | 语言设置 | string | 'zh-CN' |
分页配置 (pagination)
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
pageSize | 每页显示条数 | number | 20 |
pageSizeOptions | 每页条数选项 | Array<number> | [10, 20, 50, 100] |
showControls | 是否显示分页控制(上一页、下一页等) | boolean | false |
选择配置 (selection)
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
type | 选择类型 | 'none' | 'single' | 'multiple' | 'none' |
showCheckboxInHeader | 多选时是否在表头显示全选复选框 | boolean | true |
功能配置 (features)
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
columnResize | 是否允许调整列宽 | boolean | true |
columnReorder | 是否允许拖拽调整列顺序 | boolean | true |
stickyHeader | 是否固定表头 | boolean | true |
virtualScroll | 是否启用虚拟滚动 | boolean | false |
inlineEdit | 是否启用行内编辑 | boolean | false |
列配置 (SaTableColumn)
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
field | 字段名(对应数据对象的属性) | string | - |
prop | 字段名(field 的别名) | string | - |
label | 列标题 | string | - |
width | 列宽度 | number | string | - |
minWidth | 最小列宽 | number | - |
align | 对齐方式 | 'left' | 'center' | 'right' | 'left' |
sortable | 是否可排序 | boolean | false |
sortComparator | 自定义排序函数 | function(a, b): number | - |
fixed | 固定列 | 'left' | 'right' | - |
hidden | 是否隐藏 | boolean | false |
formatter | 格式化函数 | function(value, row, column): string | - |
render | 自定义渲染函数 | function(value, row, column): HTMLElement | string | - |
type | 列类型 | 'rowNumber' | 'selection' | 'action' | - |
事件回调
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
onSelect | 行选择变化回调 | function(rows: Array, keys: Array): void | null |
onFilter | 筛选按钮点击回调 | function(filters: Object, setFilters: Function): void | null |
onRowClick | 行点击回调 | function(row: Object, index: number): void | null |
onRowDblClick | 行双击回调 | function(row: Object, index: number): void | null |
方法
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
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 | 是否显示斑马纹 | boolean | true |
data-border | 是否显示边框 | boolean | false |
示例代码
手动创建实例
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 组件中...
注意事项
- 数据格式:表格数据必须是对象数组,每个对象代表一行数据
- rowKey:如果使用选择功能,必须设置
rowKey指定唯一标识字段 - 服务端分页:使用
dataFetcher时,函数必须返回{ rows, total }格式 - 列配置:
field和prop都可以指定列字段,field优先级更高 - 性能优化:大数据量时建议启用
virtualScroll虚拟滚动 - 固定列:固定列会创建额外的 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);
}
}