进行了一些小优化

This commit is contained in:
2026-08-30 21:48:59 +08:00
parent a73fa9a6c0
commit 71444b139a
12 changed files with 280 additions and 147 deletions
+11
View File
@@ -984,6 +984,17 @@ strong, b {
.but a { color: inherit; }
/* 关注控件(.follow 且无 .but):纯文字色样式,无背景色;
清除原生 button 的 UA 背景/边框,并保持 .but 同款可点反馈 */
.follow:not(.but) {
background: transparent;
border: none;
padding: 0;
cursor: pointer;
transition: 0.15s;
}
.follow:not(.but):hover { opacity: 0.8; }
.but .icon,
.but > .fa:not(.fa-angle-right),
.but > i[class*="fa-"]:not(.fa-angle-right) { margin-right: 0.25em; }
+1 -1
View File
File diff suppressed because one or more lines are too long
+11
View File
@@ -178,6 +178,17 @@
.but a { color: inherit; }
/* 关注控件(.follow 且无 .but):纯文字色样式,无背景色;
清除原生 button 的 UA 背景/边框,并保持 .but 同款可点反馈 */
.follow:not(.but) {
background: transparent;
border: none;
padding: 0;
cursor: pointer;
transition: 0.15s;
}
.follow:not(.but):hover { opacity: 0.8; }
.but .icon,
.but > .fa:not(.fa-angle-right),
.but > i[class*="fa-"]:not(.fa-angle-right) { margin-right: 0.25em; }
-4
View File
@@ -99,8 +99,6 @@
.aksrt-widget-text-title { padding: 18px 0; }.aksrt-widget-text-title.is-center { text-align: center; }.aksrt-widget-text-title.is-right { text-align: right; }
.aksrt-widget-text-title h2 { margin: 0; font-size: clamp(26px,4vw,44px); line-height: 1.25; }.aksrt-widget-text-title p { margin: 12px 0 0; color: var(--muted-color,#8a8c98); line-height: 1.8; }
.aksrt-user-card__guest-actions .aksrt-widget-button, .aksrt-user-card__actions > .aksrt-widget-button { display: inline-flex; min-height: 40px; padding: 9px 18px; align-items: center; justify-content: center; gap: 7px; border: 0; border-radius: 9px; background: var(--focus-color,#ff5473); color: #fff !important; font-weight: 600; cursor: pointer; transition: filter .2s, transform .2s; }
.aksrt-user-card__guest-actions .aksrt-widget-button:hover, .aksrt-user-card__actions > .aksrt-widget-button:hover { filter: brightness(1.07); transform: translateY(-1px); }.aksrt-user-card__guest-actions .aksrt-widget-button.is-secondary { background: rgba(127,127,127,.12); color: inherit !important; }
.aksrt-widget-gap { height: var(--aksrt-gap); }
.aksrt-widget-tag-cloud { display: flex; flex-wrap: wrap; gap: 8px; }.aksrt-widget-tag-cloud a { padding: 6px 11px; border-radius: 6px; background: rgba(127,127,127,.1); font-size: 13px; transition: background .2s,color .2s; }.aksrt-widget-tag-cloud a:hover { background: var(--focus-color,#ff5473); color: #fff; }.aksrt-widget-tag-cloud small { margin-left: 5px; opacity: .65; }
.aksrt-widget-comments { display: grid; gap: 15px; margin: 0; padding: 0; list-style: none; }.aksrt-widget-comments li { display: flex; gap: 11px; }.aksrt-widget-comments__avatar img { width: 42px; height: 42px; border-radius: 50%; }.aksrt-widget-comments p { margin: 3px 0; color: var(--muted-color,#8a8c98); font-size: 13px; line-height: 1.55; }.aksrt-widget-comments__source { font-size: 12px; opacity: .65; }
@@ -134,9 +132,7 @@
.aksrt-widget-slider { height: var(--aksrt-slider-height); overflow: hidden; border-radius: var(--aksrt-widget-radius); background: #111; }.aksrt-widget-slider .swiper-slide, .aksrt-widget-slider .swiper-slide > a { position: relative; display: block; width: 100%; height: 100%; overflow: hidden; }.aksrt-widget-slider .swiper-slide > img, .aksrt-widget-slider .swiper-slide > video, .aksrt-widget-slider .swiper-slide > a > img, .aksrt-widget-slider .swiper-slide > a > video { width: 100%; height: 100%; object-fit: cover; }.aksrt-slide-copy { position: absolute; z-index: 2; right: 8%; bottom: 12%; left: 8%; color: #fff; text-shadow: 0 2px 12px rgba(0,0,0,.4); }.aksrt-slide-copy.is-center { top: 50%; bottom: auto; text-align: center; transform: translateY(-50%); }.aksrt-slide-copy.is-right-bottom { text-align: right; }.aksrt-slide-copy h3 { margin: 0; font-size: clamp(25px,4vw,44px); }.aksrt-slide-copy p { margin: 8px 0 0; }.aksrt-slide-layer { position: absolute; z-index: 1; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }.aksrt-slider-combo { display: grid; grid-template-columns: minmax(0,2fr) minmax(240px,1fr); gap: 15px; }.aksrt-slider-combo__side { display: grid; gap: 15px; }.aksrt-slider-combo__side .aksrt-media-card { min-height: 0; }.aksrt-slider-combo__side .aksrt-media-card__media { aspect-ratio: 2/1; }
.aksrt-user-card { position: relative; overflow: hidden; border-radius: var(--main-radius,8px); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); text-align: center; }.aksrt-user-card__cover { height: 105px; overflow: hidden; background: var(--muted-border-color,#eee); background-position: center; background-size: cover; }.aksrt-user-card__cover img { display: block; width: 100%; height: 100%; object-fit: cover; }.aksrt-user-card__body { position: relative; padding: 15px; }.aksrt-user-card__avatar { display: inline-block; margin-top: -62px; padding: 4px; border-radius: 50%; background: var(--main-bg-color,#fff); }.aksrt-user-card__avatar img { display: block; width: 84px; height: 84px; border-radius: 50%; object-fit: cover; }.aksrt-user-card.is-guest .aksrt-user-card__avatar { margin-top: -58px; }.aksrt-user-card h3 { margin: 7px 0; }.aksrt-user-card__identity { margin: 6px 0; }.aksrt-user-card__body > p { color: var(--muted-color,#8a8c98); }.aksrt-user-card__guest-title { margin: 8px 0 14px; font-size: 14px; }.aksrt-user-card__guest-actions { display: flex; flex-wrap: nowrap; justify-content: center; gap: 10px; }.aksrt-user-card__guest-actions > .aksrt-widget-button { flex: 1 1 0; min-width: 0; }.aksrt-user-card__stats { display: flex; justify-content: center; gap: 24px; margin: 16px 0; }.aksrt-user-card__stats span { display: flex; flex-direction: column; font-size: 12px; color: var(--muted-color,#8a8c98); }.aksrt-user-card__stats b { color: inherit; font-size: 17px; }.aksrt-user-card__actions { display: flex; justify-content: center; gap: 9px; }.aksrt-user-card__actions > .aksrt-widget-button, .aksrt-user-card__actions > .aksrt-user-card__checkin { flex: 1 1 0; min-width: 0; }.aksrt-user-card__checkin > .aksrt-header-checkin { height: 100%; min-height: 40px; }.aksrt-user-card__checkin.is-primary > .aksrt-header-checkin { background: var(--focus-color,#ff5473) !important; color: #fff !important; }.aksrt-user-card__checkin.is-primary > .aksrt-header-checkin > i { color: inherit !important; }.aksrt-user-card__checkin.is-secondary > .aksrt-header-checkin { background: rgba(127,127,127,.12) !important; color: inherit !important; }.aksrt-user-card__checkin.is-secondary > .aksrt-header-checkin > i { color: inherit !important; }.aksrt-user-card__follow { position: absolute; top: 14px; right: 14px; padding: 5px 12px; border: 0; border-radius: 999px; background: var(--focus-color,#ff5473); color: #fff; cursor: pointer; }.aksrt-user-card__follow.is-following { background: rgba(0,0,0,.5); }.aksrt-user-card__posts { padding: 0 16px 16px; text-align: left; }
.aksrt-user-ranking { display: grid; gap: 11px; margin: 0; padding: 0; list-style: none; }.aksrt-user-ranking li { display: flex; align-items: center; gap: 9px; }.aksrt-user-ranking__index { width: 24px; text-align: center; color: var(--muted-color,#8a8c98); font-weight: 700; }.aksrt-user-ranking__index.is-top { color: #f59e0b; }.aksrt-user-tile { display: flex; min-width: 0; flex: 1; align-items: center; gap: 10px; }.aksrt-user-tile img { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }.aksrt-user-tile span { display: flex; min-width: 0; flex-direction: column; }.aksrt-user-tile strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.aksrt-user-tile small { color: var(--muted-color,#8a8c98); }.aksrt-user-list { --aksrt-cols: 4; }.aksrt-user-list article { padding: 14px; border-radius: 10px; background: var(--main-bg,#fff); box-shadow: 0 6px 20px rgba(31,35,48,.06); }
.aksrt-user-card.no-cover .aksrt-user-card__avatar { margin-top: 0; }.aksrt-user-card__oauth { margin-top: 14px; }
@media (max-width: 767px) {
.aksrt-widget { margin: var(--aksrt-widget-space-m-top,0) var(--aksrt-widget-space-m-right,0) var(--aksrt-widget-space-m-bottom,15px) var(--aksrt-widget-space-m-left,0); }
+7 -6
View File
@@ -11,10 +11,11 @@ if (!function_exists('aksrt_header_user_checkin_btn')) :
/**
* Check-in CTA for header dropdown and compact card placements.
*
* @param int $user_id Current user ID.
* @param bool $compact Render a short card-friendly label.
* @param int $user_id Current user ID.
* @param bool $compact Render a short card-friendly label.
* @param string|null $class Override the button classes (keeps data-signin behavior).
*/
function aksrt_header_user_checkin_btn(int $user_id = 0, bool $compact = false): string {
function aksrt_header_user_checkin_btn(int $user_id = 0, bool $compact = false, ?string $class = null): string {
$user_id = $user_id > 0 ? $user_id : get_current_user_id();
if ($user_id <= 0 || !class_exists('\AKSRT\Credits\Signin')) {
return '';
@@ -28,15 +29,15 @@ if (!function_exists('aksrt_header_user_checkin_btn')) :
$signed = \AKSRT\Credits\Signin::get_instance()->is_signed_today($user_id);
$compact_class = $compact ? ' aksrt-header-checkin--compact' : '';
$classes = $class ?? ('but block c-yellow aksrt-header-checkin' . ($compact ? ' aksrt-header-checkin--compact' : ''));
if ($signed) {
return '<button type="button" class="but block c-yellow aksrt-header-checkin' . $compact_class . ' is-done" data-signin-btn data-signin-idle-label="' . esc_attr__('签到', 'aksrt') . '" data-signin-done-label="' . esc_attr__('已签到', 'aksrt') . '" disabled>'
return '<button type="button" class="' . esc_attr($classes . ' is-done') . '" data-signin-btn data-signin-idle-label="' . esc_attr__('签到', 'aksrt') . '" data-signin-done-label="' . esc_attr__('已签到', 'aksrt') . '" disabled>'
. '<i class="fa fas fa-calendar-check" aria-hidden="true"></i>'
. '<span data-signin-label>' . esc_html($compact ? __('已签到', 'aksrt') : __('今日已签到', 'aksrt')) . '</span>'
. '</button>';
}
return '<button type="button" class="but block c-yellow aksrt-header-checkin' . $compact_class . '" data-signin-btn data-signin-idle-label="' . esc_attr__('签到', 'aksrt') . '" data-signin-done-label="' . esc_attr__('已签到', 'aksrt') . '">'
return '<button type="button" class="' . esc_attr($classes) . '" data-signin-btn data-signin-idle-label="' . esc_attr__('签到', 'aksrt') . '" data-signin-done-label="' . esc_attr__('已签到', 'aksrt') . '">'
. '<i class="fa fas fa-calendar-check" aria-hidden="true"></i>'
. '<span data-signin-label>' . esc_html($compact ? __('签到', 'aksrt') : __('签到领取今日奖励', 'aksrt')) . '</span>'
. '</button>';
+7 -2
View File
@@ -223,10 +223,15 @@ if (!function_exists('aksrt_get_user_name')) {
$follow_html = '';
if (!empty($args['follow']) && get_current_user_id() !== $user_id) {
if (is_user_logged_in()) {
$follow_html = '<button type="button" class="focus-color ml10 follow flex0 but" data-aksrt-follow="' . (int) $user_id . '"><i class="fa fas fa-heart em09" aria-hidden="true"></i> ' . esc_html__('关注', 'aksrt') . '</button>';
// 服务端直接输出真实关注状态(legacy parity: zib_get_user_follow 恒渲染当前态)。
$is_following = class_exists('\AKSRT\User\Follow') && \AKSRT\User\Follow::get_instance()->is_following(get_current_user_id(), $user_id);
$follow_label = $is_following ? __('已关注', 'aksrt') : __('关注', 'aksrt');
$follow_title = $is_following ? __('点击取消关注', 'aksrt') : __('关注该用户', 'aksrt');
$follow_state = $is_following ? ' is-following' : '';
$follow_html = '<button type="button" class="focus-color ml10 follow flex0' . $follow_state . '" data-aksrt-follow="' . (int) $user_id . '" title="' . esc_attr($follow_title) . '"><i class="fa fas fa-heart em09" aria-hidden="true"></i> ' . esc_html($follow_label) . '</button>';
} else {
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
$follow_html = '<a class="focus-color ml10 follow flex0 but" href="' . esc_url($login) . '"><i class="fa fas fa-heart em09" aria-hidden="true"></i> ' . esc_html__('关注', 'aksrt') . '</a>';
$follow_html = '<a class="focus-color ml10 follow flex0" href="' . esc_url($login) . '"><i class="fa fas fa-heart em09" aria-hidden="true"></i> ' . esc_html__('关注', 'aksrt') . '</a>';
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ final class FrontendRenderer {
return;
}
$classes = ['aksrt-widget', 'aksrt-panel-wrap', 'aksrt-widget--' . $definition->slug(), 'aksrt-widget--' . $definition->size()];
if ($definition->size() === 'sidebar' && $definition->slug() !== 'user_card') {
if ($definition->size() === 'sidebar' && !in_array($definition->slug(), ['user_card', 'author_card'], true)) {
$classes[] = 'aksrt-panel';
}
$device_class = WidgetVisibility::device_class($instance);
+161 -72
View File
@@ -25,32 +25,71 @@ final class UserRenderer {
return self::guest_card($i);
}
$user = get_userdata($user_id);
return $user instanceof \WP_User ? self::profile_card($user, $i, true) : '';
if (!$user instanceof \WP_User) {
return '';
}
return self::card_box($user_id, [
'class' => 'widget',
'show_posts' => false,
'show_checkin' => true,
'show_img_bg' => !isset($i['show_img_bg']) || !empty($i['show_img_bg']),
] + self::card_button_args($i));
}
private static function author_card(array $i): string {
$post = get_post();
if (!$post instanceof \WP_Post) {
return '';
}
$user = get_userdata((int) $post->post_author);
if (!$user instanceof \WP_User) {
return '';
}
$post_style = sanitize_key((string) ($i['post_style'] ?? 'mini'));
return self::card_box((int) $user->ID, [
'class' => 'widget',
'show_img_bg' => !isset($i['show_img_bg']) || !empty($i['show_img_bg']),
'show_posts' => !isset($i['show_posts']) || !empty($i['show_posts']),
'limit' => max(0, min(12, (int) ($i['limit'] ?? 6))),
'orderby' => sanitize_key((string) ($i['orderby'] ?? 'date')),
'post_style' => $post_style === 'card' ? 'card' : 'mini',
] + self::card_button_args($i));
}
private static function card_button_args(array $i): array {
return [
'show_button' => !isset($i['show_button']) || !empty($i['show_button']),
'button_1' => (string) ($i['button_1'] ?? 'post'),
'button_1_class' => (string) ($i['button_1_class'] ?? 'jb-pink'),
'button_1_text' => (string) ($i['button_1_text'] ?? ''),
'button_2' => (string) ($i['button_2'] ?? 'center'),
'button_2_class' => (string) ($i['button_2_class'] ?? 'jb-blue'),
'button_2_text' => (string) ($i['button_2_text'] ?? ''),
];
}
/**
* 未登录卡片使用独立组件结构,避免嵌套全局 .widget 卡片外观。
* 未登录卡片(legacy zib_get_user_singin_page_box parity)。
*/
private static function guest_card(array $i): string {
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
$register = function_exists('aksrt_get_register_url') ? aksrt_get_register_url() : wp_registration_url();
$title = trim((string) ($i['loged_title'] ?? ''));
if ($title === '') {
$title = __('Hi!请登录', 'aksrt');
}
$card_classes = 'aksrt-user-card is-guest';
$cover = '';
if (!empty($i['show_cover'])) {
$cover_url = function_exists('aksrt_get_user_cover_url') ? aksrt_get_user_cover_url(0) : get_theme_file_uri('assets/img/user_t.jpg');
$cover = '<div class="aksrt-user-card__cover"><img' . aksrt_get_lazy_attr('lazy_cover', $cover_url, 'lazyload fit-cover', aksrt_get_lazy_thumb('lg')) . ' alt=""></div>';
} else {
$card_classes .= ' no-cover';
if (!isset($i['show_img_bg']) || !empty($i['show_img_bg'])) {
$cover_url = aksrt_get_user_cover_url(0);
$cover = '<div class="user-cover graphic" style="padding-bottom: 50%;"><img' . aksrt_get_lazy_attr('lazy_other', $cover_url, 'fit-cover user-cover', aksrt_get_lazy_thumb()) . ' alt="' . esc_attr__('用户封面', 'aksrt') . '"></div>';
}
$buttons = '<a class="signin-loader aksrt-widget-button is-primary" href="' . esc_url($login) . '"><i class="fa fas fa-fw fa-sign-in-alt" aria-hidden="true"></i>' . esc_html__('登录', 'aksrt') . '</a>';
$avatar = '<span class="avatar-img avatar-lg">' . aksrt_get_avatar_html(0, 80, ['class' => 'fit-cover avatar']) . '</span>';
$buttons = '<a href="javascript:;" class="signin-loader but jb-blue padding-lg"><i class="fa fas fa-fw fa-sign-in" aria-hidden="true"></i>' . esc_html__('登录', 'aksrt') . '</a>';
if (get_option('users_can_register')) {
$buttons .= '<a class="signup-loader aksrt-widget-button is-secondary" href="' . esc_url($register) . '"><i class="fa fas fa-fw fa-user-plus" aria-hidden="true"></i>' . esc_html__('注册', 'aksrt') . '</a>';
$buttons .= '<a href="javascript:;" class="signup-loader ml10 but jb-yellow padding-lg">' . aksrt_renderer_svg_icon('signup') . esc_html__('注册', 'aksrt') . '</a>';
}
$oauth_html = '';
@@ -61,74 +100,124 @@ final class UserRenderer {
}
}
$avatar = '<span class="aksrt-user-card__avatar">' . aksrt_get_avatar_html(0, 80, ['class' => 'avatar fit-cover']) . '</span>';
return '<article class="' . esc_attr($card_classes) . '">' . $cover
. '<div class="aksrt-user-card__body">'
. $avatar
. '<p class="aksrt-user-card__guest-title">' . esc_html($title) . '</p>'
. '<div class="aksrt-user-card__guest-actions">' . $buttons . '</div>'
return '<div class="user-card zib-widget widget">' . $cover
. '<div class="card-content mt10">'
. '<div class="user-content">'
. '<div class="user-avatar">' . $avatar . '</div>'
. '<div class="user-info mt10">'
. '<div class="text-center box-body">'
. '<p class="muted-color box-body em12">' . esc_html($title) . '</p>'
. '<p>' . $buttons . '</p>'
. $oauth_html
. '</div></article>';
. '</div></div></div></div></div>';
}
private static function author_card(array $i): string {
$post = get_post();
if (!$post instanceof \WP_Post) {
/**
* 登录卡片(legacy zib_get_user_card_box parity)。
*
* @param int $user_id 卡片用户。
* @param array $args class/show_posts/show_checkin/show_img_bg/show_button/button_1,2 与文章模块参数。
*/
private static function card_box(int $user_id, array $args = []): string {
$args = wp_parse_args($args, [
'class' => '',
'show_posts' => true,
'show_checkin' => false,
'show_img_bg' => false,
'show_button' => true,
'button_1' => 'post',
'button_1_class' => 'jb-pink',
'button_1_text' => __('发布文章', 'aksrt'),
'button_2' => 'center',
'button_2_class' => 'jb-blue',
'button_2_text' => __('用户中心', 'aksrt'),
'limit' => 6,
'orderby' => 'views',
'post_style' => 'card',
]);
$avatar = aksrt_get_avatar_box($user_id, 'avatar-img avatar-lg');
$name = aksrt_get_user_name([
'id' => $user_id,
'class' => 'flex1 flex ac',
'vip' => false,
'medal' => true,
'auth' => true,
'level' => true,
'follow' => true,
]);
$tag_metas = aksrt_get_user_badges($user_id);
$desc = aksrt_get_user_description($user_id);
$btns = '';
$posts = '';
$checkin = '';
$cuid = get_current_user_id();
if (!$cuid || $cuid !== $user_id) {
$args['show_button'] = false;
}
if ($args['show_posts'] && (int) $args['limit'] > 0) {
$posts = aksrt_render_user_card_posts([
'user_id' => $user_id,
'limit' => (int) $args['limit'],
'orderby' => (string) $args['orderby'],
'post_type' => 'post',
'post_style' => (string) $args['post_style'],
]);
}
if ($args['show_checkin'] && $cuid === $user_id) {
$checkin = aksrt_header_user_checkin_btn($user_id, true, 'img-badge jb-yellow');
}
if ($args['show_button']) {
$btns .= self::new_add_btn((string) $args['button_1'], 'but pw-1em mr6 ' . (string) $args['button_1_class'], (string) $args['button_1_text']);
if ('home' === $args['button_2']) {
$btns .= self::card_link(aksrt_get_user_home_url($user_id), (string) $args['button_2_class'], (string) $args['button_2_text']);
} else {
$btns .= self::card_link(aksrt_get_user_center_url(), (string) $args['button_2_class'], (string) $args['button_2_text'], true);
}
$btns = $btns !== '' ? '<div class="user-btns mt20">' . $btns . '</div>' : '';
}
$cover = '';
if ($args['show_img_bg']) {
$cover_url = aksrt_get_user_cover_url($user_id);
$cover = '<div class="user-cover graphic" style="padding-bottom: 50%;"><img' . aksrt_get_lazy_attr('lazy_cover', $cover_url, 'fit-cover user-cover', aksrt_get_lazy_thumb()) . ' alt="' . esc_attr__('用户封面', 'aksrt') . '"></div>';
}
return '<div class="user-card zib-widget ' . esc_attr((string) $args['class']) . '">' . $cover
. '<div class="card-content mt10 relative">'
. '<div class="user-content">' . $checkin
. '<div class="user-avatar">' . $avatar . '</div>'
. '<div class="user-info mt20 mb10">'
. '<div class="user-name flex jc">' . $name . '</div>'
. '<div class="author-tag mt10 mini-scrollbar">' . $tag_metas . '</div>'
. '<div class="user-desc mt10 muted-2-color em09">' . esc_html($desc) . '</div>'
. $btns
. '</div></div>'
. $posts
. '</div></div>';
}
/**
* 卡片按钮1(legacy zib_get_new_add_btns 的 post 类型 parity)。
*/
private static function new_add_btn(string $type, string $class, string $text): string {
if ($type !== 'post') {
return '';
}
$user = get_userdata((int) $post->post_author);
return $user instanceof \WP_User ? self::profile_card($user, $i, false) : '';
}
private static function profile_card(\WP_User $user, array $i, bool $self_card): string {
$user_id = (int) $user->ID;
$home = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user) : get_author_posts_url($user_id);
$cover = function_exists('aksrt_get_user_cover_url') ? aksrt_get_user_cover_url($user_id) : '';
$description = function_exists('aksrt_get_user_description') ? aksrt_get_user_description($user_id) : (string) $user->description;
$followers = class_exists('AKSRT\\User\\Follow') ? \AKSRT\User\Follow::get_instance()->get_follower_count($user_id) : 0;
$following = class_exists('AKSRT\\User\\Follow') ? \AKSRT\User\Follow::get_instance()->get_following_count($user_id) : 0;
$html = '<article class="aksrt-user-card' . (!$self_card ? ' is-author' : '') . (empty($i['show_cover']) ? ' no-cover' : '') . '">';
if (!empty($i['show_cover'])) {
$html .= '<div class="aksrt-user-card__cover"' . ($cover ? ' style="background-image:url(' . esc_url($cover) . ')"' : '') . '></div>';
}
$html .= '<div class="aksrt-user-card__body"><a class="aksrt-user-card__avatar" href="' . esc_url($home) . '">' . (function_exists('aksrt_get_avatar_html') ? aksrt_get_avatar_html($user_id, 96) : get_avatar($user_id, 96)) . '</a><h3><a href="' . esc_url($home) . '">' . esc_html($user->display_name) . '</a></h3>';
if (function_exists('aksrt_render_user_identity_chips')) {
$html .= '<div class="aksrt-user-card__identity">' . aksrt_render_user_identity_chips($user_id, 'widget') . '</div>';
}
$html .= '<p>' . esc_html(wp_trim_words($description, 24)) . '</p><div class="aksrt-user-card__stats"><span><b>' . number_format_i18n(count_user_posts($user_id, 'post', true)) . '</b>' . esc_html__('文章', 'aksrt') . '</span><span><b>' . number_format_i18n($followers) . '</b>' . esc_html__('粉丝', 'aksrt') . '</span><span><b>' . number_format_i18n($following) . '</b>' . esc_html__('关注', 'aksrt') . '</span></div>';
if (!empty($i['show_buttons'])) {
$html .= '<div class="aksrt-user-card__actions">' . self::action((string) $i['primary_action'], $user, true) . self::action((string) $i['secondary_action'], $user, false) . '</div>';
}
if (!$self_card && get_current_user_id() > 0 && get_current_user_id() !== $user_id) {
$active = class_exists('AKSRT\\User\\Follow') && \AKSRT\User\Follow::get_instance()->is_following(get_current_user_id(), $user_id);
$html .= '<button class="aksrt-user-card__follow' . ($active ? ' is-following' : '') . '" type="button" data-aksrt-follow="' . $user_id . '">' . esc_html($active ? __('已关注', 'aksrt') : __('关注', 'aksrt')) . '</button>';
}
$html .= '</div>';
if (!empty($i['show_posts']) && function_exists('aksrt_render_user_card_posts')) {
$html .= '<div class="aksrt-user-card__posts">' . aksrt_render_user_card_posts(['user_id' => $user_id, 'limit' => 4, 'post_style' => 'mini']) . '</div>';
}
return $html . '</article>';
}
private static function action(string $action, \WP_User $user, bool $primary): string {
$labels = ['home' => __('个人主页', 'aksrt'), 'submit' => __('发布文章', 'aksrt'), 'vip' => __('会员中心', 'aksrt'), 'orders' => __('订单中心', 'aksrt'), 'recharge' => __('余额充值', 'aksrt'), 'signin' => __('签到', 'aksrt')];
$variant = $primary ? ' is-primary' : ' is-secondary';
if ($action === 'signin' && function_exists('aksrt_header_user_checkin_btn') && get_current_user_id() === (int) $user->ID) {
return '<span class="aksrt-user-card__checkin' . $variant . '">' . aksrt_header_user_checkin_btn((int) $user->ID, true) . '</span>';
}
if ($action === 'home') {
$url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user) : get_author_posts_url((int) $user->ID);
} elseif ($action === 'submit') {
$url = function_exists('aksrt_get_new_post_url') ? aksrt_get_new_post_url() : home_url('/submit/');
} else {
$tab = $action === 'signin' ? 'credits' : $action;
$url = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url($tab) : home_url('/user/');
}
$url = aksrt_get_new_post_url();
if ($url === '') {
return '';
}
return '<a class="aksrt-widget-button' . $variant . '" href="' . esc_url($url) . '">' . esc_html($labels[$action] ?? $labels['home']) . '</a>';
return '<a rel="nofollow" class="newadd-btns ' . esc_attr($class) . '" href="' . esc_url($url) . '"><icon class="jb-green"><i class="fa fas fa-pencil-square" aria-hidden="true"></i></icon><text>' . esc_html($text) . '</text></a>';
}
private static function card_link(string $url, string $class, string $text, bool $nofollow = false): string {
if ($url === '') {
return '';
}
return '<a' . ($nofollow ? ' rel="nofollow"' : '') . ' href="' . esc_url($url) . '" class="but pw-1em ml6 ' . esc_attr($class) . '">' . esc_html($text) . '</a>';
}
private static function ranking(array $i): string {
+2 -2
View File
@@ -52,8 +52,8 @@ final class WidgetCatalog {
['slider_media_combo', '幻灯片加图文视频卡片', '幻灯片与图文视频卡片组合布局', 'wide'],
],
'user' => [
['user_card', '用户个人信息', '根据登录状态显示登录入口或用户信息', 'sidebar'],
['author_card', '文章作者信息', '显示当前文章作者与关注入口', 'sidebar'],
['user_card', '用户个人信息', '未登录时候显示登录注册按钮,登录后显示登录用户的个人信息', 'sidebar'],
['author_card', '文章作者信息', '显示当前文章作者的个人信息,只会在文章页显示', 'sidebar'],
['user_ranking', '用户排行榜', '按签到、积分或粉丝生成排行', 'sidebar'],
['user_list', '用户列表', '筛选并显示注册用户列表', 'wide'],
],
+21 -12
View File
@@ -359,17 +359,30 @@ final class WidgetSchemas {
['id' => 'orderby', 'type' => 'select', 'label' => '排序', 'default' => 'registered', 'options' => ['registered' => '注册时间', 'display_name' => '名称', 'post_count' => '文章数', 'points' => '积分', 'followers' => '粉丝']],
];
}
$fields = [
['id' => 'show_cover', 'type' => 'switcher', 'label' => '显示封面', 'default' => true],
['id' => 'show_posts', 'type' => 'switcher', 'label' => '显示文章', 'default' => $slug === 'author_card'],
['id' => 'show_buttons', 'type' => 'switcher', 'label' => '显示快捷按钮', 'default' => true],
['id' => 'primary_action', 'type' => 'select', 'label' => '主按钮', 'default' => 'submit', 'options' => self::user_actions()],
['id' => 'secondary_action', 'type' => 'select', 'label' => '次按钮', 'default' => 'home', 'options' => self::user_actions()],
$button_fields = [
['id' => 'show_button', 'type' => 'switcher', 'label' => '显示按钮', 'description' => $slug === 'user_card' ? '登录后才会显示' : '当登录用户就是文章作者的时候才会显示', 'default' => true],
['id' => 'button_1', 'type' => 'radio', 'label' => '按钮1类型', 'default' => 'post', 'options' => ['post' => '文章投稿'], 'dependency' => ['show_button', '!=', '']],
['id' => 'button_1_class', 'type' => 'palette', 'label' => '按钮1颜色', 'default' => 'jb-pink', 'options' => self::zib_palette(), 'dependency' => ['show_button', '!=', '']],
['id' => 'button_1_text', 'type' => 'text', 'label' => '按钮1文字', 'default' => '发布文章', 'dependency' => ['show_button', '!=', '']],
['id' => 'button_2', 'type' => 'radio', 'label' => '按钮2类型', 'default' => 'center', 'options' => ['home' => '个人主页', 'center' => '用户中心'], 'dependency' => ['show_button', '!=', '']],
['id' => 'button_2_class', 'type' => 'palette', 'label' => '按钮2颜色', 'default' => 'jb-blue', 'options' => self::zib_palette(), 'dependency' => ['show_button', '!=', '']],
['id' => 'button_2_text', 'type' => 'text', 'label' => '按钮2文字', 'default' => '用户中心', 'dependency' => ['show_button', '!=', '']],
];
if ($slug === 'user_card') {
$fields[] = ['id' => 'loged_title', 'type' => 'text', 'label' => '未登录文案', 'default' => 'Hi!请登录'];
return [
['id' => 'show_img_bg', 'type' => 'switcher', 'label' => '显示封面', 'default' => true],
['id' => 'loged_title', 'type' => 'text', 'label' => '未登录的文案', 'default' => 'HI!请登录'],
...$button_fields,
];
}
return $fields;
return [
['id' => 'show_img_bg', 'type' => 'switcher', 'label' => '显示封面', 'default' => true],
['id' => 'show_posts', 'type' => 'switcher', 'label' => '显示文章', 'default' => true],
['id' => 'post_style', 'type' => 'radio', 'label' => '显示样式', 'default' => 'mini', 'options' => ['mini' => '简约风格', 'card' => '图文卡片'], 'dependency' => ['show_posts', '!=', '']],
['id' => 'limit', 'type' => 'spinner', 'label' => '显示数量', 'default' => 6, 'min' => 2, 'max' => 12, 'step' => 1, 'unit' => '篇', 'dependency' => ['show_posts', '!=', '']],
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => 'date', 'options' => ['modified' => '最近更新', 'date' => '最新发布', 'views' => '最多浏览', 'like' => '最多点赞', 'comment_count' => '最多评论', 'favorite' => '最多收藏', 'rand' => '随机'], 'dependency' => ['show_posts', '!=', '']],
...$button_fields,
];
}
private static function icon_cards_fields(): array {
@@ -474,8 +487,4 @@ final class WidgetSchemas {
private static function pay_types(): array {
return ['' => '全部', 'reading' => '付费阅读', 'download' => '付费下载', 'gallery' => '付费图库', 'video' => '付费视频', 'vip' => 'VIP 专属'];
}
private static function user_actions(): array {
return ['home' => '个人主页', 'submit' => '前台投稿', 'vip' => '会员中心', 'orders' => '订单中心', 'recharge' => '余额充值', 'signin' => '签到'];
}
}
+32 -32
View File
@@ -46,23 +46,20 @@ $classes = trim('article-avatar ' . $extra_class);
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink());
$is_self = is_user_logged_in() && get_current_user_id() === $author_id;
// Identity: prefer aksrt_get_user_name (legacy order); fall back to chips.
$identity_chips = '';
$name_html = '';
if (function_exists('aksrt_get_user_name')) {
$name_html = aksrt_get_user_name([
'id' => $author_id,
'class' => 'name flex ac flex1',
'name_class' => '',
'vip' => true,
'medal' => true,
'auth' => true,
'level' => true,
'follow' => false,
]);
} elseif (function_exists('aksrt_render_user_identity_chips')) {
$identity_chips = aksrt_render_user_identity_chips($author_id, 'author');
}
// Identity: aksrt_get_user_name is the canonical renderer (legacy order).
$name_html = '';
if (function_exists('aksrt_get_user_name')) {
$name_html = aksrt_get_user_name([
'id' => $author_id,
'class' => 'name flex ac flex1',
'name_class' => '',
'vip' => true,
'medal' => true,
'auth' => true,
'level' => true,
'follow' => false,
]);
}
?>
<div class="<?php echo esc_attr($classes); ?>">
@@ -83,9 +80,6 @@ $is_self = is_user_logged_in() && get_current_user_id() === $author_id;
<?php else : ?>
<span class="name flex ac flex1">
<a class="display-name text-ellipsis" href="<?php echo esc_url($author_url); ?>"><?php echo esc_html($author_name); ?></a>
<?php if ($identity_chips !== '') : ?>
<?php echo $identity_chips; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php endif; ?>
</span>
<?php endif; ?>
<div class="px12-sm muted-2-color text-ellipsis">
@@ -113,20 +107,26 @@ $is_self = is_user_logged_in() && get_current_user_id() === $author_id;
<div class="flex0 user-action">
<?php if (!$is_self) : ?>
<?php if (is_user_logged_in()) : ?>
<button type="button" class="px12-sm ml10 follow but c-red" data-aksrt-follow="<?php echo (int) $author_id; ?>">
<i class="fa fas fa-heart em09" aria-hidden="true"></i> <span><?php esc_html_e('关注', 'aksrt'); ?></span>
</button>
<?php
$reward_button = function_exists('aksrt_get_rewards_button')
? aksrt_get_rewards_button($author_id, 'ml6 but c-yellow px12-sm', get_the_ID())
: '';
if ($reward_button !== '') {
echo $reward_button; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
aksrt_render_rewards_modal_for_user($author_id, get_the_ID());
}
<?php
// 服务端直接输出真实关注状态(legacy parity: zib_get_user_follow 恒渲染当前态)。
$is_following = class_exists('\AKSRT\User\Follow') && \AKSRT\User\Follow::get_instance()->is_following(get_current_user_id(), $author_id);
$follow_label = $is_following ? __('已关注', 'aksrt') : __('关注', 'aksrt');
$follow_title = $is_following ? __('点击取消关注', 'aksrt') : __('关注该用户', 'aksrt');
?>
<button type="button" class="px12-sm ml10 follow c-red<?php echo $is_following ? ' is-following' : ''; ?>" data-aksrt-follow="<?php echo (int) $author_id; ?>" title="<?php echo esc_attr($follow_title); ?>">
<i class="fa fas fa-heart em09" aria-hidden="true"></i> <span><?php echo esc_html($follow_label); ?></span>
</button>
<?php
$reward_button = function_exists('aksrt_get_rewards_button')
? aksrt_get_rewards_button($author_id, 'ml6 but c-yellow px12-sm', get_the_ID())
: '';
if ($reward_button !== '') {
echo $reward_button; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
aksrt_render_rewards_modal_for_user($author_id, get_the_ID());
}
?>
<?php else : ?>
<a href="<?php echo esc_url($login_url); ?>" class="px12-sm ml10 follow but c-red">
<a href="<?php echo esc_url($login_url); ?>" class="px12-sm ml10 follow c-red">
<i class="fa fas fa-heart em09" aria-hidden="true"></i> <?php esc_html_e('关注', 'aksrt'); ?>
</a>
<?php endif; ?>
@@ -66,7 +66,7 @@ test('page display exposes exactly eight ordered routes', () => {
const expected = ['header-nav', 'header-components', 'footer', 'homepage', 'category', 'tag', 'user-home', 'other']
const schemaOrder = [...schema.matchAll(/^\t\t\t\t'([^']+)'\s+=> self::/gm)].map(match => match[1])
assert.deepEqual(schemaOrder, expected)
const routeOrder = [...routes.matchAll(/^\s+\['([^']+)',/gm)].map(match => match[1])
const routeOrder = [...routes.matchAll(/section:\s*'([^']+)'/g)].map(match => match[1])
assert.deepEqual(routeOrder, expected)
})
@@ -262,20 +262,31 @@ test('header multifunction search keeps focus and submit alignment route-indepen
)
})
test('user profile widget owns its only card surface and keeps membership below the name', () => {
assert.match(frontendRenderer, /size\(\) === 'sidebar' && \$definition->slug\(\) !== 'user_card'/)
assert.match(userRenderer, /display_name\) \. '<\/a><\/h3>'/)
assert.match(userRenderer, /aksrt-user-card__identity.*aksrt_render_user_identity_chips\(\$user_id, 'widget'\)/s)
assert.doesNotMatch(userRenderer, /display_name\) \. '<\/a>' \. \(function_exists\('aksrt_render_vip_badge'/)
assert.match(userRenderer, /\$variant = \$primary \? ' is-primary' : ' is-secondary'/)
// 未登录卡片使用独立结构,避免嵌套通用 .widget 卡片背景。
assert.doesNotMatch(userRenderer, /user-card widget/)
assert.match(userRenderer, /aksrt-user-card is-guest/)
assert.match(userRenderer, /aksrt-user-card__guest-actions/)
assert.match(userRenderer, /signin-loader aksrt-widget-button is-primary/)
assert.match(userRenderer, /signup-loader aksrt-widget-button is-secondary/)
assert.match(userRenderer, /fa fas fa-fw fa-sign-in-alt/)
assert.match(widgetCss, /\.aksrt-user-card__guest-actions\s*\{[^}]*display:\s*flex;[^}]*flex-wrap:\s*nowrap;/s)
test('user profile and author widgets render the legacy user-card box', () => {
assert.match(frontendRenderer, /!in_array\(\$definition->slug\(\), \['user_card', 'author_card'\], true\)/)
// 登录卡片与 legacy zib_get_user_card_box 同构:封面、头像框、名字行、统计徽章、简介、按钮。
assert.match(userRenderer, /user-card zib-widget/)
assert.match(userRenderer, /user-cover graphic" style="padding-bottom: 50%;"/)
assert.match(userRenderer, /avatar-img avatar-lg/)
assert.match(userRenderer, /user-name flex jc/)
assert.match(userRenderer, /author-tag mt10 mini-scrollbar/)
assert.match(userRenderer, /user-desc mt10 muted-2-color em09/)
assert.match(userRenderer, /user-btns mt20/)
assert.match(userRenderer, /aksrt_get_user_badges\(\$user_id\)/)
assert.match(userRenderer, /'follow' => true,/)
// 用户个人信息小工具不显示文章模块,显示签到徽章。
assert.match(userRenderer, /'show_posts' => false,/)
assert.match(userRenderer, /'show_checkin' => true,/)
assert.match(userRenderer, /aksrt_header_user_checkin_btn\(\$user_id, true, 'img-badge jb-yellow'\)/)
// 文章作者信息小工具显示文章模块。
assert.match(userRenderer, /'show_posts' => !isset\(\$i\['show_posts'\]\) \|\| !empty\(\$i\['show_posts'\]\),/)
assert.match(userRenderer, /aksrt_render_user_card_posts\(/)
// 未登录卡片与 legacy zib_get_user_singin_page_box 同构。
assert.match(userRenderer, /signin-loader but jb-blue padding-lg/)
assert.match(userRenderer, /signup-loader ml10 but jb-yellow padding-lg/)
assert.match(userRenderer, /loged_title/)
// 旧的自造卡片样式已清理。
assert.doesNotMatch(widgetCss, /aksrt-user-card/)
assert.doesNotMatch(userRenderer, /aksrt-widget-button is-ghost/)
})