Разработка кастомной темы OpenCart

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомной темы OpenCart
Средняя
~5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка кастомной темы OpenCart

Готовые темы из маркетплейса OpenCart — компромисс между скоростью запуска и соответствием бренду. Кастомная тема даёт полный контроль над HTML-структурой, CSS, производительностью и доступностью. При грамотной реализации кастомная тема быстрее готовых тем за счёт отсутствия неиспользуемого кода.

Архитектура тем в OpenCart 4.x

OpenCart 4.x использует Twig как шаблонизатор вместо PHP-шаблонов в версиях 3.x. Это изменило подход к разработке тем.

Структура кастомной темы:

catalog/view/theme/{theme_name}/
├── template/
│   ├── common/
│   │   ├── header.twig
│   │   ├── footer.twig
│   │   ├── cart.twig
│   │   └── search.twig
│   ├── product/
│   │   ├── category.twig       ← каталог
│   │   ├── product.twig        ← карточка товара
│   │   ├── search.twig
│   │   └── special.twig
│   ├── checkout/
│   │   ├── cart.twig
│   │   └── checkout.twig
│   └── account/
│       ├── login.twig
│       ├── register.twig
│       └── order.twig
└── stylesheet/
    └── (для минимальных переопределений)

CSS и JS подключаются не через папку темы, а через события и конфигурацию контроллера.

Наследование от default-темы

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

Admin → System → Settings → Store → Theme → Parent Theme: default

Тогда если в папке кастомной темы нет нужного файла — OpenCart берёт его из default. Это ускоряет разработку: переопределяем только изменённые шаблоны.

Регистрация темы

// Создаём файл extension/myshop/catalog/controller/startup/theme.php
// (или через event system)

// В таблице oc_extension регистрируем тему:
INSERT INTO `oc_extension` (`extension_id`, `extension`, `type`, `code`)
VALUES (NULL, 'opencart', 'theme', 'myshop');

// В oc_setting прописываем путь:
INSERT INTO `oc_setting` (`store_id`, `code`, `key`, `value`)
VALUES (0, 'config', 'config_theme', 'myshop');

Или через Extension Installer, если тема упакована как расширение.

Базовый шаблон header.twig

<!DOCTYPE html>
<html lang="{{ lang }}" dir="{{ direction }}">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ title }}</title>
    <meta name="description" content="{{ description }}">

    {% if canonical %}
    <link rel="canonical" href="{{ canonical }}">
    {% endif %}

    {# Подключаем Bootstrap или собственный CSS #}
    <link rel="stylesheet" href="{{ stylesheet }}">

    {% for style in styles %}
    <link rel="stylesheet" type="text/css" href="{{ style.href }}" media="{{ style.media }}">
    {% endfor %}
</head>
<body class="{{ class }}">

<header class="site-header">
    <div class="container">
        <a class="site-logo" href="{{ home }}">
            {% if logo %}
            <img src="{{ logo }}" alt="{{ name }}" loading="eager">
            {% else %}
            <span>{{ name }}</span>
            {% endif %}
        </a>

        <nav class="site-nav">
            {% for category in categories %}
            <a href="{{ category.href }}" {% if category.children %}class="has-dropdown"{% endif %}>
                {{ category.name }}
                {% if category.children %}
                <ul class="dropdown">
                    {% for child in category.children %}
                    <li><a href="{{ child.href }}">{{ child.name }}</a></li>
                    {% endfor %}
                </ul>
                {% endif %}
            </a>
            {% endfor %}
        </nav>

        <div class="header-actions">
            <a href="{{ cart }}" class="cart-icon" data-count="{{ cart_count }}">
                Корзина ({{ cart_quantity }})
            </a>
            {% if logged %}
            <a href="{{ account }}">Кабинет</a>
            {% else %}
            <a href="{{ login }}">Войти</a>
            {% endif %}
        </div>
    </div>
</header>

Карточка товара — product.twig

Ключевые переменные, доступные в шаблоне карточки товара:

{# Основные данные #}
{{ product_id }}, {{ name }}, {{ description }}, {{ model }}
{{ price }}, {{ special }}, {{ tax }}
{{ rating }}, {{ reviews }}
{{ manufacturer }}, {{ manufacturer_href }}

{# Изображения #}
{{ thumb }}       {# основное изображение #}
{{ images }}      {# массив дополнительных изображений #}

{# Опции #}
{% for option in options %}
    {{ option.name }}, {{ option.type }}
    {% for value in option.product_option_value %}
        {{ value.name }}, {{ value.price }}
    {% endfor %}
{% endfor %}

{# SEO #}
{{ meta_title }}, {{ meta_description }}, {{ meta_keyword }}
{{ canonical }}

Шаблон с галереей и выбором опций:

<section class="product-page">
    <div class="product-gallery">
        <img id="product-image-main"
             src="{{ thumb }}"
             alt="{{ name }}"
             loading="eager"
             fetchpriority="high">

        <div class="thumbnails">
            <img src="{{ thumb }}" data-src="{{ image }}" class="thumb active">
            {% for image in images %}
            <img src="{{ image.thumb }}" data-src="{{ image.popup }}" class="thumb">
            {% endfor %}
        </div>
    </div>

    <div class="product-info">
        <h1>{{ name }}</h1>

        <div class="product-price">
            {% if special %}
            <span class="price-old">{{ price }}</span>
            <span class="price-new">{{ special }}</span>
            {% else %}
            <span class="price-current">{{ price }}</span>
            {% endif %}
        </div>

        {% for option in options %}
        <div class="product-option">
            <label>{{ option.name }}{% if option.required %} *{% endif %}</label>

            {% if option.type == 'select' %}
            <select name="option[{{ option.product_option_id }}]">
                <option value="">— Выберите —</option>
                {% for value in option.product_option_value %}
                <option value="{{ value.product_option_value_id }}"
                        {% if value.price %}data-price="{{ value.price }}"{% endif %}>
                    {{ value.name }}{% if value.price %} (+ {{ value.price }}){% endif %}
                </option>
                {% endfor %}
            </select>

            {% elseif option.type == 'radio' %}
            <div class="option-radios">
                {% for value in option.product_option_value %}
                <label class="option-radio">
                    <input type="radio"
                           name="option[{{ option.product_option_id }}]"
                           value="{{ value.product_option_value_id }}">
                    {{ value.name }}
                </label>
                {% endfor %}
            </div>
            {% endif %}
        </div>
        {% endfor %}

        <div class="quantity-row">
            <input type="number" name="quantity" value="1" min="1">
            <button id="btn-cart" data-id="{{ product_id }}">В корзину</button>
        </div>
    </div>
</section>

JavaScript в теме

OpenCart 4.x использует собственный AJAX для корзины. Расширение через события:

// catalog/view/javascript/myshop/theme.js

// Добавление в корзину
document.querySelectorAll('[data-id]').forEach(btn => {
    btn.addEventListener('click', async function() {
        const productId = this.dataset.id
        const quantity = document.querySelector('[name="quantity"]')?.value || 1
        const options = {}

        document.querySelectorAll('[name^="option"]').forEach(el => {
            const match = el.name.match(/option\[(\d+)\]/)
            if (match && el.value) {
                options[match[1]] = el.value
            }
        })

        const response = await fetch('index.php?route=checkout/cart.add', {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({
                product_id: productId,
                quantity,
                ...Object.fromEntries(
                    Object.entries(options).map(([k, v]) => [`option[${k}]`, v])
                )
            })
        })

        const data = await response.json()

        if (data.success) {
            updateCartWidget(data)
            showNotification(data.success)
        } else {
            showErrors(data.error)
        }
    })
})

Подключение ресурсов темы

Скрипты и стили подключаются в контроллере через систему событий или напрямую в шаблоне через переменные:

// В event-обработчике или startup-контроллере темы:
$this->document->addStyle(
    'catalog/view/javascript/myshop/css/theme.css',
    'screen',
    100  // sort_order
);

$this->document->addScript(
    'catalog/view/javascript/myshop/js/theme.js',
    'footer',
    100
);

Для production — сборка через Vite или Webpack: минификация, хеш-суффикс для cache busting:

# package.json в корне темы
npm run build
# Генерирует: theme.abc123.css, theme.abc123.js

Адаптивная верстка

OpenCart-тема должна корректно работать на мобильных. Breakpoints:

/* Mobile-first approach */
.product-grid { grid-template-columns: repeat(2, 1fr); gap: 16px; }

@media (min-width: 768px) {
    .product-grid { grid-template-columns: repeat(3, 1fr); }
}

@media (min-width: 1200px) {
    .product-grid { grid-template-columns: repeat(4, 1fr); }
}

Изображения — с loading="lazy" для всего, кроме первого экрана, srcset для разных плотностей экрана.

Сроки разработки темы

  • Верстка header + footer + навигация: 1–2 дня
  • Главная страница (баннер, категории, хиты): 1–2 дня
  • Каталог с фильтрами: 1–2 дня
  • Карточка товара с галереей + опциями: 1–2 дня
  • Корзина + оформление заказа: 2–3 дня
  • Личный кабинет + страница заказа: 1–2 дня
  • Адаптивность + кроссбраузерность: 1–2 дня

Итого: 1,5–2 недели при наличии готового дизайна в Figma. Без дизайна — добавить 1–2 недели на дизайн.