From 0e8c9d12c7e85e7d00ecbeb3ffc24e176ce2d273 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 21 Jul 2026 14:36:00 +0800 Subject: [PATCH 1/4] feat(widgets): plate moderator apply modal + JS submit Replace bare apply link with modal form posting to forum moderator REST; empty/admin states and modal CSS. --- assets/css/frontend/widget-batch5.css | 35 ++++++ assets/js/widget-set.js | 85 ++++++++++++++ .../Widgets/WidgetForumPlateModerator.php | 111 ++++++++++++++---- 3 files changed, 207 insertions(+), 24 deletions(-) diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css index b5a663a4..eb500440 100644 --- a/assets/css/frontend/widget-batch5.css +++ b/assets/css/frontend/widget-batch5.css @@ -2565,3 +2565,38 @@ a.main-color > .icon-cover-card:hover, z-index: 2; } .avatar-img.link-img img.aksrt-lazy { object-fit: cover; } + + +/* batch49-51 residual */ +.aksrt-mod-apply-modal[hidden] { display: none !important; } +.aksrt-mod-apply-modal { + position: fixed; inset: 0; z-index: 10050; + display: flex; align-items: center; justify-content: center; +} +.aksrt-mod-apply-modal__backdrop { + position: absolute; inset: 0; background: rgba(15, 23, 42, .45); +} +.aksrt-mod-apply-modal__dialog { + position: relative; z-index: 1; width: min(420px, calc(100vw - 32px)); + max-height: min(80vh, 560px); overflow: auto; + padding: 16px; border-radius: 12px; background: var(--surface-1, #fff); + box-shadow: 0 18px 50px rgba(0,0,0,.2); +} +.aksrt-mod-apply-modal__dialog textarea { + width: 100%; min-height: 96px; border: 1px solid var(--color-line, #e5e7eb); + border-radius: 8px; padding: 8px 10px; resize: vertical; +} +.aksrt-mod-apply-modal__dialog .js-aksrt-action-feedback.is-error { color: #ef4444; } +.aksrt-mod-apply-modal__dialog .js-aksrt-action-feedback.is-success { color: #10b981; } +.aksrt-mod-apply-modal__dialog .js-aksrt-action-feedback.is-loading { color: #64748b; } +body.aksrt-mod-apply-open { overflow: hidden; } +.aksrt-product-widget__item--list { gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--color-line, rgba(0,0,0,.06)); } +.aksrt-product-widget__item--list:last-child { border-bottom: 0; } +.aksrt-product-widget__thumb.list { + flex: 0 0 80px; width: 80px; height: 80px; border-radius: 10px; overflow: hidden; + background: var(--surface-2, #f3f4f6); display: block; +} +.aksrt-product-widget__thumb.list img { width: 100%; height: 100%; object-fit: cover; display: block; } +.text-ellipsis-2 { + display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; +} diff --git a/assets/js/widget-set.js b/assets/js/widget-set.js index 4bbdad30..aea86eeb 100644 --- a/assets/js/widget-set.js +++ b/assets/js/widget-set.js @@ -42,6 +42,9 @@ tab_count: tabCount, style: style, paginate: paginate, + topics: tab.getAttribute('data-topics') || '', + limit_day: tab.getAttribute('data-limit-day') || '0', + pay_type: tab.getAttribute('data-pay-type') || '', }); var url = rest + (rest.indexOf('?') > -1 ? '&' : '?') + qs.toString(); panel.classList.add('is-ajax-loading'); @@ -379,6 +382,87 @@ }); } + + function initModeratorApplyModal() { + document.addEventListener('click', function (e) { + var openBtn = e.target.closest('[data-aksrt-mod-apply-open]'); + if (openBtn) { + e.preventDefault(); + var id = openBtn.getAttribute('data-aksrt-mod-apply-open'); + var modal = id ? document.getElementById(id) : null; + if (modal) { + modal.hidden = false; + document.body.classList.add('aksrt-mod-apply-open'); + } + return; + } + var closeBtn = e.target.closest('[data-aksrt-mod-apply-close]'); + if (closeBtn) { + var box = closeBtn.closest('.aksrt-mod-apply-modal'); + if (box) { + box.hidden = true; + document.body.classList.remove('aksrt-mod-apply-open'); + } + } + }); + + document.querySelectorAll('.js-aksrt-moderator-apply-form').forEach(function (form) { + if (form.dataset.aksrtModApplyBound === '1') return; + form.dataset.aksrtModApplyBound = '1'; + form.addEventListener('submit', function (event) { + event.preventDefault(); + var boardId = parseInt(form.getAttribute('data-board-id') || '0', 10); + var reasonField = form.querySelector('[name="reason"]'); + var reason = reasonField ? reasonField.value.trim() : ''; + var feedback = form.querySelector('.js-aksrt-action-feedback'); + var submitButton = form.querySelector('button[type="submit"]'); + if (!boardId || !reason) { + if (feedback) { + feedback.textContent = '请填写完整的申请理由。'; + feedback.className = 'js-aksrt-action-feedback mb10 is-error'; + } + return; + } + if (submitButton) submitButton.disabled = true; + if (feedback) { + feedback.textContent = '正在提交申请…'; + feedback.className = 'js-aksrt-action-feedback mb10 is-loading'; + } + var siteUrl = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/+$/, '') : ''; + var url = siteUrl + '/wp-json/aksrt/v1/forum/moderator/apply'; + fetch(url, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'X-WP-Nonce': (window.aksrtData && aksrtData.restNonce) || '', + }, + body: JSON.stringify({ board_id: boardId, reason: reason }), + }) + .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); }) + .then(function (res) { + if (!res.ok || (res.j && res.j.success === false)) { + throw new Error((res.j && res.j.message) || '申请提交失败'); + } + var msg = (res.j && (res.j.message || (res.j.data && res.j.data.message))) || '申请已提交'; + if (feedback) { + feedback.textContent = msg; + feedback.className = 'js-aksrt-action-feedback mb10 is-success'; + } + if (reasonField) reasonField.disabled = true; + }) + .catch(function (err) { + if (feedback) { + feedback.textContent = (err && err.message) || '申请提交失败。'; + feedback.className = 'js-aksrt-action-feedback mb10 is-error'; + } + if (submitButton) submitButton.disabled = false; + }); + }); + }); + } + function initFloatingQRCodes() { var wrap = document.querySelector('.float-right'); if (!wrap) return; @@ -424,6 +508,7 @@ initProductTabs(); initMainPostsLazy(); initFeatureIntro(); + initModeratorApplyModal(); initFloatingQRCodes(); } diff --git a/src/AKSRT/Widgets/WidgetForumPlateModerator.php b/src/AKSRT/Widgets/WidgetForumPlateModerator.php index ff9bfb27..444fe7c9 100644 --- a/src/AKSRT/Widgets/WidgetForumPlateModerator.php +++ b/src/AKSRT/Widgets/WidgetForumPlateModerator.php @@ -22,6 +22,70 @@ class WidgetForumPlateModerator extends \WP_Widget { ); } + /** + * Build apply entry HTML (modal form). + */ + public static function render_apply_html(int $plate_id, bool $show_apply = true): string { + if (!$show_apply || $plate_id <= 0) { + return ''; + } + $forum = class_exists('\AKSRT\Forum\Forum') ? \AKSRT\Forum\Forum::get_instance() : null; + $cuid = get_current_user_id(); + $show_always = true; + if (function_exists('aksrt_get_setting')) { + $show_always = (bool) aksrt_get_setting('bbs_show_apply_moderator', true); + } + $can_apply = $cuid > 0 && $forum && method_exists($forum, 'forum_user_can') + ? $forum->forum_user_can($cuid, 'moderator_apply') + : ($cuid > 0); + if (!$show_always && !$can_apply) { + return ''; + } + $is_mod = false; + if ($cuid > 0 && $forum && method_exists($forum, 'is_board_moderator')) { + $is_mod = $forum->is_board_moderator($cuid, $plate_id); + } + if ($is_mod || ($cuid > 0 && user_can($cuid, 'manage_options'))) { + return ''; + } + if ($cuid <= 0) { + $login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url(get_permalink($plate_id)) : wp_login_url(get_permalink($plate_id)); + return '' . esc_html__('登录后申请版主', 'aksrt') . ''; + } + $pending = false; + if ($forum && method_exists($forum, 'get_board_moderator_applications')) { + foreach ((array) $forum->get_board_moderator_applications($plate_id) as $app) { + if ((int) ($app['user_id'] ?? 0) === $cuid && (string) ($app['status'] ?? '') === 'pending') { + $pending = true; + break; + } + } + } + if ($pending) { + return '' . esc_html__('申请审核中', 'aksrt') . ''; + } + $uid = 'aksrt-mod-apply-' . $plate_id . '-' . wp_rand(1000, 9999); + $desc = function_exists('aksrt_get_setting') ? (string) aksrt_get_setting('bbs_apply_moderator_desc', '') : ''; + if ($desc === '') { + $desc = __('欢迎有热情的用户申请成为版主,协助管理社区秩序。', 'aksrt'); + } + obs_start(); + echo ''; + echo ''; + return (string) ob_get_clean(); + } + /** * @return array{html:string} */ @@ -62,10 +126,6 @@ class WidgetForumPlateModerator extends \WP_Widget { } $people[] = ['id' => $mid, 'role' => 'moderator']; } - if (!$people) { - return ['html' => '']; - } - $title_text = (string) ($instance['title_text'] ?? __('版主', 'aksrt')); $show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']); $style = sanitize_key((string) ($instance['style'] ?? 'list')); @@ -73,31 +133,30 @@ class WidgetForumPlateModerator extends \WP_Widget { $style = 'list'; } - $show_apply = !isset($instance['apply_btn']) || !empty($instance['apply_btn']); - $apply_html = ''; - if ($show_apply && is_user_logged_in()) { - $forum = class_exists('\AKSRT\Forum\Forum') ? \AKSRT\Forum\Forum::get_instance() : null; - $cuid = get_current_user_id(); - $can_apply = $forum && method_exists($forum, 'forum_user_can') - ? $forum->forum_user_can($cuid, 'moderator_apply') - : true; - $is_mod = $forum && method_exists($forum, 'is_board_moderator') - ? $forum->is_board_moderator($cuid, $id) - : in_array($cuid, array_merge([$author_id], $mods, $super), true); - if ($can_apply && !$is_mod) { - $apply_url = add_query_arg(['aksrt_apply_moderator' => 1, 'plate' => $id], get_permalink($id)); - $apply_html = '' . esc_html__('申请版主', 'aksrt') . ''; + $show_apply = !isset($instance['apply_btn']) || !empty($instance['apply_btn']); + $apply_html = self::render_apply_html($id, $show_apply); + + if (!$people && $apply_html === '') { + if (current_user_can('manage_options')) { + return [ + 'html' => function_exists('aksrt_get_widget_null') + ? aksrt_get_widget_null(__('当前版块暂无版主', 'aksrt')) + : '

' . esc_html__('当前版块暂无版主', 'aksrt') . '

', + ]; } + return ['html' => '']; } - ob_start(); - echo '
'; + + obs_start(); + echo '
'; if ($title_text !== '' || $apply_html !== '') { echo '
'; echo $title_text !== '' ? '
' . esc_html($title_text) . '
' : '
'; echo $apply_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '
'; } - echo '
'; + if ($people) { + echo '
'; foreach ($people as $row) { $uid = (int) $row['id']; if (function_exists('aksrt_render_moderator_row')) { @@ -121,14 +180,18 @@ class WidgetForumPlateModerator extends \WP_Widget { } echo ''; if ($show_avatar) { - echo ''; + echo ''; } echo '' . esc_html($user->display_name) . ''; echo '' . esc_html($role_label) . ''; echo ''; } - echo '
'; - return ['html' => (string) ob_get_clean()]; + echo '
'; + } else { + echo '
' . esc_html__('暂无版主,欢迎申请加入管理。', 'aksrt') . '
'; + } + echo '
'; + return ['html' => (string) ob_get_clean()]; } public function widget($args, $instance): void { From 9f485cbe451a51a7f0a54fcaf4fadba0a768f384 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 21 Jul 2026 14:36:01 +0800 Subject: [PATCH 2/4] feat(widgets): per-tab topics/limit_day/pay_type filters TabPosts each tab can filter topics taxonomy, date window and pay type; REST and widget-set.js pass the extra query args. --- src/AKSRT/Core/WidgetAjax.php | 5 ++++- src/AKSRT/Widgets/TabPostsWidget.php | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/AKSRT/Core/WidgetAjax.php b/src/AKSRT/Core/WidgetAjax.php index 1a3cdbf8..aa293aee 100644 --- a/src/AKSRT/Core/WidgetAjax.php +++ b/src/AKSRT/Core/WidgetAjax.php @@ -93,7 +93,10 @@ class WidgetAjax { 'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')), 'count' => max(0, min(20, (int) ($request->get_param('tab_count') ?? 0))), 'enabled' => true, - ]; + + 'topics' => sanitize_text_field((string) ($request->get_param('topics') ?? '')), + 'limit_day' => max(0, (int) ($request->get_param('limit_day') ?? 0)), + 'pay_type' => sanitize_key((string) ($request->get_param('pay_type') ?? '')),]; $opts = [ 'style' => $style, 'paginate' => $paginate, diff --git a/src/AKSRT/Widgets/TabPostsWidget.php b/src/AKSRT/Widgets/TabPostsWidget.php index 1e9dcdb9..fc42a830 100644 --- a/src/AKSRT/Widgets/TabPostsWidget.php +++ b/src/AKSRT/Widgets/TabPostsWidget.php @@ -107,6 +107,9 @@ class TabPostsWidget extends \WP_Widget { 'count' => max(1, min(30, $count)), 'orderby' => sanitize_key((string) ($tab['orderby'] ?? 'date')), 'cat' => absint($tab['category_id'] ?? 0), + 'topics' => sanitize_text_field((string) ($tab['topics'] ?? '')), + 'limit_day' => max(0, (int) ($tab['limit_day'] ?? 0)), + 'pay_type' => sanitize_key((string) ($tab['pay_type'] ?? '')), 'style' => $style, 'show_thumb' => !isset($opts['show_thumb']) ? 1 : (int) $opts['show_thumb'], 'show_meta' => !isset($opts['show_meta']) ? 1 : (int) $opts['show_meta'], @@ -213,6 +216,9 @@ class TabPostsWidget extends \WP_Widget { data-orderby="" data-cat="" data-tab-count="" + data-topics="" + data-limit-day="" + data-pay-type="" aria-selected="" > @@ -295,7 +301,7 @@ class TabPostsWidget extends \WP_Widget { $max_tabs = 8; $tabs = array_slice($tabs, 0, $max_tabs); while (count($tabs) < 3) { - $tabs[] = ['title' => '', 'category_id' => 0, 'orderby' => 'date', 'count' => 0, 'enabled' => false]; + $tabs[] = ['title' => '', 'category_id' => 0, 'orderby' => 'date', 'count' => 0, 'topics' => '', 'limit_day' => 0, 'pay_type' => '', 'enabled' => false]; } foreach ($tabs as $i => $tab) : ?> @@ -323,6 +329,23 @@ class TabPostsWidget extends \WP_Widget { " value="">

+

+ + " value=""> +

+

+ + " value=""> +

+

+ + +

Date: Tue, 21 Jul 2026 14:36:01 +0800 Subject: [PATCH 3/4] feat(widgets): product list vs mini DOM + admin empty polish Differentiate product list/mini row sizes; custom HTML and posts-navs admin empty states via aksrt_get_widget_null. --- src/AKSRT/Widgets/CustomHtmlGapWidget.php | 4 +++- src/AKSRT/Widgets/WidgetPostsNavs.php | 7 +++++++ src/AKSRT/Widgets/WidgetProductList.php | 17 ++++++++++------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/AKSRT/Widgets/CustomHtmlGapWidget.php b/src/AKSRT/Widgets/CustomHtmlGapWidget.php index 154493d9..55ab4486 100644 --- a/src/AKSRT/Widgets/CustomHtmlGapWidget.php +++ b/src/AKSRT/Widgets/CustomHtmlGapWidget.php @@ -38,7 +38,9 @@ class CustomHtmlGapWidget extends \WP_Widget { if (trim(wp_strip_all_tags($content)) === '' && trim($content) === '') { if (current_user_can('manage_options')) { echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - echo '
' . esc_html__('自定义 HTML:', 'aksrt') . '' . esc_html__('请填写内容或切换为间距模式', 'aksrt') . '
'; + echo function_exists('aksrt_get_widget_null') + ? aksrt_get_widget_null(__('请填写 HTML 内容或切换为间距模式', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + : '
' . esc_html__('自定义 HTML:', 'aksrt') . '' . esc_html__('请填写内容或切换为间距模式', 'aksrt') . '
'; echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } return; diff --git a/src/AKSRT/Widgets/WidgetPostsNavs.php b/src/AKSRT/Widgets/WidgetPostsNavs.php index 3567c6e5..0c0dfc4c 100644 --- a/src/AKSRT/Widgets/WidgetPostsNavs.php +++ b/src/AKSRT/Widgets/WidgetPostsNavs.php @@ -27,6 +27,13 @@ class WidgetPostsNavs extends \WP_Widget { public function widget($args, $instance): void { if (!is_singular(['post', 'page', 'aksrt_forum_topic', 'aksrt_product'])) { + if (current_user_can('manage_options')) { + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo function_exists('aksrt_get_widget_null') + ? aksrt_get_widget_null(__('目录树仅在文章/帖子/商品详情页显示', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + : '

' . esc_html__('目录树仅在详情页显示', 'aksrt') . '

'; + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } return; } diff --git a/src/AKSRT/Widgets/WidgetProductList.php b/src/AKSRT/Widgets/WidgetProductList.php index b455b40e..3fbe06a4 100644 --- a/src/AKSRT/Widgets/WidgetProductList.php +++ b/src/AKSRT/Widgets/WidgetProductList.php @@ -94,7 +94,8 @@ class WidgetProductList extends \WP_Widget { return ''; } $oneline = $style === 'oneline'; - $mini = $style === 'mini' || $style === 'list'; + $mini = $style === 'mini'; + $list = $style === 'list'; ob_start(); while ($products->have_posts()) { $products->the_post(); @@ -108,16 +109,18 @@ class WidgetProductList extends \WP_Widget { if ($oneline) { echo '
'; } - if ($mini) { - // Compact row: thumb + title + price (zibll mini shop row lite). - echo '
'; - echo ''; + if ($mini || $list) { + $row_cls = $list ? 'aksrt-product-widget__item aksrt-product-widget__item--list ajax-item flex ac' : 'aksrt-product-widget__item aksrt-product-widget__item--mini ajax-item flex ac'; + $thumb_cls = $list ? 'aksrt-product-widget__thumb list' : 'aksrt-product-widget__thumb mini'; + $wh = $list ? 80 : 56; + echo '
'; + echo ''; if ($image !== '') { - echo '' . esc_attr(get_the_title($pid)) . ''; + echo '' . esc_attr(get_the_title($pid)) . ''; } echo ''; echo '
'; - echo '' . esc_html(get_the_title($pid)) . ''; + echo '' . esc_html(get_the_title($pid)) . ''; echo '
'; echo '¥' . esc_html(number_format_i18n($price, 2)) . ''; if (!empty($card['sales'])) { From 6a7ace0f89f15641348e821a08047db7453f1ca0 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 21 Jul 2026 14:49:37 +0800 Subject: [PATCH 4/4] fix(widgets): persist tab topics/limit_day/pay_type on save update() now stores per-tab filter fields added to the form/normalize path. --- src/AKSRT/Widgets/TabPostsWidget.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/AKSRT/Widgets/TabPostsWidget.php b/src/AKSRT/Widgets/TabPostsWidget.php index fc42a830..ee3c542e 100644 --- a/src/AKSRT/Widgets/TabPostsWidget.php +++ b/src/AKSRT/Widgets/TabPostsWidget.php @@ -381,6 +381,9 @@ class TabPostsWidget extends \WP_Widget { 'category_id' => absint($tab['category_id'] ?? 0), 'orderby' => $orderby, 'count' => max(0, min(20, (int) ($tab['count'] ?? 0))), + 'topics' => sanitize_text_field((string) ($tab['topics'] ?? '')), + 'limit_day' => max(0, (int) ($tab['limit_day'] ?? 0)), + 'pay_type' => sanitize_key((string) ($tab['pay_type'] ?? '')), 'enabled' => !empty($tab['enabled']), ]; }