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

@ -34,7 +34,7 @@ class AdminRoleController extends Controller
{
$permissionRepo = (new PermissionRepo());
$data = [
'core_permissions' => $permissionRepo->getRoleCorePermissions(),
'core_permissions' => $permissionRepo->getRoleCorePermissions(),
'plugin_permissions' => $permissionRepo->getRolePluginPermissions(),
];
@ -49,9 +49,9 @@ class AdminRoleController extends Controller
$role = Role::query()->findOrFail($id);
$permissionRepo = (new PermissionRepo())->setRole($role);
$data = [
'core_permissions' => $permissionRepo->getRoleCorePermissions(),
'core_permissions' => $permissionRepo->getRoleCorePermissions(),
'plugin_permissions' => $permissionRepo->getRolePluginPermissions(),
'role' => $role,
'role' => $role,
];
$data = hook_filter('admin.admin_role.edit.data', $data);

View File

@ -18,9 +18,13 @@ class DesignController extends Controller
public function index(Request $request): View
{
$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'),
];
$data = hook_filter('admin.design.index.data', $data);
return view('admin::pages.design.builder.index', $data);
@ -39,7 +43,11 @@ class DesignController extends Controller
$moduleId = $module['module_id'] ?? '';
$moduleCode = $module['code'] ?? '';
$content = $module['content'] ?? '';
$viewPath = "design.{$moduleCode}";
$viewPath = $module['view_path'] ?? '';
if (empty($viewPath)) {
$viewPath = "design.{$moduleCode}";
}
$viewData = [
'code' => $moduleCode,
@ -48,6 +56,7 @@ class DesignController extends Controller
'content' => DesignService::handleModuleContent($moduleCode, $content),
'design' => (bool) $request->get('design'),
];
$viewData = hook_filter('admin.design.preview.data', $viewData);
return view($viewPath, $viewData);

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@ use Beike\Models\AdminUser;
use Beike\Plugin\Manager;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
class PluginServiceProvider extends ServiceProvider
{
@ -34,6 +35,7 @@ class PluginServiceProvider extends ServiceProvider
/**
* Bootstrap Plugin Service Provider
* @throws \Exception
*/
public function boot()
{
@ -41,16 +43,8 @@ class PluginServiceProvider extends ServiceProvider
return;
}
$manager = app('plugin');
$plugins = $manager->getEnabledPlugins();
$this->pluginBasePath = base_path('plugins');
foreach ($plugins as $plugin) {
$pluginCode = $plugin->getDirname();
$this->bootPlugin($plugin);
$this->registerRoutes($pluginCode);
$this->registerMiddleware($pluginCode);
}
$allPlugins = $manager->getPlugins();
foreach ($allPlugins as $plugin) {
$pluginCode = $plugin->getDirname();
@ -58,6 +52,15 @@ class PluginServiceProvider extends ServiceProvider
$this->loadViews($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;
}
/**
* 加载插件内首页 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
!Social
!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>
<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/cookie/js.cookie.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="{{ 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') }}">
@stack('header')
<script>
@if (locale() != 'zh_cn')
ELEMENT.locale(ELEMENT.lang['{{ locale() }}'])
@endif
const lang = {
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>
</head>
@ -65,11 +75,6 @@
</div>
</div>
@foreach($editors as $editor)
<x-dynamic-component :component="$editor" />
@endforeach
<script>
var $languages = @json(locales());
var $locale = '{{ locale() }}';
@ -139,6 +144,10 @@
});
</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.link_selector')
@include('admin::pages.design.builder.component.text_i18n')
@ -201,6 +210,7 @@
content: sourceModule.make,
module_id: module_id,
name: sourceModule.name,
view_path: sourceModule.view_path || '',
}
$http.post('design/builder/preview?design=1', _data, {hload: true}).then((res) => {

View File

@ -10,11 +10,11 @@
*/
return [
'select_all' => 'alle auswählen',
'unselect_all' => 'Auswahl aufheben',
'role_name' => 'Rollenname',
'role_management' => 'Rollenverwaltung',
'permission' => 'System Authority',
'select_all' => 'alle auswählen',
'unselect_all' => 'Auswahl aufheben',
'role_name' => 'Rollenname',
'role_management' => 'Rollenverwaltung',
'permission' => 'System Authority',
'plugin_permission' => 'Plugin permissions',
'error_roles' => 'Bitte mindestens eine Rolle auswählen',
'error_roles' => 'Bitte mindestens eine Rolle auswählen',
];

View File

@ -10,12 +10,12 @@
*/
return [
'select_all' => 'Select All',
'unselect_all' => 'Unselect All',
'role' => 'Role',
'role_name' => 'Role Name',
'role_management' => 'Role Management',
'permission' => 'System Authority',
'select_all' => 'Select All',
'unselect_all' => 'Unselect All',
'role' => 'Role',
'role_name' => 'Role Name',
'role_management' => 'Role Management',
'permission' => 'System Authority',
'plugin_permission' => 'Plugin permissions',
'error_roles' => 'Please select at least one role',
'error_roles' => 'Please select at least one role',
];

View File

@ -10,11 +10,11 @@
*/
return [
'select_all' => 'Comprobar todo',
'unselect_all' => 'desmarcar',
'role_name' => 'Nombre de rol',
'role_management' => 'gestión de roles',
'permission' => 'System Authority',
'select_all' => 'Comprobar todo',
'unselect_all' => 'desmarcar',
'role_name' => 'Nombre de rol',
'role_management' => 'gestión de roles',
'permission' => 'System Authority',
'plugin_permission' => 'Plugin permissions',
'error_roles' => 'Seleccione al menos un rol',
'error_roles' => 'Seleccione al menos un rol',
];

View File

@ -10,11 +10,11 @@
*/
return [
'select_all' => 'tout sélectionner',
'unselect_all' => 'désélectionner',
'role_name' => 'nom du rôle',
'role_management' => 'gestion des rôles',
'permission' => 'System Authority',
'select_all' => 'tout sélectionner',
'unselect_all' => 'désélectionner',
'role_name' => 'nom du rôle',
'role_management' => 'gestion des rôles',
'permission' => 'System Authority',
'plugin_permission' => 'Plugin permissions',
'error_roles' => 'Veuillez sélectionner au moins un rôle',
'error_roles' => 'Veuillez sélectionner au moins un rôle',
];

View File

@ -10,11 +10,11 @@
*/
return [
'select_all' => 'すべて選択',
'unselect_all' => 'unselect',
'role_name' => '役割名',
'role_management' => '役割管理',
'permission' => 'システム権限',
'select_all' => 'すべて選択',
'unselect_all' => 'unselect',
'role_name' => '役割名',
'role_management' => '役割管理',
'permission' => 'システム権限',
'plugin_permission' => 'プラグインのパーミッション',
'error_roles' => '少なくとも 1 つのロールを選択してください',
'error_roles' => '少なくとも 1 つのロールを選択してください',
];

View File

@ -10,11 +10,11 @@
*/
return [
'select_all' => 'выбрать все',
'unselect_all' => 'отменить выбор',
'role_name' => 'имя роли',
'role_management' => 'управление ролями',
'permission' => 'System Authority',
'select_all' => 'выбрать все',
'unselect_all' => 'отменить выбор',
'role_name' => 'имя роли',
'role_management' => 'управление ролями',
'permission' => 'System Authority',
'plugin_permission' => 'Plugin permissions',
'error_roles' => 'Выберите хотя бы одну роль',
'error_roles' => 'Выберите хотя бы одну роль',
];

View File

@ -10,12 +10,12 @@
*/
return [
'select_all' => '选中所有',
'unselect_all' => '取消选中',
'role_name' => '角色名称',
'role_management' => '角色管理',
'role' => '角色',
'permission' => '系统权限',
'select_all' => '选中所有',
'unselect_all' => '取消选中',
'role_name' => '角色名称',
'role_management' => '角色管理',
'role' => '角色',
'permission' => '系统权限',
'plugin_permission' => '插件权限',
'error_roles' => '请至少选择一个角色',
'error_roles' => '请至少选择一个角色',
];

View File

@ -10,11 +10,11 @@
*/
return [
'select_all' => '選中所有',
'unselect_all' => '取消選中',
'role_name' => '角色名稱',
'role_management' => '角色管理',
'permission' => '系統權限',
'select_all' => '選中所有',
'unselect_all' => '取消選中',
'role_name' => '角色名稱',
'role_management' => '角色管理',
'permission' => '系統權限',
'plugin_permission' => '插件權限',
'error_roles' => '請至少選擇一個角色',
'error_roles' => '請至少選擇一個角色',
];