ZHIWEI666 commited on
Commit
a47251f
·
verified ·
1 Parent(s): 9791d3d

Upload 104 files

Browse files
前端页面/components/性能优化工具.js CHANGED
@@ -240,6 +240,8 @@ export function createPaginationLoader(options) {
240
  let debounceTimer = null;
241
  let generation = 0; // 🔄 轻量级取消令牌,reset 时递增以丢弃飞行中请求
242
  let stopped = false; // 保持创建后可手动加载;stop 后仅 start 能重新启用。
 
 
243
 
244
  // 加载指示器
245
  const loadingIndicator = document.createElement("div");
@@ -258,6 +260,7 @@ export function createPaginationLoader(options) {
258
  // "没有更多"提示
259
  const endIndicator = document.createElement("div");
260
  endIndicator.className = "pagination-end";
 
261
  endIndicator.setAttribute("role", "status");
262
  endIndicator.setAttribute("aria-live", "polite");
263
  endIndicator.textContent = translate("pagination.end");
@@ -276,6 +279,63 @@ export function createPaginationLoader(options) {
276
  retryIndicator.append(retryMessage, retryButton);
277
  retryIndicator.style.display = "none";
278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  // 滚动事件处理(带防抖)
280
  const handleScroll = () => {
281
  if (stopped) return;
@@ -294,9 +354,14 @@ export function createPaginationLoader(options) {
294
  };
295
 
296
  // 加载下一页
297
- const _loadNextPage = async () => {
298
- if (stopped || isLoading || !hasMore) return;
299
  if (typeof canLoad === "function" && !canLoad()) return;
 
 
 
 
 
300
 
301
  isLoading = true;
302
  loadingIndicator.style.display = "block";
@@ -339,19 +404,17 @@ export function createPaginationLoader(options) {
339
  loadingIndicator.style.display = "none";
340
  // Search callers can scan pages with no local matches without
341
  // changing pagination behavior for other lists.
342
- if (hasMore && typeof autoFill === "function" &&
343
- (autoFill() || container.scrollHeight <= container.clientHeight + threshold)) {
344
- _loadNextPage();
345
- }
346
  }
347
  }
348
  };
349
 
350
  retryButton.addEventListener("click", () => {
351
  if (stopped || isLoading) return;
352
- retryIndicator.style.display = "none";
353
- hasMore = true;
354
- _loadNextPage();
355
  });
356
 
357
  // 启动监听
@@ -367,6 +430,7 @@ export function createPaginationLoader(options) {
367
 
368
  // 停止监听
369
  const stop = () => {
 
370
  stopped = true;
371
  generation++;
372
  if (isLoading) currentPage = Math.max(1, currentPage - 1);
@@ -382,6 +446,7 @@ export function createPaginationLoader(options) {
382
 
383
  // 重置状态
384
  const reset = () => {
 
385
  generation++; // 🔄 递增使飞行中请求结果被忽略
386
  currentPage = 1; // 首屏已渲染第1页,分页器从第2页开始
387
  hasMore = true;
 
240
  let debounceTimer = null;
241
  let generation = 0; // 🔄 轻量级取消令牌,reset 时递增以丢弃飞行中请求
242
  let stopped = false; // 保持创建后可手动加载;stop 后仅 start 能重新启用。
243
+ const focusContainer = options.focusContainer || container;
244
+ let retryFocus = null;
245
 
246
  // 加载指示器
247
  const loadingIndicator = document.createElement("div");
 
260
  // "没有更多"提示
261
  const endIndicator = document.createElement("div");
262
  endIndicator.className = "pagination-end";
263
+ endIndicator.tabIndex = -1;
264
  endIndicator.setAttribute("role", "status");
265
  endIndicator.setAttribute("aria-live", "polite");
266
  endIndicator.textContent = translate("pagination.end");
 
279
  retryIndicator.append(retryMessage, retryButton);
280
  retryIndicator.style.display = "none";
281
 
282
+ const clearRetryFocus = () => {
283
+ if (!retryFocus) return;
284
+ retryFocus.doc.removeEventListener('focusin', retryFocus.onFocus, true);
285
+ retryFocus.doc.removeEventListener('pointerdown', retryFocus.onPointer, true);
286
+ retryFocus = null;
287
+ };
288
+ const captureRetryFocus = () => {
289
+ clearRetryFocus();
290
+ const doc = container.ownerDocument;
291
+ if (doc.activeElement !== retryButton) return;
292
+ const record = { generation, children: new Set(focusContainer.children), doc };
293
+ record.onFocus = event => { if (event.target !== retryButton && event.target !== doc.body) clearRetryFocus(); };
294
+ record.onPointer = () => clearRetryFocus();
295
+ retryFocus = record;
296
+ doc.addEventListener('focusin', record.onFocus, true);
297
+ doc.addEventListener('pointerdown', record.onPointer, true);
298
+ };
299
+ const focusableSelector = 'button, a[href], input, select, textarea, [tabindex]';
300
+ const firstFocusable = element => {
301
+ const candidates = [element, ...element.querySelectorAll(focusableSelector)];
302
+ return candidates.find(node => node.matches(focusableSelector) && !node.disabled && node.tabIndex >= 0
303
+ && !node.closest('[inert], [aria-hidden="true"]') && node.getClientRects().length > 0
304
+ && node.ownerDocument.defaultView.getComputedStyle(node).visibility !== 'hidden');
305
+ };
306
+ const restoreRetryFocus = willContinue => {
307
+ if (!retryFocus) return;
308
+ const doc = container.ownerDocument;
309
+ if (stopped || retryFocus.generation !== generation || !container.isConnected || !focusContainer.isConnected
310
+ || !doc.hasFocus() || !focusContainer.getClientRects().length
311
+ || doc.defaultView.getComputedStyle(focusContainer).visibility === 'hidden'
312
+ || (doc.activeElement !== doc.body && doc.activeElement !== retryButton)) {
313
+ clearRetryFocus();
314
+ return;
315
+ }
316
+ let target;
317
+ if (retryIndicator.style.display !== 'none') target = retryButton;
318
+ else {
319
+ const children = Array.from(focusContainer.children);
320
+ for (const child of children) {
321
+ if (!retryFocus.children.has(child) && (target = firstFocusable(child))) break;
322
+ }
323
+ if (!target && willContinue) return;
324
+ if (!target && !hasMore) target = endIndicator;
325
+ if (!target) {
326
+ for (const child of children.reverse()) {
327
+ if ((target = firstFocusable(child))) break;
328
+ }
329
+ }
330
+ if (!target) {
331
+ if (!focusContainer.hasAttribute('tabindex')) focusContainer.tabIndex = -1;
332
+ target = focusContainer;
333
+ }
334
+ }
335
+ clearRetryFocus();
336
+ target.focus({ preventScroll: true });
337
+ };
338
+
339
  // 滚动事件处理(带防抖)
340
  const handleScroll = () => {
341
  if (stopped) return;
 
354
  };
355
 
356
  // 加载下一页
357
+ const _loadNextPage = async (retry = false) => {
358
+ if (stopped || isLoading || (!hasMore && !retry)) return;
359
  if (typeof canLoad === "function" && !canLoad()) return;
360
+ if (retry) {
361
+ captureRetryFocus();
362
+ retryIndicator.style.display = "none";
363
+ hasMore = true;
364
+ }
365
 
366
  isLoading = true;
367
  loadingIndicator.style.display = "block";
 
404
  loadingIndicator.style.display = "none";
405
  // Search callers can scan pages with no local matches without
406
  // changing pagination behavior for other lists.
407
+ const willContinue = hasMore && typeof autoFill === "function" &&
408
+ (autoFill() || container.scrollHeight <= container.clientHeight + threshold);
409
+ restoreRetryFocus(willContinue);
410
+ if (willContinue) _loadNextPage();
411
  }
412
  }
413
  };
414
 
415
  retryButton.addEventListener("click", () => {
416
  if (stopped || isLoading) return;
417
+ _loadNextPage(true);
 
 
418
  });
419
 
420
  // 启动监听
 
430
 
431
  // 停止监听
432
  const stop = () => {
433
+ clearRetryFocus();
434
  stopped = true;
435
  generation++;
436
  if (isLoading) currentPage = Math.max(1, currentPage - 1);
 
446
 
447
  // 重置状态
448
  const reset = () => {
449
+ clearRetryFocus();
450
  generation++; // 🔄 递增使飞行中请求结果被忽略
451
  currentPage = 1; // 首屏已渲染第1页,分页器从第2页开始
452
  hasMore = true;
前端页面/core/榜单分页控制.js CHANGED
@@ -75,6 +75,7 @@ export function setupSidebarPagination(contentArea, state, pageSize, loadMoreDat
75
  // 创建分页加载器
76
  const loader = createPaginationLoader({
77
  container: scrollContainer,
 
78
  pageSize: pageSize,
79
  threshold: 300,
80
  t,
 
75
  // 创建分页加载器
76
  const loader = createPaginationLoader({
77
  container: scrollContainer,
78
+ focusContainer: contentArea,
79
  pageSize: pageSize,
80
  threshold: 300,
81
  t,
前端页面/post/讨论区组件.js CHANGED
@@ -194,6 +194,7 @@ export function createPostsView(currentUser, keyword = "", { sort: initialSort =
194
 
195
  currentPaginator = createPaginationLoader({
196
  container: scrollContainer,
 
197
  canLoad: () => !disposed && firstPageReady && !isLoadingFromNetwork,
198
  loadMore: loadMorePosts,
199
  pageSize: PAGE_SIZE,
 
194
 
195
  currentPaginator = createPaginationLoader({
196
  container: scrollContainer,
197
+ focusContainer: postsGrid,
198
  canLoad: () => !disposed && firstPageReady && !isLoadingFromNetwork,
199
  loadMore: loadMorePosts,
200
  pageSize: PAGE_SIZE,
前端页面/prompt/提示词组件.js CHANGED
@@ -339,6 +339,7 @@ export function createPromptsView(currentUser, keyword = "", { sort: initialSort
339
 
340
  currentPaginator = createPaginationLoader({
341
  container: scrollContainer,
 
342
  canLoad: () => !disposed && firstPageReady && !isLoadingFromNetwork,
343
  loadMore: loadMorePrompts,
344
  pageSize: PAGE_SIZE,
 
339
 
340
  currentPaginator = createPaginationLoader({
341
  container: scrollContainer,
342
+ focusContainer: promptsGrid,
343
  canLoad: () => !disposed && firstPageReady && !isLoadingFromNetwork,
344
  loadMore: loadMorePrompts,
345
  pageSize: PAGE_SIZE,
前端页面/task/任务榜组件.js CHANGED
@@ -498,6 +498,7 @@ export function createTasksView(currentUser, keyword = "", { sort: initialSort =
498
  const scrollContainer = container.closest('.sidebar-scroll-container') || container;
499
  paginator = createPaginationLoader({
500
  container: scrollContainer,
 
501
  canLoad: () => !disposed && firstPageReady && !isLoadingFromNetwork,
502
  autoFill: () => Boolean(searchKeyword) && !allTasksData.some(task => _filterTasksByKeyword([task], searchKeyword).length),
503
  loadMore: async (page, pageSize, { isCurrent = () => true } = {}) => {
 
498
  const scrollContainer = container.closest('.sidebar-scroll-container') || container;
499
  paginator = createPaginationLoader({
500
  container: scrollContainer,
501
+ focusContainer: tasksList,
502
  canLoad: () => !disposed && firstPageReady && !isLoadingFromNetwork,
503
  autoFill: () => Boolean(searchKeyword) && !allTasksData.some(task => _filterTasksByKeyword([task], searchKeyword).length),
504
  loadMore: async (page, pageSize, { isCurrent = () => true } = {}) => {