Explorar el Código

缩放改好-修改样式1

qdy hace 1 mes
padre
commit
8e8415414d

+ 1
- 1
projects/base-core/src/lib/components/sticky-header/sticky-header.component.html Ver fichero

@@ -1,4 +1,4 @@
1
-<div class="sticky-header sticky top-0 z-10 transition-all duration-300 ease-in-out" [class.locked]="isLocked" [class.compact]="isCompact" [ngStyle]="isLocked ? {
1
+<div class="sticky-header sticky top-0 z-10 transition-all duration-300 ease-in-out" [style.min-height]="headerHeight" [class.locked]="isLocked" [class.compact]="isCompact" [ngStyle]="isLocked ? {
2 2
   width: headerWidth ? (typeof headerWidth === 'number' ? headerWidth + 'px' : headerWidth) : null,
3 3
   left: headerLeft ? (typeof headerLeft === 'number' ? headerLeft + 'px' : headerLeft) : null
4 4
 } : null">

+ 12
- 0
projects/base-core/src/lib/components/sticky-header/sticky-header.component.scss Ver fichero

@@ -11,6 +11,9 @@
11 11
   will-change: transform;
12 12
   /* 轻微的内阴影,增强立体感 */
13 13
   box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.8);
14
+  /* 确保内容区域填充高度 */
15
+  display: flex;
16
+  flex-direction: column;
14 17
 
15 18
   .header-content {
16 19
     background: #f8fafc; /* 简洁的灰白色背景 */
@@ -22,6 +25,9 @@
22 25
       0 1px 2px 0 rgba(0, 0, 0, 0.05); /* 轻微外阴影,保持与页面融合 */
23 26
     transition: all 0.3s ease-in-out;
24 27
     /* 移除 overflow: hidden,防止内容裁剪 */
28
+    height: 100%; /* 填充父容器高度 */
29
+    min-height: 100%; /* 确保填充最小高度 */
30
+    flex: 1; /* 填充可用空间 */
25 31
   }
26 32
 
27 33
   .header-title {
@@ -72,6 +78,9 @@
72 78
       border: 1px solid #cbd5e1;
73 79
       margin: 1rem; /* 添加外边距,避免贴边 */
74 80
       /* 保持原padding大小 */
81
+      height: 100%; /* 填充父容器高度 */
82
+      min-height: 100%; /* 确保填充最小高度 */
83
+      flex: 1; /* 填充可用空间 */
75 84
     }
76 85
 
77 86
     .header-title {
@@ -118,6 +127,9 @@
118 127
         0 1px 3px 0 rgba(0, 0, 0, 0.1); /* 轻微外阴影 */
119 128
       background: #f1f5f9; /* 稍微深一点的背景,强调状态变化 */
120 129
       border: 1px solid #cbd5e1; /* 保持一致的边框 */
130
+      height: 100%; /* 填充父容器高度 */
131
+      min-height: 100%; /* 确保填充最小高度 */
132
+      flex: 1; /* 填充可用空间 */
121 133
       
122 134
       > div:first-child {
123 135
         padding-top: 0.375rem;

+ 262
- 43
projects/base-core/src/lib/components/sticky-header/sticky-header.component.ts Ver fichero

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

Loading…
Cancelar
Guardar