如何构建Magento 2自定义模块展示客户专属畅销品列表?
Got it, let's walk through building a custom Magento 2 module that shows a customer's personal bestsellers—sorted by how many times they've ordered each product. This is a great way to add personalization to your store, and I'll break it down into actionable, tested steps:
First up, let's set up the basic module structure. We'll name it Vendor_PersonalBestsellers (swap Vendor with your actual vendor name).
Create these core files:
app/code/Vendor/PersonalBestsellers/registration.php
<?php use Magento\Framework\Component\ComponentRegistrar; ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Vendor_PersonalBestsellers', __DIR__);
app/code/Vendor/PersonalBestsellers/etc/module.xml
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Vendor_PersonalBestsellers" setup_version="1.0.0"> <sequence> <module name="Magento_Customer"/> <module name="Magento_Sales"/> </sequence> </module> </config>
We'll use a Helper class to handle the data logic: fetching the logged-in customer's orders, counting product occurrences, and sorting the list.
Create app/code/Vendor/PersonalBestsellers/Helper/Data.php:
<?php namespace Vendor\PersonalBestsellers\Helper; use Magento\Customer\Model\Session; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\Exception\NoSuchEntityException; class Data { protected $customerSession; protected $orderRepository; protected $searchCriteriaBuilder; protected $productRepository; public function __construct( Session $customerSession, OrderRepositoryInterface $orderRepository, SearchCriteriaBuilder $searchCriteriaBuilder, ProductRepositoryInterface $productRepository ) { $this->customerSession = $customerSession; $this->orderRepository = $orderRepository; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->productRepository = $productRepository; } public function getPersonalBestsellers($limit = 5) { // Only proceed if customer is logged in if (!$this->customerSession->isLoggedIn()) { return []; } $customerId = $this->customerSession->getCustomerId(); $productCounts = []; // Fetch all customer orders (uncomment the status filter to only count completed orders) $searchCriteria = $this->searchCriteriaBuilder ->addFilter('customer_id', $customerId) // ->addFilter('status', 'complete') ->create(); $orders = $this->orderRepository->getList($searchCriteria)->getItems(); // Count product occurrences across all orders foreach ($orders as $order) { foreach ($order->getItems() as $item) { $productId = $item->getProductId(); if (!isset($productCounts[$productId])) { $productCounts[$productId] = 0; } $productCounts[$productId] += $item->getQtyOrdered(); } } // Sort products by order count (descending) arsort($productCounts); // Fetch full product data and limit results $bestsellers = []; $count = 0; foreach ($productCounts as $productId => $qty) { if ($count >= $limit) break; try { $product = $this->productRepository->getById($productId); $bestsellers[] = [ 'product' => $product, 'order_count' => $qty ]; $count++; } catch (NoSuchEntityException $e) { // Skip if product no longer exists in the catalog continue; } } return $bestsellers; } }
Next, we'll make a Block class that bridges our Helper and frontend template, so the template can easily access the sorted product list.
Create app/code/Vendor/PersonalBestsellers/Block/PersonalBestsellers.php:
<?php namespace Vendor\PersonalBestsellers\Block; use Magento\Framework\View\Element\Template; use Vendor\PersonalBestsellers\Helper\Data as BestsellerHelper; class PersonalBestsellers extends Template { protected $bestsellerHelper; public function __construct( Template\Context $context, BestsellerHelper $bestsellerHelper, array $data = [] ) { $this->bestsellerHelper = $bestsellerHelper; parent::__construct($context, $data); } public function getPersonalBestsellers($limit = 5) { return $this->bestsellerHelper->getPersonalBestsellers($limit); } public function getProductUrl($product) { return $this->getUrl('catalog/product/view', ['id' => $product->getId()]); } }
Now let's create a template to display the list. It's kept simple here, but you can tweak the HTML/CSS to match your store's theme.
Create app/code/Vendor/PersonalBestsellers/view/frontend/templates/personal_bestsellers.phtml:
<?php /** @var \Vendor\PersonalBestsellers\Block\PersonalBestsellers $block */ $bestsellers = $block->getPersonalBestsellers(5); ?> <?php if (!empty($bestsellers)): ?> <div class="personal-bestsellers"> <h2><?= __('Your Top Ordered Products') ?></h2> <ul class="bestsellers-list"> <?php foreach ($bestsellers as $item): ?> <?php $product = $item['product']; ?> <li class="bestseller-item"> <a href="<?= $block->getProductUrl($product) ?>" title="<?= $product->getName() ?>"> <img src="<?= $block->getImage($product, 'product_small_image')->getUrl() ?>" alt="<?= $product->getName() ?>" class="bestseller-image"> </a> <div class="bestseller-info"> <h3><a href="<?= $block->getProductUrl($product) ?>"><?= $product->getName() ?></a></h3> <p class="order-count"><?= __('Ordered %1 times', $item['order_count']) ?></p> <?php if ($product->getFinalPrice()): ?> <p class="price"><?= $block->getProductPrice($product) ?></p> <?php endif; ?> </div> </li> <?php endforeach; ?> </ul> </div> <?php else: ?> <div class="personal-bestsellers"> <p><?= __('You haven\'t placed any orders yet. Start shopping to see your top products!') ?></p> </div> <?php endif; ?>
Let's add this block to the customer's account dashboard so they see it right when they log in.
Create app/code/Vendor/PersonalBestsellers/view/frontend/layout/customer_account_index.xml:
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Vendor\PersonalBestsellers\Block\PersonalBestsellers" name="personal.bestsellers" template="Vendor_PersonalBestsellers::personal_bestsellers.phtml" before="-"/> </referenceContainer> </body> </page>
Want to add this to the homepage? Use cms_index_index.xml instead and adjust the reference container as needed.
Now let's get the module up and running:
- Run these commands in your Magento root directory:
bin/magento module:enable Vendor_PersonalBestsellers bin/magento setup:upgrade bin/magento setup:di:compile bin/magento cache:clean - Log in as a customer with existing orders (or create a test customer and place a few orders for different products)
- Head to the customer account dashboard—you'll see the "Your Top Ordered Products" list sorted by how many times each product was ordered.
- Filter by Product Visibility: Add
if ($product->isVisibleInCatalog())in the Helper to hide products that are no longer available in the catalog. - Adjust Product Limit: Change the
$limitparameter ingetPersonalBestsellers()to show more or fewer items. - Custom Styling: Add CSS to your store's theme to style the
.personal-bestsellersclass and make it fit your design.
内容的提问来源于stack exchange,提问作者Shay Zalman

