Как добавить настраиваемое поле в параметры в расширении расширенных параметров продукта
Опубликовано: 2020-09-08Из этой статьи вы узнаете, как создать поле «GTIN» для пользовательских параметров продукта, отобразить его на странице продукта и отобразить в заказе.
Без лишних слов, давайте перейдем к пошаговым инструкциям.
Оглавление
- Шаг 1. Создать новый модуль
- Шаг 2. Добавьте наше новое поле в базу данных
- Шаг 3. Добавьте логику для работы с бэкендом
- Шаг №4. Показать наше поле на странице продукта
- Шаг № 5. Добавьте наши атрибутивные данные в детали заказа в базе данных
- Шаг №6. Отображение данных на странице заказов в панели администратора
Шаг 1. Создать новый модуль
Мы подробно описали, как создать модуль в этой статье. Поэтому давайте пропустим эту часть и сразу перейдем к коду, который вам понадобится для создания надстройки:
1.composer.json
{ "name": "mageworx/module-optiongtin", "description": "N/A", "require": { "magento/framework" : ">=100.1.0 <101", "magento/module-catalog": ">=101.0.0 <104" }, "type": "magento2-module", "version": "1.0.0", "license": [ "OSL-3.0", "AFL-3.0" ], "autoload": { "files": [ "registration.php" ], "psr-4": { "VendorName\\OptionGtin\\": "" } } }
2.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="VendorName_OptionGtin" setup_version="1.0.0"> <sequence> <module name="Magento_Catalog"/> <module name="MageWorx_OptionBase"/> </sequence> </module> </config>
3.registration.php
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'VendorName_OptionGtin', __DIR__ );
Шаг 2. Добавьте наше новое поле в базу данных
После того, как мы создали пустой модуль, пришло время создать новое поле «GTIN» и добавить его в базу данных в соответствующей таблице. Поскольку мы добавляем поле для значений опций, нам понадобится таблица «catalog_product_option».
Создадим следующий файл:
app/code/VendorName/OptionGtin/Setup/InstallSchema.php
<?php namespace VendorName\OptionGtin\Setup; use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; use Magento\Framework\DB\Ddl\Table; class InstallSchema implements InstallSchemaInterface { public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); $setup->getConnection()->addColumn( $setup->getTable('catalog_product_option'), 'gtin', [ 'type' => Table::TYPE_TEXT, 'nullable' => true, 'default' => null, 'comment' => 'Gtin (added by VendorName Option Gtin)', ] ); $setup->endSetup(); } }
Шаг 3. Добавьте логику для работы с бэкендом
Мы будем использовать механизм модификатора пула, чтобы добавить наше новое поле.
Теперь добавьте следующий файл:
app/code/VendorName/OptionGtin/etc/adminhtml/di.xml
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <virtualType name="MageWorx\OptionBase\Ui\DataProvider\Product\Form\Modifier\Pool"> <arguments> <argument name="modifiers" xsi:type="array"> <item name="mageworx-option-gtin" xsi:type="array"> <item name="class" xsi:type="string">VendorName\OptionGtin\Ui\DataProvider\Product\Form\Modifier\OptionGtin</item> <item name="sortOrder" xsi:type="number">72</item> </item> </argument> </arguments> </virtualType> </config>
Здесь давайте добавим наш модификатор в общий пул расширения Advanced Product Options―”MageWorx\OptionBase\Ui\DataProvider\Product\Form\Modifier\Pool”. «VendorName\OptionGtin\Ui\DataProvider\Product\Form\Modifier\OptionGtin» — это наш модификатор класса.
Ниже приведен код, позволяющий добавить наше поле в app/code/VendorName/OptionGtin/Ui/DataProvider/Product/Form/Modifier/OptionGtin.php
:
<?php namespace VendorName\OptionGtin\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions; use Magento\Ui\Component\Form\Element\Input; use Magento\Ui\Component\Form\Element\DataType\Number; use Magento\Ui\Component\Form\Field; use MageWorx\OptionBase\Ui\DataProvider\Product\Form\Modifier\ModifierInterface; class OptionGtin extends AbstractModifier implements ModifierInterface { /** * @var array */ protected $meta = []; /** * {@inheritdoc} */ public function modifyData(array $data) { return $data; } /** * {@inheritdoc} */ public function modifyMeta(array $meta) { $this->meta = $meta; $this->addFields(); return $this->meta; } /** * Adds fields to the meta-data */ protected function addFields() { $groupCustomOptionsName = CustomOptions::GROUP_CUSTOM_OPTIONS_NAME; $optionContainerName = CustomOptions::CONTAINER_OPTION; $commonOptionContainerName = CustomOptions::CONTAINER_COMMON_NAME; // Add fields to the option $optionFeaturesFields = $this->getOptionGtinFieldsConfig(); $this->meta[$groupCustomOptionsName]['children']['options']['children']['record']['children'] [$optionContainerName]['children'][$commonOptionContainerName]['children'] = array_replace_recursive( $this->meta[$groupCustomOptionsName]['children']['options']['children']['record']['children'] [$optionContainerName]['children'][$commonOptionContainerName]['children'], $optionFeaturesFields ); } /** * The custom option fields config * * @return array */ protected function getOptionGtinFieldsConfig() { $fields['gtin'] = $this->getGtinFieldConfig(); return $fields; } /** * Get gtin field config * * @return array */ protected function getGtinFieldConfig() { return [ 'arguments' => [ 'data' => [ 'config' => [ 'label' => __('GTIN'), 'componentType' => Field::NAME, 'formElement' => Input::NAME, 'dataType' => Number::NAME, 'dataScope' => 'gtin', 'sortOrder' => 65 ], ], ], ]; } /** * Check is current modifier for the product only * * @return bool */ public function isProductScopeOnly() { return false; } /** * Get sort order of modifier to load modifiers in the right order * * @return int */ public function getSortOrder() { return 32; } }
Теперь попробуем установить расширение и проверить, что все отображается:
- Модуль php bin/magento: включить VendorName_OptionGtin
- Установка php bin/magento:обновление
- Кэш php bin/magento: флеш
Наше новое поле было успешно добавлено:
Шаг №4. Показать наше поле на странице продукта
Расширение Mageworx Advanced Product Options уже имеет все необходимое для отображения и работы с атрибутами, которые добавляет наш модуль. Все, что нам нужно сделать, это добавить новый атрибут в общий набор данных.
Наш модуль MageWorx_OptionBase уже использует метод getExtendedOptionsConfig()
. Он собирает и отображает все настраиваемые атрибуты в блоке на интерфейсе. Откройте app/code/MageWorx/OptionBase/Block/Product/View/Options.php
, чтобы увидеть, как он реализуется.
Начнем с создания модели с нашим атрибутом:
app/code/VendorName/OptionGtin/Model/Attriburte/Option/Gtin.php
<?php namespace VendorName\OptionGtin\Model\Attribute\Option; use MageWorx\OptionBase\Model\Product\Option\AbstractAttribute; class Gtin extends AbstractAttribute { /** * @return string */ public function getName() { return 'gtin'; } }
Теперь используйте механизм «внедрения зависимостей» и добавьте наш атрибут в набор данных общих атрибутов расширения Advanced Product Options.
app/code/VendorName/OptionGtin/etc/di.xml
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <!-- Data --> <type name="MageWorx\OptionBase\Model\Product\Option\Attributes"> <arguments> <argument name="data" xsi:type="array"> <item name="gtin" xsi:type="object">VendorName\OptionGtin\Model\Attribute\Option\Gtin</item> </argument> </arguments> </type> </config>
Другими словами, открыв MageWorx\OptionBase\Model\Product\Option\Attributes
, вы увидите, что он просто собирает все объекты атрибутов в общий набор данных.
Чтобы отобразить данные нашего нового атрибута «GTIN», мы решили использовать функцию firstrun()
из app/code/MageWorx/OptionFeatures/view/base/web/js/catalog/product/features.js
. В нем уже есть вся необходимая реализация, которая лучше всего подходит для нашего примера. Чтобы избежать перезаписи всего файла, мы применим механизм «JavaScript mixins», который поможет нам изменить только нужную функцию.
Создайте следующий файл и определите в нем наш миксин: app/code/VendorName/OptionGtin/view/frontend/requirejs-config.js
var config = { config: { mixins: { 'MageWorx_OptionFeatures/js/catalog/product/features': { 'VendorName_OptionGtin/js/catalog/product/features-gtin-mixin' : true } } } };
Здесь MageWorx_OptionFeatures/js/catalog/product/features
— это корень нашего файла, метод которого нам нужно переписать. VendorName_OptionGtin/js/catalog/product/features-gtin-mixin
— это файл, в котором мы перепишем метод.
Итак, давайте создадим его: app/code/VendorName/OptionGtin/view/frontend/web/js/catalog/product/features-gtin-mixin.js
define([ 'jquery', 'jquery/ui', 'mage/utils/wrapper' ], function ($, wrapper) { 'use strict'; return function (widget) { $.widget('mageworx.optionFeatures', widget, { /** * Triggers one time at first run (from base.js) * @param optionConfig * @param productConfig * @param base * @param self */ firstRun: function firstRun(optionConfig, productConfig, base, self) { //shareable link $('#mageworx_shareable_hint_icon').qtip({ content: { text: this.options.shareable_link_hint_text }, style: { classes: 'qtip-light' }, position: { target: false } }); $('#mageworx_shareable_link').on('click', function () { try { self.copyTextToClipboard(self.getShareableLink(base)); $('.mageworx-shareable-link-container').hide(); $('.mageworx-shareable-link-success-container').show(); setTimeout(function () { $('.mageworx-shareable-link-container').show(); $('.mageworx-shareable-link-success-container').hide(); }, 2000); } catch (error) { console.log('Something goes wrong. Unable to copy'); } }); setTimeout(function () { // Qty input $('.mageworx-option-qty').each(function () { $(this).on('change', function () { var optionInput = $("[data-selector='" + $(this).attr('data-parent-selector') + "']"); optionInput.trigger('change'); }); }); }, 500); // Option\Value Description & tooltip var extendedOptionsConfig = typeof base.options.extendedOptionsConfig != 'undefined' ? base.options.extendedOptionsConfig : {}; for (var option_id in optionConfig) { if (!optionConfig.hasOwnProperty(option_id)) { continue; } var description = extendedOptionsConfig[option_id]['description'], gtin = extendedOptionsConfig[option_id]['gtin'], gtinTitle = "Global Trade Item Number: ", $option = base.getOptionHtmlById(option_id); if (1 > $option.length) { console.log('Empty option container for option with id: ' + option_id); continue; } var $label = $option.find('label'); if(gtin != null && gtin.length > 0) { if ($label.length > 0) { $label .first() .after($('<p class="option-gtin-text"><span>' + gtinTitle + '</span>' + gtin + '</p>')); } else { $label = $option.find('span'); $label .first() .parent() .after($('<p class="option-gtin-text"><span>' + gtinTitle + '</span>' + gtin + '</p>')); } } if (this.options.option_description_enabled && !_.isEmpty(extendedOptionsConfig[option_id]['description'])) { if (this.options.option_description_mode == this.options.option_description_modes.tooltip) { var $element = $option.find('label span') .first(); if ($element.length == 0) { $element = $option.find('fieldset legend span') .first(); } $element.css('border-bottom', '1px dotted black'); $element.qtip({ content: { text: description }, style: { classes: 'qtip-light' }, position: { target: false } }); } else if (this.options.option_description_mode == this.options.option_description_modes.text) { if ($label.length > 0) { $label .first() .after($('<p class="option-description-text">' + description + '</p>')); } else { $label = $option.find('span'); $label .first() .parent() .after($('<p class="option-description-text">' + description + '</p>')); } } else { console.log('Unknown option mode'); } } if (this.options.value_description_enabled) { this._addValueDescription($option, optionConfig, extendedOptionsConfig); } } } }); return $.mageworx.optionFeatures; }; });
Как правило, теперь мы можем запустить следующие команды:
- Кэш php bin/magento: флеш
- php bin/magento setup:static-content:deploy (только для рабочего режима)
и посмотрим, что у нас есть. Но сначала добавим несколько стилей к нашему новому атрибуту и сделаем так, чтобы он выглядел красиво во внешнем интерфейсе.
Создайте макет и определите в нем наш новый файл стилей: app/code/VendorName/OptionGtin/view/frontend/layout/catalog_product_view.xml
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <head> <css src="VendorName_OptionGtin::css/gtin.css"/> </head> </page>
Пришло время создать файл стилей: app/code/VendorName/OptionGtin/view/frontend/web/css/gtin.css
.option-gtin-text span { color: #6cc308; font-weight: 700; }
Теперь давайте запустим ранее описанные команды и проверим результаты:
Шаг № 5. Добавьте наши атрибутивные данные в детали заказа в базе данных
Когда клиент совершает покупку, создается заказ. Подробная информация о добавленных товарах включается в таблицу sales_order_item
. В этой таблице есть поле product_options
, которое содержит информацию о выбранных параметрах добавляемого товара. Вот куда мы должны добавить данные нашего нового атрибута.
При создании заказа запускается событие sales_quote_address_collect_totals_before
. Мы будем использовать его для добавления наших данных в опции продукта.
Давайте определим событие, создав: app/code/VendorName/OptionGtin/etc/events.xml
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> <event name="sales_quote_address_collect_totals_before"> <observer name="mageworx_optiongtin_add_gtin_to_order" instance="VendorName\OptionGtin\Observer\AddGtinToOrder" /> </event> </config>
Затем создайте наш наблюдатель: app/code/VendorName/OptionGtin/Observer/AddGtinToOrder.php
<?php namespace VendorName\OptionGtin\Observer; use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; use Magento\Catalog\Model\ProductRepository as ProductRepository; use MageWorx\OptionBase\Helper\Data as BaseHelper; class AddGtinToOrder implements ObserverInterface { /** * @var BaseHelper */ protected $baseHelper; protected $productRepository; /** * AddGtinToOrder constructor. * @param BaseHelper $baseHelper * @param ProductRepository $productRepository */ public function __construct( BaseHelper $baseHelper, ProductRepository $productRepository ) { $this->baseHelper = $baseHelper; $this->productRepository = $productRepository; } /** * Add product to quote action * Processing: gtin * * @param Observer $observer * @return $this */ public function execute(Observer $observer) { $quoteItems = $observer->getQuote()->getAllItems(); /** @var \Magento\Quote\Model\Quote\Item $quoteItem */ foreach ($quoteItems as $quoteItem) { $buyRequest = $quoteItem->getBuyRequest(); $optionIds = array_keys($buyRequest->getOptions()); $productOptions = $this->productRepository->getById($buyRequest->getProduct())->getOptions(); $quoteItemOptionGtins = []; $optionGtins = []; foreach ($productOptions as $option) { if ($option->getGtin()) { $quoteItemOptionGtins[$option->getOptionId()] = $option->getGtin(); } } foreach ($optionIds as $optionId) { $optionGtins[$optionId] = $optionId; } $optionGtins = array_intersect_key($quoteItemOptionGtins, $optionGtins); $infoBuyRequest = $quoteItem->getOptionByCode('info_buyRequest'); $buyRequest->setData('gtin', $optionGtins); $infoBuyRequest->setValue($this->baseHelper->encodeBuyRequestValue($buyRequest->getData())); $quoteItem->addOption($infoBuyRequest); } } }
Здесь с помощью обозревателя мы получаем список всех товаров в заказе и добавляем данные нашего атрибута «GTIN» в так называемый $infoBuyRequest
.
Чтобы убедиться, что все выполнено правильно, создайте заказ с товаром, опции которого имеют данные «GTIN». Вы можете проверить добавление данных в таблицу базы данных sales_order_item table
-> поле product_options
:
Шаг №6. Отображение данных на странице заказов в панели администратора
Существуют различные способы отображения необходимой информации в готовом шаблоне. Например, с помощью «js». В этой статье мы работали с «js». Давайте для разнообразия поработаем с самими шаблонами и попробуем их переписать!
Измените ранее созданный app/code/VendorName/OptionGtin/etc/adminhtml/di.xml
, добавив туда плагин:
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <virtualType name="MageWorx\OptionBase\Ui\DataProvider\Product\Form\Modifier\Pool"> <arguments> <argument name="modifiers" xsi:type="array"> <item name="mageworx-option-gtin" xsi:type="array"> <item name="class" xsi:type="string">VendorName\OptionGtin\Ui\DataProvider\Product\Form\Modifier\OptionGtin</item> <item name="sortOrder" xsi:type="number">72</item> </item> </argument> </arguments> </virtualType> <!-- Plugins--> <type name="Magento\Sales\Block\Adminhtml\Items\Column\DefaultColumn"> <plugin name="mageworx-optiongtin-add-default-column" type="VendorName\OptionGtin\Plugin\AddDefaultColumn" sortOrder="5" disabled="false" /> </type> </config>
Создайте сам плагин:
app/code/VendorName/OptionGtin/Plugin/AddDefaultColumn.php
<?php namespace VendorName\OptionGtin\Plugin; class AddDefaultColumn { /** * @param \Magento\Sales\Block\Adminhtml\Items\Column\DefaultColumn $subject * @param $result * @return array */ public function afterGetOrderOptions(\Magento\Sales\Block\Adminhtml\Items\Column\DefaultColumn $subject, $result) { if ($options = $subject->getItem()->getProductOptions()) { if (isset($result)) { foreach ($result as &$option) { if (array_key_exists($option['option_id'], $options['info_buyRequest']['gtin'])) { $option['gtin'] = $options['info_buyRequest']['gtin'][$option['option_id']]; } } } } return $result; } }
Этот плагин добавляет информацию о нашем новом атрибуте для опций заказа, для которых эти данные существуют.
vendor/magento/module-sales/view/adminhtml/templates/items/column/name.phtml
отвечает за отображение информации о вариантах товара на странице заказа в админке.
Давайте перепишем его, чтобы отобразить наш «GTIN». Для этого нам нужно переписать блок «column_name», а точнее его шаблон. Создайте макет и шаблон:
app/code/VendorName/OptionGtin/view/adminhtml/layout/sales_order_view.xml
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="column_name"> <action method="setTemplate"> <argument name="template" xsi:type="string">VendorName_OptionGtin::items/column/name.phtml</argument> </action> </referenceBlock> </body> </page>
app/code/VendorName/OptionGtin/view/adminhtml/templates/items/column/name.phtml
<?php /* @var $block \Magento\Sales\Block\Adminhtml\Items\Column\Name */ ?> <?php if ($_item = $block->getItem()) : ?> <div class="product-title"> <?= $block->escapeHtml($_item->getName()) ?> </div> <div class="product-sku-block"> <span><?= $block->escapeHtml(__('SKU'))?>:</span> <?= /* @noEscape */ implode('<br />', $this->helper(\Magento\Catalog\Helper\Data::class)->splitSku($block->escapeHtml($block->getSku()))) ?> </div> <?php if ($block->getOrderOptions()) : ?> <dl class="item-options"> <?php foreach ($block->getOrderOptions() as $_option) : ?> <dt><?= $block->escapeHtml($_option['label']) ?>:</dt> <dd> <?php if (isset($_option['custom_view']) && $_option['custom_view']) : ?> <?= /* @noEscape */ $block->getCustomizedOptionValue($_option) ?> <?php else : ?> <?php $optionValue = $block->getFormattedOption($_option['value']); ?> <?php $dots = 'dots' . uniqid(); ?> <?php $ . uniqid(); ?> <?= $block->escapeHtml($optionValue['value'], ['a', 'br']) ?><?php if (isset($optionValue['remainder']) && $optionValue['remainder']) : ?> <span> ...</span> <span><?= $block->escapeHtml($optionValue['remainder'], ['a']) ?></span> <script> require(['prototype'], function() { $('<?= /* @noEscape */ $id; ?>').hide(); $('<?= /* @noEscape */ $id; ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $id; ?>').show();}); $('<?= /* @noEscape */ $id; ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $dots; ?>').hide();}); $('<?= /* @noEscape */ $id; ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $id; ?>').hide();}); $('<?= /* @noEscape */ $id; ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $dots; ?>').show();}); }); </script> <?php endif; ?> <?php endif; ?> </dd> <dt> <?php if (isset($_option['gtin']) && $_option['gtin']) : ?> <span>GTIN:</span> <?php endif; ?> </dt> <dd> <?php if (isset($_option['gtin']) && $_option['gtin']) : ?> <span> <?= $block->escapeHtml($_option['gtin']) ?></span> <?php endif; ?> </dd> <?php endforeach; ?> </dl> <?php endif; ?> <?= $block->escapeHtml($_item->getDescription()) ?> <?php endif; ?>
Если все было выполнено правильно, очищено и скомпилировано, то вы увидите следующий результат:
Мы надеемся, что вы найдете эту статью полезной. Если у вас возникнут какие-либо трудности или проблемы, сообщите нам об этом в поле для комментариев ниже.