DevUI Design中后台产品的开源前端解决方案之DataTable 表格组件核心解析

📊 DataTable 表格组件核心解析

DevUI DataTable 是一个基于 Angular 的企业级表格组件 ,支持数据展示、排序、过滤、编辑、树形结构、虚拟滚动 等多种高级功能,适用于复杂的中后台管理系统。

1. 安装与引入

在你的 Angular 项目(要求 ^18.0.0)中,需要先安装并导入 DataTable 模块。

typescript 复制代码
// app.module.ts 或相应的功能模块
import { NgModule } from '@angular/core';
import { DataTableModule } from 'ng-devui/data-table';

@NgModule({
  imports: [
    // ... 其他模块
    DataTableModule
  ],
  // ...
})
export class AppModule { }

2. 基础使用与核心参数

一个最简单的表格只需要 dataSource 数据源和列定义即可工作。

html 复制代码
<!-- your-component.component.html -->
<d-data-table #datatable [dataSource]="basicDataSource" [scrollable]="true" [tableOverflowType]="'overlay'">
  <d-column field="$index" header="#" [width]="'50px'"></d-column>
  <d-column
    *ngFor="let colOption of dataTableOptions.columns"
    [field]="colOption.field"
    [header]="colOption.header"
    [fieldType]="colOption.fieldType"
    [width]="'150px'"
  >
  </d-column>
</d-data-table>
typescript 复制代码
import { Component,OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { CommonModule } from '@angular/common';
import { PanelModule } from 'ng-devui';
import { DataTableModule } from 'ng-devui/data-table';
import { TableWidthConfig } from 'ng-devui/data-table';
import { originSource, SourceType } from './mockdata';
// import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@Component({  
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule,RouterOutlet,PanelModule,DataTableModule],
  templateUrl: './app.component.html',
  styleUrl: './app.component.less'
})
export class AppComponent implements OnInit {
  title = 'devuidemo';
   // 基础数据源
  basicDataSource: Array<SourceType> = JSON.parse(JSON.stringify(originSource.slice(0, 6)));
  dataTableOptions = {
    columns: [
      {
        field: 'firstName',
        header: 'First Name',
        fieldType: 'text',
        order: 1
      },
      {
        field: 'lastName',
        header: 'Last Name',
        fieldType: 'text',
        order: 2
      },
      {
        field: 'gender',
        header: 'Gender',
        fieldType: 'text',
        order: 3
      },
      {
        field: 'dob',
        header: 'Date of birth',
        fieldType: 'date',
        order: 4
      }
    ]
  };
  ngOnInit() {
  }
}
🔑 核心配置参数速查

下表汇总了开发中最常用的一些参数,你可以根据需求快速查阅:

参数 类型 默认值 说明
dataSource any[] -- 必选,表格的数据源
size `'sm' 'md' 'lg'`等
checkable boolean -- 是否启用行选择(显示复选框)
fixHeader boolean false 是否固定表头(配合 maxHeight 使用)
maxHeight string -- 设置表格最大高度,超出则内部滚动
scrollable boolean -- 表格内容溢出容器时是否允许滚动
striped boolean false 是否显示斑马纹(交替行背景色)
borderType `'' 'bordered'`等 ''

3. 典型功能场景与代码示例

场景一:表头固定的交互表格

此场景常用于需要对行数据进行批量操作的列表。

html 复制代码
<d-data-table
  [dataSource]="maxHeightDataSource"
  [fixHeader]="true"
  [tableOverflowType]="'overlay'"
  tableHeight="360px"
  [containFixHeaderHeight]="true"
  [scrollable]="true"
>
  <thead dTableHead>
    <tr dTableRow>
      <th dHeadCell *ngFor="let colOption of dataTableOptions.columns">{{ colOption.header }}</th>
    </tr>
  </thead>
  <tbody dTableBody>
    <ng-template let-rowItem="rowItem" let-rowIndex="rowIndex">
      <tr dTableRow>
        <td dTableCell *ngFor="let colOption of dataTableOptions.columns">
          {{ colOption.fieldType === 'date' ? (rowItem[colOption.field] | i18nDate : 'short' : false) : rowItem[colOption.field] }}
        </td>
      </tr>
    </ng-template>
  </tbody>
</d-data-table>
typescript 复制代码
import {
  ChangeDetectionStrategy,
  Component,
  OnInit
} from '@angular/core';
import { originSource, SourceType } from '../mock-data';

@Component({
  selector: 'd-datatable-demo-maxheight',
  changeDetection: ChangeDetectionStrategy.OnPush,
  templateUrl: './data-table-demo-maxheight.component.html'
})
export class DatatableDemoMaxheightComponent implements OnInit {

  dataTableOptions = {
    columns: [
      {
        field: 'firstName',
        header: 'First Name',
        fieldType: 'text',
        sortable: true,
      },
      {
        field: 'lastName',
        header: 'Last Name',
        fieldType: 'text',
        sortable: true,
      },
      {
        field: 'gender',
        header: 'gender',
        fieldType: 'text',
        sortable: true,
      },
      {
        field: 'dob',
        header: 'Date of birth',
        fieldType: 'date',
        sortable: true,
      }
    ]
  };

  maxHeightDataSource: Array<SourceType> = JSON.parse(JSON.stringify(originSource.slice()));

  ngOnInit() {
  }
}
场景二:支持可编辑单元格的表格

单元格编辑是表格编辑时候的常用功能。

html 复制代码
<d-data-table #dataTable size="sm" [dataSource]="basicDataSource" [tableOverflowType]="'overlay'" [scrollable]="true">
  <thead dTableHead>
    <tr dTableRow>
      <th dHeadCell>Last Name</th>
      <th dHeadCell>Date of birth</th>
      <th dHeadCell>Age</th>
      <th dHeadCell>Gender</th>
    </tr>
  </thead>
  <tbody dTableBody>
    <ng-template let-rowItem="rowItem" let-rowIndex="rowIndex">
      <tr dTableRow>
        <td
          dTableCell
          [editable]="true"
          [editableTip]="editableTip"
          [(editing)]="rowItem['nameEdit']"
          [rowItem]="rowItem"
          [field]="'lastName'"
          [beforeEditStart]="beforeEditStart"
          [beforeEditEnd]="beforeEditEnd"
        >
          <span *ngIf="!rowItem['nameEdit']">{{ rowItem?.lastName }}</span>
          <div *ngIf="rowItem['nameEdit']" class="edit-padding-fix">
            <!-- 引入dCommonsModule的autofocus -->
            <input
              [dAutoFocus]="true"
              class="devui-form-control"
              name="lastname"
              [(ngModel)]="rowItem.lastName"
              [attr.maxlength]="100"
              [attr.minlength]="3"
            />
          </div>
        </td>
        <td dTableCell [editable]="true" [(editing)]="rowItem['dateEdit']">
          <span *ngIf="!rowItem['dateEdit']">{{ rowItem?.dob | i18nDate : 'short' : false }}</span>
          <form *ngIf="rowItem['dateEdit']" class="form-inline edit-padding-fix">
            <div class="devui-form-group">
              <div class="devui-input-group devui-dropdown-origin">
                <input
                  class="devui-form-control search"
                  name="date"
                  [(ngModel)]="rowItem.dob"
                  dDatepicker
                  appendToBody
                  #datePicker="datepicker"
                  [autoOpen]="true"
                  (ngModelChange)="onEditEnd(rowItem, 'dateEdit')"
                />
                <div class="devui-input-group-addon" (click)="datePicker.toggle()">
                  <i class="icon icon-calendar"></i>
                </div>
              </div>
            </div>
          </form>
        </td>
        <td dTableCell [editable]="true" [(editing)]="rowItem['ageEdit']">
          <span *ngIf="!rowItem['ageEdit']">{{ rowItem?.age }}</span>
          <div *ngIf="rowItem['ageEdit']" class="edit-padding-fix">
            <d-input-number [autoFocus]="true" [(ngModel)]="rowItem.age"></d-input-number>
          </div>
        </td>
        <td dTableCell [editable]="true" [(editing)]="rowItem['genderEdit']">
          <span *ngIf="!rowItem['genderEdit']">{{ rowItem?.gender?.label }}</span>
          <div *ngIf="rowItem['genderEdit']" class="customized-editor edit-padding-fix">
            <d-select
              [options]="genderSource"
              [isSearch]="true"
              [filterKey]="'label'"
              [autoFocus]="true"
              [toggleOnFocus]="true"
              [appendToBody]="true"
              [(ngModel)]="rowItem.gender"
              (ngModelChange)="onEditEnd(rowItem, 'genderEdit')"
            >
              <ng-template let-option="option" let-filterKey="filterKey"> gender:{{ option[filterKey] }} </ng-template>
            </d-select>
          </div>
        </td>
      </tr>
    </ng-template>
  </tbody>
</d-data-table>
typescript 复制代码
import { Component, OnInit } from '@angular/core';
import { EditableTip } from 'ng-devui/data-table';
import { cloneDeep } from 'lodash-es';
import { editableOriginSource, genderSource } from '../mock-data';

@Component({
  selector: 'd-editable',
  templateUrl: './data-table-demo-editable.component.html'
})
export class DatatableDemoEditableComponent implements OnInit {
  genderSource = genderSource;
  basicDataSource = cloneDeep(editableOriginSource.slice(0, 6));

  editableTip = EditableTip.hover;
  nameEditing: boolean;

  ngOnInit() {
  }

  onEditEnd(rowItem, field) {
    rowItem[field] = false;
  }

  beforeEditStart = (rowItem, field) => {
    return true;
  };

  beforeEditEnd = (rowItem, field) => {
    console.log('beforeEditEnd');
    if (rowItem && rowItem[field].length < 3) {
      return false;
    } else {
      return true;
    }
  };
}
场景三:可展开的树形表格

用于展示具有层级关系的数据结构。

html 复制代码
<d-button class="demo-margin" (btnClick)="setUnCheckableRelation('downward')">选中父不再选中子</d-button>
<d-button bsStyle="common" class="demo-margin" (btnClick)="setUnCheckableRelation('upward')">选中子不再选中父</d-button>
<d-button bsStyle="common" class="demo-margin" (btnClick)="toggleIcon()">使用自定义展开/收起图标</d-button>
<d-button bsStyle="common" class="demo-margin" (btnClick)="expandAll()">展开全部表格</d-button>
<d-data-table
  [dataSource]="basicDataSource"
  [tableWidthConfig]="tableWidthConfig"
  [checkableRelation]="checkableRelation"
  [loadChildrenTable]="loadChildrenTable"
  [loadAllChildrenTable]="loadAllChildrenTable"
  [scrollable]="true"
  [tableOverflowType]="'overlay'"
>
  <thead dTableHead [checkable]="true">
    <tr dTableRow>
      <th dHeadCell [nestedColumn]="true" [iconFoldTable]="iconParentOpen" [iconUnFoldTable]="iconParentClose">title</th>
      <th dHeadCell>name</th>
      <th dHeadCell>status</th>
      <th dHeadCell>date</th>
    </tr>
  </thead>
  <tbody dTableBody>
    <ng-template let-rowItem="rowItem" let-rowIndex="rowIndex" let-nestedLayer="nestedLayer" let-nestedIndex="nestedIndex">
      <tr dTableRow [ngClass]="{ 'table-row-selected': rowItem.$checked }">
        <td dTableCell class="devui-checkable-cell">
          <d-checkbox
            [ngModelOptions]="{ standalone: true }"
            [ngModel]="rowItem.$checked"
            [halfchecked]="rowItem.$halfChecked"
            [disabled]="rowItem.$checkDisabled"
            (ngModelChange)="onRowCheckChange($event, rowIndex, nestedIndex, rowItem)"
            dTooltip
            [content]="rowItem.$checkBoxTips"
            [position]="['top', 'right', 'bottom', 'left']"
          >
          </d-checkbox>
        </td>
        <td
          dTableCell
          [nestedColumn]="true"
          [nestedColumnIndent]="20"
          [rowItem]="rowItem"
          [nestedLayer]="nestedLayer"
          [iconFoldTable]="iconParentOpen"
          [iconUnFoldTable]="iconParentClose"
          (toggleChildTableEvent)="onChildTableToggle($event, rowItem)"
        >
          {{ rowItem['title'] }}
        </td>
        <td dTableCell>{{ rowItem['lastName'] }}</td>
        <td dTableCell>{{ rowItem['status'] }}</td>
        <td dTableCell>{{ rowItem['dob'] | i18nDate : 'short' : false }}</td>
      </tr>
    </ng-template>
  </tbody>
</d-data-table>
typescript 复制代码
import {
  Component,
  OnInit,
  ViewChild
} from '@angular/core';
import {
  CheckableRelation,
  DataTableComponent,
  TableWidthConfig
} from 'ng-devui/data-table';
import {
  SourceType,
  treeDataSource
} from '../mock-data';

@Component({
  selector: 'd-tree-data',
  templateUrl: './tree-data.component.html',
  styles: ['.demo-margin { margin: 5px 5px 0 0;}']
})
export class TreeDataComponent implements OnInit {
  iconParentOpen: string;
  iconParentClose: string;
  basicDataSource: Array<SourceType> = JSON.parse(JSON.stringify(treeDataSource.slice(0, 6)));
  checkableRelation: CheckableRelation = {downward: true, upward: true};
  @ViewChild(DataTableComponent, { static: true }) datatable: DataTableComponent;

  tableWidthConfig: TableWidthConfig[] = [
    {
      field: 'checkbox',
      width: '4%'
    },
    {
      field: 'title',
      width: '36%'
    },
    {
      field: 'lastName',
      width: '20%'
    },
    {
      field: 'status',
      width: '20%'
    },
    {
      field: 'dob',
      width: '20%'
    }
  ];

  ngOnInit() {
    this.basicDataSource[0].$isChildTableOpen = true;
  }

  onChildTableToggle(status, rowItem) {
    this.datatable.setRowChildToggleStatus(rowItem, status);
  }

  loadChildrenTable = (rowItem) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        if (rowItem.title === 'table title1') {
          if (rowItem.children && rowItem.children.length === 0) {
            rowItem.children.push({
              title: 'table title11',
              lastName: 'Mark',
              status: 'done',
              dob: new Date(1989, 1, 1),
              startDate: new Date(2020, 1, 4),
              endDate: new Date(2020, 1, 8)
            });
          }
        }
        resolve(rowItem);
      }, 500);

    });
  };

  loadAllChildrenTable = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        this.basicDataSource[0].children[0].children[1].children[0].children = [];
        this.basicDataSource[0].children[0].children[1].children[0].children.push({
          title: 'table title01211',
          lastName: 'Mark',
          status: 'done',
          dob: new Date(1989, 1, 1),
        },
        {
          title: 'table title01212',
          lastName: 'Mark',
          status: 'done',
          dob: new Date(1991, 3, 1)
        });
        resolve(undefined);
      }, 500);
    });
  };

  setUnCheckableRelation(type) {
    if (type === 'upward') {
      this.checkableRelation.upward = false;
    } else {
      this.checkableRelation.downward = false;
    }
  }

  toggleIcon() {
    this.iconParentOpen = '<span class="icon icon-chevron-right"></span>';
    this.iconParentClose = '<span class="icon icon-chevron-down"></span>';
  }

  onRowCheckChange(checked, rowIndex, nestedIndex, rowItem) {
    rowItem.$checked = checked;
    rowItem.$halfChecked = false;
    this.datatable.setRowCheckStatus({
      rowIndex: rowIndex,
      nestedIndex: nestedIndex,
      rowItem: rowItem,
      checked: checked
    });
  }

  expandAll() {
    this.datatable.setTableChildrenToggleStatus(true);
  }
}

4. 开发实践与建议

  1. 性能优化 :当渲染超大数据集 (如数千行)时,务必开启 virtualScroll(虚拟滚动) 功能,它只会渲染可视区域内的 DOM 元素,能极大提升性能。
  2. 状态管理 :如果表格与复杂状态关联(如多步骤表单、全局状态),建议将 dataSource前端状态管理(如 NgRx、Akita)结合,而不是依赖组件内部状态。
  3. 列配置 :对于动态列的需求,可以创建一个 Column[] 配置数组,配合 *ngFor 在模板中动态生成 <d-table-column>,这样更加灵活。
  4. 数据更新 :修改 dataSource 数组引用(例如 this.dataSource = [...newData])会触发表格完整刷新 。若只想更新某一行,可以修改该行对象的属性并配合 ChangeDetectorRefdetectChanges() 方法。

参考资料:

MateChat:https://gitcode.com/DevCloudFE/MateChat

MateChat官网:https://matechat.gitcode.com

DevUI官网:https://devui.design/home

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax