feat(categories): align archive and directory pages

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-11-05 23:51:33 +08:00
1 parent 7faa2d53d8
commit fbb53c6890
7 files changed
+443 -315

No files matched your search

+183 -190
View File
@@ -1,224 +1,217 @@
/**
* Jio - Categories Index Route Stylesheet
*
* 分类汇总页面 (templates/pages/page-categories.php) 专用样式:
* 卡片网格、封面图、文章计数徽章、子分类胶囊条、页面顶部统计与底部返回。
*
* Tokens: 颜色 / 间距 / 圆角 / 阴影 全部来自 core/tokens.css
*/
/* ================================================
* 页面头:标题 + 计数
* ================================================ */
.jio-cats-header {
margin-bottom: var(--space-7);
/* Category directory */
.jio-category-directory-nav {
margin-bottom: 20px;
padding: 14px 16px;
}
.jio-cats-header__meta {
margin-top: var(--space-2);
color: var(--color-ink-4);
font-size: 13px;
}
/* ================================================
* 网格容器
* ================================================ */
.jio-cats-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--space-5);
margin-bottom: var(--space-8);
}
/* ================================================
* 卡片
* ================================================ */
.jio-cat-card {
display: flex;
flex-direction: column;
background: var(--surface-page);
border: 1px solid var(--color-line);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-sm);
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.jio-cat-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
border-color: var(--color-primary);
}
/* 封面图 */
.jio-cat-card__cover {
position: relative;
.jio-category-directory-nav__label {
display: block;
aspect-ratio: 16 / 9;
background-color: var(--surface-3);
background-position: center;
background-size: cover;
background-repeat: no-repeat;
text-decoration: none;
}
.jio-cat-card__cover::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
180deg,
rgba(20, 28, 48, 0) 40%,
rgba(20, 28, 48, 0.45) 100%
);
pointer-events: none;
}
/* 计数徽章 */
.jio-cat-card__count-badge {
position: absolute;
top: var(--space-3);
right: var(--space-3);
z-index: 1;
display: inline-flex;
align-items: center;
padding: 4px 10px;
background: var(--color-primary);
color: #fff;
font-size: 12px;
margin-bottom: 8px;
font-weight: 600;
letter-spacing: 0.02em;
border-radius: var(--radius-pill);
box-shadow: var(--shadow-sm);
}
/* 卡片正文 */
.jio-cat-card__body {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding: var(--space-5);
flex: 1;
}
.jio-cat-card__title {
margin: 0;
font-size: 18px;
font-weight: 600;
line-height: 1.4;
}
.jio-cat-card__title a {
color: var(--color-ink);
text-decoration: none;
transition: color 0.15s ease;
}
.jio-cat-card__title a:hover {
color: var(--color-primary);
}
.jio-cat-card__desc {
margin: 0;
color: var(--color-ink-3);
font-family: var(--font-body);
font-size: 14px;
line-height: 1.6;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* ================================================
* 子分类胶囊条
* ================================================ */
.jio-cat-card__children {
.jio-category-directory-nav ul,
.jio-category-directory__children {
margin: 0;
padding: 0;
list-style: none;
}
.jio-category-directory-nav ul {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: auto;
padding-top: var(--space-3);
border-top: 1px dashed var(--color-line);
gap: 6px 14px;
}
.jio-cat-card__children li {
margin: 0;
padding: 0;
}
.jio-cat-card__children a {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
background: var(--surface-2);
border: 1px solid var(--color-line);
border-radius: var(--radius-pill);
color: var(--color-ink-3);
font-size: 12px;
line-height: 1.4;
.jio-category-directory-nav a,
.jio-category-directory__child-main a {
color: var(--key-color, var(--main-color, inherit));
text-decoration: none;
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.jio-cat-card__children a:hover {
background: var(--color-primary-soft);
border-color: var(--color-primary);
color: var(--color-ink);
.jio-category-directory-nav a:hover,
.jio-category-directory__child-main a:hover,
.jio-category-directory__intro h2 a:hover {
color: var(--focus-color, #ff5473);
}
.jio-cat-card__child-count {
.jio-category-directory {
margin-bottom: 20px;
}
.jio-category-directory__section {
margin-bottom: 20px;
padding: 15px;
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));
scroll-margin-top: 90px;
}
.jio-category-directory__summary {
display: flex;
align-items: flex-start;
gap: 15px;
margin-bottom: 14px;
}
.jio-category-directory__cover {
flex: 0 0 180px;
width: 180px;
height: 100px;
overflow: hidden;
border-radius: var(--main-radius, 8px);
background: var(--muted-border-color, #f2f3f5);
}
.jio-category-directory__cover img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform .25s ease;
}
.jio-category-directory__cover:hover img {
transform: scale(1.04);
}
.jio-category-directory__intro {
min-width: 0;
flex: 1;
}
.jio-category-directory__intro h2 {
display: inline;
margin: 0;
font-size: 1.15em;
}
.jio-category-directory__intro h2 a {
color: var(--key-color, var(--main-color, inherit));
text-decoration: none;
}
.jio-category-directory__count {
display: inline-block;
margin-left: 8px;
color: var(--muted-2-color, #999);
font-size: .88em;
white-space: nowrap;
}
.jio-category-directory__intro p,
.jio-category-directory__child-desc {
margin: 6px 0 0;
color: var(--muted-color, #777);
font-size: .92em;
line-height: 1.55;
}
.jio-category-directory__intro p {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.jio-category-directory__more {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
padding: 0 5px;
background: var(--surface-3);
border-radius: var(--radius-pill);
color: var(--color-ink-4);
font-size: 11px;
gap: 5px;
margin-top: 8px;
color: var(--focus-color, #ff5473);
font-size: .9em;
text-decoration: none;
}
.jio-category-directory__children {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
padding-top: 14px;
border-top: 1px solid var(--main-border-color, rgba(127, 127, 127, .13));
}
.jio-category-directory__child {
min-width: 0;
padding: 10px 12px;
border-radius: var(--main-radius, 8px);
background: var(--muted-border-color, rgba(127, 127, 127, .06));
}
.jio-category-directory__child-main {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
font-weight: 600;
}
/* ================================================
* 空状态
* ================================================ */
.jio-cats-empty {
padding: var(--space-9) var(--space-5);
text-align: center;
color: var(--color-ink-3);
background: var(--surface-2);
border: 1px dashed var(--color-line);
border-radius: var(--radius-lg);
.jio-category-directory__child-main a {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ================================================
* 底部返回
* ================================================ */
.jio-cats-footer {
text-align: center;
margin-top: var(--space-8);
.jio-category-directory__child .jio-category-directory__count {
flex: 0 0 auto;
margin-left: 0;
font-weight: 400;
}
/* ================================================
* 响应式
* ================================================ */
@media (max-width: 640px) {
.jio-cats-grid {
.jio-category-directory__child-desc {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.jio-category-directory__child > .jio-category-directory__children {
grid-template-columns: 1fr;
gap: 6px;
margin-top: 10px;
padding-top: 10px;
}
.jio-category-directory__child > .jio-category-directory__children > li {
padding: 7px 9px;
background: var(--main-bg-color, rgba(255, 255, 255, .6));
}
.jio-categories-empty {
padding: 28px 18px;
border: 1px dashed var(--main-border-color, rgba(127, 127, 127, .25));
border-radius: var(--main-radius, 8px);
color: var(--muted-color, #8a8c98);
text-align: center;
}
@media (max-width: 767px) {
.jio-category-directory__section {
padding: 12px;
}
.jio-category-directory__cover {
flex-basis: 110px;
width: 110px;
height: 78px;
}
.jio-category-directory__children {
grid-template-columns: 1fr;
gap: var(--space-4);
}
}
.jio-cat-card__body {
padding: var(--space-4);
@media (max-width: 420px) {
.jio-category-directory__summary {
gap: 10px;
}
.jio-cat-card__title {
font-size: 17px;
.jio-category-directory__cover {
flex-basis: 88px;
width: 88px;
height: 72px;
}
}
+17 -25
View File
@@ -10,24 +10,6 @@ defined('ABSPATH') || exit;
get_header();
$term = get_queried_object();
if (class_exists('\Jio\Core\DocsPresenter')) {
$docs_presenter = \Jio\Core\DocsPresenter::get_instance();
if ($term instanceof WP_Term && $term->taxonomy === 'category') {
$doc_candidates = get_posts([
'post_type' => 'page',
'post_status' => 'publish',
'posts_per_page' => 1,
'meta_key' => '_jio_doc_category',
'meta_value' => (string) $term->term_id,
'fields' => 'ids',
]);
if (!empty($doc_candidates)) {
get_template_part('templates/pages/page-documentnav');
get_footer();
return;
}
}
}
if (!$term instanceof WP_Term) {
get_footer();
return;
@@ -35,7 +17,10 @@ if (!$term instanceof WP_Term) {
$archive_settings = \Jio\Core\TermMeta::resolve_archive_settings($term);
$has_sidebar = jio_archive_has_sidebar(!empty($archive_settings['show_sidebar']));
$ajax_filter = !empty($archive_settings['filter_bar']);
$access = function_exists('jio_get_term_allow_view_data')
? jio_get_term_allow_view_data((int) $term->term_id)
: ['allow' => true, 'not_html' => ''];
$ajax_filter = !empty($access['allow']) && !empty($archive_settings['filter_bar']);
$sort_state = jio_archive_get_sort_state((array) ($archive_settings['orderby_option'] ?? []));
$filter_groups = [];
foreach (['category' => __('分类', 'jio'), 'post_tag' => __('标签', 'jio')] as $taxonomy => $label) {
@@ -66,7 +51,7 @@ jio_archive_render_term_header([
'post_count' => !empty($archive_settings['post_count']),
]);
ob_start();
if ($ajax_filter) {
if (!empty($access['allow']) && $ajax_filter) {
jio_archive_render_filter_bar([
'aria_label' => __('分类筛选', 'jio'), 'groups' => $filter_groups,
'sort_enabled' => !empty($archive_settings['orderby_enabled']),
@@ -76,11 +61,18 @@ if ($ajax_filter) {
]);
}
$filter_html = (string) ob_get_clean();
jio_archive_render_stream(null, jio_archive_get_render_config(['context' => 'category']), [
'page_kind' => 'category', 'query_context' => 'archive',
'empty_text' => __('暂无内容', 'jio'),
'filter_html' => $filter_html,
]);
if (!empty($access['allow'])) {
jio_archive_render_stream(null, jio_archive_get_render_config(['context' => 'category']), [
'page_kind' => 'category', 'query_context' => 'archive',
'empty_text' => __('暂无内容', 'jio'),
'filter_html' => $filter_html,
]);
} else {
echo '<div class="content-stream content-stream--category posts-row ajaxpager"><div class="ajax-item">';
echo function_exists('jio_get_null') ? jio_get_null(__('抱歉!您暂无查看此内容的权限', 'jio'), 20, 'null-cap.svg', '') : '';
echo (string) ($access['not_html'] ?? '');
echo '</div></div>';
}
jio_archive_render_widget_zone(['cat_bottom_content']);
jio_archive_close_shell(['has_sidebar' => $has_sidebar, 'bottom_fluid' => ['cat_bottom_fluid']]);
get_footer();
+9 -1
View File
@@ -31,7 +31,15 @@ if (!function_exists('jio_archive_render_term_header')) :
$description = esc_html(trim(wp_strip_all_tags((string) $args['description'])));
$more = '';
if ($term instanceof WP_Term) {
$more = '<div class="page-cover-more-btns flex ac"><a href="javascript:;" class="main-search-btn" search-type="post" search-trem="' . (int) $term->term_id . '" trem-name="' . esc_attr($term->name) . '" search-placeholder="' . esc_attr(sprintf(__('在%s中搜索', 'jio'), $term->name)) . '" title="' . esc_attr(sprintf(__('在%s中搜索', 'jio'), $term->name)) . '"><i class="fa fas fa-search" aria-hidden="true"></i></a></div>';
$edit_url = !$is_tag && current_user_can('edit_term', (int) $term->term_id)
? get_edit_term_link((int) $term->term_id, $term->taxonomy)
: false;
$edit_label = __('编辑此分类', 'jio');
$edit_button = $edit_url
? '<a class="admin-edit" href="' . esc_url($edit_url) . '" title="' . esc_attr($edit_label) . '"><i class="fa fas fa-edit" aria-hidden="true"></i><span class="screen-reader-text">' . esc_html($edit_label) . '</span></a>'
: '';
$more = '<div class="page-cover-more-btns flex ac">' . $edit_button
. '<a href="javascript:;" class="main-search-btn" search-type="post" search-trem="' . (int) $term->term_id . '" trem-name="' . esc_attr($term->name) . '" search-placeholder="' . esc_attr(sprintf(__('在%s中搜索', 'jio'), $term->name)) . '" title="' . esc_attr(sprintf(__('在%s中搜索', 'jio'), $term->name)) . '"><i class="fa fas fa-search" aria-hidden="true"></i></a></div>';
}
if (empty($args['show_cover'])) {
echo '<div class="jio-panel relative"><h4 class="title-h-left">' . $title . '</h4><div class="muted-2-color">' . $description . '</div>' . $more . '</div>';
+35 -2
View File
@@ -363,12 +363,41 @@ if (!function_exists('jio_get_term_allow_view_rule')) :
}
endif;
if (!function_exists('jio_get_term_allow_view_data')) :
/** Resolve reading permission for a category archive. */
function jio_get_term_allow_view_data(int $term_id): array {
$data = [
'allow' => true,
'type' => '',
'not_html' => '',
'allow_reason' => '',
'html' => '',
];
if ($term_id <= 0 || current_user_can('manage_options')) {
return $data;
}
$rule = jio_get_term_allow_view_rule($term_id);
if (empty($rule['type']) || $rule['type'] === 'public') {
return $data;
}
$link = get_term_link($term_id, 'category');
$redirect_url = is_wp_error($link) ? home_url('/') : (string) $link;
return jio_resolve_allow_view_rule(
(string) $rule['type'],
(array) ($rule['roles'] ?? []),
null,
$redirect_url
);
}
endif;
if (!function_exists('jio_resolve_allow_view_rule')) :
/**
* @param array<string,mixed> $roles Role constraints for type=roles.
* @return array{allow:bool,type:string,not_html:string,allow_reason:string,html:string}
*/
function jio_resolve_allow_view_rule(string $type, array $roles, WP_Post $post): array {
function jio_resolve_allow_view_rule(string $type, array $roles, ?WP_Post $post = null, string $redirect_url = ''): array {
$type = sanitize_key($type);
if (in_array($type, ['login', 'signin'], true)) {
$type = 'signin';
@@ -383,7 +412,11 @@ if (!function_exists('jio_resolve_allow_view_rule')) :
];
$user_id = get_current_user_id();
$login_url = function_exists('jio_get_login_url') ? jio_get_login_url() : wp_login_url(get_permalink($post));
if ($redirect_url !== '') {
$login_url = function_exists('jio_get_login_url') ? jio_get_login_url($redirect_url) : wp_login_url($redirect_url);
} else {
$login_url = function_exists('jio_get_login_url') ? jio_get_login_url() : wp_login_url($post instanceof WP_Post ? get_permalink($post) : '');
}
if ($type === 'signin') {
$data['html'] = '<p class="separator muted-3-color em09">' . esc_html__('登录后可查看', 'jio') . '</p>';
+2 -1
View File
@@ -528,11 +528,12 @@ trait ScriptsPart1b {
// 分类汇总页:路由专属 CSS
if (is_page_template('templates/pages/page-categories.php')) {
$categories_css = get_template_directory() . '/assets/css/routes/categories.css';
wp_enqueue_style(
'jio-route-categories',
$base_uri . '/assets/css/routes/categories.css',
['jio-public'],
$version
file_exists($categories_css) ? (string) filemtime($categories_css) : $version
);
}
+139 -96
View File
@@ -3,137 +3,180 @@
* Template Name: Jio - Categories
* Template Post Type: page
*
* 分类汇总页面:列出全站所有分类。
*
* 用法:在后台新建一个 Page,标题任意(默认显示页面标题,否则用"分类目录"),
* 模板选择 "Jio - Categories";或将 slug 设为 "categories" 自动匹配。
* Category directory page with recursive taxonomy navigation.
*
* @package Jio
*/
defined('ABSPATH') || exit;
get_header();
// ─── 数据准备 ─────────────────────────────────────────────
$post_id = (int) get_queried_object_id();
$all_terms = get_terms([
'taxonomy' => 'category',
'hide_empty' => false, // 全部展示,含 0 篇文章的分类
'orderby' => 'menu_order name',
'hide_empty' => false,
'orderby' => 'term_group',
'order' => 'ASC',
]);
if (is_wp_error($all_terms)) {
$all_terms = [];
}
$top_level = [];
$top_level = [];
$children_by_parent = [];
foreach ($all_terms as $term) {
if ((int) $term->parent === 0) {
$parent_id = (int) $term->parent;
if ($parent_id === 0) {
$top_level[] = $term;
} else {
$children_by_parent[(int) $term->parent][] = $term;
$children_by_parent[$parent_id][] = $term;
}
}
$total_cats = count($all_terms);
$total_counts = wp_count_posts();
$total_posts = $total_counts && isset($total_counts->publish) ? (int) $total_counts->publish : 0;
$page_title = trim((string) get_the_title());
if ($page_title === '') {
$page_title = __('分类目录', 'jio');
$total_posts = wp_count_posts('post');
$total_posts = $total_posts && isset($total_posts->publish) ? (int) $total_posts->publish : 0;
$page_title = trim((string) get_the_title($post_id));
$page_title = $page_title !== '' ? $page_title : __('分类目录', 'jio');
$has_sidebar = function_exists('jio_archive_has_sidebar')
? jio_archive_has_sidebar()
: (function_exists('jio_is_show_sidebar') ? jio_is_show_sidebar() : false);
$content_style = sanitize_key((string) get_post_meta($post_id, 'page_content_style', true));
if (!in_array($content_style, ['', 'not', 'nobox', 'full'], true)) {
$content_style = '';
}
?>
$content_style_class = $content_style !== '' ? ' page-content-' . $content_style : ' page-content-default';
$render_children = static function (int $parent_id, array $path) use (&$render_children, $children_by_parent): string {
$children = $children_by_parent[$parent_id] ?? [];
if ($children === []) {
return '';
}
$html = '<ul class="jio-category-directory__children">';
foreach ($children as $child) {
$child_id = (int) $child->term_id;
if (in_array($child_id, $path, true)) {
continue;
}
$link = get_category_link($child_id);
if (is_wp_error($link)) {
continue;
}
$description = class_exists('\\Jio\\Settings\\Settings')
? \Jio\Settings\Settings::get_category_recommend_desc($child_id)
: (string) $child->description;
$description = trim(wp_strip_all_tags((string) $description));
$html .= '<li class="jio-category-directory__child">';
$html .= '<div class="jio-category-directory__child-main"><a href="' . esc_url($link) . '">' . esc_html($child->name) . '</a>';
$html .= '<span class="jio-category-directory__count">' . (int) $child->count . '</span></div>';
if ($description !== '') {
$html .= '<p class="jio-category-directory__child-desc">' . esc_html(wp_trim_words($description, 18, '…')) . '</p>';
}
$nested = $render_children($child_id, array_merge($path, [$child_id]));
if ($nested !== '') {
$html .= $nested;
}
$html .= '</li>';
}
$html .= '</ul>';
return $html;
};
get_header();
<?php
if (function_exists('jio_archive_open_shell')) {
jio_archive_open_shell([
'main_class' => 'site-main container ',
'has_sidebar' => false,
'main_class' => 'site-main container' . $content_style_class,
'has_sidebar' => $has_sidebar,
'top_fluid' => ['page_top_fluid_' . $post_id, 'cat_top_fluid'],
'page_kind' => 'categories',
]);
}
?>
<div class="content-main content-main--page content-main--categories">
<?php if (function_exists('jio_breadcrumbs')) { jio_breadcrumbs(); } ?>
<?php
$cats_meta = sprintf(
/* translators: 1: total category count, 2: total published post count */
esc_html__('共 %1$d 个分类 · 共 %2$d 篇文章', 'jio'),
$total_cats,
$total_posts
);
get_template_part('templates/parts/page-header', null, [
'title' => esc_html($page_title),
'desc' => $cats_meta,
]);
?>
<?php if (!empty($top_level)) : ?>
<section class="jio-cats-grid" aria-label="<?php esc_attr_e('分类列表', 'jio'); ?>">
<?php foreach ($top_level as $cat) :
$cat_id = (int) $cat->term_id;
$cat_link = get_category_link($cat_id);
$cover_url = class_exists('\Jio\Settings\Settings')
? \Jio\Settings\Settings::get_category_cover_url($cat_id)
: (get_template_directory_uri() . '/assets/img/placeholder.svg');
$desc = class_exists('\Jio\Settings\Settings')
? \Jio\Settings\Settings::get_category_recommend_desc($cat_id)
: (string) $cat->description;
$children = $children_by_parent[$cat_id] ?? [];
?>
<article class="jio-cat-card">
<a class="jio-cat-card__cover"
href="<?php echo esc_url($cat_link); ?>"
style="background-image: url('<?php echo esc_url($cover_url); ?>');"
aria-label="<?php echo esc_attr($cat->name); ?>">
<span class="jio-cat-card__count-badge">
<?php printf(esc_html__('%d 篇', 'jio'), (int) $cat->count); ?>
</span>
</a>
<div class="jio-cat-card__body">
<h2 class="jio-cat-card__title">
<a href="<?php echo esc_url($cat_link); ?>"><?php echo esc_html($cat->name); ?></a>
</h2>
<?php if ($desc !== '') : ?>
<p class="jio-cat-card__desc"><?php echo esc_html(wp_trim_words(wp_strip_all_tags($desc), 40, '…')); ?></p>
<?php if (is_active_sidebar('page_top_content_' . $post_id)) : ?>
<?php dynamic_sidebar('page_top_content_' . $post_id); ?>
<?php endif; ?>
<?php if (!empty($children)) : ?>
<ul class="jio-cat-card__children" aria-label="<?php esc_attr_e('子分类', 'jio'); ?>">
<?php foreach ($children as $child) : ?>
<li>
<a href="<?php echo esc_url(get_category_link((int) $child->term_id)); ?>">
<span class="jio-cat-card__child-name"><?php echo esc_html($child->name); ?></span>
<span class="jio-cat-card__child-count"><?php echo (int) $child->count; ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
<?php while (have_posts()) : the_post(); ?>
<?php if (function_exists('jio_breadcrumbs')) { jio_breadcrumbs(); } ?>
<?php
get_template_part('templates/parts/page-header', null, [
'title' => esc_html($page_title),
'desc' => sprintf(
/* translators: 1: total category count, 2: total published post count */
esc_html__('共 %1$d 个分类 · 共 %2$d 篇文章', 'jio'),
count($all_terms),
$total_posts
),
]);
if ($content_style !== 'not' && trim((string) get_the_content()) !== '') {
echo '<div class="jio-panel"><article class="article wp-posts-content">';
the_content();
wp_link_pages([
'before' => '<p class="text-center post-nav-links radius8 padding-6">',
'after' => '</p>',
]);
echo '</article></div>';
}
?>
<?php if (!empty($top_level)) : ?>
<nav class="jio-category-directory-nav jio-panel" aria-label="<?php esc_attr_e('分类导航', 'jio'); ?>">
<span class="jio-category-directory-nav__label"><?php esc_html_e('分类导航', 'jio'); ?></span>
<ul>
<?php foreach ($top_level as $term) : ?>
<li><a href="#jio-category-<?php echo (int) $term->term_id; ?>"><?php echo esc_html($term->name); ?></a></li>
<?php endforeach; ?>
</ul>
</nav>
<section class="jio-category-directory" aria-label="<?php esc_attr_e('分类列表', 'jio'); ?>">
<?php foreach ($top_level as $term) :
$term_id = (int) $term->term_id;
$link = get_category_link($term_id);
if (is_wp_error($link)) {
continue;
}
$cover = function_exists('jio_widget_term_cover_url')
? jio_widget_term_cover_url($term_id, true)
: get_template_directory_uri() . '/assets/img/placeholder.svg';
$description = class_exists('\Jio\Settings\Settings')
? \Jio\Settings\Settings::get_category_recommend_desc($term_id)
: (string) $term->description;
$description = trim(wp_strip_all_tags((string) $description));
?>
<article class="jio-category-directory__section" id="jio-category-<?php echo $term_id; ?>">
<div class="jio-category-directory__summary">
<a class="jio-category-directory__cover" href="<?php echo esc_url($link); ?>" aria-label="<?php echo esc_attr($term->name); ?>">
<img <?php echo jio_get_lazy_attr('lazy_other', (string) $cover, 'fit-cover', jio_get_lazy_thumb()); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper returns escaped lazy attributes. ?> alt="">
</a>
<div class="jio-category-directory__intro">
<h2><a href="<?php echo esc_url($link); ?>"><?php echo esc_html($term->name); ?></a></h2>
<span class="jio-category-directory__count"><?php printf(esc_html__('%d 篇文章', 'jio'), (int) $term->count); ?></span>
<?php if ($description !== '') : ?>
<p><?php echo esc_html(wp_trim_words($description, 36, '…')); ?></p>
<?php endif; ?>
<a class="jio-category-directory__more" href="<?php echo esc_url($link); ?>"><?php esc_html_e('查看分类', 'jio'); ?><i class="fa fas fa-angle-right" aria-hidden="true"></i></a>
</div>
</div>
<?php echo $render_children($term_id, [$term_id]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- recursive renderer escapes term data. ?>
</article>
<?php endforeach; ?>
</section>
<?php else : ?>
<div class="no-results jio-categories-empty"><p><?php esc_html_e('暂无分类。', 'jio'); ?></p></div>
<?php endif; ?>
<?php endwhile; ?>
<?php if (is_active_sidebar('page_bottom_content_' . $post_id)) : ?>
<?php dynamic_sidebar('page_bottom_content_' . $post_id); ?>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
</section>
<?php else : ?>
<div class="no-results jio-cats-empty">
<p><?php esc_html_e('暂无分类。', 'jio'); ?></p>
</div>
<?php endif; ?>
<p class="jio-cats-footer">
<a class="btn-outline" href="<?php echo esc_url(home_url('/')); ?>">
<?php esc_html_e('返回首页', 'jio'); ?>
</a>
</p>
</div>
<?php
if (function_exists('jio_archive_close_shell')) {
jio_archive_close_shell(['has_sidebar' => false]);
jio_archive_close_shell([
'has_sidebar' => $has_sidebar,
'bottom_fluid' => ['cat_bottom_fluid', 'page_bottom_fluid_' . $post_id],
]);
}
get_footer();
@@ -0,0 +1,58 @@
const test = require('node:test')
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const root = path.resolve(__dirname, '..', '..')
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8')
test('category archives use the canonical stream and enforce category view rules', () => {
const archive = read('category.php')
const permissions = read('inc/helpers/jio-single-components.php')
const header = read('inc/helpers/jio-archive-filters.php')
assert.doesNotMatch(archive, /_jio_doc_category|page-documentnav|DocsPresenter/)
assert.match(archive, /TermMeta::resolve_archive_settings\(\$term\)/)
assert.match(archive, /jio_get_term_allow_view_data\(\(int\) \$term->term_id\)/)
assert.match(archive, /if \(!empty\(\$access\['allow'\]\)\) \{[\s\S]*?jio_archive_render_stream/)
assert.match(archive, /content-stream--category posts-row ajaxpager/)
assert.match(archive, /jio_archive_render_term_header\(/)
assert.match(archive, /cat_top_content/)
assert.match(archive, /cat_bottom_content/)
assert.match(permissions, /function jio_get_term_allow_view_data\(int \$term_id\)/)
assert.match(permissions, /jio_resolve_allow_view_rule\([\s\S]*?\$redirect_url/)
assert.match(permissions, /function jio_resolve_allow_view_rule\(string \$type, array \$roles, \?WP_Post \$post = null, string \$redirect_url = ''\)/)
assert.match(header, /current_user_can\('edit_term', \(int\) \$term->term_id\)/)
assert.match(header, /class="main-search-btn" search-type="post" search-trem=/)
})
test('category directory lists all terms recursively with archive links and page widget zones', () => {
const template = read('templates/pages/page-categories.php')
const css = read('assets/css/routes/categories.css')
const scripts = read('src/Core/ScriptsPart1b.php')
assert.match(template, /'taxonomy'\s*=>\s*'category'[\s\S]*?'hide_empty'\s*=>\s*false/)
assert.match(template, /'orderby'\s*=>\s*'term_group'/)
assert.match(template, /\$children_by_parent\[\$parent_id\]\[\]/)
assert.match(template, /\$render_children\(\$child_id, array_merge\(\$path, \[\$child_id\]\)\)/)
assert.match(template, /in_array\(\$child_id, \$path, true\)/)
assert.match(template, /get_category_link\(\$child_id\)/)
assert.match(template, /jio_widget_term_cover_url\(\$term_id, true\)/)
assert.match(template, /jio_get_lazy_attr\('lazy_other'/)
assert.doesNotMatch(template, /loading=["'](?:lazy|eager)["']/)
assert.match(template, /get_category_recommend_desc\(\$term_id\)/)
assert.match(template, /page_top_content_/)
assert.match(template, /page_bottom_content_/)
assert.match(template, /page_top_fluid_/)
assert.match(template, /page_bottom_fluid_/)
assert.match(template, /cat_top_fluid/)
assert.match(template, /cat_bottom_fluid/)
assert.match(scripts, /is_page_template\('templates\/pages\/page-categories\.php'\)/)
assert.match(css, /\.jio-category-directory-nav/)
assert.match(css, /\.jio-category-directory__section/)
assert.match(css, /\.jio-category-directory__children/)
assert.match(css, /@media \(max-width: 767px\)/)
assert.doesNotMatch(template + css, /jio-cats-grid|jio-cat-card/)
})