From c8d199a69430d991f8d1e0142c8a0f435dcc0949 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 25 Aug 2026 18:22:30 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../schema/sections/users/notifications.js | 2 +- assets/css/frontend/messages.css | 170 +++++++++++++++++- assets/js/messages.js | 62 ++++++- inc/modules/message-center.php | 62 +++++-- src/Admin/Schema/SchemaUsers.php | 8 +- templates/pages/page-messages.php | 3 +- .../message-center-contract.test.cjs | 24 +++ .../message-center-pagination-harness.php | 74 ++++++++ 9 files changed, 381 insertions(+), 25 deletions(-) create mode 100644 tests/widget-system/message-center-pagination-harness.php diff --git a/.gitignore b/.gitignore index 646e6f81..0e113860 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ !/tests/widget-system/notification-preferences-contract.test.cjs !/tests/widget-system/message-center-contract.test.cjs !/tests/widget-system/message-center-url-harness.php +!/tests/widget-system/message-center-pagination-harness.php !/tests/widget-system/asset-ledger-contract.test.cjs !/tests/widget-system/financial-flow-contract.test.cjs !/tests/widget-system/pay-orders-filter-contract.test.cjs diff --git a/assets/admin/src/schema/sections/users/notifications.js b/assets/admin/src/schema/sections/users/notifications.js index 542afe2e..ca5c4d60 100644 --- a/assets/admin/src/schema/sections/users/notifications.js +++ b/assets/admin/src/schema/sections/users/notifications.js @@ -23,7 +23,7 @@ export const notificationsSchema = { { key: 'message_s', label: '站内通知总开关', type: 'switch', default: true }, { key: 'msg_center_rewrite_slug', label: '消息中心 URL 别名', type: 'input', default: 'message' }, { key: 'message_icon_show', label: '通知图标位置', type: 'checkbox', default: ['nav_menu'], options: [{ label: '导航菜单', value: 'nav_menu' }, { label: '用户头像', value: 'avatar' }, { label: '移动端底部', value: 'mobile_bar' }] }, - { key: 'message_paginate_type', label: '列表翻页模式', type: 'radio', default: 'ajax_lists', options: [{ label: 'AJAX 翻页', value: 'ajax_lists' }, { label: '传统翻页', value: 'default' }] }, + { key: 'message_paginate_type', label: '列表翻页模式', type: 'radio', default: 'ajax_lists', options: [{ label: 'AJAX 追加加载', value: 'ajax_lists' }, { label: '数字翻页按钮', value: 'default' }] }, { key: 'message_user_set', label: '允许用户设置推送', type: 'switch', default: true, help: '允许用户在前台自行设置通知推送偏好' }, ], }, diff --git a/assets/css/frontend/messages.css b/assets/css/frontend/messages.css index 2812865f..faa08644 100644 --- a/assets/css/frontend/messages.css +++ b/assets/css/frontend/messages.css @@ -350,10 +350,139 @@ html.dark .message-detail { /* ---- 分页 ---- */ .messages-pagination { - display: flex; - justify-content: center; - gap: 8px; - margin-top: 24px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + margin-top: 24px; +} + +.messages-pagination__button, +.messages-pagination__more { + border: 1px solid var(--color-line, rgba(50, 50, 50, 0.08)); + background: var(--surface-card, #fff); + color: var(--color-ink-3, #666); + font: inherit; + cursor: pointer; + transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease, transform 0.18s ease; +} + +.messages-pagination__button { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 38px; + height: 38px; + padding: 0 10px; + border-radius: 10px; + font-size: 13px; + font-weight: 600; +} + +.messages-pagination__button--edge { + color: var(--color-ink-4, #999); +} + +.messages-pagination__button:hover:not(:disabled), +.messages-pagination__button:focus-visible { + border-color: var(--color-primary, #3b82f6); + color: var(--color-primary, #3b82f6); + outline: none; + transform: translateY(-1px); +} + +.messages-pagination__button.is-current { + border-color: var(--color-primary, #3b82f6); + background: var(--color-primary, #3b82f6); + color: #fff; + box-shadow: 0 6px 16px color-mix(in srgb, var(--color-primary, #3b82f6) 24%, transparent); +} + +.messages-pagination__button:disabled { + cursor: default; + opacity: 0.42; +} + +.messages-pagination__button.is-current:disabled { + opacity: 1; +} + +.messages-pagination__ellipsis { + min-width: 18px; + color: var(--color-ink-4, #999); + font-size: 13px; + text-align: center; +} + +.messages-pagination--load-more { + width: min(100%, 440px); + margin-right: auto; + margin-left: auto; +} + +.messages-pagination__more { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + min-height: 48px; + padding: 9px 14px 9px 18px; + border-radius: 14px; + box-shadow: 0 3px 14px rgba(30, 40, 60, 0.04); + text-align: left; +} + +.messages-pagination__more span { + color: var(--color-primary, #3b82f6); + font-size: 14px; + font-weight: 600; +} + +.messages-pagination__more small { + padding: 4px 9px; + border-radius: 999px; + background: var(--color-primary-light, rgba(59, 130, 246, 0.08)); + color: var(--color-ink-4, #999); + font-size: 11px; + line-height: 1.4; +} + +.messages-pagination__more:hover, +.messages-pagination__more:focus-visible { + border-color: var(--color-primary, #3b82f6); + background: var(--color-primary-light, rgba(59, 130, 246, 0.04)); + outline: none; + transform: translateY(-1px); +} + +.messages-center.is-loading .messages-pagination__more span::before { + display: inline-block; + width: 12px; + height: 12px; + margin-right: 7px; + border: 2px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + content: ""; + vertical-align: -2px; + animation: messages-pagination-spin 0.7s linear infinite; +} + +@keyframes messages-pagination-spin { + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: reduce) { + .messages-pagination__button, + .messages-pagination__more { + transition: none; + } + + .messages-center.is-loading .messages-pagination__more span::before { + animation: none; + } } /* ---- 响应式 ---- */ @@ -420,6 +549,27 @@ html.dark .message-detail { .message-detail__actions .message-item__action-btn { text-align: center; } + + .messages-pagination { + gap: 5px; + margin-top: 18px; + } + + .messages-pagination__button { + min-width: 34px; + height: 34px; + padding: 0 8px; + border-radius: 9px; + } + + .messages-pagination__ellipsis { + min-width: 10px; + } + + .messages-pagination__more { + min-height: 46px; + border-radius: 12px; + } } /* ---- 暗色模式 ---- */ @@ -467,6 +617,18 @@ html.dark .message-item:hover { background: var(--surface-2); } +html.dark .messages-pagination__button, +html.dark .messages-pagination__more { + border-color: var(--color-line); + background: var(--surface-1); +} + +html.dark .messages-pagination__more:hover, +html.dark .messages-pagination__more:focus-visible { + border-color: var(--color-primary); + background: var(--surface-2); +} + html.dark .message-item__icon, html.dark .message-item__icon { background: var(--color-primary-soft, rgba(240, 68, 148, 0.16)); diff --git a/assets/js/messages.js b/assets/js/messages.js index 38dc7f1f..cb5d9889 100644 --- a/assets/js/messages.js +++ b/assets/js/messages.js @@ -13,7 +13,12 @@ var ajaxUrl = typeof aksrtData !== 'undefined' ? aksrtData.ajaxUrl : '/wp-admin/admin-ajax.php'; var content = root.querySelector('[data-message-content]'); var nav = root.querySelector('[data-message-nav]'); - var state = { view: root.dataset.view || 'unread', page: 1, detail: 0 }; + var state = { + view: root.dataset.view || 'unread', + page: 1, + detail: 0, + paginateType: root.dataset.paginateType || 'ajax_lists', + }; var pollInterval = 60000; function post(action, data) { @@ -51,6 +56,34 @@ content.querySelector('.messages-empty__text').textContent = message || '加载失败,请稍后重试'; } + function appendMessagePage(markup) { + var incoming = document.createElement('div'); + incoming.innerHTML = markup; + var currentList = content.querySelector('.messages-list'); + var incomingList = incoming.querySelector('.messages-list'); + if (!currentList || !incomingList) { + content.innerHTML = markup; + return null; + } + + var existingIds = new Set(); + currentList.querySelectorAll('[data-notification-id]').forEach(function (item) { + existingIds.add(item.dataset.notificationId); + }); + var firstAdded = null; + incomingList.querySelectorAll('.message-item').forEach(function (item) { + if (existingIds.has(item.dataset.notificationId)) return; + if (!firstAdded) firstAdded = item; + currentList.appendChild(item); + }); + + var currentPagination = content.querySelector('.messages-pagination'); + var incomingPagination = incoming.querySelector('.messages-pagination'); + if (currentPagination) currentPagination.remove(); + if (incomingPagination) content.appendChild(incomingPagination); + return firstAdded; + } + function load(next, options) { var requested = { view: next.view || state.view, @@ -69,10 +102,17 @@ if (!result.success || !result.data) { throw new Error(result.data && result.data.message ? result.data.message : '加载失败,请稍后重试'); } + requested.paginateType = result.data.paginate_type || state.paginateType; state = requested; root.dataset.view = state.view; + root.dataset.paginateType = state.paginateType; nav.innerHTML = result.data.nav; - content.innerHTML = result.data.content; + var firstAdded = null; + if (settings.append && state.paginateType === 'ajax_lists') { + firstAdded = appendMessagePage(result.data.content); + } else { + content.innerHTML = result.data.content; + } updateHeaderBadge(result.data.unread); if (settings.history === 'push') { @@ -80,7 +120,12 @@ } else if (settings.history === 'replace') { window.history.replaceState(state, '', routeFor(state.view)); } - content.focus({ preventScroll: true }); + if (firstAdded) { + firstAdded.setAttribute('tabindex', '-1'); + firstAdded.focus({ preventScroll: true }); + } else { + content.focus({ preventScroll: true }); + } }).catch(function (error) { renderError(error.message); }).finally(function () { @@ -116,9 +161,20 @@ document.addEventListener('click', function (event) { var button = event.target.closest('[data-message-page]'); if (!button || !root.contains(button)) return; + event.preventDefault(); load({ view: state.view, page: button.dataset.messagePage, detail: 0 }); }); + document.addEventListener('click', function (event) { + var button = event.target.closest('[data-message-next-page]'); + if (!button || !root.contains(button)) return; + event.preventDefault(); + load( + { view: state.view, page: button.dataset.messageNextPage, detail: 0 }, + { append: true } + ); + }); + document.addEventListener('click', function (event) { var button = event.target.closest('.js-msg-mark-read'); if (!button || !root.contains(button)) return; diff --git a/inc/modules/message-center.php b/inc/modules/message-center.php index 501e1f35..1cfabee7 100644 --- a/inc/modules/message-center.php +++ b/inc/modules/message-center.php @@ -22,6 +22,14 @@ function aksrt_normalize_message_view(string $view): string { return array_key_exists($view, aksrt_message_center_views()) ? $view : 'unread'; } +function aksrt_get_message_center_paginate_type(): string { + $settings = class_exists('\AKSRT\Settings\Settings') + ? \AKSRT\Settings\Settings::notification_settings() + : []; + $type = sanitize_key((string) ($settings['paginate_type'] ?? 'ajax_lists')); + return in_array($type, ['ajax_lists', 'default'], true) ? $type : 'ajax_lists'; +} + function aksrt_message_category_for_type(string $type): string { $type = sanitize_key($type); if (str_starts_with($type, 'asset_')) { @@ -204,7 +212,10 @@ function aksrt_render_message_center_list(int $user_id, string $view, int $page $per_page = 20; $total_pages = max(1, (int) ceil(count($items) / $per_page)); $page = max(1, min($total_pages, $page)); - $items = array_slice($items, ($page - 1) * $per_page, $per_page); + $paginate_type = aksrt_get_message_center_paginate_type(); + $items = $paginate_type === 'ajax_lists' + ? array_slice($items, 0, $page * $per_page) + : array_slice($items, ($page - 1) * $per_page, $per_page); if (!$items) { ?> @@ -217,11 +228,37 @@ function aksrt_render_message_center_list(int $user_id, string $view, int $page 1) : ?> - + + + + $number >= 1 && $number <= $total_pages))); + sort($numbers); + $previous_number = 0; + ?> + + $view, - 'page' => $page, - 'detail' => $notification_id, - 'nav' => $nav_html, - 'content' => $content_html, - 'unread' => $counts['unread'], + 'view' => $view, + 'page' => $page, + 'detail' => $notification_id, + 'paginate_type' => aksrt_get_message_center_paginate_type(), + 'nav' => $nav_html, + 'content' => $content_html, + 'unread' => $counts['unread'], ]); }); diff --git a/src/Admin/Schema/SchemaUsers.php b/src/Admin/Schema/SchemaUsers.php index 1a146435..f9dae3ec 100644 --- a/src/Admin/Schema/SchemaUsers.php +++ b/src/Admin/Schema/SchemaUsers.php @@ -235,10 +235,10 @@ class SchemaUsers { ['label' => '用户头像', 'value' => 'avatar'], ['label' => '移动端底部', 'value' => 'mobile_bar'], ]], - ['key' => 'message_paginate_type', 'label' => '列表翻页模式', 'type' => 'radio', 'default' => 'ajax_lists', 'options' => [ - ['label' => 'AJAX 翻页', 'value' => 'ajax_lists'], - ['label' => '传统翻页', 'value' => 'default'], - ]], + ['key' => 'message_paginate_type', 'label' => '列表翻页模式', 'type' => 'radio', 'default' => 'ajax_lists', 'options' => [ + ['label' => 'AJAX 追加加载', 'value' => 'ajax_lists'], + ['label' => '数字翻页按钮', 'value' => 'default'], + ]], ['key' => 'message_user_set', 'label' => '允许用户设置推送', 'type' => 'switch', 'default' => true, 'desc' => '允许用户在前台自行设置通知推送偏好'], ]], ['key' => 'types', 'title' => '通知类型', 'fields' => [ diff --git a/templates/pages/page-messages.php b/templates/pages/page-messages.php index dd0a4872..910c0e49 100644 --- a/templates/pages/page-messages.php +++ b/templates/pages/page-messages.php @@ -18,6 +18,7 @@ get_header(); $messages_enabled = !class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::message_enabled(); $user_id = get_current_user_id(); $current_view = aksrt_normalize_message_view((string) get_query_var('aksrt_message_view')); +$paginate_type = aksrt_get_message_center_paginate_type(); ?>
@@ -37,7 +38,7 @@ $current_view = aksrt_normalize_message_view((string) get_query_var('aksrt_messa -
+
diff --git a/tests/widget-system/message-center-contract.test.cjs b/tests/widget-system/message-center-contract.test.cjs index bdd795ca..873dc047 100644 --- a/tests/widget-system/message-center-contract.test.cjs +++ b/tests/widget-system/message-center-contract.test.cjs @@ -45,6 +45,30 @@ test('message center URL helper builds every view from the configured alias', () }); }); +test('message pagination setting switches between cumulative loading and numeric replacement', () => { + const result = spawnSync(process.env.PHP_BINARY || 'php', [path.join(__dirname, 'message-center-pagination-harness.php')], { + cwd: root, + encoding: 'utf8', + }); + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.deepEqual(JSON.parse(result.stdout), { + ajax_items: 40, + ajax_has_more: true, + ajax_has_numbers: false, + default_items: 20, + default_has_more: false, + default_numbers: true, + default_current_2: true, + }); + + const script = read('assets/js/messages.js'); + const styles = read('assets/css/frontend/messages.css'); + assert.match(script, /data-message-next-page|dataset\.messageNextPage/); + assert.match(script, /appendMessagePage\(result\.data\.content\)/); + assert.match(styles, /\.messages-pagination__more/); + assert.match(styles, /\.messages-pagination__button\.is-current/); +}); + test('tabs, pagination, actions, and detail all replace DOM fragments', () => { const center = read('inc/modules/message-center.php'); const script = read('assets/js/messages.js'); diff --git a/tests/widget-system/message-center-pagination-harness.php b/tests/widget-system/message-center-pagination-harness.php new file mode 100644 index 00000000..2d7a2d9b --- /dev/null +++ b/tests/widget-system/message-center-pagination-harness.php @@ -0,0 +1,74 @@ += 1; $number--) { + $items[] = [ + 'id' => (string) $number, + 'type' => 'comment_reply', + 'title' => '消息 ' . $number, + 'desc' => '内容', + 'url' => '', + 'read' => 0, + 'created_at' => '2026-08-25 12:00:00', + ]; + } + return $items; + } +} + +namespace AKSRT\Settings { + final class Settings { + public static string $paginateType = 'ajax_lists'; + + public static function notification_settings(): array { + return ['paginate_type' => self::$paginateType]; + } + } +} + +namespace { + require_once dirname(__DIR__, 2) . '/inc/modules/message-center.php'; + + function render_pagination_mode(string $type): string { + \AKSRT\Settings\Settings::$paginateType = $type; + ob_start(); + aksrt_render_message_center_list(1, 'unread', 2); + return (string) ob_get_clean(); + } + + $ajax = render_pagination_mode('ajax_lists'); + $numbers = render_pagination_mode('default'); + + echo json_encode([ + 'ajax_items' => substr_count($ajax, '
str_contains($ajax, 'messages-pagination--numbers'), + 'default_items' => substr_count($numbers, '