Merge branch 'github-master' into master-github-histories

# Conflicts:
#	beike/Admin/View/Components/Header.php
#	beike/Admin/View/Components/Sidebar.php
#	beike/Helpers.php
#	beike/Models/Product.php
#	beike/Repositories/OrderRepo.php
#	beike/Repositories/PageCategoryRepo.php
#	beike/Shop/Services/CartService.php
#	composer.lock
#	resources/beike/admin/views/components/header.blade.php
#	resources/beike/admin/views/pages/design/builder/index.blade.php
#	resources/beike/admin/views/pages/marketing/show.blade.php
#	resources/beike/admin/views/pages/products/form/form.blade.php
#	resources/lang/en/admin/marketing.php
#	resources/lang/zh_cn/admin/common.php
#	resources/lang/zh_cn/admin/marketing.php
#	resources/lang/zh_cn/admin/product.php
#	themes/default/cart/mini.blade.php
#	themes/default/layout/footer.blade.php
#	themes/default/layout/master.blade.php
#	themes/default/product/product.blade.php
This commit is contained in:
liqianjin 2023-08-10 13:45:22 +08:00
commit f344d2deab
601 changed files with 17406 additions and 6644 deletions

View File

@ -56,8 +56,6 @@ PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
# Dusk Browser Test
# 前端热更新, PROXY: vhost or path, HOST: 多设备实时 DUSK_START_MAXIMIZED=true
MIX_PROXY=beikeshop.test DUSK_HEADLESS_DISABLED=false
MIX_HOST=192.168.0.122

View File

@ -5,8 +5,8 @@
## BeikeShop介绍 ## BeikeShop介绍
BeikeShop 是基于 Laravel 开发的一款开源商城系统,主要面向外贸,跨境行业提供的商品管理、订单管理、会员管理、支付、物流、系统管理等功能。 BeikeShop 是基于 Laravel 开发的一款开源商城系统
<br> 主要面向外贸,跨境行业提供的商品管理、订单管理、会员管理、支付、物流、系统管理等功能
<br> <br>
@ -14,17 +14,22 @@ BeikeShop 是基于 Laravel 开发的一款开源商城系统,主要面向外
<img src="https://beikeshop.com/readme/README-1.png"> <img src="https://beikeshop.com/readme/README-1.png">
</a> </a>
请保留我公司版权信息,如要移除,需要我公司授权 注意:请保留我公司版权信息,如要移除,需要我公司授权
## BeikeShop系统亮点 ## BeikeShop系统亮点
- **全新支持ChatGPTBeikeShop1.3.2 集成ChatGPT打造高效智能化跨境电商系统** - 0元起步BeikeShop 是真正的独立站代码100%开源数据信息100%自主可控
- 开源免费BeikeShop以开源方式发布OSL-3.0协议允许用户自由使用、修改。 - 基于 Laravel 9 框架BeikeShop 使用 Laravel 9 框架进行开发,拥有成熟的框架支持
- 轻量级框架BeikeShop基于Laravel框架开发具有高效、灵活、易用等特点。 - 无佣金和手续费BeikeShop 没有佣金、年费或手续费,降低了建站成本
- 灵活的插件机制BeikeShop提供灵活的Hook插件机制可以方便地扩展和定制系统功能。 - 微内核和插件化:采用微内核架构和插件化设计,使系统易维护 & 扩展
- 多语言多货币支持:支持多种语言/货币切换,可灵活扩展其他语言/货币。 - 清晰的代码分层和格式规范:系统代码采用分层清晰、格式规范的结构,提高代码的可读性和可维护性
- 多种支付方式:支持 PayPal、Stripe、Alipay、WeChat Pay 等多种支付方式,覆盖全球主要外贸国家 - Event 机制实现 Hook 功能:通过 Event 机制实现了灵活的 Hook 功能,方便扩展和定制化开发
- 快速搭建向VIP提供一键安装脚本帮助用户快速搭建起一个稳定、高效、易用的跨境电商独立站。 - 丰富的插件市场:官方提供了丰富的插件市场,可以方便地购买需要的功能
- 多语言和多货币支持:系统支持多语言和多货币,方便面向不同地区和国家的用户
- 界面美观和可视化装修:系统界面设计美观,支持可视化装修,提供良好的用户体验
- 严格遵循 MVC 架构:系统严格遵循 MVC 架构,提高了代码的可维护性和可扩展性
- 操作简单易上手BeikeShop 操作简单,易于上手,可以快速上线使用
![ ](https://beikeshop.com/readme/README-2.png) ![ ](https://beikeshop.com/readme/README-2.png)
<br> <br>
@ -38,27 +43,32 @@ BeikeShop 是基于 Laravel 开发的一款开源商城系统,主要面向外
</p> </p>
<a href="https://demo.beikeshop.com/" target="_blank">点击立刻体验BeikeShop演示站</a> <a href="https://demo.beikeshop.com/" target="_blank">点击立刻体验BeikeShop演示站</a>
<br> <br>
<br> <br>
## 页面展示 ## 页面展示
![ ](https://beikeshop.com/readme/README-7.png) ![ ](https://beikeshop.com/readme/README-7.png)
![ ](https://beikeshop.com/readme/README-5.png) ![ ](https://beikeshop.com/readme/README-5.png)
![ ](https://beikeshop.com/readme/README-6.png) ![ ](https://beikeshop.com/readme/README-6.png)
![ ](https://beikeshop.com/readme/README-8.png) ![ ](https://beikeshop.com/readme/README-8.png)
![ ](https://beikeshop.com/readme/README-9.png)
## 技术服务展示
![ ](https://beikeshop.com/readme/README-4.png)
## 软件架构 ## 软件架构
PHP语言开发基于 Laravel 框架,前端 Blade 模板 + Vue 使用语言 PHP 8.0
基于 Laravel 9 框架
前端 Blade 模板 + Vue
## 环境要求 ## 环境要求
- 独立服务器(不能使用虚拟空间)
- CentOS 7.0+ 或 Ubuntu 20.04+
- PHP 8.0.2+ - PHP 8.0.2+
- MySQL 5.7+ - MySQL 5.7+
- Apache httpd 2.4+ 或者 Nginx 1.10+ - Apache httpd 2.4+ 或者 Nginx 1.10+
@ -89,13 +99,13 @@ PHP语言开发基于 Laravel 框架,前端 Blade 模板 + Vue
1. 将解压文件夹下的 public 设置为网站根目录 1. 将解压文件夹下的 public 设置为网站根目录
1. 通过浏览器访问网站根据提示完成安装 1. 通过浏览器访问网站根据提示完成安装
1. <a href="https://docs.beikeshop.com/install/bt.html" target="_blank">BeikeShop详细安装指引</a> 1. <a href="https://docs.beikeshop.com/install/bt.html" target="_blank">BeikeShop详细安装指引</a>
1. 如需升级请下载最新版覆盖到服务器后网站根目录运行`composer install && php artisan migrate` 1. 如需升级, 请下载最新版覆盖到服务器(必须保留原有.env文件), 然网站根目录运行`php artisan migrate`
## 安装教程(面向开发者) ## 安装教程(面向开发者)
1. 打开命令行克隆代码 `git clone https://gitee.com/beikeshop/beikeshop.git` 1. 打开命令行克隆代码 `git clone https://gitee.com/beikeshop/beikeshop.git`
1. 命令行进入 `beikeshop` 目录, 执行 `composer install` 安装第三方包 1. 命令行进入 `beikeshop` 目录, 执行 `composer install` 安装第三方包
1. 接着执行 `cp .env.example .env` 创建配置文件 1. 接着执行 `cp .env.example .env` 创建配置文件
1. 接着执行 `npm install`node 版本需16+ 以及 `npm run dev` 编译前端 js 和 css 文件 1. 接着执行 `npm install`node 版本需16+ 以及 `npm run prod` 编译前端 js 和 css 文件
1. 将项目文件夹下的 `public` 设置为网站根目录 1. 将项目文件夹下的 `public` 设置为网站根目录
1. 通过浏览器访问网站, 根据提示完成安装 1. 通过浏览器访问网站, 根据提示完成安装
1. 如需升级请在服务器端网站根目录运行`git pull && composer install && php artisan migrate` 1. 如需升级请在服务器端网站根目录运行`git pull && composer install && php artisan migrate`
@ -106,5 +116,10 @@ PHP语言开发基于 Laravel 框架,前端 Blade 模板 + Vue
1. 提交代码 1. 提交代码
1. 新建 Merge Request 1. 新建 Merge Request
## 特别鸣谢
插件开发者撸串青年、Aegis
PR贡献者nilsir、what_村长、tanxiaoyong、Lucky、So、licy、老北、Teemo
感谢你们参与到BeikeShop的开发中共同为BeikeShop添砖加瓦
## QQ交流群 ## QQ交流群
群1: 639108380 群1: 639108380

View File

@ -64,11 +64,18 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class, \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
], ],
'api' => [ 'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api', 'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
], ],
'admin_api' => [
\App\Http\Middleware\AdminApiAuthenticate::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
]; ];
/** /**

View File

@ -0,0 +1,99 @@
<?php
/**
* AdminAuthenticate.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-20 14:44:54
* @modified 2023-04-20 14:44:54
*/
namespace App\Http\Middleware;
use Beike\Repositories\AdminUserTokenRepo;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Validation\UnauthorizedException;
class AdminApiAuthenticate
{
public const ADMIN_API_PREFIX = 'admin_api.';
/**
* Handle an incoming request.
*
* @param Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, \Closure $next)
{
$token = $request->header('token');
if (empty($token)) {
$token = $request->get('token');
}
$token = AdminUserTokenRepo::getAdminUserTokenByToken($token);
if (empty($token)) {
throw new UnauthorizedException(trans('customer.unauthorized_without_token'));
}
$adminUser = $token->adminUser;
if (! $this->hasPermission($adminUser)) {
throw new UnauthorizedException(trans('customer.unauthorized_without_permission'));
}
register('admin_user', $adminUser);
return $next($request);
}
private function hasPermission($adminUser)
{
// $routeUri = Route::current()->uri();
$routeName = Route::currentRouteName();
$routePath = str_replace(self::ADMIN_API_PREFIX, '', $routeName);
if ($routePath == 'me') {
return true;
}
$permissionName = $this->mapPermissionByRoute($routePath);
if (empty($permissionName)) {
return false;
}
return $adminUser->can($permissionName);
}
private function mapPermissionByRoute($routePath)
{
$maps = [
'categories.index' => 'categories_index',
'categories.show' => 'categories_show',
'categories.create' => 'categories_create',
'categories.update' => 'categories_update',
'categories.delete' => 'categories_delete',
'brands.index' => 'brands_index',
'brands.show' => 'brands_show',
'brands.create' => 'brands_create',
'brands.update' => 'brands_update',
'brands.delete' => 'brands_delete',
'orders.index' => 'orders_index',
'orders.show' => 'orders_show',
'orders.update_status' => 'orders_update_status',
'orders.update_shipment' => 'orders_update_status',
'products.index' => 'products_index',
'products.show' => 'products_show',
'products.create' => 'products_create',
'products.update' => 'products_update',
'products.delete' => 'products_delete',
];
return $maps[$routePath] ?? '';
}
}

View File

@ -13,5 +13,6 @@ class VerifyCsrfToken extends Middleware
*/ */
protected $except = [ protected $except = [
'callback/*', 'callback/*',
'api/*',
]; ];
} }

View File

@ -0,0 +1,100 @@
<?php
/**
* BrandController.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-20 17:19:51
* @modified 2023-04-20 17:19:51
*/
namespace Beike\API\Controllers\Admin;
use Beike\Models\Brand;
use Beike\Repositories\BrandRepo;
use Illuminate\Http\Request;
class BrandController
{
/**
* 显示品牌列表
* @param Request $request
* @return mixed
*/
public function index(Request $request)
{
$brands = BrandRepo::list($request->only('name', 'first', 'status'));
$data = [
'brands' => $brands,
];
return hook_filter('admin_api.brand.index.data', $data);
}
/**
* 创建品牌
*
* @param Request $request
* @param Brand $brand
* @return Brand
*/
public function show(Request $request, Brand $brand): Brand
{
return hook_filter('admin_api.brand.show.data', $brand);
}
/**
* 创建品牌
*
* @param Request $request
* @return array
*/
public function store(Request $request): array
{
$requestData = $request->all();
$data = [
'request_data' => $requestData,
];
hook_action('admin_api.brand.store.before', $data);
$brand = BrandRepo::create($requestData);
hook_action('admin_api.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_api.brand.update.before', $data);
$brand = BrandRepo::update($brand, $requestData);
hook_action('admin_api.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_api.brand.destroy.before', $brand);
BrandRepo::delete($brand);
hook_action('admin_api.brand.destroy.after', $brand);
return json_success(trans('common.deleted_success'));
}
}

View File

@ -0,0 +1,112 @@
<?php
/**
* CategoryController.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-20 17:00:40
* @modified 2023-04-20 17:00:40
*/
namespace Beike\API\Controllers\Admin;
use Beike\Admin\Http\Requests\CategoryRequest;
use Beike\Admin\Http\Resources\CategoryResource;
use Beike\Admin\Services\CategoryService;
use Beike\Models\Category;
use Beike\Repositories\CategoryRepo;
use Illuminate\Http\Request;
class CategoryController
{
/**
* 商品分类列表
*
* @return mixed
*/
public function index()
{
$categories = CategoryRepo::getAdminList();
$data = [
'categories' => CategoryResource::collection($categories),
];
return hook_filter('admin_api.category.index.data', $data);
}
/**
* 单个商品分类
*
* @param Request $request
* @param Category $category
* @return mixed
*/
public function show(Request $request, Category $category)
{
if (! $category->active) {
return [];
}
$category->load('description');
return hook_filter('admin_api.category.show.data', $category);
}
/**
* 保存商品分类
*
* @param CategoryRequest $request
* @return array
* @throws \Exception
*/
public function store(CategoryRequest $request)
{
$requestData = $request->all();
$category = (new CategoryService())->createOrUpdate($requestData, null);
$data = [
'category' => $category,
'request_data' => $requestData,
];
hook_action('admin_api.category.save.after', $data);
return $data;
}
/**
* 更新产品分类
*
* @param CategoryRequest $request
* @param Category $category
* @return array
* @throws \Exception
*/
public function update(CategoryRequest $request, Category $category)
{
$requestData = $request->all();
$category = (new CategoryService())->createOrUpdate($requestData, $category);
$data = [
'category' => $category,
'request_data' => $requestData,
];
hook_action('admin_api.category.save.after', $data);
return $data;
}
/**
* 删除分类
* @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'));
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* OrderController.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-20 16:40:35
* @modified 2023-04-20 16:40:35
*/
namespace Beike\API\Controllers\Admin;
use Beike\Models\Order;
use Beike\Models\OrderShipment;
use Beike\Repositories\OrderRepo;
use Beike\Services\ShipmentService;
use Beike\Services\StateMachineService;
use Illuminate\Http\Request;
class OrderController
{
/**
* 获取订单列表
*
* @param Request $request
* @return mixed
* @throws \Exception
*/
public function index(Request $request)
{
$orders = OrderRepo::filterOrders($request->all());
return hook_filter('admin_api.order.index.data', $orders);
}
/**
* 查看单个订单
*
* @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();
return hook_filter('admin_api.order.show.data', $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'));
}
}

View File

@ -0,0 +1,125 @@
<?php
/**
* ProductController.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-20 16:48:47
* @modified 2023-04-20 16:48:47
*/
namespace Beike\API\Controllers\Admin;
use Beike\Admin\Http\Requests\ProductRequest;
use Beike\Admin\Http\Resources\ProductResource;
use Beike\Admin\Services\ProductService;
use Beike\Models\Product;
use Beike\Repositories\ProductRepo;
use Beike\Shop\Http\Resources\ProductDetail;
use Illuminate\Http\Request;
class ProductController
{
/**
* 产品列表
*
* @param Request $request
* @return mixed
*/
public function index(Request $request)
{
$requestData = $request->all();
if (! isset($requestData['sort'])) {
$requestData['sort'] = 'products.updated_at';
}
$productList = ProductRepo::list($requestData);
$products = ProductResource::collection($productList);
return hook_filter('admin_api.product.index.data', $products);
}
/**
* 产品列表
*
* @param Request $request
* @param Product $product
* @return mixed
*/
public function show(Request $request, Product $product)
{
$relationIds = $product->relations->pluck('id')->toArray();
$product = ProductRepo::getProductDetail($product);
$data = [
'product' => (new ProductDetail($product))->jsonSerialize(),
'relations' => ProductRepo::getProductsByIds($relationIds)->jsonSerialize(),
];
return hook_filter('admin_api.product.show.data', $data);
}
/**
* 创建商品
*
* @param ProductRequest $request
* @return mixed
*/
public function store(ProductRequest $request)
{
try {
$requestData = $request->all();
$product = (new ProductService)->create($requestData);
$data = [
'request_data' => $requestData,
'product' => $product,
];
hook_action('admin_api.product.store.after', $data);
return json_success(trans('common.created_success'));
} catch (\Exception $e) {
return json_fail($e->getMessage());
}
}
/**
* 编辑商品
*
* @param ProductRequest $request
* @param Product $product
* @return mixed
*/
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_api.product.update.after', $data);
return json_success(trans('common.updated_success'));
} catch (\Exception $e) {
return json_fail($e->getMessage());
}
}
/**
* 删除商品
*
* @param Request $request
* @param Product $product
* @return array
*/
public function destroy(Request $request, Product $product)
{
$product->delete();
hook_action('admin_api.product.destroy.after', $product);
return json_success(trans('common.deleted_success'));
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
* UserController.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-20 14:51:26
* @modified 2023-04-20 14:51:26
*/
namespace Beike\API\Controllers\Admin;
class UserController
{
public function me()
{
return registry('admin_user');
}
}

View File

@ -0,0 +1,94 @@
<?php
/**
* AuthController.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-11 17:44:26
* @modified 2023-04-11 17:44:26
*/
namespace Beike\API\Controllers;
use App\Http\Controllers\Controller;
use Beike\Shop\Http\Resources\CustomerResource;
class AuthController extends Controller
{
/**
* Create a new AuthController instance.
*
* @return void
*/
public function __construct()
{
// $this->middleware('auth:api', ['except' => ['login']]);
}
/**
* Get a JWT via given credentials.
*
* @return \Illuminate\Http\JsonResponse
*/
public function login()
{
$credentials = request(['email', 'password']);
if (! $token = auth('api_customer')->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
/**
* Get the authenticated User.
*
* @return \Illuminate\Http\JsonResponse
*/
public function me()
{
$customer = auth('api_customer')->user();
return response()->json(new CustomerResource($customer));
}
/**
* Log the user out (Invalidate the token).
*
* @return \Illuminate\Http\JsonResponse
*/
public function logout()
{
auth('api_customer')->logout();
return response()->json(['message' => 'Successfully logged out']);
}
/**
* Refresh a token.
*
* @return \Illuminate\Http\JsonResponse
*/
public function refresh()
{
return $this->respondWithToken(auth('api_customer')->refresh());
}
/**
* Get the token array structure.
*
* @param string $token
*
* @return \Illuminate\Http\JsonResponse
*/
protected function respondWithToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api_customer')->factory()->getTTL() * 60,
]);
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* APIServiceProvider.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-11 16:08:04
* @modified 2023-04-11 16:08:04
*/
namespace Beike\API\Providers;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\ServiceProvider;
class APIServiceProvider extends ServiceProvider
{
public function boot()
{
if (is_installer()) {
return;
}
$this->loadRoutesFrom(__DIR__ . '/../Routes/api.php');
$this->registerGuard();
}
/**
* 注册后台用户 guard
*/
protected function registerGuard()
{
Config::set('auth.guards.api_customer', [
'driver' => 'jwt',
'provider' => 'shop_customer',
]);
}
}

52
beike/API/Routes/api.php Normal file
View File

@ -0,0 +1,52 @@
<?php
/**
* api.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-11 17:36:05
* @modified 2023-04-11 17:36:05
*/
use Illuminate\Support\Facades\Route;
Route::prefix('api')
->middleware(['api'])
->name('api.')
->group(function () {
Route::post('login', [\Beike\API\Controllers\AuthController::class, 'login']);
Route::post('logout', [\Beike\API\Controllers\AuthController::class, 'logout']);
Route::post('refresh', [\Beike\API\Controllers\AuthController::class, 'refresh']);
Route::get('me', [\Beike\API\Controllers\AuthController::class, 'me']);
});
Route::prefix('admin_api')
->middleware(['admin_api'])
->name('admin_api.')
->group(function () {
Route::get('brands', [\Beike\API\Controllers\Admin\BrandController::class, 'index'])->name('brands.index');
Route::get('brands/{brand}', [\Beike\API\Controllers\Admin\BrandController::class, 'show'])->name('brands.show');
Route::post('brands', [\Beike\API\Controllers\Admin\BrandController::class, 'store'])->name('brands.create');
Route::put('brands/{brand}', [\Beike\API\Controllers\Admin\BrandController::class, 'update'])->name('brands.update');
Route::delete('brands/{brand}', [\Beike\API\Controllers\Admin\BrandController::class, 'destroy'])->name('brands.delete');
Route::get('categories', [\Beike\API\Controllers\Admin\CategoryController::class, 'index'])->name('categories.index');
Route::get('categories/{category}', [\Beike\API\Controllers\Admin\CategoryController::class, 'show'])->name('categories.show');
Route::post('categories', [\Beike\API\Controllers\Admin\CategoryController::class, 'store'])->name('categories.create');
Route::put('categories/{category}', [\Beike\API\Controllers\Admin\CategoryController::class, 'update'])->name('categories.update');
Route::delete('categories/{category}', [\Beike\API\Controllers\Admin\CategoryController::class, 'destroy'])->name('categories.delete');
Route::get('me', [\Beike\API\Controllers\Admin\UserController::class, 'me'])->name('me');
Route::get('orders', [\Beike\API\Controllers\Admin\OrderController::class, 'index'])->name('orders.index');
Route::get('orders/{order}', [\Beike\API\Controllers\Admin\OrderController::class, 'show'])->name('orders.show');
Route::put('orders/{order}/status', [\Beike\API\Controllers\Admin\OrderController::class, 'updateStatus'])->name('orders.update_status');
Route::put('orders/{order}/shipments/{shipment}', [\Beike\API\Controllers\Admin\OrderController::class, 'updateShipment'])->name('orders.update_shipment');
Route::get('products', [\Beike\API\Controllers\Admin\ProductController::class, 'index'])->name('products.index');
Route::get('products/{product}', [\Beike\API\Controllers\Admin\ProductController::class, 'show'])->name('products.show');
Route::post('products', [\Beike\API\Controllers\Admin\ProductController::class, 'store'])->name('products.create');
Route::put('products/{product}', [\Beike\API\Controllers\Admin\ProductController::class, 'update'])->name('products.update');
Route::delete('products/{product}', [\Beike\API\Controllers\Admin\ProductController::class, 'destroy'])->name('products.delete');
});

View File

@ -0,0 +1,40 @@
<?php
/**
* BrandController.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2022-07-27 21:17:04
* @modified 2022-07-27 21:17:04
*/
namespace Beike\Admin\Http\Controllers;
use Beike\Admin\Repositories\AdminUserRepo;
use Beike\Repositories\AdminUserTokenRepo;
use Illuminate\Http\Request;
class AccountController extends Controller
{
public function index()
{
$user = current_user();
$data = [
'current_user' => $user,
'tokens' => AdminUserTokenRepo::getTokenByAdminUser($user)->pluck('token')->toArray(),
];
return view('admin::pages.account.index', $data);
}
public function update(Request $request)
{
$user = current_user();
$adminUserData = $request->all();
AdminUserRepo::updateAdminUser($user->id, $adminUserData);
return response()->redirectTo('admin/account')->with('success', trans('common.updated_success'));
}
}

View File

@ -12,6 +12,7 @@
namespace Beike\Admin\Http\Controllers; namespace Beike\Admin\Http\Controllers;
use Beike\Admin\Http\Requests\CurrencyRequest; use Beike\Admin\Http\Requests\CurrencyRequest;
use Beike\Models\Order;
use Beike\Repositories\CurrencyRepo; use Beike\Repositories\CurrencyRepo;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -66,9 +67,19 @@ class CurrencyController extends Controller
public function destroy(Request $request, int $currencyId) public function destroy(Request $request, int $currencyId)
{ {
try {
$currency = CurrencyRepo::find($currencyId);
$orderExist = Order::query()->where('currency_code', $currency->code)->exists();
if ($orderExist) {
throw new \Exception(trans('admin/currency.order_exist'));
}
CurrencyRepo::delete($currencyId); CurrencyRepo::delete($currencyId);
hook_action('admin.currency.destroy.after', $currencyId); hook_action('admin.currency.destroy.after', $currencyId);
return json_success(trans('common.deleted_success')); return json_success(trans('common.deleted_success'));
} catch (\Exception $e) {
return json_fail($e->getMessage());
}
} }
} }

View File

@ -31,10 +31,12 @@ class FileManagerController extends Controller
public function getFiles(Request $request): array public function getFiles(Request $request): array
{ {
$baseFolder = $request->get('base_folder'); $baseFolder = $request->get('base_folder');
$sort = $request->get('sort', 'created');
$order = $request->get('order', 'desc');
$page = (int) $request->get('page'); $page = (int) $request->get('page');
$perPage = (int) $request->get('per_page'); $perPage = (int) $request->get('per_page');
$data = (new FileManagerService)->getFiles($baseFolder, $page, $perPage); $data = (new FileManagerService)->getFiles($baseFolder, $sort, $order, $page, $perPage);
return hook_filter('admin.file_manager.files.data', $data); return hook_filter('admin.file_manager.files.data', $data);
} }
@ -124,7 +126,7 @@ class FileManagerController extends Controller
$savePath = $request->get('path'); $savePath = $request->get('path');
$originName = $file->getClientOriginalName(); $originName = $file->getClientOriginalName();
$filePath = $file->storeAs($savePath, $originName, 'catalog'); $filePath = (new FileManagerService)->uploadFile($file, $savePath, $originName);
return [ return [
'name' => $originName, 'name' => $originName,

View File

@ -5,13 +5,16 @@ namespace Beike\Admin\Http\Controllers;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Beike\Admin\Repositories\DashboardRepo; use Beike\Admin\Repositories\DashboardRepo;
use Beike\Admin\Repositories\Report\OrderReportRepo; use Beike\Admin\Repositories\Report\OrderReportRepo;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
class HomeController extends Controller class HomeController extends Controller
{ {
public function index() public function index()
{ {
$data = [ $data = [
'views' => DashboardRepo::getCustomerViewData(), 'products' => DashboardRepo::getProductData(),
// 'views' => DashboardRepo::getCustomerViewData(),
'orders' => DashboardRepo::getOrderData(), 'orders' => DashboardRepo::getOrderData(),
'customers' => DashboardRepo::getCustomerData(), 'customers' => DashboardRepo::getCustomerData(),
'order_totals' => DashboardRepo::getTotalData(), 'order_totals' => DashboardRepo::getTotalData(),
@ -24,4 +27,54 @@ class HomeController extends Controller
return view('admin::pages.home', $data); return view('admin::pages.home', $data);
} }
/**
* 通过关键字搜索菜单
*
* @return array
*/
public function menus()
{
$keyword = trim(request('keyword'));
$menus = [];
$routes = Route::getRoutes();
foreach ($routes as $route) {
$routeName = $route->getName();
if (! Str::startsWith($routeName, 'admin')) {
continue;
}
$method = $route->methods()[0];
if ($method != 'GET') {
continue;
}
$routeName = str_replace('admin.', '', $routeName);
$permissionRoute = str_replace('.', '_', $routeName);
try {
$url = admin_route($routeName);
} catch (\Exception $e) {
$url = '';
}
if (empty($url)) {
continue;
}
$title = trans("admin/common.{$permissionRoute}");
if (stripos($title, 'admin/common.') !== false) {
continue;
}
if ($keyword && stripos($title, $keyword) !== false) {
$menus[] = [
'route' => $routeName,
'url' => admin_route($routeName),
'title' => $title,
];
}
}
return $menus;
}
} }

View File

@ -0,0 +1,45 @@
<?php
/**
* AttributeController.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2023-01-04 19:45:41
* @modified 2023-01-04 19:45:41
*/
namespace Beike\Admin\Http\Controllers;
use Beike\Admin\Repositories\AttributeRepo;
use Beike\Repositories\SettingRepo;
use Illuminate\Http\Request;
class MultiFilterController extends Controller
{
public function index()
{
$multiFilter = system_setting('base.multi_filter') ?: [];
$multiFilter['attribute'] = $multiFilter['attribute'] ?? [];
if ($attributeIds = $multiFilter['attribute'] ?? []) {
$multiFilter['attribute'] = AttributeRepo::getByIds($attributeIds);
}
$data = [
'multi_filter' => $multiFilter,
];
return view('admin::pages.multi_filter.index', $data);
}
public function store(Request $request)
{
$settings = $request->all();
foreach ($settings as $key => $value) {
SettingRepo::storeValue($key, $value);
}
return redirect(admin_route('multi_filter.index'))->with('success', trans('common.updated_success'));
}
}

View File

@ -71,7 +71,7 @@ class OrderController extends Controller
*/ */
public function show(Request $request, Order $order) public function show(Request $request, Order $order)
{ {
$order->load(['orderTotals', 'orderHistories', 'orderShipments']); $order->load(['orderTotals', 'orderHistories', 'orderShipments', 'orderPayments']);
$data = hook_filter('admin.order.show.data', ['order' => $order, 'html_items' => []]); $data = hook_filter('admin.order.show.data', ['order' => $order, 'html_items' => []]);
$data['statuses'] = StateMachineService::getInstance($order)->nextBackendStatuses(); $data['statuses'] = StateMachineService::getInstance($order)->nextBackendStatuses();
$data = hook_filter('admin.order.show.data', $data); $data = hook_filter('admin.order.show.data', $data);

View File

@ -32,6 +32,111 @@ class PluginController extends Controller
return view('admin::pages.plugins.index', $data); return view('admin::pages.plugins.index', $data);
} }
/**
* @return mixed
*/
public function shipping()
{
$type = 'shipping';
$plugins = app('plugin')->getPlugins();
$plugins = $plugins->where('type', $type);
$data['plugins'] = array_values(PluginResource::collection($plugins)->jsonSerialize());
$data['type'] = $type;
$data = hook_filter('admin.plugin.index.data', $data);
return view('admin::pages.plugins.index', $data);
}
/**
* @return mixed
*/
public function payment()
{
$type = 'payment';
$plugins = app('plugin')->getPlugins();
$plugins = $plugins->where('type', $type);
$data['plugins'] = array_values(PluginResource::collection($plugins)->jsonSerialize());
$data['type'] = $type;
$data = hook_filter('admin.plugin.index.data', $data);
return view('admin::pages.plugins.index', $data);
}
/**
* @return mixed
*/
public function total()
{
$type = 'total';
$plugins = app('plugin')->getPlugins();
$plugins = $plugins->where('type', $type);
$data['plugins'] = array_values(PluginResource::collection($plugins)->jsonSerialize());
$data['type'] = $type;
$data = hook_filter('admin.plugin.index.data', $data);
return view('admin::pages.plugins.index', $data);
}
/**
* @return mixed
*/
public function social()
{
$type = 'social';
$plugins = app('plugin')->getPlugins();
$plugins = $plugins->where('type', $type);
$data['plugins'] = array_values(PluginResource::collection($plugins)->jsonSerialize());
$data['type'] = $type;
$data = hook_filter('admin.plugin.index.data', $data);
return view('admin::pages.plugins.index', $data);
}
/**
* @return mixed
*/
public function feature()
{
$type = 'feature';
$plugins = app('plugin')->getPlugins();
$plugins = $plugins->where('type', $type);
$data['plugins'] = array_values(PluginResource::collection($plugins)->jsonSerialize());
$data['type'] = $type;
$data = hook_filter('admin.plugin.index.data', $data);
return view('admin::pages.plugins.index', $data);
}
/**
* @return mixed
*/
public function language()
{
$type = 'language';
$plugins = app('plugin')->getPlugins();
$plugins = $plugins->where('type', $type);
$data['plugins'] = array_values(PluginResource::collection($plugins)->jsonSerialize());
$data['type'] = $type;
$data = hook_filter('admin.plugin.index.data', $data);
return view('admin::pages.plugins.index', $data);
}
/**
* @return mixed
*/
public function theme()
{
$type = 'theme';
$plugins = app('plugin')->getPlugins();
$plugins = $plugins->where('type', $type);
$data['plugins'] = array_values(PluginResource::collection($plugins)->jsonSerialize());
$data['type'] = $type;
$data = hook_filter('admin.plugin.index.data', $data);
return view('admin::pages.plugins.index', $data);
}
/** /**
* 上传插件 * 上传插件
*/ */

View File

@ -7,6 +7,7 @@ use Beike\Admin\Http\Resources\ProductAttributeResource;
use Beike\Admin\Http\Resources\ProductResource; use Beike\Admin\Http\Resources\ProductResource;
use Beike\Admin\Repositories\TaxClassRepo; use Beike\Admin\Repositories\TaxClassRepo;
use Beike\Admin\Services\ProductService; use Beike\Admin\Services\ProductService;
use Beike\Libraries\Weight;
use Beike\Models\Product; use Beike\Models\Product;
use Beike\Repositories\CategoryRepo; use Beike\Repositories\CategoryRepo;
use Beike\Repositories\LanguageRepo; use Beike\Repositories\LanguageRepo;
@ -20,6 +21,9 @@ class ProductController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
$requestData = $request->all(); $requestData = $request->all();
if (! isset($requestData['sort'])) {
$requestData['sort'] = 'products.updated_at';
}
$productList = ProductRepo::list($requestData); $productList = ProductRepo::list($requestData);
$products = ProductResource::collection($productList); $products = ProductResource::collection($productList);
$productsFormat = $products->jsonSerialize(); $productsFormat = $products->jsonSerialize();
@ -152,8 +156,9 @@ class ProductController extends Controller
'relations' => ProductResource::collection($product->relations)->resource, 'relations' => ProductResource::collection($product->relations)->resource,
'languages' => LanguageRepo::all(), 'languages' => LanguageRepo::all(),
'tax_classes' => $taxClasses, 'tax_classes' => $taxClasses,
'weight_classes' => Weight::getWeightUnits(),
'source' => [ 'source' => [
'categories' => CategoryRepo::flatten(locale()), 'categories' => CategoryRepo::flatten(locale(), false),
], ],
'_redirect' => $this->getRedirect(), '_redirect' => $this->getRedirect(),
]; ];

View File

@ -30,7 +30,7 @@ class SettingController extends Controller
{ {
$themes = ThemeRepo::getAllThemes(); $themes = ThemeRepo::getAllThemes();
$tax_address = [ $taxAddress = [
['value' => 'shipping', 'label' => trans('admin/setting.shipping_address')], ['value' => 'shipping', 'label' => trans('admin/setting.shipping_address')],
['value' => 'payment', 'label' => trans('admin/setting.payment_address')], ['value' => 'payment', 'label' => trans('admin/setting.payment_address')],
]; ];
@ -38,7 +38,7 @@ class SettingController extends Controller
$data = [ $data = [
'countries' => CountryRepo::listEnabled(), 'countries' => CountryRepo::listEnabled(),
'currencies' => CurrencyRepo::listEnabled(), 'currencies' => CurrencyRepo::listEnabled(),
'tax_address' => $tax_address, 'tax_address' => $taxAddress,
'customer_groups' => CustomerGroupDetail::collection(CustomerGroupRepo::list())->jsonSerialize(), 'customer_groups' => CustomerGroupDetail::collection(CustomerGroupRepo::list())->jsonSerialize(),
'themes' => $themes, 'themes' => $themes,
]; ];

View File

@ -34,6 +34,7 @@ class PageRequest extends FormRequest
{ {
$rules = [ $rules = [
'descriptions.*.title' => 'required|string|min:3|max:128', 'descriptions.*.title' => 'required|string|min:3|max:128',
'descriptions.*.summary' => 'string|max:180',
'descriptions.*.content' => 'required|string', 'descriptions.*.content' => 'required|string',
'descriptions.*.locale' => 'required|string', 'descriptions.*.locale' => 'required|string',
]; ];

View File

@ -33,7 +33,7 @@ class TaxRateRequest extends FormRequest
public function rules(): array public function rules(): array
{ {
$rule = [ $rule = [
'name' => 'required|string|max:10', 'name' => 'required|string|max:32',
'rate' => 'required|numeric', 'rate' => 'required|numeric',
'type' => 'required|in:percent,flat', 'type' => 'required|in:percent,flat',
'region_id' => 'required|int', 'region_id' => 'required|int',

View File

@ -33,7 +33,7 @@ class UploadRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'file' => 'required|image|mimes:jpg,png,jpeg,gif,svg|max:2048', 'file' => 'required|mimes:jpg,png,jpeg,gif,svg,mp4|max:20480',
]; ];
} }
} }

View File

@ -22,13 +22,13 @@ class PageCategoryResource extends JsonResource
return [ return [
'id' => $this->id, 'id' => $this->id,
'active' => $this->active, 'active' => $this->active,
'title' => $description->title, 'title' => $description->title ?? '',
'title_format' => sub_string($description->title, 64), 'title_format' => sub_string($description->title ?? '', 64),
'summary' => $description->summary, 'summary' => $description->summary ?? '',
'summary_format' => sub_string($description->summary, 128), 'summary_format' => sub_string($description->summary ?? '', 128),
'meta_title' => $description->meta_title, 'meta_title' => $description->meta_title ?? '',
'meta_description' => $description->meta_description, 'meta_description' => $description->meta_description ?? '',
'meta_keywords' => $description->meta_keywords, 'meta_keywords' => $description->meta_keywords ?? '',
'created_at' => time_format($this->created_at), 'created_at' => time_format($this->created_at),
'updated_at' => time_format($this->updated_at), 'updated_at' => time_format($this->updated_at),
]; ];

View File

@ -24,6 +24,8 @@ class ProductResource extends JsonResource
return image_resize($image); return image_resize($image);
}, $this->images ?? []), }, $this->images ?? []),
'name' => $this->description->name ?? '', 'name' => $this->description->name ?? '',
'model' => $masterSku->model,
'quantity' => $masterSku->quantity,
'price_formatted' => currency_format($masterSku->price), 'price_formatted' => currency_format($masterSku->price),
'active' => $this->active, 'active' => $this->active,
'position' => $this->position, 'position' => $this->position,

View File

@ -53,6 +53,7 @@ class AdminServiceProvider extends ServiceProvider
load_settings(); load_settings();
$this->loadRoutesFrom(__DIR__ . '/../Routes/admin.php'); $this->loadRoutesFrom(__DIR__ . '/../Routes/admin.php');
$this->registerGuard();
$adminName = admin_name(); $adminName = admin_name();
if (! Str::startsWith($uri, "/{$adminName}")) { if (! Str::startsWith($uri, "/{$adminName}")) {
@ -69,8 +70,6 @@ class AdminServiceProvider extends ServiceProvider
$this->loadAdminViewComponents(); $this->loadAdminViewComponents();
$this->registerGuard();
Config::set('filesystems.disks.catalog', [ Config::set('filesystems.disks.catalog', [
'driver' => 'local', 'driver' => 'local',
'root' => public_path('catalog'), 'root' => public_path('catalog'),

View File

@ -13,6 +13,7 @@ namespace Beike\Admin\Repositories;
use Beike\Admin\Http\Resources\AdminUserDetail; use Beike\Admin\Http\Resources\AdminUserDetail;
use Beike\Models\AdminUser; use Beike\Models\AdminUser;
use Beike\Repositories\AdminUserTokenRepo;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
class AdminUserRepo class AdminUserRepo
@ -73,7 +74,14 @@ class AdminUserRepo
$userData['password'] = bcrypt($password); $userData['password'] = bcrypt($password);
} }
$adminUser->update($userData); $adminUser->update($userData);
$adminUser->syncRoles($data['roles']);
$roles = $data['roles'] ?? [];
if ($roles) {
$adminUser->syncRoles($roles);
}
$tokens = $data['tokens'] ?? [];
AdminUserTokenRepo::updateTokensByUser($adminUser, $tokens);
return $adminUser; return $adminUser;
} }

View File

@ -68,6 +68,9 @@ class AttributeGroupRepo
public static function delete($id) public static function delete($id)
{ {
if ($id == 1) {
throw new \Exception(trans('admin/attribute_group.error_cannot_delete_default_group'));
}
$group = AttributeGroup::query()->findOrFail($id); $group = AttributeGroup::query()->findOrFail($id);
if ($group->attributes->count()) { if ($group->attributes->count()) {
throw new \Exception(trans('admin/attribute_group.error_cannot_delete_attribute_used', ['attributes' => implode(', ', $group->attributes->pluck('id')->toArray())])); throw new \Exception(trans('admin/attribute_group.error_cannot_delete_attribute_used', ['attributes' => implode(', ', $group->attributes->pluck('id')->toArray())]));

View File

@ -12,6 +12,7 @@
namespace Beike\Admin\Repositories; namespace Beike\Admin\Repositories;
use Beike\Models\Attribute; use Beike\Models\Attribute;
use Beike\Models\AttributeDescription;
use Beike\Models\AttributeValue; use Beike\Models\AttributeValue;
use Beike\Models\ProductAttribute; use Beike\Models\ProductAttribute;
@ -138,4 +139,14 @@ class AttributeRepo
return $builder->limit(10)->get(); return $builder->limit(10)->get();
} }
public static function getByIds($ids)
{
return AttributeDescription::query()
->where('locale', locale())
->whereIn('attribute_id', $ids)
->select(['attribute_id as id', 'name'])
->get()
->toArray();
}
} }

View File

@ -11,9 +11,9 @@
namespace Beike\Admin\Repositories; namespace Beike\Admin\Repositories;
use Beike\Models\Product;
use Beike\Repositories\CustomerRepo; use Beike\Repositories\CustomerRepo;
use Beike\Repositories\OrderRepo; use Beike\Repositories\OrderRepo;
use Beike\Repositories\ProductRepo;
class DashboardRepo class DashboardRepo
{ {
@ -21,12 +21,22 @@ class DashboardRepo
* 获取商品总数 * 获取商品总数
* *
* @return array * @return array
* @throws \Exception
*/ */
public static function getProductData(): array public static function getProductData(): array
{ {
$today = ProductRepo::getBuilder(['created_start' => today()->subDay(), 'created_end' => today()])->count();
$yesterday = ProductRepo::getBuilder(['created_start' => today()->subDays(2), 'created_end' => today()->subDay()])->count();
$difference = $today - $yesterday;
if ($difference && $yesterday) {
$percentage = round(($difference / $yesterday) * 100);
} else {
$percentage = 0;
}
return [ return [
'total' => quantity_format(Product::query()->count()), 'total' => $today,
'percentage' => 0, 'percentage' => $percentage,
]; ];
} }

View File

@ -83,6 +83,8 @@ class PageRepo
$page->descriptions()->createMany($data['descriptions']); $page->descriptions()->createMany($data['descriptions']);
$products = $data['products'] ?? []; $products = $data['products'] ?? [];
$page->pageProducts()->delete();
if ($products) { if ($products) {
$items = []; $items = [];
foreach ($products as $item) { foreach ($products as $item) {
@ -90,7 +92,6 @@ class PageRepo
'product_id' => $item, 'product_id' => $item,
]; ];
} }
$page->pageProducts()->delete();
$page->pageProducts()->createMany($items); $page->pageProducts()->createMany($items);
} }

View File

@ -138,7 +138,7 @@ class PermissionRepo
*/ */
private function getProductPermissions(): array private function getProductPermissions(): array
{ {
$routes = ['products_index', 'products_create', 'products_show', 'products_update', 'products_delete', 'products_trashed', 'products_restore']; $routes = ['products_index', 'products_create', 'products_show', 'products_update', 'products_delete', 'products_trashed', 'products_restore', 'products_filter_index', 'products_filter_update'];
$items = $this->getPermissionList('product', $routes); $items = $this->getPermissionList('product', $routes);
return hook_filter('role.product_permissions', $items); return hook_filter('role.product_permissions', $items);

View File

@ -19,6 +19,11 @@ Route::prefix($adminName)
Route::middleware('admin_auth:' . \Beike\Models\AdminUser::AUTH_GUARD) Route::middleware('admin_auth:' . \Beike\Models\AdminUser::AUTH_GUARD)
->group(function () { ->group(function () {
Route::get('/', [Controllers\HomeController::class, 'index'])->name('home.index'); Route::get('/', [Controllers\HomeController::class, 'index'])->name('home.index');
Route::get('/menus', [Controllers\HomeController::class, 'menus'])->name('home.menus');
//个人中心
Route::middleware('can:account_index')->get('account', [Controllers\AccountController::class, 'index'])->name('account.index');
Route::middleware('can:account_update')->put('account', [Controllers\AccountController::class, 'update'])->name('account.update');
// 属性 // 属性
Route::middleware('can:attributes_update')->post('attributes/{id}/values', [Controllers\AttributeController::class, 'storeValue'])->name('attributes.values.store'); Route::middleware('can:attributes_update')->post('attributes/{id}/values', [Controllers\AttributeController::class, 'storeValue'])->name('attributes.values.store');
@ -32,6 +37,10 @@ Route::prefix($adminName)
Route::middleware('can:attributes_update')->put('attributes/{id}', [Controllers\AttributeController::class, 'update'])->name('attributes.update'); 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:attributes_delete')->delete('attributes/{id}', [Controllers\AttributeController::class, 'destroy'])->name('attributes.destroy');
// 高级筛选
Route::middleware('can:products_filter_index')->get('multi_filter', [Controllers\MultiFilterController::class, 'index'])->name('multi_filter.index');
Route::middleware('can:products_filter_update')->post('multi_filter', [Controllers\MultiFilterController::class, 'store'])->name('multi_filter.store');
// 属性组 // 属性组
Route::middleware('can:attribute_groups_index')->get('attribute_groups', [Controllers\AttributeGroupController::class, 'index'])->name('attribute_groups.index'); 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_create')->post('attribute_groups', [Controllers\AttributeGroupController::class, 'store'])->name('attribute_groups.store');
@ -153,6 +162,15 @@ Route::prefix($adminName)
Route::middleware('can:plugins_install')->post('plugins/{code}/install', [Controllers\PluginController::class, 'install'])->name('plugins.install'); 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:plugins_uninstall')->post('plugins/{code}/uninstall', [Controllers\PluginController::class, 'uninstall'])->name('plugins.uninstall');
// 插件分组
Route::middleware('can:plugins_index')->get('plugins/shipping', [Controllers\PluginController::class, 'shipping'])->name('plugins.shipping');
Route::middleware('can:plugins_index')->get('plugins/payment', [Controllers\PluginController::class, 'payment'])->name('plugins.payment');
Route::middleware('can:plugins_index')->get('plugins/total', [Controllers\PluginController::class, 'total'])->name('plugins.total');
Route::middleware('can:plugins_index')->get('plugins/social', [Controllers\PluginController::class, 'social'])->name('plugins.social');
Route::middleware('can:plugins_index')->get('plugins/feature', [Controllers\PluginController::class, 'feature'])->name('plugins.feature');
Route::middleware('can:plugins_index')->get('plugins/language', [Controllers\PluginController::class, 'language'])->name('plugins.language');
Route::middleware('can:plugins_index')->get('plugins/theme', [Controllers\PluginController::class, 'theme'])->name('plugins.theme');
// 插件市场 // 插件市场
Route::middleware('can:marketing_index')->get('marketing', [Controllers\MarketingController::class, 'index'])->name('marketing.index'); 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_show')->get('marketing/{code}', [Controllers\MarketingController::class, 'show'])->name('marketing.show');
@ -196,7 +214,7 @@ Route::prefix($adminName)
Route::middleware('can:products_index')->get('products', [Controllers\ProductController::class, 'index'])->name('products.index'); 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')->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_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_show')->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_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:products_delete')->delete('products/{product}', [Controllers\ProductController::class, 'destroy'])->name('products.destroy');

View File

@ -13,11 +13,13 @@ namespace Beike\Admin\Services;
class FileManagerService class FileManagerService
{ {
private $fileBasePath = ''; protected $fileBasePath = '';
protected $basePath = '';
public function __construct() public function __construct()
{ {
$this->fileBasePath = public_path('catalog'); $this->fileBasePath = public_path('catalog') . $this->basePath;
} }
/** /**
@ -26,6 +28,7 @@ class FileManagerService
public function getDirectories($baseFolder = '/'): array public function getDirectories($baseFolder = '/'): array
{ {
$currentBasePath = rtrim($this->fileBasePath . $baseFolder, '/'); $currentBasePath = rtrim($this->fileBasePath . $baseFolder, '/');
$directories = glob("{$currentBasePath}/*", GLOB_ONLYDIR); $directories = glob("{$currentBasePath}/*", GLOB_ONLYDIR);
$result = []; $result = [];
@ -49,18 +52,34 @@ class FileManagerService
* 获取某个目录下的文件和文件夹 * 获取某个目录下的文件和文件夹
* *
* @param $baseFolder * @param $baseFolder
* @param $sort
* @param $order
* @param int $page * @param int $page
* @param int $perPage * @param int $perPage
* @return array * @return array
* @throws \Exception * @throws \Exception
*/ */
public function getFiles($baseFolder, int $page = 1, int $perPage = 20): array public function getFiles($baseFolder, $sort, $order, int $page = 1, int $perPage = 20): array
{ {
$currentBasePath = rtrim($this->fileBasePath . $baseFolder, '/'); $currentBasePath = rtrim($this->fileBasePath . $baseFolder, '/');
$files = glob($currentBasePath . '/*'); $files = glob($currentBasePath . '/*');
if ($sort == 'created') {
if ($order == 'desc') {
usort($files, function ($a, $b) { usort($files, function ($a, $b) {
return filemtime($a) - filemtime($b) < 0; return filemtime($a) - filemtime($b) < 0;
}); });
} else {
usort($files, function ($a, $b) {
return filemtime($a) - filemtime($b) >= 0;
});
}
} else {
natcasesort($files);
if ($order == 'desc') {
$files = array_reverse($files);
}
}
$images = []; $images = [];
foreach ($files as $file) { foreach ($files as $file) {
@ -68,7 +87,7 @@ class FileManagerService
if ($baseName == 'index.html') { if ($baseName == 'index.html') {
continue; continue;
} }
$fileName = str_replace($this->fileBasePath, '', $file); $fileName = str_replace(public_path('catalog'), '', $file);
if (is_file($file)) { if (is_file($file)) {
$images[] = $this->handleImage($fileName, $baseName); $images[] = $this->handleImage($fileName, $baseName);
} }
@ -98,7 +117,7 @@ class FileManagerService
*/ */
public function createDirectory($folderName) public function createDirectory($folderName)
{ {
$catalogFolderPath = "catalog/{$folderName}"; $catalogFolderPath = "catalog{$this->basePath}/{$folderName}";
$folderPath = public_path($catalogFolderPath); $folderPath = public_path($catalogFolderPath);
if (is_dir($folderPath)) { if (is_dir($folderPath)) {
throw new \Exception(trans('admin/file_manager.directory_already_exist')); throw new \Exception(trans('admin/file_manager.directory_already_exist'));
@ -114,7 +133,7 @@ class FileManagerService
*/ */
public function deleteDirectoryOrFile($filePath) public function deleteDirectoryOrFile($filePath)
{ {
$filePath = public_path("catalog/{$filePath}"); $filePath = public_path("catalog{$this->basePath}/{$filePath}");
if (is_dir($filePath)) { if (is_dir($filePath)) {
$files = glob($filePath . '/*'); $files = glob($filePath . '/*');
if ($files) { if ($files) {
@ -138,7 +157,7 @@ class FileManagerService
return; return;
} }
foreach ($files as $file) { foreach ($files as $file) {
$filePath = public_path("catalog/{$basePath}/$file"); $filePath = public_path("catalog{$this->basePath}/{$basePath}/$file");
if (file_exists($filePath)) { if (file_exists($filePath)) {
@unlink($filePath); @unlink($filePath);
} }
@ -154,7 +173,7 @@ class FileManagerService
*/ */
public function updateName($originPath, $newPath) public function updateName($originPath, $newPath)
{ {
$originPath = public_path("catalog/{$originPath}"); $originPath = public_path("catalog{$this->basePath}/{$originPath}");
if (! is_dir($originPath) && ! file_exists($originPath)) { if (! is_dir($originPath) && ! file_exists($originPath)) {
throw new \Exception(trans('admin/file_manager.target_not_exist')); throw new \Exception(trans('admin/file_manager.target_not_exist'));
} }
@ -166,6 +185,13 @@ class FileManagerService
@rename($originPath, $newPath); @rename($originPath, $newPath);
} }
public function uploadFile($file, $savePath, $originName)
{
$savePath = $this->basePath . $savePath;
return $file->storeAs($savePath, $originName, 'catalog');
}
/** /**
* 处理文件夹 * 处理文件夹
* *
@ -189,7 +215,7 @@ class FileManagerService
*/ */
private function hasSubFolders($folderPath): bool private function hasSubFolders($folderPath): bool
{ {
$path = public_path("catalog/{$folderPath}"); $path = public_path("catalog{$this->basePath}/{$folderPath}");
$subFiles = glob($path . '/*'); $subFiles = glob($path . '/*');
foreach ($subFiles as $subFile) { foreach ($subFiles as $subFile) {
if (is_dir($subFile)) { if (is_dir($subFile)) {
@ -211,11 +237,18 @@ class FileManagerService
private function handleImage($filePath, $baseName): array private function handleImage($filePath, $baseName): array
{ {
$path = "catalog{$filePath}"; $path = "catalog{$filePath}";
$realPath = $this->fileBasePath . $filePath;
$mime = '';
if(file_exists($realPath)) {
$mime = mime_content_type($realPath);
}
return [ return [
'path' => $path, 'path' => $path,
'name' => $baseName, 'name' => $baseName,
'origin_url' => image_origin($path), 'origin_url' => image_origin($path),
'mime' => $mime,
'selected' => false, 'selected' => false,
]; ];
} }

View File

@ -26,9 +26,12 @@ class ProductService
try { try {
DB::beginTransaction(); DB::beginTransaction();
$data['brand_id'] = (int) $data['brand_id']; $data['brand_id'] = (int) ($data['brand_id'] ?? 0);
$data['variables'] = json_decode($data['variables']); $data['position'] = (int) ($data['position'] ?? 0);
$data['weight'] = (float) ($data['weight'] ?? 0);
$data['variables'] = json_decode($data['variables'] ?? '[]');
$product->fill($data); $product->fill($data);
$product->updated_at = now();
$product->save(); $product->save();
if ($isUpdating) { if ($isUpdating) {

View File

@ -14,16 +14,19 @@ class Select extends Component
public array $options; public array $options;
public string $width;
public string $key; public string $key;
public string $label; public string $label;
public function __construct(string $name, string $value, string $title, array $options, ?string $key = 'value', ?string $label = 'label') public function __construct(string $name, string $value, string $title, array $options, string $width = '400', ?string $key = 'value', ?string $label = 'label')
{ {
$this->name = $name; $this->name = $name;
$this->title = $title; $this->title = $title;
$this->value = $value; $this->value = $value;
$this->options = $options; $this->options = $options;
$this->width = $width;
$this->key = $key; $this->key = $key;
$this->label = $label; $this->label = $label;
} }

View File

@ -3,6 +3,7 @@
namespace Beike\Admin\View\Components; namespace Beike\Admin\View\Components;
use Beike\Models\AdminUser; use Beike\Models\AdminUser;
use Illuminate\Support\Facades\View;
use Illuminate\View\Component; use Illuminate\View\Component;
class Header extends Component class Header extends Component
@ -11,6 +12,10 @@ class Header extends Component
private ?AdminUser $adminUser; private ?AdminUser $adminUser;
public array $commonLinks;
public array $historyLinks;
/** /**
* Create a new component instance. * Create a new component instance.
* *
@ -28,82 +33,102 @@ class Header extends Component
*/ */
public function render() public function render()
{ {
$sidebar = new Sidebar(); $this->commonLinks = $this->getCommonLinks();
$preparedMenus = $this->prepareMenus(); $this->historyLinks = $this->handleHistoryLinks();
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'); return view('admin::components.header');
} }
/** /**
* 默认菜单 * 常用功能链接
*/ */
private function prepareMenus() private function getCommonLinks()
{ {
$menus = [ $commonLinks = [
['name' => trans('admin/common.home'), 'route' => 'home.index', 'code' => ''], ['route' => 'design.index', 'icon' => 'bi bi-palette', 'blank' => true],
['name' => trans('admin/common.order'), 'route' => 'orders.index', 'code' => 'Order'], ['route' => 'design_footer.index', 'icon' => 'bi bi-palette', 'blank' => true],
['name' => trans('admin/common.product'), 'route' => 'products.index', 'code' => 'Product'], ['route' => 'design_menu.index', 'icon' => 'bi bi-list', 'blank' => false],
['name' => trans('admin/common.customer'), 'route' => 'customers.index', 'code' => 'Customer'], ['route' => 'languages.index', 'icon' => 'bi bi-globe2', 'blank' => false],
['name' => trans('admin/common.page'), 'route' => 'pages.index', 'code' => 'Page'], ['route' => 'currencies.index', 'icon' => 'bi bi-currency-dollar', 'blank' => false],
['name' => trans('admin/common.setting'), 'route' => 'settings.index', 'code' => 'Setting'], ['route' => 'plugins.index', 'icon' => 'bi bi-plug', 'blank' => false],
['name' => trans('admin/common.inquiry'), 'route' => 'inquiry.index', 'code' => 'Inquiry'],
]; ];
return hook_filter('admin.header_menus', $menus); foreach ($commonLinks as $index => $commonLink) {
$route = $commonLink['route'];
$permissionRoute = str_replace('.', '_', $route);
$commonLinks[$index]['url'] = admin_route($route);
$commonLinks[$index]['title'] = trans("admin/common.{$permissionRoute}");
}
return hook_filter('admin.components.header.common_links', $commonLinks);
} }
/** /**
* 添加后台顶部菜单链接 * 处理最近访问链接
*
* @param $title
* @param $route
* @param false $active
*/ */
private function addLink($title, $route, bool $active = false) private function handleHistoryLinks(): array
{ {
$permissionRoute = str_replace('.', '_', $route); $links = [];
if ($this->adminUser->cannot($permissionRoute) && $route != 'home.index') { $histories = $this->getHistoryRoutesFromSession();
return; foreach ($histories as $history) {
$routeName = str_replace('admin.', '', $history);
$permissionRoute = str_replace('.', '_', $routeName);
if (stripos($routeName, 'plugins.') !== false) {
$type = str_replace('plugins.', '', $routeName);
if ($type == 'index') {
$title = trans("admin/common.{$permissionRoute}");
} else {
$title = trans("admin/plugin.{$type}");
}
} else {
$title = trans("admin/common.{$permissionRoute}");
} }
$url = admin_route($route); if (stripos($title, 'admin/common.') !== false) {
$this->links[] = [ $tempRouteName = str_replace('s.index', '', $routeName);
'title' => $title, $title = trans("admin/common.{$tempRouteName}");
}
try {
$url = admin_route($routeName);
} catch (\Exception $e) {
$url = '';
}
if (empty($url)) {
continue;
}
$links[] = [
'route' => $routeName,
'url' => $url, 'url' => $url,
'active' => $active, 'title' => $title,
]; ];
} }
return $links;
}
/**
* session 获取最近访问的链接
*
* @return array
*/
private function getHistoryRoutesFromSession(): array
{
$histories = session('histories', []);
$currentRoute = request()->route()->getName();
$routeName = str_replace('admin.', '', $currentRoute);
if (in_array($routeName, ['edit.locale', 'home.menus'])) {
return $histories;
}
array_unshift($histories, $currentRoute);
$histories = array_slice(array_unique($histories), 0, 6);
session(['histories' => $histories]);
return $histories;
}
} }

View File

@ -3,17 +3,23 @@
namespace Beike\Admin\View\Components; namespace Beike\Admin\View\Components;
use Beike\Models\AdminUser; use Beike\Models\AdminUser;
use Illuminate\Support\Str; use Beike\Plugin\Plugin;
use Illuminate\View\Component; use Illuminate\View\Component;
class Sidebar extends Component class Sidebar extends Component
{ {
public array $links = []; public array $links = [];
public ?array $currentLink;
private string $adminName; private string $adminName;
private ?string $routeNameWithPrefix; private ?string $routeNameWithPrefix;
private ?string $currentRouteName;
private ?string $currentPrefix;
private ?AdminUser $adminUser; private ?AdminUser $adminUser;
/** /**
@ -24,8 +30,13 @@ class Sidebar extends Component
public function __construct() public function __construct()
{ {
$this->adminName = admin_name(); $this->adminName = admin_name();
$this->routeNameWithPrefix = request()->route()->getName();
$this->adminUser = current_user(); $this->adminUser = current_user();
$this->routeNameWithPrefix = request()->route()->getName();
$this->currentRouteName = str_replace($this->adminName . '.', '', $this->routeNameWithPrefix);
$routeData = explode('.', $this->currentRouteName);
$this->currentPrefix = $routeData[0] ?? '';
} }
/** /**
@ -35,81 +46,168 @@ class Sidebar extends Component
*/ */
public function render() public function render()
{ {
$adminName = $this->adminName; $this->links = $this->getMenus();
$routeNameWithPrefix = request()->route()->getName(); $this->handleMenus();
$routeName = str_replace($adminName . '.', '', $routeNameWithPrefix); $this->currentLink = $this->getCurrentLink();
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);
}
} elseif (Str::startsWith($routeName, $this->getInquirySubPrefix())) {
$routes = $this->getInquirySubRoutes();
foreach ($routes as $route) {
$this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
}
}
return view('admin::components.sidebar'); return view('admin::components.sidebar');
} }
/** /**
* 添加左侧菜单链接 * 返回所有菜单
* 小图标地址 https://icons.getbootstrap.com/
* *
* @param $routeData * @return mixed
* @param $active
* @param false $newWindow
* @param int $hide_mobile
*/ */
private function addLink($routeData, $active, bool $newWindow = false, int $hide_mobile = 0) private function getMenus(): mixed
{ {
$route = $routeData['route']; $menus = [
$icon = $routeData['icon'] ?? ''; [
$title = $routeData['title'] ?? ''; 'route' => 'home.index',
'title' => trans('admin/common.home'),
$permissionRoute = str_replace('.', '_', $route); 'icon' => 'bi bi-house',
if ($this->adminUser->cannot($permissionRoute)) { 'prefixes' => $this->getHomeSubPrefix(),
return; ],
} [
'route' => 'orders.index',
if (empty($title)) { 'title' => trans('admin/common.order'),
$title = trans("admin/common.{$permissionRoute}"); 'icon' => 'bi bi-clipboard-check',
} 'prefixes' => $this->getOrderSubPrefix(),
$url = admin_route($route); 'children' => $this->getOrderSubRoutes(),
$this->links[] = [ ],
'title' => $title, [
'url' => $url, 'route' => 'products.index',
'icon' => $icon, 'title' => trans('admin/common.product'),
'active' => $active, 'icon' => 'bi bi-box-seam',
'hide_mobile' => $hide_mobile, 'prefixes' => $this->getProductSubPrefix(),
'new_window' => $newWindow, 'children' => $this->getProductSubRoutes(),
],
[
'route' => 'customers.index',
'title' => trans('admin/common.customer'),
'icon' => 'bi bi-person-circle',
'prefixes' => $this->getCustomerSubPrefix(),
'children' => $this->getCustomerSubRoutes(),
],
[
'route' => 'pages.index',
'title' => trans('admin/common.page'),
'icon' => 'bi bi-file-earmark-text',
'prefixes' => $this->getPageSubPrefix(),
'children' => $this->getPageSubRoutes(),
],
[
'route' => 'theme.index',
'title' => trans('admin/common.design'),
'icon' => 'bi bi-palette',
'prefixes' => $this->getDesignSubPrefix(),
'children' => $this->getDesignSubRoutes(),
],
[
'route' => 'plugins.index',
'title' => trans('admin/common.plugin'),
'icon' => 'bi bi-shop',
'prefixes' => $this->getPluginSubPrefix(),
'children' => $this->getPluginSubRoutes(),
],
[
'route' => 'settings.index',
'title' => trans('admin/common.setting'),
'icon' => 'bi bi-gear',
'prefixes' => $this->getSettingSubPrefix(),
'children' => $this->getSettingSubRoutes(),
],
[
'route' => 'inquiry.index',
'title' => trans('admin/common.inquiry'),
'icon' => 'bi bi-gear',
'prefixes' => $this->getInquirySubPrefix(),
'children' => $this->getInquirySubRoutes(),
],
]; ];
return hook_filter('admin.components.sidebar.menus', $menus);
}
/**
* 获取二级菜单
*
* @return array|null
*/
private function getCurrentLink(): array|null
{
foreach ($this->links as $link) {
$prefixes = $link['prefixes'] ?? [];
if ($prefixes && in_array($this->currentPrefix, $prefixes)) {
return $link;
}
}
return null;
}
/**
* 处理是否选中等数据
*/
private function handleMenus()
{
foreach ($this->links as $index => $link) {
$prefixes = $link['prefixes'] ?? [];
if ($prefixes && in_array($this->currentPrefix, $prefixes)) {
$this->links[$index]['active'] = true;
} else {
$this->links[$index]['active'] = false;
}
$url = $link['url'] ?? '';
if (empty($url)) {
$this->links[$index]['url'] = admin_route($link['route']);
}
$title = $link['title'] ?? '';
if (empty($title)) {
$permissionRoute = str_replace('.', '_', $this->currentRouteName);
$this->links[$index]['title'] = trans("admin/common.{$permissionRoute}");
}
if (! isset($link['blank'])) {
$this->links[$index]['blank'] = false;
}
$icon = $link['icon'] ?? '';
if (empty($icon)) {
$this->links[$index]['icon'] = 'bi bi-link-45deg';
}
$children = $link['children'] ?? [];
if ($children) {
foreach ($children as $key => $item) {
$childPrefixes = $item['prefixes'] ?? [];
$excludes = $item['excludes'] ?? [];
if ($prefixes && in_array($this->currentPrefix, $childPrefixes)
&& (! $excludes || ! in_array($this->currentRouteName, $excludes))) {
$this->links[$index]['children'][$key]['active'] = true;
} else {
$this->links[$index]['children'][$key]['active'] = false;
}
$url = $item['url'] ?? '';
if (empty($url)) {
$this->links[$index]['children'][$key]['url'] = admin_route($item['route']);
}
$title = $item['title'] ?? '';
if (empty($title)) {
$permissionRoute = str_replace('.', '_', $item['route']);
$this->links[$index]['children'][$key]['title'] = trans("admin/common.{$permissionRoute}");
}
if (! isset($item['blank'])) {
$this->links[$index]['children'][$key]['blank'] = false;
}
}
}
}
} }
/** /**
@ -117,7 +215,7 @@ class Sidebar extends Component
*/ */
private function getHomeSubPrefix() private function getHomeSubPrefix()
{ {
$prefix = ['home.']; $prefix = ['home'];
return hook_filter('admin.sidebar.home.prefix', $prefix); return hook_filter('admin.sidebar.home.prefix', $prefix);
} }
@ -127,7 +225,7 @@ class Sidebar extends Component
*/ */
private function getProductSubPrefix() private function getProductSubPrefix()
{ {
$prefix = ['products.', 'categories.', 'brands.', 'attribute_groups.', 'attributes.']; $prefix = ['products', 'multi_filter', 'categories', 'brands', 'attribute_groups', 'attributes'];
return hook_filter('admin.sidebar.product.prefix', $prefix); return hook_filter('admin.sidebar.product.prefix', $prefix);
} }
@ -137,7 +235,7 @@ class Sidebar extends Component
*/ */
private function getCustomerSubPrefix() private function getCustomerSubPrefix()
{ {
$prefix = ['customers.', 'customer_groups.']; $prefix = ['customers', 'customer_groups'];
return hook_filter('admin.sidebar.customer.prefix', $prefix); return hook_filter('admin.sidebar.customer.prefix', $prefix);
} }
@ -147,7 +245,7 @@ class Sidebar extends Component
*/ */
private function getOrderSubPrefix() private function getOrderSubPrefix()
{ {
$prefix = ['orders.', 'rmas.', 'rma_reasons.']; $prefix = ['orders', 'rmas', 'rma_reasons'];
return hook_filter('admin.sidebar.order.prefix', $prefix); return hook_filter('admin.sidebar.order.prefix', $prefix);
} }
@ -157,7 +255,7 @@ class Sidebar extends Component
*/ */
private function getPageSubPrefix() private function getPageSubPrefix()
{ {
$prefix = ['pages.', 'page_categories.']; $prefix = ['pages', 'page_categories'];
return hook_filter('admin.sidebar.page.prefix', $prefix); return hook_filter('admin.sidebar.page.prefix', $prefix);
} }
@ -172,12 +270,35 @@ class Sidebar extends Component
return hook_filter('admin.sidebar.page.prefix', $prefix); return hook_filter('admin.sidebar.page.prefix', $prefix);
} }
/**
* 获取后台设计子页面路由前缀列表
*/
private function getDesignSubPrefix()
{
$prefix = ['theme', 'design_menu'];
return hook_filter('admin.sidebar.design.prefix', $prefix);
}
/**
* 获取后台设计子页面路由前缀列表
*/
private function getPluginSubPrefix()
{
$prefix = ['plugins', 'marketing'];
return hook_filter('admin.sidebar.plugin.prefix', $prefix);
}
/** /**
* 获取后台系统设置子页面路由前缀列表 * 获取后台系统设置子页面路由前缀列表
*/ */
private function getSettingSubPrefix() private function getSettingSubPrefix()
{ {
$prefix = ['settings.', 'admin_users.', 'admin_roles.', 'plugins.', 'theme.', 'marketing.', 'tax_classes', 'tax_rates', 'regions', 'currencies', 'languages', 'design_menu', 'countries', 'zones']; $prefix = [
'settings', 'admin_users', 'admin_roles', 'tax_classes', 'tax_rates',
'regions', 'currencies', 'languages', 'countries', 'zones', 'account',
];
return hook_filter('admin.sidebar.setting.prefix', $prefix); return hook_filter('admin.sidebar.setting.prefix', $prefix);
} }
@ -187,14 +308,7 @@ class Sidebar extends Component
*/ */
public function getHomeSubRoutes() public function getHomeSubRoutes()
{ {
$routes = [ $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); return hook_filter('admin.sidebar.home_routes', $routes);
} }
@ -205,12 +319,13 @@ class Sidebar extends Component
public function getProductSubRoutes() public function getProductSubRoutes()
{ {
$routes = [ $routes = [
['route' => 'categories.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'products.index', 'prefixes' => ['products'], 'excludes' => ['products.trashed']],
['route' => 'products.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'categories.index', 'prefixes' => ['categories']],
['route' => 'brands.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1], ['route' => 'brands.index', 'prefixes' => ['brands']],
['route' => 'attribute_groups.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'attribute_groups.index', 'prefixes' => ['attribute_groups']],
['route' => 'attributes.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'attributes.index', 'prefixes' => ['attributes']],
['route' => 'products.trashed', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'multi_filter.index', 'prefixes' => ['multi_filter']],
['route' => 'products.trashed', 'prefixes' => ['products'], 'excludes' => ['products.index', 'products.edit']],
]; ];
return hook_filter('admin.sidebar.product_routes', $routes); return hook_filter('admin.sidebar.product_routes', $routes);
@ -222,9 +337,9 @@ class Sidebar extends Component
public function getCustomerSubRoutes() public function getCustomerSubRoutes()
{ {
$routes = [ $routes = [
['route' => 'customers.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'customers.index', 'prefixes' => ['customers'], 'excludes' => ['customers.trashed']],
['route' => 'customer_groups.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'customer_groups.index', 'prefixes' => ['customer_groups']],
['route' => 'customers.trashed', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'customers.trashed', 'prefixes' => ['customers'], 'excludes' => ['customers.index', 'customers.edit']],
]; ];
return hook_filter('admin.sidebar.customer_routes', $routes); return hook_filter('admin.sidebar.customer_routes', $routes);
@ -236,9 +351,9 @@ class Sidebar extends Component
public function getOrderSubRoutes() public function getOrderSubRoutes()
{ {
$routes = [ $routes = [
['route' => 'orders.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'orders.index', 'prefixes' => ['orders']],
['route' => 'rmas.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'rmas.index', 'prefixes' => ['rmas']],
['route' => 'rma_reasons.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'rma_reasons.index', 'prefixes' => ['rma_reasons']],
]; ];
return hook_filter('admin.sidebar.order_routes', $routes); return hook_filter('admin.sidebar.order_routes', $routes);
@ -251,8 +366,8 @@ class Sidebar extends Component
public function getPageSubRoutes() public function getPageSubRoutes()
{ {
$routes = [ $routes = [
['route' => 'page_categories.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'pages.index', 'prefixes' => ['pages']],
['route' => 'pages.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'page_categories.index', 'prefixes' => ['page_categories']],
]; ];
return hook_filter('admin.sidebar.pages_routes', $routes); return hook_filter('admin.sidebar.pages_routes', $routes);
@ -271,6 +386,46 @@ class Sidebar extends Component
return hook_filter('admin.sidebar.pages_routes', $routes); return hook_filter('admin.sidebar.pages_routes', $routes);
} }
/**
* 获取设计子页面路由
* @return mixed
*/
public function getDesignSubRoutes()
{
$routes = [
['route' => 'theme.index', 'prefixes' => ['theme'], 'hide_mobile' => true],
['route' => 'design_menu.index', 'prefixes' => ['design_menu'], 'hide_mobile' => 1],
['route' => 'design.index', 'prefixes' => ['design'], 'blank' => true, 'hide_mobile' => true],
['route' => 'design_footer.index', 'prefixes' => ['design_footer'], 'blank' => true, 'hide_mobile' => true],
];
return hook_filter('admin.sidebar.design_routes', $routes);
}
/**
* 获取插件子页面路由
* @return mixed
*/
public function getPluginSubRoutes()
{
$types = collect(Plugin::TYPES);
$types = $types->map(function ($item) {
return 'plugins.' . $item;
});
$routes[] = ['route' => 'plugins.index', 'prefixes' => ['plugins'], 'excludes' => $types->toArray()];
$originTypes = $types->push('plugins.index', 'plugins.edit')->push();
foreach (Plugin::TYPES as $type) {
$types = $originTypes->reject("plugins.{$type}");
$routes[] = ['route' => "plugins.{$type}", 'prefixes' => ['plugins'], 'title' => trans("admin/plugin.{$type}"), 'excludes' => $types->toArray()];
}
$routes[] = ['route' => 'marketing.index', 'prefixes' => ['marketing']];
return hook_filter('admin.sidebar.plugins_routes', $routes);
}
/** /**
* 获取系统设置子页面路由 * 获取系统设置子页面路由
* @return mixed * @return mixed
@ -278,36 +433,18 @@ class Sidebar extends Component
public function getSettingSubRoutes() public function getSettingSubRoutes()
{ {
$routes = [ $routes = [
['route' => 'settings.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'settings.index', 'prefixes' => ['settings']],
['route' => 'admin_users.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'account.index', 'prefixes' => ['account']],
['route' => 'plugins.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1], ['route' => 'admin_users.index', 'prefixes' => ['admin_users', 'admin_roles']],
['route' => 'theme.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1], ['route' => 'regions.index', 'prefixes' => ['regions']],
['route' => 'marketing.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1], ['route' => 'tax_rates.index', 'prefixes' => ['tax_rates']],
['route' => 'regions.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'tax_classes.index', 'prefixes' => ['tax_classes']],
['route' => 'tax_rates.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'currencies.index', 'prefixes' => ['currencies']],
['route' => 'tax_classes.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'languages.index', 'prefixes' => ['languages']],
['route' => 'currencies.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'countries.index', 'prefixes' => ['countries']],
['route' => 'languages.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'zones.index', 'prefixes' => ['zones']],
['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); 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;
}
} }

View File

@ -11,8 +11,8 @@
return [ return [
'api_url' => env('BEIKE_API_URL', 'https://beikeshop.com'), 'api_url' => env('BEIKE_API_URL', 'https://beikeshop.com'),
'version' => '1.3.4', 'version' => '1.3.8',
'build' => '20230324', 'build' => '20230719',
'admin_name' => env('ADMIN_NAME'), 'admin_name' => env('ADMIN_NAME'),
'force_url_https' => env('APP_FORCE_HTTPS', false), 'force_url_https' => env('APP_FORCE_HTTPS', false),

View File

@ -0,0 +1,16 @@
<?php
/**
* CartException.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-06-02 17:08:18
* @modified 2023-06-02 17:08:18
*/
namespace Beike\Exceptions;
class CartException extends \Exception
{
}

View File

@ -0,0 +1,16 @@
<?php
/**
* InvalidException.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-05-25 14:48:12
* @modified 2023-05-25 14:48:12
*/
namespace Beike\Exceptions;
class InvalidException extends \Exception
{
}

View File

@ -11,6 +11,7 @@ use Beike\Repositories\CurrencyRepo;
use Beike\Repositories\LanguageRepo; use Beike\Repositories\LanguageRepo;
use Beike\Services\CurrencyService; use Beike\Services\CurrencyService;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
@ -209,7 +210,12 @@ function equal_route($routeName): bool
*/ */
function current_user(): ?AdminUser function current_user(): ?AdminUser
{ {
return auth()->guard(AdminUser::AUTH_GUARD)->user(); $user = auth()->guard(AdminUser::AUTH_GUARD)->user();
if (empty($user)) {
$user = registry('admin_user');
}
return $user;
} }
/** /**
@ -515,25 +521,28 @@ function quantity_format($quantity)
/** /**
* 返回json序列化结果 * 返回json序列化结果
*/ */
function json_success($message, $data = []): array function json_success($message, $data = [])
{ {
return [ return [
'status' => 'success', 'status' => 'success',
'message' => $message, 'message' => $message,
'data' => $data, 'data' => $data,
]; ];
} }
/** /**
* 返回json序列化结果 * 返回json序列化结果
*/ */
function json_fail($message, $data = []): array function json_fail($message, $data = [], $status = 422): JsonResponse
{ {
return [ $data = [
'status' => 'fail', 'status' => 'fail',
'message' => $message, 'message' => $message,
'data' => $data, 'data' => $data,
]; ];
return response()->json($data, $status);
} }
if (! function_exists('sub_string')) { if (! function_exists('sub_string')) {
@ -768,3 +777,42 @@ function list_sort_by($list, $field, $sortby='asc') {
return false; return false;
} }
/**
* @param $key
* @param $value
* @param bool $force
*/
function register($key, $value, bool $force = false)
{
\Beike\Libraries\Registry::set($key, $value, $force);
}
/**
* @param $key
* @param null $default
* @return mixed
*/
function registry($key, $default = null): mixed
{
return \Beike\Libraries\Registry::get($key, $default);
}
/**
* Check domain ha license.
* 删除版权信息, 请先购买授权 https://beikeshop.com/vip/subscription
*
* @return bool
*/
function check_license(): bool
{
$configLicenceCode = system_setting('base.license_code');
$appDomain = config('app.url');
$domain = new \Utopia\Domains\Domain($appDomain);
$registerDomain = $domain->getRegisterable();
if (empty($registerDomain)) {
return true;
}
return $configLicenceCode == md5(mb_substr(md5($registerDomain), 2, 8));
}

View File

@ -64,7 +64,6 @@ class Hook
return $this->get($hook, $params, $callback, $htmlContent); return $this->get($hook, $params, $callback, $htmlContent);
} }
/** /**
* @param string $hook * @param string $hook
* @param array $params * @param array $params
@ -114,11 +113,15 @@ class Hook
$priority = null; $priority = null;
} }
if (isset($this->watch[$hook][$priority])) {
$priority++;
}
$this->watch[$hook][$priority] = [ $this->watch[$hook][$priority] = [
'function' => $function, 'function' => $function,
'caller' => [ 'caller' => [
//'file' => $caller['file'], 'file' => $caller['file'],
//'line' => $caller['line'], 'line' => $caller['line'],
'class' => Arr::get($caller, 'class'), 'class' => Arr::get($caller, 'class'),
], ],
]; ];

View File

@ -5,6 +5,7 @@ namespace Beike\Hook;
use Beike\Hook\Console\HookListeners; use Beike\Hook\Console\HookListeners;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
class HookServiceProvider extends ServiceProvider class HookServiceProvider extends ServiceProvider
{ {
@ -19,7 +20,6 @@ class HookServiceProvider extends ServiceProvider
}); });
} }
public function boot() public function boot()
{ {
$this->bootHookDirectives(); $this->bootHookDirectives();
@ -37,6 +37,7 @@ class HookServiceProvider extends ServiceProvider
$parameters = explode(',', $parameter); $parameters = explode(',', $parameter);
$name = trim($parameters[0], "'"); $name = trim($parameters[0], "'");
$definedVars = $this->parseParameters($parameters);
return ' <?php return ' <?php
$__definedVars = (get_defined_vars()["__data"]); $__definedVars = (get_defined_vars()["__data"]);
@ -44,6 +45,7 @@ class HookServiceProvider extends ServiceProvider
{ {
$__definedVars = []; $__definedVars = [];
} }
'. $definedVars .'
$output = \Hook::getHook("' . $name . '",["data"=>$__definedVars],function($data) { return null; }); $output = \Hook::getHook("' . $name . '",["data"=>$__definedVars],function($data) { return null; });
if ($output) if ($output)
echo $output; echo $output;
@ -51,7 +53,6 @@ class HookServiceProvider extends ServiceProvider
}); });
} }
/** /**
* 添加 blade wrapper hook 标签 * 添加 blade wrapper hook 标签
* *
@ -86,4 +87,23 @@ class HookServiceProvider extends ServiceProvider
?>'; ?>';
}); });
} }
/**
* Parse parameters from Blade
*
* @param $parameters
* @return string
*/
protected function parseParameters($parameters):string
{
$definedVars = '';
foreach ($parameters as $paraItem) {
$paraItem = trim($paraItem);
if (Str::startsWith($paraItem,'$')) {
$paraKey = trim($paraItem, '$');
$definedVars .= '$__definedVars["'.$paraKey.'"] = $'.$paraKey.';';
}
}
return $definedVars;
}
} }

View File

@ -0,0 +1,15 @@
<?php
namespace Beike\Installer\Controllers;
use Illuminate\Routing\Controller;
class BaseController extends Controller
{
protected function checkInstalled()
{
if (installed()) {
exit('Already installed');
}
}
}

View File

@ -5,11 +5,10 @@ namespace Beike\Installer\Controllers;
use Beike\Admin\Repositories\AdminUserRepo; use Beike\Admin\Repositories\AdminUserRepo;
use Beike\Installer\Helpers\DatabaseManager; use Beike\Installer\Helpers\DatabaseManager;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
class DatabaseController extends Controller class DatabaseController extends BaseController
{ {
/** /**
* @var DatabaseManager * @var DatabaseManager
@ -31,6 +30,7 @@ class DatabaseController extends Controller
*/ */
public function index() public function index()
{ {
$this->checkInstalled();
DB::statement('SET FOREIGN_KEY_CHECKS = 0'); DB::statement('SET FOREIGN_KEY_CHECKS = 0');
$rows = DB::select('SHOW TABLES'); $rows = DB::select('SHOW TABLES');
$database = config('database.connections.mysql.database'); $database = config('database.connections.mysql.database');

View File

@ -3,14 +3,14 @@
namespace Beike\Installer\Controllers; namespace Beike\Installer\Controllers;
use Beike\Installer\Helpers\EnvironmentManager; use Beike\Installer\Helpers\EnvironmentManager;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Routing\Redirector; use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
class EnvironmentController extends Controller class EnvironmentController extends BaseController
{ {
/** /**
* @var EnvironmentManager * @var EnvironmentManager
@ -32,6 +32,7 @@ class EnvironmentController extends Controller
*/ */
public function index() public function index()
{ {
$this->checkInstalled();
$steps = 4; $steps = 4;
return view('installer::environment-wizard', compact('steps')); return view('installer::environment-wizard', compact('steps'));
@ -46,6 +47,7 @@ class EnvironmentController extends Controller
*/ */
public function saveWizard(Request $request, Redirector $redirect): RedirectResponse public function saveWizard(Request $request, Redirector $redirect): RedirectResponse
{ {
$this->checkInstalled();
$rules = config('installer.environment.form.rules'); $rules = config('installer.environment.form.rules');
$messages = [ $messages = [
'environment_custom.required_if' => trans('installer::installer_messages.environment.name_required'), 'environment_custom.required_if' => trans('installer::installer_messages.environment.name_required'),
@ -74,10 +76,11 @@ class EnvironmentController extends Controller
* 数据库信息检测 * 数据库信息检测
* *
* @param Request $request * @param Request $request
* @return array * @return JsonResponse|array
*/ */
public function validateDatabase(Request $request): array public function validateDatabase(Request $request): JsonResponse|array
{ {
$this->checkInstalled();
$rules = config('installer.environment.form.rules'); $rules = config('installer.environment.form.rules');
$messages = [ $messages = [
'environment_custom.required_if' => trans('installer::installer_messages.environment.name_required'), 'environment_custom.required_if' => trans('installer::installer_messages.environment.name_required'),
@ -108,6 +111,7 @@ class EnvironmentController extends Controller
*/ */
private function checkDatabaseConnection(Request $request): bool|array private function checkDatabaseConnection(Request $request): bool|array
{ {
$this->checkInstalled();
$connection = $request->input('database_connection'); $connection = $request->input('database_connection');
$settings = config("database.connections.$connection"); $settings = config("database.connections.$connection");

View File

@ -5,9 +5,8 @@ namespace Beike\Installer\Controllers;
use Beike\Installer\Helpers\EnvironmentManager; use Beike\Installer\Helpers\EnvironmentManager;
use Beike\Installer\Helpers\FinalInstallManager; use Beike\Installer\Helpers\FinalInstallManager;
use Beike\Installer\Helpers\InstalledFileManager; use Beike\Installer\Helpers\InstalledFileManager;
use Illuminate\Routing\Controller;
class FinalController extends Controller class FinalController extends BaseController
{ {
/** /**
* Update installed file and display finished view. * Update installed file and display finished view.
@ -19,6 +18,8 @@ class FinalController extends Controller
*/ */
public function index(InstalledFileManager $fileManager, FinalInstallManager $finalInstall, EnvironmentManager $environment) public function index(InstalledFileManager $fileManager, FinalInstallManager $finalInstall, EnvironmentManager $environment)
{ {
$this->checkInstalled();
$finalMessages = $finalInstall->runFinal(); $finalMessages = $finalInstall->runFinal();
$finalStatusMessage = $fileManager->update(); $finalStatusMessage = $fileManager->update();
$finalEnvFile = $environment->getEnvContent(); $finalEnvFile = $environment->getEnvContent();

View File

@ -3,9 +3,8 @@
namespace Beike\Installer\Controllers; namespace Beike\Installer\Controllers;
use Beike\Installer\Helpers\PermissionsChecker; use Beike\Installer\Helpers\PermissionsChecker;
use Illuminate\Routing\Controller;
class PermissionsController extends Controller class PermissionsController extends BaseController
{ {
/** /**
* @var PermissionsChecker * @var PermissionsChecker
@ -27,6 +26,7 @@ class PermissionsController extends Controller
*/ */
public function index() public function index()
{ {
$this->checkInstalled();
$permissions = $this->permissions->check( $permissions = $this->permissions->check(
config('installer.permissions') config('installer.permissions')
); );

View File

@ -3,9 +3,8 @@
namespace Beike\Installer\Controllers; namespace Beike\Installer\Controllers;
use Beike\Installer\Helpers\RequirementsChecker; use Beike\Installer\Helpers\RequirementsChecker;
use Illuminate\Routing\Controller;
class RequirementsController extends Controller class RequirementsController extends BaseController
{ {
/** /**
* @var RequirementsChecker * @var RequirementsChecker
@ -27,6 +26,7 @@ class RequirementsController extends Controller
*/ */
public function index() public function index()
{ {
$this->checkInstalled();
$phpSupportInfo = $this->requirements->checkPHPversion( $phpSupportInfo = $this->requirements->checkPHPversion(
config('installer.core.minPhpVersion') config('installer.core.minPhpVersion')
); );

View File

@ -11,10 +11,9 @@
namespace Beike\Installer\Controllers; namespace Beike\Installer\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Redirect;
class WelcomeController extends Controller class WelcomeController extends BaseController
{ {
private $languages = [ private $languages = [
'zh_cn' => '简体中文', 'zh_cn' => '简体中文',
@ -23,10 +22,7 @@ class WelcomeController extends Controller
public function index() public function index()
{ {
if (installed()) { $this->checkInstalled();
exit('Already installed');
}
$data['languages'] = $this->languages; $data['languages'] = $this->languages;
$data['locale'] = $_COOKIE['locale'] ?? 'zh_cn'; $data['locale'] = $_COOKIE['locale'] ?? 'zh_cn';
$data['steps'] = 1; $data['steps'] = 1;

View File

@ -230,7 +230,12 @@
$('.database-loading').addClass('d-none'); $('.database-loading').addClass('d-none');
}, },
success: function(json) { success: function(json) {
if (json.status == 'fail') { $('.database-link-wrap input').addClass('is-valid')
$('.title-status .text-success').removeClass('d-none')
$('.admin-data-wrap').removeClass('d-none')
},
error: function(json) {
json = json.responseJSON;
var data = Object.keys(json.data); var data = Object.keys(json.data);
data.forEach((e)=> { data.forEach((e)=> {
@ -247,13 +252,6 @@
$('.admin-data-wrap').addClass('d-none') $('.admin-data-wrap').addClass('d-none')
} }
if (json.status == 'success') {
$('.database-link-wrap input').addClass('is-valid')
$('.title-status .text-success').removeClass('d-none')
$('.admin-data-wrap').removeClass('d-none')
}
}
}); });
} }
</script> </script>

View File

@ -15,8 +15,7 @@
<script src="{{ asset('vendor/jquery/jquery-3.6.0.min.js') }}"></script> <script src="{{ asset('vendor/jquery/jquery-3.6.0.min.js') }}"></script>
<script src="{{ asset('vendor/layer/3.5.1/layer.js') }}"></script> <script src="{{ asset('vendor/layer/3.5.1/layer.js') }}"></script>
<link rel="shortcut icon" href="{{ asset('/image/favicon.png') }}"> <link rel="shortcut icon" href="{{ asset('/image/favicon.png') }}">
{{-- <script src="{{ asset('vendor/bootstrap/5.1.3/js/bootstrap.min.js') }}"></script> --}} <script src="{{ asset('vendor/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<script src="{{ asset('vendor/bootstrap/5.1.3/js/bootstrap.bundle.min.js') }}"></script>
<link rel="stylesheet" type="text/css" href="{{ asset('/install/css/app.css') }}"> <link rel="stylesheet" type="text/css" href="{{ asset('/install/css/app.css') }}">
@yield('style') @yield('style')
</head> </head>

View File

@ -11,6 +11,8 @@
$primary: #fd560f; $primary: #fd560f;
body { body {
background-color: #f3f6f8;
.install-box { .install-box {
background-color: #f3f6f8; background-color: #f3f6f8;
display: flex; display: flex;
@ -45,6 +47,10 @@ body {
} }
} }
} }
.table {
--bs-table-bg: transparent;
}
} }
@import "steps"; @import "steps";

View File

@ -34,6 +34,9 @@ return [
'PDO', 'PDO',
'Tokenizer', 'Tokenizer',
'XML', 'XML',
'ZIP',
'GD',
'PDO_MYSQL',
], ],
'apache' => [ 'apache' => [
'mod_rewrite', 'mod_rewrite',

View File

@ -0,0 +1,84 @@
<?php
/**
* Registry.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-20 16:29:54
* @modified 2023-04-20 16:29:54
*/
namespace Beike\Libraries;
class Registry
{
private array $data = [];
private static $registry;
public static function getSingleton(): self
{
if (self::$registry instanceof self) {
return self::$registry;
}
return self::$registry = new self();
}
/**
* @param $key
* @param null $default
* @return mixed
*/
public static function get($key, $default = null): mixed
{
return self::getSingleton()->getValue($key, $default);
}
/**
* @param $key
* @param $value
* @param bool $force
*/
public static function set($key, $value, bool $force = false)
{
if (self::getSingleton()->has($key) && ! $force) {
return;
}
self::getSingleton()->setValue($key, $value);
}
public function destroy()
{
self::$registry = null;
}
/**
* @param $key
* @param null $default
* @return mixed
*/
public function getValue($key, $default = null): mixed
{
return $this->data[$key] ?? $default;
}
/**
* @param $key
* @param $value
*/
public function setValue($key, $value)
{
$this->data[$key] = $value;
}
/**
* @param $key
* @return bool
*/
public function has($key): bool
{
return isset($this->data[$key]);
}
}

View File

@ -65,7 +65,7 @@ class Url
return $value->url ?? ''; return $value->url ?? '';
} elseif ($type == 'page') { } elseif ($type == 'page') {
if (! $value instanceof \Beike\Models\Page) { if (! $value instanceof \Beike\Models\Page) {
$value = \Beike\Models\Page::query()->find($value); $value = \Beike\Models\Page::query()->where('active', 1)->find($value);
} }
return $value->url ?? ''; return $value->url ?? '';

View File

@ -0,0 +1,45 @@
<?php
/**
* Weight.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2023-03-21 16:29:54
* @modified 2023-03-21 16:29:54
*/
namespace Beike\Libraries;
class Weight
{
public const WEIGHT_CLASS = [
'kg' => 0.001,
'g' => 1,
'oz' => 0.035,
'lb' => 0.0022046,
];
public const DEFAULT_CLASS = 'g';
public function __construct()
{
}
public static function getWeightUnits(): array
{
return array_keys(self::WEIGHT_CLASS);
}
public static function convert($weight, $from, $to = '')
{
if (! $to) {
$to = self::DEFAULT_CLASS;
}
if (empty($weight)) {
return 0;
}
return $weight * self::WEIGHT_CLASS[$to] / self::WEIGHT_CLASS[$from];
}
}

View File

@ -4,11 +4,13 @@ namespace Beike\Models;
use Beike\Notifications\AdminForgottenNotification; use Beike\Notifications\AdminForgottenNotification;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as AuthUser; use Illuminate\Foundation\Auth\User as AuthUser;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles; use Spatie\Permission\Traits\HasRoles;
use Tymon\JWTAuth\Contracts\JWTSubject;
class AdminUser extends AuthUser class AdminUser extends AuthUser implements JWTSubject
{ {
use HasFactory, HasRoles; use HasFactory, HasRoles;
use Notifiable; use Notifiable;
@ -17,6 +19,11 @@ class AdminUser extends AuthUser
protected $fillable = ['name', 'email', 'locale', 'password', 'active']; protected $fillable = ['name', 'email', 'locale', 'password', 'active'];
public function tokens(): HasMany
{
return $this->hasMany(AdminUserToken::class);
}
public function notifyVerifyCodeForForgotten($code) public function notifyVerifyCodeForForgotten($code)
{ {
$useQueue = system_setting('base.use_queue', true); $useQueue = system_setting('base.use_queue', true);
@ -26,4 +33,24 @@ class AdminUser extends AuthUser
$this->notifyNow(new AdminForgottenNotification($this, $code)); $this->notifyNow(new AdminForgottenNotification($this, $code));
} }
} }
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
} }

View File

@ -0,0 +1,24 @@
<?php
/**
* AdminUserToken.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-20 10:18:56
* @modified 2023-04-20 10:18:56
*/
namespace Beike\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class AdminUserToken extends Base
{
protected $fillable = ['admin_user_id', 'token'];
public function adminUser(): BelongsTo
{
return $this->belongsTo(AdminUser::class);
}
}

View File

@ -11,8 +11,9 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class Customer extends Authenticatable class Customer extends Authenticatable implements JWTSubject
{ {
use HasFactory; use HasFactory;
use SoftDeletes; use SoftDeletes;
@ -66,4 +67,24 @@ class Customer extends Authenticatable
$this->notifyNow(new ForgottenNotification($this, $code)); $this->notifyNow(new ForgottenNotification($this, $code));
} }
} }
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
} }

View File

@ -59,6 +59,11 @@ class Order extends Base
return $this->hasMany(OrderShipment::class); return $this->hasMany(OrderShipment::class);
} }
public function orderPayments(): HasMany
{
return $this->hasMany(OrderPayment::class);
}
public function subTotal() public function subTotal()
{ {
$totals = $this->orderTotals; $totals = $this->orderTotals;
@ -68,7 +73,9 @@ class Order extends Base
public function getStatusFormatAttribute() public function getStatusFormatAttribute()
{ {
return trans('order.' . $this->status); $statusMap = array_column(StateMachineService::getAllStatuses(), 'name', 'status');
return $statusMap[$this->status];
} }
public function getTotalFormatAttribute() public function getTotalFormatAttribute()

View File

@ -11,6 +11,8 @@
namespace Beike\Models; namespace Beike\Models;
use Beike\Services\StateMachineService;
class OrderHistory extends Base class OrderHistory extends Base
{ {
protected $fillable = [ protected $fillable = [
@ -21,6 +23,8 @@ class OrderHistory extends Base
public function getStatusFormatAttribute() public function getStatusFormatAttribute()
{ {
return trans("order.{$this->status}"); $statusMap = array_column(StateMachineService::getAllStatuses(), 'name', 'status');
return $statusMap[$this->status];
} }
} }

View File

@ -0,0 +1,21 @@
<?php
/**
* OrderPayment.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-05-25 10:02:52
* @modified 2023-05-25 10:02:52
*/
namespace Beike\Models;
class OrderPayment extends Base
{
protected $table = 'order_payments';
protected $fillable = [
'order_id', 'transaction_id', 'request', 'response', 'callback', 'receipt',
];
}

View File

@ -11,12 +11,11 @@
namespace Beike\Models; namespace Beike\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
class PageCategory extends Model class PageCategory extends Base
{ {
protected $table = 'page_categories'; protected $table = 'page_categories';

View File

@ -11,9 +11,7 @@
namespace Beike\Models; namespace Beike\Models;
use Illuminate\Database\Eloquent\Model; class PageCategoryDescription extends Base
class PageCategoryDescription extends Model
{ {
protected $table = 'page_category_descriptions'; protected $table = 'page_category_descriptions';

View File

@ -11,10 +11,9 @@
namespace Beike\Models; namespace Beike\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
class PageProduct extends Model class PageProduct extends Base
{ {
protected $table = 'page_products'; protected $table = 'page_products';

View File

@ -11,7 +11,7 @@ class Product extends Base
use HasFactory; use HasFactory;
use SoftDeletes; use SoftDeletes;
protected $fillable = ['images', 'video', 'position', 'brand_id', 'tax_class_id', 'active', 'variables', 'price_setting']; protected $fillable = ['images', 'video', 'position', 'brand_id', 'tax_class_id', 'weight', 'weight_class', 'active', 'variables', 'price_setting'];
protected $casts = [ protected $casts = [
'active' => 'boolean', 'active' => 'boolean',

View File

@ -36,6 +36,11 @@ class ProductSku extends Base
$product = $this->product; $product = $this->product;
$localeCode = locale(); $localeCode = locale();
$variantLabel = ''; $variantLabel = '';
if(empty($product->variables)) {
return '';
}
foreach ($product->variables as $index => $variable) { foreach ($product->variables as $index => $variable) {
$valueIndex = $this->variants[$index]; $valueIndex = $this->variants[$index];
$variantName = $variable['name'][$localeCode] ?? ''; $variantName = $variable['name'][$localeCode] ?? '';

View File

@ -21,13 +21,13 @@ use Illuminate\Support\Str;
class Plugin implements Arrayable, \ArrayAccess class Plugin implements Arrayable, \ArrayAccess
{ {
public const TYPES = [ public const TYPES = [
'shipping', // 配送方式
'payment', // 支付方式 'payment', // 支付方式
'shipping', // 配送方式
'theme', // 主题模板
'feature', // 功能模块
'total', // 订单金额 'total', // 订单金额
'social', // 社交网络 'social', // 社交网络
'feature', // 功能模块
'language', // 语言翻译 'language', // 语言翻译
'theme', // 主题模板
]; ];
protected $type; protected $type;
@ -171,6 +171,13 @@ class Plugin implements Arrayable, \ArrayAccess
$item['label'] = trans($languageKey); $item['label'] = trans($languageKey);
} }
$descriptionKey = $item['description_key'] ?? '';
$description = $item['description'] ?? '';
if (empty($description) && $descriptionKey) {
$languageKey = "{$this->dirName}::{$descriptionKey}";
$item['description'] = trans($languageKey);
}
return $item; return $item;
} }

View File

@ -0,0 +1,82 @@
<?php
/**
* AdminUserTokenRepo.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-04-20 10:21:25
* @modified 2023-04-20 10:21:25
*/
namespace Beike\Repositories;
use Beike\Models\AdminUser;
use Beike\Models\AdminUserToken;
class AdminUserTokenRepo
{
/**
* @param $adminUser
* @return mixed
*/
public static function getTokenByAdminUser($adminUser)
{
$adminUserId = self::getAdminUserId($adminUser);
if (empty($adminUserId)) {
return null;
}
return AdminUserToken::query()->where('admin_user_id', $adminUserId)->get();
}
/**
* @param $token
* @return mixed
*/
public static function getAdminUserTokenByToken($token)
{
return AdminUserToken::query()->where('token', $token)->first();
}
/**
* @param $adminUser
* @param $tokens
* @return void
*/
public static function updateTokensByUser($adminUser, $tokens)
{
$adminUserId = self::getAdminUserId($adminUser);
if (empty($adminUserId)) {
return;
}
AdminUserToken::query()->where('admin_user_id', $adminUserId)->delete();
if (empty($tokens)) {
return;
}
foreach ($tokens as $token) {
AdminUserToken::query()->create([
'admin_user_id' => $adminUserId,
'token' => $token,
]);
}
}
/**
* @param $adminUser
* @return int|mixed
*/
private static function getAdminUserId($adminUser)
{
$adminUserId = 0;
if ($adminUser instanceof AdminUser) {
$adminUserId = $adminUser->id;
} elseif (is_int($adminUser)) {
$adminUserId = $adminUser;
}
return $adminUserId;
}
}

View File

@ -55,14 +55,18 @@ class CategoryRepo
} }
} }
public static function flatten(string $locale, $separator = ' > '): array public static function flatten(string $locale, $includeInactive = true, $separator = ' > '): array
{ {
$sql = "SELECT cp.category_id AS id, TRIM(LOWER(GROUP_CONCAT(cd1.name ORDER BY cp.level SEPARATOR '{$separator}'))) AS name, c1.parent_id, c1.position"; $sql = "SELECT cp.category_id AS id, TRIM(LOWER(GROUP_CONCAT(cd1.name ORDER BY cp.level SEPARATOR '{$separator}'))) AS name, c1.parent_id, c1.position";
$sql .= ' FROM category_paths cp'; $sql .= ' FROM category_paths cp';
$sql .= ' LEFT JOIN categories c1 ON (cp.category_id = c1.id)'; $sql .= ' LEFT JOIN categories c1 ON (cp.category_id = c1.id)';
$sql .= ' LEFT JOIN categories c2 ON (cp.path_id = c2.id)'; $sql .= ' LEFT JOIN categories c2 ON (cp.path_id = c2.id)';
$sql .= ' LEFT JOIN category_descriptions cd1 ON (cp.path_id = cd1.category_id)'; $sql .= ' LEFT JOIN category_descriptions cd1 ON (cp.path_id = cd1.category_id)';
$sql .= " WHERE cd1.locale = '" . $locale . "' GROUP BY cp.category_id ORDER BY name ASC"; $sql .= " WHERE cd1.locale = '" . $locale . "' ";
if (! $includeInactive) {
$sql .= ' AND c1.active = 1 ';
}
$sql .= ' GROUP BY cp.category_id ORDER BY c1.position ASC';
return DB::select($sql); return DB::select($sql);
} }

View File

@ -98,6 +98,6 @@ class LanguageRepo
*/ */
public static function enabled() public static function enabled()
{ {
return Language::query()->where('status', true)->get(); return Language::query()->where('status', true)->orderBy('sort_order', 'asc')->get();
} }
} }

View File

@ -0,0 +1,61 @@
<?php
/**
* OrderPaymentRepo.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-05-25 10:02:39
* @modified 2023-05-25 10:02:39
*/
namespace Beike\Repositories;
use Beike\Models\OrderPayment;
class OrderPaymentRepo
{
/**
* @param $orderId
* @param $data
* @return mixed
* @throws \Throwable
*/
public static function createOrUpdatePayment($orderId, $data): mixed
{
$orderId = (int) $orderId;
if (empty($orderId) || empty($data)) {
return null;
}
$orderPayment = OrderPayment::query()->where('order_id', $orderId)->first();
if (empty($orderPayment)) {
$orderPayment = new OrderPayment();
}
$paymentData = [
'order_id' => $orderId,
];
if (isset($data['transaction_id'])) {
$paymentData['transaction_id'] = $data['transaction_id'];
}
if (isset($data['request'])) {
$paymentData['request'] = json_encode($data['request'] ?? []);
}
if (isset($data['response'])) {
$paymentData['response'] = json_encode($data['response'] ?? []);
}
if (isset($data['callback'])) {
$paymentData['callback'] = json_encode($data['callback'] ?? []);
}
if (isset($data['receipt'])) {
$paymentData['receipt'] = $data['receipt'];
}
$orderPayment->fill($paymentData);
$orderPayment->saveOrFail();
return $orderPayment;
}
}

View File

@ -31,7 +31,7 @@ class OrderRepo
*/ */
public static function filterAll(array $filters = []) public static function filterAll(array $filters = [])
{ {
$builder = self::getListBuilder($filters)->orderByDesc('created_at'); $builder = static::getListBuilder($filters)->orderByDesc('created_at');
return $builder->get(); return $builder->get();
} }
@ -44,7 +44,7 @@ class OrderRepo
*/ */
public static function getListByCustomer($customer): LengthAwarePaginator public static function getListByCustomer($customer): LengthAwarePaginator
{ {
$builder = self::getListBuilder(['customer' => $customer])->orderByDesc('created_at'); $builder = static::getListBuilder(['customer' => $customer])->orderByDesc('created_at');
return $builder->paginate(perPage()); return $builder->paginate(perPage());
} }
@ -56,7 +56,7 @@ class OrderRepo
*/ */
public static function getLatestOrders($customer, $limit) public static function getLatestOrders($customer, $limit)
{ {
return self::getListBuilder(['customer' => $customer]) return static::getListBuilder(['customer' => $customer])
->orderByDesc('created_at') ->orderByDesc('created_at')
->take($limit) ->take($limit)
->get(); ->get();
@ -68,7 +68,7 @@ class OrderRepo
*/ */
public static function filterOrders(array $filters = []): LengthAwarePaginator public static function filterOrders(array $filters = []): LengthAwarePaginator
{ {
$builder = self::getListBuilder($filters)->orderByDesc('created_at'); $builder = static::getListBuilder($filters)->orderByDesc('created_at');
return $builder->paginate(perPage()); return $builder->paginate(perPage());
} }
@ -79,7 +79,7 @@ class OrderRepo
*/ */
public static function getListBuilder(array $filters = []): Builder public static function getListBuilder(array $filters = []): Builder
{ {
$builder = Order::query()->with(['orderProducts']); $builder = Order::query()->with(['orderProducts'])->where('status', '<>', StateMachineService::CREATED);
$number = $filters['number'] ?? 0; $number = $filters['number'] ?? 0;
if ($number) { if ($number) {
@ -199,14 +199,12 @@ class OrderRepo
$shippingAddress = Address::query()->findOrFail($shippingAddressId); $shippingAddress = Address::query()->findOrFail($shippingAddressId);
$paymentAddress = Address::query()->findOrFail($paymentAddressId); $paymentAddress = Address::query()->findOrFail($paymentAddressId);
$email = $customer->email; $email = $customer->email;
} else { } else {
$shippingAddress = new Address($current['guest_shipping_address'] ?? []); $shippingAddress = new Address($current['guest_shipping_address'] ?? []);
$paymentAddress = new Address($current['guest_payment_address'] ?? []); $paymentAddress = new Address($current['guest_payment_address'] ?? []);
$email = $current['guest_shipping_address']['email']; $email = $current['guest_shipping_address']['email'];
} }
$shippingAddress->country = $shippingAddress->country->name ?? ''; $shippingAddress->country = $shippingAddress->country->name ?? '';
$shippingAddress->country_id = $shippingAddress->country->id ?? 0; $shippingAddress->country_id = $shippingAddress->country->id ?? 0;
$paymentAddress->country = $paymentAddress->country->name ?? ''; $paymentAddress->country = $paymentAddress->country->name ?? '';
@ -268,6 +266,8 @@ class OrderRepo
OrderProductRepo::createOrderProducts($order, $carts['carts']); OrderProductRepo::createOrderProducts($order, $carts['carts']);
OrderTotalRepo::createTotals($order, $totals); OrderTotalRepo::createTotals($order, $totals);
hook_filter('repository.order.create.after', ['order' => $order, 'data' => $data]);
return $order; return $order;
} }

View File

@ -31,12 +31,16 @@ class PageCategoryRepo
/** /**
* @param array $filters * @param array $filters
* @return LengthAwarePaginator * @return mixed
*/ */
public static function getActiveList(array $filters = []): LengthAwarePaginator public static function getActiveList(array $filters = []): mixed
{ {
$filters['is_active'] = 1; $filters['is_active'] = 1;
$limit = (int) ($filters['limit'] ?? 0);
$builder = self::getBuilder($filters); $builder = self::getBuilder($filters);
if ($limit > 0) {
return $builder->limit($limit)->get();
}
return $builder->paginate(perPage()); return $builder->paginate(perPage());
} }
@ -150,11 +154,20 @@ class PageCategoryRepo
} }
/** /**
* @param $page * @param $pageCategory
* @return string * @return string
*/ */
public static function getName($page) public static function getName($pageCategory): string
{ {
return $page->description->title ?? ''; if ($pageCategory instanceof PageCategory) {
return $pageCategory->description->title ?? '';
}
$pageCategoryId = (int) $pageCategory;
$pageCategory = PageCategory::query()->whereHas('description', function ($query) use ($pageCategoryId) {
$query->where('page_category_id', $pageCategoryId);
})->first();
return $pageCategory->description->title ?? '';
} }
} }

View File

@ -189,20 +189,6 @@ class PluginRepo
} }
} }
/**
* Get plugin by code
*
* @param $code
* @return mixed
*/
public static function getPlugin($code): mixed
{
$code = Str::camel($code);
$plugins = self::getPluginsByCode();
return $plugins->get($code);
}
/** /**
* 判断插件是否安装 * 判断插件是否安装
* *

View File

@ -55,7 +55,7 @@ class ProductRepo
*/ */
public static function getProductsByCategory($categoryId, $filterData) public static function getProductsByCategory($categoryId, $filterData)
{ {
$builder = self::getBuilder(array_merge(['category_id' => $categoryId, 'active' => 1], $filterData)); $builder = static::getBuilder(array_merge(['category_id' => $categoryId, 'active' => 1], $filterData));
return $builder->with('inCurrentWishlist') return $builder->with('inCurrentWishlist')
->paginate($filterData['per_page'] ?? perPage()) ->paginate($filterData['per_page'] ?? perPage())
@ -72,7 +72,7 @@ class ProductRepo
if (! $productIds) { if (! $productIds) {
return ProductSimple::collection(new Collection()); return ProductSimple::collection(new Collection());
} }
$builder = self::getBuilder(['product_ids' => $productIds])->whereHas('masterSku'); $builder = static::getBuilder(['product_ids' => $productIds])->whereHas('masterSku');
$products = $builder->with('inCurrentWishlist')->get(); $products = $builder->with('inCurrentWishlist')->get();
return ProductSimple::collection($products); return ProductSimple::collection($products);
@ -81,10 +81,11 @@ class ProductRepo
/** /**
* 获取商品筛选对象 * 获取商品筛选对象
* *
* @param array $data * @param array $filters
* @return Builder * @return Builder
* @throws \Exception
*/ */
public static function getBuilder(array $data = []): Builder public static function getBuilder(array $filters = []): Builder
{ {
$builder = Product::query()->with('description', 'skus', 'masterSku', 'attributes'); $builder = Product::query()->with('description', 'skus', 'masterSku', 'attributes');
@ -99,26 +100,26 @@ class ProductRepo
}); });
$builder->select(['products.*', 'pd.name', 'pd.content', 'pd.meta_title', 'pd.meta_description', 'pd.meta_keywords', 'pd.name', 'product_skus.price']); $builder->select(['products.*', 'pd.name', 'pd.content', 'pd.meta_title', 'pd.meta_description', 'pd.meta_keywords', 'pd.name', 'product_skus.price']);
if (isset($data['category_id'])) { if (isset($filters['category_id'])) {
$builder->whereHas('categories', function ($query) use ($data) { $builder->whereHas('categories', function ($query) use ($filters) {
if (is_array($data['category_id'])) { if (is_array($filters['category_id'])) {
$query->whereIn('category_id', $data['category_id']); $query->whereIn('category_id', $filters['category_id']);
} else { } else {
$query->where('category_id', $data['category_id']); $query->where('category_id', $filters['category_id']);
} }
}); });
} }
$productIds = $data['product_ids'] ?? []; $productIds = $filters['product_ids'] ?? [];
if ($productIds) { if ($productIds) {
$builder->whereIn('products.id', $productIds); $builder->whereIn('products.id', $productIds);
$productIds = implode(',', $productIds); $productIds = implode(',', $productIds);
$builder->orderByRaw("FIELD(products.id, {$productIds})"); $builder->orderByRaw("FIELD(products.id, {$productIds})");
} }
// attr 格式:attr=10:10/13|11:34/23|3:4 // attr 格式:attr=10:10,13|11:34,23|3:4
if (isset($data['attr']) && $data['attr']) { if (isset($filters['attr']) && $filters['attr']) {
$attributes = self::parseFilterParamsAttr($data['attr']); $attributes = self::parseFilterParamsAttr($filters['attr']);
foreach ($attributes as $attribute) { foreach ($attributes as $attribute) {
$builder->whereHas('attributes', function ($query) use ($attribute) { $builder->whereHas('attributes', function ($query) use ($attribute) {
$query->where('attribute_id', $attribute['attr']) $query->where('attribute_id', $attribute['attr'])
@ -127,21 +128,21 @@ class ProductRepo
} }
} }
if (isset($data['sku']) || isset($data['model'])) { if (isset($filters['sku']) || isset($filters['model'])) {
$builder->whereHas('skus', function ($query) use ($data) { $builder->whereHas('skus', function ($query) use ($filters) {
if (isset($data['sku'])) { if (isset($filters['sku'])) {
$query->where('sku', 'like', "%{$data['sku']}%"); $query->where('sku', 'like', "%{$filters['sku']}%");
} }
if (isset($data['model'])) { if (isset($filters['model'])) {
$query->where('model', 'like', "%{$data['model']}%"); $query->where('model', 'like', "%{$filters['model']}%");
} }
}); });
} }
if (isset($data['price']) && $data['price']) { if (isset($filters['price']) && $filters['price']) {
$builder->whereHas('skus', function ($query) use ($data) { $builder->whereHas('skus', function ($query) use ($filters) {
// price 格式:price=30-100 // price 格式:price=30-100
$prices = explode('-', $data['price']); $prices = explode('-', $filters['price']);
if (! $prices[1]) { if (! $prices[1]) {
$query->where('price', '>', $prices[0] ?: 0)->where('is_default', 1); $query->where('price', '>', $prices[0] ?: 0)->where('is_default', 1);
} else { } else {
@ -150,34 +151,54 @@ class ProductRepo
}); });
} }
if (isset($data['name'])) { if (isset($filters['name'])) {
$builder->where('pd.name', 'like', "%{$data['name']}%"); $builder->where('pd.name', 'like', "%{$filters['name']}%");
} }
$keyword = $data['keyword'] ?? ''; $keyword = trim($filters['keyword'] ?? '');
if ($keyword) { if ($keyword) {
$builder->where(function (Builder $query) use ($keyword) { $keywords = explode(' ', $keyword);
$query->whereHas('skus', function (Builder $query) use ($keyword) { $keywords = array_unique($keywords);
$query->where('sku', 'like', "%{$keyword}%") $keywords = array_diff($keywords, ['']);
$builder->where(function (Builder $query) use ($keywords) {
$query->whereHas('skus', function (Builder $query) use ($keywords) {
$keywordFirst = array_shift($keywords);
$query->where('sku', 'like', "%{$keywordFirst}%")
->orWhere('model', 'like', "%{$keywordFirst}%");
foreach ($keywords as $keyword) {
$query->orWhere('sku', 'like', "%{$keyword}%")
->orWhere('model', 'like', "%{$keyword}%"); ->orWhere('model', 'like', "%{$keyword}%");
})->orWhere('pd.name', 'like', "%{$keyword}%"); }
});
foreach ($keywords as $keyword) {
$query->orWhere('pd.name', 'like', "%{$keyword}%");
}
}); });
} }
if (isset($data['active'])) { if (isset($filters['created_start'])) {
$builder->where('active', (int) $data['active']); $builder->where('products.created_at', '>', $filters['created_start']);
}
if (isset($filters['created_end'])) {
$builder->where('products.created_at', '>', $filters['created_end']);
}
if (isset($filters['active'])) {
$builder->where('active', (int) $filters['active']);
} }
// 回收站 // 回收站
if (isset($data['trashed']) && $data['trashed']) { if (isset($filters['trashed']) && $filters['trashed']) {
$builder->onlyTrashed(); $builder->onlyTrashed();
} }
$sort = $data['sort'] ?? 'products.position'; $sort = $filters['sort'] ?? 'products.position';
$order = $data['order'] ?? 'desc'; $order = $filters['order'] ?? 'desc';
$builder->orderBy($sort, $order); $builder->orderBy($sort, $order);
return $builder; return hook_filter('repo.product.builder', $builder);
} }
public static function parseFilterParamsAttr($attr) public static function parseFilterParamsAttr($attr)
@ -191,7 +212,7 @@ class ProductRepo
return [ return [
'attr' => $itemArr[0], 'attr' => $itemArr[0],
'value' => explode('/', $itemArr[1]), 'value' => explode(',', $itemArr[1]),
]; ];
}, $attributes); }, $attributes);
@ -200,13 +221,18 @@ class ProductRepo
public static function getFilterAttribute($data): array public static function getFilterAttribute($data): array
{ {
$builder = self::getBuilder($data) $builder = static::getBuilder(array_diff_key($data, ['attr' => '', 'price' => '']))
->select(['pa.attribute_id', 'pa.attribute_value_id']) ->select(['pa.attribute_id', 'pa.attribute_value_id'])
->with(['attributes.attribute.description', 'attributes.attribute_value.description']) ->with(['attributes.attribute.description', 'attributes.attribute_value.description'])
->leftJoin('product_attributes as pa', 'pa.product_id', 'products.id') ->leftJoin('product_attributes as pa', 'pa.product_id', 'products.id')
->whereNotNull('pa.attribute_id') ->whereNotNull('pa.attribute_id')
->distinct() ->distinct()
->reorder('pa.attribute_id'); ->reorder('pa.attribute_id');
if ($attributesIds = system_setting('base.multi_filter', [])['attribute'] ?? []) {
$builder->whereIn('pa.attribute_id', $attributesIds);
}
$productAttributes = $builder->get()->toArray(); $productAttributes = $builder->get()->toArray();
$attributeMap = array_column(Attribute::query()->with('description')->orderBy('sort_order')->get()->toArray(), null, 'id'); $attributeMap = array_column(Attribute::query()->with('description')->orderBy('sort_order')->get()->toArray(), null, 'id');
@ -249,7 +275,7 @@ class ProductRepo
{ {
$selectPrice = $data['price'] ?? '-'; $selectPrice = $data['price'] ?? '-';
// unset($data['price']); // unset($data['price']);
$builder = self::getBuilder(['category_id' => $data['category_id']])->leftJoin('product_skus as ps', 'products.id', 'ps.product_id') $builder = static::getBuilder(['category_id' => $data['category_id']])->leftJoin('product_skus as ps', 'products.id', 'ps.product_id')
->where('ps.is_default', 1); ->where('ps.is_default', 1);
$min = $builder->min('ps.price'); $min = $builder->min('ps.price');
$max = $builder->max('ps.price'); $max = $builder->max('ps.price');
@ -268,7 +294,7 @@ class ProductRepo
public static function list($data = []) public static function list($data = [])
{ {
return self::getBuilder($data)->paginate($data['per_page'] ?? 20); return static::getBuilder($data)->paginate($data['per_page'] ?? 20);
} }
public static function autocomplete($name) public static function autocomplete($name)
@ -328,7 +354,7 @@ class ProductRepo
} }
$items = []; $items = [];
$products = self::getBuilder()->select('id')->get(); $products = static::getBuilder()->select('id')->get();
foreach ($products as $product) { foreach ($products as $product) {
$items[$product->id] = [ $items[$product->id] = [
'id' => $product->id, 'id' => $product->id,

View File

@ -95,9 +95,9 @@ class RmaRepo
/** /**
* @param $data * @param $data
* @return LengthAwarePaginator * @return Builder
*/ */
public static function list($data): LengthAwarePaginator public static function getBuilder($data): Builder
{ {
$builder = Rma::query(); $builder = Rma::query();
@ -124,7 +124,16 @@ class RmaRepo
} }
$builder->orderBy('id', 'DESC'); $builder->orderBy('id', 'DESC');
return $builder->paginate(perPage())->withQueryString(); return $builder;
}
/**
* @param $data
* @return LengthAwarePaginator
*/
public static function list($data): LengthAwarePaginator
{
return self::getBuilder($data)->paginate(perPage())->withQueryString();
} }
/** /**

View File

@ -13,6 +13,7 @@ namespace Beike\Repositories;
use Beike\Models\Setting; use Beike\Models\Setting;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Artisan;
class SettingRepo class SettingRepo
{ {
@ -117,6 +118,7 @@ class SettingRepo
]; ];
} }
Setting::query()->insert($rows); Setting::query()->insert($rows);
self::clearCache();
} }
/** /**
@ -154,5 +156,17 @@ class SettingRepo
} else { } else {
$setting->update($settingData); $setting->update($settingData);
} }
self::clearCache();
}
/**
* Clear all cache.
*/
public static function clearCache()
{
Artisan::call('cache:clear');
Artisan::call('config:clear');
Artisan::call('view:clear');
Artisan::call('optimize:clear');
} }
} }

View File

@ -42,12 +42,17 @@ class CurrencyService
return $amount; return $amount;
} }
$symbol_left = $this->currencies[$currency]->symbol_left; $currencyRow = $this->currencies[$currency] ?? null;
$symbol_right = $this->currencies[$currency]->symbol_right; if (empty($currencyRow)) {
$decimal_place = $this->currencies[$currency]->decimal_place; return $amount;
}
$symbol_left = $currencyRow->symbol_left;
$symbol_right = $currencyRow->symbol_right;
$decimal_place = $currencyRow->decimal_place;
if (! $value) { if (! $value) {
$value = $this->currencies[$currency]->value; $value = $currencyRow->value;
} }
$amount = $value ? (float) $amount * $value : (float) $amount; $amount = $value ? (float) $amount * $value : (float) $amount;
@ -70,7 +75,7 @@ class CurrencyService
$string .= number_format(abs($amount), (int) $decimal_place, __('currency.decimal_point'), __('currency.thousand_point')); $string .= number_format(abs($amount), (int) $decimal_place, __('currency.decimal_point'), __('currency.thousand_point'));
if ($symbol_right) { if ($symbol_right) {
$string .= $symbol_right; $string .= ' ' . $symbol_right;
} }
return $string; return $string;

View File

@ -64,7 +64,7 @@ class DesignService
return self::handleRichText($content); return self::handleRichText($content);
} }
return hook_filter('admin.service.design.module.content', $content); return hook_filter('service.design.module.content', $content);
} }
/** /**

View File

@ -20,7 +20,7 @@ class ImageService
private $imagePath; private $imagePath;
public const PLACEHOLDER_IMAGE = 'catalog/placeholder.png'; private $placeholderImage = 'catalog/placeholder.png';
/** /**
* @param $image * @param $image
@ -28,7 +28,8 @@ class ImageService
*/ */
public function __construct($image) public function __construct($image)
{ {
$this->image = $image ?: self::PLACEHOLDER_IMAGE; $this->placeholderImage = system_setting('base.placeholder');
$this->image = $image ?: $this->placeholderImage;
$this->imagePath = public_path($this->image); $this->imagePath = public_path($this->image);
} }
@ -40,7 +41,7 @@ class ImageService
public function setPluginDirName($dirName): static public function setPluginDirName($dirName): static
{ {
$originImage = $this->image; $originImage = $this->image;
if ($this->image == self::PLACEHOLDER_IMAGE) { if ($this->image == $this->placeholderImage) {
return $this; return $this;
} }
@ -48,7 +49,7 @@ class ImageService
if (file_exists($this->imagePath)) { if (file_exists($this->imagePath)) {
$this->image = strtolower('plugin/' . $dirName . $originImage); $this->image = strtolower('plugin/' . $dirName . $originImage);
} else { } else {
$this->image = self::PLACEHOLDER_IMAGE; $this->image = $this->placeholderImage;
$this->imagePath = public_path($this->image); $this->imagePath = public_path($this->image);
} }
@ -65,7 +66,7 @@ class ImageService
{ {
try { try {
if (! file_exists($this->imagePath)) { if (! file_exists($this->imagePath)) {
$this->image = self::PLACEHOLDER_IMAGE; $this->image = $this->placeholderImage;
$this->imagePath = public_path($this->image); $this->imagePath = public_path($this->image);
} }
if (! file_exists($this->imagePath)) { if (! file_exists($this->imagePath)) {

View File

@ -15,6 +15,7 @@ use Beike\Models\Order;
use Beike\Models\OrderHistory; use Beike\Models\OrderHistory;
use Beike\Models\OrderShipment; use Beike\Models\OrderShipment;
use Beike\Models\Product; use Beike\Models\Product;
use Beike\Repositories\OrderPaymentRepo;
use Throwable; use Throwable;
class StateMachineService class StateMachineService
@ -29,6 +30,8 @@ class StateMachineService
private array $shipment; private array $shipment;
private array $payment;
public const CREATED = 'created'; // 已创建 public const CREATED = 'created'; // 已创建
public const UNPAID = 'unpaid'; // 待支付 public const UNPAID = 'unpaid'; // 待支付
@ -116,6 +119,19 @@ class StateMachineService
return $this; return $this;
} }
/**
* 设置支付信息
*
* @param array $payment
* @return $this
*/
public function setPayment(array $payment = []): self
{
$this->payment = $payment;
return $this;
}
/** /**
* 获取所有订单状态列表 * 获取所有订单状态列表
* *
@ -136,7 +152,25 @@ class StateMachineService
]; ];
} }
return $result; return hook_filter('service.state_machine.all_statuses', $result);
}
/**
* 获取所有有效订单状态
* @return string[]
*/
public static function getValidStatuses(): array
{
$statuses = [
self::CREATED,
self::UNPAID,
self::PAID,
self::SHIPPED,
self::COMPLETED,
self::CANCELLED,
];
return $statuses;
} }
/** /**
@ -198,6 +232,8 @@ class StateMachineService
} }
$this->{$function}($oldStatusCode, $status); $this->{$function}($oldStatusCode, $status);
} }
hook_filter('service.state_machine.change_status.after', ['order' => $order, 'status' => $status, 'comment' => $comment, 'notify' => $notify]);
} }
/** /**
@ -208,11 +244,6 @@ class StateMachineService
*/ */
private function validStatusCode($statusCode) private function validStatusCode($statusCode)
{ {
if (! in_array($statusCode, self::ORDER_STATUS)) {
$statusCodeString = implode(', ', self::ORDER_STATUS);
throw new \Exception("Invalid order status, must be one of the '{$statusCodeString}'");
}
$orderId = $this->orderId; $orderId = $this->orderId;
$orderNumber = $this->order->number; $orderNumber = $this->order->number;
$currentStatusCode = $this->order->status; $currentStatusCode = $this->order->status;
@ -340,6 +371,18 @@ class StateMachineService
} }
} }
/**
* 添加发货单号
* @throws Throwable
*/
private function addPayment($oldCode, $newCode)
{
if (empty($this->payment)) {
return;
}
OrderPaymentRepo::createOrUpdatePayment($this->orderId, $this->payment);
}
/** /**
* 发送新订单通知 * 发送新订单通知
*/ */

View File

@ -25,7 +25,7 @@ class AddressController extends Controller
{ {
$addresses = AddressRepo::listByCustomer(current_customer()); $addresses = AddressRepo::listByCustomer(current_customer());
$data = [ $data = [
'countries' => CountryRepo::all(), 'countries' => CountryRepo::listEnabled(),
'addresses' => AddressResource::collection($addresses), 'addresses' => AddressResource::collection($addresses),
]; ];

View File

@ -22,6 +22,7 @@ class EditController extends Controller
{ {
$customer = current_customer(); $customer = current_customer();
$data['customer'] = $customer; $data['customer'] = $customer;
$data = hook_filter('account.edit.index', $data);
return view('account/edit', $data); return view('account/edit', $data);
} }

View File

@ -35,6 +35,13 @@ class LoginController extends Controller
public function store(LoginRequest $request) public function store(LoginRequest $request)
{ {
$data = [
'request_data' => $request->all(),
];
try {
hook_action('shop.account.login.before', $data);
$guestCartProduct = CartRepo::allCartProducts(0); $guestCartProduct = CartRepo::allCartProducts(0);
if (! auth(Customer::AUTH_GUARD)->attempt($request->only('email', 'password'))) { if (! auth(Customer::AUTH_GUARD)->attempt($request->only('email', 'password'))) {
throw new NotAcceptableHttpException(trans('shop/login.email_or_password_error')); throw new NotAcceptableHttpException(trans('shop/login.email_or_password_error'));
@ -50,5 +57,8 @@ class LoginController extends Controller
CartRepo::mergeGuestCart($customer, $guestCartProduct); CartRepo::mergeGuestCart($customer, $guestCartProduct);
return json_success(trans('shop/login.login_successfully')); return json_success(trans('shop/login.login_successfully'));
} catch (\Exception $e) {
return json_fail($e->getMessage());
}
} }
} }

View File

@ -57,21 +57,6 @@ class OrderController extends Controller
return view('account/order_info', $data); return view('account/order_info', $data);
} }
/**
* 订单提交成功页
*
* @param Request $request
* @param $number
* @return View
*/
public function success(Request $request, $number): View
{
$customer = current_customer();
$order = OrderRepo::getOrderByNumber($number, $customer);
return view('account/order_success', ['order' => $order]);
}
/** /**
* 订单支付页面 * 订单支付页面
* *

View File

@ -11,6 +11,7 @@
namespace Beike\Shop\Http\Controllers; namespace Beike\Shop\Http\Controllers;
use Beike\Repositories\OrderRepo;
use Beike\Shop\Services\CheckoutService; use Beike\Shop\Services\CheckoutService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -32,9 +33,9 @@ class CheckoutController extends Controller
* 更改结算信息 * 更改结算信息
* *
* @param Request $request * @param Request $request
* @return array * @return mixed
*/ */
public function update(Request $request): array public function update(Request $request): mixed
{ {
try { try {
$requestData = $request->all(); $requestData = $request->all();
@ -55,8 +56,23 @@ class CheckoutController extends Controller
*/ */
public function confirm() public function confirm()
{ {
try {
$data = (new CheckoutService)->confirm(); $data = (new CheckoutService)->confirm();
return hook_filter('checkout.confirm.data', $data); return hook_filter('checkout.confirm.data', $data);
} catch (\Exception $e) {
return json_fail($e->getMessage());
}
}
public function success()
{
$order_number = request('order_number');
$customer = current_customer();
$order = OrderRepo::getOrderByNumber($order_number, $customer);
$data = hook_filter('account.order.show.data', ['order' => $order, 'html_items' => []]);
return view('checkout/success', $data);
} }
} }

View File

@ -5,8 +5,35 @@ namespace Beike\Shop\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Validation\ValidatesRequests;
use League\Csv\CannotInsertRecord;
class Controller extends \App\Http\Controllers\Controller class Controller extends \App\Http\Controllers\Controller
{ {
use AuthorizesRequests, DispatchesJobs, ValidatesRequests; use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
/**
* @param $filename
* @param $header
* @param $records
* @return mixed
* @throws CannotInsertRecord
*/
public function downloadCsv($filename, $header, $records): mixed
{
if (! str_contains($filename, '.csv')) {
$filename = $filename . '-' . date('YmdHis') . '.csv';
}
$headers = [
'Content-Type' => 'application/octet-stream',
'Content-Transfer-Encoding' => 'binary',
'Content-Disposition' => 'attachment; filename=' . $filename,
];
$csv = \League\Csv\Writer::createFromString('');
$csv->insertOne($header);
$csv->insertAll($records);
return response($csv, 200, $headers);
}
} }

View File

@ -0,0 +1,31 @@
<?php
/**
* OrderController.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-06-06 10:47:27
* @modified 2023-06-06 10:47:27
*/
namespace Beike\Shop\Http\Controllers;
use Beike\Models\Order;
use Illuminate\Http\Request;
class OrderController extends Controller
{
public function show(Request $request, int $number)
{
$email = trim($request->get('email'));
if (empty($email)) {
return null;
}
$order = Order::query()->where('number', $number)->where('email', $email)->firstOrFail();
$data = hook_filter('order.show.data', ['order' => $order, 'html_items' => []]);
return view('order_info', $data);
}
}

View File

@ -30,6 +30,7 @@ class PageCategoryController extends Controller
'active_page_categories' => PageCategoryRepo::getActiveList(), 'active_page_categories' => PageCategoryRepo::getActiveList(),
'active_pages' => PageRepo::getCategoryPages(), 'active_pages' => PageRepo::getCategoryPages(),
]; ];
$data = hook_filter('page_categories.home.data', $data);
return view('page_categories/home', $data); return view('page_categories/home', $data);
} }
@ -41,10 +42,11 @@ class PageCategoryController extends Controller
$data = [ $data = [
'category' => new PageCategoryDetail($pageCategory), 'category' => new PageCategoryDetail($pageCategory),
'active_page_categories' => PageCategoryRepo::getActiveList(), 'active_page_categories' => PageCategoryRepo::getActiveList(['limit' => 5]),
'breadcrumb' => $breadCrumb, 'breadcrumb' => $breadCrumb,
'category_pages' => $pageCategory->pages()->paginate(perPage()), 'category_pages' => $pageCategory->pages()->paginate(12),
]; ];
$data = hook_filter('page_categories.show.data', $data);
return view('page_categories/show', $data); return view('page_categories/show', $data);
} }

View File

@ -32,10 +32,6 @@ class PageController extends Controller
$data = hook_filter('page.show.data', $data); $data = hook_filter('page.show.data', $data);
if ($page->category) {
return view('pages/article', $data);
}
return view('pages/single', $data); return view('pages/single', $data);
} }
} }

View File

@ -28,7 +28,7 @@ class ProductController extends Controller
$data = hook_filter('product.show.data', $data); $data = hook_filter('product.show.data', $data);
return view('product', $data); return view('product/product', $data);
} }
/** /**

View File

@ -25,7 +25,6 @@ class AddressRequest extends FormRequest
{ {
return [ return [
'name' => 'required|min:2|max:16', 'name' => 'required|min:2|max:16',
'phone' => 'required|min:6|max:16',
'country_id' => 'required|exists:countries,id', 'country_id' => 'required|exists:countries,id',
'zone_id' => 'required|exists:zones,id', 'zone_id' => 'required|exists:zones,id',
'address_1' => 'required', 'address_1' => 'required',
@ -36,7 +35,6 @@ class AddressRequest extends FormRequest
{ {
return [ return [
'name' => trans('address.name'), 'name' => trans('address.name'),
'phone' => trans('address.phone'),
'country_id' => trans('address.country_id'), 'country_id' => trans('address.country_id'),
'zone_id' => trans('address.zone_id'), 'zone_id' => trans('address.zone_id'),
'address_1' => trans('address.address_1'), 'address_1' => trans('address.address_1'),

View File

@ -30,7 +30,7 @@ class CartDetail extends JsonResource
$subTotal = $price * $this->quantity; $subTotal = $price * $this->quantity;
$image = $sku->image ?: $product->image; $image = $sku->image ?: $product->image;
return [ $result = [
'cart_id' => $this->id, 'cart_id' => $this->id,
'product_id' => $this->product_id, 'product_id' => $this->product_id,
'sku_id' => $this->product_sku_id, 'sku_id' => $this->product_sku_id,
@ -48,5 +48,7 @@ class CartDetail extends JsonResource
'subtotal_format' => currency_format($subTotal), 'subtotal_format' => currency_format($subTotal),
'variant_labels' => trim($sku->getVariantLabel()), 'variant_labels' => trim($sku->getVariantLabel()),
]; ];
return hook_filter('resource.cart.detail', $result);
} }
} }

View File

@ -18,7 +18,7 @@ class CategoryDetail extends JsonResource
$item = [ $item = [
'id' => $this->id, 'id' => $this->id,
'name' => $this->description->name ?? '', 'name' => $this->description->name ?? '',
'url' => shop_route('categories.show', ['category' => $this]), 'url' => $this->url,
]; ];
if ($this->relationLoaded('activeChildren') && $this->activeChildren->count() > 0) { if ($this->relationLoaded('activeChildren') && $this->activeChildren->count() > 0) {

View File

@ -40,6 +40,7 @@ class ProductDetail extends JsonResource
'meta_description' => $this->description->meta_description ?? '', 'meta_description' => $this->description->meta_description ?? '',
'brand_id' => $this->brand->id ?? 0, 'brand_id' => $this->brand->id ?? 0,
'brand_name' => $this->brand->name ?? '', 'brand_name' => $this->brand->name ?? '',
'video' => $this->video ?? '',
'images' => array_map(function ($image) { 'images' => array_map(function ($image) {
return [ return [
'preview' => image_resize($image, 500, 500), 'preview' => image_resize($image, 500, 500),

Some files were not shown because too many files have changed in this diff Show More