三、主题定制
3.1 Material Design 3 (M3) 主题系统
Angular Material 的最新版本全面支持 Material Design 3(M3),引入了一套全新、更灵活的颜色系统。
与 M2 不同,M3 使用“调色板”和“色调”的概念来管理颜色,使颜色关系更清晰、主题定制更灵活。
示例:在 styles.scss 中自定义 M3 主题
@use '@angular/material' as mat;
html {
// 定义自定义调色板颜色
--my-brand-color: #7c3aed; // 紫色
--my-error-color: #ef4444; // 红色
@include mat.theme((
color: (
primary: mat.$violet-palette,
tertiary: mat.$blue-palette,
),
typography: Roboto,
density: 0,
));
}
3.2 动态 CSS 变量主题
若要实现运行时动态切换主题(例如让用户自定义主色),可以使用第三方库 angular-material-css-vars。
安装:
npm i angular-material-css-vars -S
配置(styles.scss):
@use "angular-material-css-vars" as mat-css-vars;
@include mat-css-vars.init-material-css-vars();
配置(AppModule / app.config.ts):
import { provideMaterialCssVars } from "angular-material-css-vars";
export const appConfig: ApplicationConfig = {
providers: [
provideMaterialCssVars({
isAutoContrast: true,
primary: "#3f51b5",
}),
],
};
运行时动态修改主题:
import { MaterialCssVarsService } from "angular-material-css-vars";
@Component({...})
export class ThemeService {
constructor(private materialCssVarsService: MaterialCssVarsService) {}
setTheme(color: string) {
this.materialCssVarsService.setPrimaryColor(color);
}
toggleDarkMode() {
this.materialCssVarsService.setDarkTheme(true);
}
}
3.3 创建多套静态主题
对于需要支持多品牌主题的场景,可以通过在外层容器添加 CSS 类来实现:
@use '@angular/material' as mat;
@import '~@angular/material/theming';
// 定义浅色默认主题
$light-primary: mat-palette($mat-indigo);
$light-accent: mat-palette($mat-pink, A200, A100, A400);
$light-theme: mat-light-theme($light-primary, $light-accent);
@include angular-material-theme($light-theme);
// 定义深色备用主题
$dark-primary: mat-palette($mat-blue-grey);
$dark-accent: mat-palette($mat-amber, A200, A100, A400);
$dark-theme: mat-dark-theme($dark-primary, $dark-accent);
// 应用深色主题的容器类
.dark-theme {
@include angular-material-theme($dark-theme);
}
在组件中使用:
<!-- 默认主题 -->
<mat-toolbar color="primary">...</mat-toolbar>
<!-- 深色主题 -->
<div class="dark-theme">
<mat-toolbar color="primary">...</mat-toolbar>
</div>
四、核心组件详解
4.1 按钮组件(MatButton)
MatButton 提供了多种按钮变体:
<!-- 普通文字按钮 -->
<button mat-button>文本按钮</button>
<!-- 凸起按钮 -->
<button mat-raised-button color="primary">凸起按钮</button>
<!-- 扁平按钮 -->
<button mat-flat-button color="accent">扁平按钮</button>
<!-- 描边按钮 -->
<button mat-stroked-button>描边按钮</button>
<!-- 圆形图标按钮 -->
<button mat-fab color="warn">
<mat-icon>edit</mat-icon>
</button>
<!-- 禁用状态 -->
<button mat-button disabled>禁用按钮</button>
color 属性说明:color 属性可以设置为 primary、accent、warn,这些值会映射到主题调色板中定义的颜色。
4.2 表单控件
Material 的表单控件需要与 mat-form-field 配合使用,以获得 Material Design 特有的边框、标签动画和错误提示效果。
<!-- 标准文本输入框 -->
<mat-form-field appearance="outline">
<mat-label>用户名</mat-label>
<input matInput placeholder="请输入用户名" [formControl]="usernameControl">
<mat-icon matSuffix>person</mat-icon> <!-- 后缀图标 -->
<mat-error *ngIf="usernameControl.invalid">用户名不能为空</mat-error>
</mat-form-field>
<!-- 密码输入框 -->
<mat-form-field>
<mat-label>密码</mat-label>
<input matInput [type]="hide ? 'password' : 'text'" [formControl]="passwordControl">
<button mat-icon-button matSuffix (click)="hide = !hide">
<mat-icon>{
{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
</mat-form-field>
<!-- 下拉选择器 -->
<mat-form-field>
<mat-label>选择班级</mat-label>
<mat-select [formControl]="classControl">
<mat-option *ngFor="let class of classes" [value]="class.id">
{
{class.name}}
</mat-option>
</mat-select>
</mat-form-field>
<!-- 日期选择器 -->
<mat-form-field>
<mat-label>出生日期</mat-label>
<input matInput [matDatepicker]="picker" [formControl]="dateControl">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
appearance 属性说明:
fill(默认):填充样式,背景色填充
outline:边框样式,更现代的外框设计
standard:下划线样式
4.3 布局组件
工具栏(MatToolbar):
<mat-toolbar color="primary" class="main-header">
<!-- 图标按钮 -->
<button mat-icon-button>
<mat-icon aria-label="菜单">menu</mat-icon>
</button>
<!-- 品牌名称 -->
<span class="branding">学生管理系统</span>
<!-- 弹性空间,将后续内容推到右侧 -->
<span class="spacer"></span>
<!-- 右侧内容 -->
<button mat-button>学生列表</button>
<button mat-button>数据统计</button>
</mat-toolbar>
CSS 样式:
.spacer {
flex: 1 1 auto;
}
.main-header {
position: relative;
box-shadow: 0 1px 8px rgba(0, 0, 0, .3);
z-index: 9;
}
卡片(MatCard):
<mat-card>
<mat-card-header>
<mat-card-title>学生信息</mat-card-title>
<mat-card-subtitle>张三</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="avatar.jpg" alt="头像">
<mat-card-content>
<p>学号:2023001</p>
<p>班级:高一(1)班</p>
</mat-card-content>
<mat-card-actions>
<button mat-button>编辑</button>
<button mat-button>删除</button>
</mat-card-actions>
</mat-card>
侧边栏导航(MatSidenav):
<mat-sidenav-container>
<mat-sidenav #sidenav mode="side" opened>
<mat-nav-list>
<a mat-list-item routerLink="/students">学生管理</a>
<a mat-list-item routerLink="/classes">班级管理</a>
<a mat-list-item routerLink="/statistics">数据统计</a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<mat-toolbar>
<button mat-icon-button (click)="sidenav.toggle()">
<mat-icon>menu</mat-icon>
</button>
<span>学生管理系统</span>
</mat-toolbar>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
4.4 数据表格(MatTable)
<!-- 定义表格列 -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>姓名</th>
<td mat-cell *matCellDef="let student">{
{student.name}}</td>
</ng-container>
<ng-container matColumnDef="class">
<th mat-header-cell *matHeaderCellDef>班级</th>
<td mat-cell *matCellDef="let student">{
{student.className}}</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>操作</th>
<td mat-cell *matCellDef="let student">
<button mat-icon-button (click)="editStudent(student)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button (click)="deleteStudent(student)">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let col of displayedColumns" [matColumnDef]="col">
<th mat-header-cell *matHeaderCellDef>{
{col}}</th>
<td mat-cell *matCellDef="let element">{
{element[col]}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<!-- 分页器 -->
<mat-paginator [length]="totalRecords"
[pageSize]="10"
[pageSizeOptions]="[5, 10, 20]"
(page)="onPageChange($event)">
</mat-paginator>
配合*ngFor动态生成:
<!-- 循环生成多个卡片 [citation:3] -->
<div class="card-container">
<mat-card *ngFor="let student of students">
<mat-card-header>
<mat-card-title>{
{student.name}}</mat-card-title>
</mat-card-header>
<mat-card-content>
<p>学号:{
{student.studentNo}}</p>
<mat-progress-bar mode="determinate" [value]="student.attendanceRate"></mat-progress-bar>
</mat-card-content>
</mat-card>
</div>
4.5 对话框(MatDialog)
对话框服务:
import { MatDialog } from '@angular/material/dialog';
export class StudentListComponent {
constructor(private dialog: MatDialog) {}
openStudentDialog(student?: Student): void {
const dialogRef = this.dialog.open(StudentDialogComponent, {
width: '500px',
data: student || { id: null, name: '', classId: null }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.saveStudent(result);
}
});
}
}
对话框组件:
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-student-dialog',
template: `
<h2 mat-dialog-title>{
{data.id ? '编辑学生' : '添加学生'}}</h2>
<mat-dialog-content>
<mat-form-field appearance="outline" class="full-width">
<mat-label>姓名</mat-label>
<input matInput [(ngModel)]="data.name">
</mat-form-field>
<mat-form-field appearance="outline" class="full-width">
<mat-label>班级</mat-label>
<mat-select [(ngModel)]="data.classId">
<mat-option *ngFor="let class of classes" [value]="class.id">
{
{class.name}}
</mat-option>
</mat-select>
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onCancel()">取消</button>
<button mat-raised-button color="primary" (click)="onSave()">保存</button>
</mat-dialog-actions>
`
})
export class StudentDialogComponent {
constructor(
public dialogRef: MatDialogRef<StudentDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: Student
) {}
onCancel(): void {
this.dialogRef.close();
}
onSave(): void {
this.dialogRef.close(this.data);
}
}