Parcourir la source

mat-回车下一个字段

qdy il y a 1 mois
Parent
révision
ba48a7b6d4

+ 117
- 0
web/src/app/directives/enter-navigation.directive.ts Voir le fichier

@@ -0,0 +1,117 @@
1
+import { Directive, ElementRef, HostListener } from '@angular/core';
2
+
3
+@Directive({
4
+  selector: 'input[matInput]:not([type="submit"]):not([type="button"]), textarea[matInput]',
5
+  standalone: true
6
+})
7
+export class EnterNavigationDirective {
8
+  constructor(private elementRef: ElementRef<HTMLElement>) {}
9
+
10
+  @HostListener('keydown.enter', ['$event'])
11
+  onEnter(event: KeyboardEvent): void {
12
+    // 阻止默认的提交行为,保持现有表单提交逻辑
13
+    event.preventDefault();
14
+
15
+    // 如果是textarea且按着Shift键,允许换行
16
+    if (this.isTextArea() && event.shiftKey) {
17
+      return;
18
+    }
19
+
20
+    // 查找下一个可聚焦元素
21
+    const nextElement = this.findNextFocusable();
22
+    
23
+    if (nextElement) {
24
+      nextElement.focus();
25
+      
26
+      // 对于select元素,阻止事件进一步传播
27
+      if (nextElement.tagName === 'SELECT') {
28
+        event.stopPropagation();
29
+      }
30
+    } else {
31
+      // 没有下一个可聚焦元素,触发默认行为(提交表单)
32
+      this.triggerFormSubmit(event);
33
+    }
34
+  }
35
+
36
+  private isTextArea(): boolean {
37
+    return this.elementRef.nativeElement.tagName === 'TEXTAREA';
38
+  }
39
+
40
+  private findNextFocusable(): HTMLElement | null {
41
+    const currentElement = this.elementRef.nativeElement;
42
+    const form = this.findParentForm(currentElement);
43
+    
44
+    if (!form) {
45
+      return null;
46
+    }
47
+
48
+    const focusableElements = this.getFocusableElements(form);
49
+    const currentIndex = focusableElements.indexOf(currentElement);
50
+    
51
+    // 如果当前元素不在列表中或已经是最后一个,返回null
52
+    if (currentIndex === -1 || currentIndex >= focusableElements.length - 1) {
53
+      return null;
54
+    }
55
+
56
+    return focusableElements[currentIndex + 1];
57
+  }
58
+
59
+  private findParentForm(element: HTMLElement): HTMLElement | null {
60
+    let parent = element.parentElement;
61
+    
62
+    while (parent && parent.tagName !== 'FORM') {
63
+      parent = parent.parentElement;
64
+    }
65
+    
66
+    return parent;
67
+  }
68
+
69
+  private getFocusableElements(container: HTMLElement): HTMLElement[] {
70
+    // 定义输入类可聚焦元素的CSS选择器(排除按钮)
71
+    const inputSelectors = [
72
+      'input[matInput]:not([type="hidden"]):not([disabled]):not([readonly])',
73
+      'textarea[matInput]:not([disabled]):not([readonly])',
74
+      'select[matInput]:not([disabled])',
75
+      'input:not([matInput]):not([type="hidden"]):not([type="submit"]):not([type="button"]):not([disabled]):not([readonly])',
76
+      'textarea:not([matInput]):not([disabled]):not([readonly])',
77
+      'select:not([matInput]):not([disabled])',
78
+      '[contenteditable="true"]:not([disabled])'
79
+    ].join(', ');
80
+
81
+    // 获取所有匹配元素
82
+    const allElements = Array.from(container.querySelectorAll(inputSelectors)) as HTMLElement[];
83
+    
84
+    // 过滤掉隐藏的元素和不可交互的元素
85
+    return allElements.filter(element => {
86
+      const style = window.getComputedStyle(element);
87
+      const isVisible = style.display !== 'none' && 
88
+                       style.visibility !== 'hidden' && 
89
+                       style.opacity !== '0' &&
90
+                       !element.hasAttribute('hidden');
91
+      
92
+      // 检查元素是否在视图中(粗略检查)
93
+      const rect = element.getBoundingClientRect();
94
+      const isInViewport = rect.width > 0 && rect.height > 0;
95
+      
96
+      return isVisible && isInViewport;
97
+    });
98
+  }
99
+
100
+  private triggerFormSubmit(event: KeyboardEvent): void {
101
+    const currentElement = this.elementRef.nativeElement;
102
+    const form = this.findParentForm(currentElement);
103
+    
104
+    if (form) {
105
+      // 查找表单中的提交按钮并点击
106
+      const submitButton = form.querySelector('button[type="submit"], input[type="submit"]') as HTMLElement;
107
+      
108
+      if (submitButton) {
109
+        submitButton.click();
110
+      } else {
111
+        // 如果没有找到提交按钮,尝试触发表单的submit事件
112
+        const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
113
+        form.dispatchEvent(submitEvent);
114
+      }
115
+    }
116
+  }
117
+}

+ 6
- 19
web/src/app/pages/login/login.component.html Voir le fichier

@@ -13,32 +13,19 @@
13 13
         <mat-card-content class="card-content">
14 14
             <form [formGroup]="loginForm" (ngSubmit)="onSubmit()" class="login-form">
15 15
                 <mat-form-field appearance="outline" class="full-width">
16
+                   <mat-icon matPrefix>person</mat-icon>
16 17
                     <mat-label>用户名</mat-label>
17
-                    <input matInput formControlName="username" placeholder="请输入用户名" autocomplete="username">
18
-                    <mat-icon matPrefix>person</mat-icon>
19
-                    @if (username?.invalid && (username?.dirty || username?.touched)) {
20
-                        <mat-error>
21
-                            @if (username?.errors?.['required']) {
22
-                                用户名不能为空
23
-                            }
24
-                        </mat-error>
25
-                    }
18
+                    <input matInput formControlName="username"   autofocus>
26 19
                 </mat-form-field>
27 20
 
28 21
                 <mat-form-field appearance="outline" class="full-width">
29 22
                     <mat-label>密码</mat-label>
30
-                    <input matInput [type]="hidePassword ? 'password' : 'text'" formControlName="password" placeholder="请输入密码" autocomplete="current-password">
23
+                    <input matInput [type]="hidePassword ? 'password' : 'text'" formControlName="password" placeholder="请输入密码" >
31 24
                     <mat-icon matPrefix>lock</mat-icon>
32 25
                     <button type="button" mat-icon-button matSuffix (click)="hidePassword = !hidePassword" [attr.aria-label]="'隐藏密码'" [attr.aria-pressed]="hidePassword">
33 26
                         <mat-icon>{{hidePassword ? 'visibility_off' : 'visibility'}}</mat-icon>
34 27
                     </button>
35
-                    @if (password?.invalid && (password?.dirty || password?.touched)) {
36
-                        <mat-error>
37
-                            @if (password?.errors?.['required']) {
38
-                                密码不能为空
39
-                            }
40
-                        </mat-error>
41
-                    }
28
+  
42 29
                 </mat-form-field>
43 30
 
44 31
                 @if (errorMessage) {
@@ -63,8 +50,8 @@
63 50
 
64 51
         <mat-card-footer class="card-footer">
65 52
             <div class="footer-content">
66
-                <p>广东志华软件科技有限公司</p>
67
-                <p>2026-01-16</p>
53
+               <p>广东志华软件科技有限公司</p>
54
+               <p>Copyright &copy; 2026</p>
68 55
             </div>
69 56
         </mat-card-footer>
70 57
     </mat-card>

+ 41
- 56
web/src/app/pages/login/login.component.scss Voir le fichier

@@ -95,62 +95,6 @@
95 95
     width: 100%;
96 96
 }
97 97
 
98
-/* 美化 Material 表单字段 */
99
-::ng-deep .mat-mdc-form-field {
100
-    font-size: 14px;
101
-}
102
-
103
-::ng-deep .mat-mdc-text-field-wrapper {
104
-    background-color: #f8fafc !important;
105
-    border-radius: 12px !important;
106
-}
107
-
108
-::ng-deep .mat-mdc-form-field-subscript-wrapper {
109
-    display: none;
110
-}
111
-
112
-::ng-deep .mat-mdc-form-field-infix {
113
-    padding-top: 12px !important;
114
-    padding-bottom: 12px !important;
115
-    min-height: 48px;
116
-}
117
-
118
-::ng-deep .mat-mdc-form-field-flex {
119
-    align-items: center;
120
-}
121
-
122
-::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-text-field-wrapper {
123
-    background-color: #f8fafc !important;
124
-    box-shadow: none;
125
-}
126
-
127
-::ng-deep .mat-mdc-form-field .mat-mdc-floating-label {
128
-    top: 24px;
129
-}
130
-
131
-::ng-deep .mdc-text-field--outlined .mdc-notched-outline__leading,
132
-::ng-deep .mdc-text-field--outlined .mdc-notched-outline__notch,
133
-::ng-deep .mdc-text-field--outlined .mdc-notched-outline__trailing {
134
-    border-color: #e2e8f0 !important;
135
-    border-radius: 12px;
136
-}
137
-
138
-::ng-deep .mdc-text-field--outlined.mdc-text-field--focused .mdc-notched-outline__leading,
139
-::ng-deep .mdc-text-field--outlined.mdc-text-field--focused .mdc-notched-outline__notch,
140
-::ng-deep .mdc-text-field--outlined.mdc-text-field--focused .mdc-notched-outline__trailing {
141
-    border-color: #e2e8f0 !important;
142
-}
143
-
144
-::ng-deep .mat-mdc-form-field-icon-prefix,
145
-::ng-deep .mat-mdc-form-field-icon-suffix {
146
-    padding: 0 8px;
147
-}
148
-
149
-::ng-deep .mat-mdc-form-field-icon-prefix .mat-icon,
150
-::ng-deep .mat-mdc-form-field-icon-suffix .mat-icon {
151
-    color: #64748b;
152
-}
153
-
154 98
 /* 错误消息 */
155 99
 .error-message {
156 100
     background-color: #fee2e2;
@@ -270,4 +214,45 @@
270 214
     .card-footer {
271 215
         padding: 20px 24px;
272 216
     }
217
+}
218
+
219
+/* 简洁版:只修复连接线,调整边框颜色 */
220
+:host ::ng-deep {
221
+  .mat-mdc-form-field {
222
+    /* 核心修复:只去除 leading 的右边框 */
223
+    .mdc-notched-outline__leading {
224
+      border-right: none !important;
225
+    }
226
+    
227
+    /* 核心修复:只去除 notch 的左右边框 */
228
+    .mdc-notched-outline__notch {
229
+      border-left: none !important;
230
+      border-right: none !important;
231
+    }
232
+    
233
+    /* 图标无右边框 */
234
+    .mat-mdc-form-field-icon-prefix {
235
+      border-right: none !important;
236
+    }
237
+    
238
+    /* 调整边框颜色,与整体设计协调 */
239
+    .mdc-text-field--outlined {
240
+      .mdc-notched-outline {
241
+        .mdc-notched-outline__leading,
242
+        .mdc-notched-outline__notch,
243
+        .mdc-notched-outline__trailing {
244
+          border-color: #cbd5e1 !important; /* 更柔和的灰色 */
245
+        }
246
+      }
247
+      
248
+      /* 聚焦状态使用主题色 */
249
+      &.mdc-text-field--focused .mdc-notched-outline {
250
+        .mdc-notched-outline__leading,
251
+        .mdc-notched-outline__notch,
252
+        .mdc-notched-outline__trailing {
253
+          border-color: #7c3aed !important; /* 使用你的紫色主题色 */
254
+        }
255
+      }
256
+    }
257
+  }
273 258
 }

+ 3
- 1
web/src/app/pages/login/login.component.ts Voir le fichier

@@ -9,6 +9,7 @@ import { MatButtonModule } from '@angular/material/button';
9 9
 import { MatIconModule } from '@angular/material/icon';
10 10
 import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
11 11
 import { AuthService } from '../../services/auth.service';
12
+import { EnterNavigationDirective } from '../../directives/enter-navigation.directive';
12 13
 
13 14
 @Component({
14 15
   selector: 'app-login',
@@ -21,7 +22,8 @@ import { AuthService } from '../../services/auth.service';
21 22
     MatInputModule,
22 23
     MatButtonModule,
23 24
     MatIconModule,
24
-    MatProgressSpinnerModule
25
+    MatProgressSpinnerModule,
26
+    EnterNavigationDirective
25 27
   ],
26 28
   templateUrl: './login.component.html',
27 29
   styleUrl: './login.component.scss'

+ 3
- 1
web/src/app/pages/project-list/project-list.component.ts Voir le fichier

@@ -11,6 +11,7 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner';
11 11
 import { MatIcon } from '@angular/material/icon';
12 12
 import { ProjectService } from '../../services/project.service';
13 13
 import { Project, ProjectRequest } from '../../models/project.model';
14
+import { EnterNavigationDirective } from '../../directives/enter-navigation.directive';
14 15
 
15 16
 @Component({
16 17
   selector: 'app-project-list',
@@ -25,7 +26,8 @@ import { Project, ProjectRequest } from '../../models/project.model';
25 26
     MatFormFieldModule,
26 27
     MatInputModule,
27 28
     MatProgressSpinner,
28
-    MatIcon
29
+    MatIcon,
30
+    EnterNavigationDirective
29 31
   ],
30 32
   templateUrl: './project-list.component.html',
31 33
   styleUrl: './project-list.component.scss'

+ 3
- 1
web/src/app/pages/role-list/role-list.component.ts Voir le fichier

@@ -10,6 +10,7 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner';
10 10
 import { MatIcon } from '@angular/material/icon';
11 11
 import { RoleService } from '../../services/role.service';
12 12
 import { Role, RoleRequest } from '../../models/role.model';
13
+import { EnterNavigationDirective } from '../../directives/enter-navigation.directive';
13 14
 
14 15
 @Component({
15 16
   selector: 'app-role-list',
@@ -23,7 +24,8 @@ import { Role, RoleRequest } from '../../models/role.model';
23 24
     MatFormFieldModule,
24 25
     MatInputModule,
25 26
     MatProgressSpinner,
26
-    MatIcon
27
+    MatIcon,
28
+    EnterNavigationDirective
27 29
   ],
28 30
   templateUrl: './role-list.component.html',
29 31
   styleUrl: './role-list.component.scss'

+ 3
- 1
web/src/app/pages/tenant-list/tenant-list.component.ts Voir le fichier

@@ -10,6 +10,7 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner';
10 10
 import { MatIcon } from '@angular/material/icon';
11 11
 import { TenantService } from '../../services/tenant.service';
12 12
 import { Tenant, TenantRequest } from '../../models/tenant.model';
13
+import { EnterNavigationDirective } from '../../directives/enter-navigation.directive';
13 14
 
14 15
 @Component({
15 16
   selector: 'app-tenant-list',
@@ -23,7 +24,8 @@ import { Tenant, TenantRequest } from '../../models/tenant.model';
23 24
     MatFormFieldModule,
24 25
     MatInputModule,
25 26
     MatProgressSpinner,
26
-    MatIcon
27
+    MatIcon,
28
+    EnterNavigationDirective
27 29
   ],
28 30
   templateUrl: './tenant-list.component.html',
29 31
   styleUrl: './tenant-list.component.scss'

+ 3
- 1
web/src/app/pages/user-list/user-list.component.ts Voir le fichier

@@ -10,6 +10,7 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner';
10 10
 import { MatIcon } from '@angular/material/icon';
11 11
 import { UserService } from '../../services/user.service';
12 12
 import { User, UserRequest } from '../../models/user.model';
13
+import { EnterNavigationDirective } from '../../directives/enter-navigation.directive';
13 14
 
14 15
 @Component({
15 16
   selector: 'app-user-list',
@@ -23,7 +24,8 @@ import { User, UserRequest } from '../../models/user.model';
23 24
     MatFormFieldModule,
24 25
     MatInputModule,
25 26
     MatProgressSpinner,
26
-    MatIcon
27
+    MatIcon,
28
+    EnterNavigationDirective
27 29
   ],
28 30
   templateUrl: './user-list.component.html',
29 31
   styleUrl: './user-list.component.scss'

Loading…
Annuler
Enregistrer