Добавление вкладки на страницу заказа в Magento 2

17 май 2017

Автоматизация затрагивает всё: расчет стоимости доставки, создание и отправка накладной, получение PDF этикетки, отслеживание статусов и многое другое.

Выводить всю информацию (кнопки, статусы, формы отправки) на базовом виде заказа - не удобно, можно вынести всё в дополнительную вкладку.

Пример вкладки для Magento 1:

Опираясь на опыт с Magento 1, сделаем аналогичный функционал для Magento 2.

Создание модуля

Создадим базу модуля, подключив его через composer к нашему магазину. В нашем примере — модуль разрабатывается как готовый пакет (package) для Magento 2. В тег name вписывается название пакета, по которому модуль будет искаться в репозиториях. M2 работает с autoload (PSR-4), что упрощает работу с различными внешними модулями.

composer.json

{
  "name": "vendor/module",
  "type": "magento2-module",
  "license": "proprietary",
  "homepage": "http://www.example.ru/magento2-module.html",
  "description": "Модуль доставки",
  "keywords": [
    "magento",
    "module",
    "delivery",
    "vendor"
  ],
  "authors": [
    {
      "name": "Vendor",
      "email": "[email protected]",
      "homepage": "http://www.vendor.ru/"
    }
  ],
  "require": {
    "php": "~5.5.0|~5.6.0|~7.0.0",
    "magento/framework": "*"
  },
  "repositories": [
    {
      "type": "composer",
      "url": "https://repo.magento.com"
    }
  ],
  "autoload": {
    "files": [
      "registration.php"
    ],
    "psr-4": {
      "Vendor\\Module\\": ""
    }
  }
}

Для работы модуля нужно задать его зарегистрировать в системе как компонент.

registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::Module, 'Vendor_Module', __DIR__
);

и etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Vendor_Module" setup_version="2.0.0">
    </module>
</config>

Вторым шагом добавляем layout

Добавляем блок в нужную секцию layout.

view/adminhtml/layout/sales_order_view.xml

<?xml version="1.0"?>
    <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left"
          xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="left">
            <referenceBlock name="sales_order_tabs">
                <action method="addTab">
                    <argument name="name" xsi:type="string">Vendor Module</argument>
                    <argument name="block"
                              xsi:type="string">Vendor\Module\Block\Adminhtml\Order\View\Tab\Module</argument>
                </action>
            </referenceBlock>
        </referenceContainer>
    </body>
</page>

В теге argument мы передаем название блока, который будет подключен в качестве дополнительной вкладки на странице заказов

Определим сам блок:

Block/Adminhtml/Order/View/Tab/Module.php

<?php

namespace Vendor\Module\Block\Adminhtml\Order\View\Tab;

class Module extends \Magento\Backend\Block\Template implements \Magento\Backend\Block\Widget\Tab\TabInterface {

<span style="color: #008800; font-weight: bold">protected</span> <span style="color: #996633">$_template</span> <span style="color: #333333">=</span> <span style="background-color: #fff0f0">&#39;order/view/tab/module.phtml&#39;</span>;

<span style="color: #DD4422">/**</span>

* * @var \Magento\Framework\Registry */ protected $coreRegistry = null;

<span style="color: #DD4422">/**</span>

* @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\Registry $registry * @param array $data */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Framework\Registry $registry, array $data = [] ) { $this->coreRegistry = $registry; parent::__construct($context, $data); }

<span style="color: #008800; font-weight: bold">public</span> <span style="color: #008800; font-weight: bold">function</span> <span style="color: #0066BB; font-weight: bold">getOrder</span>()
{
    <span style="color: #008800; font-weight: bold">return</span> <span style="color: #996633">$this</span><span style="color: #333333">-&gt;</span><span style="color: #0000CC">coreRegistry</span><span style="color: #333333">-&gt;</span><span style="color: #0000CC">registry</span>(<span style="background-color: #fff0f0">&#39;current_order&#39;</span>);
}

<span style="color: #DD4422">/**</span>

* {@inheritdoc} */ public function getTabLabel() { return __(‘Module’); }

<span style="color: #DD4422">/**</span>

* {@inheritdoc} */ public function getTabTitle() { return __(‘Module’); }

<span style="color: #DD4422">/**</span>

* {@inheritdoc} */ public function canShowTab() { return true; }

<span style="color: #DD4422">/**</span>

* {@inheritdoc} */ public function isHidden() { return false; }

<span style="color: #DD4422">/**</span>

* * @return string */ public function getTabClass() { return ‘ajax only’; }

<span style="color: #DD4422">/**</span>

* * @return string */ public function getClass() { return $this->getTabClass(); }

<span style="color: #DD4422">/**</span>

* * @return string / public function getTabUrl() { return $this->getUrl(‘vendor_module//module’, [‘_current’ => true]); }

}

В функции $this->getUrl для получения url мы передаем параметры: адрес нужного нам роута 'vendor_module/*/module' и дополнительный массив параметров ['_current' => true].

Символ * в адресе роута 'vendor_module/*/module означает передачу текущего используемого роута. То есть, если мы находимся на контроллере sales, то вместо * в итоговом адресе будет тот же контроллер.

Параметр '_current' => true отвечает за передачу таких же дополнительных параметрах к основному адресу при получении ссылки. К примеру, если мы находимся на странице с параметрами order_id/12, то эти же параметры будут переданы при генерации url. Если мы не указываем нужный нам путь, то будет использован тот же модуль, контроллер и action.

Функцию canShowTab можно использовать для того, чтобы в нее передавать условия показа вкладки. К примеру - проверять, ваш ли метод доставки используется в этом заказе.

К примеру, определить принадлежность по $order в carrier можно так:

/**
     *
     * @param \Magento\Sales\Model\Order $order
     * @return boolean
     */
    public function isShippedBy(\Magento\Sales\Model\Order $order)
    {
        if (strpos($order->getShippingMethod(), $this->_code . '_') !== false) {
            return true;
        }
        return false;
    }

Подошли к важному отличию Magento 2 от Magento 1 - в панели администрирования, данные по вкладкам грузятся ajax'ом, поэтому для модуля нужно создать роут (route)

etc/adminhtml/routes.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="vendor_module" frontName="vendor_module">
            <module name="Vendor_Module"/>
        </route>
    </router>
</config>

Создадим контроллер, который будет отвечать за вкладку:

Controller/Adminhtml/Order/CustomTab.php

<?php

namespace Vendor\Module\Controller\Adminhtml\Order;

use Magento\Backend\App\Action; use Magento\Sales\Api\OrderManagementInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Psr\Log\LoggerInterface;

class Module extends \Magento\Sales\Controller\Adminhtml\Order { /** * @var \Magento\Framework\View\LayoutFactory */ protected $layoutFactory;

<span style="color: #DD4422">/**</span>

* @param Action\Context $context * @param \Magento\Framework\Registry $coreRegistry * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory * @param \Magento\Framework\Translate\InlineInterface $translateInline * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory * @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory * @param OrderManagementInterface $orderManagement * @param OrderRepositoryInterface $orderRepository * @param LoggerInterface $logger * @param \Magento\Framework\View\LayoutFactory $layoutFactory * * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ public function __construct( Action\Context $context, \Magento\Framework\Registry $coreRegistry, \Magento\Framework\App\Response\Http\FileFactory $fileFactory, \Magento\Framework\Translate\InlineInterface $translateInline, \Magento\Framework\View\Result\PageFactory $resultPageFactory, \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory, \Magento\Framework\Controller\Result\RawFactory $resultRawFactory, OrderManagementInterface $orderManagement, OrderRepositoryInterface $orderRepository, LoggerInterface $logger, \Magento\Framework\View\LayoutFactory $layoutFactory ) { $this->layoutFactory = $layoutFactory; parent::__construct( $context, $coreRegistry, $fileFactory, $translateInline, $resultPageFactory, $resultJsonFactory, $resultLayoutFactory, $resultRawFactory, $orderManagement, $orderRepository, $logger ); }

<span style="color: #008800; font-weight: bold">public</span> <span style="color: #008800; font-weight: bold">function</span> <span style="color: #0066BB; font-weight: bold">execute</span>()
{
    <span style="color: #996633">$this</span><span style="color: #333333">-&gt;</span><span style="color: #0000CC">_initOrder</span>();
    <span style="color: #996633">$layout</span> <span style="color: #333333">=</span> <span style="color: #996633">$this</span><span style="color: #333333">-&gt;</span><span style="color: #0000CC">layoutFactory</span><span style="color: #333333">-&gt;</span><span style="color: #0000CC">create</span>();
    <span style="color: #996633">$html</span> <span style="color: #333333">=</span> <span style="color: #996633">$layout</span><span style="color: #333333">-&gt;</span><span style="color: #0000CC">createBlock</span>(<span style="background-color: #fff0f0">&#39;Vendor\Module\Block\Adminhtml\Order\View\Tab\Module&#39;</span>)
        <span style="color: #333333">-&gt;</span><span style="color: #0000CC">toHtml</span>();
    <span style="color: #996633">$this</span><span style="color: #333333">-&gt;</span><span style="color: #0000CC">_translateInline</span><span style="color: #333333">-&gt;</span><span style="color: #0000CC">processResponseBody</span>(<span style="color: #996633">$html</span>);
    <span style="color: #DD4422">/** @var \Magento\Framework\Controller\Result\Raw $resultRaw */</span>
    <span style="color: #996633">$resultRaw</span> <span style="color: #333333">=</span> <span style="color: #996633">$this</span><span style="color: #333333">-&gt;</span><span style="color: #0000CC">resultRawFactory</span><span style="color: #333333">-&gt;</span><span style="color: #0000CC">create</span>();
    <span style="color: #996633">$resultRaw</span><span style="color: #333333">-&gt;</span><span style="color: #0000CC">setContents</span>(<span style="color: #996633">$html</span>);
    <span style="color: #008800; font-weight: bold">return</span> <span style="color: #996633">$resultRaw</span>;
}

}

В данном контроллере функция execute() непосредственно отвечает за выполнение action.

Отметим еще отличие Magento 2 от Magento 1: в M2 каждый action на контроллер делается как отдельный файл. В M1 контроллер мог включать в себя несколько action сразу.

Создадим шаблон вывода:

order/view/tab/module.phtml

<section class="admin__page-section module-tab-content">
    <h1>Привет из Санкт-Петербурга</h1>
</section>

Делаем очистку кеша

php bin/magento cache:clean

Заходим в заказ и проверяем:

Кликаем:

Conclusion

Готово! Надеемся, что данная статья расширила ваши представления о принципах разработки на Magento 2.

Choose languageRU

© 2009—2025 Mygento. Все права защищены. Политика конфиденциальности

Menu Menu Menu

Аккредитованная
ИТ-компания