У каждого, кто работает с 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 не работала. Пришлось пойти другим путём:
- Загружать
orderingиз базы данных отдельным запросом - Сортировать поля с помощью
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, чтобы случайно не повредить важные файлы.
Что в итоге
| Параметр | ДО | ПОСЛЕ |
|---|---|---|
| Количество полей | 35 | 50 |
| Группировка | ❌ | ✅ (8 групп) |
| Сортировка | ❌ | ✅ |
| Булевы значения | 1 / 0 | ✅ Есть / ❌ Нет |
| Ошибки | "field is not longer valid" | ✅ Отсутствуют |
| Размер Git-репозитория | 1.7 ГБ | 27 МБ |
Заключение
Структура характеристик теперь полностью адаптирована под современные смартфоны. Менеджерам стало проще заполнять товары, а покупателям — выбирать нужную модель.
Весь процесс занял несколько дней и потребовал внимания к деталям, но результат того стоил. Если у вас есть вопросы или вы хотите повторить что-то похожее на своём сайте — пишите, буду рад помочь!
P.S. Полный код всех изменений доступен в Git-репозитории. 🤓