From 93fd6a93c29d86578f5dda9eca8fa424b0603a59 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 21 Jul 2026 08:04:03 +0800 Subject: [PATCH] feat(widgets): author/user card more-posts strip + ranking empty/TOP Add shared aksrt-user-card helpers (mini list + swiper-scroll card), wire AuthorCard/UserCard show_posts options, ranking empty state and top_badge. --- assets/css/frontend/widget-batch5.css | 120 ++++++++++++++++++++ inc/bootstrap.php | 1 + inc/helpers/aksrt-user-card.php | 140 ++++++++++++++++++++++++ src/AKSRT/Widgets/WidgetAuthorCard.php | 99 ++++++++++++++--- src/AKSRT/Widgets/WidgetUserCard.php | 120 +++++++++++++++++--- src/AKSRT/Widgets/WidgetUserRanking.php | 29 ++++- 6 files changed, 473 insertions(+), 36 deletions(-) create mode 100644 inc/helpers/aksrt-user-card.php diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css index 3fc342b3..c0b1dcb4 100644 --- a/assets/css/frontend/widget-batch5.css +++ b/assets/css/frontend/widget-batch5.css @@ -883,3 +883,123 @@ .aksrt-tag-cloud .c-red-2 { background: #fecaca; color: #991b1b; } .aksrt-home-widget__ajax .ajax-pag { margin-top: 12px; text-align: center; } + +/* ---- batch10: user-card more-posts + slider wrap + ranking top badge ---- */ +.user-card-posts .more-posts-mini .item { + padding: 6px 0; + border-bottom: 1px dashed var(--color-line, rgba(0, 0, 0, 0.06)); +} +.user-card-posts .more-posts-mini .item:last-child { border-bottom: 0; } +.user-card-posts .more-posts-mini .item a { + display: block; + color: inherit; + text-decoration: none; + font-size: 13px; +} +.user-card-posts .more-posts-mini .item a:hover { + color: var(--color-primary, #3b82f6); +} +.user-card-posts .more-posts.swiper-container { + margin: 8px 0 0; + padding: 0 2px 4px; + overflow: hidden; +} +.user-card-posts .more-posts .swiper-slide { + width: auto; + max-width: 46%; +} +.aksrt-more-post-card { + display: block; + color: inherit; + text-decoration: none; + border-radius: 8px; + overflow: hidden; + background: var(--surface-2, #f8fafc); +} +.aksrt-more-post-card__thumb { + display: block; + aspect-ratio: 16 / 10; + overflow: hidden; + background: var(--surface-3, #eef2f7); +} +.aksrt-more-post-card__thumb img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} +.aksrt-more-post-card__body { + display: block; + padding: 6px 8px 8px; +} +.aksrt-more-post-card__title { + display: block; + font-size: 12px; + font-weight: 600; + line-height: 1.35; +} +.aksrt-more-post-card__meta { + display: flex; + justify-content: space-between; + gap: 6px; + margin-top: 4px; + font-size: 11px; +} + +.aksrt-widget-slider-wrap { + border-radius: 10px; + overflow: hidden; +} +.aksrt-widget-slider-wrap .new-swiper { + --pc-height: 220px; + --m-height: 180px; + height: var(--pc-height); +} +@media (max-width: 768px) { + .aksrt-widget-slider-wrap .new-swiper { + height: var(--m-height); + } +} +.aksrt-widget-slider-wrap .swiper-slide img.fit-cover { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} +.aksrt-widget-slider-wrap .aksrt-slide-text { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 18px 14px 12px; + background: linear-gradient(transparent, rgba(0, 0, 0, 0.55)); + color: #fff; + pointer-events: none; +} +.aksrt-widget-slider-wrap .aksrt-slide-text__title { + font-size: 15px; + font-weight: 700; +} +.aksrt-widget-slider-wrap .aksrt-slide-text__desc { + font-size: 12px; + opacity: 0.9; + margin-top: 2px; +} + +.muted-box.c-red, +.c-red.muted-box { + color: #b91c1c; + background: #fef2f2; + border: 1px solid #fecaca; + border-radius: 8px; + padding: 12px 14px; + font-size: 13px; +} + +/* ranking empty state */ +.aksrt-user-ranking__empty { + margin: 8px 0 0; + font-size: 13px; + color: var(--muted-2-color, #999); + text-align: center; +} diff --git a/inc/bootstrap.php b/inc/bootstrap.php index cc8d656e..aa2e6c2d 100644 --- a/inc/bootstrap.php +++ b/inc/bootstrap.php @@ -48,6 +48,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-header-userbox.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-header-menu-buttons.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-runtime-win.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php'; +require_once AKSRT_DIR . '/inc/helpers/aksrt-user-card.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php'; require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-transactions.php'; diff --git a/inc/helpers/aksrt-user-card.php b/inc/helpers/aksrt-user-card.php new file mode 100644 index 00000000..81e3c9e0 --- /dev/null +++ b/inc/helpers/aksrt-user-card.php @@ -0,0 +1,140 @@ + $args + * @return list<\WP_Post> + */ + function aksrt_user_card_query_posts(array $args): array { + $user_id = (int) ($args['user_id'] ?? 0); + $limit = max(0, min(20, (int) ($args['limit'] ?? 6))); + if ($user_id <= 0 || $limit <= 0) { + return []; + } + + $orderby = sanitize_key((string) ($args['orderby'] ?? 'date')); + $post_type = sanitize_key((string) ($args['post_type'] ?? 'post')); + if (!post_type_exists($post_type)) { + $post_type = 'post'; + } + + $query_args = [ + 'post_status' => 'publish', + 'post_type' => $post_type, + 'author' => $user_id, + 'posts_per_page' => $limit, + 'ignore_sticky_posts' => true, + 'no_found_rows' => true, + ]; + + $exclude = (int) ($args['exclude'] ?? 0); + if ($exclude <= 0) { + $exclude = (int) get_the_ID(); + } + if ($exclude > 0) { + $query_args['post__not_in'] = [$exclude]; + } + + switch ($orderby) { + case 'views': + case 'view': + $query_args['meta_key'] = 'aksrt_post_views'; + $query_args['orderby'] = 'meta_value_num'; + $query_args['order'] = 'DESC'; + break; + case 'comment_count': + case 'comments': + $query_args['orderby'] = 'comment_count'; + $query_args['order'] = 'DESC'; + break; + case 'modified': + $query_args['orderby'] = 'modified'; + $query_args['order'] = 'DESC'; + break; + case 'rand': + case 'random': + $query_args['orderby'] = 'rand'; + break; + default: + $query_args['orderby'] = 'date'; + $query_args['order'] = 'DESC'; + } + + $q = new WP_Query($query_args); + $posts = $q->posts; + wp_reset_postdata(); + return is_array($posts) ? array_values(array_filter($posts, static fn($p) => $p instanceof WP_Post)) : []; + } +endif; + +if (!function_exists('aksrt_render_user_card_posts')) : + /** + * Render more-posts strip (card swiper-scroll or mini list). + * + * @param array $args + */ + function aksrt_render_user_card_posts(array $args): string { + $style = sanitize_key((string) ($args['post_style'] ?? $args['style'] ?? 'mini')); + if (!in_array($style, ['card', 'mini'], true)) { + $style = 'mini'; + } + $posts = aksrt_user_card_query_posts($args); + $show_empty = !empty($args['show_empty']); + + if (!$posts) { + if (!$show_empty) { + return ''; + } + return '
' . + esc_html__('暂无更多文章', 'aksrt') . + '
'; + } + + ob_start(); + echo '
'; + if ($style === 'card') { + echo ''; + } else { + echo ''; + } + echo '
'; + return (string) ob_get_clean(); + } +endif; diff --git a/src/AKSRT/Widgets/WidgetAuthorCard.php b/src/AKSRT/Widgets/WidgetAuthorCard.php index 6a523eda..9afe9744 100644 --- a/src/AKSRT/Widgets/WidgetAuthorCard.php +++ b/src/AKSRT/Widgets/WidgetAuthorCard.php @@ -1,6 +1,6 @@ esc_html__('在文章/帖子侧栏显示当前内容作者信息。', 'aksrt'), + 'description' => esc_html__('在文章/帖子侧栏显示当前内容作者信息与更多文章。', 'aksrt'), 'classname' => 'widget_aksrt_author_card', ] ); @@ -41,8 +41,20 @@ class WidgetAuthorCard extends \WP_Widget { return; } - $show_bg = !isset($instance['show_img_bg']) || !empty($instance['show_img_bg']); + $show_bg = !isset($instance['show_img_bg']) || !empty($instance['show_img_bg']); $show_stats = !isset($instance['show_stats']) || !empty($instance['show_stats']); + $show_posts = !isset($instance['show_posts']) || !empty($instance['show_posts']); + $post_style = sanitize_key((string) ($instance['post_style'] ?? 'mini')); + if (!in_array($post_style, ['card', 'mini'], true)) { + // Legacy show_img checkbox from zibll. + if (isset($instance['show_img'])) { + $post_style = !empty($instance['show_img']) ? 'card' : 'mini'; + } else { + $post_style = 'mini'; + } + } + $limit = max(0, min(12, (int) ($instance['limit'] ?? 6))); + $orderby = sanitize_key((string) ($instance['orderby'] ?? 'date')); $avatar = function_exists('aksrt_get_avatar_url') ? aksrt_get_avatar_url($author_id, ['size' => 120]) @@ -68,35 +80,39 @@ class WidgetAuthorCard extends \WP_Widget { $followers = \AKSRT\User\Follow::get_instance()->get_follower_count($author_id); } + $name_html = function_exists('aksrt_get_user_name') + ? aksrt_get_user_name(['id' => $author_id, 'follow' => false, 'class' => 'flex ac jc']) + : '' . esc_html($user->display_name) . ''; + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); if ($title !== '') { echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } - echo '
'; + echo '
'; if ($show_bg) { $bg = $cover !== '' ? ' style="background-image:url(' . esc_url($cover) . ')"' : ''; - echo '
'; + echo '
'; } - echo '
'; - echo ''; - echo '' . esc_html($user->display_name) . ''; + echo '
'; + echo '
'; + echo ''; + echo '
' . $name_html . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped if ($bio !== '') { - echo '
' . esc_html(wp_trim_words($bio, 28)) . '
'; + echo '
' . esc_html(wp_trim_words($bio, 28)) . '
'; } $show_badge = !isset($instance['show_badge']) || !empty($instance['show_badge']); if ($show_badge && class_exists('\AKSRT\User\Badges') && \AKSRT\User\Badges::public_display()) { $wear = \AKSRT\User\Badges::get_wear($author_id); if ($wear === '') { - // zibll alias fallback $wear = sanitize_key((string) get_user_meta($author_id, 'wear_medal', true)); } if ($wear !== '') { $map = \AKSRT\User\Badges::get_dictionary_map(); if (isset($map[$wear])) { $badge = $map[$wear]; - echo '
'; + echo '
'; if (!empty($badge['icon_url'])) { echo ' '; } else { @@ -118,18 +134,37 @@ class WidgetAuthorCard extends \WP_Widget { } $cuid = get_current_user_id(); $show_follow = !isset($instance['show_follow']) || !empty($instance['show_follow']); + echo '
'; if ($show_follow && $cuid > 0 && $cuid !== $author_id && class_exists('\AKSRT\User\Follow')) { $following = \AKSRT\User\Follow::get_instance()->is_following($cuid, $author_id); $label = $following ? __('已关注', 'aksrt') : __('关注', 'aksrt'); - echo ''; + echo ''; } - echo '' . esc_html__('查看主页', 'aksrt') . ''; + echo '' . esc_html__('查看主页', 'aksrt') . ''; + echo '
'; + echo '
'; // .user-content + + if ($show_posts && $limit > 0 && function_exists('aksrt_render_user_card_posts')) { + echo aksrt_render_user_card_posts([ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + 'user_id' => $author_id, + 'limit' => $limit, + 'orderby' => $orderby, + 'post_style' => $post_style, + 'post_type' => 'post', + 'show_empty' => true, + ]); + } + echo '
'; echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } public function form($instance): void { + $post_style = sanitize_key((string) ($instance['post_style'] ?? 'mini')); + if (!in_array($post_style, ['card', 'mini'], true)) { + $post_style = !empty($instance['show_img']) ? 'card' : 'mini'; + } ?>

@@ -146,12 +181,44 @@ class WidgetAuthorCard extends \WP_Widget {


- +
+ +

+

+ +

+

+ +

+

+

sanitize_text_field((string) ($new_instance['title'] ?? '')), 'user_id' => max(0, (int) ($new_instance['user_id'] ?? 0)), @@ -159,6 +226,10 @@ class WidgetAuthorCard extends \WP_Widget { 'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0, 'show_follow' => !empty($new_instance['show_follow']) ? 1 : 0, 'show_badge' => !empty($new_instance['show_badge']) ? 1 : 0, + 'show_posts' => !empty($new_instance['show_posts']) ? 1 : 0, + 'limit' => max(0, min(12, (int) ($new_instance['limit'] ?? 6))), + 'post_style' => $post_style, + 'orderby' => $orderby, ]; } } diff --git a/src/AKSRT/Widgets/WidgetUserCard.php b/src/AKSRT/Widgets/WidgetUserCard.php index c47afeea..61f42ac8 100644 --- a/src/AKSRT/Widgets/WidgetUserCard.php +++ b/src/AKSRT/Widgets/WidgetUserCard.php @@ -15,7 +15,7 @@ class WidgetUserCard extends \WP_Widget { parent::__construct( 'aksrt_user_card', __('用户卡片', 'aksrt'), - ['description' => __('未登录显示登录入口;登录后显示当前用户卡片、签到与快捷按钮。', 'aksrt')] + ['description' => __('未登录显示登录入口;登录后显示当前用户卡片、签到、快捷按钮与更多文章。', 'aksrt')] ); } @@ -24,9 +24,18 @@ class WidgetUserCard extends \WP_Widget { $show_stats = !empty($instance['show_stats']) || !isset($instance['show_stats']); $show_actions = !empty($instance['show_button']) || !isset($instance['show_button']); $show_checkin = !empty($instance['show_checkin']) || !isset($instance['show_checkin']); + // zibll user widget forces show_posts=false; author widget enables it. + // AKSRT keeps optional show_posts for current-user card (default off). + $show_posts = !empty($instance['show_posts']); $loged_title = (string) ($instance['loged_title'] ?? __('Hi!请登录', 'aksrt')); $btn1_text = (string) ($instance['button_1_text'] ?? __('发布文章', 'aksrt')); $btn2_text = (string) ($instance['button_2_text'] ?? __('用户中心', 'aksrt')); + $post_style = sanitize_key((string) ($instance['post_style'] ?? 'mini')); + if (!in_array($post_style, ['card', 'mini'], true)) { + $post_style = 'mini'; + } + $limit = max(0, min(12, (int) ($instance['limit'] ?? 6))); + $orderby = sanitize_key((string) ($instance['orderby'] ?? 'date')); // Fixed user id wins; else current user. $user_id = !empty($instance['user_id']) ? (int) $instance['user_id'] : 0; @@ -63,7 +72,6 @@ class WidgetUserCard extends \WP_Widget { ? aksrt_get_user_center_url() : $profile_url; $compose_url = home_url('/'); - // Prefer front submit / write page when available. if (function_exists('get_pages')) { $pages = get_pages(['meta_key' => '_wp_page_template', 'meta_value' => 'templates/pages/page-submit.php', 'number' => 1]); if (!empty($pages[0])) { @@ -90,14 +98,32 @@ class WidgetUserCard extends \WP_Widget { $following_count = \AKSRT\User\Follow::get_instance()->get_following_count($user_id); } + $name_html = function_exists('aksrt_get_user_name') + ? aksrt_get_user_name(['id' => $user_id, 'follow' => !$is_self, 'class' => 'flex ac jc']) + : '' . esc_html($user->display_name) . ''; + echo '
'; if ($show_cover) { - $bg = $cover !== '' ? ' style="background-image:url(' . esc_url($cover) . ')"' : ''; - echo '
'; + if ($cover !== '') { + echo '
'; + } else { + // Prefer site default cover image when available. + $default_cover = ''; + if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_user_cover_default')) { + $default_cover = (string) \AKSRT\Settings\Settings::get_user_cover_default(); + } + if ($default_cover === '' && function_exists('get_theme_mod')) { + $default_cover = (string) get_theme_mod('aksrt_user_cover', ''); + } + if ($default_cover !== '') { + echo '
'; + } else { + echo '
'; + } + } } echo '
'; if ($show_checkin && $is_self && function_exists('aksrt_header_user_checkin_btn')) { - // Reuse compact check-in button markup (without details if already long). $btn = aksrt_header_user_checkin_btn($user_id); if ($btn !== '') { echo '
' . $btn . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped @@ -106,7 +132,7 @@ class WidgetUserCard extends \WP_Widget { echo '
'; echo '' . esc_attr($user->display_name) . ''; echo '
'; - echo '

' . esc_html($user->display_name) . '

'; + echo '

' . $name_html . '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped if ($bio !== '') { echo '

' . esc_html(wp_trim_words($bio, 24)) . '

'; } @@ -125,6 +151,18 @@ class WidgetUserCard extends \WP_Widget { } elseif ($show_actions) { echo ''; } + + if ($show_posts && $limit > 0 && function_exists('aksrt_render_user_card_posts')) { + echo aksrt_render_user_card_posts([ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + 'user_id' => $user_id, + 'limit' => $limit, + 'orderby' => $orderby, + 'post_style' => $post_style, + 'post_type' => 'post', + 'show_empty' => false, + ]); + } + echo '
'; echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped @@ -149,7 +187,15 @@ class WidgetUserCard extends \WP_Widget { echo '
'; if ($show_cover) { - echo '
'; + $default_cover = ''; + if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_user_cover_default')) { + $default_cover = (string) \AKSRT\Settings\Settings::get_user_cover_default(); + } + if ($default_cover !== '') { + echo '
'; + } else { + echo '
'; + } } echo '
'; echo '
'; @@ -167,6 +213,10 @@ class WidgetUserCard extends \WP_Widget { } public function form($instance): void { + $post_style = sanitize_key((string) ($instance['post_style'] ?? 'mini')); + if (!in_array($post_style, ['card', 'mini'], true)) { + $post_style = 'mini'; + } ?>

+

+ +

+

+ +

+

+

sanitize_text_field((string) ($new_instance['title'] ?? '')), - 'user_id' => max(0, (int) ($new_instance['user_id'] ?? 0)), - 'loged_title' => sanitize_text_field((string) ($new_instance['loged_title'] ?? '')), - 'button_1_text' => sanitize_text_field((string) ($new_instance['button_1_text'] ?? '')), - 'button_2_text' => sanitize_text_field((string) ($new_instance['button_2_text'] ?? '')), - 'show_img_bg' => !empty($new_instance['show_img_bg']) ? 1 : 0, - 'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0, - 'show_button' => !empty($new_instance['show_button']) ? 1 : 0, - 'show_checkin' => !empty($new_instance['show_checkin']) ? 1 : 0, + 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), + 'user_id' => max(0, (int) ($new_instance['user_id'] ?? 0)), + 'loged_title' => sanitize_text_field((string) ($new_instance['loged_title'] ?? '')), + 'button_1_text' => sanitize_text_field((string) ($new_instance['button_1_text'] ?? '')), + 'button_2_text' => sanitize_text_field((string) ($new_instance['button_2_text'] ?? '')), + 'show_img_bg' => !empty($new_instance['show_img_bg']) ? 1 : 0, + 'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0, + 'show_button' => !empty($new_instance['show_button']) ? 1 : 0, + 'show_checkin' => !empty($new_instance['show_checkin']) ? 1 : 0, + 'show_posts' => !empty($new_instance['show_posts']) ? 1 : 0, + 'limit' => max(0, min(12, (int) ($new_instance['limit'] ?? 6))), + 'post_style' => $post_style, + 'orderby' => $orderby, ]; } } diff --git a/src/AKSRT/Widgets/WidgetUserRanking.php b/src/AKSRT/Widgets/WidgetUserRanking.php index b8fb394d..4cf6eada 100644 --- a/src/AKSRT/Widgets/WidgetUserRanking.php +++ b/src/AKSRT/Widgets/WidgetUserRanking.php @@ -25,10 +25,9 @@ class WidgetUserRanking extends \WP_Widget { $desc_mode = sanitize_key((string) ($instance['desc'] ?? 'desc')); $users = $this->get_ranking($rank_by, $count, $desc_mode); - if (empty($users)) return; - echo $args['before_widget']; - echo $args['before_title'] . esc_html($title) . $args['after_title']; + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped if (!empty($instance['checkin_btn']) && is_user_logged_in() && function_exists('aksrt_header_user_checkin_btn')) { $cbtn = aksrt_header_user_checkin_btn(get_current_user_id()); @@ -37,6 +36,14 @@ class WidgetUserRanking extends \WP_Widget { } } + if (empty($users)) { + echo '

' . esc_html__('暂无排行数据', 'aksrt') . '

'; + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + return; + } + + $top_badge = !empty($instance['top_badge']); + echo '
    '; $rank = 0; foreach ($users as $user) { @@ -46,7 +53,12 @@ class WidgetUserRanking extends \WP_Widget { ? aksrt_get_user_home_url($user['id']) : get_author_posts_url($user['id']); - echo '
  • '; + echo '
  • '; + + if ($top_badge && $rank <= 3) { + $tone = $rank === 1 ? 'jb-red' : ($rank === 2 ? 'jb-yellow' : 'b-gray'); + echo 'TOP' . (int) $rank . ''; + } // 排名 echo '' . esc_html((string) $rank) . ''; @@ -76,7 +88,7 @@ class WidgetUserRanking extends \WP_Widget { } echo '
'; - echo $args['after_widget']; + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } private function get_ranking(string $rank_by, int $count, string $desc_mode = 'desc'): array { @@ -287,6 +299,12 @@ class WidgetUserRanking extends \WP_Widget {

+

+ +