From 8d2b78a322abaeeeaf5f16326a2222fa347d71f0 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 23 Aug 2026 17:07:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DAJAX=20=E5=BC=95=E6=93=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ss_b00c85f4-499c-4183-a5b5-4baf7a69aa0d.md | 48 +++++++++++++++++++ assets/js/core/pagination.js | 11 +++++ .../template-tags/pagination-permissions.php | 2 +- search.php | 10 ++-- .../post-visual-contract.test.cjs | 4 +- 5 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 .zcode/plans/plan-sess_b00c85f4-499c-4183-a5b5-4baf7a69aa0d.md diff --git a/.zcode/plans/plan-sess_b00c85f4-499c-4183-a5b5-4baf7a69aa0d.md b/.zcode/plans/plan-sess_b00c85f4-499c-4183-a5b5-4baf7a69aa0d.md new file mode 100644 index 00000000..e0bc6f26 --- /dev/null +++ b/.zcode/plans/plan-sess_b00c85f4-499c-4183-a5b5-4baf7a69aa0d.md @@ -0,0 +1,48 @@ +# 修复前台文章卡片无刷新加载出现空位的问题 + +## 根因 + +规范分页控件(`aksrt_render_pagination()` 输出的 `.ajax-pag[data-aksrt-pagination]`,内含 `.next-page.ajax-next`)携带了遗留引擎的触发类 `ajax-next`。全站加载的 `main.min.js` 在 body 上委托监听 `.ajax-next` 点击(另有 IAS 无限滚动路径用 jQuery 模拟点击 `.ajax-next/.ias-btn`),导致每次"加载更多"被两套引擎同时处理: + +1. **规范引擎**(pagination.js):POST `aksrt_load_more_posts`,把卡片正确插入 `.ajax-pag` 之前; +2. **遗留引擎**(main.min.js `post_ajax()`):抓取整页 `?paged=2` HTML,**先删除 `.ajax-pag`**,把占位加载器、重复的一批卡片和一个新的**块级** `.ajax-pag` div 插到最后一张卡之后。 + +卡片网格是 inline-block 布局(`width: calc(33.333% - 16px)`),块级分页 div / 重复批次插入流中会强制换行 → 网格中出现空位(即"隔出一个卡片位置"),且第 2 页内容被加载两次。评论分页(`inc/helpers/template-tags/search-meta.php:533`)已用 `order-ajax-next` + `ias-btn` 避开遗留引擎,文章分页是唯一例外。 + +另经确认:生产环境实际生效的懒加载运行时是 main.min.js 内置版(`assets/js/lazysizes.js` 无任何 enqueue),其 IAS 自动加载走 jQuery 模拟点击,只到得了遗留引擎——所以无限滚动模式目前完全由遗留引擎执行。若只摘掉 `ajax-next` 类而不接管 IAS 触发,无限滚动会失效,因此两处改动必须配套。 + +## 修改内容(4 个文件) + +### 1. `inc/helpers/template-tags/pagination-permissions.php:123` +`.next-page` 包装类的 `ajax-next` → `order-ajax-next ias-btn`: +- 移除遗留引擎的点击触发 token(手动点击与 IAS 模拟点击双双失效于遗留引擎); +- `order-ajax-next` 复用现有按钮样式 `.theme-pagination .order-ajax-next a`(main.css:9659+),**零 CSS 改动**; +- `ias-btn` 保持 main.min.js 的 IAS 匹配器仍能找到元素(其后续 jQuery 点击成为无害空操作),且 pagination.js 的 clickTarget 选择器 `.next-page.ajax-next, .ias-btn` 原生支持,**零 JS 选择器改动**; +- 与评论分页的既有模式完全一致。 + +### 2. `assets/js/core/pagination.js` +在点击绑定块后新增容器级 `lazybeforeunveil` 监听(仅当服务端输出了 `lazyload` 契约类时挂载):unveil 事件由 main.min.js 内置 lazySizes 原生派发(可冒泡),监听到后设置 `lazyAuto` 并原生 `click()`,把无限滚动自动加载**收归规范引擎**的既有点击管线(复用 loading 守卫、iasMax 上限、按钮状态机): +```js +if (container.classList.contains("lazyload") || clickTarget.classList.contains("lazyload")) { + container.addEventListener("lazybeforeunveil", function (e) { + if (e.target !== container && e.target !== clickTarget) return; + clickTarget.dataset.lazyAuto = "1"; + clickTarget.click(); + }); +} +``` +附带修复:评论分页的 IAS 自动加载此前同样到不了规范引擎(jQuery 模拟点击不可见),此监听一并接管。 + +### 3. `search.php:112-118` — 消除双层包裹 +去掉外层冗余 `
`,把 `mtn6` 直接传入 `aksrt_render_post_list(['wrapper_class' => 'mtn6'])`。这样规范引擎的 `previousElementSibling` 目标解析落在真正的卡片网格上,追加的卡片与首页批在同一 inline-block 网格内连续排布(此前新卡片会追加为内层网格块的兄弟节点,卡片模式下在分页边界处出现空位)。ajax-list.js(搜索 tab 切换的实际引擎)用 `querySelectorAll('.ajax-item')` 提取、其替换流程本身就会把卡片拍平到容器层,结构兼容。 + +### 4. `tests/widget-system/post-visual-contract.test.cjs:238` +- 断言 `/class="next-page ajax-next/` → `/class="next-page order-ajax-next ias-btn/`; +- 新增 `assert.doesNotMatch(php, /next-page ajax-next/)` 锁定规范控件不再携带遗留触发类; +- 新增 `assert.match(js, /lazybeforeunveil/)` 锁定 IAS 接管。 + +## 验证 + +- `php -l inc/helpers/template-tags/pagination-permissions.php` 和 `php -l search.php` +- `node --test tests/widget-system/post-visual-contract.test.cjs` +- 浏览器实测(桌面 3 列卡片网格):首页点"加载更多"只发一个 `admin-ajax` 请求、无重复批次、无网格空位、`.ajax-pag` 始终位于网格下方;无限滚动模式每次自动加载一页并在 iasMax 后转手动按钮;搜索页卡片模式追加后与首批网格连续。 \ No newline at end of file diff --git a/assets/js/core/pagination.js b/assets/js/core/pagination.js index e9aaa804..2aa9f9a2 100644 --- a/assets/js/core/pagination.js +++ b/assets/js/core/pagination.js @@ -256,6 +256,17 @@ }); } + // The runtime lazySizes dispatches lazybeforeunveil natively but follows up + // with a jQuery-simulated click, which native listeners never receive. + // Route the unveil trigger through the same click pipeline. + if (container.classList.contains("lazyload") || clickTarget.classList.contains("lazyload")) { + container.addEventListener("lazybeforeunveil", function (e) { + if (e.target !== container && e.target !== clickTarget) return; + clickTarget.dataset.lazyAuto = "1"; + clickTarget.click(); + }); + } + if (mode === "infinite") { if ((isComment && window.aksrtData && aksrtData.commentIasAuto === false) || !hasAjaxConfig()) { var autoTrigger = clickTarget.closest(".next-page") || clickTarget; diff --git a/inc/helpers/template-tags/pagination-permissions.php b/inc/helpers/template-tags/pagination-permissions.php index 69919e3f..8c4dc81b 100644 --- a/inc/helpers/template-tags/pagination-permissions.php +++ b/inc/helpers/template-tags/pagination-permissions.php @@ -120,7 +120,7 @@ if (!function_exists('aksrt_render_pagination')) { data-query-context="" data-query-vars="" data-render-config=""> -
> +
>
diff --git a/search.php b/search.php index d9e6ba9e..997c5e78 100644 --- a/search.php +++ b/search.php @@ -110,12 +110,10 @@ if (is_active_sidebar('search_top_fluid')) { } ?> have_posts()) : ?> -
- 'search', 'highlight_query' => $search_query]) : ['context' => 'search', 'mode' => 'list']; - aksrt_render_post_list(array_merge($config, ['wrapper_class' => '']), $wp_query); - ?> -
+ 'search', 'highlight_query' => $search_query]) : ['context' => 'search', 'mode' => 'list']; + aksrt_render_post_list(array_merge($config, ['wrapper_class' => 'mtn6']), $wp_query); + ?> 'search', 'render_config' => $config]); ?>
diff --git a/tests/widget-system/post-visual-contract.test.cjs b/tests/widget-system/post-visual-contract.test.cjs index b4637675..e10a7953 100644 --- a/tests/widget-system/post-visual-contract.test.cjs +++ b/tests/widget-system/post-visual-contract.test.cjs @@ -235,8 +235,10 @@ test('AJAX pagination can recover, caps auto loading and rehydrates appended med const media = read('assets/js/core/misc.js'); assert.match(php, /class="text-center theme-pagination ajax-pag"/); - assert.match(php, /class="next-page ajax-next/); + assert.match(php, /class="next-page order-ajax-next ias-btn/); + assert.doesNotMatch(php, /next-page ajax-next/); assert.match(php, /lazyload-action="ias"/); + assert.match(js, /lazybeforeunveil/); assert.match(php, /class="post_ajax_loader"/); assert.match(php, /data-page-var/); assert.match(admin, /'author', 'author_name'/);