浏览代码

缩放改好

qdy 1 个月前
父节点
当前提交
2ff98c0009

+ 1
- 1
src/app/app.component.html 查看文件

@@ -38,7 +38,7 @@
38 38
     <!-- 右侧内容区 -->
39 39
     <div class="flex-1 h-full overflow-hidden bg-white flex flex-col">
40 40
 
41
-      <div class="flex-1 overflow-auto">
41
+      <div class="flex-1 overflow-auto content-area">
42 42
         <div class="p-4 h-full">
43 43
           <router-outlet></router-outlet>
44 44
         </div>

+ 4
- 6
src/app/components/sticky-header/sticky-header.component.scss 查看文件

@@ -59,18 +59,17 @@
59 59
     margin-bottom: 0; /* 移除margin,因为现在是fixed定位 */
60 60
     transform: none; /* 移除transform层叠上下文 */
61 61
     will-change: auto;
62
-    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); /* 外阴影,表示浮动状态 */
62
+    box-shadow: none; /* 移除外阴影,增强一体感 */
63 63
     background-color: white; /* 确保背景不透明 */
64 64
     
65 65
     .header-content {
66 66
       border-radius: 0.5rem;
67 67
       box-shadow: 
68 68
         inset 0 2px 4px 0 rgba(0, 0, 0, 0.06),
69
-        inset 0 0 0 1px rgba(255, 255, 255, 0.9),
70
-        0 2px 4px 0 rgba(0, 0, 0, 0.1); /* 更强外阴影,强调固定状态 */
69
+        inset 0 0 0 1px rgba(255, 255, 255, 0.9); /* 内阴影,增强一体感 */
71 70
       background: #f1f5f9;
72 71
       border: 1px solid #cbd5e1;
73
-      margin: 1rem; /* 添加外边距,避免贴边 */
72
+      margin: 0; /* 移除外边距,确保精确对齐 */
74 73
       /* 保持原padding大小 */
75 74
     }
76 75
 
@@ -114,8 +113,7 @@
114 113
       border-radius: 0.375rem;
115 114
       box-shadow: 
116 115
         inset 0 3px 6px 0 rgba(0, 0, 0, 0.08), /* 更强的内阴影,增强凹陷感 */
117
-        inset 0 0 0 1px rgba(255, 255, 255, 0.8), /* 内边缘高光 */
118
-        0 1px 3px 0 rgba(0, 0, 0, 0.1); /* 轻微外阴影 */
116
+        inset 0 0 0 1px rgba(255, 255, 255, 0.8); /* 内边缘高光,增强一体感 */
119 117
       background: #f1f5f9; /* 稍微深一点的背景,强调状态变化 */
120 118
       border: 1px solid #cbd5e1; /* 保持一致的边框 */
121 119
       

+ 261
- 45
src/app/components/sticky-header/sticky-header.component.ts 查看文件

@@ -12,10 +12,17 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
12 12
   styleUrl: './sticky-header.component.scss'
13 13
 })
14 14
 export class StickyHeaderComponent implements AfterViewInit, OnDestroy {
15
-  constructor(
15
+   constructor(
16 16
     private renderer: Renderer2,
17 17
     private elementRef: ElementRef
18 18
   ) {}
19
+  
20
+  // 滚动优化相关属性
21
+  private scrollRafId: number | null = null;
22
+  private lastScrollTop = 0;
23
+  private lastIsLocked = false;
24
+  private lastIsCompact = false;
25
+  
19 26
   /** 标题文本 */
20 27
   @Input() title = '';
21 28
   
@@ -85,6 +92,9 @@ export class StickyHeaderComponent implements AfterViewInit, OnDestroy {
85 92
   /** 锁定时的左侧偏移(像素) */
86 93
   @Input() headerLeft: string | number | null = null;
87 94
 
95
+  /** 宽度参考目标选择器(用于锁定状态宽度匹配),如未设置则使用滚动容器 */
96
+  @Input() widthTarget: string | null = null;
97
+
88 98
   /** 按钮点击事件 */
89 99
   @Output() buttonClick = new EventEmitter<void>();
90 100
 
@@ -105,8 +115,12 @@ export class StickyHeaderComponent implements AfterViewInit, OnDestroy {
105 115
     }
106 116
   }
107 117
 
108
-  ngOnDestroy() {
118
+   ngOnDestroy() {
109 119
     this.cleanupScrollListener();
120
+    if (this.scrollRafId) {
121
+      cancelAnimationFrame(this.scrollRafId);
122
+      this.scrollRafId = null;
123
+    }
110 124
   }
111 125
 
112 126
   private setupAutoScrollDetection() {
@@ -116,9 +130,13 @@ export class StickyHeaderComponent implements AfterViewInit, OnDestroy {
116 130
     const container = this.getScrollContainer();
117 131
     const isWindowScroll = this.scrollContainer === 'window';
118 132
     
119
-    if (isWindowScroll) {
133
+     if (isWindowScroll) {
120 134
       // 窗口滚动
121 135
       this.canScroll = this.checkWindowScrollable();
136
+      const scrollHeight = document.body.scrollHeight || document.documentElement.scrollHeight || 0;
137
+      const clientHeight = window.innerHeight || document.documentElement.clientHeight || 0;
138
+      
139
+      console.log(`sticky-header: 窗口滚动性检查 - scrollHeight: ${scrollHeight}, clientHeight: ${clientHeight}, canScroll: ${this.canScroll}`);
122 140
       
123 141
       if (this.canScroll) {
124 142
         this.scrollListener = this.renderer.listen(
@@ -131,13 +149,18 @@ export class StickyHeaderComponent implements AfterViewInit, OnDestroy {
131 149
         // 初始检查窗口滚动位置
132 150
         this.checkInitialWindowScrollPosition();
133 151
       } else {
134
-        console.log('sticky-header: 窗口内容不足,禁用自动检测');
152
+        console.log('sticky-header: 窗口内容不足,禁用自动检测,设置isLocked=false, isCompact=false');
135 153
         this.isLocked = false;
136 154
         this.isCompact = false;
155
+        this.isScrolled = false;
137 156
       }
138
-    } else if (container) {
157
+     } else if (container) {
139 158
       // 容器滚动
140 159
       this.canScroll = this.checkScrollable(container);
160
+      const scrollHeight = container.scrollHeight || 0;
161
+      const clientHeight = container.clientHeight || 0;
162
+      
163
+      console.log(`sticky-header: 容器滚动性检查 - scrollHeight: ${scrollHeight}, clientHeight: ${clientHeight}, canScroll: ${this.canScroll}, selector: ${this.scrollContainer}`);
141 164
       
142 165
       if (this.canScroll) {
143 166
         this.scrollListener = this.renderer.listen(
@@ -150,10 +173,11 @@ export class StickyHeaderComponent implements AfterViewInit, OnDestroy {
150 173
         // 初始检查滚动位置
151 174
         this.checkInitialScrollPosition(container);
152 175
       } else {
153
-        console.log('sticky-header: 滚动容器内容不足,禁用自动检测');
176
+        console.log('sticky-header: 滚动容器内容不足,禁用自动检测,设置isLocked=false, isCompact=false');
154 177
         // 确保状态为false
155 178
         this.isLocked = false;
156 179
         this.isCompact = false;
180
+        this.isScrolled = false;
157 181
       }
158 182
     } else {
159 183
       console.warn('sticky-header: 未找到滚动容器:', this.scrollContainer);
@@ -187,7 +211,7 @@ export class StickyHeaderComponent implements AfterViewInit, OnDestroy {
187 211
     return canScroll;
188 212
   }
189 213
 
190
-  private checkWindowScrollable(): boolean {
214
+   private checkWindowScrollable(): boolean {
191 215
     const scrollHeight = document.body.scrollHeight || document.documentElement.scrollHeight || 0;
192 216
     const clientHeight = window.innerHeight || document.documentElement.clientHeight || 0;
193 217
     
@@ -195,78 +219,270 @@ export class StickyHeaderComponent implements AfterViewInit, OnDestroy {
195 219
     return canScroll;
196 220
   }
197 221
 
198
-  private handleWindowScroll() {
199
-    // 如果窗口不可滚动,不处理滚动事件
200
-    if (!this.canScroll) return;
222
+   private findContentContainer(): HTMLElement | null {
223
+    // 尝试查找内容区域容器,优先级:
224
+    // 1. .content-area 类 (我们在app.component.html中添加的)
225
+    // 2. 包含router-outlet的容器
226
+    // 3. 右侧内容区域
227
+    const selectors = [
228
+      '.content-area',
229
+      '.flex-1.overflow-auto',
230
+      'div[class*="content"]',
231
+      'div[class*="main"]',
232
+      'div[class*="container"]'
233
+    ];
201 234
     
202
-    const scrollTop = window.scrollY || document.documentElement.scrollTop || 0;
235
+    for (const selector of selectors) {
236
+      const container = document.querySelector(selector) as HTMLElement;
237
+      if (container) {
238
+        console.log(`sticky-header: 找到内容容器: ${selector}, 位置: ${container.getBoundingClientRect().left}px, 宽度: ${container.getBoundingClientRect().width}px`);
239
+        return container;
240
+      }
241
+    }
203 242
     
204
-    // 更新滚动状态
205
-    this.isScrolled = scrollTop > this.scrollThreshold;
206
-    this.isLocked = scrollTop > this.scrollThreshold;
207
-    this.isCompact = scrollTop > this.compactThreshold;
243
+    console.warn('sticky-header: 未找到内容容器');
244
+    return null;
245
+  }
246
+
247
+  private getWidthTargetElement(): HTMLElement | null {
248
+    // 如果设置了widthTarget,优先使用它
249
+    if (this.widthTarget) {
250
+      const target = document.querySelector(this.widthTarget) as HTMLElement;
251
+      if (target) {
252
+        console.log(`sticky-header: 找到宽度目标元素: ${this.widthTarget}, 宽度: ${target.getBoundingClientRect().width}px`);
253
+        return target;
254
+      }
255
+      console.warn(`sticky-header: 未找到宽度目标元素: ${this.widthTarget}`);
256
+    }
208 257
     
209
-    // 设置宽度匹配容器
210
-    if (this.isLocked) {
211
-      // 对于窗口滚动,使用整个视口宽度
212
-      this.headerWidth = '100%';
213
-      this.headerLeft = 0;
258
+    // 否则根据滚动容器类型返回相应元素
259
+    if (this.scrollContainer === 'window') {
260
+      return this.findContentContainer();
214 261
     } else {
215
-      this.headerWidth = null;
216
-      this.headerLeft = null;
262
+      const container = this.getScrollContainer();
263
+      return container;
217 264
     }
218 265
   }
219 266
 
220
-  private checkInitialWindowScrollPosition() {
221
-    const scrollTop = window.scrollY || document.documentElement.scrollTop || 0;
222
-    this.isScrolled = scrollTop > this.scrollThreshold;
223
-    this.isLocked = scrollTop > this.scrollThreshold;
224
-    this.isCompact = scrollTop > this.compactThreshold;
267
+  private getSidebarWidth(): number {
268
+    // 从localStorage读取侧边栏宽度,默认320px
269
+    try {
270
+      const savedWidth = localStorage.getItem('sidebarWidth');
271
+      if (savedWidth) {
272
+        const width = parseInt(savedWidth, 10);
273
+        if (!isNaN(width) && width >= 200 && width <= 600) {
274
+          return width;
275
+        }
276
+      }
277
+    } catch (e) {
278
+      console.warn('sticky-header: 读取侧边栏宽度失败', e);
279
+    }
280
+    return 320; // 默认宽度
281
+  }
282
+
283
+    private handleWindowScroll() {
284
+    // 如果窗口不可滚动,不处理滚动事件
285
+    if (!this.canScroll) {
286
+      console.log('sticky-header: 窗口不可滚动,跳过处理');
287
+      return;
288
+    }
225 289
     
226
-    if (this.isLocked) {
227
-      this.headerWidth = '100%';
228
-      this.headerLeft = 0;
290
+    // 使用requestAnimationFrame防抖,避免频繁更新
291
+    if (this.scrollRafId) {
292
+      cancelAnimationFrame(this.scrollRafId);
229 293
     }
230 294
     
231
-    console.log(`sticky-header: 初始窗口滚动位置检查: scrollTop=${scrollTop}, isLocked=${this.isLocked}, isCompact=${this.isCompact}`);
295
+    this.scrollRafId = requestAnimationFrame(() => {
296
+      this.scrollRafId = null;
297
+      
298
+      const scrollTop = window.scrollY || document.documentElement.scrollTop || 0;
299
+      
300
+      // 检查滚动位置是否显著变化,避免微小滚动触发状态更新
301
+      const scrollDelta = Math.abs(scrollTop - this.lastScrollTop);
302
+      if (scrollDelta < 1) { // 小于1像素的变化忽略
303
+        return;
304
+      }
305
+      
306
+      this.lastScrollTop = scrollTop;
307
+      
308
+      // 计算新状态
309
+      const newIsScrolled = scrollTop > this.scrollThreshold;
310
+      const newIsLocked = scrollTop > this.scrollThreshold;
311
+      const newIsCompact = scrollTop > this.compactThreshold;
312
+      
313
+      // 检查状态是否真正变化,避免不必要更新
314
+      const stateChanged = 
315
+        newIsLocked !== this.lastIsLocked || 
316
+        newIsCompact !== this.lastIsCompact ||
317
+        newIsScrolled !== this.isScrolled;
318
+      
319
+      if (!stateChanged) {
320
+        console.log(`sticky-header: 滚动位置变化但状态未变,scrollTop=${scrollTop}, isLocked=${newIsLocked}, isCompact=${newIsCompact}`);
321
+        return;
322
+      }
323
+      
324
+      this.lastIsLocked = newIsLocked;
325
+      this.lastIsCompact = newIsCompact;
326
+      
327
+      // 更新滚动状态
328
+      this.isScrolled = newIsScrolled;
329
+      this.isLocked = newIsLocked;
330
+      this.isCompact = newIsCompact;
331
+      
332
+      console.log(`sticky-header: 滚动状态更新,scrollTop=${scrollTop}, isLocked=${this.isLocked}, isCompact=${this.isCompact}`);
333
+      
334
+      // 设置宽度匹配容器
335
+      if (this.isLocked) {
336
+        // 使用宽度目标元素计算宽度
337
+        const widthTargetElement = this.getWidthTargetElement();
338
+        if (widthTargetElement) {
339
+          const rect = widthTargetElement.getBoundingClientRect();
340
+          this.headerWidth = rect.width;
341
+          this.headerLeft = rect.left;
342
+          console.log(`sticky-header: 窗口滚动锁定,目标宽度: ${rect.width}px, 左侧位置: ${rect.left}px, 目标元素: ${this.widthTarget || '默认'}`);
343
+        } else {
344
+          // 回退:使用视口宽度减去实际侧边栏宽度
345
+          const sidebarWidth = this.getSidebarWidth();
346
+          this.headerWidth = `calc(100% - ${sidebarWidth}px)`;
347
+          this.headerLeft = sidebarWidth;
348
+          console.log(`sticky-header: 未找到宽度目标元素,使用回退宽度: ${this.headerWidth}, 侧边栏宽度: ${sidebarWidth}px`);
349
+        }
350
+      } else {
351
+        this.headerWidth = null;
352
+        this.headerLeft = null;
353
+      }
354
+    });
232 355
   }
233 356
 
234
-  private handleScroll(event: Event) {
235
-    // 如果容器不可滚动,不处理滚动事件
236
-    if (!this.canScroll) return;
237
-    
238
-    const container = event.target as HTMLElement;
239
-    const scrollTop = container.scrollTop || 0;
357
+    private checkInitialWindowScrollPosition() {
358
+    const scrollTop = window.scrollY || document.documentElement.scrollTop || 0;
359
+    this.lastScrollTop = scrollTop;
240 360
     
241
-    // 更新滚动状态
242 361
     this.isScrolled = scrollTop > this.scrollThreshold;
243 362
     this.isLocked = scrollTop > this.scrollThreshold;
244 363
     this.isCompact = scrollTop > this.compactThreshold;
245 364
     
246
-    // 设置宽度匹配容器
365
+    // 记录初始状态
366
+    this.lastIsLocked = this.isLocked;
367
+    this.lastIsCompact = this.isCompact;
368
+    
247 369
     if (this.isLocked) {
248
-      const rect = container.getBoundingClientRect();
249
-      this.headerWidth = rect.width;
250
-      this.headerLeft = rect.left;
370
+      // 使用宽度目标元素计算宽度
371
+      const widthTargetElement = this.getWidthTargetElement();
372
+      if (widthTargetElement) {
373
+        const rect = widthTargetElement.getBoundingClientRect();
374
+        this.headerWidth = rect.width;
375
+        this.headerLeft = rect.left;
376
+        console.log(`sticky-header: 初始窗口滚动位置检查: scrollTop=${scrollTop}, isLocked=${this.isLocked}, isCompact=${this.isCompact}, 目标宽度: ${rect.width}px, 目标元素: ${this.widthTarget || '默认'}`);
377
+      } else {
378
+        // 回退:使用视口宽度减去实际侧边栏宽度
379
+        const sidebarWidth = this.getSidebarWidth();
380
+        this.headerWidth = `calc(100% - ${sidebarWidth}px)`;
381
+        this.headerLeft = sidebarWidth;
382
+        console.log(`sticky-header: 初始窗口滚动位置检查: scrollTop=${scrollTop}, isLocked=${this.isLocked}, isCompact=${this.isCompact}, 使用回退宽度`);
383
+      }
251 384
     } else {
252 385
       this.headerWidth = null;
253 386
       this.headerLeft = null;
254 387
     }
388
+    
389
+    console.log(`sticky-header: 初始窗口滚动位置检查完成: scrollTop=${scrollTop}, isLocked=${this.isLocked}, isCompact=${this.isCompact}, canScroll=${this.canScroll}`);
255 390
   }
256 391
 
257
-  private checkInitialScrollPosition(container: HTMLElement) {
392
+   private handleScroll(event: Event) {
393
+    // 如果容器不可滚动,不处理滚动事件
394
+    if (!this.canScroll) {
395
+      console.log('sticky-header: 容器不可滚动,跳过处理');
396
+      return;
397
+    }
398
+    
399
+    // 使用requestAnimationFrame防抖,避免频繁更新
400
+    if (this.scrollRafId) {
401
+      cancelAnimationFrame(this.scrollRafId);
402
+    }
403
+    
404
+    this.scrollRafId = requestAnimationFrame(() => {
405
+      this.scrollRafId = null;
406
+      
407
+      const container = event.target as HTMLElement;
408
+      const scrollTop = container.scrollTop || 0;
409
+      
410
+      // 检查滚动位置是否显著变化,避免微小滚动触发状态更新
411
+      const scrollDelta = Math.abs(scrollTop - this.lastScrollTop);
412
+      if (scrollDelta < 1) { // 小于1像素的变化忽略
413
+        return;
414
+      }
415
+      
416
+      this.lastScrollTop = scrollTop;
417
+      
418
+      // 计算新状态
419
+      const newIsScrolled = scrollTop > this.scrollThreshold;
420
+      const newIsLocked = scrollTop > this.scrollThreshold;
421
+      const newIsCompact = scrollTop > this.compactThreshold;
422
+      
423
+      // 检查状态是否真正变化,避免不必要更新
424
+      const stateChanged = 
425
+        newIsLocked !== this.lastIsLocked || 
426
+        newIsCompact !== this.lastIsCompact ||
427
+        newIsScrolled !== this.isScrolled;
428
+      
429
+      if (!stateChanged) {
430
+        console.log(`sticky-header: 容器滚动位置变化但状态未变,scrollTop=${scrollTop}, isLocked=${newIsLocked}, isCompact=${newIsCompact}`);
431
+        return;
432
+      }
433
+      
434
+      this.lastIsLocked = newIsLocked;
435
+      this.lastIsCompact = newIsCompact;
436
+      
437
+      // 更新滚动状态
438
+      this.isScrolled = newIsScrolled;
439
+      this.isLocked = newIsLocked;
440
+      this.isCompact = newIsCompact;
441
+      
442
+      console.log(`sticky-header: 容器滚动状态更新,scrollTop=${scrollTop}, isLocked=${this.isLocked}, isCompact=${this.isCompact}`);
443
+      
444
+      // 设置宽度匹配容器
445
+      if (this.isLocked) {
446
+        // 优先使用宽度目标元素,否则使用滚动容器
447
+        const widthTargetElement = this.getWidthTargetElement();
448
+        const targetElement = widthTargetElement || container;
449
+        const rect = targetElement.getBoundingClientRect();
450
+        this.headerWidth = rect.width;
451
+        this.headerLeft = rect.left;
452
+        console.log(`sticky-header: 容器滚动锁定,目标宽度: ${rect.width}px, 左侧位置: ${rect.left}px, 目标元素: ${widthTargetElement ? (this.widthTarget || '默认') : '滚动容器'}`);
453
+      } else {
454
+        this.headerWidth = null;
455
+        this.headerLeft = null;
456
+      }
457
+    });
458
+  }
459
+
460
+   private checkInitialScrollPosition(container: HTMLElement) {
258 461
     const scrollTop = container.scrollTop || 0;
462
+    this.lastScrollTop = scrollTop;
463
+    
259 464
     this.isScrolled = scrollTop > this.scrollThreshold;
260 465
     this.isLocked = scrollTop > this.scrollThreshold;
261 466
     this.isCompact = scrollTop > this.compactThreshold;
262 467
     
468
+    // 记录初始状态
469
+    this.lastIsLocked = this.isLocked;
470
+    this.lastIsCompact = this.isCompact;
471
+    
263 472
     if (this.isLocked) {
264
-      const rect = container.getBoundingClientRect();
473
+      // 优先使用宽度目标元素,否则使用滚动容器
474
+      const widthTargetElement = this.getWidthTargetElement();
475
+      const targetElement = widthTargetElement || container;
476
+      const rect = targetElement.getBoundingClientRect();
265 477
       this.headerWidth = rect.width;
266 478
       this.headerLeft = rect.left;
479
+      console.log(`sticky-header: 初始容器滚动位置检查: scrollTop=${scrollTop}, isLocked=${this.isLocked}, isCompact=${this.isCompact}, 目标宽度: ${rect.width}px, 目标元素: ${widthTargetElement ? (this.widthTarget || '默认') : '滚动容器'}`);
480
+    } else {
481
+      this.headerWidth = null;
482
+      this.headerLeft = null;
267 483
     }
268 484
     
269
-    console.log(`sticky-header: 初始滚动位置检查: scrollTop=${scrollTop}, isLocked=${this.isLocked}, isCompact=${this.isCompact}`);
485
+    console.log(`sticky-header: 初始容器滚动位置检查完成: scrollTop=${scrollTop}, isLocked=${this.isLocked}, isCompact=${this.isCompact}, canScroll=${this.canScroll}`);
270 486
   }
271 487
 
272 488
   private cleanupScrollListener() {

+ 8
- 7
src/app/pages/service-register-config/service-register-config.component.html 查看文件

@@ -1,4 +1,4 @@
1
-<div class="h-full flex flex-col p-4 min-w-0">
1
+<div class="h-full flex flex-col p-2 min-w-0">
2 2
   <!-- 顶部标题区域 -->
3 3
   <app-sticky-header
4 4
     [title]="title"
@@ -16,17 +16,18 @@
16 16
     [debugRegisterUrl]="debugInfo.registerUrl"
17 17
     [debugListUrl]="debugInfo.listUrl"
18 18
     [autoDetect]="true"
19
-    [scrollContainer]="'.content-area'"
20
-    [scrollThreshold]="10"
21
-    [compactThreshold]="50"
19
+      [scrollContainer]="'.content-area'"
20
+     [scrollThreshold]="20"
21
+    [compactThreshold]="80"
22
+    [widthTarget]="'#matCard'"
22 23
     (buttonClick)="onRegister()"
23 24
   ></app-sticky-header>
24 25
    
25
-      <mat-card #matCard class="flex-1">
26
+      <mat-card #matCard id="matCard" class="flex-1">
26 27
     <mat-card-content>
27 28
         <!-- AG Grid组件 -->
28 29
         <ag-grid-angular
29
-          class="ag-theme-alpine h-[calc(100vh-180px)]"
30
+           class="ag-theme-alpine h-[800px]"
30 31
           [columnDefs]="columnDefs"
31 32
           [rowModelType]="'infinite'"
32 33
           [datasource]="datasource"
@@ -34,7 +35,7 @@
34 35
           [paginationPageSize]="paginationPageSize"
35 36
           [cacheBlockSize]="paginationPageSize"
36 37
           [maxBlocksInCache]="10"
37
-          [domLayout]="'normal'"
38
+            [domLayout]="'normal'"
38 39
           [rowSelection]="'single'"
39 40
           (gridReady)="onGridReady($event)">
40 41
         </ag-grid-angular>

+ 121
- 61
src/app/pages/service-register-config/service-register-config.component.ts 查看文件

@@ -44,7 +44,7 @@ export class ServiceRegisterConfigComponent implements OnInit, AfterViewInit, On
44 44
    configMetaList: ConfigMeta[] = [];
45 45
 
46 46
    // 分页配置
47
-  paginationPageSize = 10;
47
+   paginationPageSize = 100;
48 48
   currentPage = 1;
49 49
   totalRecords = 0;
50 50
   gridApi: GridApi | null = null;
@@ -137,66 +137,123 @@ export class ServiceRegisterConfigComponent implements OnInit, AfterViewInit, On
137 137
     });
138 138
   }
139 139
 
140
-   createInfiniteDatasource(): IDatasource {
141
-     return {
142
-       getRows: (params: IGetRowsParams) => {
143
-         const pageSize = this.paginationPageSize;
144
-         const startRow = params.startRow;
145
-         const page = Math.floor(startRow / pageSize);
146
-         
147
-         const request: ConfigMetaQueryRequest = {
148
-           page: page,
149
-           pageSize: pageSize
150
-         };
151
-         
152
-         console.log(`Infinite row model: startRow=${startRow}, endRow=${params.endRow}, page=${page}, pageSize=${pageSize}`);
153
-         
154
-         this.loading = true;
155
-         this.configMetaService.listConfigMetaPaginated(request).subscribe({
156
-           next: (result: PaginatedQueryResult<ConfigMeta[]>) => {
157
-             this.loading = false;
158
-             if (result.success && result.data) {
159
-               const rowData = result.data;
160
-               const totalCount = result.totalCount || 0;
161
-               
162
-               // 成功回调,提供行数据及总行数
163
-               // 对于无限行模型,需要传递 rowCount(总行数)以便网格知道何时停止加载
164
-               const lastRow = totalCount > 0 ? totalCount : -1;
165
-               params.successCallback(rowData, lastRow);
166
-               
167
-               // 更新调试信息
168
-               this.debugInfo.dataSource = 'API数据';
169
-               this.debugInfo.recordCount = totalCount;
170
-               this.debugInfo.lastUpdated = new Date().toISOString();
171
-               this.debugInfo.useMockData = false;
172
-               this.debugInfo.registerUrl = this.configMetaService.getInitUrl();
173
-               this.debugInfo.listUrl = this.configMetaService.getListUrl();
174
-               this.debugInfo.currentPage = page + 1;
175
-               this.debugInfo.totalPages = Math.ceil(totalCount / pageSize);
176
-               this.debugInfo.pageSize = pageSize;
177
-               
178
-               console.log(`无限行模型数据加载成功: ${rowData.length}条记录,总计: ${totalCount}`);
179
-               
180
-               // 更新组件状态
181
-               this.totalRecords = totalCount;
182
-               this.rowData = rowData;
183
-               this.configMetaList = rowData;
184
-             } else {
185
-               console.error('无限行模型数据加载失败:', result);
186
-               params.failCallback();
187
-             }
188
-             this.cdRef.detectChanges();
189
-           },
190
-           error: (error: HttpErrorResponse) => {
191
-             this.loading = false;
192
-             console.error('无限行模型加载数据时出错:', error);
193
-             params.failCallback();
194
-             this.cdRef.detectChanges();
195
-           }
196
-         });
197
-       },
198
-       rowCount: undefined // 初始时不知道总行数,将在第一次加载后由successCallback提供
199
-     };
140
+    private generateMockData(page: number, pageSize: number): { data: any[], totalCount: number } {
141
+      const totalMockCount = 200;
142
+      const startIndex = page * pageSize;
143
+      const endIndex = Math.min(startIndex + pageSize, totalMockCount);
144
+      
145
+      const mockData = [];
146
+      for (let i = startIndex; i < endIndex; i++) {
147
+        mockData.push({
148
+          id: i + 1,
149
+          configName: `配置${i + 1}`,
150
+          fieldName: `field_${i + 1}`,
151
+          fieldType: i % 3 === 0 ? 'string' : i % 3 === 1 ? 'number' : 'boolean',
152
+          yamlName: `yaml_${i + 1}`,
153
+          fieldDesc: `这是第${i + 1}个配置字段的描述`,
154
+          creator: `用户${(i % 5) + 1}`,
155
+          createdAt: new Date(Date.now() - i * 86400000).toISOString()
156
+        });
157
+      }
158
+      
159
+      return {
160
+        data: mockData,
161
+        totalCount: totalMockCount
162
+      };
163
+    }
164
+
165
+    createInfiniteDatasource(): IDatasource {
166
+      return {
167
+        getRows: (params: IGetRowsParams) => {
168
+          const pageSize = this.paginationPageSize;
169
+          const startRow = params.startRow;
170
+          const page = Math.floor(startRow / pageSize);
171
+          
172
+          console.log(`Infinite row model: startRow=${startRow}, endRow=${params.endRow}, page=${page}, pageSize=${pageSize}`);
173
+          
174
+          // 使用模拟数据或真实API
175
+          if (this.debugInfo.useMockData) {
176
+            this.loading = true;
177
+            setTimeout(() => {
178
+              const mockResult = this.generateMockData(page, pageSize);
179
+              const rowData = mockResult.data;
180
+              const totalCount = mockResult.totalCount;
181
+              
182
+              const lastRow = totalCount > 0 ? totalCount : -1;
183
+              params.successCallback(rowData, lastRow);
184
+              
185
+              // 更新调试信息
186
+              this.debugInfo.dataSource = '模拟数据';
187
+              this.debugInfo.recordCount = totalCount;
188
+              this.debugInfo.lastUpdated = new Date().toISOString();
189
+              this.debugInfo.registerUrl = '模拟数据 - 无API调用';
190
+              this.debugInfo.listUrl = '模拟数据 - 无API调用';
191
+              this.debugInfo.currentPage = page + 1;
192
+              this.debugInfo.totalPages = Math.ceil(totalCount / pageSize);
193
+              this.debugInfo.pageSize = pageSize;
194
+              
195
+              console.log(`模拟数据加载成功: ${rowData.length}条记录,总计: ${totalCount}`);
196
+              
197
+              // 更新组件状态
198
+              this.totalRecords = totalCount;
199
+              this.rowData = rowData;
200
+              this.configMetaList = rowData;
201
+              this.loading = false;
202
+              this.cdRef.detectChanges();
203
+            }, 100);
204
+          } else {
205
+            const request: ConfigMetaQueryRequest = {
206
+              page: page,
207
+              pageSize: pageSize
208
+            };
209
+            
210
+            this.loading = true;
211
+            this.configMetaService.listConfigMetaPaginated(request).subscribe({
212
+              next: (result: PaginatedQueryResult<ConfigMeta[]>) => {
213
+                this.loading = false;
214
+                if (result.success && result.data) {
215
+                  const rowData = result.data;
216
+                  const totalCount = result.totalCount || 0;
217
+                  
218
+                  // 成功回调,提供行数据及总行数
219
+                  // 对于无限行模型,需要传递 rowCount(总行数)以便网格知道何时停止加载
220
+                  const lastRow = totalCount > 0 ? totalCount : -1;
221
+                  params.successCallback(rowData, lastRow);
222
+                  
223
+                  // 更新调试信息
224
+                  this.debugInfo.dataSource = 'API数据';
225
+                  this.debugInfo.recordCount = totalCount;
226
+                  this.debugInfo.lastUpdated = new Date().toISOString();
227
+                  this.debugInfo.useMockData = false;
228
+                  this.debugInfo.registerUrl = this.configMetaService.getInitUrl();
229
+                  this.debugInfo.listUrl = this.configMetaService.getListUrl();
230
+                  this.debugInfo.currentPage = page + 1;
231
+                  this.debugInfo.totalPages = Math.ceil(totalCount / pageSize);
232
+                  this.debugInfo.pageSize = pageSize;
233
+                  
234
+                  console.log(`无限行模型数据加载成功: ${rowData.length}条记录,总计: ${totalCount}`);
235
+                  
236
+                  // 更新组件状态
237
+                  this.totalRecords = totalCount;
238
+                  this.rowData = rowData;
239
+                  this.configMetaList = rowData;
240
+                } else {
241
+                  console.error('无限行模型数据加载失败:', result);
242
+                  params.failCallback();
243
+                }
244
+                this.cdRef.detectChanges();
245
+              },
246
+              error: (error: HttpErrorResponse) => {
247
+                this.loading = false;
248
+                console.error('无限行模型加载数据时出错:', error);
249
+                params.failCallback();
250
+                this.cdRef.detectChanges();
251
+              }
252
+            });
253
+          }
254
+        },
255
+        rowCount: undefined // 初始时不知道总行数,将在第一次加载后由successCallback提供
256
+      };
200 257
    }
201 258
 
202 259
      onGridReady(params: GridReadyEvent) {
@@ -253,6 +310,9 @@ export class ServiceRegisterConfigComponent implements OnInit, AfterViewInit, On
253 310
     console.log('ServiceRegisterConfigComponent view initialized');
254 311
     // 注意:滚动检测现在由 sticky-header 组件自动处理
255 312
     // 通过 [autoDetect]="true" 启用
313
+    
314
+    // 启用模拟数据用于测试滚动效果
315
+    this.debugInfo.useMockData = true;
256 316
   }
257 317
 
258 318
 

+ 14
- 9
src/app/pages/tenant-list/tenant-list.component.html 查看文件

@@ -1,14 +1,19 @@
1 1
 <div class="h-full flex flex-col p-4 min-w-0">
2
-  <div class="flex justify-between items-center mb-6">
3
-    <h1 class="text-2xl font-bold">租户管理</h1>
4
-    <button mat-raised-button color="primary" (click)="openAddDialog()">
5
-      <mat-icon>add</mat-icon>
6
-      新增租户
7
-    </button>
8
-  </div>
2
+   <app-sticky-header
3
+    [title]="'租户管理'"
4
+    [buttonText]="'新增租户'"
5
+    [buttonIcon]="'add'"
6
+    [buttonColor]="'primary'"
7
+    [autoDetect]="true"
8
+    [scrollContainer]="'.content-area'"
9
+     [scrollThreshold]="20"
10
+    [compactThreshold]="80"
11
+    [widthTarget]="'#tenantMatCard'"
12
+    (buttonClick)="openAddDialog()">
13
+  </app-sticky-header>
9 14
 
10
-  <mat-card class="flex-1">
11
-    <mat-card-content>
15
+   <mat-card id="tenantMatCard" class="h-[400px]">
16
+     <mat-card-content class="overflow-auto">
12 17
       <table mat-table [dataSource]="tenants" class="w-full">
13 18
         <!-- ID列 -->
14 19
         <ng-container matColumnDef="id">

+ 3
- 1
src/app/pages/tenant-list/tenant-list.component.ts 查看文件

@@ -11,6 +11,7 @@ import { MatIcon } from '@angular/material/icon';
11 11
 import { TenantService } from '../../services/tenant.service';
12 12
 import { Tenant, TenantRequest } from '../../models/tenant.model';
13 13
 import { EnterNavigationDirective } from 'base-core';
14
+import { StickyHeaderComponent } from '../../components/sticky-header/sticky-header.component';
14 15
 
15 16
 @Component({
16 17
   selector: 'app-tenant-list',
@@ -25,7 +26,8 @@ import { EnterNavigationDirective } from 'base-core';
25 26
     MatInputModule,
26 27
     MatProgressSpinner,
27 28
     MatIcon,
28
-    EnterNavigationDirective
29
+    EnterNavigationDirective,
30
+    StickyHeaderComponent
29 31
   ],
30 32
   templateUrl: './tenant-list.component.html',
31 33
   styleUrl: './tenant-list.component.scss'

正在加载...
取消
保存