feat(admin): rebuild page display settings

This commit is contained in:
2026-08-19 00:07:13 +08:00
parent 09b1e8fd35
commit 480e2bf07e
117 changed files with 1815 additions and 9081 deletions
@@ -1,113 +0,0 @@
<?php
/**
* Home Module · Banner — v4.0 Zibll-Inspired
*
* 内联轮播条,使用与 header-slider 相同的 Swiper 架构,
* 但以圆角卡片形式嵌入内容流中(非全宽)。
*
* 支持配置:
* - 自动播放
* - 圆角卡片样式
* - 标题/副标题叠加
* - 点击跳转
*
* @package AKSRT
* @version 4.0.0
*/
defined('ABSPATH') || exit;
if (class_exists('\AKSRT\Settings\Settings') && !\AKSRT\Settings\Settings::homepage_module_banner_enabled()) {
return;
}
$banners = function_exists('aksrt_get_banners') ? aksrt_get_banners(8) : [];
if (empty($banners)) {
if (current_user_can('manage_options')) {
echo '<div class="home-module home-module--banner home-module--banner-empty box-body">';
echo '<p class="muted-2-color">' . esc_html__('No Banner slides found. Add banner slides in admin.', 'aksrt') . '</p>';
if (function_exists('aksrt_render_admin_quick_link')) {
echo '<div class="home-module__admin-tools">';
aksrt_render_admin_quick_link('page-display/banner', __('Banner 轮播管理', 'aksrt'));
echo '</div>';
}
echo '</div>';
}
return;
}
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$slide_count = count($banners);
// Swiper config — Banner uses a subset of the global slider config
$slider_cfg = function_exists('aksrt_get_slider_config') ? aksrt_get_slider_config() : [];
$swiper_config = [
'effect' => 'slide', // Banner always uses slide effect
'loop' => $slide_count > 1 && ($slider_cfg['loop'] ?? true),
'speed' => 500,
'autoplay' => $slide_count > 1 ? ['delay' => ($slider_cfg['autoplay_interval'] ?? 5000), 'disableOnInteraction' => false] : false,
'pagination' => false,
'navigation' => !empty($slider_cfg['navigation']),
'keyboard' => ['enabled' => true],
'spaceBetween' => ($slider_cfg['space_between'] ?? 0),
];
?>
<div class="home-module home-module--banner slide-widget mb20">
<div class="banner-module zib-slider">
<div class="swiper new-swiper banner-module__swiper slide-widget-swiper" data-swiper-config="<?php echo esc_attr(wp_json_encode($swiper_config)); ?>">
<div class="swiper-wrapper">
<?php foreach ($banners as $index => $slide) :
$device_vis = $slide['device_visibility'] ?? 'all';
$slide_classes = 'swiper-slide';
if ($device_vis === 'pc') {
$slide_classes .= ' banner-slide--pc-only';
} elseif ($device_vis === 'mobile') {
$slide_classes .= ' banner-slide--mobile-only';
}
$link_target = !empty($slide['link_target']) ? $slide['link_target'] : '_self';
?>
<div class="<?php echo esc_attr($slide_classes); ?>"<?php if (!empty($slide['link'])) : ?> data-link="<?php echo esc_url($slide['link']); ?>" data-target="<?php echo esc_attr($link_target); ?>"<?php endif; ?>>
<div class="banner-module__slide-bg">
<?php if (!empty($slide['image'])) : ?>
<img<?php echo aksrt_get_lazy_attr('lazy_sider', (string) $slide['image'], 'fit-cover', aksrt_get_lazy_thumb('lg')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
alt="<?php echo esc_attr($slide['title'] ?: get_bloginfo('name')); ?>"
decoding="async"
>
<?php else : ?>
<div class="banner-module__slide-fallback"></div>
<?php endif; ?>
</div>
<div class="banner-module__slide-overlay"></div>
<?php
$show_text = !isset($slide['show_text']) || $slide['show_text'];
$has_title = !empty($slide['title']);
$has_subtitle = !empty($slide['subtitle']);
?>
<?php if ($show_text && ($has_title || $has_subtitle)) : ?>
<div class="banner-module__content">
<?php if ($has_title) : ?>
<h3 class="banner-module__title"><?php echo esc_html($slide['title']); ?></h3>
<?php endif; ?>
<?php if ($has_subtitle) : ?>
<p class="banner-module__subtitle"><?php echo esc_html($slide['subtitle']); ?></p>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php if ($slide_count > 1) : ?>
<!-- 前后箭头 -->
<div class="banner-module__prev swiper-button-prev"></div>
<div class="banner-module__next swiper-button-next"></div>
<?php endif; ?>
</div>
</div>
<?php if (function_exists('aksrt_render_admin_quick_link')) : ?>
<div class="home-module__admin-tools">
<?php aksrt_render_admin_quick_link('page-display/banner', __('Banner 轮播管理', 'aksrt')); ?>
</div>
<?php endif; ?>
</div>
@@ -1,41 +0,0 @@
<?php
/**
* Home Module · Button Group
*
* config: { title?, buttons: [{ label, url, style?, icon? }], align? }
*
* 按钮组模块,用于 CTA 引导。
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$title = (string) ($cfg['title'] ?? '');
$buttons = isset($cfg['buttons']) && is_array($cfg['buttons']) ? $cfg['buttons'] : [];
$align = sanitize_key((string) ($cfg['align'] ?? 'center'));
if (empty($buttons)) return;
?>
<section class="home-module home-module--button-group container" style="text-align:<?php echo esc_attr($align); ?>">
<?php if ($title !== '') : ?>
<h2 class="home-module__title"><?php echo esc_html($title); ?></h2>
<?php endif; ?>
<div class="home-module__buttons">
<?php foreach ($buttons as $btn) :
$label = (string) ($btn['label'] ?? '');
$url = (string) ($btn['url'] ?? '#');
$style = sanitize_key((string) ($btn['style'] ?? 'primary'));
$icon = (string) ($btn['icon'] ?? '');
if ($label === '') continue;
?>
<a href="<?php echo esc_url($url); ?>" class="aksrt-btn aksrt-btn--<?php echo esc_attr($style); ?>">
<?php if ($icon !== '') : ?>
<i class="<?php echo esc_attr($icon); ?>" aria-hidden="true"></i>
<?php endif; ?>
<span><?php echo esc_html($label); ?></span>
</a>
<?php endforeach; ?>
</div>
</section>
@@ -1,65 +0,0 @@
<?php
/**
* Home Module · Category
*
* config: { cat_id, count, layout, columns, title? }
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$cat_id = isset($cfg['cat_id']) ? (int) $cfg['cat_id'] : 0;
$count = max(1, min(20, (int) ($cfg['count'] ?? 8)));
$layout = (string) ($cfg['layout'] ?? 'card');
$columns = max(1, min(6, (int) ($cfg['columns'] ?? 4)));
$title = (string) ($cfg['title'] ?? '');
if (!in_array($layout, ['card', 'list'], true)) {
$layout = 'card';
}
$args = [
'post_type' => 'post',
'posts_per_page' => $count,
'post_status' => 'publish',
'no_found_rows' => true,
];
if ($cat_id > 0) {
$args['cat'] = $cat_id;
}
$query = new WP_Query($args);
if (!$query->have_posts()) {
wp_reset_postdata();
return;
}
$display_title = $title;
if ($display_title === '' && $cat_id > 0) {
$term = get_term($cat_id, 'category');
if ($term && !is_wp_error($term)) $display_title = $term->name;
}
$more_link = $cat_id > 0 ? get_category_link($cat_id) : '';
?>
<section class="home-module home-module--category container" aria-label="<?php echo esc_attr($display_title ?: __('分类', 'aksrt')); ?>">
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($display_title ?: __('精选内容', 'aksrt')); ?></h2>
<?php if ($more_link) : ?>
<a class="home-module__more" href="<?php echo esc_url($more_link); ?>">
<?php esc_html_e('查看更多', 'aksrt'); ?> <i class="fa fas fa-arrow-right" aria-hidden="true"></i>
</a>
<?php endif; ?>
</header>
<div class="posts-row ajaxpager">
<?php
aksrt_render_post_list([
'type' => $layout === 'card' ? 'card' : 'list',
'is_home' => true,
'skip_wrapper' => true,
], $query);
?>
</div>
</section>
@@ -1,115 +0,0 @@
<?php
/**
* Home Module · Category Recommend Cards
*
* 4 张分类卡片 2×2 布局,每张卡片包含:
* 上部:封面图 + 浏览量胶囊 + 分类名/描述 + 文章数 + 更多链接
* 下部:5 篇最新文章列表(标题 + 相对发布时间)
*
* config: { title?, cat_ids[], show_views?, show_dates? }
*
* 封面图来源:分类 term meta → 最新文章缩略图 → 占位图
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$module_title = (string) ($cfg['title'] ?? __('分类推荐', 'aksrt'));
$cat_ids = array_map('absint', (array) ($cfg['cat_ids'] ?? []));
$cat_ids = array_slice(array_filter($cat_ids, fn($id) => $id > 0), 0, 4);
$show_views = !isset($cfg['show_views']) || !empty($cfg['show_views']);
$show_dates = !isset($cfg['show_dates']) || !empty($cfg['show_dates']);
if (empty($cat_ids)) return;
$placeholder = aksrt_get_lazy_thumb('lg');
// 组装卡片数据
$cards = [];
foreach ($cat_ids as $idx => $cat_id) {
$term = get_term($cat_id, 'category');
if (!$term || is_wp_error($term)) continue;
// 封面图
$cover_url = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_category_cover_url($cat_id)
: $placeholder;
// 分类描述(优先 term meta 推荐描述,其次原生 description)
$desc = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_category_recommend_desc($cat_id)
: (string) $term->description;
// 5 篇最新文章
$posts = get_posts([
'category__in' => [$cat_id],
'posts_per_page' => 5,
'post_status' => 'publish',
'orderby' => 'date',
'order' => 'DESC',
'no_found_rows' => true,
]);
// 浏览量
$views = $show_views ? aksrt_get_category_total_views($cat_id) : 0;
$cards[] = [
'term' => $term,
'cover' => $cover_url,
'desc' => $desc,
'posts' => $posts,
'views' => $views,
'count' => (int) $term->count,
'link' => get_category_link($cat_id),
];
}
if (empty($cards)) return;
?>
<section class="home-module home-module--cat-cards" aria-label="<?php echo esc_attr($module_title); ?>">
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($module_title); ?></h2>
</header>
<div class="cat-cards-grid">
<?php foreach ($cards as $card) : ?>
<div class="cat-card">
<div class="cat-card__top">
<div class="cat-card__banner">
<?php if ($show_views && $card['views'] > 0) : ?>
<span class="cat-card__views"><?php echo esc_html(function_exists('aksrt_format_number') ? aksrt_format_number($card['views']) : number_format_i18n($card['views'])); ?></span>
<?php endif; ?>
<img<?php echo aksrt_get_lazy_attr('lazy_cover', (string) $card['cover'], 'cat-card__cover', $placeholder); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="<?php echo esc_attr($card['term']->name); ?>">
</div>
<div class="cat-card__info">
<div>
<div class="cat-card__name"><?php echo esc_html($card['term']->name); ?></div>
<?php if ($card['desc'] !== '') : ?>
<div class="cat-card__desc"><?php echo esc_html($card['desc']); ?></div>
<?php endif; ?>
</div>
<div class="cat-card__foot">
<span><?php printf(esc_html__('%d篇文章', 'aksrt'), $card['count']); ?></span>
<a href="<?php echo esc_url($card['link']); ?>" class="cat-card__more"><?php esc_html_e('>更多文章', 'aksrt'); ?></a>
</div>
</div>
</div>
<?php if (!empty($card['posts'])) : ?>
<ul class="cat-card__list">
<?php foreach ($card['posts'] as $p) : ?>
<li>
<a href="<?php echo esc_url(get_permalink($p->ID)); ?>"><?php echo esc_html(get_the_title($p->ID)); ?></a>
<?php if ($show_dates) : ?>
<span class="cat-card__time"><?php echo esc_html(aksrt_time_ago($p->post_date)); ?></span>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</section>
@@ -1,43 +0,0 @@
<?php
/**
* Home Module · Data Grid
*
* config: { title?, items: [{ value, label, icon? }], columns? }
*
* 数据展示网格,用于统计数据、里程碑等。
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$title = (string) ($cfg['title'] ?? '');
$items = isset($cfg['items']) && is_array($cfg['items']) ? $cfg['items'] : [];
$columns = max(2, min(6, (int) ($cfg['columns'] ?? 4)));
if (empty($items)) return;
?>
<section class="home-module home-module--data-grid container">
<?php if ($title !== '') : ?>
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($title); ?></h2>
</header>
<?php endif; ?>
<div class="home-module__data-grid" style="grid-template-columns:repeat(<?php echo esc_attr((string) $columns); ?>, 1fr)">
<?php foreach ($items as $item) :
$value = (string) ($item['value'] ?? '');
$label = (string) ($item['label'] ?? '');
$icon = (string) ($item['icon'] ?? '');
if ($value === '' && $label === '') continue;
?>
<div class="aksrt-data-cell">
<?php if ($icon !== '') : ?>
<i class="<?php echo esc_attr($icon); ?>" aria-hidden="true"></i>
<?php endif; ?>
<span class="aksrt-data-cell__value"><?php echo esc_html($value); ?></span>
<span class="aksrt-data-cell__label"><?php echo esc_html($label); ?></span>
</div>
<?php endforeach; ?>
</div>
</section>
@@ -1,39 +0,0 @@
<?php
/**
* Home Module · FAQ
*
* config: { title?, items: [{ q, a }], columns? }
*
* FAQ 手风琴模块。
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$title = (string) ($cfg['title'] ?? '');
$items = isset($cfg['items']) && is_array($cfg['items']) ? $cfg['items'] : [];
$columns = max(1, min(3, (int) ($cfg['columns'] ?? 1)));
if (empty($items)) return;
?>
<section class="home-module home-module--faq container">
<?php if ($title !== '') : ?>
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($title); ?></h2>
</header>
<?php endif; ?>
<div class="home-module__faq-grid" style="column-count:<?php echo esc_attr((string) $columns); ?>">
<?php foreach ($items as $i => $item) :
$q = (string) ($item['q'] ?? '');
$a = (string) ($item['a'] ?? '');
if ($q === '') continue;
?>
<details class="aksrt-faq-item">
<summary class="aksrt-faq-item__question"><?php echo esc_html($q); ?></summary>
<div class="aksrt-faq-item__answer"><?php echo wp_kses_post($a); ?></div>
</details>
<?php endforeach; ?>
</div>
</section>
@@ -1,47 +0,0 @@
<?php
/**
* Home Module · Friend Links
*
* config: { title? }
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$title = (string) ($cfg['title'] ?? '');
$friend_links = aksrt_get_friend_links();
if (!is_array($friend_links) || empty($friend_links)) return;
if (empty($friend_links)) return;
?>
<section class="home-module home-module--friends container" aria-label="<?php echo esc_attr($title ?: __('友情链接', 'aksrt')); ?>">
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($title ?: __('友情链接', 'aksrt')); ?></h2>
</header>
<ul class="home-module__friends-list">
<?php foreach ($friend_links as $link) :
$url = $link['url'] ?? '';
$name = $link['name'] ?? '';
$description = $link['description'] ?? '';
$logo = $link['logo'] ?? '';
if (!$url || !$name) continue;
?>
<li>
<a href="<?php echo esc_url($url); ?>" target="_blank" rel="noopener noreferrer external" class="home-module__friend-card">
<?php if ($logo) : ?>
<img<?php echo aksrt_get_lazy_attr('lazy_other', (string) $logo, 'home-module__friend-avatar'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="">
<?php endif; ?>
<div class="home-module__friend-meta">
<strong><?php echo esc_html($name); ?></strong>
<?php if ($description) : ?>
<span><?php echo esc_html($description); ?></span>
<?php endif; ?>
</div>
</a>
</li>
<?php endforeach; ?>
</ul>
</section>
@@ -1,52 +0,0 @@
<?php
/**
* Home Module · Highlight Cards
*
* config: { title?, items: [{ title, desc, icon?, image?, url? }], columns? }
*
* 亮点卡片模块,用于产品特性、服务介绍等。
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$title = (string) ($cfg['title'] ?? '');
$items = isset($cfg['items']) && is_array($cfg['items']) ? $cfg['items'] : [];
$columns = max(1, min(4, (int) ($cfg['columns'] ?? 3)));
if (empty($items)) return;
?>
<section class="home-module home-module--highlight-cards container">
<?php if ($title !== '') : ?>
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($title); ?></h2>
</header>
<?php endif; ?>
<div class="home-module__highlight-grid" style="grid-template-columns:repeat(<?php echo esc_attr((string) $columns); ?>, 1fr)">
<?php foreach ($items as $item) :
$item_title = (string) ($item['title'] ?? '');
$item_desc = (string) ($item['desc'] ?? '');
$item_icon = (string) ($item['icon'] ?? '');
$item_image = (string) ($item['image'] ?? '');
$item_url = (string) ($item['url'] ?? '');
if ($item_title === '') continue;
$card_tag = $item_url !== '' ? 'a' : 'div';
$card_attrs = $item_url !== '' ? ' href="' . esc_url($item_url) . '"' : '';
?>
<<?php echo esc_attr($card_tag); ?> class="aksrt-highlight-card"<?php echo $card_attrs; ?>>
<?php if ($item_image !== '') : ?>
<img<?php echo aksrt_get_lazy_attr('lazy_other', (string) $item_image, 'aksrt-highlight-card__image'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="">
<?php elseif ($item_icon !== '') : ?>
<div class="aksrt-highlight-card__icon"><i class="<?php echo esc_attr($item_icon); ?>"></i></div>
<?php endif; ?>
<h3 class="aksrt-highlight-card__title"><?php echo esc_html($item_title); ?></h3>
<?php if ($item_desc !== '') : ?>
<p class="aksrt-highlight-card__desc"><?php echo esc_html($item_desc); ?></p>
<?php endif; ?>
</<?php echo esc_attr($card_tag); ?>>
<?php endforeach; ?>
</div>
</section>
@@ -1,101 +0,0 @@
<?php
/**
* Home Module · Hotspot 热点推荐
*
* 纯文章列表(无图片),仅桌面端显示。
* config: { count, source, title? }
*
* source:
* 'auto' → 优先读取 aksrt_recommend meta,不足时用最新补充
* 'recent' → 按时间倒序
* 'views' → 按阅读量排序
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) {
$cfg = [];
}
$count = max(3, min(20, (int) ($cfg['count'] ?? 6)));
$source = (string) ($cfg['source'] ?? 'auto');
$title = (string) ($cfg['title'] ?? '');
if (!in_array($source, ['auto', 'recent', 'views'], true)) {
$source = 'auto';
}
$args = [
'post_type' => 'post',
'posts_per_page' => $count,
'post_status' => 'publish',
'no_found_rows' => true,
];
if ($source === 'auto') {
$args['meta_query'] = [
'relation' => 'OR',
['key' => 'aksrt_recommend', 'value' => '1', 'compare' => '='],
['key' => 'aksrt_recommend', 'compare' => 'NOT EXISTS'],
];
$args['orderby'] = 'modified';
$args['order'] = 'DESC';
} elseif ($source === 'views') {
$args['meta_key'] = '_aksrt_res_view_count';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
} else {
$args['orderby'] = 'date';
$args['order'] = 'DESC';
}
$query = new WP_Query($args);
if (!$query->have_posts()) {
wp_reset_postdata();
return;
}
$display_title = $title ?: __('热点推荐', 'aksrt');
?>
<section class="home-module home-module--hotspot hotspot--desktop-only" aria-label="<?php echo esc_attr($display_title); ?>">
<header class="home-module__header">
<h2 class="home-module__title">
<i class="fa fas fa-fire" aria-hidden="true"></i>
<?php echo esc_html($display_title); ?>
</h2>
</header>
<ol class="hotspot__list">
<?php
$rank = 1;
while ($query->have_posts()) :
$query->the_post();
$cats = get_the_category();
$cat_name = $cats ? $cats[0]->name : '';
$views = (int) get_post_meta(get_the_ID(), '_aksrt_res_view_count', true);
?>
<li class="hotspot__item">
<span class="hotspot__num hotspot__num--<?php echo $rank <= 3 ? 'top' : 'normal'; ?>"><?php echo (int) $rank; ?></span>
<div class="hotspot__body">
<a class="hotspot__title" href="<?php the_permalink(); ?>" title="<?php the_title_attribute(); ?>">
<?php the_title(); ?>
</a>
<div class="hotspot__meta">
<?php if ($cat_name) : ?>
<span class="hotspot__cat"><?php echo esc_html($cat_name); ?></span>
<?php endif; ?>
<span class="hotspot__date"><?php echo esc_html(get_the_date('Y-m-d')); ?></span>
<?php if ($views > 0) : ?>
<span class="hotspot__views"><?php echo esc_html(number_format_i18n($views)); ?> <?php esc_html_e('阅读', 'aksrt'); ?></span>
<?php endif; ?>
</div>
</div>
</li>
<?php
$rank++;
endwhile;
wp_reset_postdata();
?>
</ol>
</section>
@@ -1,29 +0,0 @@
<?php
/**
* Home Module · HTML
*
* config: { html, title? }
*
* 已通过 wp_kses_post 在写入时清洗,渲染时直接输出。
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$html = (string) ($cfg['html'] ?? '');
$title = (string) ($cfg['title'] ?? '');
if ($html === '') return;
?>
<section class="home-module home-module--html container">
<?php if ($title !== '') : ?>
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($title); ?></h2>
</header>
<?php endif; ?>
<div class="home-module__html-body">
<?php echo wp_kses_post($html); ?>
</div>
</section>
@@ -1,103 +0,0 @@
<?php
/**
* Home Module · More (Discover Categories)
*
* Maps to zibll widget-more capability.
* config: { title?, cat_ids[], count, columns, show_post_count, show_latest_posts, latest_count }
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) {
$cfg = [];
}
$title = trim((string) ($cfg['title'] ?? ''));
$cat_ids = isset($cfg['cat_ids']) && is_array($cfg['cat_ids']) ? array_map('absint', $cfg['cat_ids']) : [];
$count = max(1, min(12, (int) ($cfg['count'] ?? 6)));
$columns = max(2, min(4, (int) ($cfg['columns'] ?? 3)));
$show_post_count = !isset($cfg['show_post_count']) || !empty($cfg['show_post_count']);
$show_latest_posts = !isset($cfg['show_latest_posts']) || !empty($cfg['show_latest_posts']);
$latest_count = max(1, min(10, (int) ($cfg['latest_count'] ?? 5)));
// Fetch categories.
if (!empty($cat_ids)) {
$categories = get_categories([
'include' => array_slice($cat_ids, 0, $count),
'orderby' => 'include',
]);
} else {
$categories = get_categories([
'number' => $count,
'orderby' => 'count',
'order' => 'DESC',
'hide_empty' => true,
]);
}
if (empty($categories)) {
return;
}
?>
<section class="home-module home-module--more container" aria-label="<?php echo esc_attr($title ?: __('发现更多', 'aksrt')); ?>">
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($title ?: __('发现更多', 'aksrt')); ?></h2>
</header>
<div class="home-module__more-grid" style="--cols: <?php echo (int) $columns; ?>;">
<?php foreach ($categories as $cat) :
$cat_link = get_category_link($cat);
$cover = '';
if (function_exists('aksrt_get_category_cover')) {
$cover = aksrt_get_category_cover($cat->term_id);
}
?>
<div class="home-module__more-card">
<a href="<?php echo esc_url($cat_link); ?>" class="home-module__more-link">
<?php if ($cover) : ?>
<div class="home-module__more-cover">
<img<?php echo aksrt_get_lazy_attr('lazy_other', (string) $cover); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="">
</div>
<?php else : ?>
<div class="home-module__more-cover home-module__more-cover--placeholder">
<i class="fa fas fa-folder" aria-hidden="true"></i>
</div>
<?php endif; ?>
<div class="home-module__more-body">
<strong class="home-module__more-name"><?php echo esc_html($cat->name); ?></strong>
<?php if ($show_post_count) : ?>
<span class="home-module__more-count">
<?php
/* translators: %d: number of posts */
printf(esc_html__('%d 篇文章', 'aksrt'), (int) $cat->count);
?>
</span>
<?php endif; ?>
</div>
</a>
<?php if ($show_latest_posts) :
$recent = get_posts([
'category' => $cat->term_id,
'posts_per_page' => $latest_count,
'post_status' => 'publish',
'orderby' => 'date',
'order' => 'DESC',
]);
if (!empty($recent)) :
?>
<ul class="home-module__more-posts">
<?php foreach ($recent as $rp) : ?>
<li>
<a href="<?php echo esc_url(get_permalink($rp)); ?>"><?php echo esc_html($rp->post_title); ?></a>
</li>
<?php endforeach; ?>
</ul>
<?php
endif;
endif;
?>
</div>
<?php endforeach; ?>
</div>
</section>
@@ -1,54 +0,0 @@
<?php
/**
* Home Module · Partner Marquee
*
* config: { title?, items: [{ name, image, url? }], speed? }
*
* 合作伙伴横向滚动模块。
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$title = (string) ($cfg['title'] ?? '');
$items = isset($cfg['items']) && is_array($cfg['items']) ? $cfg['items'] : [];
$speed = max(10, min(120, (int) ($cfg['speed'] ?? 30)));
if (empty($items)) return;
?>
<section class="home-module home-module--partner-marquee container">
<?php if ($title !== '') : ?>
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($title); ?></h2>
</header>
<?php endif; ?>
<div class="aksrt-marquee" style="--marquee-duration:<?php echo esc_attr((string) $speed); ?>s">
<div class="aksrt-marquee__track">
<?php
// 渲染两份以实现无缝循环
for ($pass = 0; $pass < 2; $pass++) :
foreach ($items as $partner) :
$name = (string) ($partner['name'] ?? '');
$image = (string) ($partner['image'] ?? '');
$url = (string) ($partner['url'] ?? '');
if ($image === '' && $name === '') continue;
$tag = $url !== '' ? 'a' : 'span';
$attrs = $url !== '' ? ' href="' . esc_url($url) . '" target="_blank" rel="noopener"' : '';
?>
<<?php echo esc_attr($tag); ?> class="aksrt-marquee__item"<?php echo $attrs; ?>>
<?php if ($image !== '') : ?>
<img<?php echo aksrt_get_lazy_attr('lazy_other', (string) $image); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="<?php echo esc_attr($name); ?>">
<?php else : ?>
<span><?php echo esc_html($name); ?></span>
<?php endif; ?>
</<?php echo esc_attr($tag); ?>>
<?php
endforeach;
endfor;
?>
</div>
</div>
</section>
@@ -1,54 +0,0 @@
<?php
/**
* Home Module · Product Showcase
*
* config: { title?, products: [{ title, desc, image, price?, url? }], columns? }
*
* 产品展示模块,用于商城/资源推荐。
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$title = (string) ($cfg['title'] ?? '');
$products = isset($cfg['products']) && is_array($cfg['products']) ? $cfg['products'] : [];
$columns = max(1, min(4, (int) ($cfg['columns'] ?? 3)));
if (empty($products)) return;
?>
<section class="home-module home-module--product-showcase container">
<?php if ($title !== '') : ?>
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($title); ?></h2>
</header>
<?php endif; ?>
<div class="home-module__product-grid" style="grid-template-columns:repeat(<?php echo esc_attr((string) $columns); ?>, 1fr)">
<?php foreach ($products as $product) :
$p_title = (string) ($product['title'] ?? '');
$p_desc = (string) ($product['desc'] ?? '');
$p_image = (string) ($product['image'] ?? '');
$p_price = (string) ($product['price'] ?? '');
$p_url = (string) ($product['url'] ?? '');
if ($p_title === '') continue;
?>
<a href="<?php echo esc_url($p_url ?: '#'); ?>" class="aksrt-product-card">
<?php if ($p_image !== '') : ?>
<div class="aksrt-product-card__image">
<img<?php echo aksrt_get_lazy_attr('lazy_other', (string) $p_image); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="<?php echo esc_attr($p_title); ?>">
</div>
<?php endif; ?>
<div class="aksrt-product-card__body">
<h3 class="aksrt-product-card__title"><?php echo esc_html($p_title); ?></h3>
<?php if ($p_desc !== '') : ?>
<p class="aksrt-product-card__desc"><?php echo esc_html($p_desc); ?></p>
<?php endif; ?>
<?php if ($p_price !== '') : ?>
<span class="aksrt-product-card__price"><?php echo esc_html($p_price); ?></span>
<?php endif; ?>
</div>
</a>
<?php endforeach; ?>
</div>
</section>
@@ -1,98 +0,0 @@
<?php
/**
* Home Module · Ranking
*
* config: { metric: 'views|downloads|comments', count, title? }
* Lists ordinary posts only (no content-type split).
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) {
$cfg = [];
}
$metric = (string) ($cfg['metric'] ?? 'downloads');
$count = max(1, min(20, (int) ($cfg['count'] ?? 6)));
$title = (string) ($cfg['title'] ?? '');
if (!in_array($metric, ['views', 'downloads', 'comments'], true)) {
$metric = 'downloads';
}
$args = [
'post_type' => 'post',
'posts_per_page' => $count,
'post_status' => 'publish',
'no_found_rows' => true,
];
switch ($metric) {
case 'views':
$args['meta_key'] = '_aksrt_res_view_count';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
case 'comments':
$args['orderby'] = 'comment_count';
$args['order'] = 'DESC';
break;
case 'downloads':
default:
$args['meta_key'] = '_aksrt_res_download_count';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
}
$query = new WP_Query($args);
if (!$query->have_posts()) {
wp_reset_postdata();
return;
}
$metric_label = [
'views' => __('阅读量', 'aksrt'),
'downloads' => __('下载量', 'aksrt'),
'comments' => __('评论数', 'aksrt'),
][$metric];
$display_title = $title ?: sprintf(__('%s 排行榜', 'aksrt'), $metric_label);
?>
<section class="home-module home-module--ranking container" aria-label="<?php echo esc_attr($display_title); ?>">
<header class="home-module__header">
<h2 class="home-module__title">
<i class="fa fas fa-trophy" aria-hidden="true"></i>
<?php echo esc_html($display_title); ?>
</h2>
</header>
<ol class="home-module__ranking-list">
<?php
$rank = 1;
while ($query->have_posts()) :
$query->the_post();
$value = 0;
if ($metric === 'views') {
$value = (int) get_post_meta(get_the_ID(), '_aksrt_res_view_count', true);
} elseif ($metric === 'comments') {
$value = (int) get_comments_number();
} else {
$value = (int) get_post_meta(get_the_ID(), '_aksrt_res_download_count', true);
}
?>
<li class="home-module__ranking-item home-module__ranking-item--<?php echo $rank <= 3 ? 'top' : 'normal'; ?>">
<span class="home-module__ranking-num"><?php echo (int) $rank; ?></span>
<a class="home-module__ranking-link" href="<?php the_permalink(); ?>">
<?php the_title(); ?>
</a>
<span class="home-module__ranking-value"><?php echo esc_html(number_format_i18n($value)); ?></span>
</li>
<?php
$rank++;
endwhile;
wp_reset_postdata();
?>
</ol>
</section>
@@ -1,63 +0,0 @@
<?php
/**
* Home Module · Recent
*
* config: { count, layout, title? }
* Lists ordinary posts only (no content-type split).
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) {
$cfg = [];
}
$count = max(1, min(20, (int) ($cfg['count'] ?? 6)));
$layout = (string) ($cfg['layout'] ?? 'card');
$title = (string) ($cfg['title'] ?? '');
if (!in_array($layout, ['card', 'list'], true)) {
$layout = 'card';
}
$args = [
'post_type' => 'post',
'posts_per_page' => $count,
'post_status' => 'publish',
'no_found_rows' => true,
'orderby' => 'date',
'order' => 'DESC',
];
$query = new WP_Query($args);
if (!$query->have_posts()) {
wp_reset_postdata();
return;
}
$display_title = $title ?: __('最新文章', 'aksrt');
$archive_link = get_permalink(get_option('page_for_posts'));
?>
<section class="home-module home-module--recent container" aria-label="<?php echo esc_attr($display_title); ?>">
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($display_title); ?></h2>
<?php if ($archive_link) : ?>
<a class="home-module__more" href="<?php echo esc_url($archive_link); ?>">
<?php esc_html_e('查看更多', 'aksrt'); ?> <i class="fa fas fa-arrow-right" aria-hidden="true"></i>
</a>
<?php endif; ?>
</header>
<div class="posts-row ajaxpager">
<?php
aksrt_render_post_list([
'type' => $layout === 'card' ? 'card' : 'list',
'is_home' => true,
'skip_wrapper' => true,
], $query);
?>
</div>
</section>
<?php
wp_reset_postdata();
@@ -1,17 +0,0 @@
<?php
/**
* Home Module · Spacing
*
* config: { height? }
*
* 间距模块,用于模块间留白。
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$height = max(8, min(200, (int) ($cfg['height'] ?? 40)));
?>
<div class="home-module--spacing" style="height:<?php echo esc_attr((string) $height); ?>px" aria-hidden="true"></div>
@@ -1,101 +0,0 @@
<?php
/**
* Home Module · Tabs
*
* config: { cat_ids: int[], count, layout, title? }
*
* P6 阶段先实现"按 Tab 标题分组渲染",Tab 切换交互在 P9 阶段引入 JS。
* 默认行为:渲染选中分类(cat_ids[0])的卡片,其它 Tab 用 data-* 标记,
* 配合 cms-home-modules.js 切换 active。
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$cat_ids = isset($cfg['cat_ids']) && is_array($cfg['cat_ids']) ? array_map('intval', $cfg['cat_ids']) : [];
$cat_ids = array_filter($cat_ids, fn($x) => $x > 0);
$count = max(1, min(20, (int) ($cfg['count'] ?? 8)));
$layout = (string) ($cfg['layout'] ?? 'card');
$title = (string) ($cfg['title'] ?? '');
if (!in_array($layout, ['card', 'list'], true)) {
$layout = 'card';
}
if (empty($cat_ids)) {
// 没有指定 → 取站点最热的两个分类
$top_cats = get_terms([
'taxonomy' => 'category',
'orderby' => 'count',
'order' => 'DESC',
'number' => 4,
'hide_empty' => true,
'fields' => 'ids',
]);
if (is_wp_error($top_cats)) $top_cats = [];
$cat_ids = array_map('intval', $top_cats);
}
if (empty($cat_ids)) return;
$tabs = [];
foreach ($cat_ids as $cid) {
$term = get_term($cid, 'category');
if (!$term || is_wp_error($term)) continue;
$tabs[] = [
'id' => $cid,
'name' => $term->name,
'link' => get_category_link($cid),
];
}
if (empty($tabs)) return;
$active_id = $tabs[0]['id'];
?>
<section class="home-module home-module--tabs container home-tab-content index-tab-main" aria-label="<?php echo esc_attr($title ?: __('分类切换', 'aksrt')); ?>">
<header class="home-module__header home-module__header--tabs">
<h2 class="home-module__title"><?php echo esc_html($title ?: __('热门分类', 'aksrt')); ?></h2>
<nav class="home-module__tabs" role="tablist" data-aksrt-tabs>
<?php foreach ($tabs as $i => $t) : ?>
<button type="button"
role="tab"
class="home-module__tab<?php echo $i === 0 ? ' is-active' : ''; ?>"
aria-selected="<?php echo $i === 0 ? 'true' : 'false'; ?>"
data-tab-target="cat-<?php echo (int) $t['id']; ?>">
<?php echo esc_html($t['name']); ?>
</button>
<?php endforeach; ?>
</nav>
</header>
<?php foreach ($tabs as $i => $t) :
$query = new WP_Query([
'cat' => $t['id'],
'post_type' => 'post',
'posts_per_page' => $count,
'post_status' => 'publish',
'no_found_rows' => true,
]);
?>
<div class="home-module__tab-panel tab-pane<?php echo $i === 0 ? ' is-active' : ''; ?>"
role="tabpanel"
data-tab-id="cat-<?php echo (int) $t['id']; ?>"
<?php echo $i !== 0 ? 'hidden' : ''; ?>>
<?php if ($query->have_posts()) : ?>
<div class="posts-row ajaxpager tab-content">
<?php
aksrt_render_post_list([
'type' => $layout === 'card' ? 'card' : 'list',
'is_home' => true,
'skip_wrapper' => true,
], $query);
?>
</div>
<?php else : ?>
<p class="home-module__empty"><?php esc_html_e('该分类暂无内容', 'aksrt'); ?></p>
<?php endif; ?>
</div>
<?php endforeach; ?>
</section>
@@ -1,40 +0,0 @@
<?php
/**
* Home Module · Tag Cloud
*
* config: { limit, title? }
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$limit = max(5, min(100, (int) ($cfg['limit'] ?? 30)));
$title = (string) ($cfg['title'] ?? '');
$tags = get_terms([
'taxonomy' => 'post_tag',
'orderby' => 'count',
'order' => 'DESC',
'number' => $limit,
'hide_empty' => true,
]);
if (is_wp_error($tags) || empty($tags)) return;
?>
<section class="home-module home-module--tags container" aria-label="<?php echo esc_attr($title ?: __('热门标签', 'aksrt')); ?>">
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($title ?: __('热门标签', 'aksrt')); ?></h2>
</header>
<ul class="home-module__tag-cloud">
<?php foreach ($tags as $tag) : ?>
<li>
<a href="<?php echo esc_url(get_tag_link($tag->term_id)); ?>" class="home-module__tag">
<span class="home-module__tag-name">#<?php echo esc_html($tag->name); ?></span>
<span class="home-module__tag-count"><?php echo (int) $tag->count; ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</section>
@@ -1,32 +0,0 @@
<?php
/**
* Home Module · Text Title
*
* config: { title, subtitle?, align?, tag? }
*
* 纯文本标题模块,用于页面分隔和标题引导。
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) $cfg = [];
$title = (string) ($cfg['title'] ?? '');
$subtitle = (string) ($cfg['subtitle'] ?? '');
$align = sanitize_key((string) ($cfg['align'] ?? 'left'));
$tag = sanitize_key((string) ($cfg['tag'] ?? 'h2'));
if ($title === '' && $subtitle === '') return;
$valid_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
if (!in_array($tag, $valid_tags, true)) $tag = 'h2';
?>
<section class="home-module home-module--text-title container" style="text-align:<?php echo esc_attr($align); ?>">
<?php if ($title !== '') : ?>
<<?php echo esc_attr($tag); ?> class="home-module__title"><?php echo esc_html($title); ?></<?php echo esc_attr($tag); ?>>
<?php endif; ?>
<?php if ($subtitle !== '') : ?>
<p class="home-module__subtitle"><?php echo esc_html($subtitle); ?></p>
<?php endif; ?>
</section>
@@ -1,85 +0,0 @@
<?php
/**
* Home Module · User Cards
*
* Maps to zibll widget-user capability.
* config: { title?, count, columns, order(recent/posts/manual), user_ids[], show_stats }
*/
defined('ABSPATH') || exit;
$cfg = get_query_var('aksrt_module_config', []);
if (!is_array($cfg)) {
$cfg = [];
}
$title = trim((string) ($cfg['title'] ?? ''));
$count = max(1, min(12, (int) ($cfg['count'] ?? 4)));
$columns = max(2, min(6, (int) ($cfg['columns'] ?? 4)));
$order = sanitize_key((string) ($cfg['order'] ?? 'recent'));
$user_ids = isset($cfg['user_ids']) && is_array($cfg['user_ids']) ? array_map('absint', $cfg['user_ids']) : [];
$show_stats = !isset($cfg['show_stats']) || !empty($cfg['show_stats']);
// Build user query.
if ($order === 'manual' && !empty($user_ids)) {
$query_args = [
'include' => array_slice($user_ids, 0, $count),
'orderby' => 'include',
'number' => $count,
];
} elseif ($order === 'posts') {
$query_args = [
'number' => $count,
'orderby' => 'post_count',
'order' => 'DESC',
];
} else {
// 'recent' = newest registrations.
$query_args = [
'number' => $count,
'orderby' => 'registered',
'order' => 'DESC',
];
}
$users = get_users($query_args);
if (empty($users)) {
return;
}
?>
<section class="home-module home-module--users container" aria-label="<?php echo esc_attr($title ?: __('推荐用户', 'aksrt')); ?>">
<header class="home-module__header">
<h2 class="home-module__title"><?php echo esc_html($title ?: __('推荐用户', 'aksrt')); ?></h2>
</header>
<div class="home-module__users-grid" style="--cols: <?php echo (int) $columns; ?>;">
<?php foreach ($users as $user) :
$uid = $user->ID;
$avatar = get_avatar_url($uid, ['size' => 96]);
$display = $user->display_name ?: $user->user_login;
$bio = get_user_meta($uid, 'description', true);
$post_cnt = count_user_posts($uid);
$profile = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($uid)
: get_author_posts_url($uid);
?>
<div class="home-module__user-card">
<a href="<?php echo esc_url($profile); ?>" class="home-module__user-link">
<img<?php echo aksrt_get_lazy_attr('lazy_avatar', (string) $avatar, 'home-module__user-avatar', aksrt_get_lazy_thumb('sm')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="">
<strong class="home-module__user-name"><?php echo esc_html($display); ?></strong>
</a>
<?php if ($bio) : ?>
<p class="home-module__user-bio"><?php echo esc_html(wp_trim_words($bio, 12, '...')); ?></p>
<?php endif; ?>
<?php if ($show_stats) : ?>
<span class="home-module__user-stats">
<span class="home-module__user-stat">
<i class="fa fas fa-file-alt" aria-hidden="true"></i>
<?php echo (int) $post_cnt; ?>
</span>
</span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</section>