Magento 2在运输方式中添加下拉列表


16

我为一些物流公司开发运输方式。该公司有许多办事处,客户可以在此获得他的订单。我可以在API中按сity列出办公室列表,但是现在我不能更好地表示这一步了吗?

现在,我只是\Magento\Quote\Model\Quote\Address\RateResult\Method 为城镇中的每个办公室设置了新的设备,在大城市中,计数> 100,并且我认为在结帐中设置100条线不是很好。

这将是用于不同结帐设计的公共模块,因此在用户选择一种运输方式后,如何在选中的运输方式附近呈现一些下拉列表以及办公室列表,并设置价格和方法。


@Zefiryn我发现这篇文章非常有趣,但是我有一个问题,如果我必须在选择中而不是展示办公室,而是要展示Amasty模块中的商店,我该如何做第二部分?我的意思是:我在哪里呼叫Amasty的助手来填充xml组件“ vendor_carrier_form”?谢谢
maverickk89'9

如果您有新问题,请单击“ 提问”按钮提问。如果它有助于提供上下文,请包括此问题的链接。- 来自评论
Jai 18'Sep

这不是一个新问题,而是Zefiryn使用的方式的一种变化……因为我使用的是帖子的第一部分,即
maverickk89'Sep

Answers:


17

Magento结帐不支持任何形式的运送方式附加数据。但是它shippingAdditional在结帐中提供了可用于此目的的块。以下解决方案适用于标准的magento结帐。

首先,让我们准备好我们可以放入某种形式的容器。为此,请在view/frontend/layout/checkout_index_index.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.root">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="steps" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="shipping-step" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="shippingAddress" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="shippingAdditional" xsi:type="array">
                                                            <item name="component" xsi:type="string">uiComponent</item>
                                                            <item name="displayArea" xsi:type="string">shippingAdditional</item>
                                                            <item name="children" xsi:type="array">
                                                                <item name="vendor_carrier_form" xsi:type="array">
                                                                    <item name="component" xsi:type="string">Vendor_Module/js/view/checkout/shipping/form</item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

现在创建一个文件Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js,将在其中渲染出一个模板。它的内容看起来像这样

define([
    'jquery',
    'ko',
    'uiComponent',
    'Magento_Checkout/js/model/quote',
    'Magento_Checkout/js/model/shipping-service',
    'Vendor_Module/js/view/checkout/shipping/office-service',
    'mage/translate',
], function ($, ko, Component, quote, shippingService, officeService, t) {
    'use strict';

    return Component.extend({
        defaults: {
            template: 'Vendor_Module/checkout/shipping/form'
        },

        initialize: function (config) {
            this.offices = ko.observableArray();
            this.selectedOffice = ko.observable();
            this._super();
        },

        initObservable: function () {
            this._super();

            this.showOfficeSelection = ko.computed(function() {
                return this.ofices().length != 0
            }, this);

            this.selectedMethod = ko.computed(function() {
                var method = quote.shippingMethod();
                var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
                return selectedMethod;
            }, this);

            quote.shippingMethod.subscribe(function(method) {
                var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
                if (selectedMethod == 'carrier_method') {
                    this.reloadOffices();
                }
            }, this);

            this.selectedOffice.subscribe(function(office) {
                if (quote.shippingAddress().extensionAttributes == undefined) {
                    quote.shippingAddress().extensionAttributes = {};
                }
                quote.shippingAddress().extensionAttributes.carrier_office = office;
            });


            return this;
        },

        setOfficeList: function(list) {
            this.offices(list);
        },

        reloadOffices: function() {
            officeService.getOfficeList(quote.shippingAddress(), this);
            var defaultOffice = this.offices()[0];
            if (defaultOffice) {
                this.selectedOffice(defaultOffice);
            }
        },

        getOffice: function() {
            var office;
            if (this.selectedOffice()) {
                for (var i in this.offices()) {
                    var m = this.offices()[i];
                    if (m.name == this.selectedOffice()) {
                        office = m;
                    }
                }
            }
            else {
                office = this.offices()[0];
            }

            return office;
        },

        initSelector: function() {
            var startOffice = this.getOffice();
        }
    });
});

该文件使用敲除模板,应将其放置在 Vendor/Module/view/frontend/web/template/checkout/shipping/form.html

<div id="carrier-office-list-wrapper" data-bind="visible: selectedMethod() == 'carrier_method'">
    <p data-bind="visible: !showOfficeSelection(), i18n: 'Please provide postcode to see nearest offices'"></p>
    <div data-bind="visible: showOfficeSelection()">
        <p>
            <span data-bind="i18n: 'Select pickup office.'"></span>
        </p>
        <select id="carrier-office-list" data-bind="options: offices(),
                                            value: selectedOffice,
                                            optionsValue: 'name',
                                            optionsText: function(item){return item.location + ' (' + item.name +')';}">
        </select>
    </div>
</div>

现在,当在发货方法表中选择我们的方法(由其代码定义)时,将显示一个选择字段。是时候用一些选项填充它了。由于值取决于地址,因此最好的方法是创建将提供可用选项的其余端点。在Vendor/Module/etc/webapi.xml

<?xml version="1.0"?>

<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">

    <!-- Managing Office List on Checkout page -->
    <route url="/V1/module/get-office-list/:postcode/:city" method="GET">
        <service class="Vendor\Module\Api\OfficeManagementInterface" method="fetchOffices"/>
        <resources>
            <resource ref="anonymous" />
        </resources>
    </route>
</routes>

现在将接口定义Vendor/Module/Api/OfficeManagementInterface.php

namespace Vendor\Module\Api;

interface OfficeManagementInterface
{

    /**
     * Find offices for the customer
     *
     * @param string $postcode
     * @param string $city
     * @return \Vendor\Module\Api\Data\OfficeInterface[]
     */
    public function fetchOffices($postcode, $city);
}

在中定义办公室数据接口Vendor\Module\Api\Data\OfficeInterface.php。webapi模块将使用此接口来过滤输出数据,因此您需要定义需要添加到响应中的任何内容。

namespace Vendor\Module\Api\Data;

/**
 * Office Interface
 */
interface OfficeInterface
{
    /**
     * @return string
     */
    public function getName();

    /**
     * @return string
     */
    public function getLocation();
}

上课时间。首先为中的所有接口创建首选项Vendor/Module/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">
    <preference for="Vendor\Module\Api\OfficeManagementInterface" type="Vendor\Module\Model\OfficeManagement" />
    <preference for="Vendor\Module\Api\Data\OfficeInterface" type="Vendor\Module\Model\Office" />
</config>

现在,创建Vendor\Module\Model\OfficeManagement.php将实际执行获取数据逻辑的类。

namespace Vednor\Module\Model;

use Vednor\Module\Api\OfficeManagementInterface;
use Vednor\Module\Api\Data\OfficeInterfaceFactory;

class OfficeManagement implements OfficeManagementInterface
{
    protected $officeFactory;

    /**
     * OfficeManagement constructor.
     * @param OfficeInterfaceFactory $officeInterfaceFactory
     */
    public function __construct(OfficeInterfaceFactory $officeInterfaceFactory)
    {
        $this->officeFactory = $officeInterfaceFactory;
    }

    /**
     * Get offices for the given postcode and city
     *
     * @param string $postcode
     * @param string $limit
     * @return \Vendor\Module\Api\Data\OfficeInterface[]
     */
    public function fetchOffices($postcode, $city)
    {
        $result = [];
        for($i = 0, $i < 4;$i++) {
            $office = $this->officeFactory->create();
            $office->setName("Office {$i}");
            $office->setLocation("Address {$i}");
            $result[] = $office;
        }

        return $result;
    }
}

最后类OfficeInterfaceVendor/Module/Model/Office.php

namespace Vendor\Module\Model;

use Magento\Framework\DataObject;
use Vendor\Module\Api\Data\OfficeInterface;

class Office extends DataObject implements OfficeInterface
{
    /**
     * @return string
     */
    public function getName()
    {
        return (string)$this->_getData('name');
    }

    /**
     * @return string
     */
    public function getLocation()
    {
        return (string)$this->_getData('location');
    }
}

这将显示选择字段,并在地址更改时对其进行更新。但是我们缺少前端操作的另一个要素。我们需要创建将调用端点的函数。对其的调用已包含在其中Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js,它是Vendor_Module/js/view/checkout/shipping/office-service类,应Vendor/Module/view/frontend/web/js/view/checkout/shipping/office-service.js使用以下代码进行:

define(
    [
        'Vendor_Module/js/view/checkout/shipping/model/resource-url-manager',
        'Magento_Checkout/js/model/quote',
        'Magento_Customer/js/model/customer',
        'mage/storage',
        'Magento_Checkout/js/model/shipping-service',
        'Vendor_Module/js/view/checkout/shipping/model/office-registry',
        'Magento_Checkout/js/model/error-processor'
    ],
    function (resourceUrlManager, quote, customer, storage, shippingService, officeRegistry, errorProcessor) {
        'use strict';

        return {
            /**
             * Get nearest machine list for specified address
             * @param {Object} address
             */
            getOfficeList: function (address, form) {
                shippingService.isLoading(true);
                var cacheKey = address.getCacheKey(),
                    cache = officeRegistry.get(cacheKey),
                    serviceUrl = resourceUrlManager.getUrlForOfficeList(quote);

                if (cache) {
                    form.setOfficeList(cache);
                    shippingService.isLoading(false);
                } else {
                    storage.get(
                        serviceUrl, false
                    ).done(
                        function (result) {
                            officeRegistry.set(cacheKey, result);
                            form.setOfficeList(result);
                        }
                    ).fail(
                        function (response) {
                            errorProcessor.process(response);
                        }
                    ).always(
                        function () {
                            shippingService.isLoading(false);
                        }
                    );
                }
            }
        };
    }
);

它使用了另外2个js文件。Vendor_Module/js/view/checkout/shipping/model/resource-url-manager创建一个指向端点的URL,非常简单

define(
    [
        'Magento_Customer/js/model/customer',
        'Magento_Checkout/js/model/quote',
        'Magento_Checkout/js/model/url-builder',
        'mageUtils'
    ],
    function(customer, quote, urlBuilder, utils) {
        "use strict";
        return {
            getUrlForOfficeList: function(quote, limit) {
                var params = {postcode: quote.shippingAddress().postcode, city: quote.shippingAddress().city};
                var urls = {
                    'default': '/module/get-office-list/:postcode/:city'
                };
                return this.getUrl(urls, params);
            },

            /** Get url for service */
            getUrl: function(urls, urlParams) {
                var url;

                if (utils.isEmpty(urls)) {
                    return 'Provided service call does not exist.';
                }

                if (!utils.isEmpty(urls['default'])) {
                    url = urls['default'];
                } else {
                    url = urls[this.getCheckoutMethod()];
                }
                return urlBuilder.createUrl(url, urlParams);
            },

            getCheckoutMethod: function() {
                return customer.isLoggedIn() ? 'customer' : 'guest';
            }
        };
    }
);

Vendor_Module/js/view/checkout/shipping/model/office-registry是一种将结果保存在本地存储中的方法。它的代码是:

define(
    [],
    function() {
        "use strict";
        var cache = [];
        return {
            get: function(addressKey) {
                if (cache[addressKey]) {
                    return cache[addressKey];
                }
                return false;
            },
            set: function(addressKey, data) {
                cache[addressKey] = data;
            }
        };
    }
);

好的,所以我们应该在前端上全部工作。但是现在还有另一个问题需要解决。由于checkout对这种形式一无所知,因此不会将选择结果发送到后端。为此,我们需要使用extension_attributes功能。这是magento2中通知系统的一种方法,该方法认为其余调用中将包含一些其他数据。如果没有它,magento将过滤掉那些数据,并且它们将永远无法到达代码。

所以首先Vendor/Module/etc/extension_attributes.xml定义:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
    <extension_attributes for="Magento\Quote\Api\Data\AddressInterface">
        <attribute code="carrier_office" type="string"/>
    </extension_attributes>
</config>

form.js根据this.selectedOffice.subscribe()定义,该值已插入请求中。因此,上述配置只会在入口处通过。要在代码中获取它,请在中创建一个插件Vendor/Module/etc/di.xml

<type name="Magento\Quote\Model\Quote\Address">
    <plugin name="inpost-address" type="Vendor\Module\Quote\AddressPlugin" sortOrder="1" disabled="false"/>
</type>

在那堂课里

namespace Vendor\Module\Plugin\Quote;

use Magento\Quote\Model\Quote\Address;
use Vendor\Module\Model\Carrier;

class AddressPlugin
{
    /**
     * Hook into setShippingMethod.
     * As this is magic function processed by __call method we need to hook around __call
     * to get the name of the called method. after__call does not provide this information.
     *
     * @param Address $subject
     * @param callable $proceed
     * @param string $method
     * @param mixed $vars
     * @return Address
     */
    public function around__call($subject, $proceed, $method, $vars)
    {
        $result = $proceed($method, $vars);
        if ($method == 'setShippingMethod'
            && $vars[0] == Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
            && $subject->getExtensionAttributes()
            && $subject->getExtensionAttributes()->getCarrierOffice()
        ) {
            $subject->setCarrierOffice($subject->getExtensionAttributes()->getCarrierOffice());
        }
        elseif (
            $method == 'setShippingMethod'
            && $vars[0] != Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
        ) {
            //reset office when changing shipping method
            $subject->getCarrierOffice(null);
        }
        return $result;
    }
}

当然,您将在何处保存价值完全取决于您的要求。上面的代码将需要carrier_officequote_addresssales_address表中创建其他列以及一个事件(在中Vendor/Module/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_model_service_quote_submit_before">
        <observer name="copy_carrier_office" instance="Vendor\Module\Observer\Model\Order" />
    </event>
</config>

这样会将保存在报价地址中的数据复制到销售地址。

我是为波兰航空运输公司InPost的模块编写的,所以我更改了一些名称,可能会破坏代码,但我希望这能为您提供所需的信息。

[编辑]

@sangan询问的运营商模型

namespace Vendor\Module\Model;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Phrase;
use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Shipping\Model\Carrier\AbstractCarrier;
use Magento\Shipping\Model\Carrier\CarrierInterface;
use Magento\Shipping\Model\Simplexml\ElementFactory;

class Carrier extends AbstractCarrier implements CarrierInterface
{
    const CARRIER_CODE = 'mycarier';

    const METHOD_CODE = 'mymethod';

    /** @var string */
    protected $_code = self::CARRIER_CODE;

    /** @var bool */
    protected $_isFixed = true;

    /**
     * Prepare stores to show on frontend
     *
     * @param RateRequest $request
     * @return \Magento\Framework\DataObject|bool|null
     */
    public function collectRates(RateRequest $request)
    {
        if (!$this->getConfigData('active')) {
            return false;
        }

        /** @var \Magento\Shipping\Model\Rate\Result $result */
        $result = $this->_rateFactory->create();

        /** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */
        $method = $this->_rateMethodFactory->create();
        $method->setCarrier($this->_code);
        $method->setCarrierTitle($this->getConfigData('title'));

        $price = $this->getFinalPriceWithHandlingFee(0);
        $method->setMethod(self::METHOD_CODE);
        $method->setMethodTitle(new Phrase('MyMethod'));
        $method->setPrice($price);
        $method->setCost($price);
        $result->append($method);;

        return $result;
    }


    /**
     * @return array
     */
    public function getAllowedMethods()
    {
        $methods = [
            'mymethod' => new Phrase('MyMethod')
        ];
        return $methods;
    }
}

感谢您的答复,我将尝试使用您的方法解决我的问题,并将在最近几天答复结果。
Siarhey Uchukhlebau

@Zefiryn我已经创建了一个自定义的送货方式,在它下方将显示一个具有客户送货帐号的下拉列表(创建了一个自定义的客户属​​性),因此,如果我必须显示此下拉列表,那么有多少百分比的代码会有所帮助?我应该从您提供的代码中挑选什么?
Shireen N

@shireen我会说大约70%。您需要将其获取机器的部分更改为帐号。因此api定义将是slighlty和js的一部分
Zefiryn

我已经尝试过此模块...但是它没有显示任何更改,因此请共享工作模块。如果有的话
sangan 2017年

添加成功的模块..后,在结帐ajax中连续加载..在控制台中显示如下错误:require.js:166未捕获的错误:Vendor_Module / js / view / checkout / shipping / model / office-registry的脚本错误。 requirejs.org/docs/errors.html#scripterror
sangan

2

我正在添加新的答案,以扩展以前已经提供的内容,但又不会扭曲它。

这是QuoteAddressPlugin连接到的路由:

1. Magento\Checkout\Api\ShippingInformationManagementInterface::saveAddressInformation()
2. Magento\Quote\Model\QuoteRepository::save() 
3. Magento\Quote\Model\QuoteRepository\SaveHandler::save() 
4. Magento\Quote\Model\QuoteRepository\SaveHandler::processShippingAssignment() 
5. Magento\Quote\Model\Quote\ShippingAssignment\ShippingAssignmentPersister::save()
6. Magento\Quote\Model\Quote\ShippingAssignment\ShippingAssignmentProcessor::save()
7. Magento\Quote\Model\Quote\ShippingAssignment\ShippingProcessor::save()
8. Magento\Quote\Model\ShippingMethodManagement::apply() 

最后一个方法是调用Magento\Quote\Model\Quote\Address::setShippingMethod(),实际上是Magento\Quote\Model\Quote\Address::__call()我使用过的那个方法。现在,我为插件找到了一个更好的地方,那就是Magento\Quote\Model\ShippingAssignment::setShipping()方法。因此,插件部分可以重写为:

<type name="Magento\Quote\Model\ShippingAssignment">
    <plugin name="carrier-office-plugin" type="Vendor\Module\Plugin\Quote\ShippingAssignmentPlugin" sortOrder="1" disabled="false"/>
</type>

和插件本身:

namespace Vednor\Module\Plugin\Quote;

use Magento\Quote\Api\Data\AddressInterface;
use Magento\Quote\Api\Data\ShippingInterface;
use Magento\Quote\Model\ShippingAssignment;
use Vendor\Module\Model\Carrier;

/**
 * ShippingAssignmentPlugin
 */
class ShippingAssignmentPlugin
{
    /**
     * Hook into setShipping.
     *
     * @param ShippingAssignment $subject
     * @param ShippingInterface $value
     * @return Address
     */
    public function beforeSetShipping($subject, ShippingInterface $value)
    {
        $method = $value->getMethod();
        /** @var AddressInterface $address */
        $address = $value->getAddress();
        if ($method === Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
            && $address->getExtensionAttributes()
            && $address->getExtensionAttributes()->getCarrierOffice()
        ) {
            $address->setCarrierOffice($address->getExtensionAttributes()->getCarrierOffice());
        }
        elseif ($method !== Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE) {
            //reset inpost machine when changing shipping method
            $address->setCarrierOffice(null);
        }
        return [$value];
    }
}

1

@Zefiryn,我遇到了以下问题: quote.shippingAddress().extensionAttributes.carrier_office = office;

当我第一次以访客身份进入结帐时(新的私有窗口)(但与注册的客户端相同),属性办公室在第一次“下一步”之后未保存到数据库中。尽管在控制台中,我看到了正确的输出:console.log(quote.shippingAddress().extensionAttributes.carrier_office);

当我回到第一个结帐页面并再次选择办公室时,它将被保存。这种行为可能是什么原因?

我尝试使用: address.trigger_reload = new Date().getTime(); rateRegistry.set(address.getKey(), null); rateRegistry.set(address.getCacheKey(), null); quote.shippingAddress(address);

但没有成功...


0

@Zefiryn,您能用几句话解释一下您上面的插件如何工作吗?我有点困惑,因为据我所知,如果我们尝试对特定对象不存在的execute方法,就会执行__call方法。这似乎是真的,因为在app / code / Magento / Quote / Model / Quote / Address.php中,我看不到这种方法-仅注释:

/** * Sales Quote address model ... * @method Address setShippingMethod(string $value)

  1. 为什么在没有方法实现的情况下使用环绕侦听?
  2. 接下来,我看到$subject->setInpostMachine,这$subject->getCarrierOffice(null);是否意味着上面的插件方法将再次执行,因为Adress类中没有setInpostMachine()和getCarrierOffice()方法?在我看来,这就像循环。
  3. Magento从哪里执行setShippingMethod()?这种方法的正常使用情况如何?我在Magento代码中找不到任何类似的拦截。

好的,所以我根据编写的用于测试的模块准备了答案,该模块使用了inpost_machine字段,因此在此位置,这个代码没有正确地更改为carrier_office。其次,在开发此模块时,我没有找到可以同时发送选定的载体和带有扩展属性的地址的地方,除了对象setShippingMethod调用外AddressInterface,由于没有这种方法,因此我不得不使用around__call来查看是否setShippingMethod被称为或其他魔法领域。现在,我找到了一个更好的地方,并将其发布在新的回复中。
Zefiryn
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.