Tôi cần điều tương tự. Thật không may là không có gì cho nó vì vậy tôi đã phải tạo ra một cái gì đó cho thời gian. Nó không đẹp nhưng nó hoạt động nên tôi không quan tâm.
/**
* Returns renderable array of taxonomy terms from Categories vocabulary in
* hierarchical structure ready to be rendered as html list.
*
* @param int $parent
* The ID of the parent taxonomy term.
*
* @param int $max_depth
* The max depth up to which to look up children.
*
* @param string $route_name
* The name of the route to be used for link generation.
* Taxonomy term(ID) will be provided as route parameter.
*
* @return array
*/
function mymodule_categories_tree($parent = 0, $max_depth = NULL, $route_name = 'mymodule.category.view') {
// Load terms
$tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree('categories', $parent, $max_depth);
// Make sure there are terms to work with.
if (empty($tree)) {
return [];
}
// Sort tree by depth so we can easily find out the deepest level
uasort($tree, function($a, $b) {
// Change objects to array
return \Drupal\Component\Utility\SortArray::sortByKeyInt((array) $a, (array) $b, 'depth');
});
// Get the value of the deepest term
$deepest = end($tree);
$deepest = $deepest->depth;
// Create a structured array
$list = [
$parent => [
'items' => [],
'depth' => -1
]
];
foreach ($tree AS $term) {
$list[$term->tid] = (array) $term;
}
// See if we're on a node page and if so open the menu
// on the proper position.
$node = \Drupal::request()->attributes->get('node');
if ($node) {
$categories = $node->get('categories')->getValue();
// Go through each category to find out the least deep one.
// That one will be the one we'll open.
$open_category = $parent;
foreach ($categories AS $target) {
$tid = $target['target_id'];
if ($list[$tid]['depth'] > $list[$open_category]['depth']) {
$open_category = $tid;
}
}
} else {
// See if we're on a term page and set the corresponding item
// as active so we don't have to rely on JS.
$term = \Drupal::request()->attributes->get('taxonomy_term');
if ($term) {
$open_term = $term->id();
}
}
for ($i = $deepest; $i >= 0; $i--) {
foreach ($list AS $term) {
if ($term['depth'] == $i) {
$item = [
'#type' => 'link',
'#weight' => $term['weight'],
'#title' => $term['name'],
'#url' => Url::fromRoute($route_name, ['taxonomy_term' => $term['tid']]),
'#options' => [
'set_active_class' => TRUE
]
];
// If we're on a node page and this category was chosen
// as active, set the link's class.
if (isset($open_category) && $open_category == $term['tid']) {
$item['#attributes']['class'][] = 'active';
}
// If we're on term page, set the link's class to 'active'
// and if this item is a parent, open it.
if (isset($open_term) && $open_term == $term['tid']) {
$item['#attributes']['class'][] = 'active';
if (!empty($term['items'])) {
$item['#wrapper_attributes']['class'][] = 'open';
}
}
// If this item has children
if (!empty($term['items'])) {
$item['items'] = $term['items'];
$item['#wrapper_attributes']['class'][] = 'parent';
$item['#prefix'] = '<span></span>';
// If any of the child items has 'active' class,
// or is also a parent and has 'open' class
// add the 'open' class to this wrapper too.
foreach ($item['items'] AS $child) {
if (
isset($child['#attributes']['class']) && in_array('active', $child['#attributes']['class'])
|| isset($child['#wrapper_attributes']['class']) && in_array('open', $child['#wrapper_attributes']['class'])
) {
$item['#wrapper_attributes']['class'][] = 'open';
break;
}
}
}
foreach ($term['parents'] AS $pid) {
$list[$pid]['items'][$term['tid']] = $item;
}
unset($list[$term['tid']]);
}
}
}
return [
'#theme' => 'item_list',
'#items' => $list[$parent]['items'],
'#attributes' => [
'class' => ['categories-tree']
],
'#attached' => [
'library' => [
'mymodule/categories_tree'
]
]
];
}