Vue Material Steppers:如何禁止跳过步骤并绑定至指定路由?
Hey there! Let's walk through building that 5-step stepper you need, including the navigation rules and routing integration. I'll assume you're using Angular Material since you mentioned md-step (note: newer versions use mat-step, but the core logic stays the same) — let's dive in!
First, we'll implement the step dependency and reset behavior. We'll track completed steps with an array, and use that to control which steps are enabled.
Component Logic
In your stepper component class, add code to track completed steps, manage the active step, and handle navigation:
import { Component } from '@angular/core'; @Component({ selector: 'app-stepper', templateUrl: './stepper.component.html', styleUrls: ['./stepper.component.css'] }) export class StepperComponent { // Track which steps are marked as completed completedSteps: boolean[] = [false, false, false, false, false]; // Current active step index (starts at 0 for Step 1) activeStepIndex = 0; // Mark the current step as completed and move to the next onStepComplete(stepIndex: number) { this.completedSteps[stepIndex] = true; if (stepIndex < this.completedSteps.length - 1) { this.activeStepIndex = stepIndex + 1; } } // Handle navigating back to a previous step onStepNavigateBack(targetIndex: number) { this.activeStepIndex = targetIndex; // Reset all steps after the target to uncompleted/disabled for (let i = targetIndex + 1; i < this.completedSteps.length; i++) { this.completedSteps[i] = false; } } }
Template Markup
Now update your component's template to use the mat-stepper component, binding the completed/disabled states to our array:
<mat-stepper [selectedIndex]="activeStepIndex" linear> <!-- Step 1 --> <mat-step [completed]="completedSteps[0]" [disabled]="false"> <ng-template matStepLabel>Step 1</ng-template> <!-- Step 1 content goes here (for now) --> <button mat-button (click)="onStepComplete(0)">Complete Step 1</button> </mat-step> <!-- Step 2 --> <mat-step [completed]="completedSteps[1]" [disabled]="!completedSteps[0]"> <ng-template matStepLabel>Step 2</ng-template> <!-- Step 2 content goes here (for now) --> <button mat-button (click)="onStepNavigateBack(0)">Back to Step 1</button> <button mat-button (click)="onStepComplete(1)">Complete Step 2</button> </mat-step> <!-- Step 3 --> <mat-step [completed]="completedSteps[2]" [disabled]="!completedSteps[1]"> <ng-template matStepLabel>Step 3</ng-template> <!-- Step 3 content goes here (for now) --> <button mat-button (click)="onStepNavigateBack(1)">Back to Step 2</button> <button mat-button (click)="onStepComplete(2)">Complete Step 3</button> </mat-step> <!-- Step 4 --> <mat-step [completed]="completedSteps[3]" [disabled]="!completedSteps[2]"> <ng-template matStepLabel>Step 4</ng-template> <!-- Step 4 content goes here (for now) --> <button mat-button (click)="onStepNavigateBack(2)">Back to Step 3</button> <button mat-button (click)="onStepComplete(3)">Complete Step 4</button> </mat-step> <!-- Step 5 --> <mat-step [completed]="completedSteps[4]" [disabled]="!completedSteps[3]"> <ng-template matStepLabel>Step 5</ng-template> <!-- Step 5 content goes here (for now) --> <button mat-button (click)="onStepNavigateBack(3)">Back to Step 4</button> <button mat-button>Finish</button> </mat-step> </mat-stepper>
How This Works
- The
linearattribute ensures you can't jump ahead without completing previous steps, but we enhance this with ourcompletedStepsarray to reset steps when navigating back. - When you click "Back to Step X" from a later step, all steps after X are marked as uncompleted, which disables them automatically.
Now let's move each step's content into its own component and bind them to routes, so you don't have to stuff everything inside mat-step tags.
Step 1: Set Up Child Routes
First, update your app's routing module to define child routes for each step, nested under the stepper route:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { StepperComponent } from './stepper/stepper.component'; // Import your step components (create these first!) import { Step1Component } from './steps/step1/step1.component'; import { Step2Component } from './steps/step2/step2.component'; import { Step3Component } from './steps/step3/step3.component'; import { Step4Component } from './steps/step4/step4.component'; import { Step5Component } from './steps/step5/step5.component'; const routes: Routes = [ { path: 'stepper', component: StepperComponent, children: [ { path: 'step1', component: Step1Component }, { path: 'step2', component: Step2Component }, { path: 'step3', component: Step3Component }, { path: 'step4', component: Step4Component }, { path: 'step5', component: Step5Component }, { path: '', redirectTo: 'step1', pathMatch: 'full' } ] }, { path: '', redirectTo: 'stepper', pathMatch: 'full' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
Step 2: Update the Stepper Component for Routing
Modify your stepper component to sync the active step with the current route, and handle navigation via router:
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; @Component({ selector: 'app-stepper', templateUrl: './stepper.component.html', styleUrls: ['./stepper.component.css'] }) export class StepperComponent implements OnInit { completedSteps: boolean[] = [false, false, false, false, false]; activeStepIndex = 0; // Map step indexes to their route paths stepRoutes = ['step1', 'step2', 'step3', 'step4', 'step5']; constructor(private router: Router) { } ngOnInit(): void { // Sync active step with route when the component loads or route changes this.router.events.subscribe(() => { this.syncStepWithRoute(); }); } // Match the current route to the active step index syncStepWithRoute() { const currentRoute = this.router.url.split('/').pop(); this.activeStepIndex = this.stepRoutes.indexOf(currentRoute || 'step1'); } // Handle completing a step and navigating to the next onStepComplete(stepIndex: number) { this.completedSteps[stepIndex] = true; if (stepIndex < this.stepRoutes.length - 1) { this.router.navigate(['stepper', this.stepRoutes[stepIndex + 1]]); } } // Handle navigating to any step (with rules) onStepNavigate(targetIndex: number) { // Allow navigating back to any previous step, resetting later steps if (targetIndex <= this.activeStepIndex) { this.activeStepIndex = targetIndex; for (let i = targetIndex + 1; i < this.completedSteps.length; i++) { this.completedSteps[i] = false; } this.router.navigate(['stepper', this.stepRoutes[targetIndex]]); } // Allow navigating forward only if the previous step is completed else if (this.completedSteps[targetIndex - 1]) { this.activeStepIndex = targetIndex; this.router.navigate(['stepper', this.stepRoutes[targetIndex]]); } } // Listen to step components emitting completion events onStepComponentActivated(component: any) { if (component.stepCompleted) { component.stepCompleted.subscribe((stepIndex: number) => { this.onStepComplete(stepIndex); }); } } }
Step 3: Update the Stepper Template
Remove inline content from mat-step tags, add click handlers for step navigation, and add a router-outlet to load step components:
<mat-stepper [selectedIndex]="activeStepIndex" linear> <mat-step [completed]="completedSteps[0]" [disabled]="false" (click)="onStepNavigate(0)" > <ng-template matStepLabel>Step 1</ng-template> </mat-step> <mat-step [completed]="completedSteps[1]" [disabled]="!completedSteps[0]" (click)="onStepNavigate(1)" > <ng-template matStepLabel>Step 2</ng-template> </mat-step> <mat-step [completed]="completedSteps[2]" [disabled]="!completedSteps[1]" (click)="onStepNavigate(2)" > <ng-template matStepLabel>Step 3</ng-template> </mat-step> <mat-step [completed]="completedSteps[3]" [disabled]="!completedSteps[2]" (click)="onStepNavigate(3)" > <ng-template matStepLabel>Step 4</ng-template> </mat-step> <mat-step [completed]="completedSteps[4]" [disabled]="!completedSteps[3]" (click)="onStepNavigate(4)" > <ng-template matStepLabel>Step 5</ng-template> </mat-step> </mat-stepper> <!-- Load step components here via router --> <router-outlet (activate)="onStepComponentActivated($event)"></router-outlet>
Step 4: Create Step Components
For each step, create a standalone component with its content and a completion event. For example, Step1Component:
// step1.component.ts import { Component, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-step1', templateUrl: './step1.component.html', styleUrls: ['./step1.component.css'] }) export class Step1Component { // Emit event when step is completed @Output() stepCompleted = new EventEmitter<number>(); onCompleteStep() { // You can add form validation or other logic here before emitting this.stepCompleted.emit(0); // 0 is the index of Step 1 } }
<!-- step1.component.html --> <div class="step-content"> <h3>Step 1: Your Content Here</h3> <!-- Add forms, text, or other elements specific to Step 1 --> <button mat-button (click)="onCompleteStep()">Complete Step 1</button> </div>
Repeat this pattern for Steps 2-5, updating the stepCompleted.emit() index to match the step's position (1 for Step 2, etc.).
How This Routing Setup Works
- Each step lives in its own component, keeping your code organized.
- The
router-outletloads the appropriate component based on the current route. - Clicking a step label triggers navigation only if the rules are met (e.g., can't go to Step 3 unless Step 2 is completed).
- Navigating back resets all subsequent steps to uncompleted, disabling them automatically.
内容的提问来源于stack exchange,提问作者Ann HB

