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;
}
}
最后类OfficeInterface
的Vendor/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_office
在quote_address
和sales_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;
}
}