Главная
Все проекты

A-floor

Витрина для интерьерных салонов на тысячи товаров

Роль

Fullstack Developer / Data Engineer

Год

2023

Стек

HTMLSCSS (BEM)JSWordpress & WoocommerceNode.js

A-floor

Задача

Продуктовая цель: Создать профессиональный инструмент подбора материалов (напольные покрытия, двери, мебель и др.) для дизайнеров интерьера. Скрыть цены, так как они зависят от объёма и статуса партнера, и превратить сайт из классического магазина в генератор предметных диалогов с менеджером (заявки с привязкой к конкретным товарам и брендам).

Инженерный вызов:

  1. Глубоко кастомизировать WooCommerce: полностью заменить слой рендеринга товаров, отключив хуки цен и корзины на уровне архитектуры (без скрытия через CSS).
  2. Спроектировать универсальную систему фильтров, которая сама вычисляет доступные параметры в категории на основе реальных товаров, чтобы избавить клиента от ручной настройки.
  3. Построить надежный конвейер наполнения каталога: автоматизировать сбор тысяч позиций из десятка разных источников со сложной структурой прямо в целевую модель данных WooCommerce.

Этапы

01

Проектирование headless-подобного каталога

  • Переопределение базовых шаблонов WooCommerce (content-product.php и др.).
  • Интеграция лид-кнопок «Узнать цену» с невидимой передачей контекста (data-product, data-brand) в формы Contact Form 7.
02

Разработка динамического движка фильтров

  • Написание логики (get_category_filters()), которая перебирает глобальные атрибуты и выводит только реально существующие в категории характеристики.
  • Реализация серверной фильтрации через pre_get_posts с сериализацией состояния в URL.
03

AJAX-оптимизация и UX

  • Разработка 5 нативных эндпоинтов на admin-ajax для живого поиска и фильтрации проектов/брендов.
  • Интеграция history.pushState для сохранения URL при AJAX-запросах (ссылки остаются рабочими при перезагрузке).
04

Data Engineering (Сбор и импорт)

  • Написание сценариев на Playwright для парсинга сайтов производителей.
  • Прямой маппинг данных (включая машинный перевод зарубежных описаний через DeepL API) в CSV-файлы, заголовки которых строго соответствуют контракту схемы импорта WooCommerce.
05

Фронтенд и UI-сборка

  • Разработка независимого слоя вёрстки на Gulp + Webpack (BEM, ES-модули).
  • Создание «бесшовного» адаптива без ступенчатых медиазапросов с помощью миксинов adaptiveValue() для плавной интерполяции типографики и отступов.

Процесс

Решения

Витрина без цен и корзины

Каталог построен на WooCommerce — ради его модели данных: товары, иерархические категории, глобальные атрибуты, медиа-галереи, штатная админка, к которой контент-менеджер клиента привыкает за час.

Но «магазин без магазина» я делал не отключением корзины постфактум. Гасить цену фильтрами вроде woocommerce_get_price_html — путь, на котором цена рано или поздно всплывает: в микроразметке, в письме, в виджете стороннего плагина. Вместо этого слой рендеринга заменён целиком: content-product.php, single-product.php и taxonomy-product-cat.php переопределены собственными шаблонами, которые просто не вызывают хуки цены и корзины. Коммерческий блок не «спрятан» — его нет в выводе.

На месте кнопки «в корзину» — лид-кнопка «Узнать цену», которая несёт в себе контекст: data-product и data-brand. При открытии попапа эти значения подставляются в скрытые поля формы, и менеджер получает заявку сразу с привязкой к конкретному товару и бренду — без переспрашивания «а что именно вы смотрели». На отправку формы повешена цель Яндекс.Метрики, так что конверсия каталога измерима на уровне отдельных карточек.

Фильтры, которые вычисляются под категорию

Это главная инженерная часть проекта. У каждой товарной группы свой набор характеристик: у напольных покрытий — порода дерева, селекция, фаска, класс, тип соединения, наличие подложки; у дверей — совершенно другой. Держать это списком настроек под каждую категорию означало обречь клиента на ручную поддержку при каждом расширении ассортимента.

Поэтому набор фильтров выводится из данных. Функция get_category_filters() перебирает все глобальные атрибуты WooCommerce и для каждого значения проверяет, есть ли в текущей категории хотя бы один товар с этим значением. Остаются только те характеристики и только те варианты, которые реально встречаются в этой категории — атрибуты без пересечений не выводятся вообще.

Практический эффект: в «Ламинате» дизайнер видит класс износостойкости и тип соединения, в «Дверях» — свои параметры, и это работает само. Клиент заводит новый атрибут в админке и проставляет его товарам — фильтр появляется на нужных категориях без единой строки кода.

Первые три группы раскрыты по умолчанию, остальные свёрнуты в аккордеон, чтобы длинный список не съедал экран. Сайдбар липкий при скролле; на мобильных он превращается в выдвижную панель с блокировкой прокрутки под ней.

Состояние фильтров сериализуется в query-строку URL, а применяется на сервере — через pre_get_posts. Обработчик написан обобщённо: любой GET-параметр, которому соответствует существующая таксономия атрибута, превращается в условие tax_query. Ничего не захардкожено под конкретные характеристики. Побочная выгода серверной фильтрации: отфильтрованная выборка живёт по своему URL — её можно отправить клиенту в мессенджер или положить в закладки.

AJAX-слой

Пять эндпоинтов, все на admin-ajax, все возвращают готовые фрагменты разметки, отрендеренные на стороне PHP — так карточка товара описана в одном месте и выглядит одинаково, где бы она ни появилась.

ЧтоГде работает
Живой поиск по товарамвыпадающая панель в шапке, дебаунс 1 сек
Фильтр проектов по тегамкаталог реализованных проектов
Фильтр брендов по товарной группеглавная и каталог брендов
Подборка популярных товаровглавная

Поиск возвращает для каждой позиции превью, название, категорию и бренд — дизайнер узнаёт товар в выдаче, не переходя на страницу.

Фильтр проектов дополнительно синхронизирует состояние с адресной строкой через history.pushState и восстанавливает выбранный тег при загрузке. Ссылка на «проекты в Новосибирске» остаётся ссылкой — ей можно поделиться, она переживает перезагрузку и кнопку «назад».

Наполнение каталога: сбор данных → CSV → импорт

Несколько тысяч позиций руками не заводятся, а без плотно заполненных атрибутов вся конструкция с вычисляемыми фильтрами бессмысленна — фильтровать будет нечем. Поэтому каталог наполнялся автоматизированным конвейером: под каждого производителя написан отдельный сценарий сбора на Playwright.

Главное решение здесь — не «собрать, а потом разложить по полям», а писать сразу в целевую модель данных. Сборщик отдаёт CSV, заголовки которого уже представляют собой схему импорта WooCommerce:

post_title; images; attribute:pa_collection; attribute:pa_chamfer;
attribute:pa_coating; attribute:pa_texture_color; tax:product_cat

Промежуточного маппинга нет: файл загружается плагином импорта, и характеристики сразу становятся теми самыми атрибутами, из которых на странице категории собираются фильтры. Схема сборщика и схема каталога — это один контракт, а не две сущности, которые нужно синхронизировать вручную.

Каждый сценарий устроен одинаково: обход пагинации каталога, дедупликация ссылок, поштучный разбор карточек с извлечением характеристик в словарь и последующей нормализацией к единому набору полей. Товары без названия пропускаются, ошибка на отдельной странице логируется и не роняет проход целиком — на объёме в тысячи страниц это принципиально. Для сайтов, требующих авторизации, сессия переиспользуется из сохранённого состояния; описания зарубежных производителей прогоняются через DeepL с сохранением форматирования.

Бренды как самостоятельный раздел

Бренд для дизайнера — точка входа не менее важная, чем категория: часто подбор начинается с «нужен конкретный австрийский производитель». Поэтому бренд реализован как глобальный атрибут WooCommerce с собственным правилом ЧПУ и отдельным шаблоном страницы: /brand/{slug}. Описание, страна, год основания, логотип (в двух состояниях — обычном и для ховера) и фон живут в ACF-полях на самом термине.

Каталог брендов — указатель А–Я: латиница и кириллица, с корректной группировкой многобайтовых заголовков, плюс живой поиск по названию и фильтр по товарной группе. Три способа найти бренд для трёх разных сценариев: знаю название, знаю букву, знаю только что «мне нужны двери».

Избранное как мудборд

Дизайнер собирает подборку материалов под проект — это ближе к мудборду, чем к корзине. Хранение делегировано YITH Wishlist (серверное, переживает смену устройства у авторизованного пользователя), но страница избранного не использует шорткод плагина: она читает элементы напрямую и рендерит их той же карточкой, что и каталог — с кнопкой «Узнать цену».

Так избранное остаётся точкой входа в диалог, а не тупиком в виде пре-корзины. Счётчик в шапке обновляется без перезагрузки — по событиям добавления и удаления.

Вёрстка и сборка

Фронтенд собран как отдельный проект на Gulp + Webpack: 13 страниц из порядка 34 переиспользуемых партиалов, SCSS по методологии BEM.

JS — нативные ES-модули, без jQuery в слое вёрстки. Шесть отдельных конфигураций Swiper, включая связку миниатюр с основной галереей на карточке товара. Галерея проекта — lightGallery поверх justified-раскладки: фотографии интерьеров разных пропорций выстраиваются в ряды равной высоты без обрезки кадра, что для проекта про визуальное принципиально.

Сборка автоматизирует рутину: конвертация изображений в WebP с обёрткой в <picture>, SVG-спрайты, перегонка шрифтов в woff2, склейка медиазапросов, автопрефиксы, cache-busting по таймштампу.


Структура сайта

  • Каталог — вложенные категории (напольные покрытия → ламинат, кварцвинил, паркет, ковролин; мебель → диваны, столы, стулья, стеллажи, витрины), фильтры под каждую категорию, страница товара с галереей, характеристиками и связанными позициями
  • Бренды — указатель А–Я, страница бренда с описанием и его товарами
  • Проекты — каталог реализованных интерьеров с фильтрацией по тегам; страница проекта: задача, год, локация, дизайнер, фотограф, использованные материалы, галерея
  • Полезное — блог, новости, мероприятия, спецпредложения; для каждого раздела свой шаблон записи
  • Избранное, О компании, Контакты с картой салонов

Стек

Бэкенд — WordPress, WooCommerce (модель каталога, атрибуты, иерархия категорий), ACF (поля проектов, брендов, товаров), YITH Wishlist, Contact Form 7 с программной подстановкой контекста товара

Фронтенд — SCSS / BEM, нативные ES-модули, Swiper, lightGallery, noUiSlider, Tippy.js, Google Maps JS API

Наполнение каталога — Node.js, Playwright, csv-writer, DeepL API; импорт в WooCommerce через WebToffee Import Export

Сборка — Gulp 4, Webpack 5, сборка HTML из партиалов, WebP-пайплайн, SVG-спрайты, конвертация шрифтов, cache-busting

Аналитика — Яндекс.Метрика с целями на отправку форм, баннер cookie-согласия

Результаты

12

Playwright-сценариев для парсинга

4000

позиций в каталоге

0

захардкоженных фильтров в каталоге

5

кастомных AJAX-эндпоинтов

Экраны