Compare commits

...

201 Commits

Author SHA1 Message Date
liqianjin f344d2deab 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
2023-08-10 13:45:22 +08:00
pushuo bb7a6aad0d !161 Fix page summary input error prompt
* Fix installation error and no information display problem
* Fix page summary input error prompt
2023-07-20 06:22:35 +00:00
pushuo 2b12969d3a !160 Fix the front-end error reporting problem when the page is deleted
* wip
* Fix the front-end error reporting problem when the page is deleted
2023-07-20 10:16:36 +08:00
Edward Yang a3d35f6f09 code format 2023-07-20 10:13:07 +08:00
pushuo 47a6ea5297 !158 The admin tab is activated according to the parameters
* Background tab is automatically activated
2023-07-19 18:47:01 +08:00
Edward Yang d8b9be889c upgrade to v1.3.8(20230719) 2023-07-19 18:38:18 +08:00
Edward Yang 7ec745c52c !159 Stripe 升级优化
* wip
* fixed stripe charge
* wip
* wip
* code format
* wip
* wip
* 优化 stripe
* Background tab is automatically activated
2023-07-19 18:34:53 +08:00
Edward Yang 746b0382d9 fixed stripe 2023-07-19 15:24:58 +08:00
Edward Yang 99f87cc9e6 修复多个模板重复加载相同hook导致的问题 2023-07-19 11:36:53 +08:00
pushuo 7e3f2be082 !157 Fix tab_product module
* Fix tab_product module
2023-07-19 01:46:46 +00:00
Edward Yang b23a926dd5 修复切换不同模板产生模板缓存导致DIY问题 2023-07-19 09:45:32 +08:00
Edward Yang c07bdda87f fixed product variables 2023-07-19 09:29:50 +08:00
Edward Yang a1ba2917f8 Fixed category seo url on category - product list page. 2023-07-18 19:35:48 +08:00
pushuo a78d61e96e !156 Fix multiple tab_product module switching errors
* Fix multiple tab_product module switching errors
2023-07-18 10:54:00 +00:00
Edward Yang def439de4d release v1.3.8 - 20230718 2023-07-18 16:53:50 +08:00
pushuo 8750c828f7 !155 Add hooks for cart and checkout page submit buttons
* fixed license
* add setting license_code
* Add hooks for cart and checkout page submit buttons
2023-07-18 16:52:46 +08:00
Edward Yang 41ba00a3b3 code format 2023-07-18 16:52:46 +08:00
pushuo e4e6601f21 !154 Fix the error that the background product list image exceeds the display error
* Fix the error that the background product list image exceeds the display error
2023-07-18 16:52:46 +08:00
mengwb 3063e764f9 !153 所有model统一继承BASE
* model统一继承BASE
2023-07-18 16:52:46 +08:00
pushuo b3e70910ff !152 Fix ThemeSeeder data error
* wip
* Fix ThemeSeeder data error
2023-07-18 16:52:46 +08:00
宋良辰 df96ffafa5 !151 后台商品添加model和quantity 列表直接修改上下架状态
* cursor-pointer
* admin products-list add switch to edit status
* ProductResource add model and quantity
2023-07-18 16:52:46 +08:00
Edward Yang 1a3b7d134f Code format 2023-07-18 16:52:46 +08:00
Edward Yang 2d28ca8e68 add product repo builder 2023-07-18 16:52:46 +08:00
mengwb d0c4bc77e7 !150 安装环境检测
* installer detect the php extension
2023-07-18 16:52:46 +08:00
pushuo 69e9a051c1 !149 Fix configuration link status disabled and display problems
* wip
* wip
* Fix configuration link status disabled and display problems -> https:/…
2023-07-18 16:52:46 +08:00
Edward Yang f72b61a4a3 Fixed checkout for null sku https://gitee.com/beikeshop/beikeshop/issues/I7D59N?from=project-issue 2023-07-18 16:52:46 +08:00
pushuo 2e7d2b7cb2 !148 Fix the problem of switching preview when editing tab-product module
* wip
* Fix the problem of switching preview when editing tab-product module
2023-07-18 16:52:46 +08:00
Edward Yang 2430791a25 update order payment schema 2023-07-18 16:52:46 +08:00
Edward Yang 33784ebbdb 支持 blade hook 循环体传值 2023-07-18 16:52:46 +08:00
Edward Yang e1a4d1b3cc 修复 blade hook 优先级被覆盖导致插件失效 2023-07-18 16:52:46 +08:00
Edward Yang 9529d68d38 Publish JD product. 2023-07-18 16:52:46 +08:00
what_村长 ef14e65e31 !146 增加 hoot admin.product.sku.edit.item.after
* 增加hook admin.product.sku.edit.item.after ,方便在上面加上翻译插件按钮
* 增加hook admin.product.sku.edit.item ,方便在上面加上翻译插件按钮
* Merge branch 'dev' of https://gitee.com/what520/beikeshop into dev
* 测试
2023-07-18 16:52:46 +08:00
Edward Yang 2ad26852f0 Optimize plugin API 2023-07-18 16:52:46 +08:00
Edward Yang 5649d40560 Code format 2023-07-18 16:52:46 +08:00
Edward Yang 7e3ee84096 Export csv 2023-07-18 16:52:46 +08:00
pushuo a531cc0173 !144 Fix single multi-specification delete problem
* Fix single multi-specification delete problem
2023-07-18 16:52:46 +08:00
Edward Yang 754ddb58a4 fixed validate database 2023-07-18 16:52:46 +08:00
pushuo a418db518b !143 add product-page hook
* add product hook
2023-07-18 16:52:46 +08:00
huangyk 13285c6cbc 更新README 2023-07-18 09:51:19 +08:00
BeikeShop开源好用的跨境电商系统 86fa909e44
update README.md.
更新描述信息-3

Signed-off-by: BeikeShop开源好用的跨境电商系统 <huangyk@guangda.work>
2023-07-14 08:27:41 +00:00
BeikeShop开源好用的跨境电商系统 cac2adc614
update README.md.
更新描述信息-2

Signed-off-by: BeikeShop开源好用的跨境电商系统 <huangyk@guangda.work>
2023-07-14 08:26:33 +00:00
BeikeShop开源好用的跨境电商系统 65a6808c00
update README.md.
更新了描述文案

Signed-off-by: BeikeShop开源好用的跨境电商系统 <huangyk@guangda.work>
2023-07-14 07:56:33 +00:00
Edward Yang 7f585d6b51 Show product detail on panel. 2023-07-07 17:35:39 +08:00
Edward Yang 2704c42f71 Update build to 20230621 2023-06-21 14:13:40 +08:00
pushuo 35543ed409 !142 Fix the problem of duplicate data when designing the home page
* wip
* wip
* Fix the problem of duplicate data when designing the home page
2023-06-21 14:12:57 +08:00
Edward Yang 5b262df391 Fixed openai and added plugin version. 2023-06-19 19:07:43 +08:00
Edward Yang f0058fa6b4 Fixed chatgpt plugin 2023-06-19 18:56:25 +08:00
pushuo 6d274a74d9 !140 Fix Stripe image intersection
* Fix Stripe image intersection
2023-06-19 08:25:43 +00:00
Edward Yang fe0c328c76 update build 2023-06-19 16:25:08 +08:00
pushuo b8ee27e84f !139 Fix Stripe js plugin path error
* Fix Stripe js plugin path error
2023-06-19 08:14:59 +00:00
pushuo 530e5f22fe !138 Fix pagination button h5 responsive
* Fix pagination button h5 responsive ++
* Fix pagination button h5 responsive
* Optimize table UI, etc
2023-06-19 02:27:08 +00:00
Edward Yang 20131129fe fixed about 2023-06-14 19:21:00 +08:00
pushuo 9eb9a43fce !137 Fix the problem of saving multi-standard products
* wip
* Fix the problem of saving multi-standard products
2023-06-14 11:06:14 +00:00
Edward Yang 0b11ada7a2 fixed page and page category demo. 2023-06-14 19:04:05 +08:00
pushuo 4cffc1a159 !136 navigation add news
* navigation add news
2023-06-14 10:14:44 +00:00
pushuo d61c0a5e12 !135 Repair tax rate setting error
* Repair tax rate setting error
2023-06-14 08:55:10 +00:00
pushuo e9aa555609 !134 Fix backend product saving error
* Fix backend product saving error
2023-06-14 07:59:38 +00:00
pushuo 5af461e617 !133 Optimize background ui
* Optimize background ui
2023-06-14 07:03:47 +00:00
pushuo 391f28ede7 !132 Delete the background vip button and change it to License & Services
* Delete the background vip button and change it to License & Services
2023-06-14 06:46:23 +00:00
pushuo b4ddf33810 !130 Fix front-end form validation
* Fix front-end form validation + 1
2023-06-14 06:23:09 +00:00
pushuo cb3a9951b3 !131 Fix card background color
* Fix card background color
2023-06-14 06:22:37 +00:00
pushuo d89dd1b7c6 !129 Fix the problem of sku data confusion caused by deleting commodity specifications in the background
* Fix the problem of sku data confusion caused by deleting commodity spe…
* fix product variant
2023-06-14 05:57:35 +00:00
Edward Yang e33431f59a
!127 增加hook 用于实现插件控制商品详情的一些功能
Merge pull request !127 from what_村长/dev
2023-06-14 04:06:21 +00:00
pushuo b2811aafce !128 Fix front-end form validation
* fix product variant
2023-06-14 03:57:45 +00:00
tanxiaoyong 0e9395b98e 增加hook
product.detail.quantity.input
product.detail.add_to_cart
product.detail.wishlist
2023-06-14 11:37:07 +08:00
tanxiaoyong e71a98e915 增加hook
product.detail.quantity.input
product.detail.add_to_cart
product.detail.wishlist
2023-06-14 11:33:17 +08:00
tanxiaoyong b86cee3b3e Merge branch 'dev' of https://gitee.com/what520/beikeshop into dev
 Conflicts:
	themes/default/checkout/success.blade.php
	themes/default/product/product.blade.php
2023-06-14 11:20:17 +08:00
tanxiaoyong b28712e2d5 增加hook
product.detail.quantity
product.detail.add_to_cart
product.detail.wishlist
2023-06-14 11:12:15 +08:00
Edward Yang 473ed35cd9 update build 2023-06-14 09:07:32 +08:00
pushuo 975cfa1d69 !126 Optimize background rma ui and form submission validation logic
* Optimize background rma ui and form submission validation logic
2023-06-13 10:06:09 +00:00
pushuo b074e2a6c1 !125 Optimize element path
* Optimize order status modification
* Optimize element path
2023-06-13 09:02:47 +00:00
Edward Yang 2c460d696e update version to 1.3.7 2023-06-13 15:43:47 +08:00
Edward Yang 9287e0fc47 fixed summary rule 2023-06-13 15:33:34 +08:00
Edward Yang 1cbf9af4a4 optimize fail status 2023-06-13 15:26:07 +08:00
Edward Yang 2ea6cdd49b Optimize checkout confirm. 2023-06-13 15:20:38 +08:00
pushuo 56ef2dece8 !124 Add a view details link to the order email
* Add a view details link to the order email
2023-06-09 06:36:40 +00:00
Edward Yang 1ec759c7e4 update packages 2023-06-09 14:32:16 +08:00
pushuo e7019a9722 !123 Add video function
* Optimize shopping cart entries
* File manager selection type judgment
* video function
* Support video for file manager
* Optimize file manager
2023-06-09 05:58:33 +00:00
pushuo 523b8a3e7c !122 When entering the product details for the first time, only the SPU image will be displayed
* When entering the product details for the first time, only the SPU ima…
2023-06-08 07:48:00 +00:00
mengwb 05da652822 !121 优化
* 代码优化
2023-06-08 03:23:35 +00:00
Edward Yang 130a4544cc Show SPU image for product list 2023-06-07 19:23:37 +08:00
pushuo 9ecf31aecd !120 Add sorting settings
* Fixed category sort order and filter by active.
* Add sorting settings
2023-06-07 07:55:43 +00:00
pushuo 2b7b0a9011 !119 add ms hook
* wip
* 优化
* wip
* 文件管理器
* add ms hook
2023-06-07 02:57:07 +00:00
pushuo fb1c2e4c1c !118 order detail for guest
* order ui
* fixed guest order
* add number filter
* Add order detail for guest
2023-06-07 02:03:07 +00:00
pushuo 8d165eadf8 !117 Optimize the header drop-down menu ui
* Optimize the header drop-down menu ui
2023-06-06 00:43:52 +00:00
mengwb 0a5518d801 !116 结账hook
* Optimize cart list.
* 结账优化
2023-06-06 00:43:20 +00:00
Edward Yang d935eb4ff3 Optimize cart list. 2023-06-05 16:21:39 +08:00
tanxiaoyong 6d85aac561 增加物流信息展示块。当游客购买时,可以通过记录当前页面地址后,显示自己的订单信息及物流信息 2023-06-05 15:09:14 +08:00
tanxiaoyong 271732fa2b 调整订单状态显示字段,统一使用status_format 2023-06-05 15:05:58 +08:00
pushuo 43f6fdbb40 !114 Checkout request returns error message display
* Optimize checkout page error request prompt
2023-06-05 03:49:34 +00:00
mengwb fdbe7120c0 !113 hook优化
* hook优化
2023-06-05 03:19:49 +00:00
mengwb 25fd03c3c7 !112 service.checkout.update.after hook
* service.checkout.update.after
2023-06-02 10:08:18 +00:00
Edward Yang 0c18aedbb1 revert code to redirect to cart. 2023-06-02 17:33:22 +08:00
Edward Yang 65f8023891 Thrown cart exception. 2023-06-02 17:10:41 +08:00
Edward Yang 6a16540d1b Fixed flexshipping for 1.3.6 and 1.3.7 2023-06-02 16:18:42 +08:00
Edward Yang a29245b5d4 Fixed flex service. 2023-06-02 14:38:45 +08:00
pushuo fb8ac8dea4 !111 facebook pixel
* remove account order success.
* wip
* fb-pixel
2023-06-02 01:18:43 +00:00
Edward Yang 8f167ac2b0 Add view content. 2023-06-01 16:07:36 +08:00
Edward Yang 3aec43abb7 Code format 2023-06-01 14:12:33 +08:00
licy@guangda.work a55b1f5a63 提交说明 2023-06-01 14:05:12 +08:00
Edward Yang 168f65ed8e Upgrade packages. 2023-06-01 14:04:17 +08:00
Edward Yang f98c93f099 Modify upload max size to 10240 for admin panel. 2023-06-01 11:06:47 +08:00
pushuo b320aab6b6 !110 Fix background vip request error and cause other problems
* Fix background vip request error and cause other problems
2023-06-01 02:14:06 +00:00
Edward Yang 39d8d85425 Add meta pixel hook 2023-05-31 18:14:37 +08:00
Edward Yang 607751abc6 Support description multi languages. 2023-05-31 18:06:43 +08:00
Edward Yang fc609a9b67 Code format 2023-05-31 18:06:14 +08:00
mengwb 8b711fa653 !108 【WIP】代码优化和添加hook(适配多商户)
* wip
* 文件管理器优化
* 文件管理器优化
* 后台订单管理和商品管理代码优化,提供更好的扩展性,便于多商家实现
* totalService
* 添加hook
* TotalService优化
* hook
* hook
* 购物车列表hook
2023-05-30 01:56:26 +00:00
mengwb 64cedc39d4 !105 hook
* 订单详情页增加Hook
* 添加日期选择插件
* 状态机完善
* wip
* hook for checkout
2023-05-30 00:34:21 +00:00
pushuo 68f91b589f !109 Add front hook
* Add front hook
2023-05-27 01:03:24 +00:00
Edward Yang 78ed309eec add openssl to package 2023-05-26 13:51:32 +08:00
Edward Yang d6f518dff6 !107 Order Payment
* wip
* add exceptions
* order->orderPayments ui
* add order payments
* paypal 和 stripe 保存支付数据
* add order payment.
2023-05-25 09:53:49 +00:00
pushuo 5b83f02098 !106 Fix the h5 navigation without links and click to jump to the home page
* Fix the h5 navigation without links and click to jump to the home page
2023-05-23 03:10:13 +00:00
Edward Yang 619523c472 update tax rate rule 2023-05-23 10:43:21 +08:00
pushuo b5cc1ba786 !104 Product Added Quick Preview
* Added quick preview
2023-05-20 10:20:53 +00:00
Edward Yang 1574805a5a change order total service and code format. 2023-05-18 15:31:11 +08:00
pushuo 0bd592b648 !103 Fix that the slideshow module swiper plugin cannot be loaded for the first time
* Fix that the slideshow module swiper plugin cannot be loaded for the first time
2023-05-17 06:22:46 +00:00
pushuo 2fd52c1761 !102 Add article single file
* wip
2023-05-17 06:20:58 +00:00
pushuo 204d91e5d0 !100 Fix the error reported when there is no data in advanced filtering
* Fix the error reported when there is no data in advanced filtering
2023-05-17 03:28:33 +00:00
pushuo 8ea52fb8ff !101 Fix article TDK and modify article
* Fix article-related product issues
* Fix the problem that the article TDK does not display
2023-05-17 03:27:35 +00:00
Edward Yang f4657a9bed Fixed breadcrumb for page detail 2023-05-17 11:19:33 +08:00
pushuo fcb6c888dc !99 Fix the tab_product module reporting an error when there is no data
* Fix the tab_product module reporting an error when there is no data
2023-05-17 03:11:48 +00:00
pushuo 812b7655af !98 Optimize the personal center button, etc.
* wip
* Optimize the personal center button, etc.
* wip
* wip
* wip
* Modify an entry, name -> full name
2023-05-15 10:15:27 +00:00
pushuo b88d889861 !97 Modify an entry, name -> full name
* Modify an entry, name -> full name
2023-05-15 06:18:25 +00:00
what_村长 a1626dc21b !95 调整订单状态显示字段
* 调整订单状态显示字段,统一使用status_format
2023-05-15 11:38:06 +08:00
Teemo 9d020adaee !96 韩语支持
* 删除文件 resources/lang/ko/.keep
* 韩语4
* 韩语3
* 新建 admin
* 韩语2
* 韩语
* 新建 ko
2023-05-14 12:32:44 +00:00
Edward Yang 56c6c7f055 update build number 2023-05-12 11:03:54 +08:00
pushuo d2a68741d4 !94 Fix advanced filter save error
* Fix advanced filter save error
2023-05-12 02:33:05 +00:00
Edward Yang f26e239668 fixed position 2023-05-11 15:11:02 +08:00
Edward Yang ffaef2312b git revert .env.example 2023-05-10 18:45:23 +08:00
pushuo b595085b7e !92 Optimize the checkout process
* wip
* wip
* wip
* wip
* wip
* wip
* Optimize the checkout process
2023-05-10 10:37:27 +00:00
mengwb 0b6609b419 !93 【WIP】增加hook
* hook about multiseller
2023-05-10 10:35:48 +00:00
Edward Yang f60aa84886 remove unnecessary 2023-05-10 17:20:24 +08:00
Edward Yang 8de7f7e08c modified plugin sort 2023-05-10 15:03:27 +08:00
Edward Yang 89adc4d8d1 Code format 2023-05-10 11:52:19 +08:00
Edward Yang ab549d1cd8 update themes git ignore 2023-05-10 11:45:36 +08:00
licy@guangda.work 8c5178fa77 客户组折扣校验 2023-05-10 10:53:55 +08:00
licy@guangda.work d434eafec9 admin alter 2023-05-10 10:53:55 +08:00
licy@guangda.work 6636986904 admin test 2023-05-10 10:53:55 +08:00
licy@guangda.work c4e0d4a52e admin 2023-05-10 10:53:55 +08:00
licy@guangda.work 5e869252c9 代码优化调整 2023-05-10 10:53:55 +08:00
licy@guangda.work 07276ed14c admin test 2023-05-10 10:53:55 +08:00
licy@guangda.work e33521f60e admin test 2023-05-10 10:53:55 +08:00
licy@guangda.work 52fbd72915 admin测试 2023-05-10 10:53:55 +08:00
licy@guangda.work fe170590db admin测试 2023-05-10 10:53:55 +08:00
@MAOCHENG 203c937230 删除文件 phpinfo.php 2023-05-10 10:53:55 +08:00
licy@guangda.work edcc4afca8 admin测试 2023-05-10 10:53:55 +08:00
licy@guangda.work 7ba52a3bf3 个人中心 2023-05-10 10:53:54 +08:00
licy@guangda.work 7955534530 添加地址、商品购买 2023-05-10 10:53:54 +08:00
licy@guangda.work 0085f2dd2d 注册多场景 2023-05-10 10:53:54 +08:00
licy@guangda.work 1f7e2e9942 登录多场景 2023-05-10 10:53:54 +08:00
Edward Yang 6ed7d11368 Fixed browser test 2023-05-10 10:53:54 +08:00
Edward Yang a431e3785d Add laravel dusk to auto test. 2023-05-10 10:53:54 +08:00
pushuo 3da30a2ff7 !91 Fix delete currency judgment return status
* Fix delete currency judgment return status
2023-05-10 02:16:46 +00:00
Edward Yang a961ab8277 update version and build 2023-05-10 10:02:25 +08:00
Edward Yang 8fcd26dcd7 Fixed for https://gitee.com/beikeshop/beikeshop/issues/I70XJW?from=project-issue 2023-05-10 09:59:22 +08:00
pushuo 4ef9eeac5b !90 add checkout success
* add checkout success
2023-05-10 01:39:22 +00:00
pushuo 77bf0bca1a Change plugin purchase process
wip

wip

wip

wip

admin ui optimization

Optimize file manager
2023-05-09 17:53:11 +08:00
pushuo 4869e4252e Optimize the plug-in purchase process 2023-05-09 17:53:11 +08:00
Edward Yang 775171b14a update readme 2023-05-09 10:05:42 +08:00
Edward Yang c6d07b1834 fixed currency as right symbol 2023-05-06 08:55:53 +08:00
pushuo 773d1469bd Add product image lazy loading 2023-05-04 18:26:07 +08:00
Edward Yang 10eb858ef1 update header and sidebar menus for admin panel.
fixed menus

wip

wip

get sub menus

wip

add current link

wip

fixed rma

fixed routes

fixed sub routes

wip

wip

fixed order and rmas

重构后台菜单

wip

fixed active

update languages.

wip

wip

add history links

code format

wip

fixed menus

fixed menus

fixed url

fixed sub routes

wip

update menus

wip

修复角色页面没有选中上一级目录

remove unnecessary code

wip

wip

fixed header common links

code format

add design

fixed plugin

wip

添加插件二次子菜单

修复获取更多

修复最近浏览记录 插件相关语言包

wip

调整文章菜单顺序

fixed common links

fixed common links

wip

wip

wip

default link icon.

修复插件编辑二级菜单选中

wip

wip

wip

wip

wip

wip
2023-05-04 17:48:52 +08:00
pushuo 1cc2bf53a5 Optimize background navigation 2023-05-04 17:48:52 +08:00
蒲硕 3e2a27d2b8 !87 Front desk mobile terminal navigation optimization
* Front desk mobile terminal navigation optimization
2023-05-04 02:12:17 +00:00
Edward Yang c1d6c48f5b Add jwt and admin token to build API.
Add jwt to build API.

返回当前客户

后台添加,通过token获取后台用户信息

fixed token api

fixed order list and order detail

fixed product api

fixed categoryies API

add brands

fixed brands

add products

添加订单接口
2023-04-27 18:16:07 +08:00
pushuo 67d1b85952 admin adds a token
wip

fixed update

fixed account update

wip

wip

wip

wip

fixed tokens

wip

admin token
2023-04-27 18:16:07 +08:00
Edward Yang 920689fbee 首页统计改为产品总数 2023-04-26 18:57:49 +08:00
蒲硕 76b37ed867 !86 Optimization Advanced filters, taxes, currencies, etc.
* Fix region configuration province selection problem
* Optimization Advanced filters, taxes, currencies, etc.
2023-04-26 08:38:24 +00:00
Edward Yang 2956a536ff fixed Indonesia 2023-04-26 14:31:53 +08:00
pushuo 4e5fe381b4 add lang id 2023-04-26 14:19:24 +08:00
Edward Yang 2a2067204a Add hook for total service. 2023-04-26 11:12:36 +08:00
what_村长 d1fed8b618 !85 增加2处hook
* 增加hookwrapper
* Merge branch 'dev' of https://gitee.com/beikeshop/beikeshop into dev
* Merge branch 'dev' of https://gitee.com/what520/beikeshop into dev
* 增加hook,实现插件中要求的其他订单状态显示
* 调整状态字段的显示字段,与后端的一致
* !83 提交两个hook
* 增加
2023-04-20 11:32:48 +00:00
Edward Yang a3470b27c0 sort order for file manager
wip

wip

sort order by name
2023-04-17 21:00:05 +08:00
what_村长 f744dc6b02 !84 调整代码
* Merge branch 'dev' of https://gitee.com/what520/beikeshop into dev
* 增加hook,实现插件中要求的其他订单状态显示
* 调整状态字段的显示字段,与后端的一致
* 增加
2023-04-17 16:32:47 +08:00
mengwb e7734f2551 !82 商品搜索支持以空格间隔的多个词组
* The product search supports multiple strings separated by spaces.
2023-04-17 16:32:47 +08:00
what_村长 6e1b2a4347 !83 提交两个hook
* 增加
2023-04-17 16:32:47 +08:00
Edward Yang 5c99565d69 update readme 2023-04-14 13:51:56 +08:00
Edward Yang 399eeb0547 fixed readme upgrade 2023-04-13 11:57:34 +08:00
Edward Yang 318d2d9cfa Fixed customer group discount. 2023-04-12 11:40:56 +08:00
Edward Yang c4858b5875 update build number 2023-04-12 09:25:57 +08:00
TL 5f9eeb74d8 安装优化 2023-04-11 18:58:12 +08:00
蒲硕 98e962a6fd !79 Fix the background brand page turning back problem, optimize the form submission interaction, etc.
* Fix the background brand page turning back problem, optimize the form …
2023-04-11 07:40:23 +00:00
what_村长 d4b8c148a1 !78 增加几处hook,用于实现其他登录方式及登录后的数据展示控制
* Merge branch 'dev' of https://gitee.com/what520/beikeshop into dev
* 增加 @hookwrapper('account.sidebar.email')
* 增加 hook_filter('account.edit.index', $data);拦截展示数据
* @hookwrapper('account.edit.email')
* 增加@stack('login.vue.data'),@stack('login.vue.method')
* 图片翻译插件增加hook
* 增加2个hook  admin.product.name.after   admin.product.content.after 用于实现翻译插件按钮
* 1.增加 @hook('file_manager.content.head.btns.after')
* 1.增加 @hook('file_manager.content.head.btns.after')
* 在文件管理器上加入@hook('file_manager.content.head.btns.after')
2023-04-11 06:23:12 +00:00
蒲硕 d8027054bc !77 Optimize setting background page properties
* Optimize setting background page properties
2023-04-10 06:06:35 +00:00
Edward Yang 237b777c0a Remove unnecessary code. 2023-04-07 18:06:02 +08:00
mengwb 4b4f0ea2e9 !76 安装程序完善
* https://gitee.com/beikeshop/beikeshop/issues/I6T41C
2023-04-07 10:03:11 +00:00
Edward Yang 19f15b54a8 Fixed page category pagenation 2023-04-07 17:22:34 +08:00
Edward Yang 5c4ae14dbc Code format 2023-04-07 16:53:18 +08:00
mengwb e1e3ee9568 !75 编辑商品时重量为空保存报错
* 代码规范化
* Fix the issue of saving error when editing product weight is empty.
* 高级筛选哪些属性显示在筛选栏可在后台系统设置里设置
2023-04-07 08:42:56 +00:00
蒲硕 11626a73af !74 optimization admin.service.design.module.content code
* optimization admin.service.design.module.content  code
2023-04-07 06:45:44 +00:00
Edward Yang 6213226a99 prepare to publish 1.3.5 2023-04-07 14:43:35 +08:00
蒲硕 b5be111f41 !73 Optimize the breadcrumbs of the latest product plugin
* Optimize the breadcrumbs of the latest product plugin
2023-04-07 02:03:23 +00:00
Edward Yang fb6ef5d512 fixed social priority 2023-04-03 16:16:32 +08:00
what_村长 0362952282 !71 加入hook
* 图片翻译插件增加hook
* 增加2个hook  admin.product.name.after   admin.product.content.after 用于实现翻译插件按钮
* 1.增加 @hook('file_manager.content.head.btns.after')
* 1.增加 @hook('file_manager.content.head.btns.after')
* 在文件管理器上加入@hook('file_manager.content.head.btns.after')
2023-04-03 02:47:07 +00:00
mengwb d074c123d9 !68 商品重量和多个bug修复
* Optimizing Blog ui
* Optimize breadcrumb position, optimize webpack.mix.js
* Modify request vip status update logic
* 后台商品列表默认按修改时间倒序,商品编辑保存无修改时更新商品修改时间
* wip
* wip
* 高级筛选属性多选问题处理
* 修复游客结账问题
* Add payment success page
* After repairing the data loss problem after dragging the order of specifications
* wip
* Fix the problem that the image of the deleted product in the backgroun…
* wip
* wip
* 占位图
* 更改地址重新加载运费,和重新计算order total运费功能
* wip
* wip
* wip
2023-03-31 02:56:57 +00:00
Edward Yang 634a4a2bd2 Fixed social controller. 2023-03-27 17:43:45 +08:00
mengwb 86b3d33d6d !66 默认属性组不可删除
* 默认数据增加id为1的默认属性组,并且默认属性组后台不能删除
* migrations/2023_03_20_091423_product_weight.php
* 商品重量
* add weight blade
* bugfix
* 商品重量,用于计算flex shipping运费;地址变化后配送方式变化,需要重新显示新的配送方式
* Placeholder Image Backend Settings
* 商品重量,用于计算flex shipping运费;地址变化后配送方式变化,需要重新显示新的配送方式
* 增加hook
2023-03-24 10:23:55 +00:00
宋良辰 d233cd2fca !67 新增hook
* add hook
2023-03-24 10:23:26 +00:00
Edward Yang 76edc24c77 Add hook from mundosanjose 2023-03-24 17:01:02 +08:00
what_村长 3266cb4924 !65 在文件管理器上加入@hook('file_manager.content.head.btns.after')
* 1.增加 @hook('file_manager.content.head.btns.after')
* 1.增加 @hook('file_manager.content.head.btns.after')
* 在文件管理器上加入@hook('file_manager.content.head.btns.after')
* 增加hook
2023-03-24 08:26:18 +00:00
Edward Yang 9ee28acf64 fixed country list 2023-03-24 12:59:35 +08:00
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_CLUSTER="${PUSHER_APP_CLUSTER}"
# 前端热更新, PROXY: vhost or path, HOST: 多设备实时
MIX_PROXY=beikeshop.test
MIX_HOST=192.168.0.122
# Dusk Browser Test
DUSK_START_MAXIMIZED=true
DUSK_HEADLESS_DISABLED=false

View File

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

View File

@ -64,11 +64,18 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\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 = [
'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;
use Beike\Admin\Http\Requests\CurrencyRequest;
use Beike\Models\Order;
use Beike\Repositories\CurrencyRepo;
use Illuminate\Http\Request;
@ -66,9 +67,19 @@ class CurrencyController extends Controller
public function destroy(Request $request, int $currencyId)
{
CurrencyRepo::delete($currencyId);
hook_action('admin.currency.destroy.after', $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'));
}
return json_success(trans('common.deleted_success'));
CurrencyRepo::delete($currencyId);
hook_action('admin.currency.destroy.after', $currencyId);
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
{
$baseFolder = $request->get('base_folder');
$sort = $request->get('sort', 'created');
$order = $request->get('order', 'desc');
$page = (int) $request->get('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);
}
@ -124,7 +126,7 @@ class FileManagerController extends Controller
$savePath = $request->get('path');
$originName = $file->getClientOriginalName();
$filePath = $file->storeAs($savePath, $originName, 'catalog');
$filePath = (new FileManagerService)->uploadFile($file, $savePath, $originName);
return [
'name' => $originName,

View File

@ -5,13 +5,16 @@ namespace Beike\Admin\Http\Controllers;
use App\Http\Controllers\Controller;
use Beike\Admin\Repositories\DashboardRepo;
use Beike\Admin\Repositories\Report\OrderReportRepo;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
class HomeController extends Controller
{
public function index()
{
$data = [
'views' => DashboardRepo::getCustomerViewData(),
'products' => DashboardRepo::getProductData(),
// 'views' => DashboardRepo::getCustomerViewData(),
'orders' => DashboardRepo::getOrderData(),
'customers' => DashboardRepo::getCustomerData(),
'order_totals' => DashboardRepo::getTotalData(),
@ -24,4 +27,54 @@ class HomeController extends Controller
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)
{
$order->load(['orderTotals', 'orderHistories', 'orderShipments']);
$order->load(['orderTotals', 'orderHistories', 'orderShipments', 'orderPayments']);
$data = hook_filter('admin.order.show.data', ['order' => $order, 'html_items' => []]);
$data['statuses'] = StateMachineService::getInstance($order)->nextBackendStatuses();
$data = hook_filter('admin.order.show.data', $data);

View File

@ -32,6 +32,111 @@ class PluginController extends Controller
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\Repositories\TaxClassRepo;
use Beike\Admin\Services\ProductService;
use Beike\Libraries\Weight;
use Beike\Models\Product;
use Beike\Repositories\CategoryRepo;
use Beike\Repositories\LanguageRepo;
@ -20,6 +21,9 @@ class ProductController extends Controller
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);
$productsFormat = $products->jsonSerialize();
@ -145,15 +149,16 @@ class ProductController extends Controller
array_unshift($taxClasses, ['title' => trans('admin/builder.text_no'), 'id' => 0]);
$data = [
'product' => $product,
'descriptions' => $descriptions ?? [],
'category_ids' => $categoryIds ?? [],
'product_attributes' => ProductAttributeResource::collection($product->attributes),
'relations' => ProductResource::collection($product->relations)->resource,
'languages' => LanguageRepo::all(),
'tax_classes' => $taxClasses,
'source' => [
'categories' => CategoryRepo::flatten(locale()),
'product' => $product,
'descriptions' => $descriptions ?? [],
'category_ids' => $categoryIds ?? [],
'product_attributes' => ProductAttributeResource::collection($product->attributes),
'relations' => ProductResource::collection($product->relations)->resource,
'languages' => LanguageRepo::all(),
'tax_classes' => $taxClasses,
'weight_classes' => Weight::getWeightUnits(),
'source' => [
'categories' => CategoryRepo::flatten(locale(), false),
],
'_redirect' => $this->getRedirect(),
];

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ class UploadRequest extends FormRequest
public function rules()
{
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 [
'id' => $this->id,
'active' => $this->active,
'title' => $description->title,
'title_format' => sub_string($description->title, 64),
'summary' => $description->summary,
'summary_format' => sub_string($description->summary, 128),
'meta_title' => $description->meta_title,
'meta_description' => $description->meta_description,
'meta_keywords' => $description->meta_keywords,
'title' => $description->title ?? '',
'title_format' => sub_string($description->title ?? '', 64),
'summary' => $description->summary ?? '',
'summary_format' => sub_string($description->summary ?? '', 128),
'meta_title' => $description->meta_title ?? '',
'meta_description' => $description->meta_description ?? '',
'meta_keywords' => $description->meta_keywords ?? '',
'created_at' => time_format($this->created_at),
'updated_at' => time_format($this->updated_at),
];

View File

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

View File

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

View File

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

View File

@ -68,6 +68,9 @@ class AttributeGroupRepo
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);
if ($group->attributes->count()) {
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;
use Beike\Models\Attribute;
use Beike\Models\AttributeDescription;
use Beike\Models\AttributeValue;
use Beike\Models\ProductAttribute;
@ -138,4 +139,14 @@ class AttributeRepo
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;
use Beike\Models\Product;
use Beike\Repositories\CustomerRepo;
use Beike\Repositories\OrderRepo;
use Beike\Repositories\ProductRepo;
class DashboardRepo
{
@ -21,12 +21,22 @@ class DashboardRepo
* 获取商品总数
*
* @return array
* @throws \Exception
*/
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 [
'total' => quantity_format(Product::query()->count()),
'percentage' => 0,
'total' => $today,
'percentage' => $percentage,
];
}

View File

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

View File

@ -138,7 +138,7 @@ class PermissionRepo
*/
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);
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)
->group(function () {
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');
@ -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_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_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_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_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_create')->get('products/create', [Controllers\ProductController::class, 'create'])->name('products.create');
Route::middleware('can:products_create')->post('products', [Controllers\ProductController::class, 'store'])->name('products.store');
Route::middleware('can:products_update')->get('products/{product}/edit', [Controllers\ProductController::class, 'edit'])->name('products.edit');
Route::middleware('can:products_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_delete')->delete('products/{product}', [Controllers\ProductController::class, 'destroy'])->name('products.destroy');

View File

@ -13,11 +13,13 @@ namespace Beike\Admin\Services;
class FileManagerService
{
private $fileBasePath = '';
protected $fileBasePath = '';
protected $basePath = '';
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
{
$currentBasePath = rtrim($this->fileBasePath . $baseFolder, '/');
$directories = glob("{$currentBasePath}/*", GLOB_ONLYDIR);
$result = [];
@ -49,18 +52,34 @@ class FileManagerService
* 获取某个目录下的文件和文件夹
*
* @param $baseFolder
* @param $sort
* @param $order
* @param int $page
* @param int $perPage
* @return array
* @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, '/');
$files = glob($currentBasePath . '/*');
usort($files, function ($a, $b) {
return filemtime($a) - filemtime($b) < 0;
});
if ($sort == 'created') {
if ($order == 'desc') {
usort($files, function ($a, $b) {
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 = [];
foreach ($files as $file) {
@ -68,7 +87,7 @@ class FileManagerService
if ($baseName == 'index.html') {
continue;
}
$fileName = str_replace($this->fileBasePath, '', $file);
$fileName = str_replace(public_path('catalog'), '', $file);
if (is_file($file)) {
$images[] = $this->handleImage($fileName, $baseName);
}
@ -98,7 +117,7 @@ class FileManagerService
*/
public function createDirectory($folderName)
{
$catalogFolderPath = "catalog/{$folderName}";
$catalogFolderPath = "catalog{$this->basePath}/{$folderName}";
$folderPath = public_path($catalogFolderPath);
if (is_dir($folderPath)) {
throw new \Exception(trans('admin/file_manager.directory_already_exist'));
@ -114,7 +133,7 @@ class FileManagerService
*/
public function deleteDirectoryOrFile($filePath)
{
$filePath = public_path("catalog/{$filePath}");
$filePath = public_path("catalog{$this->basePath}/{$filePath}");
if (is_dir($filePath)) {
$files = glob($filePath . '/*');
if ($files) {
@ -138,7 +157,7 @@ class FileManagerService
return;
}
foreach ($files as $file) {
$filePath = public_path("catalog/{$basePath}/$file");
$filePath = public_path("catalog{$this->basePath}/{$basePath}/$file");
if (file_exists($filePath)) {
@unlink($filePath);
}
@ -154,7 +173,7 @@ class FileManagerService
*/
public function updateName($originPath, $newPath)
{
$originPath = public_path("catalog/{$originPath}");
$originPath = public_path("catalog{$this->basePath}/{$originPath}");
if (! is_dir($originPath) && ! file_exists($originPath)) {
throw new \Exception(trans('admin/file_manager.target_not_exist'));
}
@ -166,6 +185,13 @@ class FileManagerService
@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
{
$path = public_path("catalog/{$folderPath}");
$path = public_path("catalog{$this->basePath}/{$folderPath}");
$subFiles = glob($path . '/*');
foreach ($subFiles as $subFile) {
if (is_dir($subFile)) {
@ -210,12 +236,19 @@ class FileManagerService
*/
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 [
'path' => $path,
'name' => $baseName,
'origin_url' => image_origin($path),
'mime' => $mime,
'selected' => false,
];
}

View File

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

View File

@ -14,16 +14,19 @@ class Select extends Component
public array $options;
public string $width;
public string $key;
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->title = $title;
$this->value = $value;
$this->options = $options;
$this->width = $width;
$this->key = $key;
$this->label = $label;
}

View File

@ -3,6 +3,7 @@
namespace Beike\Admin\View\Components;
use Beike\Models\AdminUser;
use Illuminate\Support\Facades\View;
use Illuminate\View\Component;
class Header extends Component
@ -11,6 +12,10 @@ class Header extends Component
private ?AdminUser $adminUser;
public array $commonLinks;
public array $historyLinks;
/**
* Create a new component instance.
*
@ -28,82 +33,102 @@ class Header extends Component
*/
public function render()
{
$sidebar = new Sidebar();
$preparedMenus = $this->prepareMenus();
foreach ($preparedMenus as $menu) {
$menuCode = $menu['code'] ?? '';
if ($menuCode) {
$routes = [];
$subRoutesMethod = "get{$menu['code']}SubRoutes";
if (method_exists($sidebar, $subRoutesMethod)) {
$sideMenuRoutes = $sidebar->{"get{$menu['code']}SubRoutes"}();
foreach ($sideMenuRoutes as $route) {
$routeFirst = explode('.', $route['route'])[0] ?? '';
$routes[] = 'admin.' . $route['route'];
$routes[] = 'admin.' . $routeFirst . '.edit';
$routes[] = 'admin.' . $routeFirst . '.show';
}
}
$data = [
'menu_code' => $menuCode,
'routes' => $routes,
];
$filterRoutes = hook_filter('admin.components.header.routes', $data);
$routes = $filterRoutes['routes'] ?? [];
if (empty($routes)) {
$is_route = equal_route('admin.' . $menu['route']);
} else {
$is_route = equal_route($routes);
}
} else {
$is_route = equal_route('admin.' . $menu['route']);
}
$this->addLink($menu['name'], $menu['route'], $is_route);
}
$this->commonLinks = $this->getCommonLinks();
$this->historyLinks = $this->handleHistoryLinks();
return view('admin::components.header');
}
/**
* 默认菜单
* 常用功能链接
*/
private function prepareMenus()
private function getCommonLinks()
{
$menus = [
['name' => trans('admin/common.home'), 'route' => 'home.index', 'code' => ''],
['name' => trans('admin/common.order'), 'route' => 'orders.index', 'code' => 'Order'],
['name' => trans('admin/common.product'), 'route' => 'products.index', 'code' => 'Product'],
['name' => trans('admin/common.customer'), 'route' => 'customers.index', 'code' => 'Customer'],
['name' => trans('admin/common.page'), 'route' => 'pages.index', 'code' => 'Page'],
['name' => trans('admin/common.setting'), 'route' => 'settings.index', 'code' => 'Setting'],
['name' => trans('admin/common.inquiry'), 'route' => 'inquiry.index', 'code' => 'Inquiry'],
$commonLinks = [
['route' => 'design.index', 'icon' => 'bi bi-palette', 'blank' => true],
['route' => 'design_footer.index', 'icon' => 'bi bi-palette', 'blank' => true],
['route' => 'design_menu.index', 'icon' => 'bi bi-list', 'blank' => false],
['route' => 'languages.index', 'icon' => 'bi bi-globe2', 'blank' => false],
['route' => 'currencies.index', 'icon' => 'bi bi-currency-dollar', 'blank' => false],
['route' => 'plugins.index', 'icon' => 'bi bi-plug', 'blank' => false],
];
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);
if ($this->adminUser->cannot($permissionRoute) && $route != 'home.index') {
return;
$links = [];
$histories = $this->getHistoryRoutesFromSession();
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}");
}
if (stripos($title, 'admin/common.') !== false) {
$tempRouteName = str_replace('s.index', '', $routeName);
$title = trans("admin/common.{$tempRouteName}");
}
try {
$url = admin_route($routeName);
} catch (\Exception $e) {
$url = '';
}
if (empty($url)) {
continue;
}
$links[] = [
'route' => $routeName,
'url' => $url,
'title' => $title,
];
}
$url = admin_route($route);
$this->links[] = [
'title' => $title,
'url' => $url,
'active' => $active,
];
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;
use Beike\Models\AdminUser;
use Illuminate\Support\Str;
use Beike\Plugin\Plugin;
use Illuminate\View\Component;
class Sidebar extends Component
{
public array $links = [];
public ?array $currentLink;
private string $adminName;
private ?string $routeNameWithPrefix;
private ?string $currentRouteName;
private ?string $currentPrefix;
private ?AdminUser $adminUser;
/**
@ -23,9 +29,14 @@ class Sidebar extends Component
*/
public function __construct()
{
$this->adminName = admin_name();
$this->adminName = admin_name();
$this->adminUser = current_user();
$this->routeNameWithPrefix = request()->route()->getName();
$this->adminUser = current_user();
$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()
{
$adminName = $this->adminName;
$routeNameWithPrefix = request()->route()->getName();
$routeName = str_replace($adminName . '.', '', $routeNameWithPrefix);
if (Str::startsWith($routeName, $this->getHomeSubPrefix())) {
$routes = $this->getHomeSubRoutes();
foreach ($routes as $route) {
$this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
}
} elseif (Str::startsWith($routeName, $this->getProductSubPrefix())) {
$routes = $this->getProductSubRoutes();
foreach ($routes as $route) {
$this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
}
} elseif (Str::startsWith($routeName, $this->getCustomerSubPrefix())) {
$routes = $this->getCustomerSubRoutes();
foreach ($routes as $route) {
$this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
}
} elseif (Str::startsWith($routeName, $this->getOrderSubPrefix())) {
$routes = $this->getOrderSubRoutes();
foreach ($routes as $route) {
$this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
}
} elseif (Str::startsWith($routeName, $this->getPageSubPrefix())) {
$routes = $this->getPageSubRoutes();
foreach ($routes as $route) {
$this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
}
} elseif (Str::startsWith($routeName, $this->getSettingSubPrefix())) {
$routes = $this->getSettingSubRoutes();
foreach ($routes as $route) {
$this->addLink($route, $this->equalRoute($route['route']), (bool) ($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
}
} 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);
}
}
$this->links = $this->getMenus();
$this->handleMenus();
$this->currentLink = $this->getCurrentLink();
return view('admin::components.sidebar');
}
/**
* 添加左侧菜单链接
* 返回所有菜单
* 小图标地址 https://icons.getbootstrap.com/
*
* @param $routeData
* @param $active
* @param false $newWindow
* @param int $hide_mobile
* @return mixed
*/
private function addLink($routeData, $active, bool $newWindow = false, int $hide_mobile = 0)
private function getMenus(): mixed
{
$route = $routeData['route'];
$icon = $routeData['icon'] ?? '';
$title = $routeData['title'] ?? '';
$permissionRoute = str_replace('.', '_', $route);
if ($this->adminUser->cannot($permissionRoute)) {
return;
}
if (empty($title)) {
$title = trans("admin/common.{$permissionRoute}");
}
$url = admin_route($route);
$this->links[] = [
'title' => $title,
'url' => $url,
'icon' => $icon,
'active' => $active,
'hide_mobile' => $hide_mobile,
'new_window' => $newWindow,
$menus = [
[
'route' => 'home.index',
'title' => trans('admin/common.home'),
'icon' => 'bi bi-house',
'prefixes' => $this->getHomeSubPrefix(),
],
[
'route' => 'orders.index',
'title' => trans('admin/common.order'),
'icon' => 'bi bi-clipboard-check',
'prefixes' => $this->getOrderSubPrefix(),
'children' => $this->getOrderSubRoutes(),
],
[
'route' => 'products.index',
'title' => trans('admin/common.product'),
'icon' => 'bi bi-box-seam',
'prefixes' => $this->getProductSubPrefix(),
'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()
{
$prefix = ['home.'];
$prefix = ['home'];
return hook_filter('admin.sidebar.home.prefix', $prefix);
}
@ -127,7 +225,7 @@ class Sidebar extends Component
*/
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);
}
@ -137,7 +235,7 @@ class Sidebar extends Component
*/
private function getCustomerSubPrefix()
{
$prefix = ['customers.', 'customer_groups.'];
$prefix = ['customers', 'customer_groups'];
return hook_filter('admin.sidebar.customer.prefix', $prefix);
}
@ -147,7 +245,7 @@ class Sidebar extends Component
*/
private function getOrderSubPrefix()
{
$prefix = ['orders.', 'rmas.', 'rma_reasons.'];
$prefix = ['orders', 'rmas', 'rma_reasons'];
return hook_filter('admin.sidebar.order.prefix', $prefix);
}
@ -157,7 +255,7 @@ class Sidebar extends Component
*/
private function getPageSubPrefix()
{
$prefix = ['pages.', 'page_categories.'];
$prefix = ['pages', 'page_categories'];
return hook_filter('admin.sidebar.page.prefix', $prefix);
}
@ -172,12 +270,35 @@ class Sidebar extends Component
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()
{
$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);
}
@ -187,14 +308,7 @@ class Sidebar extends Component
*/
public function getHomeSubRoutes()
{
$routes = [
['route' => 'design.index', 'icon' => 'fa fa-tachometer-alt', 'blank' => 1, 'hide_mobile' => 1],
['route' => 'design_footer.index', 'icon' => 'fa fa-tachometer-alt', 'blank' => 1, 'hide_mobile' => 1],
['route' => 'design_menu.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
['route' => 'languages.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
['route' => 'currencies.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
['route' => 'plugins.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
];
$routes = [];
return hook_filter('admin.sidebar.home_routes', $routes);
}
@ -205,12 +319,13 @@ class Sidebar extends Component
public function getProductSubRoutes()
{
$routes = [
['route' => 'categories.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'products.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'brands.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
['route' => 'attribute_groups.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'attributes.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'products.trashed', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'products.index', 'prefixes' => ['products'], 'excludes' => ['products.trashed']],
['route' => 'categories.index', 'prefixes' => ['categories']],
['route' => 'brands.index', 'prefixes' => ['brands']],
['route' => 'attribute_groups.index', 'prefixes' => ['attribute_groups']],
['route' => 'attributes.index', 'prefixes' => ['attributes']],
['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);
@ -222,9 +337,9 @@ class Sidebar extends Component
public function getCustomerSubRoutes()
{
$routes = [
['route' => 'customers.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'customer_groups.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'customers.trashed', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'customers.index', 'prefixes' => ['customers'], 'excludes' => ['customers.trashed']],
['route' => 'customer_groups.index', 'prefixes' => ['customer_groups']],
['route' => 'customers.trashed', 'prefixes' => ['customers'], 'excludes' => ['customers.index', 'customers.edit']],
];
return hook_filter('admin.sidebar.customer_routes', $routes);
@ -236,9 +351,9 @@ class Sidebar extends Component
public function getOrderSubRoutes()
{
$routes = [
['route' => 'orders.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'rmas.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'rma_reasons.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'orders.index', 'prefixes' => ['orders']],
['route' => 'rmas.index', 'prefixes' => ['rmas']],
['route' => 'rma_reasons.index', 'prefixes' => ['rma_reasons']],
];
return hook_filter('admin.sidebar.order_routes', $routes);
@ -251,8 +366,8 @@ class Sidebar extends Component
public function getPageSubRoutes()
{
$routes = [
['route' => 'page_categories.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'pages.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'pages.index', 'prefixes' => ['pages']],
['route' => 'page_categories.index', 'prefixes' => ['page_categories']],
];
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 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
@ -278,36 +433,18 @@ class Sidebar extends Component
public function getSettingSubRoutes()
{
$routes = [
['route' => 'settings.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'admin_users.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'plugins.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
['route' => 'theme.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
['route' => 'marketing.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
['route' => 'regions.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'tax_rates.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'tax_classes.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'currencies.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'languages.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'countries.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'zones.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'design.index', 'icon' => 'fa fa-tachometer-alt', 'blank' => true, 'hide_mobile' => 1],
['route' => 'design_footer.index', 'icon' => 'fa fa-tachometer-alt', 'blank' => true, 'hide_mobile' => 1],
['route' => 'design_menu.index', 'icon' => 'fa fa-tachometer-alt', 'hide_mobile' => 1],
['route' => 'settings.index', 'prefixes' => ['settings']],
['route' => 'account.index', 'prefixes' => ['account']],
['route' => 'admin_users.index', 'prefixes' => ['admin_users', 'admin_roles']],
['route' => 'regions.index', 'prefixes' => ['regions']],
['route' => 'tax_rates.index', 'prefixes' => ['tax_rates']],
['route' => 'tax_classes.index', 'prefixes' => ['tax_classes']],
['route' => 'currencies.index', 'prefixes' => ['currencies']],
['route' => 'languages.index', 'prefixes' => ['languages']],
['route' => 'countries.index', 'prefixes' => ['countries']],
['route' => 'zones.index', 'prefixes' => ['zones']],
];
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 [
'api_url' => env('BEIKE_API_URL', 'https://beikeshop.com'),
'version' => '1.3.4',
'build' => '20230324',
'version' => '1.3.8',
'build' => '20230719',
'admin_name' => env('ADMIN_NAME'),
'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\Services\CurrencyService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Session;
@ -209,7 +210,12 @@ function equal_route($routeName): bool
*/
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序列化结果
*/
function json_success($message, $data = []): array
function json_success($message, $data = [])
{
return [
'status' => 'success',
'message' => $message,
'data' => $data,
];
}
/**
* 返回json序列化结果
*/
function json_fail($message, $data = []): array
function json_fail($message, $data = [], $status = 422): JsonResponse
{
return [
$data = [
'status' => 'fail',
'message' => $message,
'data' => $data,
];
return response()->json($data, $status);
}
if (! function_exists('sub_string')) {
@ -768,3 +777,42 @@ function list_sort_by($list, $field, $sortby='asc') {
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

@ -49,10 +49,10 @@ class Hook
}
/**
* @param string $hook
* @param array $params
* @param string $hook
* @param array $params
* @param callable|null $callback
* @param string $htmlContent
* @param string $htmlContent
* @return string|void|null
*/
public function getHook(string $hook, array $params = [], callable $callback = null, string $htmlContent = '')
@ -64,12 +64,11 @@ class Hook
return $this->get($hook, $params, $callback, $htmlContent);
}
/**
* @param string $hook
* @param array $params
* @param string $hook
* @param array $params
* @param callable|null $callback
* @param string $htmlContent
* @param string $htmlContent
* @return string|void|null
*/
public function getWrapper(string $hook, array $params = [], callable $callback = null, string $htmlContent = '')
@ -114,11 +113,15 @@ class Hook
$priority = null;
}
if (isset($this->watch[$hook][$priority])) {
$priority++;
}
$this->watch[$hook][$priority] = [
'function' => $function,
'caller' => [
//'file' => $caller['file'],
//'line' => $caller['line'],
'file' => $caller['file'],
'line' => $caller['line'],
'class' => Arr::get($caller, 'class'),
],
];
@ -192,7 +195,7 @@ class Hook
* Return a new callback object.
*
* @param callable $callback function
* @param array $params parameters
* @param array $params parameters
*
* @return Callback
*/

View File

@ -5,6 +5,7 @@ namespace Beike\Hook;
use Beike\Hook\Console\HookListeners;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
class HookServiceProvider extends ServiceProvider
{
@ -19,7 +20,6 @@ class HookServiceProvider extends ServiceProvider
});
}
public function boot()
{
$this->bootHookDirectives();
@ -33,10 +33,11 @@ class HookServiceProvider extends ServiceProvider
protected function bootHookDirectives()
{
Blade::directive('hook', function ($parameter) {
$parameter = trim($parameter, '()');
$parameter = trim($parameter, '()');
$parameters = explode(',', $parameter);
$name = trim($parameters[0], "'");
$definedVars = $this->parseParameters($parameters);
return ' <?php
$__definedVars = (get_defined_vars()["__data"]);
@ -44,6 +45,7 @@ class HookServiceProvider extends ServiceProvider
{
$__definedVars = [];
}
'. $definedVars .'
$output = \Hook::getHook("' . $name . '",["data"=>$__definedVars],function($data) { return null; });
if ($output)
echo $output;
@ -51,7 +53,6 @@ class HookServiceProvider extends ServiceProvider
});
}
/**
* 添加 blade wrapper hook 标签
*
@ -60,9 +61,9 @@ class HookServiceProvider extends ServiceProvider
protected function bootWrapperHookDirectives()
{
Blade::directive('hookwrapper', function ($parameter) {
$parameter = trim($parameter, '()');
$parameter = trim($parameter, '()');
$parameters = explode(',', $parameter);
$name = trim($parameters[0], "'");
$name = trim($parameters[0], "'");
return ' <?php
$__hook_name="' . $name . '";
@ -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\Installer\Helpers\DatabaseManager;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class DatabaseController extends Controller
class DatabaseController extends BaseController
{
/**
* @var DatabaseManager
@ -31,6 +30,7 @@ class DatabaseController extends Controller
*/
public function index()
{
$this->checkInstalled();
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
$rows = DB::select('SHOW TABLES');
$database = config('database.connections.mysql.database');

View File

@ -3,14 +3,14 @@
namespace Beike\Installer\Controllers;
use Beike\Installer\Helpers\EnvironmentManager;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
class EnvironmentController extends Controller
class EnvironmentController extends BaseController
{
/**
* @var EnvironmentManager
@ -32,6 +32,7 @@ class EnvironmentController extends Controller
*/
public function index()
{
$this->checkInstalled();
$steps = 4;
return view('installer::environment-wizard', compact('steps'));
@ -46,6 +47,7 @@ class EnvironmentController extends Controller
*/
public function saveWizard(Request $request, Redirector $redirect): RedirectResponse
{
$this->checkInstalled();
$rules = config('installer.environment.form.rules');
$messages = [
'environment_custom.required_if' => trans('installer::installer_messages.environment.name_required'),
@ -74,10 +76,11 @@ class EnvironmentController extends Controller
* 数据库信息检测
*
* @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');
$messages = [
'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
{
$this->checkInstalled();
$connection = $request->input('database_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\FinalInstallManager;
use Beike\Installer\Helpers\InstalledFileManager;
use Illuminate\Routing\Controller;
class FinalController extends Controller
class FinalController extends BaseController
{
/**
* Update installed file and display finished view.
@ -19,6 +18,8 @@ class FinalController extends Controller
*/
public function index(InstalledFileManager $fileManager, FinalInstallManager $finalInstall, EnvironmentManager $environment)
{
$this->checkInstalled();
$finalMessages = $finalInstall->runFinal();
$finalStatusMessage = $fileManager->update();
$finalEnvFile = $environment->getEnvContent();

View File

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

View File

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

View File

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

View File

@ -230,29 +230,27 @@
$('.database-loading').addClass('d-none');
},
success: function(json) {
if (json.status == 'fail') {
var data = Object.keys(json.data);
$('.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);
data.forEach((e)=> {
$('.database-link-wrap input[name="' + e + '"]').addClass('is-invalid').next('.invalid-feedback').text(json.data[e])
})
data.forEach((e)=> {
$('.database-link-wrap input[name="' + e + '"]').addClass('is-invalid').next('.invalid-feedback').text(json.data[e])
})
if (json.data.database_version) {
$('.title-status .text-danger').removeClass('d-none').find('span').text(json.data.database_version);
}
if (json.data.database_other) {
$('.title-status .text-danger').removeClass('d-none').find('span').text(json.data.database_other);
}
$('.admin-data-wrap').addClass('d-none')
if (json.data.database_version) {
$('.title-status .text-danger').removeClass('d-none').find('span').text(json.data.database_version);
}
if (json.status == 'success') {
$('.database-link-wrap input').addClass('is-valid')
$('.title-status .text-success').removeClass('d-none')
$('.admin-data-wrap').removeClass('d-none')
if (json.data.database_other) {
$('.title-status .text-danger').removeClass('d-none').find('span').text(json.data.database_other);
}
$('.admin-data-wrap').addClass('d-none')
}
});
}

View File

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

View File

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

View File

@ -34,6 +34,9 @@ return [
'PDO',
'Tokenizer',
'XML',
'ZIP',
'GD',
'PDO_MYSQL',
],
'apache' => [
'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 ?? '';
} elseif ($type == '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 ?? '';

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 Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as AuthUser;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
use Tymon\JWTAuth\Contracts\JWTSubject;
class AdminUser extends AuthUser
class AdminUser extends AuthUser implements JWTSubject
{
use HasFactory, HasRoles;
use Notifiable;
@ -17,6 +19,11 @@ class AdminUser extends AuthUser
protected $fillable = ['name', 'email', 'locale', 'password', 'active'];
public function tokens(): HasMany
{
return $this->hasMany(AdminUserToken::class);
}
public function notifyVerifyCodeForForgotten($code)
{
$useQueue = system_setting('base.use_queue', true);
@ -26,4 +33,24 @@ class AdminUser extends AuthUser
$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\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class Customer extends Authenticatable
class Customer extends Authenticatable implements JWTSubject
{
use HasFactory;
use SoftDeletes;
@ -66,4 +67,24 @@ class Customer extends Authenticatable
$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);
}
public function orderPayments(): HasMany
{
return $this->hasMany(OrderPayment::class);
}
public function subTotal()
{
$totals = $this->orderTotals;
@ -68,7 +73,9 @@ class Order extends Base
public function getStatusFormatAttribute()
{
return trans('order.' . $this->status);
$statusMap = array_column(StateMachineService::getAllStatuses(), 'name', 'status');
return $statusMap[$this->status];
}
public function getTotalFormatAttribute()

View File

@ -11,6 +11,8 @@
namespace Beike\Models;
use Beike\Services\StateMachineService;
class OrderHistory extends Base
{
protected $fillable = [
@ -21,6 +23,8 @@ class OrderHistory extends Base
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;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
class PageCategory extends Model
class PageCategory extends Base
{
protected $table = 'page_categories';

View File

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

View File

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

View File

@ -11,7 +11,7 @@ class Product extends Base
use HasFactory;
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 = [
'active' => 'boolean',

View File

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

View File

@ -21,13 +21,13 @@ use Illuminate\Support\Str;
class Plugin implements Arrayable, \ArrayAccess
{
public const TYPES = [
'shipping', // 配送方式
'payment', // 支付方式
'shipping', // 配送方式
'theme', // 主题模板
'feature', // 功能模块
'total', // 订单金额
'social', // 社交网络
'feature', // 功能模块
'language', // 语言翻译
'theme', // 主题模板
];
protected $type;
@ -171,6 +171,13 @@ class Plugin implements Arrayable, \ArrayAccess
$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;
}

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 .= ' FROM category_paths cp';
$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 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);
}

View File

@ -98,6 +98,6 @@ class LanguageRepo
*/
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 = [])
{
$builder = self::getListBuilder($filters)->orderByDesc('created_at');
$builder = static::getListBuilder($filters)->orderByDesc('created_at');
return $builder->get();
}
@ -44,7 +44,7 @@ class OrderRepo
*/
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());
}
@ -56,7 +56,7 @@ class OrderRepo
*/
public static function getLatestOrders($customer, $limit)
{
return self::getListBuilder(['customer' => $customer])
return static::getListBuilder(['customer' => $customer])
->orderByDesc('created_at')
->take($limit)
->get();
@ -68,7 +68,7 @@ class OrderRepo
*/
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());
}
@ -79,7 +79,7 @@ class OrderRepo
*/
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;
if ($number) {
@ -199,14 +199,12 @@ class OrderRepo
$shippingAddress = Address::query()->findOrFail($shippingAddressId);
$paymentAddress = Address::query()->findOrFail($paymentAddressId);
$email = $customer->email;
$email = $customer->email;
} else {
$shippingAddress = new Address($current['guest_shipping_address'] ?? []);
$paymentAddress = new Address($current['guest_payment_address'] ?? []);
$email = $current['guest_shipping_address']['email'];
}
$shippingAddress->country = $shippingAddress->country->name ?? '';
$shippingAddress->country_id = $shippingAddress->country->id ?? 0;
$paymentAddress->country = $paymentAddress->country->name ?? '';
@ -268,6 +266,8 @@ class OrderRepo
OrderProductRepo::createOrderProducts($order, $carts['carts']);
OrderTotalRepo::createTotals($order, $totals);
hook_filter('repository.order.create.after', ['order' => $order, 'data' => $data]);
return $order;
}

View File

@ -31,12 +31,16 @@ class PageCategoryRepo
/**
* @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;
$limit = (int) ($filters['limit'] ?? 0);
$builder = self::getBuilder($filters);
if ($limit > 0) {
return $builder->limit($limit)->get();
}
return $builder->paginate(perPage());
}
@ -150,11 +154,20 @@ class PageCategoryRepo
}
/**
* @param $page
* @param $pageCategory
* @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)
{
$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')
->paginate($filterData['per_page'] ?? perPage())
@ -72,7 +72,7 @@ class ProductRepo
if (! $productIds) {
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();
return ProductSimple::collection($products);
@ -81,10 +81,11 @@ class ProductRepo
/**
* 获取商品筛选对象
*
* @param array $data
* @param array $filters
* @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');
@ -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']);
if (isset($data['category_id'])) {
$builder->whereHas('categories', function ($query) use ($data) {
if (is_array($data['category_id'])) {
$query->whereIn('category_id', $data['category_id']);
if (isset($filters['category_id'])) {
$builder->whereHas('categories', function ($query) use ($filters) {
if (is_array($filters['category_id'])) {
$query->whereIn('category_id', $filters['category_id']);
} 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) {
$builder->whereIn('products.id', $productIds);
$productIds = implode(',', $productIds);
$builder->orderByRaw("FIELD(products.id, {$productIds})");
}
// attr 格式:attr=10:10/13|11:34/23|3:4
if (isset($data['attr']) && $data['attr']) {
$attributes = self::parseFilterParamsAttr($data['attr']);
// attr 格式:attr=10:10,13|11:34,23|3:4
if (isset($filters['attr']) && $filters['attr']) {
$attributes = self::parseFilterParamsAttr($filters['attr']);
foreach ($attributes as $attribute) {
$builder->whereHas('attributes', function ($query) use ($attribute) {
$query->where('attribute_id', $attribute['attr'])
@ -127,21 +128,21 @@ class ProductRepo
}
}
if (isset($data['sku']) || isset($data['model'])) {
$builder->whereHas('skus', function ($query) use ($data) {
if (isset($data['sku'])) {
$query->where('sku', 'like', "%{$data['sku']}%");
if (isset($filters['sku']) || isset($filters['model'])) {
$builder->whereHas('skus', function ($query) use ($filters) {
if (isset($filters['sku'])) {
$query->where('sku', 'like', "%{$filters['sku']}%");
}
if (isset($data['model'])) {
$query->where('model', 'like', "%{$data['model']}%");
if (isset($filters['model'])) {
$query->where('model', 'like', "%{$filters['model']}%");
}
});
}
if (isset($data['price']) && $data['price']) {
$builder->whereHas('skus', function ($query) use ($data) {
if (isset($filters['price']) && $filters['price']) {
$builder->whereHas('skus', function ($query) use ($filters) {
// price 格式:price=30-100
$prices = explode('-', $data['price']);
$prices = explode('-', $filters['price']);
if (! $prices[1]) {
$query->where('price', '>', $prices[0] ?: 0)->where('is_default', 1);
} else {
@ -150,34 +151,54 @@ class ProductRepo
});
}
if (isset($data['name'])) {
$builder->where('pd.name', 'like', "%{$data['name']}%");
if (isset($filters['name'])) {
$builder->where('pd.name', 'like', "%{$filters['name']}%");
}
$keyword = $data['keyword'] ?? '';
$keyword = trim($filters['keyword'] ?? '');
if ($keyword) {
$builder->where(function (Builder $query) use ($keyword) {
$query->whereHas('skus', function (Builder $query) use ($keyword) {
$query->where('sku', 'like', "%{$keyword}%")
->orWhere('model', 'like', "%{$keyword}%");
})->orWhere('pd.name', 'like', "%{$keyword}%");
$keywords = explode(' ', $keyword);
$keywords = array_unique($keywords);
$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}%");
}
});
foreach ($keywords as $keyword) {
$query->orWhere('pd.name', 'like', "%{$keyword}%");
}
});
}
if (isset($data['active'])) {
$builder->where('active', (int) $data['active']);
if (isset($filters['created_start'])) {
$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();
}
$sort = $data['sort'] ?? 'products.position';
$order = $data['order'] ?? 'desc';
$sort = $filters['sort'] ?? 'products.position';
$order = $filters['order'] ?? 'desc';
$builder->orderBy($sort, $order);
return $builder;
return hook_filter('repo.product.builder', $builder);
}
public static function parseFilterParamsAttr($attr)
@ -191,7 +212,7 @@ class ProductRepo
return [
'attr' => $itemArr[0],
'value' => explode('/', $itemArr[1]),
'value' => explode(',', $itemArr[1]),
];
}, $attributes);
@ -200,13 +221,18 @@ class ProductRepo
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'])
->with(['attributes.attribute.description', 'attributes.attribute_value.description'])
->leftJoin('product_attributes as pa', 'pa.product_id', 'products.id')
->whereNotNull('pa.attribute_id')
->distinct()
->reorder('pa.attribute_id');
if ($attributesIds = system_setting('base.multi_filter', [])['attribute'] ?? []) {
$builder->whereIn('pa.attribute_id', $attributesIds);
}
$productAttributes = $builder->get()->toArray();
$attributeMap = array_column(Attribute::query()->with('description')->orderBy('sort_order')->get()->toArray(), null, 'id');
@ -249,7 +275,7 @@ class ProductRepo
{
$selectPrice = $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);
$min = $builder->min('ps.price');
$max = $builder->max('ps.price');
@ -268,7 +294,7 @@ class ProductRepo
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)
@ -328,7 +354,7 @@ class ProductRepo
}
$items = [];
$products = self::getBuilder()->select('id')->get();
$products = static::getBuilder()->select('id')->get();
foreach ($products as $product) {
$items[$product->id] = [
'id' => $product->id,

View File

@ -95,9 +95,9 @@ class RmaRepo
/**
* @param $data
* @return LengthAwarePaginator
* @return Builder
*/
public static function list($data): LengthAwarePaginator
public static function getBuilder($data): Builder
{
$builder = Rma::query();
@ -124,7 +124,16 @@ class RmaRepo
}
$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 Carbon\Carbon;
use Illuminate\Support\Facades\Artisan;
class SettingRepo
{
@ -117,6 +118,7 @@ class SettingRepo
];
}
Setting::query()->insert($rows);
self::clearCache();
}
/**
@ -154,5 +156,17 @@ class SettingRepo
} else {
$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;
}
$symbol_left = $this->currencies[$currency]->symbol_left;
$symbol_right = $this->currencies[$currency]->symbol_right;
$decimal_place = $this->currencies[$currency]->decimal_place;
$currencyRow = $this->currencies[$currency] ?? null;
if (empty($currencyRow)) {
return $amount;
}
$symbol_left = $currencyRow->symbol_left;
$symbol_right = $currencyRow->symbol_right;
$decimal_place = $currencyRow->decimal_place;
if (! $value) {
$value = $this->currencies[$currency]->value;
$value = $currencyRow->value;
}
$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'));
if ($symbol_right) {
$string .= $symbol_right;
$string .= ' ' . $symbol_right;
}
return $string;

View File

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

View File

@ -15,6 +15,7 @@ use Beike\Models\Order;
use Beike\Models\OrderHistory;
use Beike\Models\OrderShipment;
use Beike\Models\Product;
use Beike\Repositories\OrderPaymentRepo;
use Throwable;
class StateMachineService
@ -29,6 +30,8 @@ class StateMachineService
private array $shipment;
private array $payment;
public const CREATED = 'created'; // 已创建
public const UNPAID = 'unpaid'; // 待支付
@ -116,6 +119,19 @@ class StateMachineService
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);
}
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)
{
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;
$orderNumber = $this->order->number;
$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());
$data = [
'countries' => CountryRepo::all(),
'countries' => CountryRepo::listEnabled(),
'addresses' => AddressResource::collection($addresses),
];

View File

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

View File

@ -35,20 +35,30 @@ class LoginController extends Controller
public function store(LoginRequest $request)
{
$guestCartProduct = CartRepo::allCartProducts(0);
if (! auth(Customer::AUTH_GUARD)->attempt($request->only('email', 'password'))) {
throw new NotAcceptableHttpException(trans('shop/login.email_or_password_error'));
$data = [
'request_data' => $request->all(),
];
try {
hook_action('shop.account.login.before', $data);
$guestCartProduct = CartRepo::allCartProducts(0);
if (! auth(Customer::AUTH_GUARD)->attempt($request->only('email', 'password'))) {
throw new NotAcceptableHttpException(trans('shop/login.email_or_password_error'));
}
$customer = current_customer();
if ($customer && $customer->status != 1) {
Auth::guard(Customer::AUTH_GUARD)->logout();
throw new NotFoundHttpException(trans('shop/login.customer_inactive'));
}
CartRepo::mergeGuestCart($customer, $guestCartProduct);
return json_success(trans('shop/login.login_successfully'));
} catch (\Exception $e) {
return json_fail($e->getMessage());
}
$customer = current_customer();
if ($customer && $customer->status != 1) {
Auth::guard(Customer::AUTH_GUARD)->logout();
throw new NotFoundHttpException(trans('shop/login.customer_inactive'));
}
CartRepo::mergeGuestCart($customer, $guestCartProduct);
return json_success(trans('shop/login.login_successfully'));
}
}

View File

@ -57,21 +57,6 @@ class OrderController extends Controller
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;
use Beike\Repositories\OrderRepo;
use Beike\Shop\Services\CheckoutService;
use Illuminate\Http\Request;
@ -32,9 +33,9 @@ class CheckoutController extends Controller
* 更改结算信息
*
* @param Request $request
* @return array
* @return mixed
*/
public function update(Request $request): array
public function update(Request $request): mixed
{
try {
$requestData = $request->all();
@ -55,8 +56,23 @@ class CheckoutController extends Controller
*/
public function confirm()
{
$data = (new CheckoutService)->confirm();
try {
$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\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use League\Csv\CannotInsertRecord;
class Controller extends \App\Http\Controllers\Controller
{
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_pages' => PageRepo::getCategoryPages(),
];
$data = hook_filter('page_categories.home.data', $data);
return view('page_categories/home', $data);
}
@ -41,10 +42,11 @@ class PageCategoryController extends Controller
$data = [
'category' => new PageCategoryDetail($pageCategory),
'active_page_categories' => PageCategoryRepo::getActiveList(),
'active_page_categories' => PageCategoryRepo::getActiveList(['limit' => 5]),
'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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

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