Вывод плоской таксономии.
Отлично реализуется через views.
- Создаем обычное представление.
Показать Содержимое с нужным типом - Вид отображения: Поля.
Добавляем нужные поля.
Добавляем поле содержимого с термином таксономии (Исключить из вывода: Да) - В разделе "Формат - настройки" в Группирующее поле Nr.1 выставляем поле с термином таксономии.
У меня получилось на выходе: 
Вывод одноуровневой иерархии
Через связку Views tree и Views Field View.
Стандартный модуль Views Tree не совсем подходит. Проблема в том, что Views Tree работает по принципу «одна строка — один элемент дерева». Когда добавляется связь с нодами, Views начинает дублировать строки терминов для каждой ноды или схлопывать их, что ломает иерархию дерева.
Чтобы вывести все ноды под каждым термином в иерархическом дереве, лучше всего использовать связку: Views Tree (для структуры терминов) + Views Field View (для вывода списка нод).
Шаг 1. Создаем «Вложенное» представление (Child View)
Это представление будет отвечать только за вывод списка нод для одного конкретного термина.
- Создаем новое View (тип: Содержимое/Nodes).
- Создаем дисплей типа Embed (Вставка).
- Добавляем Контекстный фильтр: Содержимое: Has taxonomy term ID.
В настройках фильтра выбираем: «Provide default value» -> «Taxonomy term ID from URL» (хотя передавать мы его будем вручную).
Настроиваем нужные для отображения поля (заголовок ноды, ссылка и т.д.).

Шаг 2. Настраиваем «Основное» view (Parent View)
Здесь мы строим дерево таксономии с помощью Views Tree.
- Создаем новое View (тип: Термины таксономии типа наш словарь).
- Добавляем поля
- Добавляем поле ID термина (Идентификатор tid термина таксономии).
Исключить из вывода: Да. - Добавляем поле Родители Термина (Родительский термин термина. Это может привести к дублирующим записям, если вы используете словарь, который позволяет нескольких родителей.)
Исключить из вывода: Да.
Форматер - Идентификатор сущности. - Добавляем поле Просмотр Глобальный (Embed a view as a field. This can cause slow performance, so enable some caching).
Просмотр - выбираем дочернее представление, созданное на шаге 1.
Отображение - если сразу выставить Embed друпал выдаст ошибку. Оставляем default.
В контекстные фильтры пишем {{ raw_fields.tid }}. Посмотреть заполнитель можно в разделе Подстановочные шаблоны.
Сохраняем. - Снова открываем поле Просмотр Глобальный .
В разделе Отображение выставляем Embed.
- Добавляем поле ID термина (Идентификатор tid термина таксономии).
- В разделе настройки представления Формат выбираем Tree (list)

-
Открываем настройки выбранного формата.
В настройке Main field выбираем поле с tid термина
В настройке Parent tid выбираем поле Родители термина
Итоговое отображение

Через views и тему сайта (не рекомендуется).
Шаг 1. Создаем «Вложенное» представление (Child View). Как делали до этого.
Шаг 2. Создаем пустую страницу. Выставляем alias страницы. В моем случае это /resources2
Шаг 3. В своей теме сайта:
/**
* Implements hook_preprocess_page().
*/
function your_theme_preprocess_page(&$variables) { // <-- вписать название своей темы
// Получаем текущий путь
$current_path = \Drupal::service('path.current')->getPath();
$alias = \Drupal::service('path_alias.manager')->getAliasByPath($current_path);
// Проверяем по алиасу, а не по системному пути
if ($alias === '/resources2') { // <-- заменить на нужный алиас
$tree = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->loadTree('link_area'); // <-- заменить на нужный словарь
$items = [];
foreach ($tree as $item) {
/** @var \Drupal\taxonomy\TermInterface $term */
$term = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->load($item->tid);
// Вложенные ссылки
$links_view = views_embed_view('category_links', 'embed_1', $item->tid); // <-- заменить имя представления и название вида
$items[] = [
'name' => $term->label(),
'url' => $term->toUrl()->toString(),
'depth' => $item->depth,
'links' => $links_view,
];
}
$variables['link_category_tree'] = $items;
}
}
function your_theme_theme_suggestions_page_alter(array &$suggestions, array $variables) { // <-- вписать название своей темы
$current_path = \Drupal::service('path.current')->getPath();
$alias = \Drupal::service('path_alias.manager')->getAliasByPath($current_path);
if ($alias === '/resources2') { // <-- заменить на нужный алиас
$suggestions[] = 'page__resources2'; // <-- заменить на нужный алиас
}
}
Шаг 4. Добавить шалон для страницы.
Скопируем page.html.twig в page--resources2.html.twig
<!-- заменяем {{ page.content }} на наш код -->
{% if link_category_tree %}
<div class="resources-tree">
<ul class="category-tree">
{% for item in link_category_tree %}
<li style="margin-left: {{ item.depth * 20 }}px;">
<a href="{{ item.url }}">{{ item.name }}</a>
{% if item.links %}
<div class="category-links">{{ item.links }}</div>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endif %}
Шаг 5. Чистим кеш.
Открываем страницу /resources2 - должен отобразиться список терминов с нодами.
Через views и кастомный модуль (рекомендуется).
Шаг 1. Создаем «Вложенное» представление (Child View). Как делали до этого.
Шаг 2. Создаем кастомный модуль
Например: my_resources
Папка: modules/custom/my_resources
Путь страницы в моем случае будет /resources3
Файлы:
-
my_resources.info.yml
name: My Resources type: module description: 'Страница ресурсов с деревом категорий' core_version_requirement: ^10.3 || ^11 package: Custom -
my_resources.routing.yml
my_resources.page: path: '/resources3' # <-- заменить на свой defaults: _controller: '\Drupal\my_resources\Controller\ResourcesController::build' _title: 'Ресурсы' requirements: _access: 'TRUE' -
src/Controller/ResourcesController.php
<?php namespace Drupal\my_resources\Controller; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Cache\CacheableResponse; use Drupal\taxonomy\TermInterface; class ResourcesController extends ControllerBase { public function build() { $tree = $this->entityTypeManager() ->getStorage('taxonomy_term') ->loadTree('link_area'); // <-- заменить на нужный словарь $items = []; foreach ($tree as $item) { $term = $this->entityTypeManager() ->getStorage('taxonomy_term') ->load($item->tid); $links_view = views_embed_view('category_links', 'embed_1', $item->tid); // <-- заменить имя представления и имя вида $items[] = [ 'name' => $term->label(), 'url' => $term->toUrl()->toString(), 'depth' => $item->depth, 'links' => $links_view, ]; } $build = [ '#theme' => 'my_resources_tree', '#items' => $items, '#cache' => [ 'tags' => ['taxonomy_term_list:link_area'], // <-- заменить на нужный словарь 'contexts' => ['url.path'], ], ]; return $build; } } -
templates/my-resources-tree.html.twig (опционально)
<div class="resources-tree"> <ul class="category-tree"> {% for item in items %} <li style="margin-left: {{ item.depth * 20 }}px;"> <a href="{{ item.url }}">{{ item.name }}</a> {% if item.links %} <div class="category-links">{{ item.links }}</div> {% endif %} </li> {% endfor %} </ul> </div> -
Регистрируем шаблон (если создавали в пункте 4) в my_resources.module
<?php /** * @file * Hooks for My Resources module. */ /** * Implements hook_theme(). */ function my_resources_theme($existing, $type, $theme, $path) { return [ 'my_resources_tree' => [ 'variables' => ['items' => []], 'template' => 'my-resources-tree', ], ]; } - Включаем модуль в админке. Переходим на страницу
/resources3