Angular多步骤表单实现D&D角色装备动态选择方案咨询
实现D&D角色表单的动态装备选择功能
需求概述
在Angular构建的D&D角色创建多步骤表单中,当用户选择职业后,需要从data.ts的装备列表中动态加载对应职业的装备选择项,每个选项包含item和alternate两个可选值,且选中item时需禁用alternate选项(反之同理)。
解决方案
不需要修改data.ts的JSON结构,现有结构已满足需求。我们通过Angular响应式表单动态生成装备选择控件,并通过表单状态监听实现互斥禁用逻辑。
步骤1:重构表单结构与职业监听
为每个装备选择项创建FormGroup,记录用户选择的类型和具体值,同时监听职业选择变化,自动加载对应装备选项。
修改creator.component.ts:
import { Component } from "@angular/core"; import { FormBuilder, FormGroup, Validators, FormArray, FormControl, } from "@angular/forms"; import { dndClasses, dndRaces, dndBackgrounds, dndRacialBonuses, classArchetypesWithSpells, classSkills, fightingStyles, equipment, } from "./data"; @Component({ selector: "app-creator", templateUrl: "./creator.component.html", styleUrls: ["./creator.component.scss"], }) export class CreatorComponent { currentStep = 1; abilityPoints = 27; maxAbilityPoints = 27; classes = dndClasses; races = dndRaces; backgrounds = dndBackgrounds; racialBonuses = dndRacialBonuses; archetype = classArchetypesWithSpells; fightingStyles = fightingStyles; skills = classSkills; characterCreationForm: FormGroup; filteredArchetypes: string[] = []; classAndBackgroundSkill: string[] = []; selectedSkills: string[] = []; currentEquipmentChoices: any[] = []; constructor(private fb: FormBuilder) { this.characterCreationForm = this.fb.group({ name: ["", [Validators.required, Validators.minLength(1)]], race: ["", Validators.required], class: ["", Validators.required], background: ["", Validators.required], abilityScores: this.fb.group({ strength: [8, [Validators.required, Validators.min(1), Validators.max(20)]], dexterity: [8, [Validators.required, Validators.min(1), Validators.max(20)]], constitution: [8, [Validators.required, Validators.min(1), Validators.max(20)]], intelligence: [8, [Validators.required, Validators.min(1), Validators.max(20)]], wisdom: [8, [Validators.required, Validators.min(1), Validators.max(20)]], charisma: [8, [Validators.required, Validators.min(1), Validators.max(20)]], }), archeTypes: ["", Validators.required], skills: this.fb.array([], Validators.required), equipment_choices: this.fb.array([]), standart_equipment: this.fb.array([]), }); // 监听职业选择变化,自动加载装备选项 this.characterCreationForm.get('class')?.valueChanges.subscribe(selectedClass => { this.loadEquipmentChoices(selectedClass); }); } get skillsFormArray(): FormArray { return this.characterCreationForm.get("skills") as FormArray; } get equipmentChoicesFormArray(): FormArray { return this.characterCreationForm.get("equipment_choices") as FormArray; } // 加载对应职业的装备选择项 loadEquipmentChoices(selectedClass: string) { const classEquipment = equipment.find(equip => equip.class === selectedClass); if (!classEquipment) { this.equipmentChoicesFormArray.clear(); this.currentEquipmentChoices = []; return; } this.currentEquipmentChoices = classEquipment.equipment_choices; this.equipmentChoicesFormArray.clear(); // 为每个装备选项创建FormGroup classEquipment.equipment_choices.forEach(() => { this.equipmentChoicesFormArray.push( this.fb.group({ selectedType: new FormControl(''), selectedValue: new FormControl('', Validators.required) }) ); }); } // 处理装备选项选择,同步表单值 onEquipmentOptionSelect(index: number, type: string, value: any) { const group = this.equipmentChoicesFormArray.at(index) as FormGroup; group.patchValue({ selectedType: type, selectedValue: Array.isArray(value) ? value.join(', ') : value }); } // 判断alternate是否禁用 isAlternateDisabled(index: number): boolean { const group = this.equipmentChoicesFormArray.at(index) as FormGroup; return group.get('selectedType')?.value === 'item'; } // 判断item是否禁用 isItemDisabled(index: number): boolean { const group = this.equipmentChoicesFormArray.at(index) as FormGroup; return group.get('selectedType')?.value === 'alternate'; } // 处理标准装备选择 onEquipmentChange(event: any, equipment: any) { const standardEquipmentArray = this.characterCreationForm.get('standart_equipment') as FormArray; if (event.target.checked) { standardEquipmentArray.push(new FormControl(equipment)); } else { const index = standardEquipmentArray.controls.findIndex(c => c.value === equipment); if (index !== -1) { standardEquipmentArray.removeAt(index); } } } nextStep() { this.selectArchetype(); this.selectBackground(); this.getSkills(); if (this.currentStep < 4) { this.currentStep++; } } prevStep() { if (this.currentStep > 1) { this.currentStep--; } } onSubmit() { if (this.characterCreationForm.valid) { this.nextStep(); this.calculateRacialBonus(); console.log("Form data:", this.characterCreationForm.value); } else { console.log("请填写所有必填字段。", this.characterCreationForm); } } // 补充缺失的方法避免报错 selectArchetype() {} selectBackground() {} getSkills() {} calculateRacialBonus() {} }
步骤2:模板渲染与交互逻辑
修改creator.component.html,在装备步骤中渲染动态生成的装备选择项,实现互斥禁用:
<!-- 装备选择步骤 --> <div *ngIf="currentStep === 3"> <h2>装备选择</h2> <!-- 标准装备选择 --> <h3>标准装备</h3> <div *ngIf="characterCreationForm.get('class')?.value"> <div *ngFor="let equip of equipment.find(e => e.class === characterCreationForm.get('class')?.value)?.standard_equipment"> <label> <input type="checkbox" (change)="onEquipmentChange($event, equip)" /> {{ equip }} </label> </div> </div> <!-- 动态装备选择项 --> <h3>可选装备</h3> <div *ngFor="let choice of currentEquipmentChoices; let i = index" class="equipment-choice"> <p>请选择其一:</p> <label> <input type="radio" name="equipment-{{i}}" (change)="onEquipmentOptionSelect(i, 'item', choice.item)" [disabled]="isItemDisabled(i)" /> {{ choice.item }} </label> <!-- 处理单个alternate选项 --> <div *ngIf="!Array.isArray(choice.alternate)"> <label> <input type="radio" name="equipment-{{i}}" (change)="onEquipmentOptionSelect(i, 'alternate', choice.alternate)" [disabled]="isAlternateDisabled(i)" /> {{ choice.alternate }} </label> </div> <!-- 处理数组类型的alternate选项 --> <div *ngIf="Array.isArray(choice.alternate)"> <p>或选择以下之一:</p> <div *ngFor="let alt of choice.alternate"> <label> <input type="radio" name="equipment-{{i}}" (change)="onEquipmentOptionSelect(i, 'alternate', alt)" [disabled]="isAlternateDisabled(i)" /> {{ alt }} </label> </div> </div> </div> <button (click)="prevStep()">上一步</button> <button (click)="nextStep()" [disabled]="!characterCreationForm.valid">下一步</button> </div>
关键说明
- 动态表单生成:通过职业选择的
valueChanges事件自动加载对应装备,为每个选项创建独立的FormGroup记录选择状态。 - 互斥禁用逻辑:通过
isItemDisabled和isAlternateDisabled方法判断控件状态,确保用户只能选择item或alternate中的一类选项。 - 数组类型兼容:针对
alternate为数组的场景(如Bard的武器选项),模板自动渲染多个单选按钮,适配不同装备结构。
内容的提问来源于stack exchange,提问作者Andriy Panasiuk
相关产品推荐
相关产品推荐

