diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..787d50ea
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,21 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.{js,scss,blade.php}]
+indent_size = 2
+
+[*.{yml,yaml}]
+indent_size = 2
+
+[docker-compose.yml]
+indent_size = 4
diff --git a/.env b/.env
new file mode 100644
index 00000000..db4e8122
--- /dev/null
+++ b/.env
@@ -0,0 +1,28 @@
+APP_NAME='BeikeShop'
+APP_ENV=
+APP_KEY=base64:PItUypiY6FmV8oQTVIcIHyNQJAuuS36FmEs8exQbYAw=
+APP_DEBUG=false
+APP_LOG_LEVEL=
+APP_URL=http://43.153.17.83
+
+BEIKE_API_URL=https://beikeshop.com
+
+DB_CONNECTION=mysql
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE=wyyl
+DB_USERNAME=wyyl
+DB_PASSWORD='wyyl@2023'
+
+BROADCAST_DRIVER=log
+CACHE_DRIVER=file
+SESSION_DRIVER=file
+QUEUE_CONNECTION=sync
+
+MAIL_DRIVER=
+MAIL_HOST=
+MAIL_PORT=
+MAIL_USERNAME=
+MAIL_PASSWORD=
+MAIL_ENCRYPTION=
+
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 00000000..3aec5e27
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,21 @@
+
+
+
+请保留我公司版权信息,如要移除,需要我公司授权。
+
+
+## BeikeShop系统亮点
+- **全新!支持ChatGPT:BeikeShop1.3.2 集成ChatGPT,打造高效智能化跨境电商系统**
+- 开源免费:BeikeShop以开源方式发布,OSL-3.0协议允许用户自由使用、修改。
+- 轻量级框架:BeikeShop基于Laravel框架开发,具有高效、灵活、易用等特点。
+- 灵活的插件机制:BeikeShop提供灵活的Hook插件机制,可以方便地扩展和定制系统功能。
+- 多语言多货币支持:支持多种语言/货币切换,可灵活扩展其他语言/货币。
+- 多种支付方式:支持 PayPal、Stripe、Alipay、WeChat Pay 等多种支付方式,覆盖全球主要外贸国家
+- 快速搭建:向VIP提供一键安装脚本,帮助用户快速搭建起一个稳定、高效、易用的跨境电商独立站。
+
+
+
+
+
+
+
+
+
+## 安装教程(面向非开发者)
+1. 下载BeikeShop
+1. 上传到你的服务器并解压
+1. 将解压文件夹下的 public 设置为网站根目录
+1. 通过浏览器访问网站根据提示完成安装
+1. BeikeShop详细安装指引
+1. 如需升级请下载最新版覆盖到服务器后网站根目录运行`composer install && php artisan migrate`
+
+## 安装教程(面向开发者)
+1. 打开命令行克隆代码 `git clone https://gitee.com/beikeshop/beikeshop.git`
+1. 命令行进入 `beikeshop` 目录, 执行 `composer install` 安装第三方包
+1. 接着执行 `cp .env.example .env` 创建配置文件
+1. 接着执行 `npm install`(node 版本需16+) 以及 `npm run dev` 编译前端 js 和 css 文件
+1. 将项目文件夹下的 `public` 设置为网站根目录
+1. 通过浏览器访问网站, 根据提示完成安装
+1. 如需升级请在服务器端网站根目录运行`git pull && composer install && php artisan migrate`
+
+## 参与贡献
+1. Fork 本仓库
+1. 新建 feature-xxx 分支
+1. 提交代码
+1. 新建 Merge Request
+
+## QQ交流群:
+群1: 639108380
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
new file mode 100644
index 00000000..3c8d77dd
--- /dev/null
+++ b/app/Console/Kernel.php
@@ -0,0 +1,32 @@
+command('inspire')->hourly();
+ }
+
+ /**
+ * Register the commands for the application.
+ *
+ * @return void
+ */
+ protected function commands()
+ {
+ $this->load(__DIR__ . '/Commands');
+
+ require base_path('routes/console.php');
+ }
+}
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
new file mode 100644
index 00000000..0dc01dda
--- /dev/null
+++ b/app/Exceptions/Handler.php
@@ -0,0 +1,75 @@
+reportable(function (Throwable $e) {
+ //
+ });
+ }
+
+ /**
+ * Convert the given exception to an array.
+ *
+ * @param \Throwable $e
+ * @return array
+ */
+ protected function convertExceptionToArray(Throwable $e)
+ {
+
+ return config('app.debug') ? [
+ 'message' => $e->getMessage(),
+ 'exception' => get_class($e),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'trace' => collect($e->getTrace())->map(fn ($trace) => Arr::except($trace, ['args']))->all(),
+ ] : [
+ 'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
+ ];
+ }
+
+ /**
+ * 自定义错误信息页面, 前台与后台不同, 需要分开定义
+ */
+ protected function registerErrorViewPaths()
+ {
+ if (is_admin()) {
+ (new RegisterAdminErrorViewPaths())();
+ } else {
+ (new RegisterErrorViewPaths())();
+ }
+ }
+}
diff --git a/app/Exceptions/RegisterAdminErrorViewPaths.php b/app/Exceptions/RegisterAdminErrorViewPaths.php
new file mode 100644
index 00000000..3cfa6d56
--- /dev/null
+++ b/app/Exceptions/RegisterAdminErrorViewPaths.php
@@ -0,0 +1,23 @@
+map(function ($path) {
+ return "{$path}/errors";
+ })->all());
+ }
+ }
+}
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
new file mode 100644
index 00000000..a0a2a8a3
--- /dev/null
+++ b/app/Http/Controllers/Controller.php
@@ -0,0 +1,13 @@
+ locale => share_data => error_page
+ * @var array
+ */
+ protected $middlewareGroups = [
+ 'shop' => [
+ \App\Http\Middleware\EncryptCookies::class,
+ \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+ \Illuminate\Session\Middleware\StartSession::class,
+ // \Illuminate\Session\Middleware\AuthenticateSession::class,
+ \App\Http\Middleware\SetLocaleFromSession::class,
+ \App\Http\Middleware\ShareViewData::class,
+ \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+ \App\Http\Middleware\VerifyCsrfToken::class,
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ ],
+ 'admin' => [
+ \App\Http\Middleware\EncryptCookies::class,
+ \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+ \Illuminate\Session\Middleware\StartSession::class,
+ \App\Http\Middleware\SetLocaleAdmin::class,
+ \App\Http\Middleware\ShareViewData::class,
+ \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+ \App\Http\Middleware\VerifyCsrfToken::class,
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ ],
+ 'installer' => [
+ \App\Http\Middleware\EncryptCookies::class,
+ \Illuminate\Session\Middleware\StartSession::class,
+ \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+ ],
+
+ 'web' => [
+ \App\Http\Middleware\EncryptCookies::class,
+ \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+ \Illuminate\Session\Middleware\StartSession::class,
+ // \Illuminate\Session\Middleware\AuthenticateSession::class,
+ \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+ \App\Http\Middleware\VerifyCsrfToken::class,
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ ],
+ 'api' => [
+ // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
+ 'throttle:api',
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ ],
+ ];
+
+ /**
+ * The application's route middleware.
+ *
+ * These middleware may be assigned to groups or used individually.
+ *
+ * @var array
+ */
+ protected $routeMiddleware = [
+ 'admin_auth' => \App\Http\Middleware\Authenticate::class,
+ 'shop_auth' => \App\Http\Middleware\ShopAuthenticate::class,
+ 'checkout_auth' => \App\Http\Middleware\CheckoutAuthenticate::class,
+ 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+ 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
+ 'can' => \Illuminate\Auth\Middleware\Authorize::class,
+ 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
+ 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
+ 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
+ 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+ 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
+ ];
+}
diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php
new file mode 100644
index 00000000..7f26ada0
--- /dev/null
+++ b/app/Http/Middleware/Authenticate.php
@@ -0,0 +1,22 @@
+expectsJson()) {
+ // 后台
+ return admin_route('login.show');
+ }
+ }
+}
diff --git a/app/Http/Middleware/CheckoutAuthenticate.php b/app/Http/Middleware/CheckoutAuthenticate.php
new file mode 100644
index 00000000..7a82cf0c
--- /dev/null
+++ b/app/Http/Middleware/CheckoutAuthenticate.php
@@ -0,0 +1,72 @@
+authenticate($request, $guards);
+
+ $customer = current_customer();
+ if ($customer->status != 1) {
+ Auth::guard(Customer::AUTH_GUARD)->logout();
+
+ return redirect(shop_route('login.index'));
+ }
+
+ return $next($request);
+ }
+
+ /**
+ * Get the path the user should be redirected to when they are not authenticated.
+ *
+ * @param Request $request
+ */
+ protected function redirectTo($request)
+ {
+ if (! $request->expectsJson()) {
+ return shop_route('login.index');
+ }
+ }
+
+ /**
+ * Handle an unauthenticated user.
+ *
+ * @param Request $request
+ * @param array $guards
+ * @return void
+ *
+ * @throws AuthenticationException
+ */
+ protected function unauthenticated($request, array $guards)
+ {
+ if (system_setting('base.guest_checkout', 1)) {
+ return;
+ }
+
+ throw new AuthenticationException(
+ trans('common.unauthenticated'), $guards, $this->redirectTo($request)
+ );
+ }
+}
diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php
new file mode 100644
index 00000000..033136ad
--- /dev/null
+++ b/app/Http/Middleware/EncryptCookies.php
@@ -0,0 +1,17 @@
+check()) {
+ return redirect(RouteServiceProvider::HOME);
+ }
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/SetLocaleAdmin.php b/app/Http/Middleware/SetLocaleAdmin.php
new file mode 100644
index 00000000..30c03c9b
--- /dev/null
+++ b/app/Http/Middleware/SetLocaleAdmin.php
@@ -0,0 +1,29 @@
+locale ?? 'zh_cn';
+ if (in_array($currentLocale, admin_languages())) {
+ App::setLocale($currentLocale);
+ } else {
+ App::setLocale('en');
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/SetLocaleFromSession.php b/app/Http/Middleware/SetLocaleFromSession.php
new file mode 100644
index 00000000..06feb132
--- /dev/null
+++ b/app/Http/Middleware/SetLocaleFromSession.php
@@ -0,0 +1,31 @@
+toArray())) {
+ App::setLocale($sessionLocale);
+ } else {
+ $configLocale = system_setting('base.locale');
+ App::setLocale($configLocale);
+ session(['locale' => $configLocale]);
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/SetLocaleInstaller.php b/app/Http/Middleware/SetLocaleInstaller.php
new file mode 100644
index 00000000..97955f2d
--- /dev/null
+++ b/app/Http/Middleware/SetLocaleInstaller.php
@@ -0,0 +1,35 @@
+getEnvContent();
+
+ if ($locale && in_array($locale, $languages)) {
+ App::setLocale($locale);
+ } else {
+ App::setLocale('zh_cn');
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/ShareViewData.php b/app/Http/Middleware/ShareViewData.php
new file mode 100644
index 00000000..60291c70
--- /dev/null
+++ b/app/Http/Middleware/ShareViewData.php
@@ -0,0 +1,75 @@
+
+ * @created 2022-08-03 15:46:13
+ * @modified 2022-08-03 15:46:13
+ */
+
+namespace App\Http\Middleware;
+
+use Beike\Repositories\FooterRepo;
+use Beike\Repositories\LanguageRepo;
+use Beike\Repositories\MenuRepo;
+use Closure;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\View;
+
+class ShareViewData
+{
+ public function handle(Request $request, Closure $next)
+ {
+ $this->loadShopShareViewData();
+
+ return $next($request);
+ }
+
+ /**
+ * @throws \Exception
+ */
+ protected function loadShopShareViewData()
+ {
+ if (is_admin()) {
+ $adminLanguages = $this->handleAdminLanguages();
+ $loggedAdminUser = current_user();
+ if ($loggedAdminUser) {
+ $currentLanguage = $loggedAdminUser->locale ?: 'en';
+ View::share('admin_languages', $adminLanguages);
+ View::share('admin_language', collect($adminLanguages)->where('code', $currentLanguage)->first());
+ }
+ } else {
+ View::share('design', request('design') == 1);
+ View::share('languages', LanguageRepo::enabled());
+ View::share('shop_base_url', shop_route('home.index'));
+ View::share('footer_content', hook_filter('footer.content', FooterRepo::handleFooterData()));
+ View::share('menu_content', hook_filter('menu.content', MenuRepo::handleMenuData()));
+ }
+ }
+
+ /**
+ * 处理后台语言包列表
+ *
+ * @return array
+ */
+ private function handleAdminLanguages(): array
+ {
+ $items = [];
+ $languages = admin_languages();
+ foreach ($languages as $language) {
+ $path = lang_path("{$language}/admin/base.php");
+ if (file_exists($path)) {
+ $baseData = require_once $path;
+ }
+ $name = $baseData['name'] ?? '';
+ $items[] = [
+ 'code' => $language,
+ 'name' => $name,
+ ];
+ }
+
+ return $items;
+ }
+}
diff --git a/app/Http/Middleware/ShopAuthenticate.php b/app/Http/Middleware/ShopAuthenticate.php
new file mode 100644
index 00000000..ad363db3
--- /dev/null
+++ b/app/Http/Middleware/ShopAuthenticate.php
@@ -0,0 +1,64 @@
+authenticate($request, $guards);
+
+ $customer = current_customer();
+ if ($customer->status != 1) {
+ Auth::guard(Customer::AUTH_GUARD)->logout();
+
+ return redirect(shop_route('login.index'));
+ }
+
+ return $next($request);
+ }
+
+ /**
+ * Get the path the user should be redirected to when they are not authenticated.
+ *
+ * @param Request $request
+ */
+ protected function redirectTo($request)
+ {
+ if (! $request->expectsJson()) {
+ return shop_route('login.index');
+ }
+ }
+
+ /**
+ * Handle an unauthenticated user.
+ *
+ * @param Request $request
+ * @param array $guards
+ * @return void
+ *
+ * @throws AuthenticationException
+ */
+ protected function unauthenticated($request, array $guards)
+ {
+ throw new AuthenticationException(
+ trans('common.unauthenticated'), $guards, $this->redirectTo($request)
+ );
+ }
+}
diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php
new file mode 100644
index 00000000..a8a252df
--- /dev/null
+++ b/app/Http/Middleware/TrimStrings.php
@@ -0,0 +1,19 @@
+allSubdomainsOfApplicationUrl(),
+ ];
+ }
+}
diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php
new file mode 100644
index 00000000..beb5d137
--- /dev/null
+++ b/app/Http/Middleware/TrustProxies.php
@@ -0,0 +1,28 @@
+ 'required|string|max:100|min:10',
+ ];
+ }
+}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
new file mode 100644
index 00000000..4843b8de
--- /dev/null
+++ b/app/Providers/AppServiceProvider.php
@@ -0,0 +1,31 @@
+ 'App\Policies\ModelPolicy',
+ ];
+
+ /**
+ * Register any authentication / authorization services.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+ $this->registerPolicies();
+
+ Gate::before(function ($user, $ability) {
+ return $user->id == 1;
+ });
+ }
+}
diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php
new file mode 100644
index 00000000..395c518b
--- /dev/null
+++ b/app/Providers/BroadcastServiceProvider.php
@@ -0,0 +1,21 @@
+ [
+ // SendEmailVerificationNotification::class,
+ // ],
+ ];
+
+ /**
+ * Register any events for your application.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+ //
+ }
+}
diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php
new file mode 100644
index 00000000..251266a9
--- /dev/null
+++ b/app/Providers/HorizonServiceProvider.php
@@ -0,0 +1,42 @@
+email, [
+
+ ]);
+ });
+ }
+}
diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php
new file mode 100644
index 00000000..3bd3c81e
--- /dev/null
+++ b/app/Providers/RouteServiceProvider.php
@@ -0,0 +1,63 @@
+configureRateLimiting();
+
+ $this->routes(function () {
+ Route::prefix('api')
+ ->middleware('api')
+ ->namespace($this->namespace)
+ ->group(base_path('routes/api.php'));
+
+ Route::middleware('web')
+ ->namespace($this->namespace)
+ ->group(base_path('routes/web.php'));
+ });
+ }
+
+ /**
+ * Configure the rate limiters for the application.
+ *
+ * @return void
+ */
+ protected function configureRateLimiting()
+ {
+ RateLimiter::for('api', function (Request $request) {
+ return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
+ });
+ }
+}
diff --git a/artisan b/artisan
new file mode 100644
index 00000000..67a3329b
--- /dev/null
+++ b/artisan
@@ -0,0 +1,53 @@
+#!/usr/bin/env php
+make(Illuminate\Contracts\Console\Kernel::class);
+
+$status = $kernel->handle(
+ $input = new Symfony\Component\Console\Input\ArgvInput,
+ new Symfony\Component\Console\Output\ConsoleOutput
+);
+
+/*
+|--------------------------------------------------------------------------
+| Shutdown The Application
+|--------------------------------------------------------------------------
+|
+| Once Artisan has finished running, we will fire off the shutdown events
+| so that any final work may be done by the application before we shut
+| down the process. This is the last thing to happen to the request.
+|
+*/
+
+$kernel->terminate($input, $status);
+
+exit($status);
diff --git a/beike/Admin/Database/Seeders/ProductSeeder.php b/beike/Admin/Database/Seeders/ProductSeeder.php
new file mode 100644
index 00000000..b7921331
--- /dev/null
+++ b/beike/Admin/Database/Seeders/ProductSeeder.php
@@ -0,0 +1,62 @@
+addMinute();
+ $productId = DB::table('products')->insertGetId([
+ 'image' => $faker->imageUrl(100, 100),
+ 'video' => '',
+ 'position' => 0,
+ 'active' => 1,
+ 'variables' => null,
+ 'created_at' => $time,
+ 'updated_at' => $time,
+ ]);
+
+ $descriptions = [];
+ foreach (locales() as $locale) {
+ $descriptions[] = [
+ 'product_id' => $productId,
+ 'locale' => $locale['code'],
+ 'name' => $faker->words(5, true),
+ 'content' => $faker->paragraphs(6, true),
+ 'meta_title' => $faker->words(10, true),
+ 'meta_description' => $faker->sentence(10),
+ 'meta_keywords' => $faker->words(10, true),
+ 'created_at' => $time,
+ 'updated_at' => $time,
+ ];
+ }
+ DB::table('product_descriptions')->insert($descriptions);
+
+ $costPrice = $faker->numberBetween(100, 500);
+ DB::table('product_skus')->insert([
+ 'product_id' => $productId,
+ 'variants' => null,
+ 'position' => 0,
+ 'image' => $faker->imageUrl(100, 100),
+ 'model' => $faker->isbn10(),
+ 'sku' => $faker->isbn10(),
+ 'price' => $costPrice + 10,
+ 'origin_price' => $costPrice + 50,
+ 'cost_price' => $costPrice,
+ 'quantity' => $faker->numberBetween(100, 1000),
+ 'is_default' => 1,
+ 'created_at' => $time,
+ 'updated_at' => $time,
+ ]);
+ }
+ }
+}
diff --git a/beike/Admin/Http/Controllers/AddressController.php b/beike/Admin/Http/Controllers/AddressController.php
new file mode 100644
index 00000000..1a96193d
--- /dev/null
+++ b/beike/Admin/Http/Controllers/AddressController.php
@@ -0,0 +1,68 @@
+
+ * @created 2022-06-28 20:17:04
+ * @modified 2022-06-28 20:17:04
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Resources\AddressResource;
+use Beike\Admin\Services\AddressService;
+use Beike\Repositories\AddressRepo;
+use Illuminate\Http\Request;
+
+class AddressController extends Controller
+{
+ public function index(Request $request, int $customerId)
+ {
+ $addresses = AddressRepo::listByCustomer($customerId);
+ $data = [
+ 'addresses' => AddressResource::collection($addresses),
+ ];
+
+ return hook_filter('admin.address.index.data', $data);
+ }
+
+ public function store(Request $request, int $customerId)
+ {
+ $requestData = $request->all();
+
+ $beforeData = [
+ 'customer_id' => $customerId,
+ 'data' => $requestData,
+ ];
+ hook_action('admin.address.store.before', $beforeData);
+ $address = AddressService::addForCustomer($customerId, $requestData);
+ hook_action('admin.address.store.after', $address);
+
+ return json_success(trans('common.created_success'), $address);
+ }
+
+ public function update(Request $request, int $customerId, int $addressId)
+ {
+ $requestData = $request->all();
+ $beforeData = [
+ 'customer_id' => $customerId,
+ 'address_id' => $addressId,
+ 'data' => $requestData,
+ ];
+ hook_action('admin.address.update.before', $beforeData);
+ $address = AddressService::update($addressId, $request->all());
+ hook_action('admin.address.update.after', $address);
+
+ return json_success(trans('common.updated_success'), $address);
+ }
+
+ public function destroy(Request $request, int $customerId, int $addressId)
+ {
+ AddressRepo::delete($addressId);
+ hook_action('admin.address.destroy.after', $addressId);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/AdminRoleController.php b/beike/Admin/Http/Controllers/AdminRoleController.php
new file mode 100644
index 00000000..f49a2dd7
--- /dev/null
+++ b/beike/Admin/Http/Controllers/AdminRoleController.php
@@ -0,0 +1,88 @@
+
+ * @created 2022-08-01 11:44:54
+ * @modified 2022-08-01 11:44:54
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Requests\AdminRoleRequest;
+use Beike\Admin\Repositories\AdminRoleRepo;
+use Beike\Admin\Repositories\PermissionRepo;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+use Spatie\Permission\Models\Role;
+
+class AdminRoleController extends Controller
+{
+ public function index()
+ {
+ $data = [
+ 'roles' => Role::query()->get(),
+ ];
+ $data = hook_filter('admin.admin_role.index.data', $data);
+
+ return view('admin::pages.admin_roles.index', $data);
+ }
+
+ public function create(Request $request)
+ {
+ $permissionRepo = (new PermissionRepo());
+ $data = [
+ 'core_permissions' => $permissionRepo->getRoleCorePermissions(),
+ 'plugin_permissions' => $permissionRepo->getRolePluginPermissions(),
+ ];
+
+ $data = hook_filter('admin.admin_role.create.data', $data);
+
+ return view('admin::pages.admin_roles.edit', $data);
+ }
+
+ public function edit(Request $request, int $id)
+ {
+ Cache::forget('spatie.permission.cache');
+ $role = Role::query()->findOrFail($id);
+ $permissionRepo = (new PermissionRepo())->setRole($role);
+ $data = [
+ 'core_permissions' => $permissionRepo->getRoleCorePermissions(),
+ 'plugin_permissions' => $permissionRepo->getRolePluginPermissions(),
+ 'role' => $role,
+ ];
+ $data = hook_filter('admin.admin_role.edit.data', $data);
+
+ return view('admin::pages.admin_roles.edit', $data);
+ }
+
+ /**
+ * 保存后台用户角色
+ *
+ * @param AdminRoleRequest $request
+ * @return array
+ * @throws \Exception
+ */
+ public function store(AdminRoleRequest $request): array
+ {
+ $adminUser = AdminRoleRepo::createAdminRole($request->toArray());
+
+ return json_success(trans('common.created_success'), $adminUser);
+ }
+
+ public function update(Request $request, int $adminUserId)
+ {
+ $adminUser = AdminRoleRepo::updateAdminRole($request->toArray());
+
+ return json_success(trans('common.updated_success'), $adminUser);
+ }
+
+ public function destroy(Request $request, int $adminUserId)
+ {
+ AdminRoleRepo::deleteAdminRole($adminUserId);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/AdminUserController.php b/beike/Admin/Http/Controllers/AdminUserController.php
new file mode 100644
index 00000000..8bfedabb
--- /dev/null
+++ b/beike/Admin/Http/Controllers/AdminUserController.php
@@ -0,0 +1,70 @@
+
+ * @created 2022-08-01 11:44:54
+ * @modified 2022-08-01 11:44:54
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Requests\AdminUserRequest;
+use Beike\Admin\Repositories\AdminUserRepo;
+use Illuminate\Http\Request;
+use Spatie\Permission\Models\Role;
+
+class AdminUserController extends Controller
+{
+ /**
+ * 获取后台用户列表
+ *
+ * @return mixed
+ */
+ public function index()
+ {
+ $data = [
+ 'admin_users' => AdminUserRepo::getAdminUsers(),
+ 'admin_roles' => Role::query()->get(),
+ ];
+ $data = hook_filter('admin.admin_user.index.data', $data);
+
+ return view('admin::pages.admin_users.index', $data);
+ }
+
+ /**
+ * 创建后台管理员
+ *
+ * @param AdminUserRequest $request
+ * @return array
+ */
+ public function store(AdminUserRequest $request)
+ {
+ $adminUser = AdminUserRepo::createAdminUser($request->toArray());
+
+ return json_success(trans('common.created_success'), $adminUser);
+ }
+
+ /**
+ * 更新后台管理员
+ *
+ * @param AdminUserRequest $request
+ * @param int $adminUserId
+ * @return array
+ */
+ public function update(AdminUserRequest $request, int $adminUserId)
+ {
+ $adminUser = AdminUserRepo::updateAdminUser($adminUserId, $request->toArray());
+
+ return json_success(trans('common.updated_success'), $adminUser);
+ }
+
+ public function destroy(Request $request, int $adminUserId)
+ {
+ AdminUserRepo::deleteAdminUser($adminUserId);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/AttributeController.php b/beike/Admin/Http/Controllers/AttributeController.php
new file mode 100644
index 00000000..a630b26b
--- /dev/null
+++ b/beike/Admin/Http/Controllers/AttributeController.php
@@ -0,0 +1,151 @@
+
+ * @created 2023-01-04 19:45:41
+ * @modified 2023-01-04 19:45:41
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Resources\AttributeDetailResource;
+use Beike\Admin\Http\Resources\AttributeResource;
+use Beike\Admin\Http\Resources\AttributeValueResource;
+use Beike\Admin\Http\Resources\AutocompleteResource;
+use Beike\Admin\Repositories\AttributeGroupRepo;
+use Beike\Admin\Repositories\AttributeRepo;
+use Exception;
+use Illuminate\Http\Request;
+
+class AttributeController extends Controller
+{
+ public function index(Request $request)
+ {
+ try {
+ $attributes = AttributeRepo::getList();
+ $data = [
+ 'attribute_list' => $attributes,
+ 'attribute_list_format' => AttributeResource::collection($attributes),
+ 'attribute_group' => AttributeGroupRepo::getList(),
+ ];
+ $data = hook_filter('admin.attribute.index.data', $data);
+ if ($request->expectsJson()) {
+ return json_success(trans('success'), $data);
+ }
+ } catch (Exception $e) {
+ return view('admin::pages.attributes.index', $data)->withErrors(['error' => $e->getMessage()]);
+ }
+
+ return view('admin::pages.attributes.index', $data);
+ }
+
+ public function show(Request $request, int $id)
+ {
+ try {
+ $data = [
+ 'attribute' => (new AttributeDetailResource(AttributeRepo::find($id)))->jsonSerialize(),
+ 'attribute_group' => AttributeGroupRepo::getList(),
+ ];
+ $data = hook_filter('admin.attribute.show.data', $data);
+ } catch (Exception $e) {
+ return view('admin::pages.attributes.form', $data)->withErrors(['error' => $e->getMessage()]);
+ }
+
+ return view('admin::pages.attributes.form', $data);
+ }
+
+ public function store(Request $request)
+ {
+ try {
+ $requestData = json_decode($request->getContent(), true);
+ $item = AttributeRepo::create($requestData);
+ } catch (Exception $e) {
+ return json_fail($e->getMessage(), []);
+ }
+
+ return json_success(trans('common.created_success'), $item);
+ }
+
+ public function update(Request $request, int $id)
+ {
+ try {
+ $requestData = json_decode($request->getContent(), true);
+ $item = AttributeRepo::update($id, $requestData);
+ } catch (Exception $e) {
+ return json_fail($e->getMessage(), []);
+ }
+
+ return json_success(trans('common.updated_success'), $item);
+ }
+
+ public function storeValue(Request $request, int $id)
+ {
+ try {
+ $requestData = json_decode($request->getContent(), true);
+ $item = AttributeRepo::createValue(array_merge($requestData, ['attribute_id' => $id]));
+ } catch (Exception $e) {
+ return json_fail($e->getMessage(), []);
+ }
+
+ return json_success(trans('common.created_success'), new AttributeValueResource($item));
+ }
+
+ public function updateValue(Request $request, int $id, int $value_id)
+ {
+ try {
+ $requestData = json_decode($request->getContent(), true);
+ $item = AttributeRepo::updateValue($value_id, $requestData);
+ } catch (Exception $e) {
+ return json_fail($e->getMessage(), []);
+ }
+
+ return json_success(trans('common.updated_success'), new AttributeValueResource($item));
+ }
+
+ public function destroyValue(Request $request, int $id, int $value_id)
+ {
+ try {
+ AttributeRepo::deleteValue($value_id);
+ } catch (Exception $e) {
+ return json_fail($e->getMessage(), []);
+ }
+
+ return json_success(trans('common.deleted_success'));
+ }
+
+ public function destroy(Request $request, int $id)
+ {
+ try {
+ AttributeRepo::delete($id);
+ } catch (Exception $e) {
+ return json_fail($e->getMessage(), []);
+ }
+
+ return json_success(trans('common.deleted_success'));
+ }
+
+ public function autocomplete(Request $request): array
+ {
+ try {
+ $items = AttributeRepo::autocomplete($request->get('name') ?? '', 0);
+ } catch (Exception $e) {
+ return json_fail($e->getMessage(), []);
+ }
+
+ return json_success(trans('common.get_success'), AutocompleteResource::collection($items));
+ }
+
+ public function autocompleteValue(Request $request, int $id): array
+ {
+ try {
+ $items = AttributeRepo::autocompleteValue($id, $request->get('name') ?? '');
+ } catch (Exception $e) {
+ return json_fail($e->getMessage(), []);
+ }
+
+ return json_success(trans('common.get_success'), AutocompleteResource::collection($items));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/AttributeGroupController.php b/beike/Admin/Http/Controllers/AttributeGroupController.php
new file mode 100644
index 00000000..1102a508
--- /dev/null
+++ b/beike/Admin/Http/Controllers/AttributeGroupController.php
@@ -0,0 +1,68 @@
+
+ * @created 2023-01-04 19:45:41
+ * @modified 2023-01-04 19:45:41
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Repositories\AttributeGroupRepo;
+use Exception;
+use Illuminate\Http\Request;
+
+class AttributeGroupController extends Controller
+{
+ public function index()
+ {
+ try {
+ $data = [
+ 'attribute_groups' => AttributeGroupRepo::getList(),
+ ];
+ $data = hook_filter('admin.attribute_group.index.data', $data);
+ } catch (Exception $e) {
+ return view('admin::pages.attribute_group.index', $data)->withErrors(['error' => $e->getMessage()]);
+ }
+
+ return view('admin::pages.attribute_group.index', $data);
+ }
+
+ public function store(Request $request)
+ {
+ try {
+ $requestData = json_decode($request->getContent(), true);
+ $item = AttributeGroupRepo::create($requestData);
+ } catch (Exception $e) {
+ return json_fail($e->getMessage(), []);
+ }
+
+ return json_success(trans('common.created_success'), $item);
+ }
+
+ public function update(Request $request, int $id)
+ {
+ try {
+ $requestData = json_decode($request->getContent(), true);
+ $item = AttributeGroupRepo::update($id, $requestData);
+ } catch (Exception $e) {
+ return json_fail($e->getMessage(), []);
+ }
+
+ return json_success(trans('common.updated_success'), $item);
+ }
+
+ public function destroy(Request $request, int $id)
+ {
+ try {
+ AttributeGroupRepo::delete($id);
+ } catch (Exception $e) {
+ return json_fail($e->getMessage(), []);
+ }
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/BrandController.php b/beike/Admin/Http/Controllers/BrandController.php
new file mode 100644
index 00000000..2163e2b6
--- /dev/null
+++ b/beike/Admin/Http/Controllers/BrandController.php
@@ -0,0 +1,129 @@
+
+ * @created 2022-07-27 21:17:04
+ * @modified 2022-07-27 21:17:04
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Models\Brand;
+use Beike\Repositories\BrandRepo;
+use Exception;
+use Illuminate\Http\Request;
+
+class BrandController extends Controller
+{
+ /**
+ * 显示品牌列表
+ * @param Request $request
+ * @return mixed
+ */
+ public function index(Request $request)
+ {
+ $brands = BrandRepo::list($request->only('name', 'first', 'status'));
+ $data = [
+ 'brands' => $brands,
+ ];
+ $data = hook_filter('admin.brand.index.data', $data);
+ if ($request->expectsJson()) {
+ return json_success(trans('common.success'), $data);
+ }
+
+ return view('admin::pages.brands.index', $data);
+ }
+
+ /**
+ * 创建品牌
+ *
+ * @param Request $request
+ * @return array
+ */
+ public function store(Request $request): array
+ {
+ $requestData = $request->all();
+ $data = [
+ 'request_data' => $requestData,
+ ];
+
+ hook_action('admin.brand.store.before', $data);
+ $brand = BrandRepo::create($requestData);
+ hook_action('admin.brand.store.after', ['brand' => $brand, 'request_data' => $requestData]);
+
+ return json_success(trans('common.created_success'), $brand);
+ }
+
+ /**
+ * @param Request $request
+ * @param Brand $brand
+ * @return array
+ * @throws Exception
+ */
+ public function update(Request $request, Brand $brand): array
+ {
+ $requestData = $request->all();
+ $data = [
+ 'brand_id' => $brand,
+ 'request_data' => $requestData,
+ ];
+ hook_action('admin.brand.update.before', $data);
+ $brand = BrandRepo::update($brand, $requestData);
+ hook_action('admin.brand.update.after', $data);
+
+ return json_success(trans('common.updated_success'), $brand);
+ }
+
+ /**
+ * @param Request $request
+ * @param Brand $brand
+ * @return array
+ */
+ public function destroy(Request $request, Brand $brand): array
+ {
+ hook_action('admin.brand.destroy.before', $brand);
+ BrandRepo::delete($brand);
+ hook_action('admin.brand.destroy.after', $brand);
+
+ return json_success(trans('common.deleted_success'));
+ }
+
+ /**
+ * @param Request $request
+ * @return array
+ */
+ public function autocomplete(Request $request): array
+ {
+ $brands = BrandRepo::autocomplete($request->get('name') ?? '', 0);
+
+ return json_success(trans('common.get_success'), $brands);
+ }
+
+ /**
+ * @param int $id
+ * @return array
+ */
+ public function name(int $id): array
+ {
+ $name = BrandRepo::getName($id);
+
+ return json_success(trans('common.get_success'), $name);
+ }
+
+ /**
+ * 根据商品ID批量获取商品名称
+ *
+ * @param Request $request
+ * @return array
+ */
+ public function getNames(Request $request): array
+ {
+ $ids = explode(',', $request->get('ids'));
+ $name = BrandRepo::getNames($ids);
+
+ return json_success(trans('common.get_success'), $name);
+ }
+}
diff --git a/beike/Admin/Http/Controllers/CategoryController.php b/beike/Admin/Http/Controllers/CategoryController.php
new file mode 100644
index 00000000..e80f2288
--- /dev/null
+++ b/beike/Admin/Http/Controllers/CategoryController.php
@@ -0,0 +1,108 @@
+ CategoryResource::collection($categories),
+ ];
+ $data = hook_filter('admin.category.index.data', $data);
+
+ return view('admin::pages.categories.index', $data);
+ }
+
+ public function create(Request $request)
+ {
+ return $this->form($request);
+ }
+
+ public function store(CategoryRequest $request)
+ {
+ return $this->save($request);
+ }
+
+ public function edit(Request $request, Category $category)
+ {
+ return $this->form($request, $category);
+ }
+
+ public function update(CategoryRequest $request, Category $category)
+ {
+ return $this->save($request, $category);
+ }
+
+ /**
+ * 删除分类
+ * @param Request $request
+ * @param Category $category
+ * @return array
+ * @throws \Exception
+ */
+ public function destroy(Request $request, Category $category): array
+ {
+ CategoryRepo::delete($category);
+ hook_action('admin.category.destroy.after', $category);
+
+ return json_success(trans('common.deleted_success'));
+ }
+
+ public function name(int $id)
+ {
+ $name = CategoryRepo::getName($id);
+
+ return json_success(trans('common.get_success'), $name);
+ }
+
+ protected function form(Request $request, Category $category = null)
+ {
+ if ($category) {
+ $descriptions = $category->descriptions->keyBy('locale');
+ }
+
+ $data = [
+ 'category' => $category ?? new Category(),
+ 'languages' => LanguageRepo::all(),
+ 'descriptions' => $descriptions ?? null,
+ 'categories' => CategoryRepo::flatten(locale()),
+ '_redirect' => $this->getRedirect(),
+ ];
+ $data = hook_filter('admin.category.form.data', $data);
+
+ return view('admin::pages.categories.form', $data);
+ }
+
+ protected function save(Request $request, ?Category $category = null)
+ {
+ $requestData = $request->all();
+ $category = (new CategoryService())->createOrUpdate($requestData, $category);
+ $data = [
+ 'category' => $category,
+ 'request_data' => $requestData,
+ ];
+
+ hook_action('admin.category.save.after', $data);
+
+ return redirect($this->getRedirect())->with('success', 'Category created successfully');
+ }
+
+ public function autocomplete(Request $request)
+ {
+ $categories = CategoryRepo::autocomplete($request->get('name') ?? '');
+
+ return json_success(trans('common.get_success'), $categories);
+ }
+}
diff --git a/beike/Admin/Http/Controllers/Controller.php b/beike/Admin/Http/Controllers/Controller.php
new file mode 100644
index 00000000..b746357b
--- /dev/null
+++ b/beike/Admin/Http/Controllers/Controller.php
@@ -0,0 +1,89 @@
+defaultRoute)) {
+ $this->defaultRoute = $this->getDefaultRoute();
+ }
+
+ return request('_redirect') ?? request()->header('referer', admin_route($this->defaultRoute));
+ }
+
+ /**
+ * 获取当前管理界面列表页路由
+ * @return string
+ */
+ private function getDefaultRoute(): string
+ {
+ $currentRouteName = Route::getCurrentRoute()->getName();
+ $names = explode('.', $currentRouteName);
+ $name = $names[1] ?? '';
+
+ return "{$name}.index";
+ }
+
+ /**
+ * 导出CSV
+ *
+ * @param $fileName
+ * @param $items
+ * @param string $module
+ * @return BinaryFileResponse
+ * @throws \Exception
+ */
+ protected function downloadCsv($fileName, $items, string $module = ''): BinaryFileResponse
+ {
+ $module = $module ?: $fileName;
+ $charset = app()->getLocale() == 'zh-hk' ? 'BIG5' : 'GBK';
+
+ if (empty($items)) {
+ throw new \Exception(trans('common.empty_items'));
+ }
+ if (! str_contains($fileName, '.csv')) {
+ $fileName = $fileName . '-' . date('YmdHis') . '.csv';
+ }
+ $headers = [
+ 'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
+ 'Content-type' => 'application/octet-stream',
+ // 'Content-type' => 'text/csv',
+ 'Content-Disposition' => "attachment; filename={$fileName}",
+ 'Content-Transfer-Encoding' => 'binary',
+ 'Expires' => '0',
+ 'Pragma' => 'public',
+ ];
+
+ $columns = array_keys($items[0]);
+ foreach ($columns as $index => $column) {
+ $columns[$index] = iconv('UTF-8', "{$charset}//IGNORE", trans("$module.{$column}"));
+ }
+ foreach ($items as $index => $item) {
+ foreach ($item as $field => $value) {
+ $items[$index][$field] = iconv('UTF-8', "{$charset}//IGNORE", $value);
+ }
+ }
+
+ $filePath = storage_path('app/' . $fileName);
+ $file = fopen($filePath, 'w');
+ fputcsv($file, $columns);
+ foreach ($items as $item) {
+ fputcsv($file, $item);
+ }
+ fclose($file);
+
+ return response()->download($filePath, $fileName, $headers);
+ }
+}
diff --git a/beike/Admin/Http/Controllers/CountryController.php b/beike/Admin/Http/Controllers/CountryController.php
new file mode 100644
index 00000000..1d69afbb
--- /dev/null
+++ b/beike/Admin/Http/Controllers/CountryController.php
@@ -0,0 +1,60 @@
+
+ * @created 2022-08-29 16:17:04
+ * @modified 2022-08-29 16:17:04
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Repositories\CountryRepo;
+use Illuminate\Http\Request;
+
+class CountryController extends Controller
+{
+ public function index(Request $request)
+ {
+ $countries = CountryRepo::list($request->only('name', 'code', 'status'));
+
+ $data = [
+ 'country' => $countries,
+ ];
+ $data = hook_filter('admin.country.index.data', $data);
+ if ($request->expectsJson()) {
+ return json_success(trans('common.success'), $data);
+ }
+
+ return view('admin::pages.country.index', $data);
+ }
+
+ public function store(Request $request)
+ {
+ $country = CountryRepo::create($request->only('name', 'code', 'sort_order', 'status'));
+
+ hook_action('admin.country.store.after', $country);
+
+ return json_success(trans('common.created_success'), $country);
+ }
+
+ public function update(Request $request, int $id)
+ {
+ $country = CountryRepo::update($id, $request->only('name', 'code', 'sort_order', 'status'));
+
+ hook_action('admin.country.store.after', $country);
+
+ return json_success(trans('common.updated_success'), $country);
+ }
+
+ public function destroy(int $id)
+ {
+ CountryRepo::delete($id);
+
+ hook_action('admin.country.destroy.after', $id);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/CurrencyController.php b/beike/Admin/Http/Controllers/CurrencyController.php
new file mode 100644
index 00000000..f38adc76
--- /dev/null
+++ b/beike/Admin/Http/Controllers/CurrencyController.php
@@ -0,0 +1,74 @@
+
+ * @created 2022-06-30 16:17:04
+ * @modified 2022-06-30 16:17:04
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Requests\CurrencyRequest;
+use Beike\Repositories\CurrencyRepo;
+use Illuminate\Http\Request;
+
+class CurrencyController extends Controller
+{
+ protected string $defaultRoute = 'currencies.index';
+
+ public function index(Request $request)
+ {
+ $currencies = CurrencyRepo::all();
+
+ $data = [
+ 'currencies' => $currencies,
+ ];
+ $data = hook_filter('admin.currency.index.data', $data);
+
+ return view('admin::pages.currencies.index', $data);
+ }
+
+ public function store(CurrencyRequest $request)
+ {
+ $data = [
+ 'name' => $request->get('name', ''),
+ 'code' => $request->get('code', ''),
+ 'symbol_left' => $request->get('symbol_left', ''),
+ 'symbol_right' => $request->get('symbol_right', ''),
+ 'decimal_place' => (float) $request->get('decimal_place', 0),
+ 'value' => (float) $request->get('value', 1),
+ 'status' => (int) $request->get('status', 0),
+ ];
+ $currency = CurrencyRepo::create($data);
+ hook_action('admin.currency.store.after', $currency);
+
+ return json_success(trans('common.created_success'), $currency);
+ }
+
+ public function update(CurrencyRequest $request, int $id)
+ {
+ $data = [
+ 'name' => $request->get('name', ''),
+ 'code' => $request->get('code', ''),
+ 'symbol_left' => $request->get('symbol_left', ''),
+ 'symbol_right' => $request->get('symbol_right', ''),
+ 'decimal_place' => (float) $request->get('decimal_place', 0),
+ 'value' => (float) $request->get('value', 1),
+ 'status' => (int) $request->get('status', 0),
+ ];
+ $currency = CurrencyRepo::update($id, $data);
+
+ return json_success(trans('common.updated_success'), $currency);
+ }
+
+ public function destroy(Request $request, int $currencyId)
+ {
+ CurrencyRepo::delete($currencyId);
+ hook_action('admin.currency.destroy.after', $currencyId);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/CustomerController.php b/beike/Admin/Http/Controllers/CustomerController.php
new file mode 100644
index 00000000..8177d07d
--- /dev/null
+++ b/beike/Admin/Http/Controllers/CustomerController.php
@@ -0,0 +1,141 @@
+
+ * @created 2022-06-28 16:17:04
+ * @modified 2022-06-28 16:17:04
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Requests\CustomerRequest;
+use Beike\Admin\Http\Resources\AddressResource;
+use Beike\Admin\Http\Resources\CustomerGroupDetail;
+use Beike\Admin\Http\Resources\CustomerResource;
+use Beike\Admin\Services\CustomerService;
+use Beike\Repositories\AddressRepo;
+use Beike\Repositories\CountryRepo;
+use Beike\Repositories\CustomerGroupRepo;
+use Beike\Repositories\CustomerRepo;
+use Illuminate\Http\Request;
+
+class CustomerController extends Controller
+{
+ public function index(Request $request)
+ {
+ $customers = CustomerRepo::list($request->only(['name', 'email', 'status', 'from', 'customer_group_id']));
+
+ $data = [
+ 'customers' => $customers,
+ 'customers_format' => CustomerResource::collection($customers)->jsonSerialize(),
+ 'customer_groups' => CustomerGroupDetail::collection(CustomerGroupRepo::list())->jsonSerialize(),
+ 'type' => 'customer',
+ ];
+ $data = hook_filter('admin.customer.index.data', $data);
+ if ($request->expectsJson()) {
+ return json_success(trans('success'), $data);
+ }
+
+ return view('admin::pages.customers.index', $data);
+ }
+
+ public function trashed(Request $request)
+ {
+ $customers = CustomerRepo::list(array_merge($request->only(['name', 'email', 'status', 'from', 'customer_group_id']), ['only_trashed' => true]));
+
+ $data = [
+ 'customers' => $customers,
+ 'customers_format' => CustomerResource::collection($customers)->jsonSerialize(),
+ 'customer_groups' => CustomerGroupDetail::collection(CustomerGroupRepo::list())->jsonSerialize(),
+ 'type' => 'trashed',
+ ];
+ $data = hook_filter('admin.customer.trashed.data', $data);
+ if ($request->expectsJson()) {
+ return json_success(trans('success'), $data);
+ }
+
+ return view('admin::pages.customers.index', $data);
+ }
+
+ public function store(CustomerRequest $request)
+ {
+ $requestData = $request->all();
+
+ hook_action('admin.customer.store.before', ['request_data' => $requestData]);
+
+ $customer = CustomerService::create($requestData);
+
+ hook_action('admin.customer.store.after', ['customer_id' => $customer->id, 'request_data' => $requestData]);
+
+ return json_success(trans('common.success'), new CustomerResource($customer));
+ }
+
+ public function edit(Request $request, int $customerId)
+ {
+ $addresses = AddressRepo::listByCustomer($customerId);
+ $customer = CustomerRepo::find($customerId);
+ $data = [
+ 'customer' => $customer,
+ 'customer_groups' => CustomerGroupDetail::collection(CustomerGroupRepo::list())->jsonSerialize(),
+ 'addresses' => AddressResource::collection($addresses)->jsonSerialize(),
+ 'countries' => CountryRepo::all(),
+ 'country_id' => system_setting('base.country_id'),
+ '_redirect' => $this->getRedirect(),
+ ];
+ $data = hook_filter('admin.customer.edit.data', $data);
+
+ return view('admin::pages.customers.form', $data);
+ }
+
+ public function update(CustomerRequest $request, int $customerId)
+ {
+ $requestData = $request->all();
+ $password = $requestData['password'] ?? '';
+ if (empty($password)) {
+ unset($requestData['password']);
+ }
+
+ hook_action('admin.customer.update.before', ['customer_id' => $customerId, 'request_data' => $requestData]);
+
+ $customer = CustomerRepo::update($customerId, $requestData);
+
+ hook_action('admin.customer.update.after', ['customer_id' => $customerId, 'request_data' => $requestData]);
+
+ return json_success(trans('common.updated_success'), $customer);
+ }
+
+ public function destroy(Request $request, int $customerId)
+ {
+ CustomerRepo::delete($customerId);
+ hook_action('admin.customer.destroy.after', $customerId);
+
+ return json_success(trans('common.deleted_success'));
+ }
+
+ public function restore(Request $request, int $customerId)
+ {
+ CustomerRepo::restore($customerId);
+ hook_action('admin.customer.restore.after', $customerId);
+
+ return json_success(trans('common.restored_success'));
+ }
+
+ public function forceDelete(Request $request, int $customerId)
+ {
+ CustomerRepo::forceDelete($customerId);
+ hook_action('admin.customer.force_delete.after', $customerId);
+
+ return json_success(trans('common.success'));
+ }
+
+ public function forceDeleteAll(Request $request)
+ {
+ CustomerRepo::forceDeleteAll();
+ hook_action('admin.customer.force_delete_all.after', ['module' => 'customer']);
+
+ return json_success(trans('common.success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/CustomerGroupController.php b/beike/Admin/Http/Controllers/CustomerGroupController.php
new file mode 100644
index 00000000..bfbaaa81
--- /dev/null
+++ b/beike/Admin/Http/Controllers/CustomerGroupController.php
@@ -0,0 +1,65 @@
+
+ * @created 2022-06-30 21:17:04
+ * @modified 2022-06-30 21:17:04
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Requests\CustomerGroupRequest;
+use Beike\Admin\Services\CustomerGroupService;
+use Beike\Repositories\CustomerGroupRepo;
+use Beike\Repositories\LanguageRepo;
+use Illuminate\Http\Request;
+
+class CustomerGroupController extends Controller
+{
+ protected string $defaultRoute = 'customer_groups.index';
+
+ public function index(Request $request)
+ {
+ $customers = CustomerGroupRepo::list();
+
+ $data = [
+ 'customer_groups' => $customers,
+ 'languages' => LanguageRepo::all(),
+ ];
+ $data = hook_filter('admin.customer_group.index.data', $data);
+
+ return view('admin::pages.customer_groups.index', $data);
+ }
+
+ public function store(CustomerGroupRequest $request)
+ {
+ $customerGroup = CustomerGroupService::create($request->all());
+ $customerGroup->load('descriptions', 'description');
+
+ hook_action('admin.customer_group.store.after', $customerGroup);
+
+ return json_success(trans('common.created_success'), $customerGroup);
+ }
+
+ public function update(CustomerGroupRequest $request, int $id)
+ {
+ $customerGroup = CustomerGroupService::update($id, $request->all());
+ $customerGroup->load('descriptions', 'description');
+
+ hook_action('admin.customer_group.update.after', $customerGroup);
+
+ return json_success(trans('common.updated_success'), $customerGroup);
+ }
+
+ public function destroy(Request $request, int $id)
+ {
+ CustomerGroupRepo::delete($id);
+
+ hook_action('admin.customer_group.destroy.after', $id);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/DesignController.php b/beike/Admin/Http/Controllers/DesignController.php
new file mode 100644
index 00000000..c56edc32
--- /dev/null
+++ b/beike/Admin/Http/Controllers/DesignController.php
@@ -0,0 +1,82 @@
+ [
+ 'editor-slide_show', 'editor-image401', 'editor-tab_product', 'editor-product', 'editor-image100',
+ 'editor-brand', 'editor-icons', 'editor-rich_text', 'editor-image200', 'editor-image300',
+ ],
+ 'design_settings' => system_setting('base.design_setting'),
+ ];
+
+ $data = hook_filter('admin.design.index.data', $data);
+
+ return view('admin::pages.design.builder.index', $data);
+ }
+
+ /**
+ * 预览模块显示结果
+ *
+ * @param Request $request
+ * @return View
+ * @throws \Exception
+ */
+ public function preview(Request $request): View
+ {
+ $module = json_decode($request->getContent(), true);
+ $moduleId = $module['module_id'] ?? '';
+ $moduleCode = $module['code'] ?? '';
+ $content = $module['content'] ?? '';
+ $viewPath = $module['view_path'] ?? '';
+
+ if (empty($viewPath)) {
+ $viewPath = "design.{$moduleCode}";
+ }
+
+ $viewData = [
+ 'code' => $moduleCode,
+ 'module_id' => $moduleId,
+ 'view_path' => $viewPath,
+ 'content' => DesignService::handleModuleContent($moduleCode, $content),
+ 'design' => (bool) $request->get('design'),
+ ];
+
+ $viewData = hook_filter('admin.design.preview.data', $viewData);
+
+ return view($viewPath, $viewData);
+ }
+
+ /**
+ * 更新所有数据
+ *
+ * @param Request $request
+ * @return array
+ * @throws \Throwable
+ */
+ public function update(Request $request): array
+ {
+ $content = json_decode($request->getContent(), true);
+ $moduleData = DesignService::handleRequestModules($content);
+ SettingRepo::storeValue('design_setting', $moduleData);
+
+ hook_action('admin.design.update.after', $moduleData);
+
+ return json_success(trans('common.updated_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/DesignFooterController.php b/beike/Admin/Http/Controllers/DesignFooterController.php
new file mode 100644
index 00000000..ccbfd311
--- /dev/null
+++ b/beike/Admin/Http/Controllers/DesignFooterController.php
@@ -0,0 +1,65 @@
+ system_setting('base.footer_setting'),
+ ];
+ $data = hook_filter('admin.design_footer.index.data', $data);
+
+ return view('admin::pages.design.builder.footer', $data);
+ }
+
+ /**
+ * 预览模块显示结果
+ *
+ * @param Request $request
+ * @return View
+ * @throws \Exception
+ */
+ public function preview(Request $request): View
+ {
+ $content = json_decode($request->getContent(), true);
+ $viewPath = 'layout.footer';
+
+ $viewData = [
+ 'view_path' => $viewPath,
+ 'footer_content' => FooterRepo::handleFooterData($content),
+ 'design' => (bool) $request->get('design'),
+ ];
+ $viewData = hook_filter('admin.design_footer.index.data', $viewData);
+
+ return view($viewPath, $viewData);
+ }
+
+ /**
+ * 更新所有数据
+ *
+ * @param Request $request
+ * @return array
+ * @throws \Throwable
+ */
+ public function update(Request $request): array
+ {
+ $content = json_decode($request->getContent(), true);
+
+ SettingRepo::storeValue('footer_setting', $content);
+
+ return json_success(trans('common.updated_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/DesignMenuController.php b/beike/Admin/Http/Controllers/DesignMenuController.php
new file mode 100644
index 00000000..bef9fba2
--- /dev/null
+++ b/beike/Admin/Http/Controllers/DesignMenuController.php
@@ -0,0 +1,42 @@
+ system_setting('base.menu_setting', []),
+ ];
+ $data = hook_filter('admin.design_menu.index.data', $data);
+
+ return view('admin::pages.design.builder.menu', $data);
+ }
+
+ /**
+ * 更新所有数据
+ *
+ * @param Request $request
+ * @return array
+ * @throws \Throwable
+ */
+ public function update(Request $request): array
+ {
+ $content = json_decode($request->getContent(), true);
+
+ SettingRepo::storeValue('menu_setting', $content);
+
+ return json_success(trans('common.updated_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/EditController.php b/beike/Admin/Http/Controllers/EditController.php
new file mode 100644
index 00000000..4c7a76b5
--- /dev/null
+++ b/beike/Admin/Http/Controllers/EditController.php
@@ -0,0 +1,25 @@
+update($request->only('email', 'name', 'password'));
+
+ return redirect()->back()->withErrors(['email' => 'Invalid credentials']);
+ }
+
+ public function locale(Request $request)
+ {
+ $user = current_user();
+ $user->update($request->only('locale'));
+
+ return redirect()->back();
+ }
+}
diff --git a/beike/Admin/Http/Controllers/FileManagerController.php b/beike/Admin/Http/Controllers/FileManagerController.php
new file mode 100644
index 00000000..924e71b6
--- /dev/null
+++ b/beike/Admin/Http/Controllers/FileManagerController.php
@@ -0,0 +1,134 @@
+getDirectories();
+ $data = hook_filter('admin.file_manager.index.data', $data);
+
+ return view('admin::pages.file_manager.index', ['directories' => $data]);
+ }
+
+ /**
+ * 获取某个文件夹下面的文件列表
+ *
+ * @param Request $request
+ * @return array
+ * @throws \Exception
+ */
+ public function getFiles(Request $request): array
+ {
+ $baseFolder = $request->get('base_folder');
+ $page = (int) $request->get('page');
+ $perPage = (int) $request->get('per_page');
+
+ $data = (new FileManagerService)->getFiles($baseFolder, $page, $perPage);
+
+ return hook_filter('admin.file_manager.files.data', $data);
+ }
+
+ /**
+ * 获取文件夹列表
+ * @param Request $request
+ * @return mixed
+ * @throws \Exception
+ */
+ public function getDirectories(Request $request)
+ {
+ $baseFolder = $request->get('base_folder');
+
+ $data = (new FileManagerService)->getDirectories($baseFolder);
+
+ return hook_filter('admin.file_manager.directories.data', $data);
+ }
+
+ /**
+ * 创建文件夹
+ * POST /admin/file_manager
+ * @throws \Exception
+ */
+ public function createDirectory(Request $request): array
+ {
+ $folderName = $request->get('name');
+ (new FileManagerService)->createDirectory($folderName);
+
+ return json_success(trans('common.created_success'));
+ }
+
+ /**
+ * 文件或文件夹改名
+ * PUT /admin/file_manager/rename
+ * @throws \Exception
+ */
+ public function rename(Request $request): array
+ {
+ $originPath = $request->get('origin_name');
+ $newPath = $request->get('new_name');
+ (new FileManagerService)->updateName($originPath, $newPath);
+
+ return json_success(trans('common.updated_success'));
+ }
+
+ /**
+ * 删除文件或文件夹
+ * DELETE /admin/file_manager/files {"path":"/xx/yy", "files":["1.jpg", "2.png"]}
+ * @throws \Exception
+ */
+ public function destroyFiles(Request $request): array
+ {
+ $requestData = json_decode($request->getContent(), true);
+ $basePath = $requestData['path'] ?? '';
+ $files = $requestData['files'] ?? [];
+ (new FileManagerService)->deleteFiles($basePath, $files);
+
+ return json_success(trans('common.deleted_success'));
+ }
+
+ /**
+ * 删除文件夹
+ *
+ * @param Request $request
+ * @return array
+ * @throws \Exception
+ */
+ public function destroyDirectories(Request $request): array
+ {
+ $folderName = $request->get('name');
+ (new FileManagerService)->deleteDirectoryOrFile($folderName);
+
+ return json_success(trans('common.deleted_success'));
+ }
+
+ /**
+ * 上传文件
+ * POST /admin/file_manager/upload
+ *
+ * @param UploadRequest $request
+ * @return array
+ */
+ public function uploadFiles(UploadRequest $request): array
+ {
+ $file = $request->file('file');
+ $savePath = $request->get('path');
+
+ $originName = $file->getClientOriginalName();
+ $filePath = $file->storeAs($savePath, $originName, 'catalog');
+
+ return [
+ 'name' => $originName,
+ 'url' => asset('catalog/' . $filePath),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Controllers/ForgottenController.php b/beike/Admin/Http/Controllers/ForgottenController.php
new file mode 100644
index 00000000..906682c5
--- /dev/null
+++ b/beike/Admin/Http/Controllers/ForgottenController.php
@@ -0,0 +1,53 @@
+
+ * @created 2022-07-14 11:39:08
+ * @modified 2022-07-14 11:39:08
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Requests\ForgottenRequest;
+use Beike\Admin\Http\Requests\VerifyCodeRequest;
+use Beike\Admin\Services\UserService;
+use Illuminate\Http\Request;
+
+class ForgottenController
+{
+ /**
+ * 找回密码页面
+ * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
+ */
+ public function index()
+ {
+ return view('admin::pages.user.forgotten');
+ }
+
+ /**
+ * 接收email地址,生成验证码发送到邮件地址
+ * @param Request $request
+ * @return array
+ */
+ public function sendVerifyCode(VerifyCodeRequest $request)
+ {
+ UserService::sendVerifyCodeForForgotten($request->get('email'));
+
+ return json_success(trans('admin/forgotten.verify_code_sent'));
+ }
+
+ /**
+ * 接收验证码和新密码、确认密码,验证验证码是否正确、密码和确认密码是否相等,然后修改密码
+ * @param Request $request
+ * @return array
+ */
+ public function changePassword(ForgottenRequest $request)
+ {
+ UserService::verifyAndChangePassword($request->get('code'), $request->get('email'), $request->get('password'));
+
+ return json_success(trans('common.updated_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/HomeController.php b/beike/Admin/Http/Controllers/HomeController.php
new file mode 100644
index 00000000..1f6f55cb
--- /dev/null
+++ b/beike/Admin/Http/Controllers/HomeController.php
@@ -0,0 +1,27 @@
+ DashboardRepo::getCustomerViewData(),
+ 'orders' => DashboardRepo::getOrderData(),
+ 'customers' => DashboardRepo::getCustomerData(),
+ 'order_totals' => DashboardRepo::getTotalData(),
+ 'order_trends' => [
+ 'latest_month' => OrderReportRepo::getLatestMonth(),
+ 'latest_week' => OrderReportRepo::getLatestWeek(),
+ 'latest_year' => OrderReportRepo::getLatestYear(),
+ ],
+ ];
+
+ return view('admin::pages.home', $data);
+ }
+}
diff --git a/beike/Admin/Http/Controllers/LanguageController.php b/beike/Admin/Http/Controllers/LanguageController.php
new file mode 100644
index 00000000..e6f9302c
--- /dev/null
+++ b/beike/Admin/Http/Controllers/LanguageController.php
@@ -0,0 +1,74 @@
+
+ * @created 2022-07-05 16:37:04
+ * @created 2022-07-05 16:37:04
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Services\LanguageService;
+use Beike\Repositories\LanguageRepo;
+use Exception;
+use Illuminate\Http\Request;
+
+class LanguageController extends Controller
+{
+ /**
+ * 语言列表
+ * @return mixed
+ */
+ public function index()
+ {
+ $languages = LanguageService::all();
+
+ $data = [
+ 'languages' => $languages,
+ ];
+ $data = hook_filter('admin.language.index.data', $data);
+
+ return view('admin::pages.languages.index', $data);
+ }
+
+ /**
+ * 新建语言
+ * @param Request $request
+ * @return array
+ */
+ public function store(Request $request): array
+ {
+ $language = LanguageService::create($request->only('name', 'code'));
+
+ return json_success(trans('common.created_success'), $language);
+ }
+
+ /**
+ * @param Request $request
+ * @param int $id
+ * @return array
+ * @throws Exception
+ */
+ public function update(Request $request, int $id): array
+ {
+ $language = LanguageRepo::update($id, $request->except('status'));
+
+ return json_success(trans('common.updated_success'), $language);
+ }
+
+ /**
+ * 删除语言
+ *
+ * @param int $currencyId
+ * @return array
+ */
+ public function destroy(int $id): array
+ {
+ LanguageService::delete($id);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/LoginController.php b/beike/Admin/Http/Controllers/LoginController.php
new file mode 100644
index 00000000..9de1416a
--- /dev/null
+++ b/beike/Admin/Http/Controllers/LoginController.php
@@ -0,0 +1,37 @@
+
+ * @created 2022-12-21 14:22:26
+ * @modified 2022-12-21 14:22:26
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use App\Http\Controllers\Controller;
+use Beike\Admin\Http\Requests\LoginRequest;
+use Beike\Models\AdminUser;
+
+class LoginController extends Controller
+{
+ public function show()
+ {
+ if (auth(AdminUser::AUTH_GUARD)->check()) {
+ return redirect()->back();
+ }
+
+ return view('admin::pages.login.login', \request()->only('admin_email', 'admin_password'));
+ }
+
+ public function store(LoginRequest $loginRequest)
+ {
+ if (auth(AdminUser::AUTH_GUARD)->attempt($loginRequest->validated())) {
+ return redirect(admin_route('home.index'));
+ }
+
+ return redirect()->back()->with(['error' => trans('auth.failed')])->withInput();
+ }
+}
diff --git a/beike/Admin/Http/Controllers/LogoutController.php b/beike/Admin/Http/Controllers/LogoutController.php
new file mode 100644
index 00000000..9cd16dcc
--- /dev/null
+++ b/beike/Admin/Http/Controllers/LogoutController.php
@@ -0,0 +1,18 @@
+logout();
+
+ return redirect(admin_route('login.show'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/MarketingController.php b/beike/Admin/Http/Controllers/MarketingController.php
new file mode 100644
index 00000000..fbd67013
--- /dev/null
+++ b/beike/Admin/Http/Controllers/MarketingController.php
@@ -0,0 +1,100 @@
+
+ * @created 2022-09-26 11:49:34
+ * @modified 2022-09-26 11:49:34
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Services\MarketingService;
+use Beike\Repositories\PluginRepo;
+use Illuminate\Http\Request;
+
+class MarketingController
+{
+ /**
+ * @param Request $request
+ * @return mixed
+ */
+ public function index(Request $request)
+ {
+ $filters = [
+ 'type' => $request->get('type'),
+ 'keyword' => $request->get('keyword'),
+ 'page' => $request->get('page'),
+ ];
+ $plugins = MarketingService::getInstance()->getList($filters);
+ $data = [
+ 'plugins' => $plugins,
+ 'domain' => str_replace(['http://', 'https://'], '', config('app.url')),
+ 'types' => PluginRepo::getTypes(),
+ ];
+ $data = hook_filter('admin.marketing.index.data', $data);
+ if ($request->expectsJson()) {
+ return json_success(trans('common.success'), $data);
+ }
+
+ return view('admin::pages.marketing.index', $data);
+ }
+
+ /**
+ * 获取单个插件详情
+ */
+ public function show(Request $request)
+ {
+ try {
+ $pluginCode = $request->code;
+ $plugin = MarketingService::getInstance()->getPlugin($pluginCode);
+ $data = [
+ 'domain' => str_replace(['http://', 'https://'], '', config('app.url')),
+ 'plugin' => $plugin,
+ ];
+
+ $data = hook_filter('admin.marketing.show.data', $data);
+
+ if ($request->expectsJson()) {
+ return $data;
+ }
+
+ return view('admin::pages.marketing.show', $data);
+ } catch (\Exception $e) {
+ return redirect(admin_route('marketing.index'))->withErrors(['error' => $e->getMessage()]);
+ }
+ }
+
+ /**
+ * 下单购买插件
+ */
+ public function buy(Request $request)
+ {
+ try {
+ $postData = $request->getContent();
+ $pluginCode = $request->code;
+ $result = MarketingService::getInstance()->buy($pluginCode, $postData);
+
+ return json_success('获取成功', $result);
+ } catch (\Exception $e) {
+ return json_fail($e->getMessage());
+ }
+ }
+
+ /**
+ * 下载插件安装包到本地
+ */
+ public function download(Request $request)
+ {
+ try {
+ $pluginCode = $request->code;
+ MarketingService::getInstance()->download($pluginCode);
+
+ return json_success('下载解压成功, 请去插件列表安装');
+ } catch (\Exception $e) {
+ return json_fail($e->getMessage());
+ }
+ }
+}
diff --git a/beike/Admin/Http/Controllers/OrderController.php b/beike/Admin/Http/Controllers/OrderController.php
new file mode 100644
index 00000000..07ca08a7
--- /dev/null
+++ b/beike/Admin/Http/Controllers/OrderController.php
@@ -0,0 +1,123 @@
+
+ * @created 2022-07-05 10:45:26
+ * @modified 2022-07-05 10:45:26
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Resources\OrderSimple;
+use Beike\Models\Order;
+use Beike\Models\OrderShipment;
+use Beike\Repositories\OrderRepo;
+use Beike\Services\ShipmentService;
+use Beike\Services\StateMachineService;
+use Beike\Shop\Http\Resources\Account\OrderList;
+use Illuminate\Http\Request;
+
+class OrderController extends Controller
+{
+ /**
+ * 获取订单列表
+ *
+ * @param Request $request
+ * @return mixed
+ * @throws \Exception
+ */
+ public function index(Request $request)
+ {
+ $orders = OrderRepo::filterOrders($request->all());
+ $data = [
+ 'orders' => OrderList::collection($orders),
+ 'statuses' => StateMachineService::getAllStatuses(),
+ ];
+ $data = hook_filter('admin.order.index.data', $data);
+
+ return view('admin::pages.orders.index', $data);
+ }
+
+ /**
+ * 导出订单列表
+ *
+ * @param Request $request
+ * @return mixed
+ * @throws \Exception
+ */
+ public function export(Request $request)
+ {
+ try {
+ $orders = OrderRepo::filterAll($request->all());
+ $items = OrderSimple::collection($orders)->jsonSerialize();
+ $items = hook_filter('admin.order.export.data', $items);
+
+ return $this->downloadCsv('orders', $items, 'order');
+ } catch (\Exception $e) {
+ return redirect(admin_route('orders.index'))->withErrors(['error' => $e->getMessage()]);
+ }
+ }
+
+ /**
+ * 查看单个订单
+ *
+ * @param Request $request
+ * @param Order $order
+ * @return mixed
+ * @throws \Exception
+ */
+ public function show(Request $request, Order $order)
+ {
+ $order->load(['orderTotals', 'orderHistories', 'orderShipments']);
+ $data = hook_filter('admin.order.show.data', ['order' => $order, 'html_items' => []]);
+ $data['statuses'] = StateMachineService::getInstance($order)->nextBackendStatuses();
+ $data = hook_filter('admin.order.show.data', $data);
+
+ return view('admin::pages.orders.form', $data);
+ }
+
+ /**
+ * 更新订单状态,添加订单更新日志
+ *
+ * @param Request $request
+ * @param Order $order
+ * @return array
+ * @throws \Throwable
+ */
+ public function updateStatus(Request $request, Order $order)
+ {
+ $status = $request->get('status');
+ $comment = $request->get('comment');
+ $notify = $request->get('notify');
+
+ $shipment = ShipmentService::handleShipment(\request('express_code'), \request('express_number'));
+
+ $stateMachine = new StateMachineService($order);
+ $stateMachine->setShipment($shipment)->changeStatus($status, $comment, $notify);
+
+ $orderStatusData = $request->all();
+
+ hook_action('admin.order.update_status.after', $orderStatusData);
+
+ return json_success(trans('common.updated_success'));
+ }
+
+ /**
+ * 更新发货信息
+ */
+ public function updateShipment(Request $request, Order $order, int $orderShipmentId): array
+ {
+ $data = $request->all();
+ $orderShipment = OrderShipment::query()->where('order_id', $order->id)->findOrFail($orderShipmentId);
+ ShipmentService::updateShipment($orderShipment, $data);
+ hook_action('admin.order.update_shipment.after', [
+ 'request_data' => $data,
+ 'shipment' => $orderShipment,
+ ]);
+
+ return json_success(trans('common.updated_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/PageCategoryController.php b/beike/Admin/Http/Controllers/PageCategoryController.php
new file mode 100644
index 00000000..934da06c
--- /dev/null
+++ b/beike/Admin/Http/Controllers/PageCategoryController.php
@@ -0,0 +1,153 @@
+
+ * @created 2023-02-09 10:21:27
+ * @modified 2023-02-09 10:21:27
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Requests\PageCategoryRequest;
+use Beike\Admin\Http\Resources\PageCategoryResource;
+use Beike\Models\PageCategory;
+use Beike\Repositories\PageCategoryRepo;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Http\Request;
+
+class PageCategoryController extends Controller
+{
+ /**
+ * 显示文章分类列表
+ *
+ * @return mixed
+ */
+ public function index()
+ {
+ $pageCategoryList = PageCategoryRepo::getList();
+ $data = [
+ 'page_categories' => $pageCategoryList,
+ 'page_categories_format' => PageCategoryResource::collection($pageCategoryList)->jsonSerialize(),
+ ];
+
+ $data = hook_filter('admin.page_category.index.data', $data);
+
+ return view('admin::pages.page_categories.index', $data);
+ }
+
+ /**
+ * 创建文章分类
+ *
+ * @return mixed
+ */
+ public function create(): mixed
+ {
+ return view('admin::pages.page_categories.form', ['page_category' => new PageCategory()]);
+ }
+
+ /**
+ * 保存新建
+ *
+ * @param PageCategoryRequest $request
+ * @return RedirectResponse
+ * @throws \Throwable
+ */
+ public function store(PageCategoryRequest $request)
+ {
+ try {
+ $requestData = $request->all();
+ hook_action('admin.page_category.store.before', $requestData);
+ $pageCategory = PageCategoryRepo::createOrUpdate($requestData);
+
+ hook_action('admin.page_category.store.after', ['page_category' => $pageCategory, 'request_data' => $requestData]);
+
+ return redirect(admin_route('page_categories.index'));
+ } catch (\Exception $e) {
+ return redirect(admin_route('page_categories.index'))->withErrors(['error' => $e->getMessage()]);
+ }
+ }
+
+ /**
+ * @param Request $request
+ * @param PageCategory $pageCategory
+ * @return mixed
+ */
+ public function edit(Request $request, PageCategory $pageCategory)
+ {
+ $pageCategory->load(['descriptions', 'parent.description']);
+ $descriptions = $pageCategory->descriptions->keyBy('locale')->toArray();
+ $data = [
+ 'page_category' => $pageCategory,
+ 'descriptions' => $descriptions,
+ ];
+
+ $data = hook_filter('admin.page_category.edit.data', $data);
+
+ return view('admin::pages.page_categories.form', $data);
+ }
+
+ /**
+ * 保存更新
+ *
+ * @param PageCategoryRequest $request
+ * @param PageCategory $pageCategory
+ * @return RedirectResponse
+ * @throws \Throwable
+ */
+ public function update(PageCategoryRequest $request, PageCategory $pageCategory)
+ {
+ try {
+ $requestData = $request->all();
+ $requestData['id'] = $pageCategory->id;
+ hook_action('admin.page_category.update.before', $requestData);
+ $pageCategory = PageCategoryRepo::createOrUpdate($requestData);
+ hook_action('admin.page_category.update.after', ['page_category' => $pageCategory, 'request_data' => $requestData]);
+
+ return redirect()->to(admin_route('page_categories.index'));
+ } catch (\Exception $e) {
+ return redirect(admin_route('page_categories.index'))->withErrors(['error' => $e->getMessage()]);
+ }
+ }
+
+ /**
+ * 删除单页
+ *
+ * @param Request $request
+ * @param int $pageId
+ * @return array
+ */
+ public function destroy(Request $request, int $pageId): array
+ {
+ PageCategoryRepo::deleteById($pageId);
+ hook_action('admin.page_category.store.after', $pageId);
+
+ return json_success(trans('common.deleted_success'));
+ }
+
+ /**
+ * 搜索页面标题自动完成
+ * @param Request $request
+ * @return array
+ */
+ public function autocomplete(Request $request): array
+ {
+ $products = PageCategoryRepo::autocomplete($request->get('name') ?? '');
+
+ return json_success(trans('common.get_success'), $products);
+ }
+
+ /**
+ * 获取单页名称
+ * @param PageCategory $pageCategory
+ * @return array
+ */
+ public function name(PageCategory $pageCategory): array
+ {
+ $name = $pageCategory->description->title ?? '';
+
+ return json_success(trans('common.get_success'), $name);
+ }
+}
diff --git a/beike/Admin/Http/Controllers/PagesController.php b/beike/Admin/Http/Controllers/PagesController.php
new file mode 100644
index 00000000..5aa3fa47
--- /dev/null
+++ b/beike/Admin/Http/Controllers/PagesController.php
@@ -0,0 +1,145 @@
+
+ * @created 2022-08-08 15:07:33
+ * @modified 2022-08-08 15:07:33
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Requests\PageRequest;
+use Beike\Admin\Repositories\PageRepo;
+use Beike\Models\Page;
+use Beike\Shop\Http\Resources\PageDetail;
+use Beike\Shop\Http\Resources\ProductSimple;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Http\Request;
+
+class PagesController extends Controller
+{
+ /**
+ * 显示单页列表
+ *
+ * @return mixed
+ */
+ public function index()
+ {
+ $pageList = PageRepo::getList();
+ $data = [
+ 'pages' => $pageList,
+ 'pages_format' => PageDetail::collection($pageList)->jsonSerialize(),
+ ];
+
+ return view('admin::pages.pages.index', $data);
+ }
+
+ /**
+ * 创建页面
+ *
+ * @return mixed
+ */
+ public function create()
+ {
+ return view('admin::pages.pages.form', ['page' => new Page()]);
+ }
+
+ /**
+ * 保存新建
+ *
+ * @param PageRequest $request
+ * @return RedirectResponse
+ */
+ public function store(PageRequest $request)
+ {
+ try {
+ $requestData = $request->all();
+ $page = PageRepo::createOrUpdate($requestData);
+ hook_action('admin.page.store.after', ['request_data' => $requestData, 'page' => $page]);
+
+ return redirect(admin_route('pages.index'));
+ } catch (\Exception $e) {
+ return redirect(admin_route('pages.index'))->withErrors(['error' => $e->getMessage()]);
+ }
+ }
+
+ /**
+ * @param Request $request
+ * @param Page $page
+ * @return mixed
+ */
+ public function edit(Request $request, Page $page)
+ {
+ $page->load(['products.description', 'category.description']);
+
+ $data = [
+ 'page' => $page,
+ 'products' => ProductSimple::collection($page->products)->jsonSerialize(),
+ 'descriptions' => PageRepo::getDescriptionsByLocale($page->id),
+ ];
+
+ return view('admin::pages.pages.form', $data);
+ }
+
+ /**
+ * 保存更新
+ *
+ * @param PageRequest $request
+ * @param int $pageId
+ * @return RedirectResponse
+ */
+ public function update(PageRequest $request, int $pageId)
+ {
+ try {
+ $requestData = $request->all();
+ $requestData['id'] = $pageId;
+ $page = PageRepo::createOrUpdate($requestData);
+ hook_action('admin.page.update.after', ['request_data' => $requestData, 'page' => $page]);
+
+ return redirect()->to(admin_route('pages.index'));
+ } catch (\Exception $e) {
+ return redirect(admin_route('pages.index'))->withErrors(['error' => $e->getMessage()]);
+ }
+ }
+
+ /**
+ * 删除单页
+ *
+ * @param Request $request
+ * @param int $pageId
+ * @return array
+ */
+ public function destroy(Request $request, int $pageId): array
+ {
+ PageRepo::deleteById($pageId);
+
+ return json_success(trans('common.deleted_success'));
+ }
+
+ /**
+ * 搜索页面标题自动完成
+ * @param Request $request
+ * @return array
+ */
+ public function autocomplete(Request $request): array
+ {
+ $products = PageRepo::autocomplete($request->get('name') ?? '');
+
+ return json_success(trans('common.get_success'), $products);
+ }
+
+ /**
+ * 获取单页名称
+ * @param Page $page
+ * @return array
+ */
+ public function name(Page $page): array
+ {
+ $name = $page->description->title ?? '';
+
+ return json_success(trans('common.get_success'), $name);
+ }
+}
diff --git a/beike/Admin/Http/Controllers/PluginController.php b/beike/Admin/Http/Controllers/PluginController.php
new file mode 100644
index 00000000..6a688225
--- /dev/null
+++ b/beike/Admin/Http/Controllers/PluginController.php
@@ -0,0 +1,137 @@
+
+ * @created 2022-06-29 16:02:15
+ * @modified 2022-06-29 16:02:15
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Resources\PluginResource;
+use Beike\Repositories\PluginRepo;
+use Beike\Repositories\SettingRepo;
+use Exception;
+use Illuminate\Contracts\View\View;
+use Illuminate\Http\Request;
+
+class PluginController extends Controller
+{
+ /**
+ * @throws Exception
+ */
+ public function index()
+ {
+ $plugins = app('plugin')->getPlugins();
+ $data['plugins'] = array_values(PluginResource::collection($plugins)->jsonSerialize());
+ $data = hook_filter('admin.plugin.index.data', $data);
+
+ return view('admin::pages.plugins.index', $data);
+ }
+
+ /**
+ * 上传插件
+ */
+ public function import(Request $request): array
+ {
+ $zipFile = $request->file('file');
+ app('plugin')->import($zipFile);
+
+ return json_success(trans('common.success'));
+ }
+
+ /**
+ * @param Request $request
+ * @param $code
+ * @return array
+ * @throws Exception
+ */
+ public function install(Request $request, $code): array
+ {
+ $plugin = app('plugin')->getPluginOrFail($code);
+ PluginRepo::installPlugin($plugin);
+
+ return json_success(trans('common.success'));
+ }
+
+ /**
+ * @param Request $request
+ * @param $code
+ * @return array
+ * @throws Exception
+ */
+ public function uninstall(Request $request, $code): array
+ {
+ $plugin = app('plugin')->getPluginOrFail($code);
+ PluginRepo::uninstallPlugin($plugin);
+
+ return json_success(trans('common.success'));
+ }
+
+ /**
+ * @param Request $request
+ * @param $code
+ * @return View
+ * @throws Exception
+ */
+ public function edit(Request $request, $code): View
+ {
+ $plugin = app('plugin')->getPluginOrFail($code);
+ $columnView = $plugin->getColumnView();
+ $view = $columnView ?: 'admin::pages.plugins.form';
+
+ $data = [
+ 'view' => $view,
+ 'plugin' => $plugin,
+ ];
+
+ $data = hook_filter('admin.plugin.edit.data', $data);
+
+ return view($view, $data);
+ }
+
+ /**
+ * @param Request $request
+ * @param $code
+ * @return mixed
+ * @throws Exception
+ */
+ public function update(Request $request, $code)
+ {
+ $fields = $request->all();
+ $plugin = app('plugin')->getPluginOrFail($code);
+ if (method_exists($plugin, 'validate')) {
+ $validator = $plugin->validate($fields);
+ if ($validator->fails()) {
+ return back()->withErrors($validator)->withInput();
+ }
+ }
+
+ $data = ['plugin_code' => $code, 'fields' => $fields];
+ hook_action('plugin.update.before', $data);
+
+ SettingRepo::update('plugin', $code, $fields);
+
+ hook_action('plugin.update.after', $data);
+
+ return redirect($this->getRedirect())->with('success', trans('common.updated_success'));
+ }
+
+ /**
+ * @param Request $request
+ * @param $code
+ * @return array
+ * @throws Exception
+ */
+ public function updateStatus(Request $request, $code): array
+ {
+ app('plugin')->getPluginOrFail($code);
+ $status = $request->get('status');
+ SettingRepo::update('plugin', $code, ['status' => $status]);
+
+ return json_success(trans('common.updated_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/ProductController.php b/beike/Admin/Http/Controllers/ProductController.php
new file mode 100644
index 00000000..2435c2ee
--- /dev/null
+++ b/beike/Admin/Http/Controllers/ProductController.php
@@ -0,0 +1,215 @@
+all();
+ $productList = ProductRepo::list($requestData);
+ $products = ProductResource::collection($productList);
+ $productsFormat = $products->jsonSerialize();
+
+ $data = [
+ 'categories' => CategoryRepo::flatten(locale()),
+ 'products_format' => $productsFormat,
+ 'products' => $products,
+ 'type' => 'products',
+ ];
+
+ $data = hook_filter('admin.product.index.data', $data);
+
+ if ($request->expectsJson()) {
+ return $productsFormat;
+ }
+
+ return view('admin::pages.products.index', $data);
+ }
+
+ public function trashed(Request $request)
+ {
+ $requestData = $request->all();
+ $requestData['trashed'] = true;
+ $productList = ProductRepo::list($requestData);
+ $products = ProductResource::collection($productList);
+ $productsFormat = $products->jsonSerialize();
+
+ $data = [
+ 'categories' => CategoryRepo::flatten(locale()),
+ 'products_format' => $productsFormat,
+ 'products' => $products,
+ 'type' => 'trashed',
+ ];
+
+ $data = hook_filter('admin.product.trashed.data', $data);
+
+ if ($request->expectsJson()) {
+ return $products;
+ }
+
+ return view('admin::pages.products.index', $data);
+ }
+
+ public function create(Request $request)
+ {
+ return $this->form($request, new Product());
+ }
+
+ public function store(ProductRequest $request)
+ {
+ try {
+ $requestData = $request->all();
+ $product = (new ProductService)->create($requestData);
+
+ $data = [
+ 'request_data' => $requestData,
+ 'product' => $product,
+ ];
+
+ hook_action('admin.product.store.after', $data);
+
+ return redirect()->to(admin_route('products.index'))
+ ->with('success', trans('common.created_success'));
+ } catch (\Exception $e) {
+ return redirect(admin_route('products.create'))
+ ->withInput()
+ ->withErrors(['error' => $e->getMessage()]);
+ }
+ }
+
+ public function edit(Request $request, Product $product)
+ {
+ return $this->form($request, $product);
+ }
+
+ public function update(ProductRequest $request, Product $product)
+ {
+ try {
+ $requestData = $request->all();
+ $product = (new ProductService)->update($product, $requestData);
+
+ $data = [
+ 'request_data' => $requestData,
+ 'product' => $product,
+ ];
+ hook_action('admin.product.update.after', $data);
+
+ return redirect()->to($this->getRedirect())->with('success', trans('common.updated_success'));
+ } catch (\Exception $e) {
+ return redirect(admin_route('products.edit', $product))->withErrors(['error' => $e->getMessage()]);
+ }
+ }
+
+ public function destroy(Request $request, Product $product)
+ {
+ $product->delete();
+ hook_action('admin.product.destroy.after', $product);
+
+ return json_success(trans('common.deleted_success'));
+ }
+
+ public function restore(Request $request)
+ {
+ $productId = $request->id ?? 0;
+ Product::withTrashed()->find($productId)->restore();
+
+ hook_action('admin.product.restore.after', $productId);
+
+ return ['success' => true];
+ }
+
+ protected function form(Request $request, Product $product)
+ {
+ if ($product->id) {
+ $descriptions = $product->descriptions->keyBy('locale');
+ $categoryIds = $product->categories->pluck('id')->toArray();
+ $product->load('brand', 'attributes');
+ }
+
+ $product = hook_filter('admin.product.form.product', $product);
+ $taxClasses = TaxClassRepo::getList();
+ array_unshift($taxClasses, ['title' => trans('admin/builder.text_no'), 'id' => 0]);
+
+ $data = [
+ 'product' => $product,
+ 'descriptions' => $descriptions ?? [],
+ 'category_ids' => $categoryIds ?? [],
+ 'product_attributes' => ProductAttributeResource::collection($product->attributes),
+ 'relations' => ProductResource::collection($product->relations)->resource,
+ 'languages' => LanguageRepo::all(),
+ 'tax_classes' => $taxClasses,
+ 'source' => [
+ 'categories' => CategoryRepo::flatten(locale()),
+ ],
+ '_redirect' => $this->getRedirect(),
+ ];
+
+ $data = hook_filter('admin.product.form.data', $data);
+
+ return view('admin::pages.products.form.form', $data);
+ }
+
+ public function name(int $id)
+ {
+ $name = ProductRepo::getName($id);
+
+ return json_success(trans('common.get_success'), $name);
+ }
+
+ /**
+ * 根据商品ID批量获取商品名称
+ *
+ * @param Request $request
+ * @return array
+ */
+ public function getNames(Request $request): array
+ {
+ $productIds = explode(',', $request->get('product_ids'));
+ $name = ProductRepo::getNames($productIds);
+
+ return json_success(trans('common.get_success'), $name);
+ }
+
+ public function autocomplete(Request $request)
+ {
+ $products = ProductRepo::autocomplete($request->get('name') ?? '');
+
+ return json_success(trans('common.get_success'), $products);
+ }
+
+ public function updateStatus(Request $request)
+ {
+ ProductRepo::updateStatusByIds($request->get('ids'), $request->get('status'));
+
+ return json_success(trans('common.updated_success'), []);
+ }
+
+ public function destroyByIds(Request $request)
+ {
+ $productIds = $request->get('ids');
+ ProductRepo::DeleteByIds($productIds);
+
+ hook_action('admin.product.destroy_by_ids.after', $productIds);
+
+ return json_success(trans('common.deleted_success'), []);
+ }
+
+ public function trashedClear()
+ {
+ ProductRepo::forceDeleteTrashed();
+ }
+}
diff --git a/beike/Admin/Http/Controllers/RegionController.php b/beike/Admin/Http/Controllers/RegionController.php
new file mode 100644
index 00000000..4d0ee061
--- /dev/null
+++ b/beike/Admin/Http/Controllers/RegionController.php
@@ -0,0 +1,55 @@
+
+ * @created 2022-07-26 20:01:02
+ * @modified 2022-07-26 20:01:02
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Repositories\RegionRepo;
+use Beike\Repositories\CountryRepo;
+use Illuminate\Http\Request;
+
+class RegionController
+{
+ public function index()
+ {
+ $data = [
+ 'regions' => RegionRepo::getList(),
+ 'countries' => CountryRepo::all(),
+ ];
+
+ $data = hook_filter('admin.region.index.data', $data);
+
+ return view('admin::pages.regions.index', $data);
+ }
+
+ public function store(Request $request)
+ {
+ $requestData = json_decode($request->getContent(), true);
+ $region = RegionRepo::createOrUpdate($requestData);
+
+ return json_success(trans('common.created_success'), $region);
+ }
+
+ public function update(Request $request, int $regionId)
+ {
+ $requestData = json_decode($request->getContent(), true);
+ $requestData['id'] = $regionId;
+ $region = RegionRepo::createOrUpdate($requestData);
+
+ return json_success(trans('common.updated_success'), $region);
+ }
+
+ public function destroy(Request $request, int $regionId)
+ {
+ RegionRepo::deleteById($regionId);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/RmaController.php b/beike/Admin/Http/Controllers/RmaController.php
new file mode 100644
index 00000000..4f7bf2a6
--- /dev/null
+++ b/beike/Admin/Http/Controllers/RmaController.php
@@ -0,0 +1,74 @@
+
+ * @created 2022-08-03 21:17:04
+ * @modified 2022-08-03 21:17:04
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Resources\RmaDetail;
+use Beike\Admin\Http\Resources\RmaHistoryDetail;
+use Beike\Repositories\RmaRepo;
+use Illuminate\Http\Request;
+
+class RmaController extends Controller
+{
+ public function index(Request $request)
+ {
+ $rmas = RmaRepo::list($request->only('name', 'email', 'telephone', 'product_name', 'sku', 'type', 'status'));
+ $data = [
+ 'rmas' => $rmas,
+ 'rmas_format' => RmaDetail::collection($rmas)->jsonSerialize(),
+ ];
+
+ $data = hook_filter('admin.rma.index.data', $data);
+
+ return view('admin::pages.rmas.index', $data);
+ }
+
+ /**
+ * @param int $id
+ * @return mixed
+ */
+ public function show(int $id)
+ {
+ $rma = RmaRepo::find($id);
+ $data = [
+ 'rma' => (new RmaDetail($rma))->jsonSerialize(),
+ 'histories' => RmaHistoryDetail::collection($rma->histories)->jsonSerialize(),
+ 'statuses' => RmaRepo::getStatuses(),
+ 'types' => RmaRepo::getTypes(),
+ ];
+
+ $data = hook_filter('admin.rma.show.data', $data);
+
+ return view('admin::pages.rmas.info', $data);
+ }
+
+ public function addHistory(Request $request, int $id)
+ {
+ RmaRepo::addHistory($id, $request->only('status', 'notify', 'comment'));
+ $data = [
+ 'rma' => (new RmaDetail(RmaRepo::find($id)))->jsonSerialize(),
+ 'statuses' => RmaRepo::getStatuses(),
+ ];
+
+ hook_filter('admin.rma.add_history.data', $data);
+
+ return json_success(trans('common.updated_success'), $data);
+ }
+
+ public function destroy(int $id): array
+ {
+ RmaRepo::delete($id);
+
+ hook_action('admin.rma.destroy.after', $id);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/RmaReasonController.php b/beike/Admin/Http/Controllers/RmaReasonController.php
new file mode 100644
index 00000000..45306424
--- /dev/null
+++ b/beike/Admin/Http/Controllers/RmaReasonController.php
@@ -0,0 +1,63 @@
+
+ * @created 2022-08-03 21:17:04
+ * @modified 2022-08-03 21:17:04
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Resources\RmaReasonDetail;
+use Beike\Repositories\LanguageRepo;
+use Beike\Repositories\RmaReasonRepo;
+use Exception;
+use Illuminate\Http\Request;
+
+class RmaReasonController extends Controller
+{
+ public function index(Request $request)
+ {
+ $rmaReasons = RmaReasonRepo::list($request->only('name'));
+
+ $data = [
+ 'languages' => LanguageRepo::all(),
+ 'rmaReasons' => RmaReasonDetail::collection($rmaReasons)->jsonSerialize(),
+ ];
+
+ $data = hook_filter('admin.rma_reason.index.data', $data);
+
+ if ($request->expectsJson()) {
+ return json_success(trans('common.success'), $data);
+ }
+
+ return view('admin::pages.rma_reasons.index', $data);
+ }
+
+ public function store(Request $request): array
+ {
+ $rmaReason = RmaReasonRepo::create($request->only('name'));
+
+ return json_success(trans('common.created_success'), $rmaReason);
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function update(Request $request, int $id): array
+ {
+ $rmaReason = RmaReasonRepo::update($id, $request->only('name'));
+
+ return json_success(trans('common.updated_success'), $rmaReason);
+ }
+
+ public function destroy(int $id): array
+ {
+ RmaReasonRepo::delete($id);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/SettingController.php b/beike/Admin/Http/Controllers/SettingController.php
new file mode 100644
index 00000000..1c2a5ce9
--- /dev/null
+++ b/beike/Admin/Http/Controllers/SettingController.php
@@ -0,0 +1,76 @@
+
+ * @created 2022-06-29 16:02:15
+ * @modified 2022-06-29 16:02:15
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Resources\CustomerGroupDetail;
+use Beike\Repositories\CountryRepo;
+use Beike\Repositories\CurrencyRepo;
+use Beike\Repositories\CustomerGroupRepo;
+use Beike\Repositories\SettingRepo;
+use Beike\Repositories\ThemeRepo;
+use Illuminate\Http\Request;
+
+class SettingController extends Controller
+{
+ /**
+ * 显示系统设置页面
+ *
+ * @return mixed
+ */
+ public function index()
+ {
+ $themes = ThemeRepo::getAllThemes();
+
+ $tax_address = [
+ ['value' => 'shipping', 'label' => trans('admin/setting.shipping_address')],
+ ['value' => 'payment', 'label' => trans('admin/setting.payment_address')],
+ ];
+
+ $data = [
+ 'countries' => CountryRepo::listEnabled(),
+ 'currencies' => CurrencyRepo::listEnabled(),
+ 'tax_address' => $tax_address,
+ 'customer_groups' => CustomerGroupDetail::collection(CustomerGroupRepo::list())->jsonSerialize(),
+ 'themes' => $themes,
+ ];
+
+ $data = hook_filter('admin.setting.index.data', $data);
+
+ return view('admin::pages.setting', $data);
+ }
+
+ /**
+ * 更新系统设置
+ *
+ * @throws \Throwable
+ */
+ public function store(Request $request)
+ {
+ $settings = $request->all();
+ foreach ($settings as $key => $value) {
+ SettingRepo::storeValue($key, $value);
+ }
+
+ $oldAdminName = admin_name();
+ $newAdminName = $settings['admin_name'] ?: 'admin';
+ $settingUrl = str_replace($oldAdminName, $newAdminName, admin_route('settings.index'));
+
+ return redirect($settingUrl)->with('success', trans('common.updated_success'));
+ }
+
+ public function storeDeveloperToken(Request $request)
+ {
+ SettingRepo::storeValue('developer_token', $request->get('developer_token'));
+
+ return json_success(trans('common.updated_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/TaxClassController.php b/beike/Admin/Http/Controllers/TaxClassController.php
new file mode 100644
index 00000000..80c6fadd
--- /dev/null
+++ b/beike/Admin/Http/Controllers/TaxClassController.php
@@ -0,0 +1,56 @@
+
+ * @created 2022-07-26 19:45:41
+ * @modified 2022-07-26 19:45:41
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Repositories\TaxClassRepo;
+use Beike\Models\TaxRate;
+use Illuminate\Http\Request;
+
+class TaxClassController extends Controller
+{
+ public function index()
+ {
+ $data = [
+ 'tax_classes' => TaxClassRepo::getList(),
+ 'all_tax_rates' => TaxRate::all(),
+ 'bases' => TaxClassRepo::BASE_TYPES,
+ ];
+
+ $data = hook_filter('admin.tax_class.index.data', $data);
+
+ return view('admin::pages.tax_classes.index', $data);
+ }
+
+ public function store(Request $request)
+ {
+ $requestData = json_decode($request->getContent(), true);
+ $taxClass = TaxClassRepo::createOrUpdate($requestData);
+
+ return json_success(trans('common.created_success'), $taxClass);
+ }
+
+ public function update(Request $request, int $taxClassId)
+ {
+ $requestData = json_decode($request->getContent(), true);
+ $requestData['id'] = $taxClassId;
+ $taxClass = TaxClassRepo::createOrUpdate($requestData);
+
+ return json_success(trans('common.updated_success'), $taxClass);
+ }
+
+ public function destroy(Request $request, int $taxClassId)
+ {
+ TaxClassRepo::deleteById($taxClassId);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/TaxRateController.php b/beike/Admin/Http/Controllers/TaxRateController.php
new file mode 100644
index 00000000..f3357453
--- /dev/null
+++ b/beike/Admin/Http/Controllers/TaxRateController.php
@@ -0,0 +1,58 @@
+
+ * @created 2022-07-26 20:00:13
+ * @modified 2022-07-26 20:00:13
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Admin\Http\Requests\TaxRateRequest;
+use Beike\Admin\Repositories\TaxRateRepo;
+use Beike\Models\Region;
+use Illuminate\Http\Request;
+
+class TaxRateController
+{
+ public function index()
+ {
+ $data = [
+ 'tax_rates' => TaxRateRepo::getList(),
+ 'regions' => Region::all(),
+ ];
+
+ $data = hook_filter('admin.tax_rate.index.data', $data);
+
+ return view('admin::pages.tax_rates.index', $data);
+ }
+
+ public function store(TaxRateRequest $request)
+ {
+ $requestData = json_decode($request->getContent(), true);
+ $taxRate = TaxRateRepo::createOrUpdate($requestData);
+ $taxRate->load('region');
+
+ return json_success(trans('common.created_success'), $taxRate);
+ }
+
+ public function update(TaxRateRequest $request, int $taxRateId)
+ {
+ $requestData = json_decode($request->getContent(), true);
+ $requestData['id'] = $taxRateId;
+ $taxRate = TaxRateRepo::createOrUpdate($requestData);
+ $taxRate->load('region');
+
+ return json_success(trans('common.updated_success'), $taxRate);
+ }
+
+ public function destroy(Request $request, int $taxRateId)
+ {
+ TaxRateRepo::deleteById($taxRateId);
+
+ return json_success(trans('common.deleted_success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/ThemeController.php b/beike/Admin/Http/Controllers/ThemeController.php
new file mode 100644
index 00000000..b8db8687
--- /dev/null
+++ b/beike/Admin/Http/Controllers/ThemeController.php
@@ -0,0 +1,83 @@
+
+ * @created 2023-03-16 12:00:13
+ * @modified 2023-03-16 12:00:13
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Repositories\PluginRepo;
+use Beike\Repositories\SettingRepo;
+use Database\Seeders\ThemeSeeder;
+use Illuminate\Http\Request;
+
+class ThemeController extends Controller
+{
+ /**
+ * Themes index
+ *
+ * @return mixed
+ * @throws \Exception
+ */
+ public function index()
+ {
+ $currentTheme = system_setting('base.theme');
+ $themes = PluginRepo::getEnabledThemes();
+
+ $data['themes'][] = [
+ 'name' => trans('admin/theme.theme_name'),
+ 'code' => 'default',
+ 'demo' => true,
+ 'image' => image_origin('image/default-theme.jpg'),
+ 'status' => $currentTheme == 'default',
+ ];
+
+ foreach ($themes as $theme) {
+ $themeCode = $theme->code;
+ $plugin = $theme->plugin;
+ $imagePath = $plugin->theme ?? 'image/theme.jpg';
+ $data['themes'][] = [
+ 'name' => $plugin->getLocaleName(),
+ 'code' => $themeCode,
+ 'demo' => true,
+ 'image' => plugin_asset($themeCode, $imagePath),
+ 'status' => $currentTheme == $themeCode,
+ ];
+ }
+
+ return view('admin::pages.theme.index', $data);
+ }
+
+ /**
+ * Enable theme
+ *
+ * @param Request $request
+ * @param $themeCode
+ * @return mixed
+ * @throws \Exception
+ */
+ public function update(Request $request, $themeCode)
+ {
+ $importDemo = $request->get('import_demo');
+ if ($importDemo) {
+ if ($themeCode == 'default') {
+ $seeder = new ThemeSeeder();
+ $seeder->run();
+ } else {
+ $plugin = plugin($themeCode);
+ if ($plugin) {
+ PluginRepo::runSeeder($plugin);
+ }
+ }
+ }
+
+ SettingRepo::update('system', 'base', ['theme' => $themeCode]);
+
+ return json_success(trans('common.success'));
+ }
+}
diff --git a/beike/Admin/Http/Controllers/ZoneController.php b/beike/Admin/Http/Controllers/ZoneController.php
new file mode 100644
index 00000000..2428fa14
--- /dev/null
+++ b/beike/Admin/Http/Controllers/ZoneController.php
@@ -0,0 +1,69 @@
+
+ * @created 2022-06-30 16:17:04
+ * @modified 2022-06-30 16:17:04
+ */
+
+namespace Beike\Admin\Http\Controllers;
+
+use Beike\Repositories\CountryRepo;
+use Beike\Repositories\ZoneRepo;
+use Illuminate\Http\Request;
+
+class ZoneController extends Controller
+{
+ public function index(Request $request)
+ {
+ $zones = ZoneRepo::list($request->only('name', 'code', 'status'));
+
+ $data = [
+ 'zones' => $zones,
+ 'countries' => CountryRepo::all(),
+ ];
+
+ $data = hook_filter('admin.zone.index.data', $data);
+
+ if ($request->expectsJson()) {
+ return json_success(trans('common.success'), $data);
+ }
+
+ return view('admin::pages.zones.index', $data);
+ }
+
+ public function store(Request $request)
+ {
+ $zone = ZoneRepo::create($request->only('country_id', 'name', 'code', 'sort_order', 'status'));
+
+ return json_success(trans('common.created_success'), $zone);
+ }
+
+ public function update(Request $request, int $id)
+ {
+ $zone = ZoneRepo::update($id, $request->only('country_id', 'name', 'code', 'sort_order', 'status'));
+
+ return json_success(trans('common.updated_success'), $zone);
+ }
+
+ public function destroy(int $id)
+ {
+ ZoneRepo::delete($id);
+
+ return json_success(trans('common.deleted_success'));
+ }
+
+ public function listByCountry(Request $request, int $countryId)
+ {
+ ZoneRepo::listByCountry($countryId);
+
+ $data = [
+ 'zones' => ZoneRepo::listByCountry($countryId),
+ ];
+
+ return json_success(trans('common.success'), $data);
+ }
+}
diff --git a/beike/Admin/Http/Requests/AdminRoleRequest.php b/beike/Admin/Http/Requests/AdminRoleRequest.php
new file mode 100644
index 00000000..5489fa47
--- /dev/null
+++ b/beike/Admin/Http/Requests/AdminRoleRequest.php
@@ -0,0 +1,46 @@
+
+ * @created 2022-08-12 17:48:08
+ * @modified 2022-08-12 17:48:08
+ */
+
+namespace Beike\Admin\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class AdminRoleRequest extends FormRequest
+{
+ /**
+ * Determine if the user is authorized to make this request.
+ *
+ * @return bool
+ */
+ public function authorize(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules(): array
+ {
+ return [
+ 'name' => 'required|string|max:10',
+ ];
+ }
+
+ public function attributes()
+ {
+ return [
+ 'name' => trans('role.name'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/AdminUserRequest.php b/beike/Admin/Http/Requests/AdminUserRequest.php
new file mode 100644
index 00000000..e8a7aeb6
--- /dev/null
+++ b/beike/Admin/Http/Requests/AdminUserRequest.php
@@ -0,0 +1,50 @@
+
+ * @created 2022-08-15 18:58:20
+ * @modified 2022-08-15 18:58:20
+ */
+
+namespace Beike\Admin\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class AdminUserRequest extends FormRequest
+{
+ /**
+ * Determine if the user is authorized to make this request.
+ *
+ * @return bool
+ */
+ public function authorize(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules(): array
+ {
+ if (! $this->id) {
+ $rules['email'] = 'required|email:rfc|unique:admin_users,email';
+ } else {
+ $rules['email'] = 'required|email:rfc|unique:admin_users,email,' . $this->id;
+ }
+
+ return $rules;
+ }
+
+ public function attributes()
+ {
+ return [
+ 'email' => trans('user.email'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/CategoryRequest.php b/beike/Admin/Http/Requests/CategoryRequest.php
new file mode 100644
index 00000000..8f427699
--- /dev/null
+++ b/beike/Admin/Http/Requests/CategoryRequest.php
@@ -0,0 +1,37 @@
+ 'required|max:255',
+ ];
+ }
+
+ public function attributes()
+ {
+ return [
+ 'descriptions.*.name' => trans('category.name'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/CurrencyRequest.php b/beike/Admin/Http/Requests/CurrencyRequest.php
new file mode 100644
index 00000000..1511bcdd
--- /dev/null
+++ b/beike/Admin/Http/Requests/CurrencyRequest.php
@@ -0,0 +1,47 @@
+ 'required',
+ 'code' => 'required|max:16',
+ 'symbol_left' => 'max:16',
+ 'symbol_right' => 'max:16',
+ 'value' => 'required',
+ 'decimal_place' => 'required|max:1',
+ ];
+ }
+
+ public function attributes()
+ {
+ return [
+ 'name' => trans('currency.name'),
+ 'code' => trans('currency.code'),
+ 'symbol_left' => trans('currency.symbol_left'),
+ 'symbol_right' => trans('currency.symbol_right'),
+ 'value' => trans('currency.value'),
+ 'decimal_place' => trans('currency.decimal_place'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/CustomerGroupRequest.php b/beike/Admin/Http/Requests/CustomerGroupRequest.php
new file mode 100644
index 00000000..b79322b5
--- /dev/null
+++ b/beike/Admin/Http/Requests/CustomerGroupRequest.php
@@ -0,0 +1,48 @@
+
+ * @created 2022-07-01 14:17:04
+ * @modified 2022-07-01 14:17:04
+ */
+
+namespace Beike\Admin\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class CustomerGroupRequest extends FormRequest
+{
+ /**
+ * Determine if the user is authorized to make this request.
+ *
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+ 'name.*' => 'required|max:64',
+ 'level' => 'required|max:16',
+ ];
+ }
+
+ public function attributes()
+ {
+ return [
+ 'descriptions.*.name' => trans('customer_group.name'),
+ 'level' => trans('customer_group.level'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/CustomerRequest.php b/beike/Admin/Http/Requests/CustomerRequest.php
new file mode 100644
index 00000000..51bc8da5
--- /dev/null
+++ b/beike/Admin/Http/Requests/CustomerRequest.php
@@ -0,0 +1,58 @@
+
+ * @created 2022-07-01 15:17:04
+ * @modified 2022-07-01 15:17:04
+ */
+
+namespace Beike\Admin\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class CustomerRequest extends FormRequest
+{
+ /**
+ * Determine if the user is authorized to make this request.
+ *
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules()
+ {
+ $rules = [
+ 'name' => 'required|max:64',
+ 'email' => 'required|email:rfc|unique:customers',
+ 'customer_group_id' => 'required|exists:customer_groups,id',
+ ];
+ if (! $this->id) {
+ $rules['password'] = 'required|max:64';
+ } else {
+ $rules['email'] = 'required|email:rfc|unique:customers,email,' . $this->id;
+ }
+
+ return $rules;
+ }
+
+ public function attributes()
+ {
+ return [
+ 'name' => trans('customer.name'),
+ 'email' => trans('customer.email'),
+ 'password' => trans('customer.password'),
+ 'customer_group_id' => trans('customer.customer_group_id'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/ForgottenRequest.php b/beike/Admin/Http/Requests/ForgottenRequest.php
new file mode 100644
index 00000000..9f74aafc
--- /dev/null
+++ b/beike/Admin/Http/Requests/ForgottenRequest.php
@@ -0,0 +1,37 @@
+ 'required|confirmed',
+ ];
+ }
+
+ public function attributes()
+ {
+ return [
+ 'password' => trans('customer.password'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/LoginRequest.php b/beike/Admin/Http/Requests/LoginRequest.php
new file mode 100644
index 00000000..93febce2
--- /dev/null
+++ b/beike/Admin/Http/Requests/LoginRequest.php
@@ -0,0 +1,31 @@
+
+ */
+ public function rules(): array
+ {
+ return [
+ 'email' => ['required', 'email'],
+ 'password' => 'required',
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/PageCategoryRequest.php b/beike/Admin/Http/Requests/PageCategoryRequest.php
new file mode 100644
index 00000000..be7676f3
--- /dev/null
+++ b/beike/Admin/Http/Requests/PageCategoryRequest.php
@@ -0,0 +1,49 @@
+
+ * @created 2023-02-10 16:04:14
+ * @modified 2023-02-10 16:04:14
+ */
+
+namespace Beike\Admin\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class PageCategoryRequest extends FormRequest
+{
+ /**
+ * Determine if the user is authorized to make this request.
+ *
+ * @return bool
+ */
+ public function authorize(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules(): array
+ {
+ return [
+ 'descriptions.*.locale' => 'required|string',
+ 'descriptions.*.title' => 'required|string|min:3|max:32',
+ 'descriptions.*.summary' => 'string',
+ ];
+ }
+
+ public function attributes()
+ {
+ return [
+ 'title' => trans('page_category.title'),
+ 'summary' => trans('page_category.summary'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/PageRequest.php b/beike/Admin/Http/Requests/PageRequest.php
new file mode 100644
index 00000000..95234e77
--- /dev/null
+++ b/beike/Admin/Http/Requests/PageRequest.php
@@ -0,0 +1,51 @@
+
+ * @created 2022-08-15 18:58:20
+ * @modified 2022-08-15 18:58:20
+ */
+
+namespace Beike\Admin\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class PageRequest extends FormRequest
+{
+ /**
+ * Determine if the user is authorized to make this request.
+ *
+ * @return bool
+ */
+ public function authorize(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules(): array
+ {
+ $rules = [
+ 'descriptions.*.title' => 'required|string|min:3|max:128',
+ 'descriptions.*.content' => 'required|string',
+ 'descriptions.*.locale' => 'required|string',
+ ];
+
+ return $rules;
+ }
+
+ public function attributes()
+ {
+ return [
+ 'title' => trans('page.title'),
+ 'content' => trans('page.content'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/ProductRequest.php b/beike/Admin/Http/Requests/ProductRequest.php
new file mode 100644
index 00000000..eb036a6b
--- /dev/null
+++ b/beike/Admin/Http/Requests/ProductRequest.php
@@ -0,0 +1,56 @@
+
+ * @created 2022-08-19 21:58:20
+ * @modified 2022-08-19 21:58:20
+ */
+
+namespace Beike\Admin\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class ProductRequest extends FormRequest
+{
+ /**
+ * Determine if the user is authorized to make this request.
+ *
+ * @return bool
+ */
+ public function authorize(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules(): array
+ {
+ return [
+ 'descriptions.*.name' => 'required|string|min:3|max:128',
+ 'brand_id' => 'int',
+ 'skus.*.sku' => 'required|string',
+ 'skus.*.price' => 'required|numeric',
+ 'skus.*.origin_price' => 'required|numeric',
+ 'skus.*.cost_price' => 'numeric',
+ ];
+ }
+
+ public function attributes()
+ {
+ return [
+ 'descriptions.*.name' => trans('product.name'),
+ 'brand_id' => trans('product.brand'),
+ 'skus.*.sku' => trans('product.sku'),
+ 'skus.*.price' => trans('product.price'),
+ 'skus.*.origin_price' => trans('product.origin_price'),
+ 'skus.*.cost_price' => trans('product.cost_price'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/RmaRequest.php b/beike/Admin/Http/Requests/RmaRequest.php
new file mode 100644
index 00000000..671eab1f
--- /dev/null
+++ b/beike/Admin/Http/Requests/RmaRequest.php
@@ -0,0 +1,60 @@
+
+ * @created 2022-08-03 11:17:04
+ * @modified 2022-08-03 11:17:04
+ */
+
+namespace Beike\Admin\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class RmaRequest extends FormRequest
+{
+ /**
+ * Determine if the user is authorized to make this request.
+ *
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules()
+ {
+ $rules = [
+ 'order_id' => 'required|exists:orders,id',
+ 'order_product_id' => 'required|exists:order_products,id',
+ 'customer_id' => 'required|exists:customers,id',
+ 'quantity' => 'required',
+ 'opened' => 'required',
+ 'rma_reason_id' => 'required|exists:rma_reasons,id',
+ 'type' => 'required',
+ ];
+
+ return $rules;
+ }
+
+ public function attributes()
+ {
+ return [
+ 'order_id' => trans('rma.order_id'),
+ 'order_product_id' => trans('rma.order_product_id'),
+ 'customer_id' => trans('rma.customer_id'),
+ 'quantity' => trans('rma.quantity'),
+ 'opened' => trans('rma.opened'),
+ 'rma_reason_id' => trans('rma.rma_reason_id'),
+ 'type' => trans('rma.type'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/TaxRateRequest.php b/beike/Admin/Http/Requests/TaxRateRequest.php
new file mode 100644
index 00000000..2d6915b2
--- /dev/null
+++ b/beike/Admin/Http/Requests/TaxRateRequest.php
@@ -0,0 +1,57 @@
+
+ * @created 2022-08-12 17:48:08
+ * @modified 2022-08-12 17:48:08
+ */
+
+namespace Beike\Admin\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class TaxRateRequest extends FormRequest
+{
+ /**
+ * Determine if the user is authorized to make this request.
+ *
+ * @return bool
+ */
+ public function authorize(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules(): array
+ {
+ $rule = [
+ 'name' => 'required|string|max:10',
+ 'rate' => 'required|numeric',
+ 'type' => 'required|in:percent,flat',
+ 'region_id' => 'required|int',
+ ];
+
+ if ($this->type == 'percent') {
+ $rule['rate'] = 'required|numeric|gt:0|lt:100';
+ }
+
+ return $rule;
+ }
+
+ public function attributes()
+ {
+ return [
+ 'name' => trans('validation.attributes.tax_rate.name'),
+ 'rate' => trans('validation.attributes.tax_rate.rate'),
+ 'type' => trans('validation.attributes.tax_rate.type'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/UploadRequest.php b/beike/Admin/Http/Requests/UploadRequest.php
new file mode 100644
index 00000000..46c98c17
--- /dev/null
+++ b/beike/Admin/Http/Requests/UploadRequest.php
@@ -0,0 +1,39 @@
+
+ * @created 2022-07-22 14:51:27
+ * @modified 2022-07-22 14:51:27
+ */
+
+namespace Beike\Admin\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class UploadRequest extends FormRequest
+{
+ /**
+ * Determine if the user is authorized to make this request.
+ *
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+ 'file' => 'required|image|mimes:jpg,png,jpeg,gif,svg|max:2048',
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Requests/VerifyCodeRequest.php b/beike/Admin/Http/Requests/VerifyCodeRequest.php
new file mode 100644
index 00000000..f39d3c46
--- /dev/null
+++ b/beike/Admin/Http/Requests/VerifyCodeRequest.php
@@ -0,0 +1,37 @@
+ 'required|email:rfc|exists:admin_users,email',
+ ];
+ }
+
+ public function attributes()
+ {
+ return [
+ 'email' => trans('shop/login.email_address'),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Resources/AddressResource.php b/beike/Admin/Http/Resources/AddressResource.php
new file mode 100644
index 00000000..feaa85ab
--- /dev/null
+++ b/beike/Admin/Http/Resources/AddressResource.php
@@ -0,0 +1,32 @@
+ $this->name,
+ 'phone' => $this->phone,
+ 'country_id' => $this->country_id,
+ 'country' => $this->country->name,
+ 'zone_id' => $this->zone_id,
+ 'zone' => $this->zone,
+ 'city' => $this->city,
+ 'zipcode' => $this->zipcode,
+ 'address_1' => $this->address_1,
+ 'address_2' => $this->address_2,
+ ];
+
+ return $data;
+ }
+}
diff --git a/beike/Admin/Http/Resources/AdminUserDetail.php b/beike/Admin/Http/Resources/AdminUserDetail.php
new file mode 100644
index 00000000..a7311719
--- /dev/null
+++ b/beike/Admin/Http/Resources/AdminUserDetail.php
@@ -0,0 +1,31 @@
+
+ * @created 2022-08-12 15:56:28
+ * @modified 2022-08-12 15:56:28
+ */
+
+namespace Beike\Admin\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class AdminUserDetail extends JsonResource
+{
+ public function toArray($request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'email' => $this->email,
+ 'locale' => $this->locale,
+ 'roles' => $this->roles,
+ 'roles_name' => $this->roles->pluck('name')->toArray(),
+ 'created_at' => time_format($this->created_at),
+ 'updated_at' => time_format($this->updated_at),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Resources/AttributeDetailResource.php b/beike/Admin/Http/Resources/AttributeDetailResource.php
new file mode 100644
index 00000000..76ceaea4
--- /dev/null
+++ b/beike/Admin/Http/Resources/AttributeDetailResource.php
@@ -0,0 +1,30 @@
+ $this->id,
+ 'attribute_group_id' => $this->attribute_group_id,
+ 'name' => $this->description->name ?? '',
+ 'sort_order' => $this->sort_order,
+ 'attribute_group_name' => $this->attributeGroup->description->name ?? '',
+ 'created_at' => time_format($this->created_at),
+ 'values' => AttributeValueResource::collection($this->values),
+ 'descriptions' => $this->descriptions,
+ ];
+
+ return $data;
+ }
+}
diff --git a/beike/Admin/Http/Resources/AttributeResource.php b/beike/Admin/Http/Resources/AttributeResource.php
new file mode 100644
index 00000000..13e22bb3
--- /dev/null
+++ b/beike/Admin/Http/Resources/AttributeResource.php
@@ -0,0 +1,27 @@
+ $this->id,
+ 'name' => $this->description->name ?? '',
+ 'sort_order' => $this->sort_order,
+ 'attribute_group_name' => $this->attributeGroup->description->name ?? '',
+ 'created_at' => time_format($this->created_at),
+ ];
+
+ return $data;
+ }
+}
diff --git a/beike/Admin/Http/Resources/AttributeValueResource.php b/beike/Admin/Http/Resources/AttributeValueResource.php
new file mode 100644
index 00000000..e2b9b4a2
--- /dev/null
+++ b/beike/Admin/Http/Resources/AttributeValueResource.php
@@ -0,0 +1,28 @@
+ $this->id,
+ 'attribute_id' => $this->attribute_id,
+ 'name' => $this->description->name,
+ 'description' => $this->description,
+ 'descriptions' => $this->descriptions,
+ 'created_at' => time_format($this->created_at),
+ ];
+
+ return $data;
+ }
+}
diff --git a/beike/Admin/Http/Resources/AutocompleteResource.php b/beike/Admin/Http/Resources/AutocompleteResource.php
new file mode 100644
index 00000000..52e37da1
--- /dev/null
+++ b/beike/Admin/Http/Resources/AutocompleteResource.php
@@ -0,0 +1,24 @@
+ $this->id,
+ 'name' => $this->description->name ?? '',
+ ];
+
+ return $data;
+ }
+}
diff --git a/beike/Admin/Http/Resources/CategoryResource.php b/beike/Admin/Http/Resources/CategoryResource.php
new file mode 100644
index 00000000..85020f3d
--- /dev/null
+++ b/beike/Admin/Http/Resources/CategoryResource.php
@@ -0,0 +1,29 @@
+ $this->id,
+ 'name' => $this->description->name ?? '',
+ 'parent_id' => $this->parent_id,
+ 'position' => $this->position,
+ 'active' => $this->active,
+ 'url_edit' => admin_route('categories.edit', $this),
+ 'children' => self::collection($this->children),
+ ];
+
+ return $data;
+ }
+}
diff --git a/beike/Admin/Http/Resources/CustomerGroupDetail.php b/beike/Admin/Http/Resources/CustomerGroupDetail.php
new file mode 100644
index 00000000..fbb7aac3
--- /dev/null
+++ b/beike/Admin/Http/Resources/CustomerGroupDetail.php
@@ -0,0 +1,31 @@
+ $this->id,
+ 'total' => $this->total,
+ 'reward_point_factor' => $this->reward_point_factor,
+ 'use_point_factor' => $this->use_point_factor,
+ 'discount_factor' => $this->discount_factor,
+ 'level' => $this->level,
+ 'name' => $this->description->name ?? '',
+ 'description' => $this->description->description ?? '',
+ ];
+
+ return $data;
+ }
+}
diff --git a/beike/Admin/Http/Resources/CustomerResource.php b/beike/Admin/Http/Resources/CustomerResource.php
new file mode 100644
index 00000000..08777bc4
--- /dev/null
+++ b/beike/Admin/Http/Resources/CustomerResource.php
@@ -0,0 +1,39 @@
+ $this->id,
+ 'name' => $this->name,
+ 'email' => $this->email,
+ 'status' => $this->status,
+ 'created_at' => time_format($this->created_at),
+ 'avatar' => image_resize($this->avatar),
+ 'from' => $this->from,
+ 'customer_group_name' => $this->customerGroup->description->name ?? '',
+ 'edit' => admin_route('customers.edit', $this->id),
+ 'delete' => admin_route('customers.destroy', $this->id),
+ ];
+
+ $params = [
+ 'object' => $this,
+ 'data' => $data,
+ ];
+
+ return hook_filter('resource.customer', $params)['data'];
+ }
+}
diff --git a/beike/Admin/Http/Resources/OrderSimple.php b/beike/Admin/Http/Resources/OrderSimple.php
new file mode 100644
index 00000000..057978fb
--- /dev/null
+++ b/beike/Admin/Http/Resources/OrderSimple.php
@@ -0,0 +1,33 @@
+
+ * @created 2022-08-24 10:51:53
+ * @modified 2022-08-24 10:51:53
+ */
+
+namespace Beike\Admin\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class OrderSimple extends JsonResource
+{
+ public function toArray($request): array
+ {
+ $data = [
+ 'id' => $this->id,
+ 'number' => $this->number,
+ 'customer_name' => $this->customer_name,
+ 'email' => $this->email,
+ 'telephone' => $this->telephone,
+ 'created_at' => $this->created_at,
+ 'updated_at' => $this->updated_at,
+ 'status_format' => $this->status_format,
+ ];
+
+ return $data;
+ }
+}
diff --git a/beike/Admin/Http/Resources/PageCategoryResource.php b/beike/Admin/Http/Resources/PageCategoryResource.php
new file mode 100644
index 00000000..693f0027
--- /dev/null
+++ b/beike/Admin/Http/Resources/PageCategoryResource.php
@@ -0,0 +1,36 @@
+
+ * @created 2023-02-10 09:20:33
+ * @modified 2023-02-10 09:20:33
+ */
+
+namespace Beike\Admin\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class PageCategoryResource extends JsonResource
+{
+ public function toArray($request): array
+ {
+ $description = $this->description;
+
+ return [
+ 'id' => $this->id,
+ 'active' => $this->active,
+ 'title' => $description->title,
+ 'title_format' => sub_string($description->title, 64),
+ 'summary' => $description->summary,
+ 'summary_format' => sub_string($description->summary, 128),
+ 'meta_title' => $description->meta_title,
+ 'meta_description' => $description->meta_description,
+ 'meta_keywords' => $description->meta_keywords,
+ 'created_at' => time_format($this->created_at),
+ 'updated_at' => time_format($this->updated_at),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Resources/PluginResource.php b/beike/Admin/Http/Resources/PluginResource.php
new file mode 100644
index 00000000..aa22d381
--- /dev/null
+++ b/beike/Admin/Http/Resources/PluginResource.php
@@ -0,0 +1,35 @@
+ $this->code,
+ 'name' => $this->getLocaleName(),
+ 'description' => $this->getLocaleDescription(),
+ 'path' => $this->path,
+ 'version' => $this->version,
+ 'dir_name' => $this->dirName,
+ 'type' => $this->type,
+ 'type_format' => trans('admin/plugin.' . $this->type),
+ 'icon' => plugin_resize($this->code, $this->icon),
+ 'author' => $this->author,
+ 'status' => $this->getStatus(),
+ 'installed' => $this->getInstalled(),
+ 'edit_url' => $this->getEditUrl(),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Resources/ProductAttributeResource.php b/beike/Admin/Http/Resources/ProductAttributeResource.php
new file mode 100644
index 00000000..00b4cc9a
--- /dev/null
+++ b/beike/Admin/Http/Resources/ProductAttributeResource.php
@@ -0,0 +1,33 @@
+load('attribute', 'attributeValue');
+ $data = [
+ 'attribute' => [
+ 'id' => $this->attribute_id,
+ 'name' => $this->attribute->description->name,
+ ],
+ 'attribute_value' => [
+ 'id' => $this->attribute_value_id,
+ 'name' => $this->attributeValue->description->name,
+ ],
+ ];
+
+ return $data;
+ }
+}
diff --git a/beike/Admin/Http/Resources/ProductResource.php b/beike/Admin/Http/Resources/ProductResource.php
new file mode 100644
index 00000000..b75a0cab
--- /dev/null
+++ b/beike/Admin/Http/Resources/ProductResource.php
@@ -0,0 +1,38 @@
+masterSku;
+
+ $data = [
+ 'id' => $this->id,
+ 'images' => array_map(function ($image) {
+ return image_resize($image);
+ }, $this->images ?? []),
+ 'name' => $this->description->name ?? '',
+ 'price_formatted' => currency_format($masterSku->price),
+ 'active' => $this->active,
+ 'position' => $this->position,
+ 'url' => $this->url,
+ 'created_at' => time_format($this->created_at),
+ 'deleted_at' => $this->deleted_at ? time_format($this->deleted_at) : '',
+ 'url_edit' => admin_route('products.edit', $this->id),
+ ];
+
+ return hook_filter('resource.product', $data);
+ }
+}
diff --git a/beike/Admin/Http/Resources/RmaDetail.php b/beike/Admin/Http/Resources/RmaDetail.php
new file mode 100644
index 00000000..92b2d00d
--- /dev/null
+++ b/beike/Admin/Http/Resources/RmaDetail.php
@@ -0,0 +1,42 @@
+
+ * @created 2022-08-31 11:56:28
+ * @modified 2022-08-31 11:56:28
+ */
+
+namespace Beike\Admin\Http\Resources;
+
+use Beike\Repositories\RmaRepo;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class RmaDetail extends JsonResource
+{
+ public function toArray($request): array
+ {
+ $types = RmaRepo::getTypes();
+ $statuses = RmaRepo::getStatuses();
+
+ return [
+ 'id' => $this->id,
+ 'order_product_id' => $this->order_product_id,
+ 'quantity' => $this->quantity,
+ 'opened' => $this->opened,
+ 'type' => $types[$this->type],
+ 'comment' => $this->comment,
+ 'status' => $statuses[$this->status],
+ 'created_at' => time_format($this->created_at),
+ 'email' => $this->email,
+ 'telephone' => $this->telephone,
+ 'product_name' => $this->product_name,
+ 'name' => $this->name,
+ 'sku' => $this->sku,
+ 'reason' => $this->reason ? (json_decode($this->reason->name, true)[locale()] ?? '') : '',
+ 'type_text' => $this->type_text,
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Resources/RmaHistoryDetail.php b/beike/Admin/Http/Resources/RmaHistoryDetail.php
new file mode 100644
index 00000000..3d3f5482
--- /dev/null
+++ b/beike/Admin/Http/Resources/RmaHistoryDetail.php
@@ -0,0 +1,32 @@
+
+ * @created 2022-09-30 11:56:28
+ * @modified 2022-09-30 11:56:28
+ */
+
+namespace Beike\Admin\Http\Resources;
+
+use Beike\Repositories\RmaRepo;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class RmaHistoryDetail extends JsonResource
+{
+ public function toArray($request): array
+ {
+ $statuses = RmaRepo::getStatuses();
+
+ return [
+ 'id' => $this->id,
+ 'rma_id' => $this->rma_id,
+ 'status' => $statuses[$this->status],
+ 'created_at' => time_format($this->created_at),
+ 'notify' => $this->notify,
+ 'comment' => $this->comment,
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Resources/RmaReasonDetail.php b/beike/Admin/Http/Resources/RmaReasonDetail.php
new file mode 100644
index 00000000..5216383c
--- /dev/null
+++ b/beike/Admin/Http/Resources/RmaReasonDetail.php
@@ -0,0 +1,26 @@
+
+ * @created 2022-08-31 11:56:28
+ * @modified 2022-08-31 11:56:28
+ */
+
+namespace Beike\Admin\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class RmaReasonDetail extends JsonResource
+{
+ public function toArray($request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'name' => json_decode($this->name, true)[locale()] ?? '',
+ 'names' => json_decode($this->name, true),
+ ];
+ }
+}
diff --git a/beike/Admin/Http/Resources/TaxClassDetail.php b/beike/Admin/Http/Resources/TaxClassDetail.php
new file mode 100644
index 00000000..e928a248
--- /dev/null
+++ b/beike/Admin/Http/Resources/TaxClassDetail.php
@@ -0,0 +1,30 @@
+
+ * @created 2022-08-12 15:56:28
+ * @modified 2022-08-12 15:56:28
+ */
+
+namespace Beike\Admin\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class TaxClassDetail extends JsonResource
+{
+ public function toArray($request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'title' => $this->title,
+ 'description' => sub_string($this->description),
+ 'created_at' => time_format($this->created_at),
+ 'updated_at' => time_format($this->updated_at),
+ 'tax_rates' => $this->taxRates->toArray(),
+ 'tax_rules' => $this->taxRules->toArray(),
+ ];
+ }
+}
diff --git a/beike/Admin/Providers/AdminServiceProvider.php b/beike/Admin/Providers/AdminServiceProvider.php
new file mode 100644
index 00000000..7c8f1610
--- /dev/null
+++ b/beike/Admin/Providers/AdminServiceProvider.php
@@ -0,0 +1,197 @@
+
+ * @created 2022-08-08 08:08:08
+ * @modified 2022-08-08 08:08:08
+ */
+
+namespace Beike\Admin\Providers;
+
+use Beike\Admin\View\Components\Alert;
+use Beike\Admin\View\Components\Filter;
+use Beike\Admin\View\Components\Form\Image;
+use Beike\Admin\View\Components\Form\Input;
+use Beike\Admin\View\Components\Form\InputLocale;
+use Beike\Admin\View\Components\Form\RichText;
+use Beike\Admin\View\Components\Form\Select;
+use Beike\Admin\View\Components\Form\SwitchRadio;
+use Beike\Admin\View\Components\Form\Textarea;
+use Beike\Admin\View\Components\Header;
+use Beike\Admin\View\Components\NoData;
+use Beike\Admin\View\Components\Sidebar;
+use Beike\Console\Commands\ChangeRootPassword;
+use Beike\Console\Commands\FetchCurrencyRate;
+use Beike\Console\Commands\GenerateDatabaseDict;
+use Beike\Console\Commands\GenerateSitemap;
+use Beike\Console\Commands\MakeRootAdminUser;
+use Beike\Console\Commands\MigrateFromOpenCart;
+use Beike\Models\AdminUser;
+use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\View;
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Support\Str;
+use Illuminate\View\FileViewFinder;
+
+class AdminServiceProvider extends ServiceProvider
+{
+ /**
+ * @throws \Exception
+ */
+ public function boot()
+ {
+ $uri = request()->getRequestUri();
+ if (is_installer()) {
+ return;
+ }
+
+ $this->loadCommands();
+ $this->publishResources();
+
+ load_settings();
+ $this->loadRoutesFrom(__DIR__ . '/../Routes/admin.php');
+
+ $adminName = admin_name();
+ if (! Str::startsWith($uri, "/{$adminName}")) {
+ return;
+ }
+
+ $this->mergeConfigFrom(__DIR__ . '/../../Config/beike.php', 'beike');
+ $this->loadViewsFrom(resource_path('/beike/admin/views'), 'admin');
+ $this->loadThemeViewPath();
+
+ $this->app->booted(function () {
+ $this->loadShareViewData();
+ });
+
+ $this->loadAdminViewComponents();
+
+ $this->registerGuard();
+
+ Config::set('filesystems.disks.catalog', [
+ 'driver' => 'local',
+ 'root' => public_path('catalog'),
+ ]);
+
+ $this->loadDesignComponents();
+ }
+
+ /**
+ * 加载后台命令行脚本
+ */
+ protected function loadCommands()
+ {
+ if ($this->app->runningInConsole()) {
+ $this->commands([
+ ChangeRootPassword::class,
+ FetchCurrencyRate::class,
+ GenerateDatabaseDict::class,
+ GenerateSitemap::class,
+ MakeRootAdminUser::class,
+ MigrateFromOpenCart::class,
+ ]);
+ }
+ }
+
+ /**
+ * 注册后台用户 guard
+ */
+ protected function registerGuard()
+ {
+ Config::set('auth.guards.' . AdminUser::AUTH_GUARD, [
+ 'driver' => 'session',
+ 'provider' => 'admin_users',
+ ]);
+
+ Config::set('auth.providers.admin_users', [
+ 'driver' => 'eloquent',
+ 'model' => AdminUser::class,
+ ]);
+ }
+
+ /**
+ * 加载主题模板, 用于装修预览
+ */
+ protected function loadThemeViewPath()
+ {
+ $this->app->singleton('view.finder', function ($app) {
+ $paths = $app['config']['view.paths'];
+ if ($theme = system_setting('base.theme')) {
+ $customTheme[] = base_path("themes/{$theme}");
+ $paths = array_merge($customTheme, $paths);
+ }
+
+ return new FileViewFinder($app['files'], $paths);
+ });
+ }
+
+ /**
+ * 后台UI组件
+ */
+ protected function loadAdminViewComponents()
+ {
+ $this->loadViewComponentsAs('admin', [
+ 'header' => Header::class,
+ 'sidebar' => Sidebar::class,
+ 'filter' => Filter::class,
+ 'alert' => Alert::class,
+ 'form-input-locale' => InputLocale::class,
+ 'form-switch' => SwitchRadio::class,
+ 'form-input' => Input::class,
+ 'form-select' => Select::class,
+ 'form-image' => Image::class,
+ 'form-textarea' => Textarea::class,
+ 'form-rich-text' => RichText::class,
+ 'no-data' => NoData::class,
+ ]);
+ }
+
+ /**
+ * seeder 数据
+ */
+ protected function publishResources()
+ {
+ $this->publishes([
+ __DIR__ . '/../Database/Seeders/ProductSeeder.php' => database_path('seeders/ProductSeeder.php'),
+ ], 'beike-seeders');
+ }
+
+ /**
+ * 加载首页 page builder 相关组件
+ *
+ * @throws \Exception
+ */
+ protected function loadDesignComponents()
+ {
+ $viewPath = base_path() . '/beike/Admin/View';
+ $builderPath = $viewPath . '/DesignBuilders/';
+
+ $builders = glob($builderPath . '*');
+ foreach ($builders as $builder) {
+ $builderName = basename($builder, '.php');
+ $aliasName = Str::snake($builderName);
+ $componentName = Str::studly($builderName);
+ $classBaseName = "\\Beike\\Admin\\View\\DesignBuilders\\{$componentName}";
+
+ if (! class_exists($classBaseName)) {
+ throw new \Exception("请先定义自定义模板类 {$classBaseName}");
+ }
+ $this->loadViewComponentsAs('editor', [
+ $aliasName => $classBaseName,
+ ]);
+ }
+ }
+
+ /**
+ * 后台公共数据
+ */
+ protected function loadShareViewData()
+ {
+ View::share('languages', languages());
+ View::share('admin_base_url', admin_route('home.index'));
+ View::share('shop_base_url', shop_route('home.index'));
+ }
+}
diff --git a/beike/Admin/Repositories/AdminRoleRepo.php b/beike/Admin/Repositories/AdminRoleRepo.php
new file mode 100644
index 00000000..a8bfcaa2
--- /dev/null
+++ b/beike/Admin/Repositories/AdminRoleRepo.php
@@ -0,0 +1,91 @@
+
+ * @created 2022-08-01 21:12:11
+ * @modified 2022-08-01 21:12:11
+ */
+
+namespace Beike\Admin\Repositories;
+
+use Spatie\Permission\Models\Permission;
+use Spatie\Permission\Models\Role;
+
+class AdminRoleRepo
+{
+ /**
+ * 创建新角色
+ *
+ * @param $data
+ * @return Role
+ * @throws \Exception
+ */
+ public static function createAdminRole($data): Role
+ {
+ $adminRole = Role::findOrCreate($data['name'], 'web_admin');
+ $permissions = array_merge($data['core_permissions'], $data['plugin_permissions']);
+ self::syncPermissions($adminRole, $permissions);
+
+ return $adminRole;
+ }
+
+ /**
+ * 编辑新角色
+ *
+ * @param $data
+ * @return Role
+ * @throws \Exception
+ */
+ public static function updateAdminRole($data): Role
+ {
+ $adminRole = Role::findById($data['id']);
+ $adminRole->update([
+ 'name' => $data['name'],
+ 'guard_name' => 'web_admin',
+ ]);
+
+ $permissions = array_merge($data['core_permissions'], $data['plugin_permissions']);
+ self::syncPermissions($adminRole, $permissions);
+
+ return $adminRole;
+ }
+
+ /**
+ * 同步所有权限
+ *
+ * @param Role $adminRole
+ * @param $permissions
+ * @throws \Exception
+ */
+ private static function syncPermissions(Role $adminRole, $permissions)
+ {
+ $items = [];
+ foreach ($permissions as $groupedPermissions) {
+ foreach ($groupedPermissions['permissions'] as $groupedPermission) {
+ if ($groupedPermission['selected']) {
+ $code = $groupedPermission['code'];
+ Permission::findOrCreate($code);
+ $items[] = $code;
+ }
+ }
+ }
+ if (empty($items)) {
+ throw new \Exception(trans('admin/role.select_one_at_least'));
+ }
+ $adminRole->syncPermissions($items);
+ }
+
+ /**
+ * 删除角色
+ *
+ * @param $adminRoleId
+ */
+ public static function deleteAdminRole($adminRoleId)
+ {
+ $adminRole = Role::query()->find($adminRoleId);
+ $adminRole->delete();
+ }
+}
diff --git a/beike/Admin/Repositories/AdminUserRepo.php b/beike/Admin/Repositories/AdminUserRepo.php
new file mode 100644
index 00000000..22fcf442
--- /dev/null
+++ b/beike/Admin/Repositories/AdminUserRepo.php
@@ -0,0 +1,95 @@
+
+ * @created 2022-08-01 20:30:44
+ * @modified 2022-08-01 20:30:44
+ */
+
+namespace Beike\Admin\Repositories;
+
+use Beike\Admin\Http\Resources\AdminUserDetail;
+use Beike\Models\AdminUser;
+use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
+
+class AdminUserRepo
+{
+ /**
+ * 获取后台用户管理员列表
+ */
+ public static function getAdminUsers(): array
+ {
+ $builder = AdminUser::query()->with(['roles']);
+ $adminUsers = $builder->get();
+
+ return AdminUserDetail::collection($adminUsers)->jsonSerialize();
+ }
+
+ /**
+ * 创建后台管理员用户
+ *
+ * @param $data
+ * @return AdminUser
+ */
+ public static function createAdminUser($data): AdminUser
+ {
+ $adminUser = new AdminUser([
+ 'name' => $data['name'],
+ 'email' => $data['email'],
+ 'password' => bcrypt($data['password']),
+ 'locale' => $data['locale'],
+ 'active' => true,
+ ]);
+ $adminUser->save();
+
+ if (isset($data['roles'])) {
+ $adminUser->assignRole($data['roles']);
+ }
+
+ return $adminUser;
+ }
+
+ /**
+ * 更新后台管理员用户
+ *
+ * @param $adminUserId
+ * @param $data
+ * @return mixed
+ */
+ public static function updateAdminUser($adminUserId, $data)
+ {
+ $password = $data['password'] ?? '';
+ $adminUser = AdminUser::query()->findOrFail($adminUserId);
+ $userData = [
+ 'name' => $data['name'],
+ 'email' => $data['email'],
+ 'locale' => $data['locale'],
+ 'active' => true,
+ ];
+ if ($password) {
+ $userData['password'] = bcrypt($password);
+ }
+ $adminUser->update($userData);
+ $adminUser->syncRoles($data['roles']);
+
+ return $adminUser;
+ }
+
+ /**
+ * 删除后台用户
+ *
+ * @param $adminUserId
+ * @throws \Exception
+ */
+ public static function deleteAdminUser($adminUserId)
+ {
+ if ($adminUserId == 1) {
+ throw new NotAcceptableHttpException(trans('admin/customer.cannot_delete_root'));
+ }
+ $adminUser = AdminUser::query()->find($adminUserId);
+ $adminUser->delete();
+ }
+}
diff --git a/beike/Admin/Repositories/AttributeGroupRepo.php b/beike/Admin/Repositories/AttributeGroupRepo.php
new file mode 100644
index 00000000..e66aeba3
--- /dev/null
+++ b/beike/Admin/Repositories/AttributeGroupRepo.php
@@ -0,0 +1,78 @@
+
+ * @created 2023-01-04 19:45:41
+ * @modified 2023-01-04 19:45:41
+ */
+
+namespace Beike\Admin\Repositories;
+
+use Beike\Models\AttributeGroup;
+
+class AttributeGroupRepo
+{
+ public static function getList()
+ {
+ return AttributeGroup::query()->orderByDesc('id')->with('description', 'descriptions')->get();
+ }
+
+ public static function create($data)
+ {
+ $attributeGroup = AttributeGroup::query()->create([
+ 'sort_order' => $data['sort_order'],
+ ]);
+
+ $descriptions = [];
+ foreach ($data['name'] as $locale => $name) {
+ $descriptions[] = [
+ 'locale' => $locale,
+ 'name' => $name,
+ ];
+ }
+ $attributeGroup->descriptions()->createMany($descriptions);
+
+ $attributeGroup->load('description', 'descriptions');
+
+ return $attributeGroup;
+ }
+
+ public static function update($id, $data)
+ {
+ $attributeGroup = AttributeGroup::query()->updateOrCreate(['id' => $id], [
+ 'sort_order' => $data['sort_order'],
+ ]);
+
+ $descriptions = [];
+ foreach ($data['name'] as $locale => $name) {
+ $descriptions[] = [
+ 'locale' => $locale,
+ 'name' => $name,
+ ];
+ }
+ $attributeGroup->descriptions()->delete();
+ $attributeGroup->descriptions()->createMany($descriptions);
+
+ $attributeGroup->load('description', 'descriptions');
+
+ return $attributeGroup;
+ }
+
+ public static function find($id)
+ {
+ return AttributeGroup::query()->find($id);
+ }
+
+ public static function delete($id)
+ {
+ $group = AttributeGroup::query()->findOrFail($id);
+ if ($group->attributes->count()) {
+ throw new \Exception(trans('admin/attribute_group.error_cannot_delete_attribute_used', ['attributes' => implode(', ', $group->attributes->pluck('id')->toArray())]));
+ }
+ $group->descriptions()->delete();
+ $group->delete();
+ }
+}
diff --git a/beike/Admin/Repositories/AttributeRepo.php b/beike/Admin/Repositories/AttributeRepo.php
new file mode 100644
index 00000000..76515a52
--- /dev/null
+++ b/beike/Admin/Repositories/AttributeRepo.php
@@ -0,0 +1,141 @@
+
+ * @created 2023-01-04 19:45:41
+ * @modified 2023-01-04 19:45:41
+ */
+
+namespace Beike\Admin\Repositories;
+
+use Beike\Models\Attribute;
+use Beike\Models\AttributeValue;
+use Beike\Models\ProductAttribute;
+
+class AttributeRepo
+{
+ public static function getList()
+ {
+ return Attribute::query()->orderByDesc('id')->paginate()->withQueryString();
+ }
+
+ public static function create($data)
+ {
+ $attribute = Attribute::query()->create([
+ 'attribute_group_id' => $data['attribute_group_id'],
+ 'sort_order' => $data['sort_order'],
+ ]);
+
+ $descriptions = [];
+ foreach ($data['name'] as $locale => $name) {
+ $descriptions[] = [
+ 'locale' => $locale,
+ 'name' => $name,
+ ];
+ }
+ $attribute->descriptions()->createMany($descriptions);
+
+ return $attribute;
+ }
+
+ public static function update($id, $data)
+ {
+ $attribute = Attribute::query()->updateOrCreate(['id' => $id], [
+ 'attribute_group_id' => $data['attribute_group_id'],
+ 'sort_order' => $data['sort_order'],
+ ]);
+
+ $descriptions = [];
+ foreach ($data['name'] as $locale => $name) {
+ $descriptions[] = [
+ 'locale' => $locale,
+ 'name' => $name,
+ ];
+ }
+ $attribute->descriptions()->delete();
+ $attribute->descriptions()->createMany($descriptions);
+
+ return $attribute;
+ }
+
+ public static function createValue($data)
+ {
+ $attributeValue = AttributeValue::query()->create([
+ 'attribute_id' => $data['attribute_id'],
+ ]);
+
+ $descriptions = [];
+ foreach ($data['name'] as $locale => $name) {
+ $descriptions[] = [
+ 'locale' => $locale,
+ 'name' => $name,
+ ];
+ }
+ $attributeValue->descriptions()->createMany($descriptions);
+
+ return $attributeValue;
+ }
+
+ public static function updateValue($id, $data)
+ {
+ $attributeValue = AttributeValue::query()->findOrFail($id);
+
+ $descriptions = [];
+ foreach ($data['name'] as $locale => $name) {
+ $descriptions[] = [
+ 'locale' => $locale,
+ 'name' => $name,
+ ];
+ }
+ $attributeValue->descriptions()->delete();
+ $attributeValue->descriptions()->createMany($descriptions);
+
+ return $attributeValue;
+ }
+
+ public static function deleteValue($id)
+ {
+ AttributeValue::query()->findOrFail($id)->delete();
+ }
+
+ public static function find($id)
+ {
+ return Attribute::query()->with('values.descriptions')->find($id);
+ }
+
+ public static function delete($id)
+ {
+ $productIds = ProductAttribute::query()->where('attribute_id', $id)->pluck('product_id')->toArray();
+ if ($productIds) {
+ throw new \Exception(trans('admin/attribute.error_cannot_delete_product_used', ['product_ids' => implode(', ', $productIds)]));
+ }
+ $attribute = Attribute::query()->findOrFail($id);
+ $attribute->descriptions()->delete();
+ $attribute->values()->delete();
+ $attribute->delete();
+ }
+
+ public static function autocomplete($name)
+ {
+ $builder = Attribute::query()->with('description')
+ ->whereHas('description', function ($query) use ($name) {
+ $query->where('name', 'like', "{$name}%");
+ });
+
+ return $builder->limit(10)->get();
+ }
+
+ public static function autocompleteValue($attributeId, $name)
+ {
+ $builder = AttributeValue::query()->with('description')
+ ->where('attribute_id', $attributeId)
+ ->whereHas('description', function ($query) use ($name) {
+ $query->where('name', 'like', "{$name}%");
+ });
+
+ return $builder->limit(10)->get();
+ }
+}
diff --git a/beike/Admin/Repositories/DashboardRepo.php b/beike/Admin/Repositories/DashboardRepo.php
new file mode 100644
index 00000000..a963e354
--- /dev/null
+++ b/beike/Admin/Repositories/DashboardRepo.php
@@ -0,0 +1,120 @@
+
+ * @created 2022-08-03 18:16:53
+ * @modified 2022-08-03 18:16:53
+ */
+
+namespace Beike\Admin\Repositories;
+
+use Beike\Models\Product;
+use Beike\Repositories\CustomerRepo;
+use Beike\Repositories\OrderRepo;
+
+class DashboardRepo
+{
+ /**
+ * 获取商品总数
+ *
+ * @return array
+ */
+ public static function getProductData(): array
+ {
+ return [
+ 'total' => quantity_format(Product::query()->count()),
+ 'percentage' => 0,
+ ];
+ }
+
+ /**
+ * 获取客户访问统计今日昨日比较
+ * @return array
+ * @todo
+ */
+ public static function getCustomerViewData(): array
+ {
+ $today = 10;
+ $yesterday = 8;
+ $difference = $today - $yesterday;
+ if ($difference && $yesterday) {
+ $percentage = round(($difference / $yesterday) * 100);
+ } else {
+ $percentage = 0;
+ }
+
+ return [
+ 'total' => $today,
+ 'percentage' => $percentage,
+ ];
+ }
+
+ /**
+ * 获取订单基础统计, 总数和今日昨日比较
+ *
+ * @return array
+ */
+ public static function getOrderData(): array
+ {
+ $today = OrderRepo::getListBuilder(['start' => today()->subDay(), 'end' => today()])->count();
+ $yesterday = OrderRepo::getListBuilder(['start' => today()->subDays(2), 'end' => today()->subDay()])->count();
+ $difference = $today - $yesterday;
+ if ($difference && $yesterday) {
+ $percentage = round(($difference / $yesterday) * 100);
+ } else {
+ $percentage = 0;
+ }
+
+ return [
+ 'total' => $today,
+ 'percentage' => $percentage,
+ ];
+ }
+
+ /**
+ * 获取客户注册今日昨日比较
+ *
+ * @return array
+ */
+ public static function getCustomerData(): array
+ {
+ $today = CustomerRepo::getListBuilder(['start' => today()->subDay(), 'end' => today()])->count();
+ $yesterday = CustomerRepo::getListBuilder(['start' => today()->subDays(2), 'end' => today()->subDay()])->count();
+ $difference = $today - $yesterday;
+ if ($difference && $yesterday) {
+ $percentage = round(($difference / $yesterday) * 100);
+ } else {
+ $percentage = 0;
+ }
+
+ return [
+ 'total' => $today,
+ 'percentage' => $percentage,
+ ];
+ }
+
+ /**
+ * 获取订单总额基础统计, 总数和今日昨日比较
+ *
+ * @return array
+ */
+ public static function getTotalData(): array
+ {
+ $today = OrderRepo::getListBuilder(['start' => today()->subDay(), 'end' => today()])->sum('total');
+ $yesterday = OrderRepo::getListBuilder(['start' => today()->subDays(2), 'end' => today()->subDay()])->sum('total');
+ $difference = $today - $yesterday;
+ if ($difference && $yesterday) {
+ $percentage = round(($difference / $yesterday) * 100);
+ } else {
+ $percentage = 0;
+ }
+
+ return [
+ 'total' => currency_format($today),
+ 'percentage' => $percentage,
+ ];
+ }
+}
diff --git a/beike/Admin/Repositories/PageRepo.php b/beike/Admin/Repositories/PageRepo.php
new file mode 100644
index 00000000..a6c1fb98
--- /dev/null
+++ b/beike/Admin/Repositories/PageRepo.php
@@ -0,0 +1,132 @@
+
+ * @created 2022-07-26 21:08:07
+ * @modified 2022-07-26 21:08:07
+ */
+
+namespace Beike\Admin\Repositories;
+
+use Beike\Models\Page;
+use Illuminate\Contracts\Pagination\LengthAwarePaginator;
+use Illuminate\Support\Facades\DB;
+
+class PageRepo
+{
+ /**
+ * 获取列表页数据
+ *
+ * @return LengthAwarePaginator
+ */
+ public static function getList(): LengthAwarePaginator
+ {
+ $builder = Page::query()->with([
+ 'description',
+ ])->orderByDesc('updated_at');
+
+ return $builder->paginate(perPage());
+ }
+
+ public static function findByPageId($pageId)
+ {
+ $page = Page::query()->findOrFail($pageId);
+ $page->load(['descriptions']);
+
+ return $page;
+ }
+
+ public static function getDescriptionsByLocale($pageId)
+ {
+ $page = self::findByPageId($pageId);
+
+ return $page->descriptions->keyBy('locale')->toArray();
+ }
+
+ public static function createOrUpdate($data)
+ {
+ try {
+ DB::beginTransaction();
+ $region = self::pushPage($data);
+ DB::commit();
+
+ return $region;
+ } catch (\Exception $e) {
+ DB::rollBack();
+
+ throw $e;
+ }
+ }
+
+ public static function pushPage($data)
+ {
+ $id = $data['id'] ?? 0;
+ if ($id) {
+ $page = Page::query()->findOrFail($id);
+ } else {
+ $page = new Page();
+ }
+ $page->fill([
+ 'page_category_id' => (int) ($data['page_category_id'] ?? 0),
+ 'position' => (int) ($data['position'] ?? 0),
+ 'active' => (bool) ($data['active'] ?? true),
+ 'author' => $data['author'] ?? '',
+ 'views' => (int) ($data['views'] ?? 0),
+ ]);
+
+ $page->saveOrFail();
+
+ $page->descriptions()->delete();
+ $page->descriptions()->createMany($data['descriptions']);
+
+ $products = $data['products'] ?? [];
+ if ($products) {
+ $items = [];
+ foreach ($products as $item) {
+ $items[] = [
+ 'product_id' => $item,
+ ];
+ }
+ $page->pageProducts()->delete();
+ $page->pageProducts()->createMany($items);
+ }
+
+ $page->load(['descriptions', 'pageProducts']);
+
+ return $page;
+ }
+
+ public static function deleteById($id)
+ {
+ $page = Page::query()->findOrFail($id);
+ $page->descriptions()->delete();
+ $page->delete();
+ }
+
+ /**
+ * 页面内容自动完成
+ *
+ * @param $name
+ * @return array
+ */
+ public static function autocomplete($name): array
+ {
+ $pages = Page::query()->with('description')
+ ->whereHas('description', function ($query) use ($name) {
+ $query->where('title', 'like', "{$name}%");
+ })->limit(10)->get();
+ $results = [];
+ foreach ($pages as $page) {
+ $results[] = [
+ 'id' => $page->id,
+ 'name' => $page->description->title,
+ 'status' => $page->active,
+ ];
+ }
+
+ return $results;
+ }
+}
diff --git a/beike/Admin/Repositories/PermissionRepo.php b/beike/Admin/Repositories/PermissionRepo.php
new file mode 100644
index 00000000..96103f58
--- /dev/null
+++ b/beike/Admin/Repositories/PermissionRepo.php
@@ -0,0 +1,485 @@
+
+ * @created 2022-08-01 20:49:45
+ * @modified 2022-08-01 20:49:45
+ */
+
+namespace Beike\Admin\Repositories;
+
+use Beike\Models\AdminUser;
+use Spatie\Permission\Exceptions\PermissionDoesNotExist;
+use Spatie\Permission\Models\Role;
+
+class PermissionRepo
+{
+ private ?AdminUser $adminUser = null;
+
+ private ?Role $adminRole = null;
+
+ public function setUser($user): self
+ {
+ $this->adminUser = $user;
+
+ return $this;
+ }
+
+ public function setRole($role): self
+ {
+ $this->adminRole = $role;
+
+ return $this;
+ }
+
+ /**
+ * 所有权限列表
+ *
+ * @return array
+ * @throws \Exception
+ */
+ public function getRoleCorePermissions(): array
+ {
+ $corePermissions = [
+ ['title' => trans('admin/common.order'), 'permissions' => $this->getOrderPermissions()],
+ ['title' => trans('admin/common.rma'), 'permissions' => $this->getRmaPermissions()],
+ ['title' => trans('admin/common.rma_reason'), 'permissions' => $this->getRmaReasonPermissions()],
+ ['title' => trans('admin/common.product'), 'permissions' => $this->getProductPermissions()],
+ ['title' => trans('admin/common.category'), 'permissions' => $this->getCategoryPermissions()],
+ ['title' => trans('admin/common.brand'), 'permissions' => $this->getBrandPermissions()],
+ ['title' => trans('admin/common.attribute'), 'permissions' => $this->getAttributePermissions()],
+ ['title' => trans('admin/common.attribute_group'), 'permissions' => $this->getAttributeGroupPermissions()],
+ ['title' => trans('admin/common.customer'), 'permissions' => $this->getCustomerPermissions()],
+ ['title' => trans('admin/common.customer_group'), 'permissions' => $this->getCustomerGroupPermissions()],
+ ['title' => trans('admin/common.page'), 'permissions' => $this->getPagePermissions()],
+ ['title' => trans('admin/common.page_category'), 'permissions' => $this->getPageCategoryPermissions()],
+ ['title' => trans('admin/common.setting'), 'permissions' => $this->getSettingPermissions()],
+
+ ['title' => trans('admin/common.plugin'), 'permissions' => $this->getPluginPermissions()],
+ ['title' => trans('admin/common.marketing'), 'permissions' => $this->getMarketingPermissions()],
+ ['title' => trans('admin/common.admin_user'), 'permissions' => $this->getAdminUserPermissions()],
+ ['title' => trans('admin/common.admin_role'), 'permissions' => $this->getAdminRolePermissions()],
+ ['title' => trans('admin/common.region'), 'permissions' => $this->getRegionPermissions()],
+ ['title' => trans('admin/common.tax_rate'), 'permissions' => $this->getTaxRatePermissions()],
+ ['title' => trans('admin/common.tax_class'), 'permissions' => $this->getTaxClassPermissions()],
+ ['title' => trans('admin/common.currency'), 'permissions' => $this->getCurrencyPermissions()],
+ ['title' => trans('admin/common.language'), 'permissions' => $this->getLanguagePermissions()],
+ ['title' => trans('admin/common.file_manager'), 'permissions' => $this->getFileManagerPermissions()],
+ ['title' => trans('admin/common.zone'), 'permissions' => $this->getZonePermissions()],
+ ['title' => trans('admin/common.country'), 'permissions' => $this->getCountryPermissions()],
+ ];
+
+ $corePermissions = hook_filter('role.permissions.all', $corePermissions);
+
+ return $corePermissions;
+ }
+
+ /**
+ * 插件权限
+ *
+ * @return array
+ * @throws \Exception
+ */
+ public function getRolePluginPermissions(): array
+ {
+ $pluginPermissions = hook_filter('role.permissions.plugin', []);
+
+ $pluginPermissions = $this->handlePluginPermission($pluginPermissions);
+
+ return $pluginPermissions;
+ }
+
+ /**
+ * 订单权限列表
+ *
+ * @return array
+ */
+ private function getOrderPermissions(): array
+ {
+ $routes = ['orders_index', 'orders_export', 'orders_show', 'orders_update_status'];
+ $items = $this->getPermissionList('order', $routes);
+
+ return hook_filter('role.order_permissions', $items);
+ }
+
+ /**
+ * 售后(退换货)权限列表
+ *
+ * @return array
+ */
+ private function getRmaPermissions(): array
+ {
+ $routes = ['rmas_index', 'rmas_show', 'rmas_update', 'rmas_delete'];
+ $items = $this->getPermissionList('rma', $routes);
+
+ return hook_filter('role.rma_permissions', $items);
+ }
+
+ /**
+ * 售后(退换货)原因权限列表
+ *
+ * @return array
+ */
+ private function getRmaReasonPermissions(): array
+ {
+ $routes = ['rma_reasons_index', 'rma_reasons_create', 'rma_reasons_update', 'rma_reasons_delete'];
+ $items = $this->getPermissionList('rma_reason', $routes);
+
+ return hook_filter('role.rma_reason_permissions', $items);
+ }
+
+ /**
+ * 商品权限列表
+ *
+ * @return array
+ */
+ private function getProductPermissions(): array
+ {
+ $routes = ['products_index', 'products_create', 'products_show', 'products_update', 'products_delete', 'products_trashed', 'products_restore'];
+ $items = $this->getPermissionList('product', $routes);
+
+ return hook_filter('role.product_permissions', $items);
+ }
+
+ /**
+ * 分类权限列表
+ *
+ * @return array
+ */
+ private function getCategoryPermissions(): array
+ {
+ $routes = ['categories_index', 'categories_create', 'categories_show', 'categories_update', 'categories_delete'];
+ $items = $this->getPermissionList('category', $routes);
+
+ return hook_filter('role.category_permissions', $items);
+ }
+
+ /**
+ * 品牌权限列表
+ *
+ * @return array
+ */
+ private function getBrandPermissions(): array
+ {
+ $routes = ['brands_index', 'brands_create', 'brands_show', 'brands_update', 'brands_delete'];
+ $items = $this->getPermissionList('brand', $routes);
+
+ return hook_filter('role.brand_permissions', $items);
+ }
+
+ /**
+ * 属性权限列表
+ *
+ * @return array
+ */
+ private function getAttributePermissions(): array
+ {
+ $routes = ['attributes_index', 'attributes_create', 'attributes_show', 'attributes_update', 'attributes_delete'];
+ $items = $this->getPermissionList('attribute', $routes);
+
+ return hook_filter('role.attribute_permissions', $items);
+ }
+
+ /**
+ * 属性组权限列表
+ *
+ * @return array
+ */
+ private function getAttributeGroupPermissions(): array
+ {
+ $routes = ['attribute_groups_index', 'attribute_groups_create', 'attribute_groups_update', 'attribute_groups_delete'];
+ $items = $this->getPermissionList('attribute_group', $routes);
+
+ return hook_filter('role.attribute_group_permissions', $items);
+ }
+
+ /**
+ * 客户权限列表
+ *
+ * @return array
+ */
+ private function getCustomerPermissions(): array
+ {
+ $routes = ['customers_index', 'customers_create', 'customers_show', 'customers_update', 'customers_delete'];
+ $items = $this->getPermissionList('customer', $routes);
+
+ return hook_filter('role.customer_permissions', $items);
+ }
+
+ /**
+ * 客户组权限列表
+ *
+ * @return array
+ */
+ private function getCustomerGroupPermissions(): array
+ {
+ $routes = ['customer_groups_index', 'customer_groups_create', 'customer_groups_show', 'customer_groups_update', 'customer_groups_delete'];
+ $items = $this->getPermissionList('customer_group', $routes);
+
+ return hook_filter('role.customer_group_permissions', $items);
+ }
+
+ /**
+ * 设置权限列表
+ *
+ * @return array
+ */
+ private function getSettingPermissions(): array
+ {
+ $routes = ['settings_index', 'settings_update', 'design_index', 'design_footer_index', 'design_menu_index'];
+ $items = $this->getPermissionList('setting', $routes);
+
+ return hook_filter('role.setting_permissions', $items);
+ }
+
+ /**
+ * 文章管理列表
+ * @return array
+ */
+ private function getPagePermissions(): array
+ {
+ $routes = ['pages_index', 'pages_create', 'pages_show', 'pages_update', 'pages_delete'];
+ $items = $this->getPermissionList('page', $routes);
+
+ return hook_filter('role.page_permissions', $items);
+ }
+
+ /**
+ * 文章分类管理列表
+ * @return array
+ */
+ private function getPageCategoryPermissions(): array
+ {
+ $routes = ['page_categories_index', 'page_categories_create', 'page_categories_show', 'page_categories_update', 'page_categories_delete'];
+ $items = $this->getPermissionList('page_category', $routes);
+
+ return hook_filter('role.page_category_permissions', $items);
+ }
+
+ /**
+ * 插件权限列表
+ *
+ * @return array
+ */
+ private function getPluginPermissions(): array
+ {
+ $routes = ['plugins_index', 'plugins_import', 'plugins_update', 'plugins_show', 'plugins_install', 'plugins_update_status', 'plugins_uninstall'];
+ $items = $this->getPermissionList('plugin', $routes);
+
+ return hook_filter('role.plugin_permissions', $items);
+ }
+
+ /**
+ * 插件权限列表
+ *
+ * @return array
+ */
+ private function getMarketingPermissions(): array
+ {
+ $routes = ['marketing_index', 'marketing_show', 'marketing_buy', 'marketing_download'];
+ $items = $this->getPermissionList('marketing', $routes);
+
+ return hook_filter('role.marketing_permissions', $items);
+ }
+
+ /**
+ * 后台管理员权限列表
+ *
+ * @return array
+ */
+ private function getAdminUserPermissions(): array
+ {
+ $routes = ['admin_users_index', 'admin_users_create', 'admin_users_show', 'admin_users_update', 'admin_users_delete'];
+ $items = $this->getPermissionList('user', $routes);
+
+ return hook_filter('role.user_permissions', $items);
+ }
+
+ /**
+ * 后台管理员权限列表
+ *
+ * @return array
+ */
+ private function getAdminRolePermissions(): array
+ {
+ $routes = ['admin_roles_index', 'admin_roles_create', 'admin_roles_show', 'admin_roles_update', 'admin_roles_delete'];
+ $items = $this->getPermissionList('role', $routes);
+
+ return hook_filter('role.role_permissions', $items);
+ }
+
+ /**
+ * 区域分组权限列表
+ *
+ * @return array
+ */
+ private function getRegionPermissions(): array
+ {
+ $routes = ['regions_index', 'regions_create', 'regions_show', 'regions_update', 'regions_delete'];
+ $items = $this->getPermissionList('region', $routes);
+
+ return hook_filter('role.region_permissions', $items);
+ }
+
+ /**
+ * 获取税率权限列表
+ *
+ * @return array[]
+ */
+ private function getTaxRatePermissions(): array
+ {
+ $routes = ['tax_rates_index', 'tax_rates_create', 'tax_rates_show', 'tax_rates_update', 'tax_rates_delete'];
+ $items = $this->getPermissionList('tax_rate', $routes);
+
+ return hook_filter('role.tax_rate_permissions', $items);
+ }
+
+ /**
+ * 获取税类权限列表
+ *
+ * @return array[]
+ */
+ private function getTaxClassPermissions(): array
+ {
+ $routes = ['tax_classes_index', 'tax_classes_create', 'tax_classes_show', 'tax_classes_update', 'tax_classes_delete'];
+ $items = $this->getPermissionList('tax_class', $routes);
+
+ return hook_filter('role.tax_class_permissions', $items);
+ }
+
+ /**
+ * 获取汇率权限列表
+ *
+ * @return array[]
+ */
+ private function getCurrencyPermissions(): array
+ {
+ $routes = ['currencies_index', 'currencies_create', 'currencies_show', 'currencies_update', 'currencies_delete'];
+ $items = $this->getPermissionList('currency', $routes);
+
+ return hook_filter('role.currency_permissions', $items);
+ }
+
+ /**
+ * 获取语言权限列表
+ *
+ * @return array[]
+ */
+ private function getLanguagePermissions(): array
+ {
+ $routes = ['languages_index', 'languages_create', 'languages_update', 'languages_delete'];
+ $items = $this->getPermissionList('language', $routes);
+
+ return hook_filter('role.language_permissions', $items);
+ }
+
+ /**
+ * 获取文件管理器权限列表
+ *
+ * @return array[]
+ */
+ private function getFileManagerPermissions(): array
+ {
+ $routes = ['file_manager_create', 'file_manager_show', 'file_manager_update', 'file_manager_delete'];
+ $items = $this->getPermissionList('file_manager', $routes);
+
+ return hook_filter('role.file_manager_permissions', $items);
+ }
+
+ /**
+ * 获取省份权限列表
+ *
+ * @return array[]
+ */
+ private function getZonePermissions(): array
+ {
+ $routes = ['zones_create', 'zones_index', 'zones_update', 'zones_delete'];
+ $items = $this->getPermissionList('zone', $routes);
+
+ return hook_filter('role.zone_permissions', $items);
+ }
+
+ /**
+ * 获取国家权限列表
+ *
+ * @return array[]
+ */
+ private function getCountryPermissions(): array
+ {
+ $routes = ['countries_create', 'countries_index', 'countries_update', 'countries_delete'];
+ $items = $this->getPermissionList('country', $routes);
+
+ return hook_filter('role.country_permissions', $items);
+ }
+
+ /**
+ * 处理第三方插件权限
+ *
+ * @param $pluginPermissions
+ * @return array
+ * @throws \Exception
+ */
+ private function handlePluginPermission($pluginPermissions): array
+ {
+ if (empty($pluginPermissions)) {
+ return [];
+ }
+
+ foreach ($pluginPermissions as $index => $pluginPermission) {
+ $itemPermissions = $pluginPermission['permissions'] ?? [];
+ if (empty($itemPermissions)) {
+ throw new \Exception('Empty plugin permission!');
+ }
+ foreach ($itemPermissions as $ipIndex => $itemPermission) {
+ $code = $itemPermission['code'] ?? '';
+ if (empty($code)) {
+ throw new \Exception('Empty plugin permission code!');
+ }
+ $pluginPermissions[$index]['permissions'][$ipIndex]['selected'] = $this->hasPermission($code);
+ }
+ }
+
+ return $pluginPermissions;
+ }
+
+ /**
+ * 根据模块和路由返回权限列表
+ *
+ * @param $module
+ * @param $routes
+ * @return array
+ */
+ private function getPermissionList($module, $routes): array
+ {
+ $items = [];
+ foreach ($routes as $route) {
+ $items[] = ['code' => $route, 'name' => trans("admin/{$module}.{$route}"), 'selected' => $this->hasPermission($route)];
+ }
+
+ return $items;
+ }
+
+ /**
+ * 判断当前用户或者角色是否有权限
+ *
+ * @param $permission
+ * @return bool
+ */
+ private function hasPermission($permission): bool
+ {
+ try {
+ if ($this->adminRole) {
+ return $this->adminRole->hasPermissionTo($permission);
+ } elseif ($this->adminUser) {
+ return $this->adminUser->can($permission);
+ }
+ } catch (PermissionDoesNotExist $exception) {
+ return false;
+ }
+
+ return false;
+ }
+}
diff --git a/beike/Admin/Repositories/RegionRepo.php b/beike/Admin/Repositories/RegionRepo.php
new file mode 100644
index 00000000..c5b13a1b
--- /dev/null
+++ b/beike/Admin/Repositories/RegionRepo.php
@@ -0,0 +1,77 @@
+
+ * @created 2022-07-27 10:48:25
+ * @modified 2022-07-27 10:48:25
+ */
+
+namespace Beike\Admin\Repositories;
+
+use Beike\Models\Region;
+use Illuminate\Support\Facades\DB;
+
+class RegionRepo
+{
+ public static function getList()
+ {
+ return Region::query()->with('regionZones.zone')->get();
+ }
+
+ public static function createOrUpdate($data)
+ {
+ try {
+ DB::beginTransaction();
+ $region = self::pushRegion($data);
+ DB::commit();
+
+ return $region;
+ } catch (\Exception $e) {
+ DB::rollBack();
+
+ throw $e;
+ }
+ }
+
+ public static function pushRegion($data)
+ {
+ $id = $data['id'] ?? 0;
+ if ($id) {
+ $region = Region::query()->findOrFail($id);
+ } else {
+ $region = new Region();
+ }
+ $region->fill([
+ 'name' => $data['name'],
+ 'description' => $data['description'],
+ ]);
+ $region->saveOrFail();
+
+ $newRegionZones = [];
+ foreach ($data['region_zones'] as $regionZone) {
+ if ($regionZone['country_id']) {
+ $newRegionZones[] = [
+ 'country_id' => (int) $regionZone['country_id'],
+ 'zone_id' => (int) $regionZone['zone_id'],
+ ];
+ }
+ }
+ if ($newRegionZones) {
+ $region->regionZones()->delete();
+ $region->regionZones()->createMany($newRegionZones);
+ }
+ $region->load(['regionZones']);
+
+ return $region;
+ }
+
+ public static function deleteById($id)
+ {
+ $region = Region::query()->findOrFail($id);
+ $region->regionZones()->delete();
+ $region->delete();
+ }
+}
diff --git a/beike/Admin/Repositories/Report/OrderReportRepo.php b/beike/Admin/Repositories/Report/OrderReportRepo.php
new file mode 100644
index 00000000..0d2d50e3
--- /dev/null
+++ b/beike/Admin/Repositories/Report/OrderReportRepo.php
@@ -0,0 +1,143 @@
+
+ * @created 2022-08-05 14:33:49
+ * @modified 2022-08-05 14:33:49
+ */
+
+namespace Beike\Admin\Repositories\Report;
+
+use Beike\Repositories\OrderRepo;
+use Carbon\Carbon;
+use Carbon\CarbonPeriod;
+use Illuminate\Support\Facades\DB;
+
+class OrderReportRepo
+{
+ /**
+ * 获取最近一个月每日销售订单数
+ * @return mixed
+ */
+ public static function getLatestMonth()
+ {
+ $orderTotals = OrderRepo::getListBuilder(['start' => today()->subMonth(), 'end' => today()])
+ ->select(DB::raw('DATE(created_at) as date, count(*) as total'))
+ ->groupBy('date')
+ ->get()
+ ->keyBy('date');
+
+ $orderAmounts = OrderRepo::getListBuilder(['start' => today()->subMonth(), 'end' => today()])
+ ->select(DB::raw('DATE(created_at) as date, sum(total) as amount'))
+ ->groupBy('date')
+ ->get()
+ ->keyBy('date');
+
+ $dates = $totals = $amounts = [];
+ $period = CarbonPeriod::create(today()->subMonth(), today()->subDay())->toArray();
+ foreach ($period as $date) {
+ $dateFormat = $date->format('Y-m-d');
+ $orderTotal = $orderTotals[$dateFormat] ?? null;
+ $orderAmount = $orderAmounts[$dateFormat] ?? null;
+
+ $dates[] = $dateFormat;
+ $totals[] = $orderTotal ? $orderTotal->total : 0;
+ $amounts[] = $orderAmount ? $orderAmount->amount : 0;
+ }
+
+ $data = [
+ 'period' => $dates,
+ 'totals' => $totals,
+ 'amounts' => $amounts,
+ ];
+
+ return hook_filter('dashboard.order_report_month', $data);
+ }
+
+ /**
+ * 获取最近一周每日销售订单数
+ */
+ public static function getLatestWeek()
+ {
+ $orderTotals = OrderRepo::getListBuilder(['start' => today()->subWeek(), 'end' => today()])
+ ->select(DB::raw('DATE(created_at) as date, count(*) as total'))
+ ->groupBy('date')
+ ->get()
+ ->keyBy('date');
+
+ $orderAmounts = OrderRepo::getListBuilder(['start' => today()->subWeek(), 'end' => today()])
+ ->select(DB::raw('DATE(created_at) as date, sum(total) as amount'))
+ ->groupBy('date')
+ ->get()
+ ->keyBy('date');
+
+ $dates = $totals = $amounts = [];
+ $period = CarbonPeriod::create(today()->subWeek(), today()->subDay())->toArray();
+ foreach ($period as $date) {
+ $dateFormat = $date->format('Y-m-d');
+ $orderTotal = $orderTotals[$dateFormat] ?? null;
+ $orderAmount = $orderAmounts[$dateFormat] ?? null;
+
+ $dates[] = $dateFormat;
+ $totals[] = $orderTotal ? $orderTotal->total : 0;
+ $amounts[] = $orderAmount ? $orderAmount->amount : 0;
+ }
+
+ $data = [
+ 'period' => $dates,
+ 'totals' => $totals,
+ 'amounts' => $amounts,
+ ];
+
+ return hook_filter('dashboard.order_report_week', $data);
+ }
+
+ /**
+ * 获取最近一年每月销售订单数
+ */
+ public static function getLatestYear()
+ {
+ $orderTotals = OrderRepo::getListBuilder(['start' => today()->subYear(), 'end' => today()])
+ ->select(DB::raw('YEAR(created_at) as year, MONTH(created_at) as month, count(*) as total'))
+ ->groupBy(['year', 'month'])
+ ->get();
+ $orderMonthTotals = [];
+ foreach ($orderTotals as $orderTotal) {
+ $key = Carbon::create($orderTotal->year, $orderTotal->month)->format('Y-m');
+ $orderMonthTotals[$key] = $orderTotal['total'];
+ }
+
+ $orderAmounts = OrderRepo::getListBuilder(['start' => today()->subYear(), 'end' => today()])
+ ->select(DB::raw('YEAR(created_at) as year, MONTH(created_at) as month, sum(total) as amount'))
+ ->groupBy(['year', 'month'])
+ ->get();
+ $orderMonthAmounts = [];
+ foreach ($orderAmounts as $orderAmount) {
+ $key = Carbon::create($orderAmount->year, $orderAmount->month)->format('Y-m');
+ $orderMonthAmounts[$key] = $orderAmount['amount'];
+ }
+
+ $dates = $totals = $amounts = [];
+ $period = CarbonPeriod::create(today()->subYear()->endOfMonth(), '1 month', today()->endOfMonth())->toArray();
+ foreach ($period as $date) {
+ $dateFormat = $date->format('Y-m');
+ $orderTotal = $orderMonthTotals[$dateFormat] ?? null;
+ $orderAmount = $orderMonthAmounts[$dateFormat] ?? null;
+
+ $dates[] = $dateFormat;
+ $totals[] = $orderTotal ?: 0;
+ $amounts[] = $orderAmount ?: 0;
+ }
+
+ $data = [
+ 'period' => $dates,
+ 'totals' => $totals,
+ 'amounts' => $amounts,
+ ];
+
+ return hook_filter('dashboard.order_report_year', $data);
+ }
+}
diff --git a/beike/Admin/Repositories/TaxClassRepo.php b/beike/Admin/Repositories/TaxClassRepo.php
new file mode 100644
index 00000000..8df54501
--- /dev/null
+++ b/beike/Admin/Repositories/TaxClassRepo.php
@@ -0,0 +1,66 @@
+
+ * @created 2022-07-26 21:08:07
+ * @modified 2022-07-26 21:08:07
+ */
+
+namespace Beike\Admin\Repositories;
+
+use Beike\Admin\Http\Resources\TaxClassDetail;
+use Beike\Models\TaxClass;
+
+class TaxClassRepo
+{
+ public const BASE_TYPES = ['store', 'payment', 'shipping'];
+
+ public static function getList()
+ {
+ $taxClass = TaxClass::query()->with([
+ 'taxRates.region',
+ 'taxRules',
+ ])->get();
+
+ return TaxClassDetail::collection($taxClass)->jsonSerialize();
+ }
+
+ public static function createOrUpdate($data)
+ {
+ $id = $data['id'] ?? 0;
+ if ($id) {
+ $taxClass = TaxClass::query()->findOrFail($id);
+ } else {
+ $taxClass = new TaxClass();
+ }
+ $taxClass->fill([
+ 'title' => $data['title'],
+ 'description' => $data['description'],
+ ]);
+ $taxClass->saveOrFail();
+
+ $rules = [];
+ foreach ($data['tax_rules'] as $rule) {
+ $rules[] = [
+ 'tax_rate_id' => $rule['tax_rate_id'],
+ 'based' => $rule['based'],
+ 'priority' => (int) $rule['priority'],
+ ];
+ }
+ $taxClass->taxRules()->delete();
+ $taxClass->taxRules()->createMany($rules);
+ $taxClass->load(['taxRules']);
+
+ return $taxClass;
+ }
+
+ public static function deleteById($id)
+ {
+ $taxClass = TaxClass::query()->findOrFail($id);
+ $taxClass->taxRules()->delete();
+ $taxClass->delete();
+ }
+}
diff --git a/beike/Admin/Repositories/TaxRateRepo.php b/beike/Admin/Repositories/TaxRateRepo.php
new file mode 100644
index 00000000..341e370f
--- /dev/null
+++ b/beike/Admin/Repositories/TaxRateRepo.php
@@ -0,0 +1,56 @@
+
+ * @created 2022-07-27 11:21:14
+ * @modified 2022-07-27 11:21:14
+ */
+
+namespace Beike\Admin\Repositories;
+
+use Beike\Models\TaxRate;
+
+class TaxRateRepo
+{
+ public static function getList()
+ {
+ return TaxRate::query()->with([
+ 'region',
+ ])->get();
+ }
+
+ public static function createOrUpdate($data)
+ {
+ $id = $data['id'] ?? 0;
+ if ($id) {
+ $taxRate = TaxRate::query()->findOrFail($id);
+ } else {
+ $taxRate = new TaxRate();
+ }
+ $taxRate->fill([
+ 'region_id' => $data['region_id'],
+ 'name' => $data['name'],
+ 'rate' => $data['rate'],
+ 'type' => $data['type'],
+ ]);
+ $taxRate->saveOrFail();
+
+ return $taxRate;
+ }
+
+ public static function deleteById($id)
+ {
+ $taxRate = TaxRate::query()->findOrFail($id);
+ $taxRate->delete();
+ }
+
+ public static function getNameByRateId($taxRateId)
+ {
+ $taxRate = TaxRate::query()->findOrFail($taxRateId);
+
+ return $taxRate->name;
+ }
+}
diff --git a/beike/Admin/Routes/admin.php b/beike/Admin/Routes/admin.php
new file mode 100644
index 00000000..5abfda76
--- /dev/null
+++ b/beike/Admin/Routes/admin.php
@@ -0,0 +1,246 @@
+middleware(['admin'])
+ ->name("{$adminName}.")
+ ->group(function () {
+ Route::get('login', [Controllers\LoginController::class, 'show'])->name('login.show');
+ Route::post('login', [Controllers\LoginController::class, 'store'])->name('login.store');
+
+ Route::get('forgotten', [ForgottenController::class, 'index'])->name('forgotten.index');
+ Route::post('forgotten/send_code', [ForgottenController::class, 'sendVerifyCode'])->name('forgotten.send_code');
+ Route::post('forgotten/password', [ForgottenController::class, 'changePassword'])->name('forgotten.password');
+
+ Route::middleware('admin_auth:' . \Beike\Models\AdminUser::AUTH_GUARD)
+ ->group(function () {
+ Route::get('/', [Controllers\HomeController::class, 'index'])->name('home.index');
+
+ // 属性
+ Route::middleware('can:attributes_update')->post('attributes/{id}/values', [Controllers\AttributeController::class, 'storeValue'])->name('attributes.values.store');
+ Route::middleware('can:attributes_show')->get('attributes/{id}/values/autocomplete', [Controllers\AttributeController::class, 'autocompleteValue'])->name('attributes.values.autocomplete');
+ Route::middleware('can:attributes_update')->put('attributes/{id}/values/{value_id}', [Controllers\AttributeController::class, 'updateValue'])->name('attributes.values.update');
+ Route::middleware('can:attributes_update')->delete('attributes/{id}/values/{value_id}', [Controllers\AttributeController::class, 'destroyValue'])->name('attributes.values.destroy');
+ Route::middleware('can:attributes_index')->get('attributes', [Controllers\AttributeController::class, 'index'])->name('attributes.index');
+ Route::middleware('can:attributes_show')->get('attributes/autocomplete', [Controllers\AttributeController::class, 'autocomplete'])->name('attributes.autocomplete');
+ Route::middleware('can:attributes_show')->get('attributes/{id}', [Controllers\AttributeController::class, 'show'])->name('attributes.show');
+ Route::middleware('can:attributes_create')->post('attributes', [Controllers\AttributeController::class, 'store'])->name('attributes.store');
+ Route::middleware('can:attributes_update')->put('attributes/{id}', [Controllers\AttributeController::class, 'update'])->name('attributes.update');
+ Route::middleware('can:attributes_delete')->delete('attributes/{id}', [Controllers\AttributeController::class, 'destroy'])->name('attributes.destroy');
+
+ // 属性组
+ Route::middleware('can:attribute_groups_index')->get('attribute_groups', [Controllers\AttributeGroupController::class, 'index'])->name('attribute_groups.index');
+ Route::middleware('can:attribute_groups_create')->post('attribute_groups', [Controllers\AttributeGroupController::class, 'store'])->name('attribute_groups.store');
+ Route::middleware('can:attribute_groups_update')->put('attribute_groups/{id}', [Controllers\AttributeGroupController::class, 'update'])->name('attribute_groups.update');
+ Route::middleware('can:attribute_groups_delete')->delete('attribute_groups/{id}', [Controllers\AttributeGroupController::class, 'destroy'])->name('attribute_groups.destroy');
+
+ // 商品品牌
+ Route::middleware('can:brands_index')->get('brands/names', [Controllers\BrandController::class, 'getNames'])->name('brands.names');
+ Route::middleware('can:brands_index')->get('brands/autocomplete', [Controllers\BrandController::class, 'autocomplete'])->name('brands.autocomplete');
+ Route::middleware('can:brands_show')->get('brands/{id}/name', [Controllers\BrandController::class, 'name'])->name('brands.name');
+ Route::middleware('can:brands_index')->get('brands', [Controllers\BrandController::class, 'index'])->name('brands.index');
+ Route::middleware('can:brands_create')->post('brands', [Controllers\BrandController::class, 'store'])->name('brands.store');
+ Route::middleware('can:brands_update')->put('brands/{brand}', [Controllers\BrandController::class, 'update'])->name('brands.update');
+ Route::middleware('can:brands_delete')->delete('brands/{brand}', [Controllers\BrandController::class, 'destroy'])->name('brands.destroy');
+
+ // 商品分类
+ Route::middleware('can:categories_index')->get('categories/autocomplete', [Controllers\CategoryController::class, 'autocomplete'])->name('categories.autocomplete');
+ Route::middleware('can:categories_show')->get('categories/{category}/name', [Controllers\CategoryController::class, 'name'])->name('categories.name');
+ Route::middleware('can:categories_index')->get('categories', [Controllers\CategoryController::class, 'index'])->name('categories.index');
+ Route::middleware('can:categories_create')->get('categories/create', [Controllers\CategoryController::class, 'create'])->name('categories.create');
+ Route::middleware('can:categories_create')->post('categories', [Controllers\CategoryController::class, 'store'])->name('categories.store');
+ Route::middleware('can:categories_update')->get('categories/{category}/edit', [Controllers\CategoryController::class, 'edit'])->name('categories.edit');
+ Route::middleware('can:categories_update')->put('categories/{category}', [Controllers\CategoryController::class, 'update'])->name('categories.update');
+ Route::middleware('can:categories_delete')->delete('categories/{category}', [Controllers\CategoryController::class, 'destroy'])->name('categories.destroy');
+
+ // 国家
+ Route::middleware('can:countries_index')->get('countries', [Controllers\CountryController::class, 'index'])->name('countries.index');
+ Route::middleware('can:countries_create')->post('countries', [Controllers\CountryController::class, 'store'])->name('countries.store');
+ Route::middleware('can:countries_update')->put('countries/{id}', [Controllers\CountryController::class, 'update'])->name('countries.update');
+ Route::middleware('can:countries_delete')->delete('countries/{id}', [Controllers\CountryController::class, 'destroy'])->name('countries.destroy');
+
+ // 省份
+ Route::middleware('can:zones_index')->get('zones', [Controllers\ZoneController::class, 'index'])->name('zones.index');
+ Route::middleware('can:zones_create')->post('zones', [Controllers\ZoneController::class, 'store'])->name('zones.store');
+ Route::middleware('can:zones_update')->put('zones/{id}', [Controllers\ZoneController::class, 'update'])->name('zones.update');
+ Route::middleware('can:zones_delete')->delete('zones/{id}', [Controllers\ZoneController::class, 'destroy'])->name('zones.destroy');
+
+ // 客户
+ Route::middleware('can:customers_index')->get('customers/trashed', [Controllers\CustomerController::class, 'trashed'])->name('customers.trashed');
+ Route::middleware('can:customers_index')->get('customers', [Controllers\CustomerController::class, 'index'])->name('customers.index');
+ Route::middleware('can:customers_create')->post('customers', [Controllers\CustomerController::class, 'store'])->name('customers.store');
+ Route::middleware('can:customers_show')->get('customers/{id}/edit', [Controllers\CustomerController::class, 'edit'])->name('customers.edit');
+ Route::middleware('can:customers_update')->put('customers/{id}', [Controllers\CustomerController::class, 'update'])->name('customers.update');
+ Route::middleware('can:customers_create')->delete('customers/{id}/restore', [Controllers\CustomerController::class, 'restore'])->name('customers.restore');
+ Route::middleware('can:customers_delete')->delete('customers/{id}', [Controllers\CustomerController::class, 'destroy'])->name('customers.destroy');
+ Route::middleware('can:customers_delete')->delete('customers/{id}/force', [Controllers\CustomerController::class, 'forceDelete'])->name('customers.force_delete');
+ Route::middleware('can:customers_delete')->post('customers/force_delete_all', [Controllers\CustomerController::class, 'forceDeleteAll'])->name('customers.force_delete_all');
+
+ // 客户地址
+ Route::middleware('can:customers_show')->get('customers/{customer_id}/addresses', [Controllers\AddressController::class, 'index'])->name('customers.addresses.index');
+ Route::middleware('can:customers_update')->post('customers/{customer_id}/addresses', [Controllers\AddressController::class, 'store'])->name('customers.addresses.store');
+ Route::middleware('can:customers_update')->put('customers/{customer_id}/addresses/{id}', [Controllers\AddressController::class, 'update'])->name('customers.addresses.update');
+ Route::middleware('can:customers_update')->delete('customers/{customer_id}/addresses/{id}', [Controllers\AddressController::class, 'destroy'])->name('customers.addresses.destroy');
+
+ Route::get('countries/{country_id}/zones', [Controllers\ZoneController::class, 'listByCountry'])->name('countries.zones.index');
+
+ // 客户组
+ Route::middleware('can:customer_groups_index')->get('customer_groups', [Controllers\CustomerGroupController::class, 'index'])->name('customer_groups.index');
+ Route::middleware('can:customer_groups_create')->post('customer_groups', [Controllers\CustomerGroupController::class, 'store'])->name('customer_groups.store');
+ Route::middleware('can:customer_groups_update')->put('customer_groups/{id}', [Controllers\CustomerGroupController::class, 'update'])->name('customer_groups.update');
+ Route::middleware('can:customer_groups_delete')->delete('customer_groups/{id}', [Controllers\CustomerGroupController::class, 'destroy'])->name('customer_groups.destroy');
+
+ // 货币
+ Route::middleware('can:currencies_index')->get('currencies', [Controllers\CurrencyController::class, 'index'])->name('currencies.index');
+ Route::middleware('can:currencies_create')->post('currencies', [Controllers\CurrencyController::class, 'store'])->name('currencies.store');
+ Route::middleware('can:currencies_update')->put('currencies/{id}', [Controllers\CurrencyController::class, 'update'])->name('currencies.update');
+ Route::middleware('can:currencies_delete')->delete('currencies/{id}', [Controllers\CurrencyController::class, 'destroy'])->name('currencies.destroy');
+
+ // 页面装修
+ Route::middleware('can:design_index')->get('design/builder', [Controllers\DesignController::class, 'index'])->name('design.index');
+ Route::middleware('can:design_index')->put('design/builder', [Controllers\DesignController::class, 'update'])->name('design.update');
+ Route::middleware('can:design_index')->post('design/builder/preview', [Controllers\DesignController::class, 'preview'])->name('design.module.preview');
+
+ Route::middleware('can:design_footer_index')->get('design_footer/builder', [Controllers\DesignFooterController::class, 'index'])->name('design_footer.index');
+ Route::middleware('can:design_footer_index')->put('design_footer/builder', [Controllers\DesignFooterController::class, 'update'])->name('design_footer.update');
+ Route::middleware('can:design_footer_index')->post('design_footer/builder/preview', [Controllers\DesignFooterController::class, 'preview'])->name('design_footer.module.preview');
+
+ Route::middleware('can:design_menu_index')->get('design_menu/builder', [Controllers\DesignMenuController::class, 'index'])->name('design_menu.index');
+ Route::middleware('can:design_menu_index')->put('design_menu/builder', [Controllers\DesignMenuController::class, 'update'])->name('design_menu.update');
+
+ // 模板主题
+ Route::middleware('can:theme_index')->get('themes', [Controllers\ThemeController::class, 'index'])->name('theme.index');
+ Route::middleware('can:theme_update')->put('themes/{code}', [Controllers\ThemeController::class, 'update'])->name('theme.update');
+
+ Route::put('edit', [Controllers\EditController::class, 'update'])->name('edit');
+ Route::get('edit/locale', [Controllers\EditController::class, 'locale'])->name('edit.locale');
+
+ // 图片库
+ Route::middleware('can:file_manager_show')->get('file_manager', [Controllers\FileManagerController::class, 'index'])->name('file_manager.index');
+ Route::middleware('can:file_manager_show')->get('file_manager/files', [Controllers\FileManagerController::class, 'getFiles'])->name('file_manager.get_files');
+ Route::middleware('can:file_manager_show')->get('file_manager/directories', [Controllers\FileManagerController::class, 'getDirectories'])->name('file_manager.get_directories');
+ Route::middleware('can:file_manager_create')->post('file_manager/directories', [Controllers\FileManagerController::class, 'createDirectory'])->name('file_manager.create_directory');
+ Route::middleware('can:file_manager_create')->post('file_manager/upload', [Controllers\FileManagerController::class, 'uploadFiles'])->name('file_manager.upload');
+ Route::middleware('can:file_manager_update')->post('file_manager/rename', [Controllers\FileManagerController::class, 'rename'])->name('file_manager.rename');
+ Route::middleware('can:file_manager_delete')->delete('file_manager/files', [Controllers\FileManagerController::class, 'destroyFiles'])->name('file_manager.delete_files');
+ Route::middleware('can:file_manager_delete')->delete('file_manager/directories', [Controllers\FileManagerController::class, 'destroyDirectories'])->name('file_manager.delete_directories');
+
+ Route::get('logout', [Controllers\LogoutController::class, 'index'])->name('logout.index');
+
+ // 语言管理
+ Route::middleware('can:languages_index')->get('languages', [Controllers\LanguageController::class, 'index'])->name('languages.index');
+ Route::middleware('can:languages_create')->post('languages', [Controllers\LanguageController::class, 'store'])->name('languages.store');
+ Route::middleware('can:languages_update')->put('languages/{id}', [Controllers\LanguageController::class, 'update'])->name('languages.update');
+ Route::middleware('can:languages_delete')->delete('languages/{id}', [Controllers\LanguageController::class, 'destroy'])->name('languages.destroy');
+
+ // 订单
+ Route::middleware('can:orders_index')->get('orders', [Controllers\OrderController::class, 'index'])->name('orders.index');
+ Route::middleware('can:orders_export')->get('orders/export', [Controllers\OrderController::class, 'export'])->name('orders.export');
+ Route::middleware('can:orders_show')->get('orders/{order}', [Controllers\OrderController::class, 'show'])->name('orders.show');
+ Route::middleware('can:orders_update_status')->put('orders/{order}/status', [Controllers\OrderController::class, 'updateStatus'])->name('orders.update_status');
+ Route::middleware('can:orders_update_status')->put('orders/{order}/shipments/{shipment}', [Controllers\OrderController::class, 'updateShipment'])->name('orders.update_shipment');
+
+ // 插件
+ Route::middleware('can:plugins_index')->get('plugins', [Controllers\PluginController::class, 'index'])->name('plugins.index');
+ Route::middleware('can:plugins_import')->post('plugins/import', [Controllers\PluginController::class, 'import'])->name('plugins.import');
+ Route::middleware('can:plugins_show')->get('plugins/{code}/edit', [Controllers\PluginController::class, 'edit'])->name('plugins.edit');
+ Route::middleware('can:plugins_update')->put('plugins/{code}', [Controllers\PluginController::class, 'update'])->name('plugins.update');
+ Route::middleware('can:plugins_update_status')->put('plugins/{code}/status', [Controllers\PluginController::class, 'updateStatus'])->name('plugins.update_status');
+ Route::middleware('can:plugins_install')->post('plugins/{code}/install', [Controllers\PluginController::class, 'install'])->name('plugins.install');
+ Route::middleware('can:plugins_uninstall')->post('plugins/{code}/uninstall', [Controllers\PluginController::class, 'uninstall'])->name('plugins.uninstall');
+
+ // 插件市场
+ Route::middleware('can:marketing_index')->get('marketing', [Controllers\MarketingController::class, 'index'])->name('marketing.index');
+ Route::middleware('can:marketing_show')->get('marketing/{code}', [Controllers\MarketingController::class, 'show'])->name('marketing.show');
+ Route::middleware('can:marketing_buy')->post('marketing/{code}/buy', [Controllers\MarketingController::class, 'buy'])->name('marketing.buy');
+ Route::middleware('can:marketing_download')->post('marketing/{code}/download', [Controllers\MarketingController::class, 'download'])->name('marketing.download');
+
+ // 文章
+ Route::middleware('can:pages_index')->get('pages', [Controllers\PagesController::class, 'index'])->name('pages.index');
+ Route::middleware('can:pages_index')->get('pages/autocomplete', [Controllers\PagesController::class, 'autocomplete'])->name('pages.autocomplete');
+ Route::middleware('can:pages_create')->get('pages/create', [Controllers\PagesController::class, 'create'])->name('pages.create');
+ Route::middleware('can:pages_show')->get('pages/{page}/edit', [Controllers\PagesController::class, 'edit'])->name('pages.edit');
+ Route::middleware('can:pages_show')->get('pages/{page}/name', [Controllers\PagesController::class, 'name'])->name('pages.name');
+ Route::middleware('can:pages_create')->post('pages', [Controllers\PagesController::class, 'store'])->name('pages.store');
+ Route::middleware('can:pages_update')->put('pages/{page}', [Controllers\PagesController::class, 'update'])->name('pages.update');
+ Route::middleware('can:pages_delete')->delete('pages/{page}', [Controllers\PagesController::class, 'destroy'])->name('pages.destroy');
+
+ // 文章分类
+ Route::middleware('can:page_categories_index')->get('page_categories', [Controllers\PageCategoryController::class, 'index'])->name('page_categories.index');
+ Route::middleware('can:page_categories_index')->get('page_categories/autocomplete', [Controllers\PageCategoryController::class, 'autocomplete'])->name('page_categories.autocomplete');
+ Route::middleware('can:page_categories_create')->get('page_categories/create', [Controllers\PageCategoryController::class, 'create'])->name('page_categories.create');
+ Route::middleware('can:page_categories_show')->get('page_categories/{page_category}/edit', [Controllers\PageCategoryController::class, 'edit'])->name('page_categories.edit');
+ Route::middleware('can:page_categories_show')->get('page_categories/{page_category}/name', [Controllers\PageCategoryController::class, 'name'])->name('page_categories.name');
+ Route::middleware('can:page_categories_create')->post('page_categories', [Controllers\PageCategoryController::class, 'store'])->name('page_categories.store');
+ Route::middleware('can:page_categories_update')->put('page_categories/{page_category}', [Controllers\PageCategoryController::class, 'update'])->name('page_categories.update');
+ Route::middleware('can:page_categories_delete')->delete('page_categories/{page_category}', [Controllers\PageCategoryController::class, 'destroy'])->name('page_categories.destroy');
+
+ // 商品
+ Route::middleware('can:products_restore')->put('products/restore', [Controllers\ProductController::class, 'restore']);
+ Route::middleware('can:products_trashed')->get('products/trashed', [Controllers\ProductController::class, 'trashed'])->name('products.trashed');
+ Route::middleware('can:products_trashed')->post('products/trashed/clear', [Controllers\ProductController::class, 'trashedClear'])->name('products.trashed.clear');
+ Route::middleware('can:products_show')->get('products/{id}/name', [Controllers\ProductController::class, 'name'])->name('products.name');
+ Route::middleware('can:products_index')->get('products/names', [Controllers\ProductController::class, 'getNames'])->name('products.names');
+ Route::middleware('can:products_index')->get('products/autocomplete', [Controllers\ProductController::class, 'autocomplete'])->name('products.autocomplete');
+
+ Route::middleware('can:products_update')->post('products/status', [Controllers\ProductController::class, 'updateStatus'])->name('products.update_status');
+ Route::middleware('can:products_delete')->delete('products/delete', [Controllers\ProductController::class, 'destroyByIds'])->name('products.batch_delete');
+ Route::middleware('can:products_index')->get('products', [Controllers\ProductController::class, 'index'])->name('products.index');
+ Route::middleware('can:products_create')->get('products/create', [Controllers\ProductController::class, 'create'])->name('products.create');
+ Route::middleware('can:products_create')->post('products', [Controllers\ProductController::class, 'store'])->name('products.store');
+ Route::middleware('can:products_update')->get('products/{product}/edit', [Controllers\ProductController::class, 'edit'])->name('products.edit');
+ Route::middleware('can:products_update')->put('products/{product}', [Controllers\ProductController::class, 'update'])->name('products.update');
+ Route::middleware('can:products_delete')->delete('products/{product}', [Controllers\ProductController::class, 'destroy'])->name('products.destroy');
+
+ // 区域组
+ Route::middleware('can:regions_index')->get('regions', [Controllers\RegionController::class, 'index'])->name('regions.index');
+ Route::middleware('can:regions_create')->post('regions', [Controllers\RegionController::class, 'store'])->name('regions.store');
+ Route::middleware('can:regions_update')->put('regions/{id}', [Controllers\RegionController::class, 'update'])->name('regions.update');
+ Route::middleware('can:regions_delete')->delete('regions/{id}', [Controllers\RegionController::class, 'destroy'])->name('regions.destroy');
+
+ // RMA
+ Route::middleware('can:rmas_update')->post('rmas/history/{id}', [Controllers\RmaController::class, 'addHistory'])->name('rmas.add_history');
+ Route::middleware('can:rmas_index')->get('rmas', [Controllers\RmaController::class, 'index'])->name('rmas.index');
+ Route::middleware('can:rmas_show')->get('rmas/{id}', [Controllers\RmaController::class, 'show'])->name('rmas.show');
+ Route::middleware('can:rmas_delete')->delete('rmas/{id}', [Controllers\RmaController::class, 'destroy'])->name('rmas.destroy');
+
+ Route::middleware('can:rma_reasons_index')->get('rma_reasons', [Controllers\RmaReasonController::class, 'index'])->name('rma_reasons.index');
+ Route::middleware('can:rma_reasons_create')->post('rma_reasons', [Controllers\RmaReasonController::class, 'store'])->name('rma_reasons.store');
+ Route::middleware('can:rma_reasons_update')->put('rma_reasons/{id}', [Controllers\RmaReasonController::class, 'update'])->name('rma_reasons.update');
+ Route::middleware('can:rma_reasons_delete')->delete('rma_reasons/{id}', [Controllers\RmaReasonController::class, 'destroy'])->name('rma_reasons.destroy');
+
+ Route::middleware('can:settings_index')->get('settings', [Controllers\SettingController::class, 'index'])->name('settings.index');
+ Route::middleware('can:settings_update')->post('settings', [Controllers\SettingController::class, 'store'])->name('settings.store');
+ Route::middleware('can:settings_update')->post('settings/store_token', [Controllers\SettingController::class, 'storeDeveloperToken'])->name('settings.store_token');
+
+ // 税类
+ Route::middleware('can:tax_classes_index')->get('tax_classes', [Controllers\TaxClassController::class, 'index'])->name('tax_classes.index');
+ Route::middleware('can:tax_classes_create')->post('tax_classes', [Controllers\TaxClassController::class, 'store'])->name('tax_classes.store');
+ Route::middleware('can:tax_classes_update')->put('tax_classes/{tax_class}', [Controllers\TaxClassController::class, 'update'])->name('tax_classes.update');
+ Route::middleware('can:tax_classes_delete')->delete('tax_classes/{tax_class}', [Controllers\TaxClassController::class, 'destroy'])->name('tax_classes.destroy');
+
+ // 税费
+ Route::middleware('can:tax_rates_index')->get('tax_rates', [Controllers\TaxRateController::class, 'index'])->name('tax_rates.index');
+ Route::middleware('can:tax_rates_create')->post('tax_rates', [Controllers\TaxRateController::class, 'store'])->name('tax_rates.store');
+ Route::middleware('can:tax_rates_update')->put('tax_rates/{tax_rate}', [Controllers\TaxRateController::class, 'update'])->name('tax_rates.update');
+ Route::middleware('can:tax_rates_delete')->delete('tax_rates/{tax_rate}', [Controllers\TaxRateController::class, 'destroy'])->name('tax_rates.destroy');
+
+ // 后台用户
+ Route::middleware('can:admin_users_index')->get('admin_users', [Controllers\AdminUserController::class, 'index'])->name('admin_users.index');
+ Route::middleware('can:admin_users_create')->post('admin_users', [Controllers\AdminUserController::class, 'store'])->name('admin_users.store');
+ Route::middleware('can:admin_users_update')->put('admin_users/{user_id}', [Controllers\AdminUserController::class, 'update'])->name('admin_users.update');
+ Route::middleware('can:admin_users_delete')->delete('admin_users/{user_id}', [Controllers\AdminUserController::class, 'destroy'])->name('admin_users.destroy');
+
+ // 后台用户组
+ Route::middleware('can:admin_roles_index')->get('admin_roles', [Controllers\AdminRoleController::class, 'index'])->name('admin_roles.index');
+ Route::middleware('can:admin_roles_create')->get('admin_roles/create', [Controllers\AdminRoleController::class, 'create'])->name('admin_roles.create');
+ Route::middleware('can:admin_roles_create')->post('admin_roles', [Controllers\AdminRoleController::class, 'store'])->name('admin_roles.store');
+ Route::middleware('can:admin_roles_show')->get('admin_roles/{role_id}/edit', [Controllers\AdminRoleController::class, 'edit'])->name('admin_roles.edit');
+ Route::middleware('can:admin_roles_update')->put('admin_roles/{role_id}', [Controllers\AdminRoleController::class, 'update'])->name('admin_roles.update');
+ Route::middleware('can:admin_roles_delete')->delete('admin_roles/{role_id}', [Controllers\AdminRoleController::class, 'destroy'])->name('admin_roles.destroy');
+ });
+ });
diff --git a/beike/Admin/Services/AddressService.php b/beike/Admin/Services/AddressService.php
new file mode 100644
index 00000000..197198d2
--- /dev/null
+++ b/beike/Admin/Services/AddressService.php
@@ -0,0 +1,57 @@
+
+ * @created 2022-07-04 11:15:25
+ * @modified 2022-07-04 11:15:25
+ */
+
+namespace Beike\Admin\Services;
+
+use Beike\Repositories\AddressRepo;
+use Beike\Repositories\ZoneRepo;
+
+class AddressService
+{
+ public static function addForCustomer($customerId, $data)
+ {
+ $data = self::getParams($data);
+ $data['customer_id'] = $customerId;
+ $address = AddressRepo::create($data);
+
+ return $address;
+ }
+
+ public static function update($addressId, $data)
+ {
+ $data = self::getParams($data);
+ $address = AddressRepo::update($addressId, $data);
+
+ return $address;
+ }
+
+ /**
+ * @param $data
+ * @return array
+ */
+ public static function getParams($data): array
+ {
+ $data = [
+ 'name' => $data['name'] ?? '',
+ 'phone' => $data['phone'] ?? '',
+ 'country_id' => (int) $data['country_id'] ?? 0,
+ 'zone_id' => (int) $data['zone_id'] ?? 0,
+ 'zone' => ZoneRepo::find($data['zone_id'])->name,
+ 'city_id' => (int) $data['city_id'] ?? 0,
+ 'city' => $data['city'] ?? '',
+ 'zipcode' => $data['zipcode'] ?? '',
+ 'address_1' => $data['address_1'] ?? '',
+ 'address_2' => $data['address_2'] ?? '',
+ ];
+
+ return $data;
+ }
+}
diff --git a/beike/Admin/Services/CategoryService.php b/beike/Admin/Services/CategoryService.php
new file mode 100644
index 00000000..ed3115a1
--- /dev/null
+++ b/beike/Admin/Services/CategoryService.php
@@ -0,0 +1,182 @@
+
+ * @created 2022-05-07 15:15:25
+ * @modified 2022-05-07 15:15:25
+ */
+
+namespace Beike\Admin\Services;
+
+use Beike\Models\Category;
+use Beike\Models\CategoryPath;
+use Illuminate\Support\Facades\DB;
+
+class CategoryService
+{
+ public function createOrUpdate(array $data, ?Category $category)
+ {
+ $isUpdating = $category !== null;
+ if ($category === null) {
+ $category = new Category();
+ }
+
+ try {
+ DB::beginTransaction();
+
+ $category->fill($data);
+ $category->save();
+
+ $descriptions = [];
+ foreach ($data['descriptions'] as $locale => $description) {
+ $descriptions[] = [
+ 'locale' => $locale,
+ 'name' => $description['name'],
+ 'content' => $description['content'] ?? '',
+ 'meta_title' => $description['meta_title'] ?? '',
+ 'meta_description' => $description['meta_description'] ?? '',
+ 'meta_keywords' => $description['meta_keywords'] ?? '',
+ ];
+ }
+ if ($isUpdating) {
+ $category->descriptions()->delete();
+ }
+ $category->descriptions()->createMany($descriptions);
+
+ if ($isUpdating) {
+ $this->updatePath($category);
+ } else {
+ $this->createPath($category);
+ }
+
+ DB::commit();
+ } catch (\Exception $e) {
+ DB::rollBack();
+
+ throw $e;
+ }
+
+ return $category;
+ }
+
+ public function createPath(Category $category)
+ {
+ // Paths
+ $paths = [];
+ // 复制上级分类的 paths
+ $level = 0;
+ $parentPaths = CategoryPath::query()->where('category_id', $category->parent_id)->orderBy('level')->get();
+ foreach ($parentPaths as $path) {
+ $paths[] = [
+ 'path_id' => $path->path_id,
+ 'level' => $level,
+ ];
+ $level++;
+ }
+ // 自身
+ $paths[] = [
+ 'path_id' => $category->id,
+ 'level' => $level,
+ ];
+ $category->paths()->createMany($paths);
+ }
+
+ public function updatePath(Category $category)
+ {
+ $categoryPaths = CategoryPath::query()
+ ->where('path_id', $category->id)
+ ->orderBy('level')
+ ->get();
+
+ // Get the nodes new parents
+ $newParentPathIds = CategoryPath::query()
+ ->where('category_id', $category->parent_id)
+ ->orderBy('level')
+ ->pluck('path_id')
+ ->toArray();
+
+ $paths = [];
+ if ($categoryPaths->count()) {
+ foreach ($categoryPaths as $category_path) {
+ $newPathIds = $newParentPathIds;
+
+ $results = CategoryPath::query()
+ ->where('category_id', (int) $category_path->category_id)
+ ->where('level', '>=', $category_path->level)
+ ->orderBy('level')
+ ->get();
+
+ foreach ($results as $result) {
+ $newPathIds[] = $result->path_id;
+ }
+
+ $level = 0;
+ foreach ($newPathIds as $path_id) {
+ $paths[] = [
+ 'category_id' => $category_path->category_id,
+ 'path_id' => $path_id,
+ 'level' => $level,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ];
+ $level++;
+ }
+ }
+ }
+
+ CategoryPath::query()
+ ->whereIn('category_id', $categoryPaths->pluck('category_id'))
+ ->delete();
+ CategoryPath::insert($paths);
+
+ // $this->repairCategories(0);
+ }
+
+ /**
+ * 重建category path
+ *
+ * @param int $parentId
+ */
+ public static function repairCategories(int $parentId = 0)
+ {
+ $categories = Category::query()->where('parent_id', $parentId)->get();
+
+ foreach ($categories as $category) {
+ // Delete the path below the current one
+ CategoryPath::query()->where('category_id', $category->id)->delete();
+
+ // Fix for records with no paths
+ $level = 0;
+ $subCategoryPaths = CategoryPath::query()->where('category_id', $parentId)->orderBy('level')->get();
+ foreach ($subCategoryPaths as $path) {
+ CategoryPath::query()->create([
+ 'category_id' => $category->id,
+ 'path_id' => $path->path_id,
+ 'level' => $level,
+ ]);
+ $level++;
+ }
+
+ $path = CategoryPath::query()
+ ->where('category_id', $category->id)
+ ->where('path_id', $category->id)
+ ->where('level', $level)
+ ->first();
+ $pathData = [
+ 'category_id' => $category->id,
+ 'path_id' => $category->id,
+ 'level' => $level,
+ ];
+ if ($path) {
+ $path->update($pathData);
+ } else {
+ CategoryPath::query()->create($pathData);
+ }
+
+ self::repairCategories($category->id);
+ }
+ }
+}
diff --git a/beike/Admin/Services/CustomerGroupService.php b/beike/Admin/Services/CustomerGroupService.php
new file mode 100644
index 00000000..ab27b0a8
--- /dev/null
+++ b/beike/Admin/Services/CustomerGroupService.php
@@ -0,0 +1,76 @@
+
+ * @created 2022-07-01 11:15:25
+ * @modified 2022-07-01 11:15:25
+ */
+
+namespace Beike\Admin\Services;
+
+use Beike\Repositories\CustomerGroupRepo;
+
+class CustomerGroupService
+{
+ /**
+ * @param $data
+ * @return int
+ */
+ public static function create($data)
+ {
+ $data = self::getParams($data);
+ $customerGroup = CustomerGroupRepo::create($data);
+
+ $descriptions = [];
+ foreach ($data['descriptions'] as $locale => $description) {
+ $description['locale'] = $locale;
+ $descriptions[] = $description;
+ }
+ $customerGroup->descriptions()->createMany($descriptions);
+
+ return $customerGroup;
+ }
+
+ public static function update($id, $data)
+ {
+ $data = self::getParams($data);
+ $customerGroup = CustomerGroupRepo::find($id);
+
+ $customerGroup->update($data);
+
+ $customerGroup->descriptions()->delete();
+ $descriptions = [];
+ foreach ($data['descriptions'] as $locale => $description) {
+ $description['locale'] = $locale;
+ $descriptions[] = $description;
+ }
+ $customerGroup->descriptions()->createMany($descriptions);
+
+ return $customerGroup;
+ }
+
+ private static function getParams($data)
+ {
+ $descriptions = [];
+ foreach ($data['name'] as $locale => $value) {
+ $descriptions[$locale] = [
+ 'name' => $value,
+ 'description' => $data['description'][$locale] ?? '',
+ ];
+ }
+
+ $params = [
+ 'total' => (int) $data['total'] ?? 0,
+ 'reward_point_factor' => (float) $data['reward_point_factor'] ?? 0,
+ 'use_point_factor' => (float) $data['use_point_factor'] ?? 0,
+ 'discount_factor' => (float) $data['discount_factor'] ?? 0,
+ 'level' => (int) $data['level'] ?? 0,
+ 'descriptions' => $descriptions,
+ ];
+
+ return $params;
+ }
+}
diff --git a/beike/Admin/Services/CustomerService.php b/beike/Admin/Services/CustomerService.php
new file mode 100644
index 00000000..19b872ab
--- /dev/null
+++ b/beike/Admin/Services/CustomerService.php
@@ -0,0 +1,26 @@
+
+ * @created 2022-07-01 11:15:25
+ * @modified 2022-07-01 11:15:25
+ */
+
+namespace Beike\Admin\Services;
+
+use Beike\Repositories\CustomerRepo;
+
+class CustomerService
+{
+ public static function create($data)
+ {
+ $data['locale'] = system_setting('base.locale');
+ $data['from'] = 'admin';
+ $customer = CustomerRepo::create($data);
+
+ return $customer;
+ }
+}
diff --git a/beike/Admin/Services/FileManagerService.php b/beike/Admin/Services/FileManagerService.php
new file mode 100644
index 00000000..60549a7c
--- /dev/null
+++ b/beike/Admin/Services/FileManagerService.php
@@ -0,0 +1,222 @@
+
+ * @created 2022-07-12 15:12:48
+ * @modified 2022-07-12 15:12:48
+ */
+
+namespace Beike\Admin\Services;
+
+class FileManagerService
+{
+ private $fileBasePath = '';
+
+ public function __construct()
+ {
+ $this->fileBasePath = public_path('catalog');
+ }
+
+ /**
+ * 获取某个目录下所有文件夹
+ */
+ public function getDirectories($baseFolder = '/'): array
+ {
+ $currentBasePath = rtrim($this->fileBasePath . $baseFolder, '/');
+ $directories = glob("{$currentBasePath}/*", GLOB_ONLYDIR);
+
+ $result = [];
+ foreach ($directories as $directory) {
+ $baseName = basename($directory);
+ $dirName = str_replace($this->fileBasePath, '', $directory);
+ if (is_dir($directory)) {
+ $item = $this->handleFolder($dirName, $baseName);
+ $subDirectories = $this->getDirectories($dirName);
+ if ($subDirectories) {
+ $item['children'] = $subDirectories;
+ }
+ $result[] = $item;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 获取某个目录下的文件和文件夹
+ *
+ * @param $baseFolder
+ * @param int $page
+ * @param int $perPage
+ * @return array
+ * @throws \Exception
+ */
+ public function getFiles($baseFolder, int $page = 1, int $perPage = 20): array
+ {
+ $currentBasePath = rtrim($this->fileBasePath . $baseFolder, '/');
+ $files = glob($currentBasePath . '/*');
+ usort($files, function ($a, $b) {
+ return filemtime($a) - filemtime($b) < 0;
+ });
+
+ $images = [];
+ foreach ($files as $file) {
+ $baseName = basename($file);
+ if ($baseName == 'index.html') {
+ continue;
+ }
+ $fileName = str_replace($this->fileBasePath, '', $file);
+ if (is_file($file)) {
+ $images[] = $this->handleImage($fileName, $baseName);
+ }
+ }
+
+ $page = $page > 0 ? $page : 1;
+ $imageCollection = collect($images);
+
+ $currentImages = $imageCollection->forPage($page, $perPage);
+ $currentImages = $currentImages->map(function ($item) {
+ $item['url'] = image_resize("{$item['path']}");
+
+ return $item;
+ });
+
+ return [
+ 'images' => $currentImages->values(),
+ 'image_total' => $imageCollection->count(),
+ 'image_page' => $page,
+ ];
+ }
+
+ /**
+ * 创建目录
+ * @param $folderName
+ * @throws \Exception
+ */
+ public function createDirectory($folderName)
+ {
+ $catalogFolderPath = "catalog/{$folderName}";
+ $folderPath = public_path($catalogFolderPath);
+ if (is_dir($folderPath)) {
+ throw new \Exception(trans('admin/file_manager.directory_already_exist'));
+ }
+ create_directories($catalogFolderPath);
+ }
+
+ /**
+ * 删除文件或文件夹
+ *
+ * @param $filePath
+ * @throws \Exception
+ */
+ public function deleteDirectoryOrFile($filePath)
+ {
+ $filePath = public_path("catalog/{$filePath}");
+ if (is_dir($filePath)) {
+ $files = glob($filePath . '/*');
+ if ($files) {
+ throw new \Exception(trans('admin/file_manager.directory_not_empty'));
+ }
+ @rmdir($filePath);
+ } elseif (file_exists($filePath)) {
+ @unlink($filePath);
+ }
+ }
+
+ /**
+ * 批量删除文件
+ *
+ * @param $basePath
+ * @param $files
+ */
+ public function deleteFiles($basePath, $files)
+ {
+ if (empty($basePath) && empty($files)) {
+ return;
+ }
+ foreach ($files as $file) {
+ $filePath = public_path("catalog/{$basePath}/$file");
+ if (file_exists($filePath)) {
+ @unlink($filePath);
+ }
+ }
+ }
+
+ /**
+ * 修改文件夹或者文件名称
+ *
+ * @param $originPath
+ * @param $newPath
+ * @throws \Exception
+ */
+ public function updateName($originPath, $newPath)
+ {
+ $originPath = public_path("catalog/{$originPath}");
+ if (! is_dir($originPath) && ! file_exists($originPath)) {
+ throw new \Exception(trans('admin/file_manager.target_not_exist'));
+ }
+ $originBase = dirname($originPath);
+ $newPath = $originBase . '/' . $newPath;
+ if ($originPath == $newPath) {
+ return;
+ }
+ @rename($originPath, $newPath);
+ }
+
+ /**
+ * 处理文件夹
+ *
+ * @param $folderPath
+ * @param $baseName
+ * @return array
+ */
+ private function handleFolder($folderPath, $baseName): array
+ {
+ return [
+ 'path' => $folderPath,
+ 'name' => $baseName,
+ ];
+ }
+
+ /**
+ * 检测是否含有子文件夹
+ *
+ * @param $folderPath
+ * @return bool
+ */
+ private function hasSubFolders($folderPath): bool
+ {
+ $path = public_path("catalog/{$folderPath}");
+ $subFiles = glob($path . '/*');
+ foreach ($subFiles as $subFile) {
+ if (is_dir($subFile)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 处理文件
+ *
+ * @param $filePath
+ * @param $baseName
+ * @return array
+ * @throws \Exception
+ */
+ private function handleImage($filePath, $baseName): array
+ {
+ $path = "catalog{$filePath}";
+
+ return [
+ 'path' => $path,
+ 'name' => $baseName,
+ 'origin_url' => image_origin($path),
+ 'selected' => false,
+ ];
+ }
+}
diff --git a/beike/Admin/Services/LanguageService.php b/beike/Admin/Services/LanguageService.php
new file mode 100644
index 00000000..2d5699d0
--- /dev/null
+++ b/beike/Admin/Services/LanguageService.php
@@ -0,0 +1,86 @@
+toArray();
+ $languages = array_column($languages, null, 'code');
+
+ $result = [];
+ foreach (admin_languages() as $languageCode) {
+ $langFile = resource_path("lang/$languageCode/admin/base.php");
+ if (! is_file($langFile)) {
+ throw new \Exception("File ($langFile) not exist!");
+ }
+ $baseData = require $langFile;
+ $name = $baseData['name'] ?? $languageCode;
+ $result[] = [
+ 'code' => $languageCode,
+ 'name' => $name,
+ 'id' => $languages[$languageCode]['id'] ?? 0,
+ 'image' => $languages[$languageCode]['image'] ?? '',
+ 'sort_order' => $languages[$languageCode]['sort_order'] ?? '',
+ 'status' => $languages[$languageCode]['status'] ?? '',
+ ];
+ }
+
+ return $result;
+ }
+
+ public static function create($data)
+ {
+ $language = LanguageRepo::create($data);
+
+ if ($language->code == system_setting('base.locale')) {
+ return $language;
+ }
+
+ $models = self::$models;
+ foreach ($models as $className) {
+ $className = "\\Beike\\Models\\$className";
+ $items = $className::query()->where('locale', system_setting('base.locale', 'en'))->get()->toArray();
+ foreach ($items as &$item) {
+ if (isset($item['created_at'])) {
+ $item['created_at'] = now();
+ }
+ if (isset($item['updated_at'])) {
+ $item['updated_at'] = now();
+ }
+ unset($item['id']);
+ $item['locale'] = $language->code;
+ }
+ $className::query()->insert($items);
+ }
+
+ return $language;
+ }
+
+ public static function delete($id)
+ {
+ $language = LanguageRepo::find($id);
+ if (! $language) {
+ return;
+ }
+ if ($language->code == system_setting('base.locale')) {
+ throw new NotAcceptableHttpException(trans('admin/language.error_default_language_cannot_delete'));
+ }
+ LanguageRepo::delete($id);
+
+ $models = self::$models;
+ foreach ($models as $className) {
+ $className = "\\Beike\\Models\\$className";
+ $className::query()->where('locale', $language->code)->delete();
+ }
+ }
+}
diff --git a/beike/Admin/Services/MarketingService.php b/beike/Admin/Services/MarketingService.php
new file mode 100644
index 00000000..6273d48d
--- /dev/null
+++ b/beike/Admin/Services/MarketingService.php
@@ -0,0 +1,112 @@
+
+ * @created 2022-09-26 11:50:34
+ * @modified 2022-09-26 11:50:34
+ */
+
+namespace Beike\Admin\Services;
+
+use Illuminate\Http\Client\PendingRequest;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Storage;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use ZanySoft\Zip\Zip;
+
+class MarketingService
+{
+ private PendingRequest $httpClient;
+
+ public function __construct()
+ {
+ $this->httpClient = Http::withOptions([
+ 'verify' => false,
+ ])->withHeaders([
+ 'developer-token' => system_setting('base.developer_token'),
+ ]);
+ }
+
+ public static function getInstance()
+ {
+ return new self;
+ }
+
+ /**
+ * 获取可插件市场插件列表
+ *
+ * @param array $filters
+ * @return mixed
+ */
+ public function getList(array $filters = []): mixed
+ {
+ $url = config('beike.api_url') . '/api/plugins';
+ if (! empty($filters)) {
+ $url .= '?' . http_build_query($filters);
+ }
+
+ return $this->httpClient->get($url)->json();
+ }
+
+ /**
+ * 获取插件市场单个插件信息
+ *
+ * @param $pluginCode
+ * @return mixed
+ */
+ public function getPlugin($pluginCode): mixed
+ {
+ $url = config('beike.api_url') . "/api/plugins/{$pluginCode}";
+ $plugin = $this->httpClient->get($url)->json();
+ if (empty($plugin)) {
+ throw new NotFoundHttpException('该插件不存在或已下架');
+ }
+
+ return $plugin;
+ }
+
+ /**
+ * 购买插件市场单个插件
+ *
+ * @throws \Exception
+ */
+ public function buy($pluginCode, $postData)
+ {
+ $url = config('beike.api_url') . "/api/plugins/{$pluginCode}/buy";
+
+ $content = $this->httpClient->withBody($postData, 'application/json')
+ ->post($url)
+ ->json();
+
+ $status = $content['status'] ?? '';
+ if ($status == 'success') {
+ return $content['data'];
+ }
+
+ throw new \Exception($content['message'] ?? '');
+ }
+
+ /**
+ * 下载插件到网站
+ *
+ * @param $pluginCode
+ * @throws \Exception
+ */
+ public function download($pluginCode)
+ {
+ $datetime = date('Y-m-d');
+ $url = config('beike.api_url') . "/api/plugins/{$pluginCode}/download";
+
+ $content = $this->httpClient->get($url)->body();
+
+ $pluginPath = "plugins/{$pluginCode}-{$datetime}.zip";
+ Storage::disk('local')->put($pluginPath, $content);
+
+ $pluginZip = storage_path('app/' . $pluginPath);
+ $zipFile = (new Zip)->open($pluginZip);
+ $zipFile->extract(base_path('plugins'));
+ }
+}
diff --git a/beike/Admin/Services/ProductService.php b/beike/Admin/Services/ProductService.php
new file mode 100644
index 00000000..9ffcf3ac
--- /dev/null
+++ b/beike/Admin/Services/ProductService.php
@@ -0,0 +1,73 @@
+createOrUpdate($product, $data);
+ }
+
+ public function update(Product $product, array $data): Product
+ {
+ return $this->createOrUpdate($product, $data);
+ }
+
+ protected function createOrUpdate(Product $product, array $data): Product
+ {
+ $isUpdating = $product->id > 0;
+
+ try {
+ DB::beginTransaction();
+
+ $data['brand_id'] = (int) $data['brand_id'];
+ $data['variables'] = json_decode($data['variables']);
+ $product->fill($data);
+ $product->save();
+
+ if ($isUpdating) {
+ $product->skus()->delete();
+ $product->descriptions()->delete();
+ $product->attributes()->delete();
+ }
+
+ $descriptions = [];
+ foreach ($data['descriptions'] as $locale => $description) {
+ $description['locale'] = $locale;
+ $description['content'] = $description['content'] ?? '';
+
+ $descriptions[] = $description;
+ }
+ $product->descriptions()->createMany($descriptions);
+
+ $product->attributes()->createMany($data['attributes'] ?? []);
+
+ $skus = [];
+ foreach ($data['skus'] as $index => $sku) {
+ $sku['position'] = $index;
+ $sku['origin_price'] = (float) $sku['origin_price'];
+ $sku['cost_price'] = (float) $sku['cost_price'];
+ $sku['quantity'] = (int) $sku['quantity'];
+ $skus[] = $sku;
+ }
+ $product->skus()->createMany($skus);
+
+ $product->categories()->sync($data['categories'] ?? []);
+ $product->relations()->sync($data['relations'] ?? []);
+
+ DB::commit();
+
+ return $product;
+ } catch (\Exception $e) {
+ DB::rollBack();
+
+ throw $e;
+ }
+ }
+}
diff --git a/beike/Admin/Services/UserService.php b/beike/Admin/Services/UserService.php
new file mode 100644
index 00000000..1b755273
--- /dev/null
+++ b/beike/Admin/Services/UserService.php
@@ -0,0 +1,69 @@
+
+ * @created 2022-07-14 12:12:57
+ * @modified 2022-07-14 12:12:57
+ */
+
+namespace Beike\Admin\Services;
+
+use Beike\Repositories\UserRepo;
+use Beike\Repositories\VerifyCodeRepo;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\Log;
+
+class UserService
+{
+ /**
+ * 发送验证码通过$type方式,type为email或telephone
+ * @param $email
+ * @return void
+ */
+ public static function sendVerifyCodeForForgotten($email)
+ {
+ $code = str_pad(mt_rand(10, 999999), 6, '0', STR_PAD_LEFT);
+
+ VerifyCodeRepo::deleteByAccount($email);
+ VerifyCodeRepo::create([
+ 'account' => $email,
+ 'code' => $code,
+ ]);
+
+ Log::info("找回密码验证码:{$code}");
+
+ UserRepo::findByEmail($email)->notifyVerifyCodeForForgotten($code);
+ }
+
+ /**
+ * 验证验证码是否正确,并修改密码为新密码
+ * @param $code
+ * @param $account
+ * @param $password
+ * @return void
+ */
+ public static function verifyAndChangePassword($code, $account, $password)
+ {
+ $verifyCode = VerifyCodeRepo::findByAccount($account);
+ if ($verifyCode->created_at->addMinutes(10) < Carbon::now()) {
+ $verifyCode->delete();
+
+ throw new \Exception(trans('admin/user.verify_code_expired'));
+ }
+
+ if ($verifyCode->code != $code) {
+ throw new \Exception(trans('admin/user.verify_code_error'));
+ }
+
+ $user = UserRepo::findByEmail($account);
+ if (! $user) {
+ throw new \Exception(trans('admin/user.account_not_exist'));
+ }
+
+ UserRepo::update($user, ['password' => $password]);
+ $verifyCode->delete();
+ }
+}
diff --git a/beike/Admin/View/Components/Alert.php b/beike/Admin/View/Components/Alert.php
new file mode 100644
index 00000000..7d60c203
--- /dev/null
+++ b/beike/Admin/View/Components/Alert.php
@@ -0,0 +1,23 @@
+type = $type ?? 'success';
+ $this->msg = $msg;
+ }
+
+ public function render()
+ {
+ return view('admin::components.alert');
+ }
+}
diff --git a/beike/Admin/View/Components/Filter.php b/beike/Admin/View/Components/Filter.php
new file mode 100644
index 00000000..a67c458d
--- /dev/null
+++ b/beike/Admin/View/Components/Filter.php
@@ -0,0 +1,33 @@
+url = $url;
+ $this->queries = request()->query() ?? [];
+ }
+
+ /**
+ * Get the view / contents that represent the component.
+ *
+ * @return \Illuminate\Contracts\View\View|\Closure|string
+ */
+ public function render()
+ {
+ return view('Resources::components.admin.filter');
+ }
+}
diff --git a/beike/Admin/View/Components/Form/Image.php b/beike/Admin/View/Components/Form/Image.php
new file mode 100644
index 00000000..f362eb86
--- /dev/null
+++ b/beike/Admin/View/Components/Form/Image.php
@@ -0,0 +1,26 @@
+name = $name;
+ $this->title = $title ?? '';
+ $this->value = $value ?? '';
+ }
+
+ public function render()
+ {
+ return view('admin::components.form.image');
+ }
+}
diff --git a/beike/Admin/View/Components/Form/Input.php b/beike/Admin/View/Components/Form/Input.php
new file mode 100644
index 00000000..c9f4c3dd
--- /dev/null
+++ b/beike/Admin/View/Components/Form/Input.php
@@ -0,0 +1,47 @@
+name = $name;
+ $this->title = $title;
+ $this->value = html_entity_decode($value, ENT_QUOTES);
+ $this->error = $error;
+ $this->width = $width;
+ $this->placeholder = $placeholder;
+ $this->type = $type;
+ $this->step = $step;
+ $this->required = $required;
+ $this->description = $description;
+ }
+
+ public function render()
+ {
+ return view('admin::components.form.input');
+ }
+}
diff --git a/beike/Admin/View/Components/Form/InputLocale.php b/beike/Admin/View/Components/Form/InputLocale.php
new file mode 100644
index 00000000..7f4e8dc2
--- /dev/null
+++ b/beike/Admin/View/Components/Form/InputLocale.php
@@ -0,0 +1,87 @@
+name = $name;
+ $this->title = $title;
+ $this->width = $width;
+ $this->placeholder = $placeholder;
+ $this->value = $value;
+ $this->required = $required;
+ }
+
+ public function render()
+ {
+ return view('admin::components.form.input-locale');
+ }
+
+ public function formatName(string $code)
+ {
+ // descriptions.*.name => descriptions[zh_cn][name]
+
+ $segments = explode('.', $this->name);
+ $key = $segments[0];
+ for ($i = 1; $i < count($segments); $i++) {
+ $segment = $segments[$i];
+ if ($segment == '*') {
+ $key .= '[' . $code . ']';
+ } else {
+ $key .= '[' . $segment . ']';
+ }
+ }
+
+ return $key;
+ }
+
+ /**
+ * Get value from database
+ *
+ * @param $code
+ * @return mixed
+ */
+ public function formatValue($code)
+ {
+ $oldKey = str_replace('*', $code, $this->name);
+
+ if (is_string($this->value)) {
+ $value = json_decode($this->value, true);
+
+ return old($oldKey, Arr::get($value, $code, ''));
+ } elseif ($this->value instanceof Collection) {
+ // descriptions.*.name
+ $segments = explode('.', $this->name);
+ array_shift($segments);
+ $valueKey = implode('.', $segments);
+ $valueKey = str_replace('*', $code, $valueKey);
+
+ return old($oldKey, Arr::get($this->value, $valueKey, ''));
+ }
+
+ return '';
+ }
+
+ public function errorKey($code)
+ {
+ return str_replace('*', $code, $this->name);
+ }
+}
diff --git a/beike/Admin/View/Components/Form/RichText.php b/beike/Admin/View/Components/Form/RichText.php
new file mode 100644
index 00000000..c3a16e7f
--- /dev/null
+++ b/beike/Admin/View/Components/Form/RichText.php
@@ -0,0 +1,38 @@
+name = $name;
+ $this->title = $title;
+ $this->value = $value;
+ $this->required = $required;
+ $this->multiple = $multiple;
+ }
+
+ public function render()
+ {
+ return view('admin::components.form.rich-text');
+ }
+}
diff --git a/beike/Admin/View/Components/Form/Select.php b/beike/Admin/View/Components/Form/Select.php
new file mode 100644
index 00000000..3984247c
--- /dev/null
+++ b/beike/Admin/View/Components/Form/Select.php
@@ -0,0 +1,35 @@
+name = $name;
+ $this->title = $title;
+ $this->value = $value;
+ $this->options = $options;
+ $this->key = $key;
+ $this->label = $label;
+ }
+
+ public function render()
+ {
+ return view('admin::components.form.select');
+ }
+}
diff --git a/beike/Admin/View/Components/Form/SwitchRadio.php b/beike/Admin/View/Components/Form/SwitchRadio.php
new file mode 100644
index 00000000..6604ed61
--- /dev/null
+++ b/beike/Admin/View/Components/Form/SwitchRadio.php
@@ -0,0 +1,26 @@
+name = $name;
+ $this->title = $title;
+ $this->value = $value;
+ }
+
+ public function render()
+ {
+ return view('admin::components.form.switch-radio');
+ }
+}
diff --git a/beike/Admin/View/Components/Form/Textarea.php b/beike/Admin/View/Components/Form/Textarea.php
new file mode 100644
index 00000000..b0ed7ef1
--- /dev/null
+++ b/beike/Admin/View/Components/Form/Textarea.php
@@ -0,0 +1,29 @@
+name = $name;
+ $this->title = $title;
+ $this->value = html_entity_decode($value, ENT_QUOTES);
+ $this->required = $required;
+ }
+
+ public function render()
+ {
+ return view('admin::components.form.textarea');
+ }
+}
diff --git a/beike/Admin/View/Components/Header.php b/beike/Admin/View/Components/Header.php
new file mode 100644
index 00000000..b1a13a0e
--- /dev/null
+++ b/beike/Admin/View/Components/Header.php
@@ -0,0 +1,108 @@
+adminUser = current_user();
+ }
+
+ /**
+ * Get the view / contents that represent the component.
+ *
+ * @return mixed
+ */
+ public function render()
+ {
+ $sidebar = new Sidebar();
+ $preparedMenus = $this->prepareMenus();
+
+ foreach ($preparedMenus as $menu) {
+ $menuCode = $menu['code'] ?? '';
+ if ($menuCode) {
+ $routes = [];
+ $subRoutesMethod = "get{$menu['code']}SubRoutes";
+ if (method_exists($sidebar, $subRoutesMethod)) {
+ $sideMenuRoutes = $sidebar->{"get{$menu['code']}SubRoutes"}();
+ foreach ($sideMenuRoutes as $route) {
+ $routeFirst = explode('.', $route['route'])[0] ?? '';
+ $routes[] = 'admin.' . $route['route'];
+ $routes[] = 'admin.' . $routeFirst . '.edit';
+ $routes[] = 'admin.' . $routeFirst . '.show';
+ }
+ }
+
+ $data = [
+ 'menu_code' => $menuCode,
+ 'routes' => $routes,
+ ];
+ $filterRoutes = hook_filter('admin.components.header.routes', $data);
+ $routes = $filterRoutes['routes'] ?? [];
+ if (empty($routes)) {
+ $is_route = equal_route('admin.' . $menu['route']);
+ } else {
+ $is_route = equal_route($routes);
+ }
+ } else {
+ $is_route = equal_route('admin.' . $menu['route']);
+ }
+
+ $this->addLink($menu['name'], $menu['route'], $is_route);
+ }
+
+ return view('admin::components.header');
+ }
+
+ /**
+ * 默认菜单
+ */
+ private function prepareMenus()
+ {
+ $menus = [
+ ['name' => trans('admin/common.home'), 'route' => 'home.index', 'code' => ''],
+ ['name' => trans('admin/common.order'), 'route' => 'orders.index', 'code' => 'Order'],
+ ['name' => trans('admin/common.product'), 'route' => 'products.index', 'code' => 'Product'],
+ ['name' => trans('admin/common.customer'), 'route' => 'customers.index', 'code' => 'Customer'],
+ ['name' => trans('admin/common.page'), 'route' => 'pages.index', 'code' => 'Page'],
+ ['name' => trans('admin/common.setting'), 'route' => 'settings.index', 'code' => 'Setting'],
+ ];
+
+ return hook_filter('admin.header_menus', $menus);
+ }
+
+ /**
+ * 添加后台顶部菜单链接
+ *
+ * @param $title
+ * @param $route
+ * @param false $active
+ */
+ private function addLink($title, $route, bool $active = false)
+ {
+ $permissionRoute = str_replace('.', '_', $route);
+ if ($this->adminUser->cannot($permissionRoute) && $route != 'home.index') {
+ return;
+ }
+
+ $url = admin_route($route);
+ $this->links[] = [
+ 'title' => $title,
+ 'url' => $url,
+ 'active' => $active,
+ ];
+ }
+}
diff --git a/beike/Admin/View/Components/NoData.php b/beike/Admin/View/Components/NoData.php
new file mode 100644
index 00000000..67e2e262
--- /dev/null
+++ b/beike/Admin/View/Components/NoData.php
@@ -0,0 +1,20 @@
+text = $text ?: trans('common.no_data');
+ }
+
+ public function render()
+ {
+ return view('admin::components.no-data');
+ }
+}
diff --git a/beike/Admin/View/Components/Sidebar.php b/beike/Admin/View/Components/Sidebar.php
new file mode 100644
index 00000000..5c32fd9e
--- /dev/null
+++ b/beike/Admin/View/Components/Sidebar.php
@@ -0,0 +1,285 @@
+adminName = admin_name();
+ $this->routeNameWithPrefix = request()->route()->getName();
+ $this->adminUser = current_user();
+ }
+
+ /**
+ * Get the view / contents that represent the component.
+ *
+ * @return mixed
+ */
+ public function render()
+ {
+ $adminName = $this->adminName;
+ $routeNameWithPrefix = request()->route()->getName();
+ $routeName = str_replace($adminName . '.', '', $routeNameWithPrefix);
+
+ if (Str::startsWith($routeName, $this->getHomeSubPrefix())) {
+ $routes = $this->getHomeSubRoutes();
+ foreach ($routes as $route) {
+ $this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
+ }
+ } elseif (Str::startsWith($routeName, $this->getProductSubPrefix())) {
+ $routes = $this->getProductSubRoutes();
+ foreach ($routes as $route) {
+ $this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
+ }
+ } elseif (Str::startsWith($routeName, $this->getCustomerSubPrefix())) {
+ $routes = $this->getCustomerSubRoutes();
+ foreach ($routes as $route) {
+ $this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
+ }
+ } elseif (Str::startsWith($routeName, $this->getOrderSubPrefix())) {
+ $routes = $this->getOrderSubRoutes();
+ foreach ($routes as $route) {
+ $this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
+ }
+ } elseif (Str::startsWith($routeName, $this->getPageSubPrefix())) {
+ $routes = $this->getPageSubRoutes();
+ foreach ($routes as $route) {
+ $this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
+ }
+ } elseif (Str::startsWith($routeName, $this->getSettingSubPrefix())) {
+ $routes = $this->getSettingSubRoutes();
+ foreach ($routes as $route) {
+ $this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
+ }
+ }
+
+ return view('admin::components.sidebar');
+ }
+
+ /**
+ * 添加左侧菜单链接
+ *
+ * @param $routeData
+ * @param $active
+ * @param false $newWindow
+ * @param int $hide_mobile
+ */
+ private function addLink($routeData, $active, bool $newWindow = false, int $hide_mobile = 0)
+ {
+ $route = $routeData['route'];
+ $icon = $routeData['icon'] ?? '';
+ $title = $routeData['title'] ?? '';
+
+ $permissionRoute = str_replace('.', '_', $route);
+ if ($this->adminUser->cannot($permissionRoute)) {
+ return;
+ }
+
+ if (empty($title)) {
+ $title = trans("admin/common.{$permissionRoute}");
+ }
+ $url = admin_route($route);
+ $this->links[] = [
+ 'title' => $title,
+ 'url' => $url,
+ 'icon' => $icon,
+ 'active' => $active,
+ 'hide_mobile' => $hide_mobile,
+ 'new_window' => $newWindow,
+ ];
+ }
+
+ /**
+ * 获取后台首页子页面路由前缀列表
+ */
+ private function getHomeSubPrefix()
+ {
+ $prefix = ['home.'];
+
+ return hook_filter('admin.sidebar.home.prefix', $prefix);
+ }
+
+ /**
+ * 获取后台产品子页面路由前缀列表
+ */
+ private function getProductSubPrefix()
+ {
+ $prefix = ['products.', 'categories.', 'brands.', 'attribute_groups.', 'attributes.'];
+
+ return hook_filter('admin.sidebar.product.prefix', $prefix);
+ }
+
+ /**
+ * 获取后台客户子页面路由前缀列表
+ */
+ private function getCustomerSubPrefix()
+ {
+ $prefix = ['customers.', 'customer_groups.'];
+
+ return hook_filter('admin.sidebar.customer.prefix', $prefix);
+ }
+
+ /**
+ * 获取后台订单子页面路由前缀列表
+ */
+ private function getOrderSubPrefix()
+ {
+ $prefix = ['orders.', 'rmas.', 'rma_reasons.'];
+
+ return hook_filter('admin.sidebar.order.prefix', $prefix);
+ }
+
+ /**
+ * 获取后台内容子页面路由前缀列表
+ */
+ private function getPageSubPrefix()
+ {
+ $prefix = ['pages.', 'page_categories.'];
+
+ return hook_filter('admin.sidebar.page.prefix', $prefix);
+ }
+
+ /**
+ * 获取后台系统设置子页面路由前缀列表
+ */
+ private function getSettingSubPrefix()
+ {
+ $prefix = ['settings.', 'admin_users.', 'admin_roles.', 'plugins.', 'theme.', 'marketing.', 'tax_classes', 'tax_rates', 'regions', 'currencies', 'languages', 'design_menu', 'countries', 'zones'];
+
+ return hook_filter('admin.sidebar.setting.prefix', $prefix);
+ }
+
+ /**
+ * 获取首页子页面路由
+ */
+ public function getHomeSubRoutes()
+ {
+ $routes = [
+ ['route' => 'design.index', 'icon' => 'fa fa-tachometer-alt', 'blank' => 1, 'hide_mobile' => 1],
+ ['route' => 'design_footer.index', 'icon' => 'fa fa-tachometer-alt', 'blank' => 1, 'hide_mobile' => 1],
+ ['route' => 'design_menu.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
+ ['route' => 'languages.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
+ ['route' => 'currencies.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
+ ['route' => 'plugins.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
+ ];
+
+ return hook_filter('admin.sidebar.home_routes', $routes);
+ }
+
+ /**
+ * 获取商品子页面路由
+ */
+ public function getProductSubRoutes()
+ {
+ $routes = [
+ ['route' => 'categories.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'products.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'brands.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
+ ['route' => 'attribute_groups.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'attributes.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'products.trashed', 'icon' => 'fa fa-tachometer-alt'],
+ ];
+
+ return hook_filter('admin.sidebar.product_routes', $routes);
+ }
+
+ /**
+ * 获取商品子页面路由
+ */
+ public function getCustomerSubRoutes()
+ {
+ $routes = [
+ ['route' => 'customers.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'customer_groups.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'customers.trashed', 'icon' => 'fa fa-tachometer-alt'],
+ ];
+
+ return hook_filter('admin.sidebar.customer_routes', $routes);
+ }
+
+ /**
+ * 获取订单子页面路由
+ */
+ public function getOrderSubRoutes()
+ {
+ $routes = [
+ ['route' => 'orders.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'rmas.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'rma_reasons.index', 'icon' => 'fa fa-tachometer-alt'],
+ ];
+
+ return hook_filter('admin.sidebar.order_routes', $routes);
+ }
+
+ /**
+ * 获取文章管理子页面路由
+ * @return mixed
+ */
+ public function getPageSubRoutes()
+ {
+ $routes = [
+ ['route' => 'page_categories.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'pages.index', 'icon' => 'fa fa-tachometer-alt'],
+ ];
+
+ return hook_filter('admin.sidebar.pages_routes', $routes);
+ }
+
+ /**
+ * 获取系统设置子页面路由
+ * @return mixed
+ */
+ public function getSettingSubRoutes()
+ {
+ $routes = [
+ ['route' => 'settings.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'admin_users.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'plugins.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
+ ['route' => 'theme.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
+ ['route' => 'marketing.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
+ ['route' => 'regions.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'tax_rates.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'tax_classes.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'currencies.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'languages.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'countries.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'zones.index', 'icon' => 'fa fa-tachometer-alt'],
+ ['route' => 'design.index', 'icon' => 'fa fa-tachometer-alt', 'blank' => true, 'hide_mobile' => 1],
+ ['route' => 'design_footer.index', 'icon' => 'fa fa-tachometer-alt', 'blank' => true, 'hide_mobile' => 1],
+ ['route' => 'design_menu.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
+ ];
+
+ return hook_filter('admin.sidebar.setting_routes', $routes);
+ }
+
+ /**
+ * 是否为当前访问路由
+ *
+ * @param $routeName
+ * @return bool
+ */
+ private function equalRoute($routeName): bool
+ {
+ $currentRouteName = str_replace($this->adminName . '.', '', $this->routeNameWithPrefix);
+
+ return $routeName == $currentRouteName;
+ }
+}
diff --git a/beike/Admin/View/DesignBuilders/Brand.php b/beike/Admin/View/DesignBuilders/Brand.php
new file mode 100644
index 00000000..7dafab5f
--- /dev/null
+++ b/beike/Admin/View/DesignBuilders/Brand.php
@@ -0,0 +1,44 @@
+
+ * @created 2022-07-08 17:09:15
+ * @modified 2022-07-08 17:09:15
+ */
+
+namespace Beike\Admin\View\DesignBuilders;
+
+use Illuminate\Contracts\View\View;
+use Illuminate\View\Component;
+
+class Brand 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' => 'brand',
+ 'sort' => 0,
+ 'name' => trans('admin/design_builder.module_brand'),
+ 'icon' => '',
+ ];
+
+ return view('admin::pages.design.module.brand', $data);
+ }
+}
diff --git a/beike/Admin/View/DesignBuilders/Icons.php b/beike/Admin/View/DesignBuilders/Icons.php
new file mode 100644
index 00000000..80e5af1a
--- /dev/null
+++ b/beike/Admin/View/DesignBuilders/Icons.php
@@ -0,0 +1,44 @@
+
+ * @created 2022-07-08 17:09:15
+ * @modified 2022-07-08 17:09:15
+ */
+
+namespace Beike\Admin\View\DesignBuilders;
+
+use Illuminate\Contracts\View\View;
+use Illuminate\View\Component;
+
+class Icons 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' => 'icons',
+ 'sort' => 0,
+ 'name' => trans('admin/design_builder.module_icons'),
+ 'icon' => '',
+ ];
+
+ return view('admin::pages.design.module.icons', $data);
+ }
+}
diff --git a/beike/Admin/View/DesignBuilders/Image100.php b/beike/Admin/View/DesignBuilders/Image100.php
new file mode 100644
index 00000000..709d3f45
--- /dev/null
+++ b/beike/Admin/View/DesignBuilders/Image100.php
@@ -0,0 +1,44 @@
+
+ * @created 2022-07-08 17:09:15
+ * @modified 2022-07-08 17:09:15
+ */
+
+namespace Beike\Admin\View\DesignBuilders;
+
+use Illuminate\Contracts\View\View;
+use Illuminate\View\Component;
+
+class Image100 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' => 'image100',
+ 'sort' => 0,
+ 'name' => trans('admin/design_builder.module_banner'),
+ 'icon' => '',
+ ];
+
+ return view('admin::pages.design.module.image100', $data);
+ }
+}
diff --git a/beike/Admin/View/DesignBuilders/Image200.php b/beike/Admin/View/DesignBuilders/Image200.php
new file mode 100644
index 00000000..804ef694
--- /dev/null
+++ b/beike/Admin/View/DesignBuilders/Image200.php
@@ -0,0 +1,44 @@
+
+ * @created 2022-07-08 17:09:15
+ * @modified 2022-07-08 17:09:15
+ */
+
+namespace Beike\Admin\View\DesignBuilders;
+
+use Illuminate\Contracts\View\View;
+use Illuminate\View\Component;
+
+class Image200 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' => 'image200',
+ 'sort' => 0,
+ 'name' => trans('admin/builder.modules_image_200'),
+ 'icon' => '',
+ ];
+
+ return view('admin::pages.design.module.image200', $data);
+ }
+}
diff --git a/beike/Admin/View/DesignBuilders/Image300.php b/beike/Admin/View/DesignBuilders/Image300.php
new file mode 100644
index 00000000..50d88fcb
--- /dev/null
+++ b/beike/Admin/View/DesignBuilders/Image300.php
@@ -0,0 +1,44 @@
+
+ * @created 2022-07-08 17:09:15
+ * @modified 2022-07-08 17:09:15
+ */
+
+namespace Beike\Admin\View\DesignBuilders;
+
+use Illuminate\Contracts\View\View;
+use Illuminate\View\Component;
+
+class Image300 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' => 'image300',
+ 'sort' => 0,
+ 'name' => trans('admin/builder.modules_image_300'),
+ 'icon' => '',
+ ];
+
+ return view('admin::pages.design.module.image300', $data);
+ }
+}
diff --git a/beike/Admin/View/DesignBuilders/Image401.php b/beike/Admin/View/DesignBuilders/Image401.php
new file mode 100644
index 00000000..ed2b07d8
--- /dev/null
+++ b/beike/Admin/View/DesignBuilders/Image401.php
@@ -0,0 +1,44 @@
+
+ * @created 2022-07-08 17:09:15
+ * @modified 2022-07-08 17:09:15
+ */
+
+namespace Beike\Admin\View\DesignBuilders;
+
+use Illuminate\Contracts\View\View;
+use Illuminate\View\Component;
+
+class Image401 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' => 'image401',
+ 'sort' => 0,
+ 'name' => trans('admin/design_builder.module_four_image_pro'),
+ 'icon' => '',
+ ];
+
+ return view('admin::pages.design.module.image401', $data);
+ }
+}
diff --git a/beike/Admin/View/DesignBuilders/Product.php b/beike/Admin/View/DesignBuilders/Product.php
new file mode 100644
index 00000000..3630af4e
--- /dev/null
+++ b/beike/Admin/View/DesignBuilders/Product.php
@@ -0,0 +1,44 @@
+
+ * @created 2022-07-08 17:09:15
+ * @modified 2022-07-08 17:09:15
+ */
+
+namespace Beike\Admin\View\DesignBuilders;
+
+use Illuminate\Contracts\View\View;
+use Illuminate\View\Component;
+
+class Product 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' => 'product',
+ 'sort' => 0,
+ 'name' => trans('admin/design_builder.module_product'),
+ 'icon' => '',
+ ];
+
+ return view('admin::pages.design.module.product', $data);
+ }
+}
diff --git a/beike/Admin/View/DesignBuilders/RichText.php b/beike/Admin/View/DesignBuilders/RichText.php
new file mode 100644
index 00000000..293d5f64
--- /dev/null
+++ b/beike/Admin/View/DesignBuilders/RichText.php
@@ -0,0 +1,44 @@
+
+ * @created 2022-07-08 17:09:15
+ * @modified 2022-07-08 17:09:15
+ */
+
+namespace Beike\Admin\View\DesignBuilders;
+
+use Illuminate\Contracts\View\View;
+use Illuminate\View\Component;
+
+class RichText 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' => 'rich_text',
+ 'sort' => 0,
+ 'name' => trans('admin/design_builder.module_rich_text'),
+ 'icon' => '',
+ ];
+
+ return view('admin::pages.design.module.rich_text', $data);
+ }
+}
diff --git a/beike/Admin/View/DesignBuilders/SlideShow.php b/beike/Admin/View/DesignBuilders/SlideShow.php
new file mode 100644
index 00000000..e45211ee
--- /dev/null
+++ b/beike/Admin/View/DesignBuilders/SlideShow.php
@@ -0,0 +1,45 @@
+
+ * @created 2022-07-08 17:09:15
+ * @modified 2022-07-08 17:09:15
+ */
+
+namespace Beike\Admin\View\DesignBuilders;
+
+use Illuminate\Contracts\View\View;
+use Illuminate\View\Component;
+
+class SlideShow 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' => 'slideshow',
+ 'sort' => 0,
+ 'name' => trans('admin/design_builder.module_slideshow'),
+ 'icon' => '',
+ 'style' => 'font-size: 40px;',
+ ];
+
+ return view('admin::pages.design.module.slideshow', $data);
+ }
+}
diff --git a/beike/Admin/View/DesignBuilders/TabProduct.php b/beike/Admin/View/DesignBuilders/TabProduct.php
new file mode 100644
index 00000000..c620310b
--- /dev/null
+++ b/beike/Admin/View/DesignBuilders/TabProduct.php
@@ -0,0 +1,44 @@
+
+ * @created 2022-07-08 17:09:15
+ * @modified 2022-07-08 17:09:15
+ */
+
+namespace Beike\Admin\View\DesignBuilders;
+
+use Illuminate\Contracts\View\View;
+use Illuminate\View\Component;
+
+class TabProduct 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' => 'tab_product',
+ 'sort' => 0,
+ 'name' => trans('admin/design_builder.module_tab_products'),
+ 'icon' => '',
+ ];
+
+ return view('admin::pages.design.module.tab_product', $data);
+ }
+}
diff --git a/beike/Config/beike.php b/beike/Config/beike.php
new file mode 100644
index 00000000..00486c5b
--- /dev/null
+++ b/beike/Config/beike.php
@@ -0,0 +1,19 @@
+
+ * @created 2022-06-06 09:09:09
+ * @modified 2022-09-13 22:32:41
+ */
+
+return [
+ 'api_url' => env('BEIKE_API_URL', 'https://beikeshop.com'),
+ 'version' => '1.3.4',
+ 'build' => '20230318',
+
+ 'admin_name' => env('ADMIN_NAME'),
+ 'force_url_https' => env('APP_FORCE_HTTPS', false),
+];
diff --git a/beike/Console/Commands/ChangeRootPassword.php b/beike/Console/Commands/ChangeRootPassword.php
new file mode 100644
index 00000000..dbfba762
--- /dev/null
+++ b/beike/Console/Commands/ChangeRootPassword.php
@@ -0,0 +1,41 @@
+
+ * @created 2023-02-13 20:56:16
+ * @modified 2023-02-13 20:56:16
+ */
+
+namespace Beike\Console\Commands;
+
+use Beike\Models\AdminUser;
+use Illuminate\Console\Command;
+
+class ChangeRootPassword extends Command
+{
+ protected $signature = 'root:password';
+
+ protected $description = '修改后台Root账号(第一个管理员)';
+
+ /**
+ * @throws \Throwable
+ */
+ public function handle()
+ {
+ $user = AdminUser::query()->first();
+ $newPassword = $this->ask("请为管理员 {$user->email} 设置新密码");
+
+ if (! $newPassword) {
+ $this->info('请输入新密码');
+
+ return;
+ }
+
+ $user->password = bcrypt($newPassword);
+ $user->saveOrFail();
+ $this->info('管理员密码设置成功!');
+ }
+}
diff --git a/beike/Console/Commands/FetchCurrencyRate.php b/beike/Console/Commands/FetchCurrencyRate.php
new file mode 100644
index 00000000..cd7e590d
--- /dev/null
+++ b/beike/Console/Commands/FetchCurrencyRate.php
@@ -0,0 +1,35 @@
+info(sprintf('获取 %s 汇率数据开始', $today));
+
+ try {
+ $tableRows = [];
+ $data = CurrencyService::getInstance()->getRatesFromApi($today);
+
+ foreach ($data as $key => $val) {
+ $tableRows[] = [$key, $val];
+ }
+
+ $this->table(['货币', '汇率'], $tableRows);
+
+ $this->info(sprintf('获取 %s 汇率数据完成', $today));
+ }catch (\Exception) {
+ $this->error(sprintf('获取 %s 汇率数据失败', $today));
+ }
+ }
+}
diff --git a/beike/Console/Commands/GenerateDatabaseDict.php b/beike/Console/Commands/GenerateDatabaseDict.php
new file mode 100644
index 00000000..ae7e0fc2
--- /dev/null
+++ b/beike/Console/Commands/GenerateDatabaseDict.php
@@ -0,0 +1,51 @@
+
+ * @created 2022-11-11 09:00:25
+ * @modified 2022-11-11 09:00:25
+ */
+
+namespace Beike\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\DB;
+use UniSharp\DocUs\Parser;
+
+class GenerateDatabaseDict extends Command
+{
+ protected $signature = 'make:dict';
+
+ protected $description = '生成数据字典 Markdown 文档';
+
+ public function handle()
+ {
+ $filePath = storage_path('database.md');
+ // $filePath = '/Users/edward/Guangda/Products/beike/beikedocs/docs/dev/database.md';
+ $tables = $this->getTables();
+ $markdown = view('vendor.unisharp.markdown', compact('tables'))->render();
+ file_put_contents($filePath, $markdown);
+ dump('done');
+ }
+
+ /**
+ * 获取所有数据表信息
+ * @return Collection
+ */
+ private function getTables(): Collection
+ {
+ $schema = Parser::getSchema();
+
+ return $schema->map(function ($item) {
+ $tableName = $item['name'];
+ $result = collect(DB::select("SHOW TABLE STATUS WHERE Name='{$tableName}'"))->first();
+ $item['comment'] = $result->Comment;
+
+ return $item;
+ });
+ }
+}
diff --git a/beike/Console/Commands/GenerateSitemap.php b/beike/Console/Commands/GenerateSitemap.php
new file mode 100644
index 00000000..3b155516
--- /dev/null
+++ b/beike/Console/Commands/GenerateSitemap.php
@@ -0,0 +1,32 @@
+ask('请输入登录邮箱地址');
+ $password = $this->ask('请输入密码');
+
+ if (! $email || ! $password) {
+ $this->info('邮箱地址/手机号码不能为空,退出');
+
+ return;
+ }
+
+ $admin = AdminUser::create([
+ 'name' => 'John Doe',
+ 'email' => $email,
+ 'password' => bcrypt($password),
+ 'active' => true,
+ ]);
+
+ $this->info('账号创建成功,退出');
+ }
+}
diff --git a/beike/Console/Commands/MigrateFromOpenCart.php b/beike/Console/Commands/MigrateFromOpenCart.php
new file mode 100644
index 00000000..da8fedf3
--- /dev/null
+++ b/beike/Console/Commands/MigrateFromOpenCart.php
@@ -0,0 +1,460 @@
+
+ * @created 2023-01-03 18:57:53
+ * @modified 2023-01-03 18:57:53
+ */
+
+namespace Beike\Console\Commands;
+
+use Beike\Admin\Services\CategoryService;
+use Beike\Admin\Services\ProductService;
+use Beike\Models\Brand;
+use Beike\Models\Category;
+use Beike\Models\CategoryDescription;
+use Beike\Models\CategoryPath;
+use Beike\Models\Product;
+use Beike\Models\ProductDescription;
+use Beike\Models\ProductSku;
+use Illuminate\Console\Command;
+use Illuminate\Database\ConnectionInterface;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\DB;
+
+class MigrateFromOpenCart extends Command
+{
+ public const PER_PAGE = 1000;
+
+ public const LANG_MAPPING = [
+ 'zh_cn' => 11,
+ 'es' => 6,
+ ];
+
+ protected $signature = 'migrate:oc';
+
+ protected $description = '从 OpenCart 迁移数据';
+
+ protected ConnectionInterface $ocdb;
+
+ private $ocProductVariants;
+
+ private $ocVariantDescriptions;
+
+ private $ocVariantValues;
+
+ private $ocVariantValueDescriptions;
+
+ private $ocProductImages;
+
+ private $ocProductCategories;
+
+ private int $page = 1;
+
+ public function __construct()
+ {
+ parent::__construct();
+ $this->ocdb = DB::connection('opencart');
+ }
+
+ /**
+ * 导入OC产品数据
+ */
+ public function handle()
+ {
+ $this->importCategories();
+ $this->importBrands();
+ $this->importProducts();
+ }
+
+ /**
+ * 导入分类数据
+ */
+ private function importCategories()
+ {
+ Category::query()->truncate();
+ CategoryDescription::query()->truncate();
+ CategoryPath::query()->truncate();
+
+ $this->ocdb->table('category')
+ ->orderBy('category_id')
+ ->chunk(self::PER_PAGE, function ($ocCategories) {
+ $bkCategories = [];
+ foreach ($ocCategories as $ocCategory) {
+ $bkCategories[] = [
+ 'id' => $ocCategory->category_id,
+ 'parent_id' => $ocCategory->parent_id,
+ 'position' => $ocCategory->sort_order,
+ 'active' => $ocCategory->status,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ];
+ }
+ Category::query()->insert($bkCategories);
+ });
+
+ $descriptions = [];
+ $ocDescriptions = $this->ocdb->table('category_description')->orderBy('category_id')->get();
+ $langMapping = array_flip(self::LANG_MAPPING);
+ foreach ($ocDescriptions as $description) {
+ $descriptions[] = [
+ 'category_id' => $description->category_id,
+ 'locale' => $langMapping[$description->language_id],
+ 'name' => $description->name,
+ 'content' => html_entity_decode($description->description, ENT_QUOTES),
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ];
+ }
+ CategoryDescription::query()->insert($descriptions);
+
+ CategoryService::repairCategories();
+ }
+
+ /**
+ * 导入品牌数据
+ */
+ private function importBrands()
+ {
+ Brand::query()->truncate();
+ $this->ocdb->table('manufacturer')
+ ->orderBy('manufacturer_id')
+ ->chunk(self::PER_PAGE, function ($ocBrands) {
+ $bkBrands = [];
+ foreach ($ocBrands as $ocBrand) {
+ $bkBrands[] = [
+ 'id' => $ocBrand->manufacturer_id,
+ 'name' => $ocBrand->name,
+ 'first' => mb_strtoupper(mb_substr($ocBrand->name, 0, 1)),
+ 'logo' => $ocBrand->image,
+ 'sort_order' => $ocBrand->sort_order,
+ 'status' => $ocBrand->status,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ];
+ }
+ Brand::query()->insert($bkBrands);
+ });
+ }
+
+ /**
+ * 导入产品
+ */
+ private function importProducts()
+ {
+ $this->ocProductVariants = $this->ocdb->table('product_variant')->get()->groupBy('product_id');
+ $this->ocVariantDescriptions = $this->ocdb->table('variant_description')->get()->groupBy('variant_id');
+ $this->ocVariantValues = $this->ocdb->table('variant_value')->get()->keyBy('variant_value_id');
+ $this->ocVariantValueDescriptions = $this->ocdb->table('variant_value_description')->get()->groupBy('variant_value_id');
+ $this->ocProductImages = $this->ocdb->table('product_image')->get()->groupBy('product_id');
+ $this->ocProductCategories = $this->ocdb->table('product_to_category')->get()->groupBy('product_id');
+
+ $this->clearData();
+ $this->ocdb->table('product')
+ ->where('parent_id', 0)
+ // ->where('product_id', 1894)
+ // ->where('sku', '10012378')
+ ->orderBy('product_id')
+ ->chunk(self::PER_PAGE, function ($ocProducts) {
+ $this->importProduct($ocProducts);
+ });
+ }
+
+ /**
+ * 导入单个产品
+ * @param Collection $ocProducts
+ */
+ private function importProduct(Collection $ocProducts)
+ {
+ $total = $ocProducts->count();
+ $ocProductIds = $ocProducts->pluck('product_id');
+ $childProducts = $this->ocdb->table('product')->whereIn('parent_id', $ocProductIds)->get()->groupBy('parent_id');
+ foreach ($ocProducts as $index => $ocProduct) {
+ dump("Start handle Page: $this->page - {$index}/{$total}");
+ $ocProductId = $ocProduct->product_id;
+ $productVariants = $this->ocProductVariants[$ocProductId] ?? [];
+ $childProducts = $childProducts[$ocProductId] ?? [];
+ $bkProduct = $this->generateBeikeProduct($ocProduct, $productVariants, $childProducts);
+ (new ProductService)->create($bkProduct);
+ }
+ $this->page++;
+ }
+
+ /**
+ * 构造 beike 产品
+ */
+ private function generateBeikeProduct($ocProduct, $productVariants, $childProducts)
+ {
+ $variables = $this->generateVariables($ocProduct, $childProducts);
+ $bkProduct = [
+ 'active' => $ocProduct->status,
+ 'brand_id' => $ocProduct->manufacturer_id,
+ 'position' => $ocProduct->sort_order,
+ 'tax_class_id' => $ocProduct->tax_class_id,
+ 'variables' => json_encode($variables),
+ ];
+ $bkProduct['descriptions'] = $this->generateDescriptions($ocProduct);
+ $bkProduct['images'] = [$ocProduct->image];
+ $bkProduct['skus'] = $this->generateSkus($ocProduct, $productVariants, $childProducts, $variables);
+ $bkProduct['categories'] = $this->generateCategories($ocProduct);
+
+ return $bkProduct;
+ }
+
+ /**
+ * 生成 beike 产品规格
+ * @return array[]
+ */
+ private function generateVariables($ocProduct, $childProducts): array
+ {
+ $productIds = [$ocProduct->product_id];
+ foreach ($childProducts as $childProduct) {
+ $productIds[] = $childProduct->product_id;
+ }
+
+ $locales = locales();
+ $items = $values = [];
+ foreach ($productIds as $productId) {
+ $productVariants = $this->ocProductVariants[$productId] ?? [];
+ foreach ($productVariants as $productVariant) {
+ $productVariantId = $productVariant->variant_id;
+ $productVariantValueId = $productVariant->variant_value_id;
+
+ $variants = $this->ocVariantDescriptions[$productVariantId];
+ $variantValue = $this->ocVariantValues[$productVariantValueId];
+ $variantValueDescriptions = $this->ocVariantValueDescriptions[$productVariantValueId];
+
+ $variants = $variants->keyBy('language_id');
+ $names = [];
+ foreach ($locales as $locale) {
+ $variant = $variants[self::LANG_MAPPING[$locale['code']]];
+ $names[$locale['code']] = $variant->name;
+ }
+
+ $valueNames = [];
+ $variantValueDescriptions = $variantValueDescriptions->keyBy('language_id');
+ foreach ($locales as $locale) {
+ $variantValueDescription = $variantValueDescriptions[self::LANG_MAPPING[$locale['code']]];
+ $valueNames[$locale['code']] = $variantValueDescription->name;
+ }
+
+ $values[$productVariantValueId] = [
+ 'variant_value_id' => $productVariantValueId,
+ 'name' => $valueNames,
+ 'image' => $variantValue->image,
+ ];
+
+ $items[$productVariantId] = [
+ 'variant_id' => $productVariantId,
+ 'name' => $names,
+ 'values' => $values,
+ 'isImage' => (bool) $variantValue->image,
+ ];
+ }
+ }
+
+ $items = array_values($items);
+ foreach ($items as $index => $item) {
+ $items[$index]['values'] = array_values($item['values']);
+ }
+
+ return $items;
+ }
+
+ /**
+ * 生成 beike 产品描述
+ * @param $ocProduct
+ * @return array
+ */
+ private function generateDescriptions($ocProduct): array
+ {
+ $descriptions = [];
+ $locales = locales();
+ $ocDescriptions = $this->ocdb->table('product_description')
+ ->where('product_id', $ocProduct->product_id)
+ ->get()
+ ->keyBy('language_id')
+ ->toArray();
+
+ foreach ($locales as $locale) {
+ $ocDescription = $ocDescriptions[self::LANG_MAPPING[$locale['code']]];
+ $descriptions[$locale['code']] = [
+ 'name' => $ocDescription->name,
+ 'content' => html_entity_decode($ocDescription->description, ENT_QUOTES),
+ ];
+ }
+
+ return $descriptions;
+ }
+
+ /**
+ * 生成 beike 产品 SKU
+ *
+ * @param $ocProduct
+ * @param $productVariants
+ * @param $childProducts
+ * @param array $variables
+ * @return array
+ */
+ private function generateSkus($ocProduct, $productVariants, $childProducts, array $variables): array
+ {
+ // 简单商品
+ if (count($productVariants) == 0) {
+ return $this->generateSimpleSku($ocProduct);
+ }
+
+ $masterSku = $childSkus = [];
+ // 有主商品
+ if (count($productVariants) > 0) {
+ $masterSku = $this->generateMasterSku($ocProduct, $productVariants, $variables);
+ }
+ // 有子商品
+ if (count($childProducts) > 0) {
+ $childSkus = $this->generateChildSkus($childProducts, $variables);
+ }
+ if ($masterSku) {
+ array_push($childSkus, $masterSku);
+ }
+
+ return $childSkus;
+ }
+
+ /**
+ * 获取简单商品 SKU
+ * @param $ocProduct
+ * @return array[]
+ */
+ private function generateSimpleSku($ocProduct): array
+ {
+ $images = $this->ocProductImages[$ocProduct->product_id] ?? [];
+
+ return [
+ [
+ 'image' => $images,
+ 'model' => $ocProduct->model,
+ 'sku' => $ocProduct->sku,
+ 'price' => $ocProduct->price,
+ 'origin_price' => $ocProduct->price,
+ 'cost_price' => $ocProduct->cost_price,
+ 'quantity' => $ocProduct->quantity,
+ 'variants' => null,
+ 'position' => $ocProduct->sort_order,
+ 'is_default' => 1,
+ ],
+ ];
+ }
+
+ /**
+ * 获取主商品 SKU
+ * @param $ocProduct
+ * @param $productVariants
+ * @param $variables
+ * @return array
+ */
+ private function generateMasterSku($ocProduct, $productVariants, $variables): array
+ {
+ $variants = $this->generateVariants($productVariants, $variables);
+ $images = $this->ocProductImages[$ocProduct->product_id] ?? [];
+
+ return [
+ 'image' => $images,
+ 'model' => $ocProduct->model,
+ 'sku' => $ocProduct->sku,
+ 'price' => $ocProduct->price,
+ 'origin_price' => $ocProduct->price,
+ 'cost_price' => $ocProduct->cost_price,
+ 'quantity' => $ocProduct->quantity,
+ 'variants' => $variants,
+ 'position' => $ocProduct->sort_order,
+ 'is_default' => 1,
+ ];
+ }
+
+ /**
+ * 获取子商品SKU
+ * @param $childProducts
+ * @param $variables
+ * @return array
+ */
+ private function generateChildSkus($childProducts, $variables): array
+ {
+ $items = [];
+ foreach ($childProducts as $ocProduct) {
+ $ocProductId = $ocProduct->product_id;
+ $productVariants = $this->ocProductVariants[$ocProductId] ?? [];
+
+ $images = $this->ocProductImages[$ocProductId] ?? [];
+ $variants = $this->generateVariants($productVariants, $variables);
+ $items[] = [
+ 'image' => $images,
+ 'model' => $ocProduct->model,
+ 'sku' => $ocProduct->sku,
+ 'price' => $ocProduct->price,
+ 'origin_price' => $ocProduct->price,
+ 'cost_price' => $ocProduct->cost_price,
+ 'quantity' => $ocProduct->quantity,
+ 'variants' => $variants,
+ 'position' => $ocProduct->sort_order,
+ 'is_default' => 0,
+ ];
+ }
+
+ return $items;
+ }
+
+ /**
+ * 生成商品 SKU 多规格数据
+ *
+ * @param $productVariants
+ * @param $variables
+ * @return array
+ */
+ private function generateVariants($productVariants, $variables): array
+ {
+ $items = [];
+ foreach ($productVariants as $productVariant) {
+ $variantId = $productVariant->variant_id;
+ $variantValueId = $productVariant->variant_value_id;
+ foreach ($variables as $index1 => $variable) {
+ if ($variantId == $variable['variant_id']) {
+ foreach ($variable['values'] as $index2 => $value) {
+ if ($variantValueId == $value['variant_value_id']) {
+ $items[$index1] = (string) $index2;
+ }
+ }
+ }
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * 生成商品分类关联
+ *
+ * @param $ocProduct
+ * @return array
+ */
+ private function generateCategories($ocProduct): array
+ {
+ $ocProductCategories = $this->ocProductCategories[$ocProduct->product_id] ?? [];
+ if ($ocProductCategories) {
+ return $ocProductCategories->pluck('category_id')->toArray();
+ }
+
+ return [];
+ }
+
+ /**
+ * 清空产品相关数据
+ */
+ private function clearData()
+ {
+ Product::query()->truncate();
+ ProductSku::query()->truncate();
+ ProductDescription::query()->truncate();
+ }
+}
diff --git a/beike/Helpers.php b/beike/Helpers.php
new file mode 100644
index 00000000..6fdb9a4d
--- /dev/null
+++ b/beike/Helpers.php
@@ -0,0 +1,720 @@
+ $code, 'path' => $filePath]);
+}
+
+/**
+ * 获取后台管理前缀名称, 默认为 admin
+ */
+function admin_name(): string
+{
+ if ($envAdminName = config('beike.admin_name')) {
+ return Str::snake($envAdminName);
+ } elseif ($settingAdminName = system_setting('base.admin_name')) {
+ return Str::snake($settingAdminName);
+ }
+
+ return 'admin';
+}
+
+/**
+ * 获取后台设置项
+ */
+function load_settings()
+{
+ if (is_installer() || ! file_exists(__DIR__ . '/../storage/installed')) {
+ return;
+ }
+ $result = \Beike\Repositories\SettingRepo::getGroupedSettings();
+ config(['bk' => $result]);
+}
+
+/**
+ * 获取后台链接
+ *
+ * @param $route
+ * @param mixed $params
+ * @return string
+ */
+function admin_route($route, $params = []): string
+{
+ $adminName = admin_name();
+
+ return route("{$adminName}.{$route}", $params);
+}
+
+/**
+ * 获取前台链接
+ *
+ * @param $route
+ * @param mixed $params
+ * @return string
+ */
+function shop_route($route, $params = []): string
+{
+ return route('shop.' . $route, $params);
+}
+
+/**
+ * 获取 category, product, brand, page, static, custom 路由链接
+ *
+ * @param $type
+ * @param $value
+ * @return string
+ * @throws Exception
+ */
+function type_route($type, $value): string
+{
+ return \Beike\Libraries\Url::getInstance()->link($type, $value);
+}
+
+/**
+ * 获取 category, product, brand, page, static, custom 链接名称
+ *
+ * @param $type
+ * @param $value
+ * @param array $texts
+ * @return string
+ */
+function type_label($type, $value, array $texts = []): string
+{
+ return \Beike\Libraries\Url::getInstance()->label($type, $value, $texts);
+}
+
+/**
+ * 处理配置链接
+ *
+ * @param $link
+ * @return array
+ * @throws Exception
+ */
+function handle_link($link): array
+{
+ $type = $link['type'] ?? '';
+ $value = $link['value'] ?? '';
+ $texts = $link['text'] ?? [];
+
+ $link['link'] = type_route($type, $value);
+ $link['text'] = type_label($type, $value, $texts);
+
+ return $link;
+}
+
+/**
+ * 是否访问的后端
+ * @return bool
+ */
+function is_admin(): bool
+{
+ $adminName = admin_name();
+ $uri = request()->getRequestUri();
+ if (Str::startsWith($uri, "/{$adminName}")) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * 是否访问安装页面
+ * @return bool
+ */
+function is_installer(): bool
+{
+ $uri = request()->getRequestUri();
+
+ return Str::startsWith($uri, '/installer');
+}
+
+/**
+ * 是否为当前访问路由
+ *
+ * @param $routeName
+ * @return bool
+ */
+function equal_route($routeName): bool
+{
+ if (is_array($routeName)) {
+ return in_array(Route::getCurrentRoute()->getName(), $routeName);
+ }
+
+ return $routeName == Route::getCurrentRoute()->getName();
+}
+
+/**
+ * 获取后台当前登录用户
+ *
+ * @return mixed
+ */
+function current_user(): ?AdminUser
+{
+ return auth()->guard(AdminUser::AUTH_GUARD)->user();
+}
+
+/**
+ * 获取前台当前登录客户
+ *
+ * @return mixed
+ */
+function current_customer(): ?Customer
+{
+ return auth()->guard(Customer::AUTH_GUARD)->user();
+}
+
+/**
+ * 获取语言列表
+ *
+ * @return array
+ */
+function locales(): array
+{
+ $locales = LanguageRepo::enabled()->toArray();
+
+ return array_map(function ($item) {
+ return [
+ 'name' => $item['name'],
+ 'code' => $item['code'],
+ ];
+ }, $locales);
+}
+
+/**
+ * 获取当前语言
+ *
+ * @return string
+ */
+function locale(): string
+{
+ if (is_admin()) {
+ $locales = collect(locales())->pluck('code');
+ $userLocale = current_user()->locale;
+
+ return ($locales->contains($userLocale)) ? $userLocale : 'en';
+ }
+
+ return Session::get('locale') ?? system_setting('base.locale');
+}
+
+/**
+ * 获取后台当前语言
+ *
+ * @return string
+ */
+function admin_locale(): string
+{
+ if (is_admin()) {
+ return current_user()->locale;
+ }
+
+ return locale();
+}
+
+/**
+ * 货币格式化
+ *
+ * @param $price
+ * @param string $currency
+ * @param string $value
+ * @param bool $format
+ * @return string
+ */
+function currency_format($price, string $currency = '', string $value = '', bool $format = true): string
+{
+ if (! $currency) {
+ $currency = is_admin() ? system_setting('base.currency') : current_currency_code();
+ }
+
+ return CurrencyService::getInstance()->format($price, $currency, $value, $format);
+}
+
+/**
+ * 获取指定货币汇率
+ *
+ * @return string
+ */
+function current_currency_rate(): float
+{
+ $currency = current_currency_code();
+
+ return Currency::query()->where('code', $currency)->value('value') ?? 1;
+}
+
+/**
+ * 时间格式化
+ *
+ * @param null $datetime
+ * @return false|string
+ */
+function time_format($datetime = null)
+{
+ $format = 'Y-m-d H:i:s';
+ if ($datetime instanceof Illuminate\Support\Carbon) {
+ return $datetime->format($format);
+ } elseif (is_int($datetime)) {
+ return date($format, $datetime);
+ }
+
+ return date($format);
+}
+
+/**
+ * 获取插件根目录
+ *
+ * @param string $path
+ * @return string
+ */
+function plugin_path(string $path = ''): string
+{
+ return base_path('plugins') . ($path ? DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR) : $path);
+}
+
+/**
+ * @param $code
+ * @return Plugin|null
+ */
+function plugin($code): ?Plugin
+{
+ return app('plugin')->getPlugin($code);
+}
+
+/**
+ * 插件图片缩放
+ *
+ * @param $pluginCode
+ * @param $image
+ * @param int $width
+ * @param int $height
+ * @return mixed|void
+ * @throws Exception
+ */
+function plugin_resize($pluginCode, $image, int $width = 100, int $height = 100)
+{
+ if (Str::startsWith($image, 'http')) {
+ return $image;
+ }
+
+ $plugin = plugin($pluginCode);
+ $pluginDirName = $plugin->getDirname();
+
+ return (new \Beike\Services\ImageService($image))->setPluginDirName($pluginDirName)->resize($width, $height);
+}
+
+/**
+ * Get origin image from plugin
+ *
+ * @param $pluginCode
+ * @param $image
+ * @return mixed|void
+ * @throws Exception
+ */
+function plugin_origin($pluginCode, $image)
+{
+ if (Str::startsWith($image, 'http')) {
+ return $image;
+ }
+
+ $plugin = plugin($pluginCode);
+ $pluginDirName = $plugin->getDirname();
+
+ return (new \Beike\Services\ImageService($image))->setPluginDirName($pluginDirName)->originUrl();
+}
+
+/**
+ * 图片缩放
+ *
+ * @param $image
+ * @param int $width
+ * @param int $height
+ * @return mixed|void
+ * @throws Exception
+ */
+function image_resize($image, int $width = 100, int $height = 100)
+{
+ if (Str::startsWith($image, 'http')) {
+ return $image;
+ }
+
+ return (new \Beike\Services\ImageService($image))->resize($width, $height);
+}
+
+/**
+ * 获取原图地址
+ * @throws Exception
+ */
+function image_origin($image)
+{
+ if (Str::startsWith($image, 'http')) {
+ return $image;
+ }
+
+ return (new \Beike\Services\ImageService($image))->originUrl();
+}
+
+/**
+ * 获取后台开启的所有语言
+ *
+ * @return Collection
+ */
+function languages(): Collection
+{
+ return LanguageRepo::enabled()->pluck('code');
+}
+
+/**
+ * 当前语言名称
+ *
+ * @return string
+ */
+function current_language()
+{
+ $code = locale();
+
+ return Language::query()->where('code', $code)->first();
+}
+
+/**
+ * 获取后台所有语言包列表
+ *
+ * @return array
+ */
+function admin_languages(): array
+{
+ $packages = language_packages();
+ $adminLanguages = collect($packages)->filter(function ($package) {
+ return file_exists(resource_path("lang/{$package}/admin"));
+ })->toArray();
+
+ return array_values($adminLanguages);
+}
+
+/**
+ * 获取语言包列表
+ * @return array
+ */
+function language_packages(): array
+{
+ $languageDir = resource_path('lang');
+
+ return array_values(array_diff(scandir($languageDir), ['..', '.']));
+}
+
+/**
+ * @return Builder[]|\Illuminate\Database\Eloquent\Collection
+ */
+function currencies()
+{
+ return CurrencyRepo::listEnabled();
+}
+
+/**
+ * 获取当前货币
+ *
+ * @return string
+ */
+function current_currency_code(): string
+{
+ return Session::get('currency') ?? system_setting('base.currency');
+}
+
+/**
+ * 获取当前货币
+ *
+ * @return string
+ */
+function current_currency_id(): string
+{
+ $currencyCode = current_currency_code();
+ $currency = \Beike\Models\Currency::query()->where('code', $currencyCode)->first();
+
+ return $currency->id ?? 0;
+}
+
+/**
+ * 数量格式化, 用于商品、订单统计
+ *
+ * @param $quantity
+ * @return mixed|string
+ */
+function quantity_format($quantity)
+{
+ if ($quantity > 1000000000000) {
+ return round($quantity / 1000000000000, 1) . 'T';
+ } elseif ($quantity > 1000000000) {
+ return round($quantity / 1000000000, 1) . 'B';
+ } elseif ($quantity > 1000000) {
+ return round($quantity / 1000000, 1) . 'M';
+ } elseif ($quantity > 1000) {
+ return round($quantity / 1000, 1) . 'K';
+ }
+
+ return $quantity;
+
+}
+
+/**
+ * 返回json序列化结果
+ */
+function json_success($message, $data = []): array
+{
+ return [
+ 'status' => 'success',
+ 'message' => $message,
+ 'data' => $data,
+ ];
+}
+
+/**
+ * 返回json序列化结果
+ */
+function json_fail($message, $data = []): array
+{
+ return [
+ 'status' => 'fail',
+ 'message' => $message,
+ 'data' => $data,
+ ];
+}
+
+if (! function_exists('sub_string')) {
+ /**
+ * @param $string
+ * @param int $length
+ * @param string $dot
+ * @return string
+ */
+ function sub_string($string, int $length = 16, string $dot = '...'): string
+ {
+ $strLength = mb_strlen($string);
+ if ($length <= 0) {
+ return $string;
+ } elseif ($strLength <= $length) {
+ return $string;
+ }
+
+ return mb_substr($string, 0, $length) . $dot;
+ }
+}
+
+/**
+ * 根据 $builder 对象输出SQL语句
+ * @param mixed $builder
+ * @return string|string[]|null
+ */
+function to_sql($builder): array|string|null
+{
+ $sql = $builder->toSql();
+ foreach ($builder->getBindings() as $binding) {
+ $value = is_numeric($binding) ? $binding : "'" . $binding . "'";
+ $sql = preg_replace('/\?/', $value, $sql, 1);
+ }
+
+ return $sql;
+}
+
+/**
+ * 递归创建文件夹
+ * @param $directoryPath
+ */
+function create_directories($directoryPath)
+{
+ $path = '';
+ $directories = explode('/', $directoryPath);
+ foreach ($directories as $directory) {
+ $path = $path . '/' . $directory;
+ if (! is_dir(public_path($path))) {
+ @mkdir(public_path($path), 0755);
+ }
+ }
+}
+
+/**
+ * 是否安装 debugbar
+ *
+ * @return bool
+ */
+function has_debugbar(): bool
+{
+ return class_exists(\Barryvdh\Debugbar\Facades\Debugbar::class);
+}
+
+/**
+ * PHP 代码 hook filter 埋点
+ *
+ * @param $hookKey
+ * @param $hookValue
+ * @return mixed
+ */
+function hook_filter($hookKey, $hookValue): mixed
+{
+ if (config('app.debug') && has_debugbar()) {
+ Debugbar::log('HOOK === hook_filter: ' . $hookKey);
+ }
+
+ return Eventy::filter($hookKey, $hookValue);
+}
+
+/**
+ * PHP 代码 hook action 埋点
+ *
+ * @param $hookKey
+ * @param $hookValue
+ */
+function hook_action($hookKey, $hookValue)
+{
+ if (config('app.debug') && has_debugbar()) {
+ Debugbar::log('HOOK === hook_action: ' . $hookKey);
+ }
+ Eventy::action($hookKey, $hookValue);
+}
+
+/**
+ * 添加 Filter, 执行 PHP 逻辑
+ *
+ * @param $hookKey
+ * @param $callback
+ * @param int $priority
+ * @param int $arguments
+ * @return mixed
+ */
+function add_hook_filter($hookKey, $callback, int $priority = 20, int $arguments = 1): mixed
+{
+ return Eventy::addFilter($hookKey, $callback, $priority, $arguments);
+}
+
+/**
+ * 添加 Action, 执行 PHP 逻辑
+ *
+ * @param $hookKey
+ * @param $callback
+ * @param int $priority
+ * @param int $arguments
+ */
+function add_hook_action($hookKey, $callback, int $priority = 20, int $arguments = 1)
+{
+ Eventy::addAction($hookKey, $callback, $priority, $arguments);
+}
+
+/**
+ * 采用 Hook 修改 Blade 代码
+ *
+ * @param $hookKey
+ * @param $callback
+ * @param int $priority
+ */
+function add_hook_blade($hookKey, $callback, int $priority = 0)
+{
+ Hook::listen($hookKey, $callback, $priority);
+}
+
+/**
+ * 检测系统是否已安装
+ *
+ * @return bool
+ */
+function installed(): bool
+{
+ return file_exists(storage_path('installed'));
+}
+
+/**
+ * 是否为移动端访问
+ *
+ * @return bool
+ */
+function is_mobile(): bool
+{
+ return (new \Jenssegers\Agent\Agent())->isMobile();
+}
+
+/**
+ * 当前访问协议是否为 https
+ *
+ * @return bool
+ */
+function is_secure(): bool
+{
+ if (! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') {
+ return true;
+ } elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') {
+ return true;
+ } elseif (! empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off') {
+ return true;
+ } elseif (isset($_SERVER['SERVER_PORT']) && intval($_SERVER['SERVER_PORT']) === 443) {
+ return true;
+ } elseif (isset($_SERVER['REQUEST_SCHEME']) && strtolower($_SERVER['REQUEST_SCHEME']) === 'https') {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * 每页商品显示数量
+ *
+ * @return int
+ */
+function perPage(): int
+{
+ return (int) system_setting('base.product_per_page', 20);
+}
diff --git a/beike/Hook/Callback.php b/beike/Hook/Callback.php
new file mode 100644
index 00000000..693e022b
--- /dev/null
+++ b/beike/Hook/Callback.php
@@ -0,0 +1,37 @@
+setCallback($function, $parameters);
+ }
+
+ public function setCallback($function, $parameters)
+ {
+ $this->function = $function;
+ $this->parameters = $parameters;
+ }
+
+ public function call($parameters = null)
+ {
+ if ($this->run) {
+ $this->run = false;
+
+ return call_user_func_array($this->function, ($parameters ?: $this->parameters));
+ }
+ }
+
+ public function reset()
+ {
+ $this->run = true;
+ }
+}
diff --git a/beike/Hook/Console/HookListeners.php b/beike/Hook/Console/HookListeners.php
new file mode 100644
index 00000000..3535f4e7
--- /dev/null
+++ b/beike/Hook/Console/HookListeners.php
@@ -0,0 +1,48 @@
+ $lister) {
+ foreach ($lister as $key => $element) {
+ $array[] = [
+ $key,
+ $hook,
+ $element['caller']['class'],
+ ];
+ }
+ }
+
+ $headers = ['Sort', 'Hook name', 'Listener class'];
+
+ $this->table($headers, $array);
+ }
+}
diff --git a/beike/Hook/Facades/Hook.php b/beike/Hook/Facades/Hook.php
new file mode 100644
index 00000000..1bc22cf9
--- /dev/null
+++ b/beike/Hook/Facades/Hook.php
@@ -0,0 +1,13 @@
+createCallbackObject($callback, $params);
+
+ $output = $this->returnMockIfDebugModeAndMockExists($hook);
+ if ($output) {
+ return $output;
+ }
+
+ $output = $this->run($hook, $params, $callbackObject, $htmlContent);
+
+ if (! $output) {
+ $output = $callbackObject->call();
+ }
+
+ unset($callbackObject);
+
+ return $output;
+ }
+
+ /**
+ * @param string $hook
+ * @param array $params
+ * @param callable|null $callback
+ * @param string $htmlContent
+ * @return string|void|null
+ */
+ public function getHook(string $hook, array $params = [], callable $callback = null, string $htmlContent = '')
+ {
+ if (config('app.debug') && has_debugbar()) {
+ Debugbar::log("HOOK === @hook: {$hook}");
+ }
+
+ return $this->get($hook, $params, $callback, $htmlContent);
+ }
+
+
+ /**
+ * @param string $hook
+ * @param array $params
+ * @param callable|null $callback
+ * @param string $htmlContent
+ * @return string|void|null
+ */
+ public function getWrapper(string $hook, array $params = [], callable $callback = null, string $htmlContent = '')
+ {
+ if (config('app.debug') && has_debugbar()) {
+ Debugbar::log("HOOK === @hookwrapper: {$hook}");
+ }
+
+ return $this->get($hook, $params, $callback, $htmlContent);
+ }
+
+ /**
+ * Stop all another hook running.
+ *
+ * @param string $hook Hook name
+ */
+ public function stop(string $hook)
+ {
+ $this->stop[$hook] = true;
+ }
+
+ /**
+ * Subscribe to hook.
+ *
+ * @param string $hook Hook name
+ * @param $priority
+ * @param $function
+ */
+ public function listen(string $hook, $function, $priority = null)
+ {
+ $caller = debug_backtrace(null, 3)[2];
+
+ if (in_array(Arr::get($caller, 'function'), ['include', 'require'])) {
+ $caller = debug_backtrace(null, 4)[3];
+ }
+
+ if (empty($this->watch[$hook])) {
+ $this->watch[$hook] = [];
+ }
+
+ if (! is_numeric($priority)) {
+ $priority = null;
+ }
+
+ $this->watch[$hook][$priority] = [
+ 'function' => $function,
+ 'caller' => [
+ //'file' => $caller['file'],
+ //'line' => $caller['line'],
+ 'class' => Arr::get($caller, 'class'),
+ ],
+ ];
+
+ ksort($this->watch[$hook]);
+ }
+
+ /**
+ * Return all registered hooks.
+ *
+ * @return array
+ */
+ public function getHooks(): array
+ {
+ $hookNames = (array_keys($this->watch));
+ ksort($hookNames);
+
+ return $hookNames;
+ }
+
+ /**
+ * Return all listeners for hook.
+ *
+ * @param string $hook
+ *
+ * @return array
+ */
+ public function getEvents(string $hook): array
+ {
+ $output = [];
+
+ foreach ($this->watch[$hook] as $key => $value) {
+ $output[$key] = $value['caller'];
+ }
+
+ return $output;
+ }
+
+ /**
+ * For testing.
+ *
+ * @param string $name Hook name
+ * @param mixed $return Answer
+ */
+ public function mock(string $name, mixed $return)
+ {
+ $this->testing = true;
+ $this->mock[$name] = ['return' => $return];
+ }
+
+ /**
+ * Return the mock value.
+ *
+ * @param string $hook Hook name
+ */
+ protected function returnMockIfDebugModeAndMockExists(string $hook)
+ {
+ if ($this->testing) {
+ if (array_key_exists($hook, $this->mock)) {
+ $output = $this->mock[$hook]['return'];
+ unset($this->mock[$hook]);
+
+ return $output;
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Return a new callback object.
+ *
+ * @param callable $callback function
+ * @param array $params parameters
+ *
+ * @return Callback
+ */
+ protected function createCallbackObject(callable $callback, array $params): Callback
+ {
+ return new Callback($callback, $params);
+ }
+
+ /**
+ * Run hook events.
+ *
+ * @param string $hook Hook name
+ * @param array $params Parameters
+ * @param Callback $callback Callback object
+ * @param string|null $output html wrapped by hook
+ *
+ * @return mixed
+ */
+ protected function run(string $hook, array $params, Callback $callback, string $output = null): mixed
+ {
+ array_unshift($params, $output);
+ array_unshift($params, $callback);
+
+ if (array_key_exists($hook, $this->watch)) {
+ if (is_array($this->watch[$hook])) {
+ foreach ($this->watch[$hook] as $function) {
+ if (! empty($this->stop[$hook])) {
+ unset($this->stop[$hook]);
+
+ break;
+ }
+
+ $output = call_user_func_array($function['function'], $params);
+ $params[1] = $output;
+ }
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Return the listeners.
+ *
+ * @return array
+ */
+ public function getListeners(): array
+ {
+ return $this->watch;
+ }
+}
diff --git a/beike/Hook/HookServiceProvider.php b/beike/Hook/HookServiceProvider.php
new file mode 100644
index 00000000..9c7297cf
--- /dev/null
+++ b/beike/Hook/HookServiceProvider.php
@@ -0,0 +1,89 @@
+commands([
+ HookListeners::class,
+ ]);
+
+ $this->app->singleton('Hook', function () {
+ return new Hook();
+ });
+ }
+
+
+ public function boot()
+ {
+ $this->bootHookDirectives();
+ $this->bootWrapperHookDirectives();
+ }
+
+ /**
+ * 添加 blade hook 标签, 不需要 @endhook
+ * @hook('xxx'), 添加 hook 直接输出到页面
+ */
+ protected function bootHookDirectives()
+ {
+ Blade::directive('hook', function ($parameter) {
+ $parameter = trim($parameter, '()');
+ $parameters = explode(',', $parameter);
+
+ $name = trim($parameters[0], "'");
+
+ return ' $__definedVars],function($data) { return null; });
+ if ($output)
+ echo $output;
+ ?>';
+ });
+ }
+
+
+ /**
+ * 添加 blade wrapper hook 标签
+ *
+ * @hookwrapper('xxx') --- @endhookwrapper, 将某段代码打包输出再添加 hook 输出
+ */
+ protected function bootWrapperHookDirectives()
+ {
+ Blade::directive('hookwrapper', function ($parameter) {
+ $parameter = trim($parameter, '()');
+ $parameters = explode(',', $parameter);
+ $name = trim($parameters[0], "'");
+
+ return ' ';
+ });
+
+ Blade::directive('endhookwrapper', function () {
+ return ' $__definedVars],function($data) { return null; },$__hook_content);
+ unset($__hook_name);
+ unset($__hook_content);
+ if ($output)
+ echo $output;
+ ?>';
+ });
+ }
+}
diff --git a/beike/Installer/Controllers/DatabaseController.php b/beike/Installer/Controllers/DatabaseController.php
new file mode 100644
index 00000000..0b1a2422
--- /dev/null
+++ b/beike/Installer/Controllers/DatabaseController.php
@@ -0,0 +1,68 @@
+databaseManager = $databaseManager;
+ }
+
+ /**
+ * Migrate and seed the database.
+ *
+ * @return RedirectResponse
+ */
+ public function index()
+ {
+ DB::statement('SET FOREIGN_KEY_CHECKS = 0');
+ $rows = DB::select('SHOW TABLES');
+ $database = config('database.connections.mysql.database');
+ $tables = array_column($rows, 'Tables_in_' . $database);
+ foreach ($tables as $table) {
+ Schema::drop($table);
+ }
+ DB::statement('SET FOREIGN_KEY_CHECKS = 1');
+
+ $params = request()->all();
+
+ try {
+ $response = $this->databaseManager->migrateAndSeed();
+ $status = $response['status'] ?? '';
+ $message = $response['message'] ?? '';
+ if ($status == 'error' && $message) {
+ return redirect()->route('installer.environment')->withInput($params)->withErrors(['error' => $message]);
+ }
+ } catch (\Exception $e) {
+ return redirect()->route('installer.environment')->withInput($params)->withErrors(['error' => $e->getMessage()]);
+ }
+
+ $email = request('admin_email');
+ $data = [
+ 'name' => substr($email, 0, strpos($email, '@')),
+ 'email' => $email,
+ 'password' => request('admin_password'),
+ 'locale' => session('locale') ?? 'zh_cn',
+ 'active' => true,
+ ];
+ AdminUserRepo::createAdminUser($data);
+
+ return redirect()->route('installer.final', request()->only('admin_email', 'admin_password'))->with(['message' => $response]);
+ }
+}
diff --git a/beike/Installer/Controllers/EnvironmentController.php b/beike/Installer/Controllers/EnvironmentController.php
new file mode 100644
index 00000000..c96f6192
--- /dev/null
+++ b/beike/Installer/Controllers/EnvironmentController.php
@@ -0,0 +1,175 @@
+EnvironmentManager = $environmentManager;
+ }
+
+ /**
+ * Display the Environment page.
+ *
+ * @return \Illuminate\View\View
+ */
+ public function index()
+ {
+ $steps = 4;
+
+ return view('installer::environment-wizard', compact('steps'));
+ }
+
+ /**
+ * Processes the newly saved environment configuration (Form Wizard).
+ *
+ * @param Request $request
+ * @param Redirector $redirect
+ * @return RedirectResponse
+ */
+ public function saveWizard(Request $request, Redirector $redirect): RedirectResponse
+ {
+ $rules = config('installer.environment.form.rules');
+ $messages = [
+ 'environment_custom.required_if' => trans('installer::installer_messages.environment.name_required'),
+ ];
+
+ $validator = Validator::make($request->all(), $rules, $messages);
+
+ if ($validator->fails()) {
+ return $redirect->route('installer.environment')->withInput()->withErrors($validator->errors());
+ }
+
+ if ($this->checkDatabaseConnection($request) !== true) {
+ return $redirect->route('installer.environment')->withInput()->withErrors([
+ 'database_connection' => trans('installer::installer_messages.environment.db_connection_failed'),
+ ]);
+ }
+
+ $this->EnvironmentManager->saveFileWizard($request);
+
+ $params = $request->all();
+
+ return redirect(route('installer.database', $params));
+ }
+
+ /**
+ * 数据库信息检测
+ *
+ * @param Request $request
+ * @return array
+ */
+ public function validateDatabase(Request $request): array
+ {
+ $rules = config('installer.environment.form.rules');
+ $messages = [
+ 'environment_custom.required_if' => trans('installer::installer_messages.environment.name_required'),
+ ];
+
+ unset($rules['admin_email'], $rules['admin_password']);
+
+ $validator = Validator::make($request->all(), $rules, $messages);
+
+ if ($validator->fails()) {
+ return json_fail('', $validator->errors());
+ }
+
+ $dbValidateResult = $this->checkDatabaseConnection($request);
+ if ($dbValidateResult !== true) {
+ return json_fail('', $dbValidateResult);
+ }
+
+ return json_success('');
+ }
+
+ /**
+ * TODO: We can remove this code if PR will be merged: https://github.com/RachidLaasri/LaravelInstaller/pull/162
+ * Validate database connection with user credentials (Form Wizard).
+ *
+ * @param Request $request
+ * @return array|bool
+ */
+ private function checkDatabaseConnection(Request $request): bool|array
+ {
+ $connection = $request->input('database_connection');
+
+ $settings = config("database.connections.$connection");
+
+ config([
+ 'database' => [
+ 'default' => $connection,
+ 'connections' => [
+ $connection => array_merge($settings, [
+ 'driver' => $connection,
+ 'host' => $request->input('database_hostname'),
+ 'port' => $request->input('database_port'),
+ 'database' => $request->input('database_name'),
+ 'username' => $request->input('database_username'),
+ 'password' => $request->input('database_password'),
+ 'options' => [
+ \PDO::ATTR_TIMEOUT => 1,
+ ],
+ ]),
+ ],
+ ],
+ ]);
+
+ DB::purge();
+
+ $result = [];
+
+ try {
+ $pdo = DB::connection()->getPdo();
+ $serverVersion = $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION);
+ if (version_compare($serverVersion, '5.7', '<')) {
+ $result['database_version'] = trans('installer::installer_messages.environment.db_connection_failed_invalid_version');
+
+ return $result;
+ }
+
+ return true;
+ } catch (\PDOException $e) {
+ switch ($e->getCode()) {
+ case 1115:
+ $result['database_version'] = trans('installer::installer_messages.environment.db_connection_failed_invalid_version');
+
+ break;
+ case 2002:
+ $result['database_hostname'] = trans('installer::installer_messages.environment.db_connection_failed_host_port');
+ $result['database_port'] = trans('installer::installer_messages.environment.db_connection_failed_host_port');
+
+ break;
+ case 1045:
+ $result['database_username'] = trans('installer::installer_messages.environment.db_connection_failed_user_password');
+ $result['database_password'] = trans('installer::installer_messages.environment.db_connection_failed_user_password');
+
+ break;
+ case 1049:
+ $result['database_name'] = trans('installer::installer_messages.environment.db_connection_failed_database_name');
+
+ break;
+ default:
+ $result['database_other'] = $e->getMessage();
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/beike/Installer/Controllers/FinalController.php b/beike/Installer/Controllers/FinalController.php
new file mode 100644
index 00000000..4fb40035
--- /dev/null
+++ b/beike/Installer/Controllers/FinalController.php
@@ -0,0 +1,34 @@
+runFinal();
+ $finalStatusMessage = $fileManager->update();
+ $finalEnvFile = $environment->getEnvContent();
+
+ $steps = 5;
+
+ $data = compact('finalMessages', 'finalStatusMessage', 'finalEnvFile', 'steps');
+ $data['admin_email'] = request('admin_email');
+ $data['admin_password'] = request('admin_password');
+
+ return view('installer::finished', $data);
+ }
+}
diff --git a/beike/Installer/Controllers/PermissionsController.php b/beike/Installer/Controllers/PermissionsController.php
new file mode 100644
index 00000000..2e6769fc
--- /dev/null
+++ b/beike/Installer/Controllers/PermissionsController.php
@@ -0,0 +1,38 @@
+permissions = $checker;
+ }
+
+ /**
+ * Display the permissions check page.
+ *
+ * @return \Illuminate\View\View
+ */
+ public function index()
+ {
+ $permissions = $this->permissions->check(
+ config('installer.permissions')
+ );
+
+ $steps = 3;
+
+ return view('installer::permissions', compact('permissions', 'steps'));
+ }
+}
diff --git a/beike/Installer/Controllers/RequirementsController.php b/beike/Installer/Controllers/RequirementsController.php
new file mode 100644
index 00000000..36a61397
--- /dev/null
+++ b/beike/Installer/Controllers/RequirementsController.php
@@ -0,0 +1,41 @@
+requirements = $checker;
+ }
+
+ /**
+ * Display the requirements page.
+ *
+ * @return \Illuminate\View\View
+ */
+ public function index()
+ {
+ $phpSupportInfo = $this->requirements->checkPHPversion(
+ config('installer.core.minPhpVersion')
+ );
+ $requirements = $this->requirements->check(
+ config('installer.requirements')
+ );
+
+ $steps = 2;
+
+ return view('installer::requirements', compact('requirements', 'phpSupportInfo', 'steps'));
+ }
+}
diff --git a/beike/Installer/Controllers/WelcomeController.php b/beike/Installer/Controllers/WelcomeController.php
new file mode 100644
index 00000000..34fa95f3
--- /dev/null
+++ b/beike/Installer/Controllers/WelcomeController.php
@@ -0,0 +1,43 @@
+
+ * @created 2022-08-12 20:17:04
+ * @modified 2022-08-12 20:17:04
+ */
+
+namespace Beike\Installer\Controllers;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Redirect;
+
+class WelcomeController extends Controller
+{
+ private $languages = [
+ 'zh_cn' => '简体中文',
+ 'en' => 'English',
+ ];
+
+ public function index()
+ {
+ if (installed()) {
+ exit('Already installed');
+ }
+
+ $data['languages'] = $this->languages;
+ $data['locale'] = $_COOKIE['locale'] ?? 'zh_cn';
+ $data['steps'] = 1;
+
+ return view('installer::welcome', $data);
+ }
+
+ public function locale($lang)
+ {
+ setcookie('locale', $lang, 0, '/');
+
+ return Redirect::back();
+ }
+}
diff --git a/beike/Installer/Helpers/DatabaseManager.php b/beike/Installer/Helpers/DatabaseManager.php
new file mode 100644
index 00000000..7c20f2db
--- /dev/null
+++ b/beike/Installer/Helpers/DatabaseManager.php
@@ -0,0 +1,95 @@
+sqlite($outputLog);
+
+ return $this->migrate($outputLog);
+ }
+
+ /**
+ * Run the migration and call the seeder.
+ *
+ * @param \Symfony\Component\Console\Output\BufferedOutput $outputLog
+ * @return array
+ */
+ private function migrate(BufferedOutput $outputLog)
+ {
+ try {
+ Artisan::call('migrate', ['--force' => true], $outputLog);
+ } catch (Exception $e) {
+ return $this->response($e->getMessage(), 'error', $outputLog);
+ }
+
+ return $this->seed($outputLog);
+ }
+
+ /**
+ * Seed the database.
+ *
+ * @param \Symfony\Component\Console\Output\BufferedOutput $outputLog
+ * @return array
+ */
+ private function seed(BufferedOutput $outputLog)
+ {
+ try {
+ Artisan::call('db:seed', ['--force' => true], $outputLog);
+ } catch (Exception $e) {
+ return $this->response($e->getMessage(), 'error', $outputLog);
+ }
+
+ return $this->response(trans('installer::installer_messages.final.finished'), 'success', $outputLog);
+ }
+
+ /**
+ * Return a formatted error messages.
+ *
+ * @param string $message
+ * @param string $status
+ * @param \Symfony\Component\Console\Output\BufferedOutput $outputLog
+ * @return array
+ */
+ private function response($message, $status, BufferedOutput $outputLog)
+ {
+ return [
+ 'status' => $status,
+ 'message' => $message,
+ 'dbOutputLog' => $outputLog->fetch(),
+ ];
+ }
+
+ /**
+ * Check database type. If SQLite, then create the database file.
+ *
+ * @param \Symfony\Component\Console\Output\BufferedOutput $outputLog
+ */
+ private function sqlite(BufferedOutput $outputLog)
+ {
+ if (DB::connection() instanceof SQLiteConnection) {
+ $database = DB::connection()->getDatabaseName();
+ if (! file_exists($database)) {
+ touch($database);
+ DB::reconnect(Config::get('database.default'));
+ }
+ $outputLog->write('Using SqlLite database: ' . $database, 1);
+ }
+ }
+}
diff --git a/beike/Installer/Helpers/EnvironmentManager.php b/beike/Installer/Helpers/EnvironmentManager.php
new file mode 100644
index 00000000..d3405859
--- /dev/null
+++ b/beike/Installer/Helpers/EnvironmentManager.php
@@ -0,0 +1,133 @@
+envPath = base_path('.env');
+ $this->envExamplePath = base_path('.env.example');
+ }
+
+ /**
+ * Get the content of the .env file.
+ *
+ * @return string
+ */
+ public function getEnvContent()
+ {
+ if (! file_exists($this->envPath)) {
+ if (file_exists($this->envExamplePath)) {
+ copy($this->envExamplePath, $this->envPath);
+ } else {
+ touch($this->envPath);
+ }
+ }
+
+ return file_get_contents($this->envPath);
+ }
+
+ /**
+ * Get the the .env file path.
+ *
+ * @return string
+ */
+ public function getEnvPath()
+ {
+ return $this->envPath;
+ }
+
+ /**
+ * Get the the .env.example file path.
+ *
+ * @return string
+ */
+ public function getEnvExamplePath()
+ {
+ return $this->envExamplePath;
+ }
+
+ /**
+ * Save the edited content to the .env file.
+ *
+ * @param Request $input
+ * @return string
+ */
+ public function saveFileClassic(Request $input)
+ {
+ $message = trans('installer::installer_messages.environment.success');
+
+ try {
+ file_put_contents($this->envPath, $input->get('envConfig'));
+ } catch (Exception $e) {
+ $message = trans('installer::installer_messages.environment.errors');
+ }
+
+ return $message;
+ }
+
+ /**
+ * Save the form content to the .env file.
+ *
+ * @param Request $request
+ * @return string
+ */
+ public function saveFileWizard(Request $request)
+ {
+ $results = trans('installer::installer_messages.environment.success');
+
+ $scheme = is_secure() ? 'https' : 'http';
+ $appUrl = $scheme . '://' . $_SERVER['HTTP_HOST'];
+
+ $envFileData =
+ 'APP_NAME=\'' . ($request->app_name ?: 'BeikeShop') . "'\n" .
+ 'APP_ENV=' . $request->environment . "\n" .
+ 'APP_KEY=' . 'base64:' . base64_encode(Str::random(32)) . "\n" .
+ 'APP_DEBUG=false' . "\n" .
+ 'APP_LOG_LEVEL=' . $request->app_log_level . "\n" .
+ 'APP_URL=' . $appUrl . "\n\n" .
+ 'BEIKE_API_URL=https://beikeshop.com' . "\n\n" .
+ 'DB_CONNECTION=' . $request->database_connection . "\n" .
+ 'DB_HOST=' . $request->database_hostname . "\n" .
+ 'DB_PORT=' . $request->database_port . "\n" .
+ 'DB_DATABASE=' . $request->database_name . "\n" .
+ 'DB_USERNAME=' . $request->database_username . "\n" .
+ 'DB_PASSWORD=\'' . $request->database_password . "'\n\n" .
+ 'BROADCAST_DRIVER=log' . "\n" .
+ 'CACHE_DRIVER=file' . "\n" .
+ 'SESSION_DRIVER=file' . "\n" .
+ 'QUEUE_CONNECTION=sync' . "\n\n" .
+ 'MAIL_DRIVER=' . $request->mail_driver . "\n" .
+ 'MAIL_HOST=' . $request->mail_host . "\n" .
+ 'MAIL_PORT=' . $request->mail_port . "\n" .
+ 'MAIL_USERNAME=' . $request->mail_username . "\n" .
+ 'MAIL_PASSWORD=' . $request->mail_password . "\n" .
+ 'MAIL_ENCRYPTION=' . $request->mail_encryption . "\n\n";
+
+ try {
+ file_put_contents($this->envPath, $envFileData);
+ } catch (Exception $e) {
+ $results = trans('installer::installer_messages.environment.errors');
+ }
+
+ return $results;
+ }
+}
diff --git a/beike/Installer/Helpers/FinalInstallManager.php b/beike/Installer/Helpers/FinalInstallManager.php
new file mode 100644
index 00000000..ffcf4716
--- /dev/null
+++ b/beike/Installer/Helpers/FinalInstallManager.php
@@ -0,0 +1,79 @@
+generateKey($outputLog);
+ $this->publishVendorAssets($outputLog);
+
+ return $outputLog->fetch();
+ }
+
+ /**
+ * Generate New Application Key.
+ *
+ * @param \Symfony\Component\Console\Output\BufferedOutput $outputLog
+ * @return \Symfony\Component\Console\Output\BufferedOutput|array
+ */
+ private static function generateKey(BufferedOutput $outputLog)
+ {
+ try {
+ if (config('installer.final.key')) {
+ Artisan::call('key:generate', ['--force' => true], $outputLog);
+ }
+ } catch (Exception $e) {
+ return static::response($e->getMessage(), $outputLog);
+ }
+
+ return $outputLog;
+ }
+
+ /**
+ * Publish vendor assets.
+ *
+ * @param \Symfony\Component\Console\Output\BufferedOutput $outputLog
+ * @return \Symfony\Component\Console\Output\BufferedOutput|array
+ */
+ private static function publishVendorAssets(BufferedOutput $outputLog)
+ {
+ try {
+ if (config('installer.final.publish')) {
+ Artisan::call('vendor:publish', ['--all' => true], $outputLog);
+ }
+ } catch (Exception $e) {
+ return static::response($e->getMessage(), $outputLog);
+ }
+
+ return $outputLog;
+ }
+
+ /**
+ * Return a formatted error messages.
+ *
+ * @param $message
+ * @param \Symfony\Component\Console\Output\BufferedOutput $outputLog
+ * @return array
+ */
+ private static function response($message, BufferedOutput $outputLog)
+ {
+ return [
+ 'status' => 'error',
+ 'message' => $message,
+ 'dbOutputLog' => $outputLog->fetch(),
+ ];
+ }
+}
diff --git a/beike/Installer/Helpers/InstalledFileManager.php b/beike/Installer/Helpers/InstalledFileManager.php
new file mode 100644
index 00000000..68795d8c
--- /dev/null
+++ b/beike/Installer/Helpers/InstalledFileManager.php
@@ -0,0 +1,40 @@
+create();
+ }
+}
diff --git a/beike/Installer/Helpers/MigrationsHelper.php b/beike/Installer/Helpers/MigrationsHelper.php
new file mode 100644
index 00000000..4ed717b5
--- /dev/null
+++ b/beike/Installer/Helpers/MigrationsHelper.php
@@ -0,0 +1,31 @@
+get()->pluck('migration');
+ }
+}
diff --git a/beike/Installer/Helpers/PermissionsChecker.php b/beike/Installer/Helpers/PermissionsChecker.php
new file mode 100644
index 00000000..dd064d6b
--- /dev/null
+++ b/beike/Installer/Helpers/PermissionsChecker.php
@@ -0,0 +1,83 @@
+results['permissions'] = [];
+
+ $this->results['errors'] = null;
+ }
+
+ /**
+ * Check for the folders permissions.
+ *
+ * @param array $folders
+ * @return array
+ */
+ public function check(array $folders)
+ {
+ foreach ($folders as $folder => $permission) {
+ if (! ($this->getPermission($folder) >= $permission) && php_uname('s') != 'Windows NT') {
+ $this->addFileAndSetErrors($folder, $permission, false);
+ } else {
+ $this->addFile($folder, $permission, true);
+ }
+ }
+
+ return $this->results;
+ }
+
+ /**
+ * Get a folder permission.
+ *
+ * @param $folder
+ * @return string
+ */
+ private function getPermission($folder)
+ {
+ return substr(sprintf('%o', fileperms(base_path($folder))), -4);
+ }
+
+ /**
+ * Add the file to the list of results.
+ *
+ * @param $folder
+ * @param $permission
+ * @param $isSet
+ */
+ private function addFile($folder, $permission, $isSet)
+ {
+ array_push($this->results['permissions'], [
+ 'folder' => $folder,
+ 'permission' => $permission,
+ 'isSet' => $isSet,
+ ]);
+ }
+
+ /**
+ * Add the file and set the errors.
+ *
+ * @param $folder
+ * @param $permission
+ * @param $isSet
+ */
+ private function addFileAndSetErrors($folder, $permission, $isSet)
+ {
+ $this->addFile($folder, $permission, $isSet);
+
+ $this->results['errors'] = true;
+ }
+}
diff --git a/beike/Installer/Helpers/RequirementsChecker.php b/beike/Installer/Helpers/RequirementsChecker.php
new file mode 100644
index 00000000..0a15ebfc
--- /dev/null
+++ b/beike/Installer/Helpers/RequirementsChecker.php
@@ -0,0 +1,116 @@
+ $requirement) {
+ switch ($type) {
+ // check php requirements
+ case 'php':
+ foreach ($requirements[$type] as $requirement) {
+ $results['requirements'][$type][$requirement] = true;
+
+ if (! extension_loaded($requirement)) {
+ $results['requirements'][$type][$requirement] = false;
+
+ $results['errors'] = true;
+ }
+ }
+
+ break;
+ // check apache requirements
+ case 'apache':
+ foreach ($requirements[$type] as $requirement) {
+ // if function doesn't exist we can't check apache modules
+ if (function_exists('apache_get_modules')) {
+ $results['requirements'][$type][$requirement] = true;
+
+ if (! in_array($requirement, apache_get_modules())) {
+ $results['requirements'][$type][$requirement] = false;
+
+ $results['errors'] = true;
+ }
+ }
+ }
+
+ break;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Check PHP version requirement.
+ *
+ * @return array
+ */
+ public function checkPHPversion(string $minPhpVersion = null)
+ {
+ $minVersionPhp = $minPhpVersion;
+ $currentPhpVersion = $this->getPhpVersionInfo();
+ $supported = false;
+
+ if ($minPhpVersion == null) {
+ $minVersionPhp = $this->getMinPhpVersion();
+ }
+
+ if (version_compare($currentPhpVersion['version'], $minVersionPhp) >= 0) {
+ $supported = true;
+ }
+
+ $phpStatus = [
+ 'full' => $currentPhpVersion['full'],
+ 'current' => $currentPhpVersion['version'],
+ 'minimum' => $minVersionPhp,
+ 'supported' => $supported,
+ ];
+
+ return $phpStatus;
+ }
+
+ /**
+ * Get current Php version information.
+ *
+ * @return array
+ */
+ private static function getPhpVersionInfo()
+ {
+ $currentVersionFull = PHP_VERSION;
+ preg_match("#^\d+(\.\d+)*#", $currentVersionFull, $filtered);
+ $currentVersion = $filtered[0];
+
+ return [
+ 'full' => $currentVersionFull,
+ 'version' => $currentVersion,
+ ];
+ }
+
+ /**
+ * Get minimum PHP version ID.
+ *
+ * @return string _minPhpVersion
+ */
+ protected function getMinPhpVersion()
+ {
+ return $this->_minPhpVersion;
+ }
+}
diff --git a/beike/Installer/Helpers/functions.php b/beike/Installer/Helpers/functions.php
new file mode 100644
index 00000000..1f337b6a
--- /dev/null
+++ b/beike/Installer/Helpers/functions.php
@@ -0,0 +1,28 @@
+ 'Laravel Installer',
+ 'next' => 'Next Step',
+ 'back' => 'Previous',
+ 'finish' => 'Install',
+ 'status' => 'Status',
+ 'forms' => [
+ 'errorTitle' => 'The Following errors occurred:',
+ ],
+
+ /*
+ *
+ * Home page translations.
+ *
+ */
+ 'welcome' => [
+ 'template_title' => 'Welcome',
+ 'title' => 'Welcome',
+ 'describe' => 'Welcome to install BeikeShop. Easy Installation and Setup Wizard.',
+ 'message' => 'Easy Installation and Setup Wizard.',
+ 'next' => 'Check Requirements',
+ 'copyright_btn' => 'I have read the agreement and agree',
+ 'copyright_title' => 'Copyright Description',
+ 'copyright_list_1' => '1. The copyright of this system belongs to Chengdu GuangDa Network Technology Co., Ltd.',
+ 'copyright_list_2' => '2. Any individual or organization can not be allowed to sell or lease this system and its derivatives without our explicit written authorization.',
+ 'copyright_list_3' => '3. Please keep the copyright information, and please contact us if you wanna remove it.',
+ ],
+
+ /*
+ *
+ * Requirements page translations.
+ *
+ */
+ 'requirements' => [
+ 'template_title' => 'Step 1 | Server Requirements',
+ 'environment' => 'Environmen',
+ 'title' => 'Server Requirements',
+ 'next' => 'Check Permissions',
+ ],
+
+ /*
+ *
+ * Permissions page translations.
+ *
+ */
+ 'permissions' => [
+ 'template_title' => 'Step 2 | Directory Permission',
+ 'title' => 'Directory permission detection',
+ 'next' => 'Configure Environment',
+ 'table' => 'Table of contents',
+ ],
+
+ /*
+ *
+ * Environment page translations.
+ *
+ */
+ 'environment' => [
+ 'template_title' => 'Step 3 | System Parameters',
+ 'title' => 'System parameter configuration',
+ 'name_required' => 'An environment name is required.',
+ 'database_link' => 'Database link',
+ 'admin_info' => 'Set admin account password',
+ 'app_name_label' => 'App Name',
+ 'app_name_placeholder' => 'App Name',
+ 'app_environment_label' => 'App Environment',
+ 'db_connection_failed_host_port' => 'Database host or port error!',
+ 'db_connection_failed_user_password' => 'Database username or password error!',
+ 'db_connection_failed_database_name' => 'Database name not exist!',
+ 'db_connection_failed_invalid_version' => 'MySQL version must grater than 5.7!',
+ 'app_environment_label_local' => 'Local',
+ 'app_environment_label_developement' => 'Development',
+ 'app_environment_label_qa' => 'Qa',
+ 'app_environment_label_production' => 'Production',
+ 'app_environment_label_other' => 'Other',
+ 'app_environment_placeholder_other' => 'Enter your environment...',
+ 'app_url_label' => 'App Url',
+ 'app_url_placeholder' => 'App Url',
+ 'db_connection_failed' => 'Could not connect to the database.',
+ 'db_connection_label' => 'Database Connection',
+ 'db_connection_label_mysql' => 'mysql',
+ 'db_connection_label_sqlite' => 'sqlite',
+ 'db_connection_label_pgsql' => 'pgsql',
+ 'db_connection_label_sqlsrv' => 'sqlsrv',
+ 'db_host_label' => 'Database Host',
+ 'db_host_placeholder' => 'Database Host',
+ 'db_port_label' => 'Database Port',
+ 'db_port_placeholder' => 'Database Port',
+ 'db_name_label' => 'Database Name',
+ 'db_name_placeholder' => 'Database Name',
+ 'db_username_label' => 'Database User Name',
+ 'db_username_placeholder' => 'Database User Name',
+ 'db_password_label' => 'Database Password',
+ 'db_password_placeholder' => 'Database Password',
+ 'admin_email' => 'Admin User Account',
+ 'admin_password' => 'Admin Password',
+ 'install' => 'Install',
+ 'ajax_database_parameters' => 'Check database parameters...',
+ 'ajax_database_success' => 'Database connection succeeded',
+ 'error_email' => 'Please fill in the correct email address',
+ ],
+
+ /*
+ *
+ * Installed Log translations.
+ *
+ */
+ 'installed' => [
+ 'success_log_message' => 'Laravel Installer successfully INSTALLED on ',
+ ],
+
+ /*
+ *
+ * Final page translations.
+ *
+ */
+ 'final' => [
+ 'title' => 'Installation Finished',
+ 'template_title' => 'Installation Finished',
+ 'migration' => 'Migration & Seed Console Output:',
+ 'console' => 'Application Console Output:',
+ 'log' => 'Installation Log Entry:',
+ 'env' => 'Final .env File:',
+ 'exit' => 'Click here to exit',
+ 'finished' => 'Congratulations, the system is successfully installed, let\'s experience it now',
+ 'to_front' => 'Shop',
+ 'to_admin' => 'Admin Panel',
+ ],
+
+ /*
+ *
+ * Update specific translations
+ *
+ */
+ 'updater' => [
+ /*
+ *
+ * Shared translations.
+ *
+ */
+ 'title' => 'Laravel Updater',
+
+ /*
+ *
+ * Welcome page translations for update feature.
+ *
+ */
+ 'welcome' => [
+ 'title' => 'Welcome To The Updater',
+ 'message' => 'Welcome to the update wizard.',
+ ],
+
+ /*
+ *
+ * Welcome page translations for update feature.
+ *
+ */
+ 'overview' => [
+ 'title' => 'Overview',
+ 'message' => 'There is 1 update.|There are :number updates.',
+ 'install_updates' => 'Install Updates',
+ ],
+
+ /*
+ *
+ * Final page translations.
+ *
+ */
+ 'final' => [
+ 'title' => 'Finished',
+ 'finished' => 'Application\'s database has been successfully updated.',
+ 'exit' => 'Click here to exit',
+ ],
+
+ 'log' => [
+ 'success_message' => 'Laravel Installer successfully UPDATED on ',
+ ],
+ ],
+];
diff --git a/beike/Installer/Lang/zh_cn/installer_messages.php b/beike/Installer/Lang/zh_cn/installer_messages.php
new file mode 100644
index 00000000..848659c0
--- /dev/null
+++ b/beike/Installer/Lang/zh_cn/installer_messages.php
@@ -0,0 +1,109 @@
+ 'Laravel安装程序',
+ 'next' => '下一步',
+ 'finish' => '安装',
+ 'status' => '状态',
+
+ /*
+ *
+ * Home page translations.
+ *
+ */
+ 'welcome' => [
+ 'template_title' => '欢迎',
+ 'title' => '欢迎来到安装引导程序',
+ 'describe' => '欢迎使用安装引导,在后面的步骤中我们将检测您的系统环境和安装条件是否达标,请根据每一步中的提示信息操作,谢谢。',
+ 'message' => '欢迎来到安装向导.',
+ 'next' => '检测系统环境',
+ 'copyright_title' => '版权说明',
+ 'copyright_btn' => '已阅读协议并同意',
+ 'copyright_list_1' => '1、本系统版权归属成都光大网络科技有限公司所有。',
+ 'copyright_list_2' => '2、除本公司书面许可外,任何个人、单位、组织不得将本系统及其衍生品作为商品贩卖租赁销售获利。',
+ 'copyright_list_3' => '3、请保留我公司版权信息,如要移除,需要我公司授权。',
+ ],
+
+ /*
+ *
+ * Requirements page translations.
+ *
+ */
+ 'requirements' => [
+ 'template_title' => '第一步 | 服务器环境',
+ 'title' => '系统环境要求检测',
+ 'environment' => '环境',
+ 'next' => '检测权限',
+ ],
+
+ /*
+ *
+ * Permissions page translations.
+ *
+ */
+ 'permissions' => [
+ 'template_title' => '第二步 | 目录权限',
+ 'title' => '目录权限检测',
+ 'table' => '目录',
+ 'next' => '配置环境参数',
+ ],
+
+ /*
+ *
+ * Environment page translations.
+ *
+ */
+ 'environment' => [
+ 'template_title' => '第三步 | 系统参数',
+ 'title' => '系统参数配置',
+ 'app_url_label' => '您的应用URL',
+ 'database_link' => '数据库链接',
+ 'admin_info' => '设置后台账号密码',
+ 'app_url_placeholder' => '输入您的应用URL',
+ 'db_connection_failed' => '无法连接到数据库!',
+ 'db_connection_label' => '数据库类型',
+ 'db_connection_failed_host_port' => '数据库主机或端口错误!',
+ 'db_connection_failed_user_password' => '数据库账号或密码错误!',
+ 'db_connection_failed_database_name' => '数据库名不存在!',
+ 'db_connection_failed_invalid_version' => '数据库版本必须大于5.7!',
+ 'db_connection_label_mysql' => 'MySQL',
+ 'db_connection_label_sqlite' => 'SQLite',
+ 'db_connection_label_pgsql' => 'PostgreSQL',
+ 'db_connection_label_sqlsrv' => 'SQL Server',
+ 'db_host_label' => '数据库主机',
+ 'db_host_placeholder' => '输入数据库主机ip或url',
+ 'db_port_label' => '数据库端口',
+ 'db_port_placeholder' => '输入数据库端口',
+ 'db_name_label' => '数据库名',
+ 'db_name_placeholder' => '输入数据库名',
+ 'db_username_label' => '数据库账号',
+ 'db_username_placeholder' => '输入数据库账号',
+ 'db_password_label' => '数据库账号密码',
+ 'db_password_placeholder' => '输入数据库账号密码',
+ 'admin_email' => '后台账号',
+ 'admin_password' => '后台密码',
+ 'install' => '安装',
+ 'ajax_database_parameters' => '检测数据库参数...',
+ 'ajax_database_success' => '数据库连接成功',
+ 'error_email' => '请填写正确的邮箱地址',
+ ],
+
+ /*
+ *
+ * Final page translations.
+ *
+ */
+ 'final' => [
+ 'template_title' => '安装完成',
+ 'title' => '获取安装结果',
+ 'finished' => '恭喜您,系统安装成功,赶快体验吧',
+ 'to_front' => '访问前台',
+ 'to_admin' => '访问后台',
+ ],
+];
diff --git a/beike/Installer/Providers/InstallerServiceProvider.php b/beike/Installer/Providers/InstallerServiceProvider.php
new file mode 100644
index 00000000..c141812a
--- /dev/null
+++ b/beike/Installer/Providers/InstallerServiceProvider.php
@@ -0,0 +1,30 @@
+loadRoutesFrom(__DIR__ . '/../Routes/installer.php');
+
+ if (! is_installer()) {
+ return;
+ }
+
+ Schema::defaultStringLength(191);
+
+ $this->mergeConfigFrom(__DIR__ . '/../config.php', 'installer');
+ $this->mergeConfigFrom(__DIR__ . '/../../Config/beike.php', 'beike');
+ $this->loadViewsFrom(__DIR__ . '/../Views', 'installer');
+
+ $pathInstaller = base_path('beike/Installer');
+ $this->loadTranslationsFrom("{$pathInstaller}/Lang", 'installer');
+ }
+}
diff --git a/beike/Installer/Routes/installer.php b/beike/Installer/Routes/installer.php
new file mode 100644
index 00000000..59f31cdd
--- /dev/null
+++ b/beike/Installer/Routes/installer.php
@@ -0,0 +1,28 @@
+name('installer.')
+ ->middleware(\App\Http\Middleware\SetLocaleInstaller::class)
+ ->group(function () {
+ Route::get('/', [WelcomeController::class, 'index'])->name('welcome');
+ Route::get('requirements', [RequirementsController::class, 'index'])->name('requirements');
+ Route::get('permissions', [PermissionsController::class, 'index'])->name('permissions');
+ Route::middleware(['installer'])
+ ->group(function () {
+ Route::get('lang/{lang}', [WelcomeController::class, 'locale'])->name('lang.switch');
+ Route::get('environment', [EnvironmentController::class, 'index'])->name('environment');
+ Route::post('environment/save', [EnvironmentController::class, 'saveWizard'])->name('environment.save');
+ Route::post('environment/validate_db', [EnvironmentController::class, 'validateDatabase'])->name('environment.validate_db');
+ Route::get('database', [DatabaseController::class, 'index'])->name('database');
+ Route::get('final', [FinalController::class, 'index'])->name('final');
+ });
+
+ });
diff --git a/beike/Installer/Views/environment-wizard.blade.php b/beike/Installer/Views/environment-wizard.blade.php
new file mode 100644
index 00000000..12f77629
--- /dev/null
+++ b/beike/Installer/Views/environment-wizard.blade.php
@@ -0,0 +1,260 @@
+@extends('installer::layouts.master')
+
+@section('template_title')
+ {{ trans('installer::installer_messages.environment.template_title') }}
+@endsection
+
+@section('title')
+
+ {!! trans('installer::installer_messages.environment.title') !!}
+@endsection
+
+@section('content')
+ @php
+ $entry_key = 'installer::installer_messages.environment.';
+ @endphp
+  }}&build_date={{ config('beike.build') }})
| {{ __('installer::installer_messages.permissions.table') }} | +{{ __('installer::installer_messages.permissions.next') }} | +{{ __('installer::installer_messages.status') }} | +
|---|---|---|
| {{ $permission['folder'] }} | +{{ $permission['permission'] }} | ++ + | +
| {{ trans('installer::installer_messages.requirements.environment') }} | +{{ trans('installer::installer_messages.status') }} | + + + @foreach ($requirements['requirements'] as $type => $requirement) +|
|---|---|---|
| + {{ ucfirst($type) }} + @if ($type == 'php') + (version {{ $phpSupportInfo['minimum'] }} required) + + {{ $phpSupportInfo['current'] }} + + + @endif + | +||
| {{ $extention }} | ++ | |
 }}&build_date={{ config('beike.build') }})
+ + {{ trans('installer::installer_messages.welcome.next') }} + + +
+