Все главы
  1. 1 Перед началом
  2. 2 Установка
  3. 3 Страницы
  4. 4 Первый шаблон
  5. 5 Компоненты
  6. 6 Рамка сайта
  7. 7 Поля страниц
  8. 8 Списки страниц
  9. 9 Свои настройки
  10. 10 Заявка на замер
  11. 11 Живые части
  12. 12 В интернет
  13. 13 Что дальше

Списки: каталог, проекты и главная

«Каталог» сам соберёт карточки кухонь с ценами, «Проекты» покажут работы по пять на странице, а главная станет витриной с кухнями и свежими проектами.

Глава 8 из 13 · 7 минут чтения

Карточки кухонь

Кухни лежат внутри «Каталога». Значит, шаблону страницы «Каталог» нужно взять её дочерние страницы и нарисовать по карточке на каждую. Создайте шаблон Catalog:

Шаблон Catalog
<script>
  import Content from '$core/Content';
  import Layout from '$components/Layout';
  import { children, image, link } from '$core';

  let { page } = $props();

  const kitchens = children(page.id);
  const rub = new Intl.NumberFormat('ru');
</script>

<Layout {page}>
  <h1>{page.title}</h1>
  <Content of={page} />

  <ul class="cards">
    {#each kitchens as kitchen (kitchen.id)}
      {@const photo = kitchen.fields.photo ? image(kitchen.fields.photo) : null}
      <li>
        <a href={link(kitchen.id)}>
          {#if photo}
            <img src={photo.src} width={photo.width} height={photo.height} alt="" />
          {/if}
          <span class="name">{kitchen.title}</span>
          <span class="meta">
            {kitchen.fields.plan} · от {rub.format(kitchen.fields.price)} ₽ за метр
          </span>
        </a>
      </li>
    {/each}
  </ul>
</Layout>

<style>
  .cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
    gap: 1rem;
    padding: 0;
    list-style: none;
  }

  .cards a {
    display: grid;
    gap: 0.35rem;
    padding: 0.75rem 0.75rem 1rem;
    border-radius: 1.25rem;
    background: #fff;
    color: inherit;
    text-decoration: none;
  }

  .cards img {
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: 0.9rem;
  }

  .name {
    font-weight: 700;
  }

  .meta {
    color: #6b6862;
    font-size: 0.9rem;
  }
</style>
  • children(page.id): дочерние страницы текущей, в порядке дерева. В пятой главе мы звали children(null) для верхнего уровня, здесь та же функция.
  • kitchen.fields.price: у каждой страницы из списка есть её поля, как у page в шаблоне кухни.
  • {@const photo = …}: заводит значение прямо внутри {#each}, своё для каждой кухни.
  • aspect-ratio: 4 / 3 и object-fit: cover: все фото в карточках одной высоты, даже если снимки разных пропорций.
  • grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)): карточки встанут в столько колонок, сколько поместится. На телефоне одна, на ноутбуке три.

Откройте страницу «Каталог» в менеджере, выберите ей шаблон Catalog и сохраните. Порядок карточек тот же, что в дереве: перетащите кухни, чтобы поменять его.

Каталог студии: карточки кухонь с планировкой и ценой
Каталог студии: карточки кухонь с планировкой и ценой

Раздел проектов

Создайте на верхнем уровне страницу «Проекты» (адрес proekty), а внутри неё три работы. У каждой заполните поле Анонс: это одно-два предложения, которые попадут в список. Проектам оставьте шаблон Base.

Для пейджера, ссылок «Назад» и «Вперёд» между страницами списка, возьмём готовый компонент. Щёлкните в шапке значок Элементы, откройте вкладку Набор компонентов и нажмите Установить у компонента Pagination. Он появится среди компонентов сайта, и его можно будет править, как свой.

Теперь шаблон Projects:

Шаблон Projects
<script>
  import Layout from '$components/Layout';
  import Pagination from '$components/Pagination';
  import { link, listing } from '$core';

  let { page } = $props();

  const projects = listing(page.id, { perPage: 5 });
  const date = new Intl.DateTimeFormat('ru', {
    dateStyle: 'long',
    timeZone: 'UTC',
  });
</script>

<Layout {page}>
  <h1>{page.title}</h1>

  {#each projects.items as project (project.id)}
    <article class="project">
      <h2><a href={link(project.id)}>{project.title}</a></h2>
      <p class="date">{date.format(new Date(project.publishedAt))}</p>
      {#if project.introText}<p>{project.introText}</p>{/if}
    </article>
  {:else}
    <p>Проектов пока нет.</p>
  {/each}

  <Pagination
    items={projects.pager()}
    prev={projects.url(projects.number - 1)}
    next={projects.url(projects.number + 1)}
  />
</Layout>

<style>
  .project {
    padding: 1.25rem 0;
    border-bottom: 1px solid #e2dfd9;
  }

  .project h2 {
    margin: 0;
    font-size: 1.375rem;
  }

  .project a {
    color: inherit;
  }

  .date {
    margin: 0.25rem 0 0;
    color: #6b6862;
    font-size: 0.9rem;
  }
</style>
  • listing(page.id, { perPage: 5 }): дочерние страницы порциями по пять. Сначала новые: последняя опубликованная работа окажется сверху.
  • projects.items: работы текущей порции. На втором экране списка это работы с шестой по десятую.
  • {:else} внутри {#each}: что показать, если список пуст.
  • projects.pager(), projects.url(…): номера страниц списка и адреса соседних. Компонент Pagination рисует из них пейджер, а при одной странице не рисует ничего.

Выберите странице «Проекты» шаблон Projects. Вторая порция откроется по адресу /proekty/page-2, когда работ станет больше пяти: Eile сам понимает такие адреса.

Главная: витрина студии

Список можно собрать и из другого раздела. Главная покажет крупный заголовок, кнопку в каталог, большое фото, три кухни с ценами и два свежих проекта. Создайте шаблон Home:

Шаблон Home
<script>
  import Content from '$core/Content';
  import Layout from '$components/Layout';
  import { children, image, link, listing } from '$core';

  let { page } = $props();

  // Разделы находим по адресу: так шаблону не нужны номера страниц.
  const top = children(null);
  const catalog = top.find((_) => _.path === '/katalog');
  const projects = top.find((_) => _.path === '/proekty');
  const contacts = top.find((_) => _.path === '/kontakty');

  const kitchens = catalog ? children(catalog.id).slice(0, 3) : [];
  const fresh = projects
    ? listing(projects.id, { perPage: 2, paged: false }).items
    : [];
  const cover = kitchens[0]?.fields.photo ? image(kitchens[0].fields.photo) : null;
  const rub = new Intl.NumberFormat('ru');
</script>

<Layout {page}>
  <section class="hero">
    <h1>{page.longTitle || page.title}</h1>
    <Content of={page} />
    <p class="actions">
      {#if catalog}<a class="button" href={link(catalog.id)}>Смотреть каталог</a>{/if}
      {#if contacts}<a href={link(contacts.id)}>Записаться на замер</a>{/if}
    </p>
    {#if cover}
      <img
        src={cover.src}
        srcset={cover.srcset}
        sizes="(min-width: 48rem) 45rem, 100vw"
        width={cover.width}
        height={cover.height}
        alt={cover.alt}
      />
    {/if}
  </section>

  {#if kitchens.length}
    <h2>Из каталога</h2>
    <ul class="kitchens">
      {#each kitchens as kitchen (kitchen.id)}
        <li>
          <a href={link(kitchen.id)}>{kitchen.title}</a>
          <span>от {rub.format(kitchen.fields.price)} ₽ за метр</span>
        </li>
      {/each}
    </ul>
  {/if}

  {#if fresh.length}
    <h2>Готовые проекты</h2>
    {#each fresh as project (project.id)}
      <p class="project"><a href={link(project.id)}>{project.title}</a></p>
    {/each}
  {/if}
</Layout>

<style>
  .hero img {
    margin-top: 1.5rem;
    border-radius: 1.5rem;
  }

  .actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.25rem;
  }

  .button {
    padding: 0.8rem 1.4rem;
    border-radius: 999px;
    background: #1f1e1c;
    color: #fff;
    text-decoration: none;
  }

  .actions a:not(.button) {
    color: inherit;
  }

  .kitchens {
    display: grid;
    gap: 0.5rem;
    padding: 0;
    list-style: none;
  }

  .kitchens li {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 0.25rem 1rem;
    padding: 0.9rem 1.1rem;
    border-radius: 1rem;
    background: #fff;
  }

  .kitchens span {
    color: #6b6862;
  }

  .kitchens a,
  .project a {
    color: inherit;
  }
</style>
  • top.find((_) => _.path === '/katalog'): ищем среди страниц верхнего уровня ту, у которой адрес /katalog. Знак _ здесь просто имя для «очередной страницы».
  • children(catalog.id).slice(0, 3): первые три кухни из «Каталога». Поменяете порядок в дереве, поменяются и они.
  • listing(projects.id, { perPage: 2, paged: false }): два свежих проекта. paged: false говорит, что этот список не постраничный: у главной не появятся адреса вида /page-2.
  • kitchens[0]?.fields.photo: фото первой кухни для обложки. Знак ?. не даёт шаблону упасть, если кухонь в каталоге пока нет.

Выберите главной шаблон Home. Крупный заголовок возьмите из поля Длинный заголовок на вкладке Поиск и соцсети: напишите там «Кухни по вашим размерам». Он же попадёт в заголовок вкладки браузера, а в меню сайта останется короткое «Главная».

Главная студии на шаблоне Home
Главная студии на шаблоне Home