ng-alain 组件开发技能
这个技能帮助使用 ng-alain 和 ng-zorro-antd 创建企业级 UI 组件。
核心库
@delon 包
- @delon/abc: 业务组件(ST, SV, SEModule 等)
- @delon/form: 动态基于模式的表单(SF)
- @delon/auth: 认证和授权
- @delon/acl: 访问控制列表
- @delon/theme: 主题和布局系统
- @delon/util: 工具函数
ng-zorro-antd
- 完整的 Ant Design 组件库
- 图标、布局、表单、表格、模态框等。
常见模式
1. ST(简单表格)组件
import { Component, signal, inject } from '@angular/core';
import { STColumn, STData, STComponent } from '@delon/abc/st';
import { SHARED_IMPORTS } from '@shared';
@Component({
selector: 'app-task-table',
standalone: true,
imports: [SHARED_IMPORTS, STComponent],
template: `
<st
[data]="tasks()"
[columns]="columns"
[loading]="loading()"
[page]="{ show: true, showSize: true }"
(change)="handleChange($event)"
/>
`
})
export class TaskTableComponent {
private taskService = inject(TaskService);
loading = signal(false);
tasks = signal<STData[]>([]);
columns: STColumn[] = [
{
title: 'ID',
index: 'id',
width: 80,
fixed: 'left'
},
{
title: '标题',
index: 'title',
width: 200
},
{
title: '状态',
index: 'status',
type: 'badge',
badge: {
pending: { text: '待处理', color: 'processing' },
'in-progress': { text: '进行中', color: 'warning' },
completed: { text: '已完成', color: 'success' }
}
},
{
title: '指派人',
index: 'assigneeName',
width: 150
},
{
title: '截止日期',
index: 'dueDate',
type: 'date',
dateFormat: 'yyyy-MM-dd'
},
{
title: '操作',
buttons: [
{
text: '编辑',
icon: 'edit',
click: (record: any) => this.edit(record)
},
{
text: '删除',
icon: 'delete',
type: 'del',
pop: {
title: '确认删除?',
okType: 'danger'
},
click: (record: any) => this.delete(record)
}
]
}
];
ngOnInit(): void {
this.loadTasks();
}
async loadTasks(): Promise<void> {
this.loading.set(true);
try {
const tasks = await this.taskService.getTasks();
this.tasks.set(tasks);
} finally {
this.loading.set(false);
}
}
handleChange(event: any): void {
console.log('表格变化:', event);
}
edit(record: any): void {
console.log('编辑:', record);
}
delete(record: any): void {
console.log('删除:', record);
}
}
2. SF(模式表单)组件
import { Component, signal, inject, output } from '@angular/core';
import { SFSchema, SFComponent } from '@delon/form';
import { SHARED_IMPORTS } from '@shared';
@Component({
selector: 'app-task-form',
standalone: true,
imports: [SHARED_IMPORTS, SFComponent],
template: `
<sf
[schema]="schema"
[loading]="loading()"
(formSubmit)="handleSubmit($event)"
(formChange)="handleChange($event)"
/>
`
})
export class TaskFormComponent {
loading = signal(false);
taskSubmit = output<any>();
schema: SFSchema = {
properties: {
title: {
type: 'string',
title: '任务标题',
maxLength: 200,
ui: {
placeholder: '输入任务标题',
grid: { span: 24 }
}
},
description: {
type: 'string',
title: '描述',
ui: {
widget: 'textarea',
autosize: { minRows: 3, maxRows: 6 },
grid: { span: 24 }
}
},
status: {
type: 'string',
title: '状态',
enum: [
{ label: '待处理', value: 'pending' },
{ label: '进行中', value: 'in-progress' },
{ label: '已完成', value: 'completed' }
],
default: 'pending',
ui: {
widget: 'select',
grid: { span: 12 }
}
},
priority: {
type: 'string',
title: '优先级',
enum: [
{ label: '低', value: 'low' },
{ label: '中', value: 'medium' },
{ label: '高', value: 'high' }
],
default: 'medium',
ui: {
widget: 'radio',
grid: { span: 12 }
}
},
assignee: {
type: 'string',
title: '指派人',
ui: {
widget: 'select',
asyncData: () => this.loadUsers(),
grid: { span: 12 }
}
},
dueDate: {
type: 'string',
title: '截止日期',
format: 'date',
ui: {
widget: 'date',
grid: { span: 12 }
}
},
tags: {
type: 'array',
title: '标签',
items: {
type: 'string'
},
ui: {
widget: 'select',
mode: 'tags',
grid: { span: 24 }
}
}
},
required: ['title', 'assignee'],
ui: {
grid: { gutter: 16 }
}
};
handleSubmit(value: any): void {
console.log('表单提交:', value);
this.taskSubmit.emit(value);
}
handleChange(value: any): void {
console.log('表单变化:', value);
}
private async loadUsers(): Promise<any[]> {
// 加载用户以供指派人下拉选择
return [
{ label: '用户1', value: 'user1' },
{ label: '用户2', value: 'user2' }
];
}
}
3. 页面头部与操作
import { Component } from '@angular/core';
import { PageHeaderComponent } from '@delon/abc/page-header';
import { SHARED_IMPORTS } from '@shared';
@Component({
selector: 'app-task-page',
standalone: true,
imports: [SHARED_IMPORTS, PageHeaderComponent],
template: `
<page-header
[title]="'任务管理'"
[subtitle]="'管理 ' + blueprintName()"
[breadcrumb]="breadcrumb"
>
<ng-template #extra>
<button nz-button nzType="primary" (click)="createTask()">
<i nz-icon nzType="plus"></i>
新任务
</button>
<button nz-button (click)="refresh()">
<i nz-icon nzType="reload"></i>
刷新
</button>
</ng-template>
</page-header>
<nz-card>
<app-task-table />
</nz-card>
`
})
export class TaskPageComponent {
blueprintName = signal('我的蓝图');
breadcrumb = [
{ title: '首页', link: '/' },
{ title: '蓝图', link: '/blueprints' },
{ title: '任务' }
];
createTask(): void {
console.log('创建新任务');
}
refresh(): void {
console.log('刷新任务');
}
}
4. ACL(访问控制)
import { Component, inject } from '@angular/core';
import { ACLService } from '@delon/acl';
import { SHARED_IMPORTS } from '@shared';
@Component({
selector: 'app-task-actions',
standalone: true,
imports: [SHARED_IMPORTS],
template: `
<nz-space>
<!-- 仅当用户有权限时显示按钮 -->
<button
*nzSpaceItem
*aclIf="'task:create'"
nz-button
nzType="primary"
(click)="create()"
>
创建任务
</button>
<button
*nzSpaceItem
*aclIf="'task:delete'"
nz-button
nzDanger
(click)="delete()"
>
删除
</button>
<!-- 在代码中检查权限 -->
@if (canEdit()) {
<button
*nzSpaceItem
nz-button
(click)="edit()"
>
编辑
</button>
}
</nz-space>
`
})
export class TaskActionsComponent {
private aclService = inject(ACLService);
canEdit = signal(false);
ngOnInit(): void {
// 程序性检查权限
this.canEdit.set(this.aclService.can('task:edit'));
}
create(): void {
console.log('创建任务');
}
edit(): void {
console.log('编辑任务');
}
delete(): void {
console.log('删除任务');
}
}
5. 响应式布局
import { Component } from '@angular/core';
import { SHARED_IMPORTS } from '@shared';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [SHARED_IMPORTS],
template: `
<div nz-row [nzGutter]="[16, 16]">
<!-- 响应式列 -->
<div nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6">
<nz-card nzTitle="总任务数">
<nz-statistic
[nzValue]="totalTasks()"
[nzPrefix]="prefixTpl"
/>
<ng-template #prefixTpl>
<i nz-icon nzType="check-circle"></i>
</ng-template>
</nz-card>
</div>
<div nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6">
<nz-card nzTitle="已完成">
<nz-statistic
[nzValue]="completedTasks()"
[nzValueStyle]="{ color: '#52c41a' }"
/>
</nz-card>
</div>
<div nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6">
<nz-card nzTitle="进行中">
<nz-statistic
[nzValue]="inProgressTasks()"
[nzValueStyle]="{ color: '#faad14' }"
/>
</nz-card>
</div>
<div nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6">
<nz-card nzTitle="待处理">
<nz-statistic [nzValue]="pendingTasks()" />
</nz-card>
</div>
</div>
`
})
export class DashboardComponent {
totalTasks = signal(100);
completedTasks = signal(60);
inProgressTasks = signal(25);
pendingTasks = signal(15);
}
6. 模态框和抽屉
import { Component, inject } from '@angular/core';
import { NzModalService } from 'ng-zorro-antd/modal';
import { NzDrawerService } from 'ng-zorro-antd/drawer';
import { SHARED_IMPORTS } from '@shared';
import { TaskFormComponent } from './task-form.component';
@Component({
selector: 'app-task-manager',
standalone: true,
imports: [SHARED_IMPORTS],
template: `
<button nz-button nzType="primary" (click)="openModal()">
打开模态框
</button>
<button nz-button (click)="openDrawer()">
打开抽屉
</button>
`
})
export class TaskManagerComponent {
private modal = inject(NzModalService);
private drawer = inject(NzDrawerService);
openModal(): void {
const modalRef = this.modal.create({
nzTitle: '创建任务',
nzContent: TaskFormComponent,
nzWidth: 720,
nzFooter: null
});
// 监听表单提交
modalRef.componentInstance!.taskSubmit.subscribe((task: any) => {
console.log('任务提交:', task);
modalRef.close();
});
}
openDrawer(): void {
const drawerRef = this.drawer.create({
nzTitle: '任务详情',
nzContent: TaskFormComponent,
nzWidth: 640,
nzClosable: true
});
drawerRef.afterClose.subscribe(() => {
console.log('抽屉关闭');
});
}
}
ng-alain 主题
使用主题变量
// 使用 ng-alain 主题变量
@import '@delon/theme/system/index';
.task-card {
background: var(--bg-color);
border: 1px solid var(--border-color);
padding: var(--padding-lg);
.title {
color: var(--text-color);
font-size: var(--font-size-lg);
}
}
暗色模式支持
import { Component, inject } from '@angular/core';
import { SettingsService } from '@delon/theme';
@Component({
selector: 'app-theme-toggle',
template: `
<button nz-button (click)="toggleTheme()">
<i nz-icon [nzType]="isDark() ? 'sun' : 'moon'"></i>
{{ isDark() ? '浅色' : '暗色' }} 模式
</button>
`
})
export class ThemeToggleComponent {
private settings = inject(SettingsService);
isDark = signal(false);
ngOnInit(): void {
this.isDark.set(this.settings.layout.theme === 'dark');
}
toggleTheme(): void {
const newTheme = this.isDark() ? 'light' : 'dark';
this.settings.setLayout('theme', newTheme);
this.isDark.set(newTheme === 'dark');
}
}
最佳实践
1. 使用 SHARED_IMPORTS
// 在共享模块中定义
export const SHARED_IMPORTS = [
CommonModule,
ReactiveFormsModule,
// ng-zorro-antd
NzButtonModule,
NzCardModule,
NzFormModule,
NzInputModule,
// @delon
STComponent,
SFComponent,
PageHeaderComponent
];
2. 响应式设计
// 使用 ng-zorro 响应式工具
<div nz-row [nzGutter]="16">
<div nz-col
[nzXs]="24" // 手机:全宽
[nzSm]="12" // 平板:半宽
[nzMd]="8" // 桌面:三分之一宽
[nzLg]="6" // 大屏:四分之一宽
>
内容
</div>
</div>
3. 可访问性
<!-- 使用适当的 ARIA 属性 -->
<button
nz-button
aria-label="创建新任务"
[attr.aria-disabled]="loading()"
>
创建
</button>
<!-- 适当的表单标签 -->
<nz-form-item>
<nz-form-label nzFor="title" nzRequired>
任务标题
</nz-form-label>
<nz-form-control>
<input nz-input id="title" name="title" />
</nz-form-control>
</nz-form-item>
检查清单
创建 ng-alain 组件时:
- [ ] 使用独立组件
- [ ] 导入 SHARED_IMPORTS
- [ ] 使用 STComponent 制作数据表格
- [ ] 使用 SFComponent 制作复杂表单
- [ ] 实现响应式布局
- [ ] 需要时添加 ACL 权限
- [ ] 使用 PageHeader 制作页面标题
- [ ] 实现适当的加载状态
- [ ] 添加错误处理
- [ ] 遵循 ng-alain 主题系统
- [ ] 支持暗色模式
- [ ] 确保可访问性(ARIA)
- [ ] 在移动设备上测试