Files
devui-demo/src/app/devui-form/devui-form.component.ts
2025-11-26 18:55:05 +08:00

323 lines
9.3 KiB
TypeScript

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { Observable, of, timer } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';
@Component({
selector: 'app-devui-form',
standalone: true,
imports: [ReactiveFormsModule, CommonModule, RouterLink],
template: `
<div class="container">
<a routerLink="/" class="back-link">← 返回首页</a>
<h1>DevUI 表单示例</h1>
<p>根据《组件使用进阶-表单的邪修使用与避坑指南》文档实现的示例</p>
<form [formGroup]="userForm" (ngSubmit)="onSubmit()" class="form-container">
<div class="form-group">
<label for="username">用户名 *</label>
<input
id="username"
type="text"
formControlName="username"
class="form-control"
[class.error]="userForm.get('username')?.invalid && userForm.get('username')?.touched">
<div class="error-message" *ngIf="userForm.get('username')?.invalid && userForm.get('username')?.touched">
<div *ngIf="userForm.get('username')?.errors?.['required']">用户名是必填项</div>
<div *ngIf="userForm.get('username')?.errors?.['minlength']">用户名至少需要3个字符</div>
<div *ngIf="userForm.get('username')?.errors?.['usernameTaken']">用户名已被占用</div>
</div>
</div>
<div class="form-group">
<label for="email">邮箱 *</label>
<input
id="email"
type="email"
formControlName="email"
class="form-control"
[class.error]="userForm.get('email')?.invalid && userForm.get('email')?.touched">
<div class="error-message" *ngIf="userForm.get('email')?.invalid && userForm.get('email')?.touched">
<div *ngIf="userForm.get('email')?.errors?.['required']">邮箱是必填项</div>
<div *ngIf="userForm.get('email')?.errors?.['email']">请输入有效的邮箱地址</div>
</div>
</div>
<div class="form-group">
<label for="age">年龄 *</label>
<input
id="age"
type="number"
formControlName="age"
class="form-control"
[class.error]="userForm.get('age')?.invalid && userForm.get('age')?.touched">
<div class="error-message" *ngIf="userForm.get('age')?.invalid && userForm.get('age')?.touched">
<div *ngIf="userForm.get('age')?.errors?.['required']">年龄是必填项</div>
<div *ngIf="userForm.get('age')?.errors?.['min']">年龄不能小于18岁</div>
<div *ngIf="userForm.get('age')?.errors?.['max']">年龄不能大于100岁</div>
</div>
</div>
<div class="form-group">
<label for="department">部门 *</label>
<select
id="department"
formControlName="department"
class="form-control"
[class.error]="userForm.get('department')?.invalid && userForm.get('department')?.touched">
<option value="">请选择部门</option>
<option value="engineering">工程部</option>
<option value="marketing">市场部</option>
<option value="sales">销售部</option>
<option value="hr">人事部</option>
</select>
<div class="error-message" *ngIf="userForm.get('department')?.invalid && userForm.get('department')?.touched">
<div *ngIf="userForm.get('department')?.errors?.['required']">部门是必选项</div>
</div>
</div>
<div class="form-group" *ngIf="showAdminFields">
<label for="adminCode">管理员代码 *</label>
<input
id="adminCode"
type="text"
formControlName="adminCode"
class="form-control"
[class.error]="userForm.get('adminCode')?.invalid && userForm.get('adminCode')?.touched">
<div class="error-message" *ngIf="userForm.get('adminCode')?.invalid && userForm.get('adminCode')?.touched">
<div *ngIf="userForm.get('adminCode')?.errors?.['required']">管理员代码是必填项</div>
<div *ngIf="userForm.get('adminCode')?.errors?.['minlength']">管理员代码至少需要6个字符</div>
</div>
</div>
<div class="form-group checkbox-group">
<label>
<input
type="checkbox"
formControlName="isAdmin"
(change)="toggleAdminFields($event)">
是否为管理员
</label>
</div>
<div class="form-actions">
<button type="submit" [disabled]="userForm.invalid" class="btn btn-primary">
提交
</button>
<button type="button" (click)="resetForm()" class="btn btn-secondary">
重置
</button>
</div>
</form>
<div class="form-data" *ngIf="submittedData">
<h3>提交的数据:</h3>
<pre>{{ submittedData | json }}</pre>
</div>
</div>
`,
styles: [`
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.back-link {
display: inline-block;
margin-bottom: 1rem;
color: #007bff;
text-decoration: none;
}
.form-container {
background: #f9f9f9;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-control {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
.form-control.error {
border-color: #dc3545;
}
.error-message {
color: #dc3545;
font-size: 14px;
margin-top: 5px;
}
.checkbox-group label {
font-weight: normal;
display: flex;
align-items: center;
}
.checkbox-group input {
width: auto;
margin-right: 8px;
}
.form-actions {
display: flex;
gap: 10px;
margin-top: 20px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.form-data {
margin-top: 30px;
padding: 20px;
background: #e9ecef;
border-radius: 4px;
}
.form-data pre {
background: white;
padding: 15px;
border-radius: 4px;
overflow: auto;
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
.form-container {
padding: 15px;
}
.form-actions {
flex-direction: column;
}
.btn {
width: 100%;
margin-bottom: 10px;
}
}
`]
})
export class DevuiFormComponent implements OnInit {
userForm: FormGroup;
showAdminFields = false;
submittedData: any = null;
constructor(private fb: FormBuilder) {
this.userForm = this.fb.group({
username: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
age: ['', [Validators.required, Validators.min(18), Validators.max(100)]],
department: ['', Validators.required],
isAdmin: [false]
});
}
ngOnInit() {
// 监听用户名变化以演示异步验证
this.userForm.get('username')?.valueChanges.subscribe(value => {
console.log('Username changed:', value);
});
}
// 动态添加/删除管理员字段
toggleAdminFields(event: any) {
const isChecked = event.target.checked;
this.showAdminFields = isChecked;
if (isChecked) {
this.userForm.addControl('adminCode',
this.fb.control('', [Validators.required, Validators.minLength(6)]));
} else {
this.userForm.removeControl('adminCode');
}
}
// 异步验证器模拟
uniqueUsernameValidator() {
return (control: any) => {
if (!control.value) {
return of(null);
}
// 模拟服务端检查用户名是否已存在
const isTaken = control.value.toLowerCase() === 'admin' ||
control.value.toLowerCase() === 'user' ||
control.value.toLowerCase() === 'test';
// 模拟网络延迟
return timer(500).pipe(
map(() => isTaken ? { usernameTaken: true } : null)
);
};
}
onSubmit() {
if (this.userForm.valid) {
this.submittedData = this.userForm.value;
console.log('Form submitted:', this.userForm.value);
} else {
// 标记所有字段为已触碰以显示验证错误
this.markFormGroupTouched();
}
}
resetForm() {
this.userForm.reset({
isAdmin: false
});
this.showAdminFields = false;
this.submittedData = null;
}
private markFormGroupTouched() {
Object.keys(this.userForm.controls).forEach(key => {
const control = this.userForm.get(key);
control?.markAsTouched();
});
}
}