UI components 完整案例

题外话

目前 Magento 2 官网上关于 UI components 的介绍文档 依然不够实用,如果要实践还是要依靠社区内容,以及自己参考 Magento 2 自带的模块。

之前发布了两篇关于 UI components 的文章,一篇是 Alan Storm 的 Magento 2 的 UI Components 介绍(翻译),另一篇是 How to Create Admin Grid in Magento 2,但这篇文章只用到了 Listing component ,没有继续下去,完成完整的增删改查。此外,该文章末尾的参考文档,其实是很有参考价值的,博主深挖到了文档对应 github 地址。

文档:How to Create Admin Grid in Magento 2
代码:mageplaza/magento-2-sample-module

上面的模块基于 Magento 2.1 ,使用了 ui component 的 listng component ,但是 form 部分是用的是 layout 方式而不是 component 方式。ui components 的 xml 写法 2.1 和 2.2 有一些差别,但是 2.2 兼容 2.1 的写法。

比如 2.1.7 中 vendor\magento\module-customer\view\base\ui_component\customer_form.xml 中的写法:

    <dataSource name="customer_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magento\Customer\Model\Customer\DataProvider</argument>
            <argument name="name" xsi:type="string">customer_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="submit_url" xsi:type="url" path="customer/index/save"/>
                    <item name="validate_url" xsi:type="url" path="customer/index/validate"/>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
    </dataSource>

在 2.2.0 版本中是这样的:

    <dataSource name="customer_form_data_source">
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
        <settings>
            <validateUrl path="customer/index/validate"/>
            <submitUrl path="customer/index/save"/>
        </settings>
        <dataProvider class="Magento\Customer\Model\Customer\DataProvider" name="customer_form_data_source">
            <settings>
                <requestFieldName>id</requestFieldName>
                <primaryFieldName>entity_id</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>

该模块在 2.2.0 版本上 di:compile 的时候会报错,主要是因为 2.2.0 中类的变动,后面还会提到,但不影响执行和研究。总的来说,是一个很好的例子,有兴趣的童鞋可以继续研究。

本文中项目的出发点是使用 UI components 完成一个完整的增删改查。和参考文档不同的是,form 部分用的是 form component 。

具体的功能点如下:

  • ui listing 的展示、筛选
  • 单个记录的增加(form component)
  • 单个记录的修改(form component)
  • 单个记录的删除
  • 批量删除
  • ui listing 的行内批量修改

Magento_Ui 模块中的 readme.md 是这样介绍的:
The Magento\Ui module introduces a set of common UI components, which could be used and configured via layout XML files.

他是为了统一 UI ,希望可以通过 layout xml 来进行配置和使用。但目前看来模块还够不稳定。

实践

创建一个模块

创建模块 ThankIT_UIcomponents

ThankIT\UIcomponents\registration.php

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

ThankIT\UIcomponents\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="ThankIT_UIcomponents" setup_version="1.0.0" />
</config>

创建数据表和 model 等

ThankIT\UIcomponents\Setup\InstallSchema.php

<?php
namespace ThankIT\UIcomponents\Setup;

class InstallSchema implements \Magento\Framework\Setup\InstallSchemaInterface
{
    /**
     * install tables
     *
     * @param \Magento\Framework\Setup\SchemaSetupInterface $setup
     * @param \Magento\Framework\Setup\ModuleContextInterface $context
     * @return void
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    public function install(
        \Magento\Framework\Setup\SchemaSetupInterface $setup,
        \Magento\Framework\Setup\ModuleContextInterface $context
    ) {
        $installer = $setup;
        $installer->startSetup();
        if (!$installer->tableExists('thankit_uicomponents_post')) {
            $table = $installer->getConnection()->newTable(
                $installer->getTable('thankit_uicomponents_post')
            )
                ->addColumn(
                    'post_id',
                    \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
                    null,
                    [
                        'identity' => true,
                        'nullable' => false,
                        'primary'  => true,
                        'unsigned' => true,
                    ],
                    'Post ID'
                )
                ->addColumn(
                    'name',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    255,
                    ['nullable => false'],
                    'Post Name'
                )
                ->addColumn(
                    'url_key',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    255,
                    [],
                    'Post URL Key'
                )
                ->addColumn(
                    'post_content',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    '64k',
                    [],
                    'Post Post Content'
                )
                ->addColumn(
                    'tags',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    255,
                    [],
                    'Post Tags'
                )
                ->addColumn(
                    'status',
                    \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
                    1,
                    [],
                    'Post Status'
                )
                ->addColumn(
                    'featured_image',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    255,
                    [],
                    'Post Featured Image'
                )
                ->addColumn(
                    'sample_country_selection',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    3,
                    [],
                    'Post Sample Country Selection'
                )
                ->addColumn(
                    'sample_upload_file',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    255,
                    [],
                    'Post Sample File'
                )
                ->addColumn(
                    'sample_multiselect',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    '64k',
                    [],
                    'Post Sample Multiselect'
                )
                ->addColumn(
                    'created_at',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
                    null,
                    [],
                    'Post Created At'
                )
                ->addColumn(
                    'updated_at',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
                    null,
                    [],
                    'Post Updated At'
                )
                ->setComment('Post Table');
            $installer->getConnection()->createTable($table);
            $installer->getConnection()->addIndex(
                $installer->getTable('thankit_uicomponents_post'),
                $setup->getIdxName(
                    $installer->getTable('thankit_uicomponents_post'),
                    ['name', 'url_key', 'post_content', 'tags', 'featured_image', 'sample_upload_file'],
                    \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_FULLTEXT
                ),
                ['name', 'url_key', 'post_content', 'tags', 'featured_image', 'sample_upload_file'],
                \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_FULLTEXT
            );
        }
        $installer->endSetup();
    }
}

ThankIT\UIcomponents\Model\Post.php

<?php
namespace ThankIT\UIcomponents\Model;
class Post extends \Magento\Framework\Model\AbstractModel
{

    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('ThankIT\UIcomponents\Model\ResourceModel\Post');
    }
}

ThankIT\UIcomponents\Model\ResourceModel\Post.php

<?php
namespace ThankIT\UIcomponents\Model\ResourceModel;

class Post extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    /**
     * Date model
     *
     * @var \Magento\Framework\Stdlib\DateTime\DateTime
     */
    protected $_date;

    /**
     * constructor
     *
     * @param \Magento\Framework\Stdlib\DateTime\DateTime $date
     * @param \Magento\Framework\Model\ResourceModel\Db\Context $context
     */
    public function __construct(
        \Magento\Framework\Stdlib\DateTime\DateTime $date,
        \Magento\Framework\Model\ResourceModel\Db\Context $context
    ) {
        $this->_date = $date;
        parent::__construct($context);
    }

    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('thankit_uicomponents_post', 'post_id');
    }

    /**
     * before save callback
     *
     * @param \Magento\Framework\Model\AbstractModel|\Mageplaza\HelloWorld\Model\Post $object
     * @return $this
     */
    protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object)
    {
        $object->setUpdatedAt($this->_date->date());
        if ($object->isObjectNew()) {
            $object->setCreatedAt($this->_date->date());
        }
        return parent::_beforeSave($object);
    }
}

ThankIT\UIcomponents\Model\ResourceModel\Post\Collection.php

<?php
namespace ThankIT\UIcomponents\Model\ResourceModel\Post;

class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
    // 这个很有意思,本来应该取到 Model\ResourceModel\Post 中设置的值
    // 如果这里不设置,那么 massDelete 中 filter 中 getIdFieldName 为空
    // 所以这里加上了,但是为啥呢?
    protected $_idFieldName = 'post_id';

    protected function _construct()
    {
        $this->_init('ThankIT\UIcomponents\Model\Post', 'ThankIT\UIcomponents\Model\ResourceModel\Post');
    }
}

后台入口

ThankIT\UIcomponents\etc\adminhtml\menu.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
       <add id="ThankIT_UIcomponents::posts" title="ThankIT" module="ThankIT_UIcomponents" sortOrder="51" resource="ThankIT_UIcomponents::posts"/>
       <add id="ThankIT_UIcomponents::post" title="Posts" module="ThankIT_UIcomponents" sortOrder="10" action="thankit_uicomponent/post/index" resource="ThankIT_UIcomponents::post" parent="ThankIT_UIcomponents::posts"/>
    </menu>
</config>

ThankIT\UIcomponents\etc\adminntml\routes.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/App/etc/routes.xsd">
    <router id="admin">
        <route id="thankit_uicomponent" frontName="thankit_uicomponent">
            <module name="ThankIT_UIcomponents" after="Magento_Ui"/>
        </route>
    </router>
</config>

ThankIT\UIcomponents\etc\acl.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
     <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="ThankIT_UIcomponents::posts" title="ThankIT" sortOrder="51">
                    <resource id="ThankIT_UIcomponents::post" title="Posts" sortOrder="10"/>
                </resource>
            </resource>
        </resources>
    </acl>
</config>

grid 的实现

admin grid 效果

ThankIT\UIcomponents\Controller\Adminhtml\Post\Index.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class Index extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $resultPageFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        parent::__construct($context);
        $this->resultPageFactory = $resultPageFactory;
    }

    public function execute()
    {
        $page = $this->resultPageFactory->create();
        // 如果没有下面这句,title 是 Magento Admin ,active menu 正常
        // 如果下面这句的 id 不存在,则 active menu 不正常 title 还是 Magento Admin
        $page->setActiveMenu('ThankIT_UIcomponents::post');
        // title 替换成 Posts
        $page->getConfig()->getTitle()->prepend((__('Posts')));

        // class: Magento\Backend\Model\View\Result\Page
        // addBreadcrumb($label, $title, $link = null)
        $page->addBreadcrumb(__('ThankIT'), __('ThankIT'));
        $page->addBreadcrumb(__('Hello World'), __('Manage Blogs'));
        return $page;
    }

    protected function _isAllowed()
    {
        return $this->_authorization->isAllowed(static::ADMIN_RESOURCE);
    }

}

ThankIT/UIcomponents/view/adminhtml/layout/thankit_uicomponent_post_index.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <uiComponent name="thankit_uicomponents_post_listing"/>
        </referenceContainer>
    </body>
</page>

ThankIT/UIcomponents/view/adminhtml/ui_component/thankit_uicomponents_post_listing.xml

<?xml version="1.0"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing_data_source</item>
            <item name="deps" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">thankit_uicomponents_post_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Post</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">thankit_uicomponent/post/new</item>
            </item>
        </item>
    </argument>
    <dataSource name="thankit_uicomponents_post_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
            <argument name="name" xsi:type="string">thankit_uicomponents_post_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">post_id</argument>
            <argument name="requestFieldName" xsi:type="string">post_id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
            </item>
        </argument>
    </dataSource>
    <container name="listing_top">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="template" xsi:type="string">ui/grid/toolbar</item>
                <item name="stickyTmpl" xsi:type="string">ui/grid/sticky/toolbar</item>
            </item>
        </argument>
        <bookmark name="bookmarks">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="storageConfig" xsi:type="array">
                        <item name="namespace" xsi:type="string">thankit_uicomponents_post_listing</item>
                    </item>
                </item>
            </argument>
        </bookmark>
        <component name="columns_controls">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="columnsData" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns</item>
                    </item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/controls/columns</item>
                    <item name="displayArea" xsi:type="string">dataGridActions</item>
                </item>
            </argument>
        </component>
        <exportButton name="export_button">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                </item>
            </argument>
        </exportButton>
        <filterSearch name="fulltext">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing_data_source</item>
                    <item name="chipsProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top.listing_filters_chips</item>
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top.bookmarks</item>
                        <item name="namespace" xsi:type="string">current.search</item>
                    </item>
                </item>
            </argument>
        </filterSearch>
        <filters name="listing_filters">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="columnsProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns</item>
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top.bookmarks</item>
                        <item name="namespace" xsi:type="string">current.filters</item>
                    </item>
                    <item name="templates" xsi:type="array">
                        <item name="filters" xsi:type="array">
                            <item name="select" xsi:type="array">
                                <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item>
                                <item name="template" xsi:type="string">ui/grid/filters/elements/ui-select</item>
                            </item>
                        </item>
                    </item>
                    <item name="childDefaults" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top.listing_filters</item>
                        <item name="imports" xsi:type="array">
                            <item name="visible" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.${ $.index }:visible</item>
                        </item>
                    </item>
                </item>
                <item name="observers" xsi:type="array">
                    <item name="column" xsi:type="string">column</item>
                </item>
            </argument>
        </filters>
        <massaction name="listing_massaction">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                </item>
            </argument>
            <action name="delete">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">delete</item>
                        <item name="label" xsi:type="string" translate="true">Delete</item>
                        <item name="url" xsi:type="url" path="thankit_uicomponent/post/massDelete"/>
                        <item name="confirm" xsi:type="array">
                            <item name="title" xsi:type="string" translate="true">Delete Posts</item>
                            <item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected Posts?</item>
                        </item>
                    </item>
                </argument>
            </action>
            <action name="edit">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">edit</item>
                        <item name="label" xsi:type="string" translate="true">Edit</item>
                        <item name="callback" xsi:type="array">
                            <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns_editor</item>
                            <item name="target" xsi:type="string">editSelected</item>
                        </item>
                    </item>
                </argument>
            </action>
        </massaction>
        <paging name="listing_paging">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top.bookmarks</item>
                        <item name="namespace" xsi:type="string">current.paging</item>
                    </item>
                    <item name="selectProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                </item>
            </argument>
        </paging>
    </container>

    <columns name="thankit_uicomponents_post_columns">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="editorConfig" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                    <item name="enabled" xsi:type="boolean">true</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                    <item name="clientConfig" xsi:type="array">
                        <item name="saveUrl" xsi:type="url" path="thankit_uicomponent/post/inlineEdit"/>
                        <item name="validateBeforeSave" xsi:type="boolean">false</item>
                    </item>
                </item>
                <!-- column clickable -->
                <item name="childDefaults" xsi:type="array">
                    <item name="fieldAction" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns_editor</item>
                        <item name="target" xsi:type="string">startEdit</item>
                        <item name="params" xsi:type="array">
                            <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                            <item name="1" xsi:type="boolean">true</item>
                        </item>
                    </item>
                </item>
            </item>
        </argument>
        <selectionsColumn name="ids">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="resizeEnabled" xsi:type="boolean">true</item>
                    <item name="resizeDefaultWidth" xsi:type="string">55</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                </item>
            </argument>
        </selectionsColumn>
        <column name="post_id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">textRange</item>
                    <item name="sorting" xsi:type="string">asc</item>
                    <item name="label" xsi:type="string" translate="true">ID</item>
                </item>
            </argument>
        </column>
        <column name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                </item>
            </argument>
        </column>
        <column name="url_key">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="visible" xsi:type="boolean">false</item>
                    <item name="label" xsi:type="string" translate="true">URL Key</item>
                    <item name="dataType" xsi:type="string">text</item>
                </item>
            </argument>
        </column>
        <column name="tags">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="visible" xsi:type="boolean">false</item>
                    <item name="label" xsi:type="string" translate="true">Tags</item>
                    <item name="dataType" xsi:type="string">text</item>
                </item>
            </argument>
        </column>
        <column name="status">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Magento\Config\Model\Config\Source\Yesno</item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="label" xsi:type="string" translate="true">Status</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item>
                    <item name="dataType" xsi:type="string">select</item>
                </item>
            </argument>
        </column>
        <column name="sample_country_selection">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Magento\Config\Model\Config\Source\Locale\Country</item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="visible" xsi:type="boolean">false</item>
                    <item name="label" xsi:type="string" translate="true">Sample Country Selection</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item>
                    <item name="dataType" xsi:type="string">select</item>
                </item>
            </argument>
        </column>
        <column name="created_at" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Created</item>
                </item>
            </argument>
        </column>
        <column name="updated_at" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Modified</item>
                </item>
            </argument>
        </column>
        <actionsColumn name="actions" class="ThankIT\UIcomponents\Ui\Component\Listing\Column\PostActions">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="resizeEnabled" xsi:type="boolean">false</item>
                    <item name="resizeDefaultWidth" xsi:type="string">107</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                </item>
            </argument>
        </actionsColumn>
    </columns>

    <container name="sticky">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/grid/sticky/sticky</item>
                <item name="toolbarProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top</item>
                <item name="listingProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns</item>
            </item>
        </argument>
    </container>
</listing>

这个文件是关键。
这里用的 2.1 的写法,和 How to Create Admin Grid in Magento 2 是一样的。

这里声明了一个 dataSource thankit_uicomponents_post_listing_data_source

我们需要在 di.xml 创建它

ThankIT\UIcomponents\etc\di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
        <arguments>
            <argument name="collections" xsi:type="array">
                <item name="thankit_uicomponents_post_listing_data_source" xsi:type="string">ThankIT\UIcomponents\Model\ResourceModel\Post\Grid\Collection</item>
            </argument>
        </arguments>
    </type>
    <type name="ThankIT\UIcomponents\Model\ResourceModel\Post\Grid\Collection">
        <arguments>
            <argument name="mainTable" xsi:type="string">thankit_uicomponents_post</argument>
            <argument name="resourceModel" xsi:type="string">ThankIT\UIcomponents\Model\ResourceModel\Post</argument>
        </arguments>
    </type>
</config>

这里和之前文章中的相比,有一些简化。

ThankIT\UIcomponents\Model\ResourceModel\Post\Grid\Collection.php

<?php
namespace ThankIT\UIcomponents\Model\ResourceModel\Post\Grid;

use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy;
use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory;
use Magento\Framework\Event\ManagerInterface as EventManager;
use Psr\Log\LoggerInterface as Logger;

class Collection extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
{
    public function __construct(
        EntityFactory $entityFactory,
        Logger $logger,
        FetchStrategy $fetchStrategy,
        EventManager $eventManager,
        $mainTable,
        $resourceModel
    ) {
        parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $mainTable, $resourceModel);
    }
}

这个 collection 和之前不一样,继承的是不同的父类,这也是之前代码在 2.2 上 di:compile 会报错的原因。

add new

新增

在 ThankIT/UIcomponents/view/adminhtml/layout/thankit_uicomponent_post_index.xml 中,关键代码如下:

<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        ...
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Post</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">thankit_uicomponent/post/new</item>
            </item>
        </item>
    </argument>

这里我们给了 url thankit_uicomponent/post/new

下面创建它的 controller 和对应的 layout
ThankIT\UIcomponents\Controller\Adminhtml\Post\NewAction.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class NewAction extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $resultPageFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        parent::__construct($context);
        $this->resultPageFactory = $resultPageFactory;
    }

    public function execute()
    {
        $page = $this->resultPageFactory->create();

        $page->setActiveMenu('ThankIT_UIcomponents::post');

        $page->getConfig()->getTitle()->prepend((__('New Post')));

        $page->addBreadcrumb(__('ThankIT'), __('ThankIT'));
        $page->addBreadcrumb(__('Hello World'), __('Manage Blogs'));
        return $page;
    }

}

这里要用 NewAction.php 作为名字。

ThankIT/UIcomponents/view/adminhtml/layout/thankit_uicomponent_post_new.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <uiComponent name="thankit_uicomponents_post_form"/>
        </referenceContainer>
    </body>
</page>

关键的地方来了,这里用的是 2.2 的写法,参考了 vendor\magento\module-catalog\view\adminhtml\ui_component\category_form.xml 的用法。

ThankIT\UIcomponents\view\adminhtml\ui_component\thankit_uicomponents_post_form.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">thankit_uicomponents_post_form.post_form_data_source</item>
            <item name="deps" xsi:type="string">thankit_uicomponents_post_form.post_form_data_source</item>
        </item>
        <item name="layout" xsi:type="array">
            <item name="type" xsi:type="string">tabs</item>
        </item>
        <item name="buttons" xsi:type="array">
            <item name="save" xsi:type="array">
                <item name="name" xsi:type="string">save</item>
                <item name="label" xsi:type="string" translate="true">Save Post</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">thankit_uicomponent/post/save</item>
            </item>
            <item name="back" xsi:type="array">
                <item name="name" xsi:type="string">back</item>
                <item name="label" xsi:type="string" translate="true">Back</item>
                <item name="class" xsi:type="string">back</item>
                <item name="url" xsi:type="string">thankit_uicomponent/post/index</item>
            </item>
        </item>
    </argument>
    <dataSource name="post_form_data_source">
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
        <settings>
            <submitUrl path="thankit_uicomponent/post/save"/>
        </settings>
        <dataProvider class="ThankIT\UIcomponents\Model\Post\DataProvider" name="post_form_data_source">
            <settings>
                <requestFieldName>post_id</requestFieldName>
                <primaryFieldName>post_id</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>
    <fieldset name="post_form">
        <settings>
            <label translate="true">Post Information</label>
            <collapsible>false</collapsible>
        </settings>
        <field name="post_id" formElement="hidden">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">Post ID</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
            </settings>
        </field>
        <field name="name" formElement="input">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                </item>
            </argument>
            <settings>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">true</rule>
                </validation>
                <dataType>text</dataType>
            </settings>
        </field>
        <field name="url_key" formElement="input">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">url key</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
            </settings>
        </field>
        <field name="post_content" formElement="wysiwyg" template="ui/form/field">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">post content</item>
                </item>
            </argument>
            <formElements>
                <wysiwyg>
                    <settings>
                        <rows>8</rows>
                        <wysiwyg>true</wysiwyg>
                    </settings>
                </wysiwyg>
            </formElements>
        </field>
        <field name="featured_image" formElement="fileUploader">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                </item>
            </argument>
            <settings>
                <elementTmpl>ui/form/element/uploader/uploader</elementTmpl>
                <dataType>string</dataType>
                <label translate="true">Featured Image</label>
                <visible>true</visible>
                <required>false</required>
            </settings>
            <formElements>
                <fileUploader>
                    <settings>
                        <required>false</required>
                        <uploaderConfig>
                            <param xsi:type="url" name="url" path="catalog/category_image/upload"/>
                        </uploaderConfig>
                        <previewTmpl>Magento_Catalog/image-preview</previewTmpl>
                    </settings>
                </fileUploader>
            </formElements>
        </field>
        <field name="tags" formElement="input">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">tags</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
            </settings>
        </field>
        <field name="status" formElement="select">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">status</item>
                </item>
            </argument>
            <settings>
                <dataType>number</dataType>
            </settings>
            <formElements>
                <select>
                    <settings>
                        <options class="Magento\Config\Model\Config\Source\Yesno"/>
                    </settings>
                </select>
            </formElements>
        </field>
        <field name="sample_country_selection" component="Magento_Ui/js/form/element/country" formElement="select">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">sample country selection</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
            </settings>
            <formElements>
                <select>
                    <settings>
                        <options class="\Magento\Config\Model\Config\Source\Locale\Country"/>
                    </settings>
                </select>
            </formElements>
        </field>
    </fieldset>
</form>

这里面声明了

<dataProvider class="ThankIT\UIcomponents\Model\Post\DataProvider" name="post_form_data_source">
            <settings>
                <requestFieldName>post_id</requestFieldName>
                <primaryFieldName>post_id</primaryFieldName>
            </settings>
</dataProvider>

ThankIT\UIcomponents\Model\Post\DataProvider.php

<?php
namespace ThankIT\UIcomponents\Model\Post;

use ThankIT\UIcomponents\Model\ResourceModel\Post\CollectionFactory as postCollectionFactory;

class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
{
    protected $request;

    protected $loadedData;

    public function __construct(
        postCollectionFactory $postCollectionFactory,
        \Magento\Framework\App\RequestInterface $request,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory,
        $name,
        $primaryFieldName,
        $requestFieldName,
        array $meta = [],
        array $data = []
    ) {
        $this->collection  = $postCollectionFactory->create();
        $this->postFactory = $postFactory;
        $this->request     = $request;
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
    }

    public function getData()
    {

        $requestId = $this->request->getParam($this->requestFieldName);

        if (isset($this->loadedData)) {
            return $this->loadedData;
        }

        $post = $this->postFactory->create();
        // 如果是 update 操作
        if ($requestId) {
            $post->load($requestId);
            if (!$post->getId()) {
                throw NoSuchEntityException::singleField('id', $requestId);
            }
            $postData = $post->getData();

            $this->loadedData[$requestId]['post_form'] = $postData;
        } else {
            // new 操作
            $this->loadedData = [];
        }

        return $this->loadedData;
    }
}

这里遇到过的错误类似:

PHP Fatal error: Method Magento\Ui\TemplateEngine\Xhtml\Result::__toString() must not throw an exception, caught Error: Call to a member function addFieldToFilter() on null in /data/sites/project_name/vendor/magento/module-ui/Component/Wrapper/UiComponent.php on line 0

通过:$this->collection = $postCollectionFactory->create(); 解决。

getData() 可以先不要管,返回 [] 也行。

保存

ThankIT\UIcomponents\view\adminhtml\ui_component\thankit_uicomponents_post_form.xml 中的保存按钮路径是 thankit_uicomponent/post/save

ThankIT\UIcomponents\Controller\Adminhtml\Post\Save.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class Save extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $postFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory
    ) {
        parent::__construct($context);
        $this->postFactory = $postFactory;
    }

    public function execute()
    {
        $data = $this->getRequest()->getParam('post_form');

        // var_dump($data);
        // return;

        $post = $this->postFactory->create();

        $postId = $data['post_id'];
        unset($data['post_id']);

        // 如果是 update
        if ($postId) {
            $post->load($postId);

            // setData 的用法有点怪 在他前面的都被 overwrite 了
            // 如果没有 post_id 则会新增
            $post->setData($data);
            $post->setData('post_id', $postId);
        } else {
            $post->setData($data);
        }

        // todo 处理图片
        if (array_key_exists('featured_image', $data)) {
            $featuredImage = $data['featured_image'];
            unset($data['featured_image']);

            // $post->setFeaturedImage($featuredImage[0]['url']);
        }
        $post->save();

        $this->_redirect('*/*/index');
    }

}

这个仅是参考,所写的考虑也不周全,尤其是图片处理部分,根本就没有处理。

update

修改

其实 update 和新增使用的是相同的 form ,相同的 save

ThankIT/UIcomponents/view/adminhtml/ui_component/thankit_uicomponents_post_listing.xml 中

<actionsColumn name="actions" class="ThankIT\UIcomponents\Ui\Component\Listing\Column\PostActions">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="resizeEnabled" xsi:type="boolean">false</item>
                    <item name="resizeDefaultWidth" xsi:type="string">107</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                </item>
            </argument>
</actionsColumn>

ThankIT\UIcomponents\Ui\Component\Listing\Column\PostActions.php

<?php
namespace ThankIT\UIcomponents\Ui\Component\Listing\Column;

class PostActions extends \Magento\Ui\Component\Listing\Columns\Column
{
    /**
     * Url path  to edit
     *
     * @var string
     */
    const URL_PATH_EDIT = 'thankit_uicomponent/post/edit';

    /**
     * Url path  to delete
     *
     * @var string
     */
    const URL_PATH_DELETE = 'thankit_uicomponent/post/delete';

    /**
     * URL builder
     *
     * @var \Magento\Framework\UrlInterface
     */
    protected $_urlBuilder;

    /**
     * constructor
     *
     * @param \Magento\Framework\UrlInterface $urlBuilder
     * @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context
     * @param \Magento\Framework\View\Element\UiComponentFactory $uiComponentFactory
     * @param array $components
     * @param array $data
     */
    public function __construct(
        \Magento\Framework\UrlInterface $urlBuilder,
        \Magento\Framework\View\Element\UiComponent\ContextInterface $context,
        \Magento\Framework\View\Element\UiComponentFactory $uiComponentFactory,
        array $components = [],
        array $data = []
    ) {
        $this->_urlBuilder = $urlBuilder;
        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     * @return array
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            foreach ($dataSource['data']['items'] as &$item) {
                if (isset($item['post_id'])) {
                    $item[$this->getData('name')] = [
                        'edit'   => [
                            'href'  => $this->_urlBuilder->getUrl(
                                static::URL_PATH_EDIT,
                                [
                                    'post_id' => $item['post_id'],
                                ]
                            ),
                            'label' => __('Edit'),
                        ],
                        'delete' => [
                            'href'    => $this->_urlBuilder->getUrl(
                                static::URL_PATH_DELETE,
                                [
                                    'post_id' => $item['post_id'],
                                ]
                            ),
                            'label'   => __('Delete'),
                            'confirm' => [
                                'title'   => __('Delete "${ $.$data.name }"'),
                                'message' => __('Are you sure you wan\'t to delete the Post "${ $.$data.name }" ?'),
                            ],
                        ],
                    ];
                }
            }
        }
        return $dataSource;
    }
}

所以我们的 edit 对应的 url 是 thankit_uicomponent/post/edit
delete 对应的 url 是 thankit_uicomponent/post/delete 并且他们都会将 post_id 传递过去。

ThankIT\UIcomponents\Controller\Adminhtml\Post\Edit.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class Edit extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $postFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        parent::__construct($context);
        $this->postFactory       = $postFactory;
        $this->resultPageFactory = $resultPageFactory;
    }

    public function execute()
    {
        // $data = $this->getRequest()->getParams();
        $page = $this->resultPageFactory->create();
        $page->setActiveMenu('ThankIT_UIcomponents::post');
        // title 替换成 Posts
        $page->getConfig()->getTitle()->prepend((__('Edit Post')));
        return $page;
    }

}

ThankIT/UIcomponents/view/adminhtml/layout/thankit_uicomponent_post_edit.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <uiComponent name="thankit_uicomponents_post_form"/>
        </referenceContainer>
    </body>
</page>

thankit_uicomponents_post_form 我们之前已经声明过了。

delete

删除

ThankIT\UIcomponents\Controller\Adminhtml\Post\Delete.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class Delete extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $postFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        parent::__construct($context);
        $this->postFactory       = $postFactory;
        $this->resultPageFactory = $resultPageFactory;
    }

    public function execute()
    {
        $id = $this->getRequest()->getParam('post_id');
        if ($id) {
            $post = $this->postFactory->create();
            $post->load($id);
            $post->delete();
            $this->messageManager->addSuccess(__('The Post has been deleted.'));
        }
        $this->_redirect('*/*/index');
    }

}

批量删除

批量删除

ThankIT/UIcomponents/view/adminhtml/ui_component/thankit_uicomponents_post_listing.xml 中定义批量删除对应的 url 为 thankit_uicomponent/post/massDelete

<massaction name="listing_massaction">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                </item>
            </argument>
            <action name="delete">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">delete</item>
                        <item name="label" xsi:type="string" translate="true">Delete</item>
                        <item name="url" xsi:type="url" path="thankit_uicomponent/post/massDelete"/>
                        <item name="confirm" xsi:type="array">
                            <item name="title" xsi:type="string" translate="true">Delete Posts</item>
                            <item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected Posts?</item>
                        </item>
                    </item>
                </argument>
            </action>
             ...
</massaction>

ThankIT\UIcomponents\Controller\Adminhtml\Post\MassDelete.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class MassDelete extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $postFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory,
        \Magento\Ui\Component\MassAction\Filter $filter,
        \ThankIT\UIcomponents\Model\ResourceModel\Post\CollectionFactory $collectionFactory,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        parent::__construct($context);
        $this->postFactory       = $postFactory;
        $this->resultPageFactory = $resultPageFactory;
        $this->collectionFactory = $collectionFactory;
        $this->filter            = $filter;
    }

    public function execute()
    {
        // $data = $this->getRequest()->getParam('selected');
        // var_dump($data);

        $collection     = $this->filter->getCollection($this->collectionFactory->create());
        $collectionSize = $collection->getSize();

        foreach ($collection as $page) {
            $page->delete();
        }

        $this->messageManager->addSuccess(__('A total of %1 record(s) have been deleted.', $collectionSize));

        $this->_redirect('*/*/index');

    }

}

批量修改

批量行内修改
ThankIT/UIcomponents/view/adminhtml/ui_component/thankit_uicomponents_post_listing.xml

...
<container name="listing_top">
...
<massaction name="listing_massaction">
    ...
    <action name="edit">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="type" xsi:type="string">edit</item>
                <item name="label" xsi:type="string" translate="true">Edit</item>
                <item name="callback" xsi:type="array">
                    <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns_editor</item>
                    <item name="target" xsi:type="string">editSelected</item>
                </item>
            </item>
        </argument>
    </action>
</massaction>
</container>
<columns name="thankit_uicomponents_post_columns">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="editorConfig" xsi:type="array">
                <item name="selectProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                <item name="enabled" xsi:type="boolean">true</item>
                <item name="indexField" xsi:type="string">post_id</item>
                <item name="clientConfig" xsi:type="array">
                    <item name="saveUrl" xsi:type="url" path="thankit_uicomponent/post/inlineEdit"/>
                    <item name="validateBeforeSave" xsi:type="boolean">false</item>
                </item>
            </item>
            <!-- column clickable -->
            <item name="childDefaults" xsi:type="array">
                <item name="fieldAction" xsi:type="array">
                    <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns_editor</item>
                    <item name="target" xsi:type="string">startEdit</item>
                    <item name="params" xsi:type="array">
                        <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                        <item name="1" xsi:type="boolean">true</item>
                    </item>
                </item>
            </item>
        </item>
    </argument>
    ....
    <column name="name">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="filter" xsi:type="string">text</item>
                <item name="editor" xsi:type="array">
                    <item name="editorType" xsi:type="string">text</item>
                    <item name="validation" xsi:type="array">
                        <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                </item>
                <item name="label" xsi:type="string" translate="true">Name</item>
            </item>
        </argument>
   </column>
   ....
</columns>
...

批量修改的 url 是 thankit_uicomponent/post/inlineEdit

这是一个 ajax 请求。

ThankIT\UIcomponents\Controller\Adminhtml\Post\InlineEdit.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class InlineEdit extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $postFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory,
        \Magento\Framework\Controller\Result\JsonFactory $jsonFactory
    ) {
        parent::__construct($context);
        $this->postFactory = $postFactory;
        $this->jsonFactory = $jsonFactory;

    }

    public function execute()
    {
        // 请求类似 http://192.168.0.251/M220/admin/thankit_uicomponent/post/inlineEdit/key/5fae8c36c82d28ec5e605d8787782dce781512bb4f1f7f14520f00c5cfb5ab53/?isAjax=true
        $resultJson = $this->jsonFactory->create();
        $error      = false;
        $messages   = [];
        $postItems  = $this->getRequest()->getParam('items', []);
        if (!($this->getRequest()->getParam('isAjax') && count($postItems))) {
            return $resultJson->setData([
                'messages' => [__('Please correct the data sent.')],
                'error'    => true,
            ]);
        }
        foreach (array_keys($postItems) as $postId) {

            $post = $this->postFactory->create()->load($postId);
            try {
                $postData = $postItems[$postId]; //todo: handle dates
                $post->addData($postData);
                $post->save();
            } catch (\Magento\Framework\Exception\LocalizedException $e) {
                $messages[] = $this->getErrorWithPostId($post, $e->getMessage());
                $error      = true;
            } catch (\RuntimeException $e) {
                $messages[] = $this->getErrorWithPostId($post, $e->getMessage());
                $error      = true;
            } catch (\Exception $e) {
                $messages[] = $this->getErrorWithPostId(
                    $post,
                    __('Something went wrong while saving the Post.')
                );
                $error = true;
            }
        }
        return $resultJson->setData([
            'messages' => $messages,
            'error'    => $error,
        ]);

    }

    protected function getErrorWithPostId(\ThankIT\UIcomponents\Model\Post $post, $errorText)
    {
        return '[Post ID: ' . $post->getId() . '] ' . $errorText;
    }

}

5 comments

  1. hugo

    看了博主的不少精彩文章,受益良多。ui-components-完整案例,照着上面的事例代码,post 却出现了Page not found. 404 Error。回头重看文章thankit_uicomponent_post_index.xml 重复出现不同的两套代理。这是否只是代码片段,是否可提供完整的事例代码呢。多谢。

    1. Pisces Post author

      po 出来的代码一定是可以实际操作的完整代码。404 的话,请使用 php bin/magento cache:clean 清理缓存,是路由没有找到。我找了下并没有重复的代码片段呢。

      1. hugo

        OK. 真的是很好的案例。期待更多好的文章。

  2. Van

    With hain so much content do you ever run into aany issues of plagorism or
    copyright infringement? My blog has a lot of completely unique content I’ve either written myself or outsourced but
    it looks like a lot of it is popping it up all over the webb without my authorization. Do you know any
    methods to help protect against content from beiong ripped off?
    I’d definitely appreciate it.

    1. Pisces Post author

      Sorry for the late reply. Maybe this (https://neilpatel.com/blog/keep-content-thieves-away/) can help.

发表评论

电子邮件地址不会被公开。 必填项已用*标注