Bestseller module plugin for home page designer

支持首页模块插件化

bestseller

wip

wip

Optimize module view path.

wip

Fixed module content

Optimize module view path.

wip

wip

Optimize module view path.
This commit is contained in:
pushuo 2023-03-07 17:00:57 +08:00 committed by Edward Yang
parent d038ad3f56
commit 8933cbbdd4
23 changed files with 389 additions and 74 deletions

View File

@ -18,9 +18,13 @@ class DesignController extends Controller
public function index(Request $request): View public function index(Request $request): View
{ {
$data = [ $data = [
'editors' => ['editor-slide_show', 'editor-image401', 'editor-tab_product', 'editor-product', 'editor-image100', 'editor-brand', 'editor-icons', 'editor-rich_text'], 'editors' => [
'editor-slide_show', 'editor-image401', 'editor-tab_product', 'editor-product', 'editor-image100',
'editor-brand', 'editor-icons', 'editor-rich_text',
],
'design_settings' => system_setting('base.design_setting'), 'design_settings' => system_setting('base.design_setting'),
]; ];
$data = hook_filter('admin.design.index.data', $data); $data = hook_filter('admin.design.index.data', $data);
return view('admin::pages.design.builder.index', $data); return view('admin::pages.design.builder.index', $data);
@ -39,7 +43,11 @@ class DesignController extends Controller
$moduleId = $module['module_id'] ?? ''; $moduleId = $module['module_id'] ?? '';
$moduleCode = $module['code'] ?? ''; $moduleCode = $module['code'] ?? '';
$content = $module['content'] ?? ''; $content = $module['content'] ?? '';
$viewPath = $module['view_path'] ?? '';
if (empty($viewPath)) {
$viewPath = "design.{$moduleCode}"; $viewPath = "design.{$moduleCode}";
}
$viewData = [ $viewData = [
'code' => $moduleCode, 'code' => $moduleCode,
@ -48,6 +56,7 @@ class DesignController extends Controller
'content' => DesignService::handleModuleContent($moduleCode, $content), 'content' => DesignService::handleModuleContent($moduleCode, $content),
'design' => (bool) $request->get('design'), 'design' => (bool) $request->get('design'),
]; ];
$viewData = hook_filter('admin.design.preview.data', $viewData); $viewData = hook_filter('admin.design.preview.data', $viewData);
return view($viewPath, $viewData); return view($viewPath, $viewData);

View File

@ -41,7 +41,7 @@ class PluginRepo
*/ */
public static function installPlugin(BPlugin $bPlugin) public static function installPlugin(BPlugin $bPlugin)
{ {
self::publishStaticFiles($bPlugin); // self::publishStaticFiles($bPlugin);
self::migrateDatabase($bPlugin); self::migrateDatabase($bPlugin);
$type = $bPlugin->type; $type = $bPlugin->type;
$code = $bPlugin->code; $code = $bPlugin->code;

View File

@ -30,6 +30,12 @@ class DesignService
if (empty($moduleId)) { if (empty($moduleId)) {
$moduleData['module_id'] = Str::random(16); $moduleData['module_id'] = Str::random(16);
} }
$viewPath = $moduleData['view_path'] ?? '';
if ($viewPath == 'design.') {
$moduleData['view_path'] = '';
}
$modulesData[$index] = $moduleData; $modulesData[$index] = $moduleData;
} }
@ -41,6 +47,7 @@ class DesignService
*/ */
public static function handleModuleContent($moduleCode, $content) public static function handleModuleContent($moduleCode, $content)
{ {
$content['module_code'] = $moduleCode;
if ($moduleCode == 'slideshow') { if ($moduleCode == 'slideshow') {
return self::handleSlideShow($content); return self::handleSlideShow($content);
} elseif (in_array($moduleCode, ['image401', 'image100'])) { } elseif (in_array($moduleCode, ['image401', 'image100'])) {
@ -57,7 +64,7 @@ class DesignService
return self::handleRichText($content); return self::handleRichText($content);
} }
return $content; return hook_filter('admin.service.design.module.content', $content);
} }
/** /**

View File

@ -23,7 +23,12 @@ class HomeController extends Controller
$code = $module['code']; $code = $module['code'];
$moduleId = $module['module_id'] ?? ''; $moduleId = $module['module_id'] ?? '';
$content = $module['content']; $content = $module['content'];
$viewPath = $module['view_path'] ?? '';
if (empty($viewPath)) {
$viewPath = "design.{$code}"; $viewPath = "design.{$code}";
}
if (view()->exists($viewPath) && $moduleId) { if (view()->exists($viewPath) && $moduleId) {
$moduleItems[] = [ $moduleItems[] = [
'code' => $code, 'code' => $code,

View File

@ -15,6 +15,7 @@ use Beike\Models\AdminUser;
use Beike\Plugin\Manager; use Beike\Plugin\Manager;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
class PluginServiceProvider extends ServiceProvider class PluginServiceProvider extends ServiceProvider
{ {
@ -34,6 +35,7 @@ class PluginServiceProvider extends ServiceProvider
/** /**
* Bootstrap Plugin Service Provider * Bootstrap Plugin Service Provider
* @throws \Exception
*/ */
public function boot() public function boot()
{ {
@ -41,16 +43,8 @@ class PluginServiceProvider extends ServiceProvider
return; return;
} }
$manager = app('plugin'); $manager = app('plugin');
$plugins = $manager->getEnabledPlugins();
$this->pluginBasePath = base_path('plugins'); $this->pluginBasePath = base_path('plugins');
foreach ($plugins as $plugin) {
$pluginCode = $plugin->getDirname();
$this->bootPlugin($plugin);
$this->registerRoutes($pluginCode);
$this->registerMiddleware($pluginCode);
}
$allPlugins = $manager->getPlugins(); $allPlugins = $manager->getPlugins();
foreach ($allPlugins as $plugin) { foreach ($allPlugins as $plugin) {
$pluginCode = $plugin->getDirname(); $pluginCode = $plugin->getDirname();
@ -58,6 +52,15 @@ class PluginServiceProvider extends ServiceProvider
$this->loadViews($pluginCode); $this->loadViews($pluginCode);
$this->loadTranslations($pluginCode); $this->loadTranslations($pluginCode);
} }
$enabledPlugins = $manager->getEnabledPlugins();
foreach ($enabledPlugins as $plugin) {
$pluginCode = $plugin->getDirname();
$this->bootPlugin($plugin);
$this->registerRoutes($pluginCode);
$this->registerMiddleware($pluginCode);
$this->loadDesignComponents($pluginCode);
}
} }
/** /**
@ -189,4 +192,31 @@ class PluginServiceProvider extends ServiceProvider
return $middlewares; return $middlewares;
} }
/**
* 加载插件内首页 page builder 相关组件
*
* @throws \Exception
*/
protected function loadDesignComponents($pluginCode)
{
$pluginBasePath = $this->pluginBasePath;
$builderPath = "{$pluginBasePath}/{$pluginCode}/Admin/View/DesignBuilders/";
$builders = glob($builderPath . '*');
foreach ($builders as $builder) {
$builderName = basename($builder, '.php');
$aliasName = Str::snake($builderName);
$componentName = Str::studly($builderName);
$classBaseName = "\\Plugin\\{$pluginCode}\\Admin\\View\\DesignBuilders\\{$componentName}";
if (! class_exists($classBaseName)) {
throw new \Exception("请先定义自定义模板类 {$classBaseName}");
}
$this->loadViewComponentsAs('editor', [
$aliasName => $classBaseName,
]);
}
}
} }

1
plugins/.gitignore vendored
View File

@ -5,3 +5,4 @@
!Paypal !Paypal
!Social !Social
!Stripe !Stripe
!Bestseller

View File

@ -0,0 +1,46 @@
<?php
/**
* Render.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-07-08 17:09:15
* @modified 2022-07-08 17:09:15
*/
namespace Plugin\Bestseller\Admin\View\DesignBuilders;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Bestseller extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
}
/**
* Get the view / contents that represent the component.
*
* @return View
*/
public function render(): View
{
$data['register'] = [
'code' => 'bestseller',
'sort' => 0,
// 'name' => trans('admin/design_builder.module_brand'),
'name' => 'Bestseller',
'icon' => '&#xe602;',
'view_path' => 'Bestseller::shop/design_module_bestseller',
];
return view('Bestseller::admin/design_module_bestseller', $data);
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* bootstrap.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-07-20 15:35:59
* @modified 2022-07-20 15:35:59
*/
namespace Plugin\Bestseller;
use Plugin\Bestseller\Repositories\ProductRepo;
class Bootstrap
{
public function boot()
{
/**
* Add module for admin design.
*/
add_hook_filter('admin.design.index.data', function ($data) {
$data['editors'][] = 'editor-bestseller';
return $data;
});
/**
* Get module content for home page and preview.
*/
add_hook_filter('admin.service.design.module.content', function ($data) {
$module = $data['module_code'] ?? '';
if ($module == 'bestseller') {
$data['title'] = $data['title'][locale()] ?? '';
$data['products'] = ProductRepo::getBestSellerProducts($data['limit']);
}
return $data;
});
}
}

View File

@ -0,0 +1,35 @@
<?php
/**
* ProductRepo.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-03-08 11:56:17
* @modified 2023-03-08 11:56:17
*/
namespace Plugin\Bestseller\Repositories;
use Beike\Shop\Http\Resources\ProductSimple;
class ProductRepo
{
/**
* Get best seller
*
* @param $limit
* @return array
*/
public static function getBestSellerProducts($limit): array
{
$products = \Beike\Repositories\ProductRepo::getBuilder([
'active' => 1,
'sort' => 'products.sales',
'order' => 'desc',
])
->whereHas('masterSku')
->limit($limit)->get();
return ProductSimple::collection($products)->jsonSerialize();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,51 @@
<template id="module-editor-bestseller-template">
<div class="image-edit-wrapper">
<div class="module-editor-row">{{ __('admin/builder.text_set_up') }}</div>
<div class="module-edit-group">
<div class="module-edit-title">{{ __('admin/builder.text_module_title') }}</div>
<text-i18n v-model="module.title"></text-i18n>
</div>
<div class="module-edit-group">
<div class="module-edit-title">数量限制</div>
<el-input type="number" v-model="module.limit" size="small"></el-input>
</div>
</div>
</template>
<script type="text/javascript">
Vue.component('module-editor-bestseller', {
template: '#module-editor-bestseller-template',
props: ['module'],
data: function () {
return {
//
}
},
watch: {
module: {
handler: function (val) {
this.$emit('on-changed', val);
},
deep: true
}
},
});
const register = @json($register);
// 定义模块的配置项
register.make = {
style: {
background_color: ''
},
title: languagesFill('{{ __('admin/builder.text_module_title') }}'),
limit: 8,
}
setTimeout(() => {
app.source.modules.push(register)
}, 100)
</script>

View File

@ -0,0 +1,66 @@
<section class="module-item {{ $design ? 'module-item-design' : ''}}" id="module-{{ $module_id }}">
@include('design._partial._module_tool')
<div class="module-info module-product mb-3 mb-md-5 swiper-style-plus">
<div class="container position-relative">
<div class="module-title">{{ $content['title'] }}</div>
@if ($content['products'])
<div class="swiper module-product-{{ $module_id }} module-slideshow">
<div class="swiper-wrapper">
@foreach ($content['products'] as $product)
<div class="swiper-slide">
@include('shared.product')
</div>
@endforeach
</div>
</div>
<div class="swiper-pagination rectangle module-product-{{ $module_id }}-pagination"></div>
<div class="swiper-button-prev product-prev"></div>
<div class="swiper-button-next product-next"></div>
@elseif (!$content['products'] and $design)
<div class="row">
@for ($s = 0; $s < 4; $s++)
<div class="col-6 col-md-4 col-lg-3">
<div class="product-wrap">
<div class="image"><a href="javascript:void(0)"><img src="{{ asset('catalog/placeholder.png') }}" class="img-fluid"></a></div>
<div class="product-name">请配置商品</div>
<div class="product-price">
<span class="price-new">66.66</span>
<span class="price-lod">99.99</span>
</div>
</div>
</div>
@endfor
</div>
@endif
</div>
</div>
<script>
new Swiper ('.module-product-{{ $module_id }}', {
loop: 1,
watchSlidesProgress: true,
breakpoints:{
320: {
slidesPerView: 2,
spaceBetween: 10,
},
768: {
slidesPerView: 4,
spaceBetween: 30,
},
},
spaceBetween: 30,
// 如果需要分页器
pagination: {
el: '.module-product-{{ $module_id }}-pagination',
clickable: true,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.product-next',
prevEl: '.product-prev',
},
})
</script>
</section>

View File

@ -0,0 +1,12 @@
{
"code": "bestseller",
"name": "热卖商品模块",
"description": "首页装修热卖商品模块",
"type": "feature",
"version": "v1.0.0",
"icon": "/image/logo.png",
"author": {
"name": "成都光大网络科技有限公司",
"email": "yangjin@guangda.work"
}
}

View File

@ -12,6 +12,7 @@
<title>{{ __('admin/builder.text_edit_home') }}</title> <title>{{ __('admin/builder.text_edit_home') }}</title>
<script src="{{ asset('vendor/jquery/jquery-3.6.0.min.js') }}"></script> <script src="{{ asset('vendor/jquery/jquery-3.6.0.min.js') }}"></script>
<script src="{{ asset('vendor/layer/3.5.1/layer.js') }}"></script> <script src="{{ asset('vendor/layer/3.5.1/layer.js') }}"></script>
<script src="{{ asset('vendor/cookie/js.cookie.min.js') }}"></script>
<script src="{{ asset('vendor/vue/2.7/vue' . (!config('app.debug') ? '.min' : '') . '.js') }}"></script> <script src="{{ asset('vendor/vue/2.7/vue' . (!config('app.debug') ? '.min' : '') . '.js') }}"></script>
<script src="{{ mix('build/beike/admin/js/app.js') }}"></script> <script src="{{ mix('build/beike/admin/js/app.js') }}"></script>
<script src="{{ asset('vendor/vue/Sortable.min.js') }}"></script> <script src="{{ asset('vendor/vue/Sortable.min.js') }}"></script>
@ -22,9 +23,18 @@
<link rel="stylesheet" type="text/css" href="{{ asset('/build/beike/admin/css/design.css') }}"> <link rel="stylesheet" type="text/css" href="{{ asset('/build/beike/admin/css/design.css') }}">
@stack('header') @stack('header')
<script> <script>
@if (locale() != 'zh_cn')
ELEMENT.locale(ELEMENT.lang['{{ locale() }}'])
@endif
const lang = { const lang = {
file_manager: '{{ __('admin/file_manager.file_manager') }}', file_manager: '{{ __('admin/file_manager.file_manager') }}',
} }
const config = {
beike_version: '{{ config('beike.version') }}',
api_url: '{{ config('beike.api_url') }}',
app_url: '{{ config('app.url') }}',
}
</script> </script>
</head> </head>
@ -65,11 +75,6 @@
</div> </div>
</div> </div>
@foreach($editors as $editor)
<x-dynamic-component :component="$editor" />
@endforeach
<script> <script>
var $languages = @json(locales()); var $languages = @json(locales());
var $locale = '{{ locale() }}'; var $locale = '{{ locale() }}';
@ -139,6 +144,10 @@
}); });
</script> </script>
@foreach($editors as $editor)
<x-dynamic-component :component="$editor" />
@endforeach
@include('admin::pages.design.builder.component.image_selector') @include('admin::pages.design.builder.component.image_selector')
@include('admin::pages.design.builder.component.link_selector') @include('admin::pages.design.builder.component.link_selector')
@include('admin::pages.design.builder.component.text_i18n') @include('admin::pages.design.builder.component.text_i18n')
@ -201,6 +210,7 @@
content: sourceModule.make, content: sourceModule.make,
module_id: module_id, module_id: module_id,
name: sourceModule.name, name: sourceModule.name,
view_path: sourceModule.view_path || '',
} }
$http.post('design/builder/preview?design=1', _data, {hload: true}).then((res) => { $http.post('design/builder/preview?design=1', _data, {hload: true}).then((res) => {