DevUI二次封装 DataTable 组件更容易上手
展示结果

前言
最近在做一个员工管理系统的时候,发现自己经常要写表格。每次都要配置列、处理分页、实现搜索、处理排序…这些代码重复性太强了。
我就想,能不能把这些常用的逻辑封装起来,让下次写表格的时候更简单一点?
于是就有了这个二次封装的 DataTable 组件。用起来真的爽多了。
一、原生 DataTable 的痛点
配置太复杂
DevUI 的 DataTable 功能很强大,但配置起来也很复杂。你需要:
// 定义列配置
const columns = [
{ field: 'id', header: 'ID', width: '80px' },
{ field: 'name', header: '姓名', width: '120px' },
// ... 更多列
];
// 定义数据
const data = [
{ id: 1, name: '张三', ... },
// ... 更多数据
];
// 配置分页
pageIndex = 0;
pageSize = 10;
pageSizeOptions = [10, 20, 50];
// 配置排序
sortField = '';
sortDirection = 'asc';
// 配置搜索
searchText = '';
// 配置选择
checkOptions = { ... };
这还只是基础配置。如果要实现搜索、排序、分页的联动,代码会更复杂。
逻辑重复
每个表格都要写:
- 分页逻辑
- 搜索过滤逻辑
- 排序逻辑
- 数据更新逻辑
如果有多个表格,这些代码会重复很多次。
数据操作麻烦
添加、删除、更新数据的时候,需要手动处理数组操作,还要更新表格配置。
二、我的解决方案
想法很直接
既然每次都要做同样的事情,那就把这些重复的逻辑提取出来,做成一个可复用的服务。这样下次需要表格的时候,就不用重复写那些配置代码了。
我的方案分为两层:
第一层是 SimplifiedDataTableService,这是给日常使用的。它提供了一些简单的方法:createTable()、addRow()、deleteRow() 等。用这个服务,创建一个表格只需要一行代码。
第二层是 SimpleDataTableService,这是给需要更多控制的场景用的。如果 SimplifiedDataTableService 不够灵活,可以用这个来做更复杂的操作。
然后是 DataTableWrapperComponent,这是一个包装器组件,负责处理所有的 UI 逻辑——分页、搜索、排序等等。
最底层就是 DevUI 的原生 DataTable 组件,我们的所有东西都是基于它来构建的。
三、开始编码
先定义接口
首先要定义两个接口。一个是列的配置,一个是表格的配置。
列配置接口很直接:
export interface DataTableColumnConfig {
field: string;
header: string;
width?: string;
sortable?: boolean;
filterable?: boolean;
template?: any;
align?: 'left' | 'center' | 'right';
}
这些字段都很好理解。field 是数据中的字段名,header 是列的标题。sortable 表示这列是否可以排序,width 是列的宽度。
然后是表格配置接口:
export interface DataTableWrapperConfig {
columns: DataTableColumnConfig[];
data: any[];
pageSize?: number;
pageSizeOptions?: number[];
showPagination?: boolean;
showCheckbox?: boolean;
loading?: boolean;
striped?: boolean;
bordered?: boolean;
hover?: boolean;
}
这个接口定义了表格需要的所有信息。columns 是列配置数组,data 是表格数据。其他的都是可选的,有默认值。
实现服务
现在来实现 SimplifiedDataTableService。这是整个方案的核心。
@Injectable({
providedIn: 'root'
})
export class SimplifiedDataTableService {
constructor(private simpleDataTableService: SimpleDataTableService) {}
createTable(
columns: DataTableColumnConfig[],
data: any[],
options?: {
pageSize?: number;
showPagination?: boolean;
showCheckbox?: boolean;
}
): DataTableWrapperConfig {
return this.simpleDataTableService.createConfig(columns, data, {
pageSize: options?.pageSize || 10,
showPagination: options?.showPagination !== false,
showCheckbox: options?.showCheckbox || false,
striped: true,
bordered: true,
hover: true
});
}
addRow(data: any[], newRow: any): any[] {
return [...data, newRow];
}
deleteRow(data: any[], index: number): any[] {
return data.filter((_, i) => i !== index);
}
updateRow(data: any[], index: number, newRow: any): any[] {
const newData = [...data];
newData[index] = newRow;
return newData;
}
search(data: any[], keyword: string, fields: string[]): any[] {
if (!keyword) return data;
return data.filter(item =>
fields.some(field =>
String(item[field]).toLowerCase().includes(keyword.toLowerCase())
)
);
}
sort(data: any[], field: string, direction: 'asc' | 'desc' = 'asc'): any[] {
return this.simpleDataTableService.sortData(data, field, direction);
}
getStats(data: any[], field: string): any {
const values = data.map(item => item[field]).filter(v => typeof v === 'number');
const total = values.reduce((a, b) => a + b, 0);
return {
total,
count: values.length,
average: values.length > 0 ? total / values.length : 0,
min: values.length > 0 ? Math.min(...values) : 0,
max: values.length > 0 ? Math.max(...values) : 0
};
}
}
这个服务提供了一些常用的方法。createTable() 用来创建表格配置,addRow() 用来添加行,deleteRow() 用来删除行,search() 用来搜索数据,getStats() 用来获取统计信息。
所有这些方法都很简单,但是很实用。
四、实际应用
现在来看看怎么用这个服务。我做了一个员工管理系统的例子。
在组件中注入服务
import { Component } from '@angular/core';
import { SimplifiedDataTableService } from './shared/datatable-wrapper.service';
import { DataTableColumnConfig, DataTableWrapperConfig } from './shared/datatable-wrapper.component';
@Component({
selector: 'app-root',
standalone: true,
providers: [SimplifiedDataTableService],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
tableConfig!: DataTableWrapperConfig;
tableColumns: DataTableColumnConfig[] = [];
tableData: any[] = [
{ id: 1, name: '张三', email: 'zhangsan@example.com', department: '技术部', salary: 15000 },
{ id: 2, name: '李四', email: 'lisi@example.com', department: '产品部', salary: 14000 },
{ id: 3, name: '王五', email: 'wangwu@example.com', department: '设计部', salary: 13000 },
{ id: 4, name: '赵六', email: 'zhaoliu@example.com', department: '技术部', salary: 16000 },
{ id: 5, name: '孙七', email: 'sunqi@example.com', department: '销售部', salary: 12000 }
];
constructor(private simplifiedDataTableService: SimplifiedDataTableService) {
this.initializeTable();
}
initializeTable(): void {
this.tableColumns = [
{ field: 'id', header: 'ID', width: '80px', sortable: true },
{ field: 'name', header: '姓名', width: '120px', sortable: true },
{ field: 'email', header: '邮箱', width: '200px' },
{ field: 'department', header: '部门', width: '120px', sortable: true },
{ field: 'salary', header: '薪资', width: '120px', sortable: true }
];
this.tableConfig = this.simplifiedDataTableService.createTable(
this.tableColumns,
this.tableData,
{
pageSize: 10,
showPagination: true,
showCheckbox: false
}
);
}
}
很简单,就是定义列配置和数据,然后调用 createTable() 方法。就这样,表格就配置好了。
添加员工
添加员工的时候,调用 addRow() 方法:
addEmployee(): void {
const newEmployee = {
id: Math.max(...this.tableData.map(e => e.id), 0) + 1,
name: '新员工',
email: 'new@example.com',
department: '技术部',
salary: 15000
};
this.tableData = this.simplifiedDataTableService.addRow(this.tableData, newEmployee);
this.tableConfig.data = this.tableData;
}
就这么简单。添加一行数据,然后更新表格配置。
删除员工
删除员工的时候,调用 deleteRow() 方法:
deleteEmployee(id: number): void {
const index = this.tableData.findIndex(e => e.id === id);
this.tableData = this.simplifiedDataTableService.deleteRow(this.tableData, index);
this.tableConfig.data = this.tableData;
}
同样很简单。找到要删除的行的索引,然后调用 deleteRow() 方法。
搜索员工
搜索员工的时候,调用 search() 方法:
searchEmployees(keyword: string): void {
if (!keyword) {
this.tableConfig.data = this.tableData;
} else {
this.tableConfig.data = this.simplifiedDataTableService.search(
this.tableData,
keyword,
['name', 'email', 'department']
);
}
}
传入关键词和要搜索的字段,就能得到搜索结果。
获取统计信息
获取统计信息的时候,调用 getStats() 方法:
getStats(): any {
return this.simplifiedDataTableService.getStats(this.tableData, 'salary');
}
就这一行代码,就能得到薪资的统计信息——总和、平均值、最大值、最小值。
五、模板和样式
HTML 模板
<div class="datatable-demo-section">
<h2>二次封装 DataTable 组件演示</h2>
<!-- 操作按钮 -->
<div class="button-section">
<d-button bsStyle="primary" (click)="addEmployee()">添加员工</d-button>
</div>
<!-- DataTable -->
<div class="datatable-wrapper-container" *ngIf="tableConfig">
<d-datatable-wrapper
[config]="tableConfig"
[showToolbar]="true"
></d-datatable-wrapper>
</div>
<!-- 统计信息 -->
<div class="stats-section" *ngIf="tableData.length > 0">
<h3>薪资统计</h3>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">总薪资</div>
<div class="stat-value">¥{{ getStats().total | number }}</div>
</div>
<div class="stat-card">
<div class="stat-label">平均薪资</div>
<div class="stat-value">¥{{ getStats().average | number:'1.0-0' }}</div>
</div>
<div class="stat-card">
<div class="stat-label">最高薪资</div>
<div class="stat-value">¥{{ getStats().max | number }}</div>
</div>
<div class="stat-card">
<div class="stat-label">最低薪资</div>
<div class="stat-value">¥{{ getStats().min | number }}</div>
</div>
</div>
</div>
</div>
模板很简洁。主要包括:
- 操作按钮区域,用于添加员工
- DataTable 组件,展示员工列表
- 统计信息区域,展示薪资统计
CSS 样式
.datatable-demo-section {
margin-top: 40px;
padding-top: 24px;
border-top: 2px solid #e1e5eb;
}
.datatable-wrapper-container {
margin-bottom: 24px;
padding: 16px;
background: #fff;
border: 1px solid #e1e5eb;
border-radius: 4px;
}
.stats-section {
margin-bottom: 24px;
padding: 20px;
background: #f8f9fa;
border-radius: 4px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.stat-card {
padding: 16px;
background: #fff;
border: 1px solid #e1e5eb;
border-radius: 4px;
text-align: center;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}
.stat-label {
color: #a8abb2;
font-size: 12px;
margin-bottom: 8px;
font-weight: 500;
}
.stat-value {
color: #5e7ce0;
font-size: 24px;
font-weight: 700;
}
样式遵循 DevUI 的设计规范,包括统一的颜色、间距和响应式设计。
六、总结一下优势
代码确实少了很多
用原生 DataTable,一个表格需要 100+ 行代码。用我的方案,只需要 20 行左右。
维护也简单了
所有 DataTable 的逻辑都在一个服务里。如果要改样式或改行为,只需要改一个地方。不用在每个组件里都改一遍。
功能更完整
内置了搜索、排序、分页、统计等常用功能。不用自己实现这些逻辑。
开发快
写表格的时候,不用关心 DataTable 怎么配置,只需要定义列和数据就行。
容易扩展
如果要加新的功能,比如导出、打印等,只需要在服务里加一个方法就行。
七、一些小技巧
多字段搜索
搜索的时候,可以搜索多个字段:
this.tableConfig.data = this.simplifiedDataTableService.search(
this.tableData,
keyword,
['name', 'email', 'department', 'position']
);
排序数据
需要排序的时候,可以用 sort() 方法:
const sortedData = this.simplifiedDataTableService.sort(
this.tableData,
'salary',
'desc'
);
批量删除
如果要删除多行,可以循环调用 deleteRow():
let data = this.tableData;
selectedIndices.forEach(index => {
data = this.simplifiedDataTableService.deleteRow(data, index);
});
this.tableData = data;
更新行
更新一行数据的时候,用 updateRow() 方法:
const updatedEmployee = { ...employee, salary: 18000 };
this.tableData = this.simplifiedDataTableService.updateRow(
this.tableData,
index,
updatedEmployee
);
八、一些建议
列配置可以复用
如果有多个表格用同样的列,可以把它提取出来:
const employeeColumns: DataTableColumnConfig[] = [
{ field: 'id', header: 'ID', width: '80px', sortable: true },
{ field: 'name', header: '姓名', width: '120px', sortable: true },
{ field: 'email', header: '邮箱', width: '200px' },
{ field: 'department', header: '部门', width: '120px', sortable: true },
{ field: 'salary', header: '薪资', width: '120px', sortable: true }
];
// 在多个地方使用
this.tableConfig1 = this.simplifiedDataTableService.createTable(employeeColumns, data1);
this.tableConfig2 = this.simplifiedDataTableService.createTable(employeeColumns, data2);
要处理空数据的情况
表格可能没有数据,所以要处理这种情况:
<div *ngIf="tableData.length > 0">
<d-datatable-wrapper [config]="tableConfig"></d-datatable-wrapper>
</div>
<div *ngIf="tableData.length === 0" class="empty-state">
<p>暂无数据</p>
</div>
操作完成后给个反馈
用户操作完成后,最好给个提示,让他知道操作成功了:
addEmployee(): void {
// ... 添加员工的逻辑
this.showSuccessMessage('员工添加成功');
}
九、常见问题
怎样改表格的宽度?
在列配置里设置 width 字段:
{ field: 'name', header: '姓名', width: '200px', sortable: true }
怎样禁用某列的排序?
在列配置里设置 sortable 为 false:
{ field: 'email', header: '邮箱', sortable: false }
怎样自定义列的内容?
使用 template 字段:
{
field: 'actions',
header: '操作',
template: actionTemplate // 传入自定义模板
}
怎样处理大数据量?
如果数据很多,可以用虚拟滚动或者服务端分页。但这超出了本文的范围。
十、最后说两句
这个方案用了一段时间,感觉效果不错。主要的好处就是:
- 代码少了很多,从 100+ 行减到 20 行
- 所有表格的风格都一样,看起来更专业
- 改样式的时候,只需要改一个地方
- 开发新功能的时候,不用关心 DataTable 怎么配置,只需要定义列和数据就行
如果你的项目里有很多表格,这个方案应该会很有帮助。
相关资源
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)