文章边栏进阶:子分类显示已发布文章数量

子分类显示已发布文章数量

 

下面是应用方法:

分类"blog"且禁用不展开第二级子分类
分类"product"且展开第二级子分类

下面是Snippets主程序:

/**
 * 统一的分类过滤器短代码
 * 根据 'type' 属性显示产品分类或博客分类。
 * 用法:
 * [unified_category_filter type="product"] 显示产品分类 (ID: 58)
 * [unified_category_filter type="blog"]    显示博客分类 (ID: 54)
 *
 * 新增需求: 'show_level1_count' 和 'show_level2_count' 在代码内部设置默认值,
 *       但如果短代码提供了这些参数,则以短代码为准。
 * 补充功能: 一级分类的数量现在包含其所有子分类的文章数量。
 * 修正: 移除 post_type 参数,因为所有内容都共享相同的文章类型。
 * 新增功能: 为当前活动分类 (一级或二级) 添加 'current-active' CSS 类。
 * 新增功能: 为 type="blog" 提供选项,可选择不展开二级子分类,直接让一级分类可点击。
 * 修正: 修正了 allow_expand_level2="false" 时一级分类总数不包含子分类数量的问题。
 */
add_shortcode('unified_category_filter', function($atts) {

  // --- 开始:可在 Snippet 内部设置的默认配置项 ---
  // 这些是默认值。如果短代码本身提供了 show_level1_count 或 show_level2_count,
  // 则短代码提供的值会覆盖这里的默认值。
  $default_show_level1_count = true;  // 默认显示一级分类的文章/产品数量
  $default_show_level2_count = true;  // 默认显示二级分类的文章/产品数量

  // 产品分类的顶级父分类ID、文本和分类法
  $product_top_level_parent_id = 58;
  $product_all_items_text = 'All Products';
  $product_taxonomy = 'category';
  $product_allow_expand_level2 = true; // 产品分类默认允许展开二级子分类

  // 博客分类的顶级父分类ID、文本和分类法
  $blog_top_level_parent_id = 54;
  $blog_all_blogs_text = 'Blogs Category';
  $blog_taxonomy = 'category';
  $blog_allow_expand_level2 = false; // 博客分类默认不允许展开二级子分类 (即一级分类直接是链接)
  // --- 结束:可在 Snippet 内部设置的默认配置项 ---


  // 解析短代码属性
  $atts = shortcode_atts(
    array(
      'type'                  => 'product', // 默认类型为 'product'
      'show_level1_count'     => $default_show_level1_count,
      'show_level2_count'     => $default_show_level2_count,
      'allow_expand_level2'   => null, // 允许通过短代码覆盖默认值 (null 表示未指定)
    ),
    $atts,
    'unified_category_filter'
  );

  $top_level_parent_id = 0; // 初始化顶级父分类ID
  $all_items_text = '';     // 初始化标题文本
  $taxonomy = '';           // 初始化分类法
  $current_filter_type = $atts['type']; // 获取当前的过滤器类型
  $allow_expand_level2 = false; // 初始化当前过滤器的二级展开权限

  // 将字符串的 'true'/'false' 转换为布尔值,因为短代码属性总是字符串
  $show_level1_count = filter_var($atts['show_level1_count'], FILTER_VALIDATE_BOOLEAN);
  // 只有当 allow_expand_level2 为 true 时,show_level2_count 才可能为 true
  $show_level2_count_actual = filter_var($atts['show_level2_count'], FILTER_VALIDATE_BOOLEAN);


  // 根据类型设置不同的父分类ID和文本
  if ($current_filter_type === 'product') {
    $top_level_parent_id = $product_top_level_parent_id;
    $all_items_text = $product_all_items_text;
    $taxonomy = $product_taxonomy;
    // 如果短代码明确指定了 'allow_expand_level2',则覆盖默认值,否则使用产品类型的默认值
    $allow_expand_level2 = ($atts['allow_expand_level2'] !== null) ? filter_var($atts['allow_expand_level2'], FILTER_VALIDATE_BOOLEAN) : $product_allow_expand_level2;
  } elseif ($current_filter_type === 'blog') {
    $top_level_parent_id = $blog_top_level_parent_id;
    $all_items_text = $blog_all_blogs_text;
    $taxonomy = $blog_taxonomy;
    // 如果短代码明确指定了 'allow_expand_level2',则覆盖默认值,否则使用博客类型的默认值
    $allow_expand_level2 = ($atts['allow_expand_level2'] !== null) ? filter_var($atts['allow_expand_level2'], FILTER_VALIDATE_BOOLEAN) : $blog_allow_expand_level2;
  } else {
    // 如果类型不识别,返回错误
    return '<p>Invalid filter type specified for [unified_category_filter].</p>';
  }

  // 获取一级分类 (top_level_parent_id 的子分类)
  $level1_terms = get_terms(array(
    'taxonomy' => $taxonomy, // 使用动态分类法
    'parent' => $top_level_parent_id,
    'hide_empty' => false, // 显示所有分类,即使它们没有文章/产品
    'orderby' => 'name',
    'order' => 'ASC',
  ));

  // 如果没有找到一级分类,则返回错误信息
  if (is_wp_error($level1_terms) || empty($level1_terms)) {
    return '<p>No subcategories found for the specified parent (' . $top_level_parent_id . ').</p>';
  }

  // 获取当前正在查看的分类对象
  $current_queried_object = get_queried_object();
  $current_term_id = 0;
  // 确保当前对象是 WP_Term 并且与我们正在处理的分类法一致
  if (is_a($current_queried_object, 'WP_Term') && $current_queried_object->taxonomy === $taxonomy) {
      $current_term_id = $current_queried_object->term_id;
  }


  // 判断哪个一级分类应该展开 (逻辑不变,依然通过父子关系判断)
  $should_expand = false; // 存储需要展开的一级分类的 term_id
  if ($current_term_id) {
    // 检查当前分类是否是某个一级分类的子分类
    foreach ($level1_terms as $l1_term) {
        // 如果当前分类的父ID与一级分类ID相同,说明当前分类是该一级分类的子分类
        if ($current_queried_object->parent === $l1_term->term_id) {
            $should_expand = $l1_term->term_id;
            break;
        }
        // 如果当前分类本身就是某个一级分类
        if ($current_term_id === $l1_term->term_id) {
            $should_expand = $l1_term->term_id;
            break;
        }
    }
  }

  // 启动输出缓冲以捕获HTML
  ob_start();
  ?>
  <div class="category-filter-sidebar" id="category-accordion-<?php echo time() . '-' . $current_filter_type; ?>">
    <h3>
      <a href="<?php echo esc_url(get_term_link($top_level_parent_id, $taxonomy)); ?>"
         <?php if ($current_term_id && $current_term_id === $top_level_parent_id): ?>class="current-active"<?php endif; ?>
      >
        <?php echo esc_html($all_items_text); ?>
      </a>
    </h3>
    <div class="accordion-container">
      <?php foreach ($level1_terms as $level1_term): ?>
        <?php
        // 检查当前一级分类是否应该展开
        $is_expanded = ($should_expand === $level1_term->term_id);

        // 获取所有子分类(无论是否允许展开二级,都需要计算总数)
        // get_term_children 返回的是 Term ID 数组
        $all_children_ids = get_term_children($level1_term->term_id, $taxonomy);

        $level2_terms_for_rendering = []; // 实际用于渲染的二级分类
        if ($allow_expand_level2 && !empty($all_children_ids)) {
            // 如果允许展开二级,且存在子分类,则获取直接子分类用于渲染
            $level2_terms_for_rendering = get_terms(array(
                'taxonomy' => $taxonomy,
                'parent' => $level1_term->term_id,
                'hide_empty' => false,
                'orderby' => 'name',
                'order' => 'ASC',
            ));
        }

        // 计算一级分类的总数 (包含其所有子分类的文章数量)
        // 核心修改点:无论是否显示二级,都累加所有子分类的文章数
        $level1_total_count = $level1_term->count; // 先加上自身直接关联的数量
        if (!empty($all_children_ids) && !is_wp_error($all_children_ids)) {
            foreach ($all_children_ids as $child_id) {
                $child_term = get_term($child_id, $taxonomy);
                if ($child_term && !is_wp_error($child_term)) {
                    $level1_total_count += $child_term->count; // 累加所有子分类的数量
                }
            }
        }


        // 判断一级分类是否是当前活动分类
        $is_level1_active = ($current_term_id && $current_term_id === $level1_term->term_id);

        // 判断该一级分类是否有二级子分类,并且是否允许展开二级分类
        // 注意这里使用 $level2_terms_for_rendering 来判断是否需要渲染手风琴
        $has_expandable_children = !empty($level2_terms_for_rendering) && !is_wp_error($level2_terms_for_rendering) && $allow_expand_level2;
        ?>
        <div class="accordion-item">
          <div class="accordion-header <?php echo $is_expanded ? 'expanded' : ''; ?>"
               <?php if ($has_expandable_children): ?>onclick="toggleAccordion(this, event)"<?php endif; ?>
          >
            <?php if ($has_expandable_children): ?>
                <span class="accordion-toggle"><?php echo $is_expanded ? '−' : '+'; ?></span>
            <?php else: ?>
                <!-- 如果没有可展开的子分类,或者不允许展开,显示一个占位符 -->
                <span class="accordion-toggle no-toggle"></span>
            <?php endif; ?>

            <a href="<?php echo esc_url(get_term_link($level1_term)); ?>" class="level1-name <?php echo $is_level1_active ? 'current-active' : ''; ?>">
              <?php echo esc_html($level1_term->name); ?>
              <?php // 显示一级分类的总数(包含子分类)
              if ($show_level1_count && $level1_total_count > 0): ?>
                <span class="category-count">(<?php echo esc_html($level1_total_count); ?>)</span>
              <?php endif; ?>
            </a>
          </div>

          <?php if ($has_expandable_children): // 只有当允许展开并且有二级子分类时才渲染内容区域 ?>
              <div class="accordion-content <?php echo $is_expanded ? 'show' : ''; ?>" style="display: <?php echo $is_expanded ? 'block' : 'none'; ?>;">
                <ul class="level2-list">
                  <?php foreach ($level2_terms_for_rendering as $level2_term): ?>
                    <?php
                    // 判断二级分类是否是当前活动分类
                    $is_level2_active = ($current_term_id && $current_term_id === $level2_term->term_id);
                    ?>
                    <li class="level2-item">
                      <a href="<?php echo esc_url(get_term_link($level2_term)); ?>" class="level2-name <?php echo $is_level2_active ? 'current-active' : ''; ?>">
                        <?php echo esc_html($level2_term->name); ?>
                        <?php // 只有当允许展开二级分类并且设置了 show_level2_count 才显示二级分类数量
                        if ($show_level2_count_actual && $level2_term->count > 0): ?>
                          <span class="category-count">(<?php echo esc_html($level2_term->count); ?>)</span>
                        <?php endif; ?>
                      </a>
                    </li>
                  <?php endforeach; ?>
                </ul>
              </div>
          <?php endif; ?>
        </div>
      <?php endforeach; ?>
    </div>
  </div>

  <script>
  /**
   * 切换手风琴的展开/折叠状态。
   * @param {HTMLElement} header - 被点击的手风琴标题元素。
   * @param {Event} event - 点击事件对象。
   */
  function toggleAccordion(header, event) {
    var content = header.nextElementSibling; // 获取手风琴内容区域
    var toggle = header.querySelector('.accordion-toggle'); // 获取加/减号切换图标
    var target = event.target; // 获取实际点击的DOM元素

    // 如果点击目标是<a>标签,并且该<a>标签位于当前手风琴标题内部
    // 则不阻止默认行为,让链接正常跳转,也不执行手风琴的展开/折叠逻辑。
    if (target.tagName === 'A' && target.closest('.accordion-header') === header) {
      return; // 允许链接跳转
    }

    // 如果不是点击链接,则阻止事件的默认行为 (例如,防止点击文本时页面滚动)
    event.preventDefault();

    // 切换header和content的'expanded'和'show'类
    header.classList.toggle('expanded');
    content.classList.toggle('show');

    // 根据展开状态更新加/减号图标和内容区域的显示样式
    if (header.classList.contains('expanded')) {
      toggle.textContent = '−'; // 展开时显示减号
      content.style.display = 'block'; // 显示内容
    } else {
      toggle.textContent = '+'; // 折叠时显示加号
      content.style.display = 'none'; // 隐藏内容
    }
  }
  </script>
  <?php
  // 结束输出缓冲并返回捕获到的HTML
  return ob_get_clean();
});

 

下面是CSS,和上一篇一样不变:

/*product sider*/
.category-filter-sidebar {
	background:#666;
  padding: 0;
  border-radius: 4px;
  margin: 0;
}

.category-filter-sidebar h3 {
	padding:15px 20px;
	font-size: 16px;
	line-height:1;
	text-align:left;
	color: #aaa;
	font-weight: 600;
}

.accordion-container {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.accordion-item {
  border-bottom:1px solid rgb(85 85 85 / 10%);
  overflow: hidden;
}

.accordion-header {
	background: #555;
	padding: 5px 20px;
	cursor: pointer;
	display: flex;
	flex-direction: row-reverse;
	align-items: center;
	gap: 10px;
	transition: background 0.2s;
	font-weight: 600;
	color:#fff;
	user-select: none;
}

.accordion-header:hover {
  background: #C1303D;
}

.accordion-header.expanded {
  background: #C1303D;
}

.accordion-toggle {
  width: 15px;
  text-align: center;
  font-size:35px;
	padding-bottom:3px;
	line-height:1;
  color: #fff;
  font-weight: normal;
  display: inline-block;
}

.level1-name {
  color: #0073aa;
  text-decoration: none;
  flex: 1;
  font-size: 14px;
	font-weight:normal;
}

.level1-name:hover {
  text-decoration: underline;
}

.accordion-content {
  background: #555;
  padding: 0;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

.accordion-content.show {
  max-height: 1000px;
}

.level2-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.level2-item {
  padding: 0;
  margin: 0;
}

.level2-name {
  display: block;
  padding: 10px 15px 10px 40px;
  color: #666;
  text-decoration: none;
  font-size: 13px;
  border-bottom:1px solid rgb(155 155 155 / 20%);
  transition: background 0.2s, color 0.2s;
	position:relative;
}
.level2-name:before{
	content:'';
	position:absolute;
	bottom:17px;
	left:25px;;
	width:0px;
	height:0px;
	display:block;
	border-bottom:5px solid transparent;
	border-right:5px solid transparent;
	border-top:5px solid transparent;
	border-left:5px solid #fff;
}

.level2-name:hover {
  background: #C1303D;
  color:#fff!important;
}

.no-subcategories {
  padding: 10px 15px 10px 40px;
  color: #999;
  font-size: 12px;
  margin: 0;
  border-bottom:0;
}

.category-filter-sidebar .current-active.level2-name:before{
	border-left: 5px solid #d74c59;
}
.category-filter-sidebar .current-active.level2-name{
	color:#d74c59;
}

 

Share the Post:

相关文章

This Headline Grabs Visitors’ Attention

A short description introducing your business and the services to visitors.