У каждого, кто работает с VirtueMart, рано или поздно возникает вопрос: как сделать характеристики товаров удобными и для менеджеров (при заполнении), и для покупателей (при просмотре)? Я тоже задался этим вопросом и решил привести всё в порядок.

В этой статье расскажу, как я адаптировал структуру характеристик для смартфонов, добавил логическую группировку, настроил сортировку и вывел всё на страницу товара в виде аккуратной таблицы.

1. С чего всё началось

На сайте был набор полей, которые добавлялись хаотично на протяжении нескольких лет. Поля с типами I (Integer) и F (Float) содержали текст вместо чисел, из-за чего VirtueMart выдавал ошибку "field is not longer valid".

-- Пример проблемного поля
INSERT INTO `mi060816_virtuemart_customs` 
  (`custom_title`, `field_type`, `custom_value`) 
VALUES
  ('Ёмкость аккумулятора', 'I', 'Ёмкость аккумулятора');
-- Ошибка: поле типа Integer, а значение — текст!
    

Первым делом я очистил данные, убрал мусор и привёл типы полей к единому стандарту.

2. Адаптация структуры под современные смартфоны

Изучив файлы с описаниями Xiaomi 15 Ultra и Xiaomi 17 Ultra, я выделил основные группы характеристик:

Группа Количество полей
🖥️ Дисплей10
⚡ Процессор и память10
📸 Камера9
🔋 Аккумулятор и зарядка5
📶 Коммуникации6
📐 Корпус и защита4
🎵 Аудио и датчики3
📦 Комплектация3

Итого: 50 полей, сгруппированных по 8 логическим блокам.

Создание подгрупп

В VirtueMart есть поле custom_parent_id, которое отвечает за вложенность. Я создал подгруппы и перенёс в них поля:

-- Создаём подгруппы
INSERT INTO `mi060816_virtuemart_customs` 
  (`custom_parent_id`, `custom_title`, `field_type`, `ordering`) 
VALUES
  (39, '🖥️ Дисплей', 'G', 10),
  (39, '⚡ Процессор и память', 'G', 20);
    

3. Сортировка внутри групп

Оказалось, что VirtueMart не передаёт поле ordering в объекты полей, поэтому сортировка через $field->ordering не работала. Пришлось пойти другим путём:

  1. Загружать ordering из базы данных отдельным запросом
  2. Сортировать поля с помощью usort() с использованием этих данных
// Загружаем ordering из базы
$db = JFactory::getDbo();
$query = $db->getQuery(true)
    ->select('virtuemart_custom_id, ordering')
    ->from('#__virtuemart_customs')
    ->where('virtuemart_custom_id IN (' . implode(',', $customIds) . ')');
$dbOrdering = $db->loadAssocList('virtuemart_custom_id', 'ordering');

// Сортируем поля внутри группы
usort($group['fields'], function($a, $b) use ($dbOrdering) {
    $orderA = $dbOrdering[$a->virtuemart_custom_id] ?? 999;
    $orderB = $dbOrdering[$b->virtuemart_custom_id] ?? 999;
    return $orderA <=> $orderB;
});
    

4. Оформление: таблица с двумя колонками

Для вывода характеристик я выбрал стиль таблицы с двумя колонками:

  • Левая колонка: название характеристики
  • Правая колонка: значение
.specs-item {
    display: table-row;
    border-bottom: 1px solid #f0f0f0;
}
.specs-label {
    display: table-cell;
    padding: 8px 15px 8px 0;
    font-weight: 500;
    width: 40%;
    background: #f9f9f9;
}
.specs-value {
    display: table-cell;
    padding: 8px 0 8px 15px;
    width: 60%;
}
    

Адаптивный вариант для мобильных устройств делает просмотр характеристик комфортным на любом экране.

5. Булевы значения: 1 → "✅ Есть", 0 → "❌ Нет"

Поля с типом B (Boolean) отображались как 1 и 0. Это неинформативно. Я добавил преобразование:

if ($field->field_type == 'B') {
    $value = ($value == '1') ? '✅ Есть' : '❌ Нет';
}
    

Теперь покупатель видит понятные иконки вместо цифр.

6. Борьба с дублированием "Гарантии"

Поле "Гарантия" (id=7) отображалось дважды: в корзине и в характеристиках. Оказалось, что в таблице mi060816_virtuemart_product_customfields были две одинаковые записи для одного товара.

-- Проверяем дубли
SELECT * FROM `mi060816_virtuemart_product_customfields` 
WHERE virtuemart_product_id = 12591 AND virtuemart_custom_id = 7;
-- Две записи с одинаковым значением!

-- Удаляем дубликат
DELETE FROM `mi060816_virtuemart_product_customfields` 
WHERE virtuemart_customfield_id = 4451730;
    

После удаления дубля поле отображается ровно один раз.

7. Итоговая сортировка

Вот как выглядят характеристики Xiaomi 17 Ultra на странице товара:

⚡ Процессор и память
  Чипсет                         Snapdragon 8 Elite Gen 5
  Процессор (CPU)                2x4.6 ГГц + 6x3.62 ГГц
  Техпроцесс (нм)                3
  Графический процессор (GPU)    Adreno 840
  Оперативная память (ГБ)        16
  Тип ОЗУ                        LPDDR5X
  Хранилище (ГБ)                 512
  Тип накопителя                 UFS 4.1

🖥️ Дисплей
  Диагональ экрана (дюйм)        6.9
  Разрешение                     2608 x 1200
  Тип матрицы                    LTPO AMOLED
  Частота обновления (Гц)        120
  Яркость (нит)                  3500
  Плотность пикселей (ppi)       416
  HDR поддержка                  HDR10+, Dolby Vision
  ...
    

8. Что ещё было сделано

Система контроля версий (Git)

Я инициализировал Git-репозиторий для отслеживания изменений в коде. Размер репозитория удалось держать под контролем — всего 27 МБ.

# Исключили из Git всё лишнее
echo "images/" >> .gitignore
echo "cache/" >> .gitignore
echo "*.sql" >> .gitignore
    

Защита файлов

Вернул защиту файлов через chattr +i, чтобы случайно не повредить важные файлы.

Что в итоге

Параметр ДО ПОСЛЕ
Количество полей3550
Группировка✅ (8 групп)
Сортировка
Булевы значения1 / 0✅ Есть / ❌ Нет
Ошибки"field is not longer valid"✅ Отсутствуют
Размер Git-репозитория1.7 ГБ27 МБ

Заключение

Структура характеристик теперь полностью адаптирована под современные смартфоны. Менеджерам стало проще заполнять товары, а покупателям — выбирать нужную модель.

Весь процесс занял несколько дней и потребовал внимания к деталям, но результат того стоил. Если у вас есть вопросы или вы хотите повторить что-то похожее на своём сайте — пишите, буду рад помочь!

P.S. Полный код всех изменений доступен в Git-репозитории. 🤓