Compare commits

...

57 Commits

Author SHA1 Message Date
wuhui_zzw 698f5166f4 修复:体积重 重量单位未转换导致运费计算错误
添加:多个物流时 按照物流费用排序
2023-10-12 14:35:31 +08:00
wuhui_zzw 71f1c85492 修复:运费计算错误
修复:管理后台添加商品时没有默认选中第一个计量单位
2023-10-11 09:54:01 +08:00
wuhui_zzw 61129e6971 修改:商品编辑页面、商品详情页面、首页商品样式布局调整优化 2023-10-10 17:59:33 +08:00
wuhui_zzw b35b056803 修改:商品详情页面价格布局优化 2023-10-08 14:33:24 +08:00
wuhui_zzw 2f608f6861 修改:商品详情页面物流点击无效的问题 2023-10-08 09:28:59 +08:00
wuhui_zzw eede9e4e25 优化:
1.产品编辑页数量文本更新为库存,前台文本把Quantity:改为Stock
2.交易信息在下,数据(变更文本为:产品信息) 在上,产品类型下放到交易信息第一排
3.产品类型调用到产品详情页第一行固定栏:产品类型:直接下单产品/非直接下单产品   英文:Product Type: Can Place Orders Wholesale/ Customization
4.产品编辑页面贸易术语必填(图片放大),产品详情页 贸易术语也是问号弹出图片,产品详情页
5.产品详情页Sales method:和Min Order删除。仅仅在按批售卖时才显示Min Order; 把一批多少(单位)和最少买多少(单位)集合到最小起订量 弹出在价格下方。格式如下:
Min Order:X批(1 batch=AA单位 | CC 单位 in total)
6.直接下单产品交易信息所有内容都是必填
2023-10-07 13:45:49 +08:00
wuhui_zzw 84d6777d0f 修复:商品组件显示起订量错误,根据数量设置价格 时起订量一直显示0 2023-09-22 17:08:16 +08:00
wuhui_zzw dcdfd1facc 优化:商品详情页面部分内容调整 2023-09-22 16:36:38 +08:00
wuhui_zzw 5663f321dc 优化:商品编辑页面布局调整,添加贸易术语设置及相关内容 2023-09-22 15:04:12 +08:00
wuhui_zzw 9ad042da88 优化:商品编辑页面布局调整,计量单位由手动填写且支持中英文切换修改为下拉框选择且不支持中英文切换 2023-09-22 10:39:48 +08:00
wuhui_zzw 650184d860 优化:设计 - 导航栏优化。非直接下单商品也可以选中,取消固定连接。商品选择表格内容优化 2023-09-22 09:48:12 +08:00
wuhui_zzw 6af1a100e0 优化:根据计抛比计算体积重。在体积重和重量重中取最大的值计算运费 2023-09-21 18:32:24 +08:00
wuhui_zzw bfc31e06c4 优化:商品详情加入购物车、立即购买、购物车页面下单 - 添加批量售卖商品下单及加入购物车处理。不符合条件禁止下单或者加入购物车 2023-09-21 16:46:55 +08:00
wuhui_zzw 525a6c1740 优化:商品详情购买处理按批购买 2023-09-21 13:38:08 +08:00
wuhui_zzw 3c62e8af7d 优化:商品编辑增加按件卖(默认)/按批卖设置 2023-09-20 16:37:49 +08:00
wuhui_zzw 19df41c8d4 优化:增加计量单位并且在商品组件和商品详情中显示。支持多语言 2023-09-20 14:49:44 +08:00
wuhui_zzw 2e36e35e17 优化:客服端 - 商品详情页面显示当前商品的最小起订量、并且如果购买数量低于最小起购量时禁止下单和添加购物车 2023-09-20 14:05:36 +08:00
wuhui_zzw b679d66adb 版本合并、和客户修改后的版本合并
修改:商品编辑页面取消sku;起订量从规格中移除,统一设置起订量
修改:客服端显示最小起订量
2023-09-20 13:36:57 +08:00
wuhui_zzw 39cd329a47 优化:商品详情页面运费显示样式优化 2023-08-31 14:38:15 +08:00
wuhui_zzw 35114816fe 优化:下单页面 - 价格信息样式优化 - 价格及数量上下一致 2023-08-31 11:40:41 +08:00
wuhui_zzw cf7825d519 优化:文本内容修改 2023-08-31 11:34:22 +08:00
wuhui_zzw 82af4074de 优化:商品编辑 - 总量单位由默认的千克改为克 2023-08-31 11:17:59 +08:00
wuhui_zzw 64fea5c8b3 优化:物流编辑 - 续重范围取消添加按钮,仅使用一个。续重范围使用最小值同步首重禁止修改。物流列表单位调整和物流实际物流一致。 2023-08-31 11:16:11 +08:00
wuhui_zzw 5a83342538 优化:国家选择器 - 添加区域分组筛选 2023-08-31 09:53:08 +08:00
wuhui_zzw df8cff8869 添加:商品详情 - 物流切换列表中不显示不符合条件的物流
优化:商品详情 - 默认显示运费最低的物流。
2023-08-29 10:36:27 +08:00
wuhui_zzw caf65fa1d8 修复:非按重量计算物流保存报错 2023-08-28 17:49:43 +08:00
wuhui_zzw e6882598fe 添加:物流添加续重范围填写项 2023-08-28 17:45:59 +08:00
wuhui_zzw 8e34333803 优化:注释后台的版权与服务,插件市场 2023-08-28 16:12:18 +08:00
wuhui_zzw 5e978f268c 添加:物流管理 - 国家选择器添加全选和取消全选(仅多选模式有效) 2023-08-28 16:01:11 +08:00
wuhui_zzw f55c520015 添加:物流管理 - 商品详情页面支持切换国家和物流;并且同步刷新对应的运费 2023-08-28 15:15:44 +08:00
wuhui_zzw 3c96766442 添加:物流管理 - 添加前台默认展示国家 2023-08-25 14:16:09 +08:00
wuhui_zzw 4032cfaaa1 优化:国家支持图标设置 2023-08-25 11:08:31 +08:00
wuhui_zzw 1dd22c3e02 文字替换:Add buy sku 修改为Quantity;Product Total 修改为 Quantity total 2023-08-25 09:48:53 +08:00
wuhui_zzw a318806058 优化:订单列表显示预计到达时间 2023-08-22 19:08:04 +08:00
wuhui_zzw a4bbe1c4d8 修复:运费计算错误、未登录和登录后的运费不一致 2023-08-22 18:22:10 +08:00
wuhui_zzw ab24642207 修复:用户登录后 无物流信息 2023-08-22 17:53:02 +08:00
wuhui_zzw 399bff4def 修复:商品详情 - 购买数量可以输入小数
修复:商品购买数量为0时,依然计算了运费
添加:添加商品总数量统计
2023-08-22 17:43:16 +08:00
wuhui_zzw 270623a9ad 优化:物流信息添加默认单位 2023-08-22 16:19:58 +08:00
wuhui_zzw 18546a254c 添加:物流运费计算方式 2023-08-22 16:00:34 +08:00
wuhui_zzw f1f2a7d1bf 修改:物流列表显示内容优化 2023-08-21 18:05:03 +08:00
wuhui_zzw afa9b0be6f 修改:物流支持多国家选择 2023-08-21 16:59:20 +08:00
wuhui_zzw 3a7aacb474 优化:首页装修页面视频播放器优化 - 显示相关控件 2023-08-18 17:57:03 +08:00
wuhui_zzw 7f31190de9 优化:订单列表没有显示物流信息 2023-08-18 17:07:43 +08:00
wuhui_zzw 64d4c6c860 修复:购物车没有商品时 在商品详情加减购买数量时会一直报购物车为空的错误 2023-08-18 16:32:59 +08:00
wuhui_zzw 8ab026f52e 优化:非直接购买商品禁止下单(但是可以加入购物车和收藏) 2023-08-18 15:41:22 +08:00
wuhui_zzw b7a2839ee4 优化:购物车页面订单金额统计优化 和支付页面一致 2023-08-18 14:01:04 +08:00
wuhui_zzw be017fdb7e 优化:商品详情页面订单金额统计优化 和支付页面一致 2023-08-18 11:19:22 +08:00
wuhui_zzw 8d7c37509f 优化:商品详情页面下单优化,支持多规格下单 2023-08-17 15:47:05 +08:00
wuhui_zzw d1c5514682 update cache 2023-08-16 14:04:20 +08:00
wuhui_zzw 67c3c7ab61 文件补齐 2023-08-16 14:01:36 +08:00
wuhui_zzw 39c5134312 update cache 2023-08-16 14:00:10 +08:00
wuhui_zzw 70c2160aca 文件补齐 2023-08-16 13:40:41 +08:00
liqianjin 02516b7825 【增加】物流及可视化 2023-08-12 18:46:10 +08:00
liqianjin bfb1ea283c 【增加】物流管理 2023-08-11 18:41:28 +08:00
liqianjin fa79136adc 【增加】产品状态名称修改 2023-08-11 15:16:23 +08:00
liqianjin 1713d0712c 【增加】视频播放装修
【增加】复制商品
【增加】商品长宽高参数
【增加】合并到第三方主版本分支,更新到最新版本
2023-08-11 13:56:05 +08:00
liqianjin 4f1beb97d9 【增加】视频播放装修
【增加】复制商品
【增加】商品长宽高参数
【增加】合并到第三方主版本分支,更新到最新版本
2023-08-11 13:55:00 +08:00
216 changed files with 56893 additions and 8668 deletions

28
.env
View File

@ -1,28 +0,0 @@
APP_NAME='wyyl'
APP_ENV=
APP_KEY=base64:PItUypiY6FmV8oQTVIcIHyNQJAuuS36FmEs8exQbYAw=
APP_DEBUG=false
APP_LOG_LEVEL=
APP_URL=http://43.153.17.83
BEIKE_API_URL=https://beikeshop.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=wyyl
DB_USERNAME=wyyl
DB_PASSWORD='wyyl@2023'
BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_CONNECTION=sync
MAIL_DRIVER=
MAIL_HOST=
MAIL_PORT=
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=

2
.gitignore vendored
View File

@ -4,7 +4,7 @@
/public/hot /public/hot
/public/storage /public/storage
/public/upload/avatar /public/upload/avatar
/public/build/beike/* #/public/build/beike/*
/public/install/css/* /public/install/css/*
/public/sitemap.xml /public/sitemap.xml
/storage/*.key /storage/*.key

119
LICENSE
View File

@ -52,121 +52,4 @@ Licensor's trademarks, copyrights, patents, trade secrets or any other
intellectual property. No patent license is granted to make, use, sell, offer intellectual property. No patent license is granted to make, use, sell, offer
for sale, have made, or import embodiments of any patent claims other than the for sale, have made, or import embodiments of any patent claims other than the
licensed claims defined in Section 2. No license is granted to the trademarks licensed claims defined in Section 2. No license is granted to the trademarks
of Licensor even if such marks are included in the Original Work. Nothing in of Licensor even if suc
this License shall be interpreted to prohibit Licensor from licensing under
terms different from this License any Original Work that Licensor otherwise
would have a right to license.
5) External Deployment. The term "External Deployment" means the use,
distribution, or communication of the Original Work or Derivative Works in any
way such that the Original Work or Derivative Works may be used by anyone
other than You, whether those works are distributed or communicated to those
persons or made available as an application intended for use over a network.
As an express condition for the grants of license hereunder, You must treat
any External Deployment by You of the Original Work or a Derivative Work as a
distribution under section 1(c).
6) Attribution Rights. You must retain, in the Source Code of any Derivative
Works that You create, all copyright, patent, or trademark notices from the
Source Code of the Original Work, as well as any notices of licensing and any
descriptive text identified therein as an "Attribution Notice." You must cause
the Source Code for any Derivative Works that You create to carry a prominent
Attribution Notice reasonably calculated to inform recipients that You have
modified the Original Work.
7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that
the copyright in and to the Original Work and the patent rights granted herein
by Licensor are owned by the Licensor or are sublicensed to You under the
terms of this License with the permission of the contributor(s) of those
copyrights and patent rights. Except as expressly stated in the immediately
preceding sentence, the Original Work is provided under this License on an "AS
IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without
limitation, the warranties of non-infringement, merchantability or fitness for
a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK
IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this
License. No license to the Original Work is granted by this License except
under this disclaimer.
8) Limitation of Liability. Under no circumstances and under no legal theory,
whether in tort (including negligence), contract, or otherwise, shall the
Licensor be liable to anyone for any indirect, special, incidental, or
consequential damages of any character arising as a result of this License or
the use of the Original Work including, without limitation, damages for loss
of goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses. This limitation of liability shall not
apply to the extent applicable law prohibits such limitation.
9) Acceptance and Termination. If, at any time, You expressly assented to this
License, that assent indicates your clear and irrevocable acceptance of this
License and all of its terms and conditions. If You distribute or communicate
copies of the Original Work or a Derivative Work, You must make a reasonable
effort under the circumstances to obtain the express assent of recipients to
the terms of this License. This License conditions your rights to undertake
the activities listed in Section 1, including your right to create Derivative
Works based upon the Original Work, and doing so without honoring these terms
and conditions is prohibited by copyright law and international treaty.
Nothing in this License is intended to affect copyright exceptions and
limitations (including "fair use" or "fair dealing"). This License shall
terminate immediately and You may no longer exercise any of the rights granted
to You by this License upon your failure to honor the conditions in Section
1(c).
10) Termination for Patent Action. This License shall terminate automatically
and You may no longer exercise any of the rights granted to You by this
License as of the date You commence an action, including a cross-claim or
counterclaim, against Licensor or any licensee alleging that the Original Work
infringes a patent. This termination provision shall not apply for an action
alleging patent infringement by combinations of the Original Work with other
software or hardware.
11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this
License may be brought only in the courts of a jurisdiction wherein the
Licensor resides or in which Licensor conducts its primary business, and under
the laws of that jurisdiction excluding its conflict-of-law provisions. The
application of the United Nations Convention on Contracts for the
International Sale of Goods is expressly excluded. Any use of the Original
Work outside the scope of this License or after its termination shall be
subject to the requirements and penalties of copyright or patent law in the
appropriate jurisdiction. This section shall survive the termination of this
License.
12) Attorneys' Fees. In any action to enforce the terms of this License or
seeking damages relating thereto, the prevailing party shall be entitled to
recover its costs and expenses, including, without limitation, reasonable
attorneys' fees and costs incurred in connection with such action, including
any appeal of such action. This section shall survive the termination of this
License.
13) Miscellaneous. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent necessary
to make it enforceable.
14) Definition of "You" in This License. "You" throughout this License,
whether in upper or lower case, means an individual or a legal entity
exercising rights under, and complying with all of the terms of, this License.
For legal entities, "You" includes any entity that controls, is controlled by,
or is under common control with you. For purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the direction or
management of such entity, whether by contract or otherwise, or (ii) ownership
of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
ownership of such entity.
15) Right to Use. You may use the Original Work in all ways not otherwise
restricted or conditioned by this License or by law, and Licensor promises not
to interfere with or be responsible for such uses by You.
16) Modification of This License. This License is Copyright © 2005 Lawrence
Rosen. Permission is granted to copy, distribute, or communicate this License
without modification. Nothing in this License permits You to modify this
License as applied to the Original Work or to Derivative Works. However, You
may modify the text of this License and copy, distribute or communicate your
modified version (the "Modified License") and apply it to other original works
of authorship subject to the following conditions: (i) You may not indicate in
any way that your Modified License is the "Open Software License" or "OSL" and
you may not use those names in the name of your Modified License; (ii) You
must replace the notice specified in the first paragraph above with the notice
"Licensed under <insert your license name here>" or with a notice of your own
that is not confusingly similar to the notice in this License; and (iii) You
may not claim that your original works are open source software unless your
Modified License has been approved by Open Source Initiative (OSI) and You
comply with its license review and certification process.

0
artisan Executable file → Normal file
View File

View File

@ -33,7 +33,7 @@ class CountryController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
$country = CountryRepo::create($request->only('name', 'code', 'sort_order', 'status')); $country = CountryRepo::create($request->only('name', 'icon', 'code', 'sort_order', 'status'));
hook_action('admin.country.store.after', $country); hook_action('admin.country.store.after', $country);
@ -42,7 +42,7 @@ class CountryController extends Controller
public function update(Request $request, int $id) public function update(Request $request, int $id)
{ {
$country = CountryRepo::update($id, $request->only('name', 'code', 'sort_order', 'status')); $country = CountryRepo::update($id, $request->only('name', 'icon', 'code', 'sort_order', 'status'));
hook_action('admin.country.store.after', $country); hook_action('admin.country.store.after', $country);
@ -57,4 +57,16 @@ class CountryController extends Controller
return json_success(trans('common.deleted_success')); return json_success(trans('common.deleted_success'));
} }
/**
* @param Request $request
* @return array
*/
public function autocomplete(Request $request): array
{
$brands = CountryRepo::autocomplete($request->get('name') ?? '', 0);
return json_success(trans('common.get_success'), $brands);
}
} }

View File

@ -20,7 +20,7 @@ class DesignController extends Controller
$data = [ $data = [
'editors' => [ 'editors' => [
'editor-slide_show', 'editor-image401', 'editor-tab_product', 'editor-product', 'editor-image100', 'editor-slide_show', 'editor-image401', 'editor-tab_product', 'editor-product', 'editor-image100',
'editor-brand', 'editor-icons', 'editor-rich_text', 'editor-image200', 'editor-image300', 'editor-brand', 'editor-icons', 'editor-rich_text', 'editor-image200', 'editor-image300','editor-slide_show_video',
], ],
'design_settings' => system_setting('base.design_setting'), 'design_settings' => system_setting('base.design_setting'),
]; ];

View File

@ -0,0 +1,255 @@
<?php
namespace Beike\Admin\Http\Controllers;
use Beike\Admin\Http\Requests\LogisticsRequest;
use Beike\Admin\Http\Resources\LogisticsAttributeResource;
use Beike\Admin\Http\Resources\LogisticsResource;
use Beike\Admin\Repositories\TaxClassRepo;
use Beike\Admin\Services\LogisticsService;
use Beike\Admin\View\DesignBuilders\Product;
use Beike\Libraries\Weight;
use Beike\Models\Country;
use Beike\Models\Logistics;
use Beike\Models\LogisticsWeight;
use Beike\Repositories\CategoryRepo;
use Beike\Repositories\CountryRepo;
use Beike\Repositories\LanguageRepo;
use Beike\Repositories\LogisticsRepo;
use Beike\Repositories\SettingRepo;
use Illuminate\Http\Request;
class LogisticsController extends Controller
{
protected string $defaultRoute = 'logistics.index';
public function index(Request $request)
{
$requestData = $request->all();
if (! isset($requestData['sort'])) {
$requestData['sort'] = 'logistics.updated_at';
}
$logisticsList = LogisticsRepo::list($requestData);
$logistics = LogisticsResource::collection($logisticsList);
$logisticsFormat = $logistics->jsonSerialize();
$countries = LogisticsRepo::getDefaultCountries();
$data = [
'logistics_format' => $logisticsFormat,
'logistics' => $logistics,
'type' => 'logistics',
'default_countries' => $countries,
'default_countries_name' => is_array($countries) && count($countries) > 0 ? array_values($countries)[0]['name'] : ''
];
$data = hook_filter('admin.logistics.index.data', $data);
if ($request->expectsJson()) {
return $logisticsFormat;
}
return view('admin::pages.logistics.index', $data);
}
public function trashed(Request $request)
{
$requestData = $request->all();
$requestData['trashed'] = true;
$productList = LogisticsRepo::list($requestData);
$products = LogisticsResource::collection($productList);
$productsFormat = $products->jsonSerialize();
$data = [
'categories' => CategoryRepo::flatten(locale()),
'products_format' => $productsFormat,
'products' => $products,
'type' => 'trashed',
];
$data = hook_filter('admin.product.trashed.data', $data);
if ($request->expectsJson()) {
return $products;
}
return view('admin::pages.products.index', $data);
}
public function create(Request $request)
{
return $this->form($request, new Logistics());
}
public function store(LogisticsRequest $request)
{
try {
$requestData = $request->all();
$logistics = (new LogisticsService)->create($requestData);
$data = [
'request_data' => $requestData,
'logistics' => $logistics,
];
hook_action('admin.logistics.store.after', $data);
return redirect()->to(admin_route('logistics.index'))
->with('success', trans('common.created_success'));
} catch (\Exception $e) {
return redirect(admin_route('logistics.create'))
->withInput()
->withErrors(['error' => $e->getMessage()]);
}
}
public function edit(Request $request, Logistics $logistics)
{
return $this->form($request, $logistics);
}
public function update(LogisticsRequest $request, Logistics $logistics)
{
try {
$requestData = $request->all();
$logistics = (new LogisticsService)->update($logistics, $requestData);
$data = [
'request_data' => $requestData,
'logistics' => $logistics,
];
hook_action('admin.logistics.update.after', $data);
return redirect()->to($this->getRedirect())->with('success', trans('common.updated_success'));
} catch (\Exception $e) {
return redirect(admin_route('logistics.index'))->withErrors(['error' => $e->getMessage()]);
}
}
public function copy(LogisticsRequest $request, Logistics $product)
{
try {
$product = (new LogisticsService)->copy($product);
$data = [
'product' => $product,
];
hook_action('admin.product.copy.after', $data);
return json_success(trans('common.copy_success'), []);
} catch (\Exception $e) {
return json_encode($e->getMessage());
}
}
public function destroy(Request $request, Logistics $logistics)
{
$logistics->delete();
hook_action('admin.logistics.destroy.after', $logistics);
return json_success(trans('common.deleted_success'));
}
public function restore(Request $request)
{
$productId = $request->id ?? 0;
Logistics::withTrashed()->find($productId)->restore();
hook_action('admin.product.restore.after', $productId);
return ['success' => true];
}
protected function form(Request $request, Logistics $logistics)
{
$logistics = hook_filter('admin.logistics.form.logistics', $logistics);
$types = [
['title' => 'weight','name'=>trans('admin/logistics.type_weight')],
['title' => 'num','name'=>trans('admin/logistics.type_num')],
['title' => 'free','name'=>trans('admin/logistics.type_free')],
];
// 获取
$countryIds = (array)explode(',',$logistics->country_ids);
$logistics->country_ids = CountryRepo::getInList($countryIds);
$logistics->logistics_weights = LogisticsWeight::getList($logistics->id);
$data = [
'logistics' => $logistics,
'types' => $types,
'_redirect' => $this->getRedirect(),
];
$data = hook_filter('admin.logistics.form.data', $data);
return view('admin::pages.logistics.form.form', $data);
}
public function name(int $id)
{
$name = LogisticsRepo::getName($id);
return json_success(trans('common.get_success'), $name);
}
/**
* 根据商品ID批量获取商品名称
*
* @param Request $request
* @return array
*/
public function getNames(Request $request): array
{
$productIds = explode(',', $request->get('product_ids'));
$name = LogisticsRepo::getNames($productIds);
return json_success(trans('common.get_success'), $name);
}
public function autocomplete(Request $request)
{
$products = LogisticsRepo::autocomplete($request->get('name') ?? '');
return json_success(trans('common.get_success'), $products);
}
public function updateStatus(Request $request)
{
LogisticsRepo::updateStatusByIds($request->get('ids'), $request->get('status'));
return json_success(trans('common.updated_success'), []);
}
public function destroyByIds(Request $request)
{
$productIds = $request->get('ids');
LogisticsRepo::DeleteByIds($productIds);
hook_action('admin.product.destroy_by_ids.after', $productIds);
return json_success(trans('common.deleted_success'), []);
}
public function trashedClear()
{
LogisticsRepo::forceDeleteTrashed();
}
/**
* Common: 设置默认物流国家
* Author: wu-hui
* Time: 2023/08/25 13:48
* @return array
* @throws \Throwable
*/
public function defaultCountries(){
$country = request()->post('country');
SettingRepo::storeValue('default_country', $country,'logistics','plugin');
return json_success(trans('common.updated_success'), []);
}
}

View File

@ -118,6 +118,22 @@ class ProductController extends Controller
} }
} }
public function copy(ProductRequest $request, Product $product)
{
try {
$product = (new ProductService)->copy($product);
$data = [
'product' => $product,
];
hook_action('admin.product.copy.after', $data);
return json_success(trans('common.copy_success'), []);
} catch (\Exception $e) {
return json_encode($e->getMessage());
}
}
public function destroy(Request $request, Product $product) public function destroy(Request $request, Product $product)
{ {
$product->delete(); $product->delete();
@ -147,20 +163,21 @@ class ProductController extends Controller
$product = hook_filter('admin.product.form.product', $product); $product = hook_filter('admin.product.form.product', $product);
$taxClasses = TaxClassRepo::getList(); $taxClasses = TaxClassRepo::getList();
array_unshift($taxClasses, ['title' => trans('admin/builder.text_no'), 'id' => 0]); array_unshift($taxClasses, ['title' => trans('admin/builder.text_no'), 'id' => 0]);
$data = [ $data = [
'product' => $product, 'product' => $product,
'descriptions' => $descriptions ?? [], 'descriptions' => $descriptions ?? [],
'category_ids' => $categoryIds ?? [], 'category_ids' => $categoryIds ?? [],
'product_attributes' => ProductAttributeResource::collection($product->attributes), 'product_attributes' => ProductAttributeResource::collection($product->attributes),
'relations' => ProductResource::collection($product->relations)->resource, 'relations' => ProductResource::collection($product->relations)->resource,
'languages' => LanguageRepo::all(), 'languages' => LanguageRepo::all(),
'tax_classes' => $taxClasses, 'tax_classes' => $taxClasses,
'weight_classes' => Weight::getWeightUnits(), 'weight_classes' => Weight::getWeightUnits(),
'source' => [ 'source' => [
'categories' => CategoryRepo::flatten(locale(), false), 'categories' => CategoryRepo::flatten(locale(),FALSE),
], ],
'_redirect' => $this->getRedirect(), '_redirect' => $this->getRedirect(),
'unit_list' => Product::getUnitList(),
'trade_term' => Product::getTradeTermList(),
]; ];
$data = hook_filter('admin.product.form.data', $data); $data = hook_filter('admin.product.form.data', $data);

View File

@ -12,6 +12,7 @@
namespace Beike\Admin\Http\Controllers; namespace Beike\Admin\Http\Controllers;
use Beike\Admin\Repositories\RegionRepo; use Beike\Admin\Repositories\RegionRepo;
use Beike\Models\Region;
use Beike\Repositories\CountryRepo; use Beike\Repositories\CountryRepo;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -52,4 +53,19 @@ class RegionController
return json_success(trans('common.deleted_success')); return json_success(trans('common.deleted_success'));
} }
/**
* Common: 获取全部的区域分组
* Author: wu-hui
* Time: 2023/08/31 9:25
* @return array
*/
public function regionsAll(){
$list = Region::query()->select(['id','name'])->get();
return json_success(trans('common.get_success'), $list);
}
} }

View File

@ -0,0 +1,70 @@
<?php
/**
* PageRequest.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-19 21:58:20
* @modified 2022-08-19 21:58:20
*/
namespace Beike\Admin\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class LogisticsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'name' => 'string',
'warehouse_name' => 'string',
'country_id' => 'int',
'type' => 'string',
'first_weight' => 'numeric',
'first_weight_fee' => 'numeric',
'continuation_weight_max' => 'numeric',
'add_weight' => 'numeric',
'continuation_weight_fee' => 'numeric',
'num_fee' => 'numeric',
'throwing_ratio' => 'int',
'day_min' => 'int',
'day_max' => 'int',
];
}
public function attributes()
{
return [
'name' => trans('logistics.name'),
'warehouse_name' => trans('logistics.warehouse_name'),
'country_id' => trans('logistics.country_id'),
'type' => trans('logistics.type'),
'first_weight' => trans('logistics.first_weight'),
'first_weight_fee' => trans('logistics.first_weight_fee'),
'continuation_weight_max' => trans('logistics.continuation_weight_max'),
'add_weight' => trans('logistics.add_weight'),
'continuation_weight_fee' => trans('logistics.continuation_weight_fee'),
'num_fee' => trans('logistics.num_fee'),
'throwing_ratio' => trans('logistics.throwing_ratio'),
'day_min' => trans('logistics.day_min'),
'day_max' => trans('logistics.day_max'),
];
}
}

View File

@ -32,14 +32,27 @@ class ProductRequest extends FormRequest
*/ */
public function rules(): array public function rules(): array
{ {
return [ $rules = [
'descriptions.*.name' => 'required|string|min:3|max:128', 'descriptions.*.name' => 'required|string|min:3|max:128',
'brand_id' => 'int', 'brand_id' => 'int',
'skus.*.sku' => 'required|string', // 'skus.*.sku' => 'required|string',
'skus.*.price' => 'required|numeric', 'skus.*.price' => 'required|numeric',
'skus.*.origin_price' => 'required|numeric', // 'skus.*.origin_price' => 'required|numeric',
'skus.*.cost_price' => 'numeric', // 'skus.*.cost_price' => 'numeric',
]; ];
// 判断:根据是否为 直接下单产品 进行对应的判断
if($this->active == 1){
// 直接下单产品 - 最小起订量必填
$rules['minimum_order'] = 'required|numeric|gt:0';
}else{
$rules['skus.*.origin_price'] = 'required|numeric';
}
// 判断:销售方式为按 批 卖每批的数量必须大于0
if($this->sales_method == 'batches'){
$rules['piece_to_batch'] = 'required|numeric|gt:0';
}
return $rules;
} }
public function attributes() public function attributes()
@ -47,10 +60,12 @@ class ProductRequest extends FormRequest
return [ return [
'descriptions.*.name' => trans('product.name'), 'descriptions.*.name' => trans('product.name'),
'brand_id' => trans('product.brand'), 'brand_id' => trans('product.brand'),
'skus.*.sku' => trans('product.sku'), // 'skus.*.sku' => trans('product.sku'),
'skus.*.price' => trans('product.price'), 'skus.*.price' => trans('product.price'),
'skus.*.origin_price' => trans('product.origin_price'), 'skus.*.origin_price' => trans('product.origin_price'),
'skus.*.cost_price' => trans('product.cost_price'), // 'skus.*.cost_price' => trans('product.cost_price'),
'minimum_order' => trans('product.minimum_order'),
'piece_to_batch' => trans('product.one_batch_is_equal_to'),
]; ];
} }
} }

View File

@ -0,0 +1,47 @@
<?php
namespace Beike\Admin\Http\Resources;
use Beike\Repositories\CountryRepo;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class LogisticsResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
* @return array
* @throws \Exception
*/
public function toArray($request): array{
$countryIds = (array)explode(',',$this->country_ids);
$countryList = CountryRepo::getInList($countryIds);
$countryNames = implode('',array_column($countryList,'name'));
$data = [
'id' => $this->id,
'name' => $this->name ?? '',
'warehouse_name' => $this->warehouse_name ?? '',
'country_id' => $this->country_id,
'country' => $countryNames,//$this->country ? $this->country->name : '',
'type' => $this->type,
'first_weight' => $this->first_weight,
'first_weight_fee' => $this->first_weight_fee,
'continuation_weight_max' => $this->continuation_weight_max,
'add_weight' => $this->add_weight,
'continuation_weight_fee' => $this->continuation_weight_fee,
'num_fee' => $this->num_fee,
'throwing_ratio' => $this->throwing_ratio,
'day_min' => $this->day_min,
'day_max' => $this->day_max,
'position' => $this->position,
'created_at' => time_format($this->created_at),
'deleted_at' => $this->deleted_at ? time_format($this->deleted_at) : '',
'url_edit' => admin_route('logistics.edit', $this->id),
];
return hook_filter('resource.product', $data);
}
}

View File

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

View File

@ -19,6 +19,7 @@ use Beike\Admin\View\Components\Form\InputLocale;
use Beike\Admin\View\Components\Form\RichText; use Beike\Admin\View\Components\Form\RichText;
use Beike\Admin\View\Components\Form\Select; use Beike\Admin\View\Components\Form\Select;
use Beike\Admin\View\Components\Form\SwitchRadio; use Beike\Admin\View\Components\Form\SwitchRadio;
use Beike\Admin\View\Components\Form\SwitchRadioStatus;
use Beike\Admin\View\Components\Form\Textarea; use Beike\Admin\View\Components\Form\Textarea;
use Beike\Admin\View\Components\Header; use Beike\Admin\View\Components\Header;
use Beike\Admin\View\Components\NoData; use Beike\Admin\View\Components\NoData;
@ -139,6 +140,7 @@ class AdminServiceProvider extends ServiceProvider
'alert' => Alert::class, 'alert' => Alert::class,
'form-input-locale' => InputLocale::class, 'form-input-locale' => InputLocale::class,
'form-switch' => SwitchRadio::class, 'form-switch' => SwitchRadio::class,
'form-switch-status' => SwitchRadioStatus::class,
'form-input' => Input::class, 'form-input' => Input::class,
'form-select' => Select::class, 'form-select' => Select::class,
'form-image' => Image::class, 'form-image' => Image::class,

View File

@ -71,6 +71,7 @@ Route::prefix($adminName)
Route::middleware('can:countries_create')->post('countries', [Controllers\CountryController::class, 'store'])->name('countries.store'); Route::middleware('can:countries_create')->post('countries', [Controllers\CountryController::class, 'store'])->name('countries.store');
Route::middleware('can:countries_update')->put('countries/{id}', [Controllers\CountryController::class, 'update'])->name('countries.update'); Route::middleware('can:countries_update')->put('countries/{id}', [Controllers\CountryController::class, 'update'])->name('countries.update');
Route::middleware('can:countries_delete')->delete('countries/{id}', [Controllers\CountryController::class, 'destroy'])->name('countries.destroy'); Route::middleware('can:countries_delete')->delete('countries/{id}', [Controllers\CountryController::class, 'destroy'])->name('countries.destroy');
Route::middleware('can:countries_index')->get('countries/autocomplete', [Controllers\CountryController::class, 'autocomplete'])->name('countries.autocomplete');
// 省份 // 省份
Route::middleware('can:zones_index')->get('zones', [Controllers\ZoneController::class, 'index'])->name('zones.index'); Route::middleware('can:zones_index')->get('zones', [Controllers\ZoneController::class, 'index'])->name('zones.index');
@ -214,15 +215,27 @@ Route::prefix($adminName)
Route::middleware('can:products_index')->get('products', [Controllers\ProductController::class, 'index'])->name('products.index'); Route::middleware('can:products_index')->get('products', [Controllers\ProductController::class, 'index'])->name('products.index');
Route::middleware('can:products_create')->get('products/create', [Controllers\ProductController::class, 'create'])->name('products.create'); Route::middleware('can:products_create')->get('products/create', [Controllers\ProductController::class, 'create'])->name('products.create');
Route::middleware('can:products_create')->post('products', [Controllers\ProductController::class, 'store'])->name('products.store'); Route::middleware('can:products_create')->post('products', [Controllers\ProductController::class, 'store'])->name('products.store');
Route::middleware('can:products_copy')->post('products/{product}/copy', [Controllers\ProductController::class, 'copy'])->name('products.copy');
Route::middleware('can:products_show')->get('products/{product}/edit', [Controllers\ProductController::class, 'edit'])->name('products.edit'); Route::middleware('can:products_show')->get('products/{product}/edit', [Controllers\ProductController::class, 'edit'])->name('products.edit');
Route::middleware('can:products_update')->put('products/{product}', [Controllers\ProductController::class, 'update'])->name('products.update'); Route::middleware('can:products_update')->put('products/{product}', [Controllers\ProductController::class, 'update'])->name('products.update');
Route::middleware('can:products_delete')->delete('products/{product}', [Controllers\ProductController::class, 'destroy'])->name('products.destroy'); Route::middleware('can:products_delete')->delete('products/{product}', [Controllers\ProductController::class, 'destroy'])->name('products.destroy');
// 物流
Route::middleware('can:logistics_index')->get('logistics', [Controllers\LogisticsController::class, 'index'])->name('logistics.index');
Route::middleware('can:logistics_create')->get('logistics/create', [Controllers\LogisticsController::class, 'create'])->name('logistics.create');
Route::middleware('can:logistics_create')->post('logistics', [Controllers\LogisticsController::class, 'store'])->name('logistics.store');
Route::middleware('can:logistics_show')->get('logistics/{logistics}/edit', [Controllers\LogisticsController::class, 'edit'])->name('logistics.edit');
Route::middleware('can:logistics_update')->put('logistics/{logistics}', [Controllers\LogisticsController::class, 'update'])->name('logistics.update');
Route::middleware('can:logistics_delete')->delete('logistics/{logistics}', [Controllers\LogisticsController::class, 'destroy'])->name('logistics.destroy');
Route::middleware('can:logistics_default_countries')->post('logistics/default_countries', [Controllers\LogisticsController::class, 'defaultCountries'])->name('logistics.default_countries');
// 区域组 // 区域组
Route::middleware('can:regions_index')->get('regions', [Controllers\RegionController::class, 'index'])->name('regions.index'); Route::middleware('can:regions_index')->get('regions', [Controllers\RegionController::class, 'index'])->name('regions.index');
Route::middleware('can:regions_create')->post('regions', [Controllers\RegionController::class, 'store'])->name('regions.store'); Route::middleware('can:regions_create')->post('regions', [Controllers\RegionController::class, 'store'])->name('regions.store');
Route::middleware('can:regions_update')->put('regions/{id}', [Controllers\RegionController::class, 'update'])->name('regions.update'); Route::middleware('can:regions_update')->put('regions/{id}', [Controllers\RegionController::class, 'update'])->name('regions.update');
Route::middleware('can:regions_delete')->delete('regions/{id}', [Controllers\RegionController::class, 'destroy'])->name('regions.destroy'); Route::middleware('can:regions_delete')->delete('regions/{id}', [Controllers\RegionController::class, 'destroy'])->name('regions.destroy');
Route::middleware('can:regions_all')->get('regions/get_all', [Controllers\RegionController::class, 'regionsAll'])->name('regions.regions_all');
// RMA // RMA
Route::middleware('can:rmas_update')->post('rmas/history/{id}', [Controllers\RmaController::class, 'addHistory'])->name('rmas.add_history'); Route::middleware('can:rmas_update')->post('rmas/history/{id}', [Controllers\RmaController::class, 'addHistory'])->name('rmas.add_history');

View File

@ -0,0 +1,99 @@
<?php
namespace Beike\Admin\Services;
use Beike\Models\Logistics;
use Beike\Models\LogisticsWeight;
use Beike\Models\Product;
use Illuminate\Support\Facades\DB;
class LogisticsService
{
public function create(array $data): Logistics
{
$logistics = new Logistics;
return $this->createOrUpdate($logistics, $data);
}
public function update(Logistics $logistics, array $data): Logistics
{
return $this->createOrUpdate($logistics, $data);
}
public function copy(Product $oldProduct): Product
{
$product = new Product;
$data = $oldProduct->toArray();
$data['id'] = 0;
$data['created_at'] = now();
$data['variables'] = json_encode($data['variables'],JSON_UNESCAPED_UNICODE);
$data['descriptions'] = $oldProduct->descriptions()->get()->toArray();
foreach ($data['descriptions'] as $locale => $description) {
$data['descriptions'][$description['locale']] = $description;
unset($data['descriptions'][$locale]);
}
$data['attributes'] = $oldProduct->attributes()->get()->toArray();
$data['skus'] = $oldProduct->skus()->get()->toArray();
$data['numPrices'] = $oldProduct->numPrices()->get()->toArray();
$data['categories'] = $oldProduct->categories()->get()->toArray();
$data['relations'] = $oldProduct->relations()->get()->toArray();
$data['categories'] = array_column($data['categories'],'id');
$data['relations'] = array_column($data['relations'],'id');
return $this->createOrUpdate($product, $data);
}
protected function createOrUpdate(Logistics $logistics, array $data): Logistics
{
$isUpdating = $logistics->id > 0;
try {
DB::beginTransaction();
$data['country_ids'] = (String) ($data['country_ids'] ? implode(',',$data['country_ids']) : '');
$data['country_id'] = (int) ($data['country_id'] ?? 0);
$data['throwing_ratio'] = (int) ($data['throwing_ratio'] ?? 0);
$data['day_min'] = (int) ($data['day_min'] ?? 0);
$data['day_max'] = (int) ($data['day_max'] ?? 0);
$data['first_weight'] = (float) ($data['first_weight'] ?? 0);
$data['first_weight_fee'] = (float) ($data['first_weight_fee'] ?? 0);
$data['continuation_weight_max'] = (float) ($data['continuation_weight_max'] ?? 0);
$data['add_weight'] = (float) ($data['add_weight'] ?? 0);
$data['continuation_weight_fee'] = (float) ($data['continuation_weight_fee'] ?? 0);
$data['num_fee'] = (float) ($data['num_fee'] ?? 0);
$data['variables'] = json_decode($data['variables'] ?? '[]');
$logistics->fill($data);
$logistics->updated_at = now();
$logistics->save();
if ($isUpdating) {}
// 续重范围处理
$continuationWeightList = (array)($data['continuation_weight_list'] ?? []);
if(count($continuationWeightList) > 0){
$logisticsId = $logistics->id;
$newWeightList = array_filter(array_map(function($item) use ($logisticsId){
if($item['max'] > 0){
$item['logistics_id'] = $logisticsId;
return $item;
}
return [];
},$continuationWeightList));
// 删除旧数据
LogisticsWeight::query()->where('logistics_id',$logisticsId)->delete();
// 添加新数据
LogisticsWeight::query()->insert($newWeightList);
}
DB::commit();
return $logistics;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
}

View File

@ -19,6 +19,28 @@ class ProductService
return $this->createOrUpdate($product, $data); return $this->createOrUpdate($product, $data);
} }
public function copy(Product $oldProduct): Product
{
$product = new Product;
$data = $oldProduct->toArray();
$data['id'] = 0;
$data['created_at'] = now();
$data['variables'] = json_encode($data['variables'],JSON_UNESCAPED_UNICODE);
$data['descriptions'] = $oldProduct->descriptions()->get()->toArray();
foreach ($data['descriptions'] as $locale => $description) {
$data['descriptions'][$description['locale']] = $description;
unset($data['descriptions'][$locale]);
}
$data['attributes'] = $oldProduct->attributes()->get()->toArray();
$data['skus'] = $oldProduct->skus()->get()->toArray();
$data['numPrices'] = $oldProduct->numPrices()->get()->toArray();
$data['categories'] = $oldProduct->categories()->get()->toArray();
$data['relations'] = $oldProduct->relations()->get()->toArray();
$data['categories'] = array_column($data['categories'],'id');
$data['relations'] = array_column($data['relations'],'id');
return $this->createOrUpdate($product, $data);
}
protected function createOrUpdate(Product $product, array $data): Product protected function createOrUpdate(Product $product, array $data): Product
{ {
$isUpdating = $product->id > 0; $isUpdating = $product->id > 0;
@ -55,8 +77,8 @@ class ProductService
$skus = []; $skus = [];
foreach ($data['skus'] as $index => $sku) { foreach ($data['skus'] as $index => $sku) {
$sku['position'] = $index; $sku['position'] = $index;
$sku['origin_price'] = (float) $sku['origin_price']; $sku['origin_price'] = (float) ($sku['origin_price'] ?? 0);
$sku['cost_price'] = (float) $sku['cost_price']; $sku['cost_price'] = (float) ($sku['cost_price'] ?? 0);
$sku['quantity'] = (int) $sku['quantity']; $sku['quantity'] = (int) $sku['quantity'];
$skus[] = $sku; $skus[] = $sku;
} }

View File

@ -0,0 +1,26 @@
<?php
namespace Beike\Admin\View\Components\Form;
use Illuminate\View\Component;
class SwitchRadioStatus extends Component
{
public string $name;
public string $value;
public string $title;
public function __construct(string $name, string $value, string $title)
{
$this->name = $name;
$this->title = $title;
$this->value = $value;
}
public function render()
{
return view('admin::components.form.switch-radio-status');
}
}

View File

@ -124,6 +124,13 @@ class Sidebar extends Component
'prefixes' => $this->getInquirySubPrefix(), 'prefixes' => $this->getInquirySubPrefix(),
'children' => $this->getInquirySubRoutes(), 'children' => $this->getInquirySubRoutes(),
], ],
[
'route' => 'logistics.index',
'title' => trans('admin/common.logistics'),
'icon' => 'bi bi-gear',
'prefixes' => $this->getInquirySubPrefix(),
'children' => $this->getInquirySubRoutes(),
],
]; ];
return hook_filter('admin.components.sidebar.menus', $menus); return hook_filter('admin.components.sidebar.menus', $menus);

View File

@ -0,0 +1,45 @@
<?php
/**
* Render.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-07-08 17:09:15
* @modified 2022-07-08 17:09:15
*/
namespace Beike\Admin\View\DesignBuilders;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class SlideShowVideo extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
}
/**
* Get the view / contents that represent the component.
*
* @return View
*/
public function render(): View
{
$data['register'] = [
'code' => 'slideshow_video',
'sort' => 0,
'name' => trans('admin/design_builder.module_slideshow_video'),
'icon' => '&#xe61b;',
'style' => 'font-size: 40px;',
];
return view('admin::pages.design.module.slideshow_video', $data);
}
}

View File

@ -14,8 +14,8 @@ namespace Beike\Libraries;
class Weight class Weight
{ {
public const WEIGHT_CLASS = [ public const WEIGHT_CLASS = [
'kg' => 0.001,
'g' => 1, 'g' => 1,
'kg' => 0.001,
'oz' => 0.035, 'oz' => 0.035,
'lb' => 0.0022046, 'lb' => 0.0022046,
]; ];

View File

@ -18,7 +18,7 @@ class Country extends Base
{ {
use HasFactory; use HasFactory;
protected $fillable = ['name', 'country_id', 'code', 'sort_order', 'status']; protected $fillable = ['name','icon', 'country_id', 'code', 'sort_order', 'status'];
public function zones(): HasMany public function zones(): HasMany
{ {

106
beike/Models/Logistics.php Normal file
View File

@ -0,0 +1,106 @@
<?php
namespace Beike\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
class Logistics extends Base{
use HasFactory;
use SoftDeletes;
protected $fillable = [
'name',
'warehouse_name',
'country_id',
'country_ids',
'type',
'first_weight',
'first_weight_fee',
'continuation_weight_max',
'add_weight',
'continuation_weight_fee',
'throwing_ratio',
'num_fee',
'day_min',
'day_max',
'position'
];
/**
* Common: 根据国家ID 获取所有关联的物流列表
* Author: wu-hui
* Time: 2023/08/22 10:31
* @param int $countryId
* @param string[] $field
* @return array
*/
public static function getAll(int $countryId,$field = ['id','name','warehouse_name','type','day_min','day_max']){
$list = self::select($field)
->whereRaw(\DB::raw('FIND_IN_SET('.$countryId.',country_ids)'))
->with(['weights'])
->orderBy('position','ASC')
->orderBy('id','ASC')
->get();
return $list ? $list->toArray() : [];
}
/**
* Common: 物流过滤 - 根据商品和物流列表过滤 仅返回有效的物流
* Author: wu-hui
* Time: 2023/08/29 10:02
* @param $logisticsList
* @param $productsList
* @return array|array[]
*/
public static function LogisticsFiltering($logisticsList,$productsList){
$eligibleList = array_map(function($logisticsItem) use ($productsList){
// 判断:物流是否按照重量计算 weight按重量计费num按数量计费free卖家包邮
if($logisticsItem['type'] == 'weight'){
$weights = (array)$logisticsItem['weights'];// 续重区间列表
$firstWeight = (float)$logisticsItem['first_weight'];// 首重
if(!$weights) {
$logisticsItem = [];
}else{
// 循环判断:只要有一个商品不符合条件 则当前物流不可用;
foreach($productsList as $productItem){
// 计算体积重 并且取数值大的作为重量计算
$volumeWeight = $productItem['volume_weight'] / $logisticsItem['throwing_ratio'];
$sumWeight = $productItem['sum_weight'] > $volumeWeight ? $productItem['sum_weight'] : $volumeWeight;
// 总重量 - 减首重 = 剩余重量
$surplusWeight = $sumWeight - $firstWeight;
// 判断如果剩余重量小于等于0 则符合条件;否则计算续重区间
if($surplusWeight <= 0){
continue;
}else{
$eligibleCount = collect($weights)
->where('min','<=',$sumWeight)//$surplusWeight
->where('max','>',$sumWeight)//$surplusWeight
->count();
if($eligibleCount <= 0) {
$logisticsItem = [];
break;
}
}
}
}
}
return $logisticsItem;
},$logisticsList);
return array_filter($eligibleList);
}
public function country(){
return $this->belongsTo(Country::class,'country_id','id');
}
public function weights(){
return $this->hasMany(LogisticsWeight::class,'logistics_id','id');
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Beike\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
class LogisticsWeight extends Base{
public $timestamps = false;
protected $fillable = [
'logistics_id',
'min',
'max',
];
/**
* Common: 根据物流ID获取全部续重范围
* Author: wu-hui
* Time: 2023/08/28 17:36
* @param $logisticsId
* @return array|mixed[]
*/
public static function getList($logisticsId){
$list = self::query()->where('logistics_id',$logisticsId)->orderBy('id','asc')->get();
return $list ? $list->toArray() : [];
}
}

View File

@ -16,6 +16,7 @@ use Beike\Notifications\UpdateOrderNotification;
use Beike\Services\StateMachineService; use Beike\Services\StateMachineService;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
class Order extends Base class Order extends Base
@ -64,6 +65,10 @@ class Order extends Base
return $this->hasMany(OrderPayment::class); return $this->hasMany(OrderPayment::class);
} }
public function logistics(): HasOne{
return $this->hasOne(Logistics::class,'id','shipping_method_code');
}
public function subTotal() public function subTotal()
{ {
$totals = $this->orderTotals; $totals = $this->orderTotals;

View File

@ -11,7 +11,26 @@ class Product extends Base
use HasFactory; use HasFactory;
use SoftDeletes; use SoftDeletes;
protected $fillable = ['images', 'video', 'position', 'brand_id', 'tax_class_id', 'weight', 'weight_class', 'active', 'variables', 'price_setting']; protected $fillable = [
'images',
'video',
'position',
'brand_id',
'tax_class_id',
'weight',
'weight_class',
'active',
'variables',
'price_setting',
'length',
'width',
'height',
'minimum_order',
'sales_method',
'piece_to_batch',
'trade_term',
'unit'
];
protected $casts = [ protected $casts = [
'active' => 'boolean', 'active' => 'boolean',
@ -23,8 +42,11 @@ class Product extends Base
public function getNumPricesByNum($num) public function getNumPricesByNum($num)
{ {
$descNumPrices = array_reverse($this->numprices->toArray()); $descNumPrices = array_reverse($this->numprices->toArray());
$multiple = 1;
if($this->sales_method == 'batches') $multiple = (int)$this->piece_to_batch;
foreach($descNumPrices as $numprice){ foreach($descNumPrices as $numprice){
if($num >= $numprice['num']){ if($num >= ($numprice['num'] * $multiple)){
return $numprice['price']; return $numprice['price'];
} }
} }
@ -105,4 +127,138 @@ class Product extends Base
return $images[0] ?? ''; return $images[0] ?? '';
} }
public static function getUnitList(){
return [
//A
['title' => 'Acre/Acres'],
['title' => 'Ampere/Amperes'],
//B
['title' => 'Bag/Bags'],
['title' => 'Barrel/Barrels'],
['title' => 'Blade/Blades'],
['title' => 'Box/Boxes'],
['title' => 'Bushel/Bushels'],
// C
['title' => 'Carat/Carats'],
['title' => 'Carton/Cartons'],
['title' => 'Case/Cases'],
['title' => 'Centimeter/Centimeters'],
['title' => 'Chain/Chains'],
['title' => 'Combo/Combos'],
['title' => 'Cubic Centimeter/Cubic Centimeters'],
['title' => 'Cubic Foot/Cubic Feet'],
['title' => 'Cubic Inch/Cubic Inches'],
['title' => 'Cubic Meter/Cubic Meters'],
['title' => 'Cubic Yard/Cubic Yards'],
// D
['title' => 'Degrees Fahrenheit'],
['title' => 'Dozen/Dozens'],
['title' => 'Dram/Drams'],
// F
['title' => 'Fluid Ounce/Fluid Ounces'],
['title' => 'Foot/Feet'],
['title' => 'Forty-FootContainer'],
['title' => 'Furlong/Furlongs'],
// G
['title' => 'Gallon/Gallons'],
['title' => 'Gill/Gills'],
['title' => 'Grain/Grains'],
['title' => 'Gram/Grams'],
['title' => 'Gross'],
// H
['title' => 'Hectare/Hectares'],
['title' => 'Hertz'],
// I
['title' => 'Inch/Inches'],
// K
['title' => 'Kiloampere/Kiloamperes'],
['title' => 'Kilogram/Kilograms'],
['title' => 'Kilohertz'],
['title' => 'Kilometer/kilometers'],
['title' => 'Kiloohm/Kiloohms'],
['title' => 'Kilovolt/Kilovolts'],
['title' => 'Kilowatt/Kilowatts'],
// L
['title' => 'Liter/Liters'],
['title' => 'Long Ton/Long Tons'],
// M
['title' => 'Megahertz'],
['title' => 'Meter/Meters'],
['title' => 'Metric Ton/Metric Tons'],
['title' => 'Mile/Miles'],
['title' => 'Milliampere/Milliamperes'],
['title' => 'Milligram/Milligrams'],
['title' => 'Millihertz'],
['title' => 'Milliliter/Milliliters'],
['title' => 'Milliohm/Milliohms'],
['title' => 'Millivolt/Millivolts'],
['title' => 'Milliwatt/Milliwatts'],
// N
['title' => 'Nautical Mile/Nautical Miles'],
// O
['title' => 'Ohm/Ohms'],
['title' => 'Ounce/Ounces'],
// P
['title' => 'Pack/Packs'],
['title' => 'Pair/Pairs'],
['title' => 'Pallet/Pallets'],
['title' => 'Parcel/Parcels'],
['title' => 'Perch/Perches'],
['title' => 'Piece/Pieces'],
['title' => 'Pint/Pints'],
['title' => 'Plant/Plants'],
['title' => 'Pole/Poles'],
['title' => 'Pound/Pounds'],
// Q
['title' => 'Quart/Quarts'],
['title' => 'Quarter/Quarters'],
// R
['title' => 'Rod/Rods'],
['title' => 'Roll/Rolls'],
// S
['title' => 'Set/Sets'],
['title' => 'Sheet/Sheets'],
['title' => 'Short Ton/Short Tons'],
['title' => 'Square Centimeter/Square Centimeters'],
['title' => 'Square Foot/Square Feet'],
['title' => 'Square Inch/Square Inches'],
['title' => 'Square Meter/Square Meters'],
['title' => 'Square Mile/Square Miles'],
['title' => 'Square Yard/Square Yards'],
['title' => 'Stone/Stones'],
['title' => 'Strand/Strands'],
// T
['title' => 'Ton/Tons'],
['title' => 'Tonne/Tonnes'],
['title' => 'Tray/Trays'],
['title' => 'Twenty-Foot Container'],
// U
['title' => 'Unit/Units'],
// V
['title' => 'Volt/Volts'],
// W
['title' => 'Watt/Watts'],
['title' => 'Wp'],
// Y
['title' => 'Yard/Yards'],
];
}
public static function getTradeTermList(){
return [
['title'=>'EXW'],
['title'=>'FCA'],
['title'=>'FAS'],
['title'=>'FOB'],
['title'=>'CFR'],
['title'=>'CIF'],
['title'=>'CPT'],
['title'=>'CIP'],
['title'=>'DAT'],
['title'=>'DAP'],
['title'=>'DDP'],
];
}
} }

View File

@ -8,5 +8,5 @@ class ProductDescription extends Base
{ {
use HasFactory; use HasFactory;
protected $fillable = ['locale', 'name', 'content', 'meta_title', 'meta_description', 'meta_keywords']; protected $fillable = ['locale', 'name', 'content', 'meta_title', 'meta_description', 'meta_keywords', 'unit'];
} }

View File

@ -12,6 +12,7 @@
namespace Beike\Repositories; namespace Beike\Repositories;
use Beike\Models\Country; use Beike\Models\Country;
use Beike\Models\RegionZone;
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
@ -26,6 +27,7 @@ class CountryRepo
{ {
return [ return [
'name' => $data['name'] ?? '', 'name' => $data['name'] ?? '',
'icon' => $data['icon'] ?? '',
'code' => $data['code'] ?? '', 'code' => $data['code'] ?? '',
'sort_order' => (int) $data['sort_order'] ?? 0, 'sort_order' => (int) $data['sort_order'] ?? 0,
'status' => (bool) $data['status'] ?? 0, 'status' => (bool) $data['status'] ?? 0,
@ -121,4 +123,60 @@ class CountryRepo
{ {
return Country::query()->select('id', 'name')->get(); return Country::query()->select('id', 'name')->get();
} }
/**
* Common: 查询国家列表
* Author: wu-hui
* Time: 2023/08/21 16:10
* @param $name
* @param int $onlyActive
* @return Builder[]|Collection
*/
public static function autocomplete($name, $onlyActive = 1){
// 参数获取
$pageSize = request()->input('page_size',10);
$regionsId = (int)request()->input('regions_id');
// 列表获取
$builder = Country::query()
->where('name', 'like', "$name%")
->when($regionsId > 0,function($query) use ($regionsId){
$ids = RegionZone::query()
->where('region_id',$regionsId)
->where('zone_id',0)
->pluck('country_id');
$ids = $ids ? $ids->toArray() : [];
if(is_array($ids) && count($ids) > 0) $query->whereIn('id',$ids);
})
->select('id', 'name', 'icon', 'status', 'code')
->orderBy('sort_order','ASC')
->orderBy('id','ASC');
// if ($onlyActive) {
// $builder->where('status', 1);
// }
if($pageSize == 'all') return $builder->get();
else return $builder->limit($pageSize)->get();
}
/**
* Common: 根据国家ID 获取对应的列表
* Author: wu-hui
* Time: 2023/08/21 16:13
* @param array $ids
* @return array|mixed[]
*/
public static function getInList(array $ids){
$list = Country::query()
->whereIn('id', $ids)
->select('id', 'name', 'code')
->orderBy('sort_order','ASC')
->orderBy('id','ASC')
->get();
if($list) {
$list = $list->toArray();
return array_column($list,null,'id');
}
return [];
}
} }

View File

@ -0,0 +1,390 @@
<?php
/**
* LogisticsRepo.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-06-23 11:19:23
* @modified 2022-06-23 11:19:23
*/
namespace Beike\Repositories;
use Beike\Models\Attribute;
use Beike\Models\AttributeValue;
use Beike\Models\Logistics;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\HigherOrderBuilderProxy;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Support\Facades\DB;
class LogisticsRepo
{
private static $allLogisticssWithName;
/**
* 获取商品详情
*/
public static function getLogisticsDetail($product)
{
if (is_int($product)) {
$product = Logistics::query()->findOrFail($product);
}
$product->load('description', 'skus', 'masterSku', 'brand', 'relations', 'numPrices');//, 'price_setting', 'numPrices');
hook_filter('repo.product.get_detail', $product);
return $product;
}
/**
* 通过单个或多个商品分类获取商品列表
*
* @param $categoryId
* @param $filterData
* @return LengthAwarePaginator
*/
public static function getLogisticssByCategory($categoryId, $filterData)
{
$builder = static::getBuilder(array_merge(['category_id' => $categoryId, 'active' => 1], $filterData));
return $builder->with('inCurrentWishlist')
->paginate($filterData['per_page'] ?? perPage())
->withQueryString();
}
/**
* 通过商品ID获取商品列表
* @param $productIds
* @return AnonymousResourceCollection
*/
public static function getLogisticssByIds($productIds): AnonymousResourceCollection
{
if (! $productIds) {
return LogisticsSimple::collection(new Collection());
}
$builder = static::getBuilder(['product_ids' => $productIds])->whereHas('masterSku');
$products = $builder->with('inCurrentWishlist')->get();
return LogisticsSimple::collection($products);
}
/**
* 获取商品筛选对象
*
* @param array $filters
* @return Builder
* @throws \Exception
*/
public static function getBuilder(array $filters = []): Builder
{
$builder = Logistics::query()->with('country');
$builder->leftJoin('countries as c', function ($build) {
$build->whereColumn('c.id', 'logistics.id');
});
$builder->select(['logistics.*', 'c.name as country_name']);
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', $filters['category_id']);
}
});
}
// 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'])
->whereIn('attribute_value_id', $attribute['value']);
});
}
}
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($filters['model'])) {
$query->where('model', 'like', "%{$filters['model']}%");
}
});
}
if (isset($filters['price']) && $filters['price']) {
$builder->whereHas('skus', function ($query) use ($filters) {
// price 格式:price=30-100
$prices = explode('-', $filters['price']);
if (! $prices[1]) {
$query->where('price', '>', $prices[0] ?: 0)->where('is_default', 1);
} else {
$query->whereBetween('price', [$prices[0] ?? 0, $prices[1]])->where('is_default', 1);
}
});
}
if (isset($filters['created_start'])) {
$builder->where('logistics.created_at', '>', $filters['created_start']);
}
if (isset($filters['created_end'])) {
$builder->where('logistics.created_at', '>', $filters['created_end']);
}
$sort = $filters['sort'] ?? 'logistics.position';
$order = $filters['order'] ?? 'desc';
$builder->orderBy($sort, $order);
return hook_filter('repo.logistics.builder', $builder);
}
public static function parseFilterParamsAttr($attr)
{
$attributes = explode('|', $attr);
$attributes = array_map(function ($item) {
$itemArr = explode(':', $item);
if (count($itemArr) != 2) {
throw new \Exception('Params attr has an error format!');
}
return [
'attr' => $itemArr[0],
'value' => explode(',', $itemArr[1]),
];
}, $attributes);
return $attributes;
}
public static function getFilterAttribute($data): array
{
$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');
$attributeValueMap = array_column(AttributeValue::query()->with('description')->get()->toArray(), null, 'id');
$attributes = isset($data['attr']) ? self::parseFilterParamsAttr($data['attr']) : [];
$attributeMaps = array_column($attributes, 'value', 'attr');
$results = [];
foreach ($productAttributes as $item) {
if (! isset($attributeMap[$item['attribute_id']]) || ! isset($attributeValueMap[$item['attribute_value_id']])) {
continue;
}
$attribute = $attributeMap[$item['attribute_id']];
$attributeValue = $attributeValueMap[$item['attribute_value_id']];
if (! isset($results[$item['attribute_id']])) {
$results[$item['attribute_id']] = [
'id' => $attribute['id'],
'name' => $attribute['description']['name'],
];
}
if (! isset($results[$item['attribute_id']]['values'][$item['attribute_value_id']])) {
$results[$item['attribute_id']]['values'][$item['attribute_value_id']] = [
'id' => $attributeValue['id'],
'name' => $attributeValue['description']['name'],
'selected' => in_array($attributeValue['id'], $attributeMaps[$attribute['id']] ?? []),
];
}
}
$results = array_map(function ($item) {
$item['values'] = array_values($item['values']);
return $item;
}, $results);
return array_values($results);
}
public static function getFilterPrice($data)
{
$selectPrice = $data['price'] ?? '-';
// unset($data['price']);
$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');
$priceArr = explode('-', $selectPrice);
$selectMin = $priceArr[0];
$selectMax = $priceArr[1];
return [
'min' => $min,
'max' => $max,
'select_min' => ($selectMin && $selectMin > $min) ? $selectMin : $min,
'select_max' => ($selectMax && $selectMax < $max) ? $selectMax : $max,
];
}
public static function list($data = [])
{
return static::getBuilder($data)->paginate($data['per_page'] ?? 20);
}
public static function autocomplete($name)
{
$products = Logistics::query()->with('description')
->whereHas('description', function ($query) use ($name) {
$query->where('name', 'like', "%{$name}%");
})->limit(10)->get();
$results = [];
foreach ($products as $product) {
$results[] = [
'id' => $product->id,
'name' => $product->description->name,
'status' => $product->active,
'image' => $product->image,
];
}
return $results;
}
/**
* 获取商品ID获取单个商品名称
*
* @param $id
* @return HigherOrderBuilderProxy|mixed|string
*/
public static function getNameById($id)
{
$product = Logistics::query()->find($id);
if ($product) {
return $product->description->name;
}
return '';
}
/**
* 通过商品ID获取商品名称
* @param $id
* @return mixed|string
*/
public static function getName($id)
{
return self::getNameById($id);
}
/**
* 获取所有商品ID和名称列表
*
* @return array|null
*/
public static function getAllLogisticssWithName(): ?array
{
if (self::$allLogisticssWithName !== null) {
return self::$allLogisticssWithName;
}
$items = [];
$products = static::getBuilder()->select('id')->get();
foreach ($products as $product) {
$items[$product->id] = [
'id' => $product->id,
'name' => $product->description->name ?? '',
];
}
return self::$allLogisticssWithName = $items;
}
/**
* @param $productIds
* @return array
*/
public static function getNames($productIds): array
{
$products = self::getListByLogisticsIds($productIds);
return $products->map(function ($product) {
return [
'id' => $product->id,
'name' => $product->description->name ?? '',
];
})->toArray();
}
/**
* 通过商品ID获取商品列表
* @return array|Builder[]|Collection
*/
public static function getListByLogisticsIds($productIds)
{
if (empty($productIds)) {
return [];
}
$products = Logistics::query()
->with(['description'])
->whereIn('id', $productIds)
->orderByRaw(DB::raw('FIELD(id, ' . implode(',', $productIds) . ')'))
->get();
return $products;
}
public static function DeleteByIds($ids)
{
Logistics::query()->whereIn('id', $ids)->delete();
}
public static function updateStatusByIds($ids, $status)
{
Logistics::query()->whereIn('id', $ids)->update(['active' => $status]);
}
public static function forceDeleteTrashed()
{
$products = Logistics::onlyTrashed();
$productsIds = $products->pluck('id')->toArray();
LogisticsRelation::query()->whereIn('product_id', $productsIds)->orWhere('relation_id', $productsIds)->delete();
LogisticsAttribute::query()->whereIn('product_id', $productsIds)->delete();
LogisticsCategory::query()->whereIn('product_id', $productsIds)->delete();
LogisticsSku::query()->whereIn('product_id', $productsIds)->delete();
LogisticsDescription::query()->whereIn('product_id', $productsIds)->delete();
$products->forceDelete();
}
/**
* Common: 获取默认国家
* Author: wu-hui
* Time: 2023/08/25 13:50
* @param false $isGetInfo
* @return array|mixed
*/
public static function getDefaultCountries($isGetInfo = false){
$set = plugin_setting('logistics.default_country');
if($set && !$isGetInfo) $set = array_column([$set],null,'id');
return $set ?? [];
}
}

View File

@ -13,6 +13,7 @@ namespace Beike\Repositories;
use Beike\Models\Address; use Beike\Models\Address;
use Beike\Models\Customer; use Beike\Models\Customer;
use Beike\Models\Logistics;
use Beike\Models\Order; use Beike\Models\Order;
use Beike\Services\StateMachineService; use Beike\Services\StateMachineService;
use Carbon\Carbon; use Carbon\Carbon;
@ -121,6 +122,11 @@ class OrderRepo
$builder->where('status', $status); $builder->where('status', $status);
} }
$builder->with(['orderShipments','logistics'=>function($query){
$query->select(['id','day_min','day_max']);
}]);
return $builder; return $builder;
} }
@ -217,6 +223,12 @@ class OrderRepo
$currency = CurrencyRepo::findByCode($currencyCode); $currency = CurrencyRepo::findByCode($currencyCode);
$currencyValue = $currency->value ?? 1; $currencyValue = $currency->value ?? 1;
$shipping_method_name = trans($shippingMethodCode);
if(is_int($shippingMethodCode)) {
$logisticsName = Logistics::query()->where('id',$shippingMethodCode)->value('name');
$shipping_method_name = $logisticsName ?? $shipping_method_name;
}
$order = new Order([ $order = new Order([
'number' => self::generateOrderNumber(), 'number' => self::generateOrderNumber(),
'customer_id' => $customer->id ?? 0, 'customer_id' => $customer->id ?? 0,
@ -235,7 +247,7 @@ class OrderRepo
'user_agent' => request()->userAgent(), 'user_agent' => request()->userAgent(),
'status' => StateMachineService::CREATED, 'status' => StateMachineService::CREATED,
'shipping_method_code' => $shippingMethodCode, 'shipping_method_code' => $shippingMethodCode,
'shipping_method_name' => trans($shippingMethodCode), 'shipping_method_name' => $shipping_method_name,
'shipping_customer_name' => $shippingAddress->name, 'shipping_customer_name' => $shippingAddress->name,
'shipping_calling_code' => $shippingAddress->calling_code ?? 0, 'shipping_calling_code' => $shippingAddress->calling_code ?? 0,
'shipping_telephone' => $shippingAddress->phone ?? '', 'shipping_telephone' => $shippingAddress->phone ?? '',

View File

@ -50,6 +50,8 @@ class DesignService
$content['module_code'] = $moduleCode; $content['module_code'] = $moduleCode;
if ($moduleCode == 'slideshow') { if ($moduleCode == 'slideshow') {
return self::handleSlideShow($content); return self::handleSlideShow($content);
} elseif ($moduleCode == 'slideshow_video') {
return self::handleSlideShow($content);
} elseif (in_array($moduleCode, ['image401', 'image100', 'image200', 'image300'])) { } elseif (in_array($moduleCode, ['image401', 'image100', 'image200', 'image300'])) {
return self::handleImage401($content); return self::handleImage401($content);
} elseif ($moduleCode == 'brand') { } elseif ($moduleCode == 'brand') {

View File

@ -12,6 +12,7 @@
namespace Beike\Services; namespace Beike\Services;
use Beike\Admin\Http\Resources\PluginResource; use Beike\Admin\Http\Resources\PluginResource;
use Beike\Models\Logistics;
use Beike\Repositories\PluginRepo; use Beike\Repositories\PluginRepo;
use Beike\Shop\Services\CheckoutService; use Beike\Shop\Services\CheckoutService;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -43,12 +44,27 @@ class ShippingMethodService
if ($quotes) { if ($quotes) {
$pluginResource = (new PluginResource($plugin))->jsonSerialize(); $pluginResource = (new PluginResource($plugin))->jsonSerialize();
$shippingMethods[] = [ $shippingMethods[] = [
'is_logistics' => 0,
'code' => $pluginCode, 'code' => $pluginCode,
'name' => $pluginResource['name'], 'name' => $pluginResource['name'],
'quotes' => $quotes, 'quotes' => $quotes,
]; ];
} }
} }
// 获取根据收货地址 获取物流信息
$cart = $checkout->cart->toArray();
$address = $checkout->cart->guest_shipping_address ?? $checkout->cart->guest_payment_address ?? $cart['payment_address'] ?? $cart['shipping_address'];
if($address){
$logisticsList = Logistics::getAll($address['country_id']);
foreach($logisticsList as $logisticsItem){
$shippingMethods[] = [
'is_logistics' => 1,
'code' => $logisticsItem['id'],
'name' => $logisticsItem['name'],
'quotes' => $logisticsItem,
];
}
}
return $shippingMethods; return $shippingMethods;
} }

View File

@ -5,6 +5,7 @@ namespace Beike\Shop\Http\Controllers;
use Beike\Models\ProductSku; use Beike\Models\ProductSku;
use Beike\Shop\Http\Requests\CartRequest; use Beike\Shop\Http\Requests\CartRequest;
use Beike\Shop\Services\CartService; use Beike\Shop\Services\CartService;
use Beike\Shop\Services\CheckoutService;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -18,7 +19,7 @@ class CartController extends Controller
$data = [ $data = [
'data' => CartService::reloadData(), 'data' => CartService::reloadData(),
]; ];
$data['totals'] = $this->getOrderMoney($data['data']['carts']);
$data = hook_filter('cart.index.data', $data); $data = hook_filter('cart.index.data', $data);
return view('cart/cart', $data); return view('cart/cart', $data);
@ -38,6 +39,7 @@ class CartController extends Controller
CartService::select($customer, $cartIds); CartService::select($customer, $cartIds);
$data = CartService::reloadData(); $data = CartService::reloadData();
$data['totals'] = $this->getOrderMoney($data['carts']);
$data = hook_filter('cart.select.data', $data); $data = hook_filter('cart.select.data', $data);
@ -56,21 +58,30 @@ class CartController extends Controller
{ {
try { try {
$skuId = $request->sku_id; $skuId = $request->sku_id;
$quantity = $request->quantity ?? 1; $quantity = $request->quantity ?? 1;
$buyNow = (bool) $request->buy_now ?? false; $buyNow = (bool) $request->buy_now ?? false;
$customer = current_customer(); $customer = current_customer();
// 多规格批量加入购物车处理
$sku = ProductSku::query() if(is_int($skuId)){
->whereRelation('product', 'active', '=', true) // 非多规格下单
->findOrFail($skuId); $sku = ProductSku::query()->findOrFail($skuId);
$cart = CartService::add($sku, $quantity, $customer);
$cart = CartService::add($sku, $quantity, $customer); $cartIds = [$cart->id];
if ($buyNow) { }else{
CartService::select($customer, [$cart->id]); // 多规格下单
$skuList = array_values(json_decode($skuId,TRUE));
foreach($skuList as $item){
if((int)$item['quantity'] > 0){
$sku = ProductSku::query()->findOrFail($item['id']);
$cart = CartService::add($sku, $item['quantity'], $customer);
$cartIds[] = $cart->id;
}
}
} }
$cart = hook_filter('cart.store.data', $cart);
if ($buyNow) CartService::select($customer, $cartIds);
$cart = hook_filter('cart.store.data', $cart);
return json_success(trans('shop/carts.added_to_cart'), $cart); return json_success(trans('shop/carts.added_to_cart'), $cart);
} catch (\Exception $e) { } catch (\Exception $e) {
return json_fail($e->getMessage()); return json_fail($e->getMessage());
@ -85,17 +96,18 @@ class CartController extends Controller
*/ */
public function update(CartRequest $request, $cartId): array public function update(CartRequest $request, $cartId): array
{ {
// return json_fail("错误");
try { try {
$customer = current_customer(); $customer = current_customer();
$quantity = (int) $request->get('quantity'); $quantity = (int) $request->get('quantity');
CartService::updateQuantity($customer, $cartId, $quantity); CartService::updateQuantity($customer, $cartId, $quantity);
$data = CartService::reloadData(); $data = CartService::reloadData();
$data['totals'] = $this->getOrderMoney($data['carts']);
$data = hook_filter('cart.update.data', $data); $data = hook_filter('cart.update.data', $data);
return json_success(trans('common.updated_success'), $data); return json_success(trans('common.updated_success'), $data);
} catch (\Exception $e) { } catch (\Exception $e) {
return json_fail($e->getMessage()); return json_fail($e->getMessage());
} }
@ -136,4 +148,22 @@ class CartController extends Controller
return json_success(trans('common.success'), $data); return json_success(trans('common.success'), $data);
} }
/**
* Common: 实时计算购物车订单金额
* Author: wu-hui
* Time: 2023/08/18 13:56
* @param $carts
* @return mixed
* @throws \Exception
*/
public function getOrderMoney($carts){
$carts = collect($carts)->where('selected', 1)->toArray();
$checkoutService = new CheckoutService();
$totalClass = hook_filter('service.checkout.total_service','Beike\Shop\Services\TotalService');
$checkoutService->totalService = (new $totalClass($checkoutService->cart,$carts));
$checkoutData = $checkoutService->checkoutData();
return $checkoutData['totals'];
}
} }

View File

@ -11,6 +11,7 @@
namespace Beike\Shop\Http\Controllers; namespace Beike\Shop\Http\Controllers;
use Beike\Models\Product;
use Beike\Repositories\OrderRepo; use Beike\Repositories\OrderRepo;
use Beike\Shop\Services\CheckoutService; use Beike\Shop\Services\CheckoutService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -22,6 +23,41 @@ class CheckoutController extends Controller
try { try {
$data = (new CheckoutService)->checkoutData(); $data = (new CheckoutService)->checkoutData();
$data = hook_filter('checkout.index.data', $data); $data = hook_filter('checkout.index.data', $data);
// 判断:当前购物车商品是否符合下单条件
$carts = $data['carts']['carts'] ?? [];
$cartQuantity = collect($carts)->groupBy('product_id')->map(function($productGroup){
$first = $productGroup->first();
return [
'product_id' => $first['product_id'],
'total_quantity' => $productGroup->sum('quantity'),
'name_format' => $first['name_format'],
'minimum_order' => $first['minimum_order'],
'sales_method' => $first['sales_method'],
'piece_to_batch' => $first['piece_to_batch'],
];
})->toArray();
foreach($carts as $cartItem){
$productInfo = $cartQuantity[$cartItem['product_id']];
// 判断:起订量 如果大于购买数量 不符合下单条件
$minimumOrder = $productInfo['sales_method'] == 'batches' ? ($productInfo['minimum_order'] * $productInfo['piece_to_batch']) : $productInfo['minimum_order'];// 起订量
if($minimumOrder > $productInfo['total_quantity']){
throw new \Exception(trans('product.quantity_error_mini',[
'goods_name'=>$productInfo['name_format'],
'num'=>$minimumOrder,
]));
break;
}
// 判断:批量销售商品 购买总数量必须是N的倍数 否则不符合下单条件
$pieceToBatch = $productInfo['sales_method'] == 'batches' ? $productInfo['piece_to_batch'] : 1;// 倍数
if(($productInfo['total_quantity'] % $pieceToBatch) != 0){
throw new \Exception(trans('product.quantity_error_multiple',[
'goods_name'=>$productInfo['name_format'],
'num'=>$pieceToBatch,
]));
break;
}
}
return view('checkout', $data); return view('checkout', $data);
} catch (\Exception $e) { } catch (\Exception $e) {
@ -57,7 +93,14 @@ class CheckoutController extends Controller
public function confirm() public function confirm()
{ {
try { try {
$data = (new CheckoutService)->confirm(); $checkoutService = new CheckoutService;
$selectedProducts = $checkoutService->selectedProducts->toArray();
foreach($selectedProducts as $product){
if($product['product']['active'] == FALSE){
return json_fail(trans('common.product_active_false'));
}
}
$data = $checkoutService->confirm();
return hook_filter('checkout.confirm.data', $data); return hook_filter('checkout.confirm.data', $data);
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@ -2,10 +2,21 @@
namespace Beike\Shop\Http\Controllers; namespace Beike\Shop\Http\Controllers;
use Beike\Models\Cart;
use Beike\Models\CartProduct;
use Beike\Models\Country;
use Beike\Models\Logistics;
use Beike\Models\Product; use Beike\Models\Product;
use Beike\Models\ProductSku;
use Beike\Repositories\AddressRepo;
use Beike\Repositories\CartRepo;
use Beike\Repositories\PluginRepo;
use Beike\Repositories\ProductRepo; use Beike\Repositories\ProductRepo;
use Beike\Services\ShippingMethodService;
use Beike\Shop\Http\Resources\ProductDetail; use Beike\Shop\Http\Resources\ProductDetail;
use Beike\Shop\Http\Resources\ProductSimple; use Beike\Shop\Http\Resources\ProductSimple;
use Beike\Shop\Services\CartService;
use Beike\Shop\Services\CheckoutService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class ProductController extends Controller class ProductController extends Controller
@ -22,6 +33,7 @@ class ProductController extends Controller
$product = ProductRepo::getProductDetail($product); $product = ProductRepo::getProductDetail($product);
$data = [ $data = [
'product_id' => $product->id,
'product' => (new ProductDetail($product))->jsonSerialize(), 'product' => (new ProductDetail($product))->jsonSerialize(),
'relations' => ProductRepo::getProductsByIds($relationIds)->jsonSerialize(), 'relations' => ProductRepo::getProductsByIds($relationIds)->jsonSerialize(),
]; ];
@ -56,4 +68,154 @@ class ProductController extends Controller
return view('search', $data); return view('search', $data);
} }
/**
* Common: 计算当前商品订单金额信息
* Author: wu-hui
* Time: 2023/08/18 10:37
* @param Request $request
* @return array|\Illuminate\Http\JsonResponse
* @throws \Exception
*/
public function computeOrderMoney(Request $request){
// 参数获取
$changeLogisticsId = (int)$request->change_logistics_id ?? 0;
$list = json_decode($request->list,TRUE) ?? '';
if(!is_array($list)) return json_fail(trans('shop/products.buy_sku_error'));
// 生成模拟数据
$diyData = [];
foreach($list as $item){
$diyData[] = [
'id' => 0,
'selected' => 1,
'product_id' => $item['product_id'],
'product_sku_id' => $item['product_sku_id'],
'quantity' => $item['quantity'],
'product' => Product::where('id',$item['product_id'])->first()->toArray(),
'sku' => ProductSku::where('id',$item['product_sku_id'])->first()->toArray(),
];
}
$cartItems = collect($diyData)->mapInto(CartProduct::class);
$cartList = CartService::cartListHandle($cartItems);
// 价格计算
$customer = current_customer();
$customerId = $customer->id ?? 0;
$sessionId = session()->getId();
$defaultAddress = AddressRepo::listByCustomer($customer)->first();
$defaultAddressId = $defaultAddress->id ?? 0;
$shippingMethod = PluginRepo::getShippingMethods()->first();
$paymentMethod = PluginRepo::getPaymentMethods()->first();
$shippingMethodCode = $shippingMethod->code ?? '';
$cart = collect([[
'customer_id' => $customerId,
'session_id' => $sessionId,
'shipping_address_id' => $defaultAddressId,
'shipping_method_code' => (int)$changeLogisticsId > 0 ? (int)$changeLogisticsId : $shippingMethodCode ,
'payment_address_id' => $defaultAddressId,
'payment_method_code' => $paymentMethod->code ?? '',
// "id" => 28,
// "customer_id" => 0,
// "session_id" => "Chwm3kGAY5YX4CC58yTKWREvSoplb9gFXl2UNnsb",
// "shipping_address_id" => 0,
// "guest_shipping_address" => null,
// "shipping_method_code" => "",
// "payment_address_id" => 0,
// "guest_payment_address" => null,
// "payment_method_code" => "paypal",
// "extra" => null,
// "created_at" => "2023-08-18 01:35:12",
// "updated_at" => "2023-08-18 01:35:12",
// "shipping_address" => null,
// "payment_address" => null,
]])->mapInto(Cart::class);
$cart = $cart[0];
// 计算
$checkoutService = new CheckoutService();
$checkoutData = $checkoutService->update([
// 'customer_id' => $customerId,
// 'session_id' => $sessionId,
'shipping_address_id' => $defaultAddressId,
'shipping_method_code' => (int)$changeLogisticsId > 0 ? (int)$changeLogisticsId : $shippingMethodCode ,
'payment_address_id' => $defaultAddressId,
'payment_method_code' => $paymentMethod->code ?? '']);
// $totalClass = hook_filter('service.checkout.total_service','Beike\Shop\Services\TotalService');
// $checkoutService->totalService = (new $totalClass($cart,$cartList));
// $checkoutData = $checkoutService->checkoutData();
$totals = $checkoutData['totals'];
return json_success(trans('common.success'),$totals);
}
/**
* Common: 获取物流列表
* Author: wu-hui
* Time: 2023/08/29 10:06
* @param Request $request
* @return array
* @throws \Exception
*/
public function productsLogistics(Request $request){
$data = [
'list' => [],
'country' => [],
];
// 参数获取
$countryId = (int)$request->country_id;
$goodsList = json_decode($request->goods_list,TRUE) ?? '';
if($countryId){
// 刷新国家信息
$country = Country::query()->where('id',$countryId)->first();
$data['country'] = $country ? $country->toArray() : [];
}
// 信息获取
$list = Logistics::query()
->select([
'id',
'name',
'day_min',
'day_max',
])
->when($countryId > 0,function($q) use ($countryId){
$q->whereRaw(\DB::raw('FIND_IN_SET('.$countryId.',country_ids)'));
})
->orderBy('position','ASC')
->orderBy('id','ASC')
->get();
$data['list'] = $list ? $list->toArray() : [];
// 循环处理内容
$time = time();
foreach($data['list'] as &$logisticsItem){
// 预计到达时间处理
// $startTime = date('Y-m-d',strtotime("+{$logisticsItem['day_min']} day", $time));
// $endTime = date('Y-m-d',strtotime("+{$logisticsItem['day_max']} day", $time));
// $logisticsItem['estimated_time'] = trans('order.expected_arrival',['start_time'=>$startTime,'end_time'=>$endTime]);
$logisticsItem['estimated_time'] = trans('order.work_days',['start_day'=>$logisticsItem['day_min'],'end_day'=>$logisticsItem['day_max']]);
// 判断:如果存在商品 计算物流运费信息
if(count($goodsList) > 0){
$request->list = $request->goods_list;
$request->change_logistics_id = $logisticsItem['id'];
$totals = $this->computeOrderMoney($request);
$shipping = array_column($totals['data'],null,'code')['shipping'] ?? [];
// $logisticsItem['shipping_fee'] = $shipping['show_tips'] == 0 ? $shipping['amount_format'] : trans('shop/carts.to_be_negotiated');
if($shipping['show_tips'] == 0) {
$logisticsItem['shipping_fee'] = $shipping['amount_format'];
$logisticsItem['amount'] = $shipping['amount'];
}else $logisticsItem = [];
}
}
// 存在多个物流 根据物流费用进行排序
if(count($data['list']) > 1){
$amounts = array_column($data['list'],'amount');
if($amounts) array_multisort($amounts,SORT_ASC,$data['list']);
}
$data['list'] = array_filter($data['list']);
return json_success(trans('common.success'),$data);
}
} }

View File

@ -1,7 +1,6 @@
<?php <?php
/** /**
* ZoneController.php * ZoneController.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved * @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com * @link https://beikeshop.com
* @author TL <mengwb@guangda.work> * @author TL <mengwb@guangda.work>
@ -11,21 +10,52 @@
namespace Beike\Shop\Http\Controllers; namespace Beike\Shop\Http\Controllers;
use Beike\Models\Region;
use Beike\Repositories\CountryRepo;
use Beike\Repositories\ZoneRepo; use Beike\Repositories\ZoneRepo;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class ZoneController extends Controller class ZoneController extends Controller{
{ public function index(Request $request,int $countryId){
public function index(Request $request, int $countryId)
{
ZoneRepo::listByCountry($countryId); ZoneRepo::listByCountry($countryId);
$data = [ $data = [
'zones' => ZoneRepo::listByCountry($countryId), 'zones' => ZoneRepo::listByCountry($countryId),
]; ];
$data = hook_filter('zone.index.data',$data);
$data = hook_filter('zone.index.data', $data); return json_success(trans('common.success'),$data);
return json_success(trans('common.success'), $data);
} }
/**
* Common: 获取全部的国家列表
* Author: wu-hui
* Time: 2023/08/25 16:57
* @param Request $request
* @return array
*/
public function countries(Request $request){
$brands = CountryRepo::autocomplete($request->get('name') ?? '', 0);
return json_success(trans('common.get_success'), $brands);
}
/**
* Common: 获取全部的区域分组
* Author: wu-hui
* Time: 2023/08/31 9:25
* @return array
*/
public function regionsAll(){
$list = Region::query()->select(['id','name'])->get();
return json_success(trans('common.get_success'), $list);
}
} }

View File

@ -36,8 +36,8 @@ class CartRequest extends FormRequest
$skuId = (int) $this->get('sku_id'); $skuId = (int) $this->get('sku_id');
return [ return [
'sku_id' => 'required|int', 'sku_id' => 'required',
'quantity' => ['required', 'int', function ($attribute, $value, $fail) use ($skuId) { 'quantity' => ['required', 'int', /*function ($attribute, $value, $fail) use ($skuId) {
$sku = ProductSku::query()->where('id', $skuId)->first(); $sku = ProductSku::query()->where('id', $skuId)->first();
$skuQuantity = $sku->quantity; $skuQuantity = $sku->quantity;
if ($value > $skuQuantity) { if ($value > $skuQuantity) {
@ -46,7 +46,7 @@ class CartRequest extends FormRequest
if($sku->product->price_setting == 'num' && $value < $sku->product->numprices[0]->num){ if($sku->product->price_setting == 'num' && $value < $sku->product->numprices[0]->num){
$fail(trans('shop/products.quantity_error')); $fail(trans('shop/products.quantity_error'));
} }
}], }*/],
'buy_now' => 'bool', 'buy_now' => 'bool',
]; ];
} }

View File

@ -26,7 +26,9 @@ class CartDetail extends JsonResource
}; };
$skuCode = $sku->sku; $skuCode = $sku->sku;
$description = $product->description; $description = $product->description;
$productName = $description->name; $productName = $description->name;
$unit = $this->unit;//$description->unit ?? '';
$subTotal = $price * $this->quantity; $subTotal = $price * $this->quantity;
$image = $sku->image ?: $product->image; $image = $sku->image ?: $product->image;
@ -37,6 +39,9 @@ class CartDetail extends JsonResource
'product_sku' => $skuCode, 'product_sku' => $skuCode,
'name' => $productName, 'name' => $productName,
'name_format' => sub_string($productName), 'name_format' => sub_string($productName),
'unit' => $unit,
'unit_format' => $unit,
'trade_term' => $this->trade_term,
'image' => $image, 'image' => $image,
'image_url' => image_resize($image), 'image_url' => image_resize($image),
'quantity' => $this->quantity, 'quantity' => $this->quantity,
@ -47,6 +52,10 @@ class CartDetail extends JsonResource
'subtotal' => $subTotal, 'subtotal' => $subTotal,
'subtotal_format' => currency_format($subTotal), 'subtotal_format' => currency_format($subTotal),
'variant_labels' => trim($sku->getVariantLabel()), 'variant_labels' => trim($sku->getVariantLabel()),
'active' => $product->active,
'minimum_order' => $product->minimum_order,
'sales_method' => $product->sales_method,
'piece_to_batch' => $product->piece_to_batch
]; ];
return hook_filter('resource.cart.detail', $result); return hook_filter('resource.cart.detail', $result);

View File

@ -38,7 +38,7 @@ class InquiryDetail extends JsonResource{
]; ];
} }
$product = $productsku->product; $product = $productsku->product;
$description = $product->description; $description = $product->description ?? '';
return [ return [
'id' => $this->id, 'id' => $this->id,
'product_sku_id' => $this->product_sku_id, 'product_sku_id' => $this->product_sku_id,

View File

@ -13,15 +13,15 @@ namespace Beike\Shop\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
class NumPricesDetail extends JsonResource class NumPricesDetail extends JsonResource{
{
public function toArray($request): array public function toArray($request): array{
{
return [ return [
'id' => $this->id, 'id' => $this->id,
'num' => $this->num, 'num' => $this->sales_method == 'piece' ? $this->num : $this->num * $this->piece_to_batch,
'price' => $this->price, 'price' => $this->price,
'price_format' => currency_format($this->price), 'price_format' => currency_format($this->price),
]; ];
} }
} }

View File

@ -31,29 +31,46 @@ class ProductDetail extends JsonResource
]; ];
} }
$salesMethod = $this->sales_method ?? 'piece';// 销售方式piece=按件卖batches=按批卖
$pieceToBatch = $this->piece_to_batch ?? 1;// 按批卖,每批等于多少件
$this->numprices = $this->numprices->map(function($numprices) use ($salesMethod,$pieceToBatch){
$numprices->sales_method = $salesMethod;
$numprices->piece_to_batch = $pieceToBatch;
return $numprices;
});
return [ return [
'id' => $this->id, 'id' => $this->id,
'name' => $this->description->name ?? '', 'name' => $this->description->name ?? '',
'description' => $this->description->content ?? '', 'unit' => $this->unit,//$this->description->unit ?? '',
'meta_title' => $this->description->meta_title ?? '', 'trade_term' => $this->trade_term,
'meta_keywords' => $this->description->meta_keywords ?? '', 'description' => $this->description->content ?? '',
'meta_title' => $this->description->meta_title ?? '',
'meta_keywords' => $this->description->meta_keywords ?? '',
'meta_description' => $this->description->meta_description ?? '', 'meta_description' => $this->description->meta_description ?? '',
'brand_id' => $this->brand->id ?? 0, 'brand_id' => $this->brand->id ?? 0,
'brand_name' => $this->brand->name ?? '', 'brand_name' => $this->brand->name ?? '',
'video' => $this->video ?? '', 'video' => $this->video ?? '',
'images' => array_map(function ($image) { 'images' => array_map(function($image){
return [ return [
'preview' => image_resize($image, 500, 500), 'preview' => image_resize($image,500,500),
'popup' => image_resize($image, 800, 800), 'popup' => image_resize($image,800,800),
'thumb' => image_resize($image, 150, 150), 'thumb' => image_resize($image,150,150),
]; ];
}, $this->images ?? []), },$this->images ?? []),
'attributes' => $attributes, 'attributes' => $attributes,
'variables' => $this->decodeVariables($this->variables), 'variables' => $this->decodeVariables($this->variables),
'skus' => SkuDetail::collection($this->skus)->jsonSerialize(), 'skus' => SkuDetail::collection($this->skus)->jsonSerialize(),
'in_wishlist' => $this->inCurrentWishlist->id ?? 0, 'in_wishlist' => $this->inCurrentWishlist->id ?? 0,
'active' => (bool) $this->active, 'active' => (bool)$this->active,
'price_setting' => $this->price_setting ?? '', 'price_setting' => $this->price_setting ?? '',
'minimum_order' => $this->minimum_order ?? 0,
'sales_method' => $this->sales_method ?? 'piece',
'piece_to_batch' => $this->piece_to_batch ?? 1,
'numPrices' => NumPricesDetail::collection($this->numprices)->jsonSerialize() ?? '', 'numPrices' => NumPricesDetail::collection($this->numprices)->jsonSerialize() ?? '',
]; ];
} }
@ -75,6 +92,7 @@ class ProductDetail extends JsonResource
return array_map(function ($item) use ($lang) { return array_map(function ($item) use ($lang) {
return [ return [
'name' => $item['name'][$lang] ?? '', 'name' => $item['name'][$lang] ?? '',
'isNumSelect' => $item['isNumSelect'] ?? FALSE,
'values' => array_map(function ($item) use ($lang) { 'values' => array_map(function ($item) use ($lang) {
return [ return [
'name' => $item['name'][$lang] ?? '', 'name' => $item['name'][$lang] ?? '',

View File

@ -31,13 +31,17 @@ class ProductSimple extends JsonResource
} }
$name = $this->description->name ?? ''; $name = $this->description->name ?? '';
$images = $this->images; $unit = $this->unit;//$this->description->unit ?? '';
$images = $this->images != NULL ? $this->images : [];
$data = [ $data = [
'id' => $this->id, 'id' => $this->id,
'sku_id' => $masterSku->id, 'sku_id' => $masterSku->id,
'name' => $name, 'name' => $name,
'name_format' => $name, 'name_format' => $name,
'unit' => $unit,
'unit_format' => $unit,
'trade_term' => $this->trade_term,
'url' => $this->url, 'url' => $this->url,
'price' => $masterSku->price, 'price' => $masterSku->price,
'origin_price' => $masterSku->origin_price, 'origin_price' => $masterSku->origin_price,
@ -47,7 +51,7 @@ class ProductSimple extends JsonResource
'in_wishlist' => $this->inCurrentWishlist->id ?? 0, 'in_wishlist' => $this->inCurrentWishlist->id ?? 0,
'price_setting' => $this->price_setting ?? '', 'price_setting' => $this->price_setting ?? '',
'numprices' => NumPricesDetail::collection($this->numprices)->jsonSerialize() ?? '', 'numprices' => NumPricesDetail::collection($this->numprices)->jsonSerialize() ?? '',
'minimum_order' => $this->minimum_order,
'images' => array_map(function ($item) { 'images' => array_map(function ($item) {
return image_resize($item, 400, 400); return image_resize($item, 400, 400);
}, $images), }, $images),

View File

@ -43,6 +43,10 @@ Route::prefix('/')
Route::get('categories/{category}', [CategoryController::class, 'show'])->name('categories.show'); Route::get('categories/{category}', [CategoryController::class, 'show'])->name('categories.show');
Route::get('countries/{id}/zones', [ZoneController::class, 'index'])->name('countries.zones.index'); Route::get('countries/{id}/zones', [ZoneController::class, 'index'])->name('countries.zones.index');
Route::get('countries/autocomplete', [ZoneController::class, 'countries'])->name('countries.zones.countries');
Route::get('regions/get_all', [ZoneController::class, 'regionsAll'])->name('regions.regions_all');
Route::get('currency/{currency}', [CurrencyController::class, 'index'])->name('currency.switch'); Route::get('currency/{currency}', [CurrencyController::class, 'index'])->name('currency.switch');
@ -65,6 +69,8 @@ Route::prefix('/')
Route::get('products/search', [ProductController::class, 'search'])->name('products.search'); Route::get('products/search', [ProductController::class, 'search'])->name('products.search');
Route::get('products/{product}', [ProductController::class, 'show'])->name('products.show'); Route::get('products/{product}', [ProductController::class, 'show'])->name('products.show');
Route::post('products/computeOrderMoney', [ProductController::class, 'computeOrderMoney'])->name('products.computeOrderMoney');
Route::post('products/productsLogistics', [ProductController::class, 'productsLogistics'])->name('products.productsLogistics');
Route::get('register', [RegisterController::class, 'index'])->name('register.index'); Route::get('register', [RegisterController::class, 'index'])->name('register.index');
Route::post('register', [RegisterController::class, 'store'])->name('register.store'); Route::post('register', [RegisterController::class, 'store'])->name('register.store');

View File

@ -11,7 +11,10 @@
namespace Beike\Shop\Services; namespace Beike\Shop\Services;
use Beike\Models\Cart;
use Beike\Models\CartProduct; use Beike\Models\CartProduct;
use Beike\Models\Product;
use Beike\Models\ProductSku;
use Beike\Repositories\CartRepo; use Beike\Repositories\CartRepo;
use Beike\Shop\Http\Resources\CartDetail; use Beike\Shop\Http\Resources\CartDetail;
use Exception; use Exception;
@ -27,18 +30,22 @@ class CartService
* @param bool $selected * @param bool $selected
* @return array * @return array
*/ */
public static function list($customer, bool $selected = false): array public static function list($customer, bool $selected = false): array{
{ if (self::$cartList !== null) return self::$cartList;
if (self::$cartList !== null) {
return self::$cartList;
}
$cartBuilder = CartRepo::allCartProductsBuilder($customer->id ?? 0); $cartBuilder = CartRepo::allCartProductsBuilder($customer->id ?? 0);
if ($selected) { if ($selected) $cartBuilder->where('selected', true);
$cartBuilder->where('selected', true);
}
$cartItems = $cartBuilder->get(); $cartItems = $cartBuilder->get();
return self::cartListHandle($cartItems);
}
/**
* Common: 处理指定的购物车商品
* Author: wu-hui
* Time: 2023/08/17 17:28
* @param $cartItems
* @return mixed
*/
public static function cartListHandle($cartItems){
$cartItems = $cartItems->filter(function ($item) { $cartItems = $cartItems->filter(function ($item) {
$description = $item->sku->product->description ?? ''; $description = $item->sku->product->description ?? '';
$product = $item->product ?? null; $product = $item->product ?? null;
@ -57,7 +64,6 @@ class CartService
return $description && $product; return $description && $product;
}); });
$productQuantitySumList = []; $productQuantitySumList = [];
foreach($cartItems as $item) { foreach($cartItems as $item) {
$productId = $item->product_id; $productId = $item->product_id;
@ -189,6 +195,13 @@ class CartService
if (empty($carts)) { if (empty($carts)) {
$carts = self::list(current_customer()); $carts = self::list(current_customer());
} }
// 判断:禁止选中 非直接下单产品
$carts = array_map(function($cartItem){
if(!$cartItem['active']){
$cartItem['selected'] = 0;
}
return $cartItem;
},$carts);
$cartList = collect($carts)->where('selected', 1); $cartList = collect($carts)->where('selected', 1);
@ -206,4 +219,5 @@ class CartService
return hook_filter('service.cart.data', $data); return hook_filter('service.cart.data', $data);
} }
} }

View File

@ -50,7 +50,7 @@ class CheckoutService
$this->cart = CartRepo::createCart($this->customer); $this->cart = CartRepo::createCart($this->customer);
$this->selectedProducts = CartRepo::selectedCartProducts($this->customer->id ?? 0); $this->selectedProducts = CartRepo::selectedCartProducts($this->customer->id ?? 0);
if ($this->selectedProducts->count() == 0) { if ($this->selectedProducts->count() == 0) {
throw new CartException(trans('shop/carts.empty_selected_products')); // throw new CartException(trans('shop/carts.empty_selected_products'));
} }
} }
@ -174,7 +174,7 @@ class CheckoutService
} }
$shippingMethodCode = $current['shipping_method_code']; $shippingMethodCode = $current['shipping_method_code'];
if (! PluginRepo::shippingEnabled($shippingMethodCode)) { if (! PluginRepo::shippingEnabled($shippingMethodCode) && !is_int($shippingMethodCode)) {
throw new \Exception(trans('shop/carts.invalid_shipping_method')); throw new \Exception(trans('shop/carts.invalid_shipping_method'));
} }
@ -252,16 +252,19 @@ class CheckoutService
foreach ($shipments as $shipment) { foreach ($shipments as $shipment) {
$shipmentCodes = array_merge($shipmentCodes, array_column($shipment['quotes'], 'code')); $shipmentCodes = array_merge($shipmentCodes, array_column($shipment['quotes'], 'code'));
} }
if (! in_array($currentCart->shipping_method_code, $shipmentCodes)) { // 判断:如果运费方式不存在 并且不是物流则设置第一个物流信息
if (!in_array($currentCart->shipping_method_code, $shipmentCodes) && !is_int($currentCart->shipping_method_code)) {
$this->updateShippingMethod($shipmentCodes[0] ?? ''); $this->updateShippingMethod($shipmentCodes[0] ?? '');
$this->totalService->setShippingMethod($shipmentCodes[0] ?? ''); $this->totalService->setShippingMethod($shipmentCodes[0] ?? '');
} }
$shippingMethodCode = !empty($currentCart->shipping_method_code) ? $currentCart->shipping_method_code : ($shipments[0]['code'] ?? '');
$data = [ $data = [
'current' => [ 'current' => [
'shipping_address_id' => $currentCart->shipping_address_id, 'shipping_address_id' => $currentCart->shipping_address_id,
'guest_shipping_address' => $currentCart->guest_shipping_address, 'guest_shipping_address' => $currentCart->guest_shipping_address,
'shipping_method_code' => $currentCart->shipping_method_code, 'shipping_method_code' => $shippingMethodCode,
'payment_address_id' => $currentCart->payment_address_id, 'payment_address_id' => $currentCart->payment_address_id,
'guest_payment_address' => $currentCart->guest_payment_address, 'guest_payment_address' => $currentCart->guest_payment_address,
'payment_method_code' => $currentCart->payment_method_code, 'payment_method_code' => $currentCart->payment_method_code,

View File

@ -114,7 +114,9 @@ class TotalService
*/ */
private function getTotalClassMaps() private function getTotalClassMaps()
{ {
$maps = []; $maps = [
'sum_quantity' => "\Beike\\Shop\\Services\\TotalServices\\SumQuantityService"
];
foreach (self::TOTAL_CODES as $code) { foreach (self::TOTAL_CODES as $code) {
$serviceName = Str::studly($code) . 'Service'; $serviceName = Str::studly($code) . 'Service';
$maps[$code] = "\Beike\\Shop\\Services\\TotalServices\\{$serviceName}"; $maps[$code] = "\Beike\\Shop\\Services\\TotalServices\\{$serviceName}";

View File

@ -12,52 +12,254 @@
namespace Beike\Shop\Services\TotalServices; namespace Beike\Shop\Services\TotalServices;
use Beike\Libraries\Weight;
use Beike\Models\Country;
use Beike\Models\Logistics;
use Beike\Models\Product;
use Beike\Repositories\LogisticsRepo;
use Beike\Shop\Services\CheckoutService; use Beike\Shop\Services\CheckoutService;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class ShippingService class ShippingService{
{ protected static $country;// 国家信息
/** protected static $productsList;// 商品分组统计列表
* @param CheckoutService $checkout
* @return array|null // 运费计算
* @throws \Exception|\Throwable public static function getTotal(CheckoutService $checkout): ?array{
*/ // 计算运费
public static function getTotal(CheckoutService $checkout): ?array
{
$totalService = $checkout->totalService; $totalService = $checkout->totalService;
$shippingMethod = $totalService->getShippingMethod(); $shippingMethod = $totalService->getShippingMethod();
if (empty($shippingMethod)) { $amount = 0;
return null; $logisticsInfo = [];// 物流信息
self::$country = LogisticsRepo::getDefaultCountries(true);// 国家信息
$showTips = 0;// 是否显示【待协商】提示
if($shippingMethod && !is_int($checkout->cart->shipping_method_code)){
// 通过插件(固定运费) 进行计算
if(empty($shippingMethod)) return NULL;
$shippingPluginCode = self::parseShippingPluginCode($shippingMethod);
$pluginCode = Str::studly($shippingPluginCode);
if(!app('plugin')->checkActive($shippingPluginCode)){
$cart = $checkout->cart;
$cart->shipping_method_code = '';
$cart->saveOrFail();
return [];
}
$className = "Plugin\\{$pluginCode}\\Bootstrap";
if(!method_exists($className,'getShippingFee')){
throw new \Exception("请在插件 {$className} 实现方法: public function getShippingFee(CheckoutService \$checkout)");
}
$amount = (float)(new $className)->getShippingFee($checkout);
} }
else{
// 商品信息处理 获取同一个商品的数量、重量
$products = $totalService->getCartProducts();
self::$productsList = collect($products)->groupBy('product_id')->map(function($group){
$firstInfo = $group->first();
$productWeightInfo = Product::query()
->select(['id','weight','weight_class','length','width','height'])
->find($firstInfo['product_id']);
if($productWeightInfo) $productWeightInfo = $productWeightInfo->toArray();
// 重量转换 - 物流重量单位为 千克;商品重量需要进行转换
$weight = $productWeightInfo['weight'] ?? 0;
$sumQuantity = $group->sum('quantity');
// 判断:如果是按批卖 则数量需要除以每批的数量 在计算总重量。按件卖则直接计算
$weightQuantity = $firstInfo['sales_method'] == 'batches' ? ($sumQuantity / $firstInfo['piece_to_batch']) : $sumQuantity;// 多少件/多少批 根据销售方式获取
$sumWeight = (float)sprintf("%.2f",($weight * $weightQuantity));
$weightClass = $productWeightInfo['weight_class'];
$sumWeight = Weight::convert($sumWeight,$weightClass);// 总重量 单位:克
// 获取 体积重 长cm*宽cm*高cm*数量/抛比=体积重
$volumeWeight = ($productWeightInfo['length'] * $productWeightInfo['width'] * $productWeightInfo['height']) * $weightQuantity;// 还需要除以计抛比
$shippingPluginCode = self::parseShippingPluginCode($shippingMethod); return [
$pluginCode = Str::studly($shippingPluginCode); 'product_id' => $firstInfo['product_id'],
'sum_quantity' => $sumQuantity,
if (! app('plugin')->checkActive($shippingPluginCode)) { 'sum_weight' => $sumWeight,
$cart = $checkout->cart; 'volume_weight' => $volumeWeight,
$cart->shipping_method_code = ''; 'weight' => $weight,
$cart->saveOrFail(); 'weight_class' => $weightClass,
];
return []; });
// 通过物流进行计算
$logisticsId = (int)$checkout->cart->shipping_method_code;
$logisticsInfo = self::getLogistics($logisticsId,$checkout->cart);
if($logisticsInfo) $amount = $logisticsInfo['amount'];// 存在物流 获取运费
else $showTips = 1;// 如果还是不能获取物流信息 则显示 待协商
} }
$className = "Plugin\\{$pluginCode}\\Bootstrap";
if (! method_exists($className, 'getShippingFee')) {
throw new \Exception("请在插件 {$className} 实现方法: public function getShippingFee(CheckoutService \$checkout)");
}
$amount = (float) (new $className)->getShippingFee($checkout);
$totalData = [ $totalData = [
'code' => 'shipping', 'code' => 'shipping',
'title' => trans('shop/carts.shipping_fee'), 'title' => trans('shop/carts.shipping_fee'),
'amount' => $amount, 'amount' => $amount,
'amount_format' => currency_format($amount), 'amount_format' => currency_format($amount),
'amount_tips' => trans('shop/carts.to_be_negotiated'),
'show_tips' => $showTips,
'country' => self::$country,// 国家信息
'logistics' => $logisticsInfo,// 物流信息
]; ];
$totalService->amount += $totalData['amount']; $totalService->amount += $totalData['amount'];
$totalService->totals[] = $totalData; $totalService->totals[] = $totalData;
return $totalData; return $totalData;
} }
/**
* Common: 运费计算 - 物流运费 - 获取使用的物流
* Author: wu-hui
* Time: 2023/08/22 11:55
* @param $logisticsId
* @param $cart
* @return array|mixed|mixed[]
*/
private static function getLogistics($logisticsId,$cart){
$logisticsField = ['id','type', 'name','first_weight','first_weight_fee','continuation_weight_max','add_weight','continuation_weight_fee','num_fee','throwing_ratio'];
// 判断:客服端是否切换国家 如果存在切换的国家id 则使用切换的国家id不存在则继续执行
$countryId = (int)request()->input('products_country_id');
if($countryId > 0) {
// 切换国家或者物流
// 仅存在国家ID直接获取当前国家全部物流信息取其中一个
// 同时存在国家ID和物流ID 则获取对应物流ID如果未查询到该物流 则执行仅存在国家ID时的操作
if($logisticsId) {
// 同时存在国家ID 获取物流ID
$logisticsInfo = Logistics::query()
->whereRaw(\DB::raw('FIND_IN_SET('.$countryId.',country_ids)'))
->with(['weights'])
->where('id',$logisticsId)
->select($logisticsField)
->first();
if($logisticsInfo) {
// 刷新国家信息
self::refreshCurrentCountry($countryId);
$logisticsList = [$logisticsInfo->toArray()];
goto logisticsListHandle;
}
}
// 仅存在国家ID时
goto CountryGetLogistics;
}
// 不存在切换国家id 正常流程
if($logisticsId <= 0){
// 获取当前收货地址国家ID
$cartArr = $cart->toArray();
$address = $cart->guest_shipping_address ?? $cart->guest_payment_address ?? $cartArr['payment_address'] ?? $cartArr['shipping_address'];
if($address) $countryId = (int)$address['country_id'];// 存在收货地址(用户已经登录 || 用户设置收货地址)
else $countryId = (int)self::$country['id'];// 不存在收货地址(用户未登录 || 用户已登录但是未设置收货地址) 获取默认国家收货地址
// 获取全部物流 返回第一个物流信息ID
CountryGetLogistics:
$logisticsList = Logistics::getAll($countryId,$logisticsField);
// 刷新国家信息
self::refreshCurrentCountry($countryId);
}else{
// 获取物流信息
$logisticsInfo = Logistics::query()
->with(['weights'])
->select($logisticsField)
->find($logisticsId);
$logisticsList = [$logisticsInfo ? $logisticsInfo->toArray() : []];
}
// 根据已查询的物流列表 筛选出符合条件的物流并且返回第一个
logisticsListHandle:
$eligibleLogisticsList = Logistics::LogisticsFiltering($logisticsList,self::$productsList);
// 循环计算物流运费
foreach($eligibleLogisticsList as &$eligibleLogisticsListItem){
// weight按重量计费num按数量计费free卖家包邮
$logisticsHandleFun = 'compute_'.$eligibleLogisticsListItem['type'];
$eligibleLogisticsListItem['amount'] = (float)self::$logisticsHandleFun($eligibleLogisticsListItem);
}
// 存在多个物流 需要获取运费最小的物流
if(count($eligibleLogisticsList) > 1){
$amounts = array_column($eligibleLogisticsList,'amount');
array_multisort($amounts,SORT_ASC,$eligibleLogisticsList);
}
return $eligibleLogisticsList[0] ?? [];
}
/**
* Common: 运费计算 - 物流运费 - 运费计算(按重量计费)
* Author: wu-hui
* Time: 2023/08/22 15:34
* @param $logisticsInfo
* @return float|int
*/
private static function compute_weight($logisticsInfo){
/**
* 计算规则:
* 总重量 - 低于首重;则运费 = 【首重运费】
* 总重量 - 高于首重;则运费 = 首重按照【首重运费】计算;(剩余重量 / 每增加重量 * 续重运费
*/
$amount = (int)0;
$firstWeight = (float)$logisticsInfo['first_weight'];// 首重
$firstWeightFee = (float)$logisticsInfo['first_weight_fee'];// 首重运费
$addWeight = (float)$logisticsInfo['add_weight'];// 每增加重量
$continuationWeightFee = (float)$logisticsInfo['continuation_weight_fee'];// 续重运费
// 循环处理商品
foreach(self::$productsList as $productInfo){
// 计算体积重 并且取数值大的作为重量计算
$volumeWeight = $productInfo['volume_weight'] / $logisticsInfo['throwing_ratio'];
$volumeWeight = (float)sprintf("%.2f",Weight::convert($volumeWeight,$productInfo['weight_class']));
$sumWeight = $productInfo['sum_weight'] > $volumeWeight ? $productInfo['sum_weight'] : $volumeWeight;
// 首重运费
$amount += (float)sprintf("%.2f",$firstWeightFee);
// 判断:如果总重量超过首重 则计算续重运费
$surplus = (float)$sumWeight - $firstWeight;
if($surplus > 0) $amount += (float)sprintf("%.2f",(ceil($surplus / $addWeight)) * $continuationWeightFee);
}
return $amount;
}
/**
* Common: 运费计算 - 物流运费 - 运费计算(按数量计费)
* Author: wu-hui
* Time: 2023/08/22 14:47
* @param $logisticsInfo
* @return float|int
*/
private static function compute_num($logisticsInfo){
/**
* 计算规则:
* 购买数量 - 低于首件数量;则运费 = 【首件运费】
* 购买数量 - 高于首件数量;则运费 = 首件数量按照【首件运费】计算;(剩余购买数量 / 每增加件数量 * 续件运费
*/
$amount = (int)0;
$firstWeight = (int)$logisticsInfo['first_weight'];// 首件数量
$firstWeightFee = (float)$logisticsInfo['first_weight_fee'];// 首件运费
$addWeight = (int)$logisticsInfo['add_weight'];// 每增加件
$continuationWeightFee = (float)$logisticsInfo['continuation_weight_fee'];// 续件运费
// 循环处理商品
foreach(self::$productsList as $productInfo){
// 首件运费
$amount += (float)sprintf("%.2f",$firstWeightFee);
// 判断:如果数量超过首件数量 则计算续件运费
$surplus = (int)$productInfo['sum_quantity'] - $firstWeight;
if($surplus > 0) $amount += (float)sprintf("%.2f",($surplus / $addWeight) * $continuationWeightFee);
}
return $amount;
}
/**
* Common: 运费计算 - 物流运费 - 运费计算(卖家包邮)
* Author: wu-hui
* Time: 2023/08/22 11:59
* @param $logisticsInfo
* @return int
*/
private static function compute_free($logisticsInfo){
return 0;
}
/**
* Common: 刷新当前选择国家信息
* Author: wu-hui
* Time: 2023/08/28 14:50
*/
private static function refreshCurrentCountry($countryId){
$newCountry = Country::query()->where('id',$countryId)->first();
self::$country = $newCountry ? $newCountry->toArray() : [];
}
/** /**
* 通过配送方式获取插件编码 * 通过配送方式获取插件编码

View File

@ -0,0 +1,22 @@
<?php
namespace Beike\Shop\Services\TotalServices;
use Beike\Shop\Services\CheckoutService;
class SumQuantityService{
public static function getTotal(CheckoutService $checkout): ?array{
$totalService = $checkout->totalService;
$sumQuantity = $totalService->countProducts();
$totalData = [
'code' => 'sum_quantity',
'title' => trans('admin/dashboard.product_total'),
'amount' => $sumQuantity,
'amount_format' => $sumQuantity,
];
$totalService->totals[] = $totalData;
return $totalData;
}
}

449
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class LogisticsFactory extends Factory{
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'id' => 0,
'image' => 'path/to/image.jpg',
'video' => '',
'sort_order' => 0,
'status' => true,
'variable' => '',
];
}
}

39
index.html Normal file
View File

@ -0,0 +1,39 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>恭喜,站点创建成功!</title>
<style>
.container {
width: 60%;
margin: 10% auto 0;
background-color: #f0f0f0;
padding: 2% 5%;
border-radius: 10px
}
ul {
padding-left: 20px;
}
ul li {
line-height: 2.3
}
a {
color: #20a53a
}
</style>
</head>
<body>
<div class="container">
<h1>恭喜, 站点创建成功!</h1>
<h3>这是默认index.html本页面由系统自动生成</h3>
<ul>
<li>本页面在FTP根目录下的index.html</li>
<li>您可以修改、删除或覆盖本页面</li>
<li>FTP相关信息请到“面板系统后台 > FTP” 查看</li>
</ul>
</div>
</body>
</html>

7572
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@
"devDependencies": { "devDependencies": {
"axios": "^0.21", "axios": "^0.21",
"bootstrap": "^5.2.2", "bootstrap": "^5.2.2",
"laravel-mix": "^6.0.6", "laravel-mix": "^6.0.49",
"lodash": "^4.17.19", "lodash": "^4.17.19",
"resolve-url-loader": "^4.0.0", "resolve-url-loader": "^4.0.0",
"sass": "^1.38.1", "sass": "^1.38.1",

BIN
placeholder-100x100.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

8
plugins/.gitignore vendored
View File

@ -1,8 +0,0 @@
*
!Bestseller
!FlatShipping
!LatestProducts
!Openai
!Paypal
!Social
!Stripe

View File

@ -53,13 +53,13 @@ class Bootstrap
$amount = $totalService->amount; $amount = $totalService->amount;
$shippingType = plugin_setting('flat_shipping.type', 'fixed'); $shippingType = plugin_setting('flat_shipping.type', 'fixed');
$shippingValue = plugin_setting('flat_shipping.value', 0); $shippingValue = plugin_setting('flat_shipping.value', 0);
if ($shippingType == 'fixed') { $sumQuantity = $totalService->countProducts();
return $shippingValue; if($sumQuantity > 0){
} elseif ($shippingType == 'percent') { if ($shippingType == 'fixed') return $shippingValue;
return $amount * $shippingValue / 100; elseif ($shippingType == 'percent') return $amount * $shippingValue / 100;
} }
return 0;
return 0;
} }
} }

View File

@ -29,7 +29,7 @@ class MenusController extends Controller
{ {
$products = ProductRepo::getBuilder( $products = ProductRepo::getBuilder(
[ [
'active' => 1, // 'active' => 1,
'sort' => 'created_at', 'sort' => 'created_at',
'order' => 'desc', 'order' => 'desc',
]) ])

View File

@ -1,7 +0,0 @@
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?s=/$1 [QSA,PT,L]
</IfModule>

1887
public/build/beike/admin/css/app.css vendored Normal file

File diff suppressed because it is too large Load Diff

18638
public/build/beike/admin/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

649
public/build/beike/admin/css/design.css vendored Normal file
View File

@ -0,0 +1,649 @@
@charset "UTF-8";
/**
* @copyright 2022 beikeshop.com - All Rights Reserved.
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-08-09 10:53:07
* @LastEditTime 2022-09-16 19:07:03
*/
@font-face {
font-family: "iconfont";
src: url("/fonts/design/iconfont.woff") format("woff"), url("/fonts/design/iconfont.ttf") format("truetype"); /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
}
body.page-design {
background-color: #fff;
padding: 0;
margin: 0;
font-size: 14px;
height: 100vh;
overflow: hidden;
}
body.page-design .iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0;
-moz-osx-font-smoothing: grayscale;
}
body.page-design [class*=" el-icon-"], body.page-design [class^=el-icon-] {
font-weight: 600;
}
body.page-design .el-tabs__header {
margin-bottom: 0;
}
body.page-design .tag {
margin: 8px 0;
color: #777;
font-size: 12px;
}
body.page-design .icon-rank {
cursor: move;
}
body.page-design .design-box {
display: flex;
height: 100vh;
}
body.page-design .design-box .design-head {
display: flex;
align-items: center;
justify-content: space-between;
}
body.page-design .design-box .design-head > div {
flex: 1;
height: 40px;
color: #fff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
background-color: #0072ff;
text-align: center;
font-size: 0.8rem;
transition: all 0.2s ease-in-out;
border-right: 1px solid #5692ff;
}
body.page-design .design-box .design-head > div:last-of-type {
border-right: none;
}
body.page-design .design-box .design-head > div i {
margin-right: 7px;
}
body.page-design .design-box .design-head > div:hover {
background-color: #005bcc;
}
body.page-design .design-box .sidebar-edit-wrap {
width: 300px;
background-color: #fff;
border-right: 1px solid #eee;
}
body.page-design .design-box .sidebar-edit-wrap .module-edit {
padding: 0 10px 14px;
overflow-y: auto;
height: 100%;
}
body.page-design .design-box .sidebar-edit-wrap .module-edit .module-editor-row {
height: 47px;
line-height: 47px;
background-color: #f5f5f5;
padding: 0 20px;
margin: 0 -14px 14px;
font-size: 16px;
color: #212121;
}
body.page-design .design-box .sidebar-edit-wrap .module-edit .module-edit-group {
margin-bottom: 20px;
}
body.page-design .design-box .sidebar-edit-wrap .module-edit .module-edit-group:last-of-type {
border-bottom: none;
}
body.page-design .design-box .sidebar-edit-wrap .module-edit .module-edit-group .module-edit-title {
margin-bottom: 10px;
position: relative;
padding-left: 6px;
display: flex;
justify-content: space-between;
}
body.page-design .design-box .sidebar-edit-wrap .module-edit .module-edit-group .module-edit-title:before {
content: "";
position: absolute;
left: 0;
top: 3px;
width: 2px;
height: 14px;
background: #0072ff;
}
body.page-design .design-box .sidebar-edit-wrap .modules-list {
background: #e6e9ec;
height: 100%;
overflow-y: auto;
padding: 0 3px 30px;
}
body.page-design .design-box .sidebar-edit-wrap .module-list {
text-align: center;
padding: 4px;
cursor: pointer;
}
body.page-design .design-box .sidebar-edit-wrap .module-list .module-info {
background: #fff;
color: #556068;
transition: all 0.25s ease-in-out;
}
body.page-design .design-box .sidebar-edit-wrap .module-list .module-info:hover {
color: #0072ff;
box-shadow: 0 6px 23px rgba(0, 0, 0, 0.2);
}
body.page-design .design-box .sidebar-edit-wrap .module-list .icon {
padding: 12px 0 7px;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
}
body.page-design .design-box .sidebar-edit-wrap .module-list .icon .img-icon {
width: 36px;
height: 36px;
}
body.page-design .design-box .sidebar-edit-wrap .module-list .icon .img-icon img {
max-width: 100%;
max-height: auto;
}
body.page-design .design-box .sidebar-edit-wrap .module-list .icon i {
font-size: 26px;
}
body.page-design .design-box .sidebar-edit-wrap .module-list .name {
font-size: 12px;
height: 27px;
overflow: hidden;
}
body.page-design .design-box .preview-iframe {
flex: 1;
}
body.page-design .pb-images-selector {
border: 1px solid #eee;
margin-bottom: 10px;
border-radius: 2px;
}
body.page-design .pb-images-selector:hover {
border-color: #ddd;
}
body.page-design .pb-images-selector:hover .selector-head {
background: #eee;
}
body.page-design .pb-images-selector .pb-images-selector-add {
width: 100%;
margin-top: 16px;
padding: 10px 20px;
}
body.page-design .pb-images-selector .selector-head {
display: flex;
align-items: center;
background-color: #f5f5f5;
padding: 4px 10px;
cursor: pointer;
justify-content: space-between;
}
body.page-design .pb-images-selector .selector-head > div.left {
display: flex;
align-items: center;
}
body.page-design .pb-images-selector .selector-head > div.left i {
margin-right: 10px;
}
body.page-design .pb-images-selector .selector-head > div.left img {
width: 24px;
}
body.page-design .pb-images-selector .selector-head > div.right {
display: flex;
align-items: center;
}
body.page-design .pb-images-selector .selector-head > div.right i {
color: #999;
font-size: 20px;
}
body.page-design .pb-images-selector .selector-head > div.right .remove-item {
margin-right: 8px;
padding-right: 8px;
position: relative;
}
body.page-design .pb-images-selector .selector-head > div.right .remove-item:after {
content: "";
border-right: 1px solid #ccc;
position: absolute;
right: 1px;
top: 20%;
height: 60%;
}
body.page-design .pb-images-selector .selector-head > div.right .remove-item i {
font-size: 15px;
}
body.page-design .pb-images-selector .pb-images-list {
padding: 7px;
padding-bottom: 8px;
position: relative;
display: none;
}
body.page-design .pb-images-selector .pb-images-list.active {
display: block;
}
body.page-design .pb-images-selector .pb-images-list .remove-item {
margin-top: 20px;
background: #ffc8c8;
color: #c70000;
z-index: 9;
height: 20px;
line-height: 20px;
padding: 5px 10px;
text-align: center;
cursor: pointer;
}
body.page-design .pb-images-selector .pb-images-list .remove-item i {
font-size: 14px;
}
body.page-design .pb-images-selector .pb-images-list .pb-image-selector {
cursor: pointer;
min-width: 50px;
min-height: 50px;
}
body.page-design .pb-images-selector .pb-images-list .pb-images-btns button {
margin-left: 0 !important;
padding: 9px 10px;
}
body.page-design .pb-images-selector .pb-images-list .el-input-group__prepend {
padding: 0 10px;
}
body.page-design .module-editor-tab-product-template .tab-info {
margin-top: 10px;
padding: 10px;
background: #f2f2f2;
border-radius: 6px;
}
body.page-design .module-editor-tab-product-template .manufacturer-list > div {
margin-bottom: 10px;
border: 1px solid #f4f4f4;
padding: 5px;
border-radius: 6px;
background: #f4f4f4;
padding-bottom: 8px;
position: relative;
}
body.page-design .module-editor-tab-product-template .manufacturer-list > div:hover .remove-btn {
display: block;
}
body.page-design .module-editor-tab-product-template .manufacturer-list > div .remove-btn {
position: absolute;
display: none;
top: 0;
right: 0;
background: red;
color: #fff;
z-index: 9;
padding: 0 2px;
cursor: pointer;
border-radius: 0 0 0 4px;
}
body.page-design .module-editor-tab-product-template .manufacturer-list > div .remove-btn:hover {
background: #c70000;
}
body.page-design .module-editor-tab-product-template .add-category {
display: flex;
justify-content: flex-end;
}
body.page-design .module-editor-tab-product-template .manufacturers .module-edit-group {
margin-top: 5px;
}
body.page-design .module-editor-tab-product-template .tab-edit-category > .el-tabs__header > .el-tabs__nav-wrap .el-tabs__item {
padding: 0 10px !important;
}
body.page-design .module-editor-tab-product-template .autocomplete-group-wrapper .item-group-wrapper {
background: #fff;
}
body.page-design .autocomplete-group-wrapper .inline-input {
margin-bottom: 10px;
width: 100%;
}
body.page-design .autocomplete-group-wrapper .item-group-wrapper {
padding: 10px;
min-height: 230px;
overflow: auto;
background-color: #f5f5f5;
border-radius: 6px;
}
body.page-design .autocomplete-group-wrapper .item-group-wrapper .item {
overflow: hidden;
position: relative;
padding: 5px 8px;
margin-bottom: 4px;
background: #fff;
border: 1px solid #eee;
cursor: move;
display: flex;
align-items: center;
justify-content: space-between;
}
body.page-design .autocomplete-group-wrapper .item-group-wrapper .item:hover {
border-color: #aaa;
}
body.page-design .autocomplete-group-wrapper .item-group-wrapper .item div {
display: flex;
line-height: 1;
width: calc(100% - 16px);
align-items: center;
}
body.page-design .autocomplete-group-wrapper .item-group-wrapper .item div i {
margin-right: 4px;
}
body.page-design .autocomplete-group-wrapper .item-group-wrapper .item span {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
body.page-design .autocomplete-group-wrapper .item-group-wrapper .item i {
color: #999;
font-weight: 400;
}
body.page-design .autocomplete-group-wrapper .item-group-wrapper .item i.right {
cursor: pointer;
}
body.page-design .autocomplete-group-wrapper .item-group-wrapper .item i.right:hover {
color: #222;
}
.footer-link-item {
padding: 6px 10px;
background: #f5f5f5;
margin-bottom: 10px;
position: relative;
}
.footer-link-item:hover .remove-item {
display: block;
}
.footer-link-item .icon-rank {
position: absolute;
top: 11px;
left: 10px;
z-index: 9;
}
.footer-link-item .link-selector-wrap > .title {
padding-left: 20px;
}
.footer-link-item .remove-item {
position: absolute;
display: none;
top: 0;
right: 0;
background: red;
color: #fff;
z-index: 9;
padding: 0 4px;
cursor: pointer;
border-radius: 0 0 0 4px;
}
.footer-link-item .remove-item:hover {
background: #c70000;
}
.file-manager-box .layui-layer-title {
background-color: #293042;
color: #fff;
border-color: #404e72;
}
.file-manager-box .layui-layer-ico {
background: url("/image/close.png") no-repeat;
background-size: cover;
background-position: center center;
}
.link-selector-wrap > .title {
margin-bottom: 6px;
position: relative;
font-size: 12px;
}
.link-selector-wrap > .title i {
margin-right: 4px;
color: #0072ff;
}
.link-selector-wrap .selector-type {
position: relative;
outline: none;
}
.link-selector-wrap .selector-type .title {
border: 1px solid #eee;
padding: 6px 16px 6px 6px;
font-size: 12px;
cursor: pointer;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
border-radius: 2px;
background-color: #fff;
}
.link-selector-wrap .selector-type .title:hover {
border-color: #ddd;
}
.link-selector-wrap .selector-type .title:before {
content: "\f282";
font-family: "bootstrap-icons";
position: absolute;
right: 10px;
top: 8px;
}
.link-selector-wrap .selector-type .selector-content {
position: absolute;
z-index: 999;
top: calc(100% + 2px);
border-radius: 2px;
left: 0;
width: 100%;
background-color: #fff;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.25);
display: none;
}
.link-selector-wrap .selector-type .selector-content.active {
display: block;
}
.link-selector-wrap .selector-type .selector-content > div {
padding: 6px 10px;
cursor: pointer;
}
.link-selector-wrap .selector-type .selector-content > div:hover {
background-color: #e0fcf6;
}
.link-dialog-box .el-dialog__header {
padding: 0;
}
.link-dialog-box .el-dialog__header .el-dialog__headerbtn {
top: 14px;
font-size: 20px;
}
.link-dialog-box .el-dialog__header .el-dialog__headerbtn i {
color: #fff;
}
.link-dialog-box .el-dialog__body {
padding-bottom: 10px;
}
.link-dialog-box .el-dialog__footer .el-button {
padding: 10px 20px;
min-width: 80px;
}
.link-dialog-box .link-dialog-header {
padding: 10px 20px;
background-color: #409eff;
}
.link-dialog-box .link-dialog-header .title {
font-weight: bold;
color: #fff;
font-size: 16px;
}
.link-dialog-box .link-dialog-header div.input-with-select {
margin-top: 16px;
display: flex;
align-items: center;
}
.link-dialog-box .link-dialog-header div.input-with-select input {
height: 34px;
border: none;
border-radius: 4px 0 0 4px;
flex: 1;
padding: 0 10px;
outline: none;
}
.link-dialog-box .link-dialog-header div.input-with-select button {
color: #333;
border: none;
background-color: #eee;
height: 34px;
line-height: 36px;
border-radius: 0 4px 4px 0;
padding: 0 14px;
overflow: hidden;
}
.link-dialog-box .link-dialog-content .product-search {
margin: -30px -20px 10px;
padding: 6px 20px;
text-align: left;
background-color: #f3f4f7;
display: flex;
justify-content: space-between;
align-items: center;
}
.link-dialog-box .link-dialog-content .product-search > a {
border: 1px solid #ccc;
background-color: #fff;
padding: 0 10px;
border-radius: 4px;
height: 26px;
line-height: 26px;
color: #333;
text-decoration: none;
}
.link-dialog-box .link-dialog-content .product-search .el-input-group__append {
background-color: #0072ff;
color: #fff;
margin-top: -1px;
}
.link-dialog-box .link-dialog-content .product-info {
height: 340px;
}
.link-dialog-box .link-dialog-content .product-info .product-info-title {
background-color: #dee1e9;
display: flex;
justify-content: space-between;
padding: 6px 20px 6px 38px;
text-align: left;
color: #30344f;
font-size: 14px;
}
.link-dialog-box .link-dialog-content .product-info .product-list {
padding-left: 0;
list-style: none;
margin-top: 4px;
margin-bottom: 0;
height: 314px;
overflow-y: auto;
}
.link-dialog-box .link-dialog-content .product-info .product-list.static {
height: 340px;
}
.link-dialog-box .link-dialog-content .product-info .product-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 10px;
border-bottom: 1px solid #eee;
}
.link-dialog-box .link-dialog-content .product-info .product-list li:not(.no-status) {
cursor: pointer;
}
.link-dialog-box .link-dialog-content .product-info .product-list li:not(.no-status):hover {
background-color: #e0fcf6;
}
.link-dialog-box .link-dialog-content .product-info .product-list li .left {
flex: 1;
display: flex;
align-items: center;
margin-right: 20px;
}
.link-dialog-box .link-dialog-content .product-info .product-list li .left .checkbox-plus {
margin-right: 12px;
flex: 0 0 14px;
height: 14px;
border: 1px solid #ddd;
border-radius: 50%;
display: flex;
position: relative;
justify-content: center;
align-items: center;
}
.link-dialog-box .link-dialog-content .product-info .product-list li .left .checkbox-plus:not(.no-status) {
cursor: pointer;
}
.link-dialog-box .link-dialog-content .product-info .product-list li .left .checkbox-plus:before {
content: "";
width: 10px;
height: 10px;
background-color: #0072ff;
border-radius: 50%;
display: none;
}
.link-dialog-box .link-dialog-content .product-info .product-list li .left .checkbox-plus.active {
border-color: #0072ff;
box-shadow: 0px 0px 4px #0072ff;
}
.link-dialog-box .link-dialog-content .product-info .product-list li .left .checkbox-plus.active:before {
display: block;
}
.link-dialog-box .link-dialog-content .product-info .product-list li .left .checkbox-plus.no-status {
background-color: #ddd;
}
.link-dialog-box .link-dialog-content .product-info .product-list li .left > div {
display: -webkit-box;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-all;
overflow: hidden;
}
.link-dialog-box .link-dialog-content .product-info .product-list li .right {
border: 1px solid #ddd;
border-radius: 2px;
padding: 2px 6px;
color: #aaa;
font-size: 12px;
background-color: #fff;
}
.link-dialog-box .link-dialog-content .product-info .product-list li .right.ok {
color: #52c41a;
border-color: #b7eb8f;
}
.link-dialog-box .link-dialog-content .product-info .product-list li img {
width: 46px;
margin-right: 10px;
}
.link-dialog-box .link-dialog-content .product-info-no {
text-align: center;
font-size: 14px;
}
.link-dialog-box .link-dialog-content .product-info-no > div {
display: block;
}
.link-dialog-box .link-dialog-content .product-info-no .icon {
margin: 50px 0 20px;
}
.link-dialog-box .link-dialog-content .product-info-no .icon i {
font-size: 100px;
color: #8c8c8c;
}
.link-dialog-box .link-dialog-content .product-info-no .no-text {
font-size: 16px;
}
.link-dialog-box .link-dialog-content .product-info-no a {
color: #0072ff;
text-decoration: underline;
}
.link-dialog-box .el-dialog__footer {
background-color: #f6f6f6;
padding: 10px 20px;
}

View File

@ -0,0 +1,288 @@
@charset "UTF-8";
/**
* @copyright 2022 beikeshop.com - All Rights Reserved.
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-08-02 19:19:52
* @LastEditTime 2022-09-16 19:06:56
*/
[v-cloak] {
display: none;
}
body.page-filemanager {
height: 100vh;
overflow: hidden;
font-size: 12px;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none; /* CSS3属性 */
}
body.page-filemanager [class*=" el-icon-"], body.page-filemanager [class^=el-icon-] {
font-weight: 600;
}
body.page-filemanager .filemanager-wrap {
display: flex;
height: 100vh;
position: relative;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar {
width: 20%;
background-color: #293042;
overflow-y: auto;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar::-webkit-scrollbar {
width: 2px;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar::-webkit-scrollbar-thumb {
background: #409EFF;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar::-webkit-scrollbar-track {
background: transparent;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar .el-tree {
background-color: transparent;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar .el-tree .el-tree-node__content {
color: #eee;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar .el-tree .el-tree-node__content:hover {
background-color: #434d66;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
background-color: #434d66;
color: #fff;
border-left: 2px solid #409EFF;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar .tree-wrap .el-tree-node.is-current > .el-tree-node__content .right {
display: block;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar .tree-wrap .el-tree-node__content {
height: 32px;
background-color: transparent;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar .tree-wrap .el-tree-node__content:hover .right {
display: block;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar .tree-wrap .custom-tree-node {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar .tree-wrap .custom-tree-node .right {
display: none;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar .tree-wrap .custom-tree-node .right span {
margin-right: 6px;
}
body.page-filemanager .filemanager-wrap .filemanager-navbar .tree-wrap .custom-tree-node .right span:hover {
color: #409EFF;
}
body.page-filemanager .filemanager-wrap .filemanager-divider {
top: 0;
width: 4px;
cursor: col-resize;
}
body.page-filemanager .filemanager-wrap .filemanager-divider:hover {
background: #409EFF;
}
body.page-filemanager .filemanager-wrap .filemanager-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-head {
height: 56px;
position: relative;
display: flex;
background-color: #fff;
align-items: center;
justify-content: space-between;
padding: 0 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-head .left a {
margin-right: 36px;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-head .left a:not(.is-disabled) {
color: #17191c;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-head .left a.is-disabled i {
color: #a6d2ff;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-head .left a i {
color: #409EFF;
font-weight: 600;
}
@media (max-width: 768px) {
body.page-filemanager .filemanager-wrap .filemanager-content .content-head {
height: 140px;
display: block;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-head .left {
margin-bottom: 5px;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-head .left a {
margin-right: 25px;
}
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center {
height: calc(100% - 56px);
background: #f7f9fc;
padding: 16px 6px;
overflow-y: auto;
align-content: flex-start;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center::-webkit-scrollbar {
width: 8px;
height: 1px;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center::-webkit-scrollbar-thumb {
border-radius: 4px;
background: #ccc;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center::-webkit-scrollbar-track {
background: transparent;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center .image-list {
display: inline-block;
background: #fff;
margin: 0 8px 16px;
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.07);
cursor: pointer;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center .image-list .img {
width: 137px;
height: 137px;
display: flex;
align-items: center;
justify-content: center;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center .image-list .img img {
max-width: 100%;
max-height: 100%;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center .image-list .img i {
font-size: 86px;
color: #333;
font-weight: 400;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center .image-list.active {
outline: 1px solid #409EFF;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center .image-list .text {
border-top: 1px solid #eee;
font-size: 12px;
width: 137px;
padding: 6px 8px;
display: flex;
align-items: center;
justify-content: space-between;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center .image-list .text span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-center .image-list .text .el-icon-check {
color: #409EFF;
font-size: 18px;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-footer {
height: 56px;
padding: 0 16px;
background-color: #fff;
display: flex;
box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.04);
align-items: center;
justify-content: space-between;
}
@media (max-width: 768px) {
body.page-filemanager .filemanager-wrap .filemanager-content .content-footer {
height: 120px;
padding: 0;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-footer .el-pagination {
white-space: inherit;
padding: 0;
text-align: center;
}
body.page-filemanager .filemanager-wrap .filemanager-content .content-footer .el-pagination__jump {
margin: 0;
margin-top: 10px;
}
}
body.page-filemanager .upload-wrap .el-dialog__body {
padding-top: 10px;
}
body.page-filemanager .upload-wrap .upload-image {
min-height: 200px;
max-height: 300px;
overflow-y: auto;
margin-right: -4px;
padding-right: 6px;
}
body.page-filemanager .upload-wrap .upload-image::-webkit-scrollbar {
width: 8px;
height: 1px;
}
body.page-filemanager .upload-wrap .upload-image::-webkit-scrollbar-thumb {
border-radius: 4px;
background: #ddd;
}
body.page-filemanager .upload-wrap .upload-image .list {
margin-bottom: 12px;
padding-bottom: 14px;
font-size: 12px;
border-bottom: 1px solid #f1f1f1;
}
body.page-filemanager .upload-wrap .upload-image .list .info {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
}
body.page-filemanager .upload-wrap .upload-image .list .name {
color: #111111;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
body.page-filemanager .upload-wrap .upload-image .list .status {
white-space: nowrap;
}
body.page-filemanager .upload-wrap .el-progress-bar__outer {
background-color: #ccc;
}
body.page-filemanager .photos-upload {
overflow: hidden;
}
body.page-filemanager .photos-upload .el-upload {
width: 100%;
display: block;
margin-bottom: 10px;
}
body.page-filemanager .photos-upload .el-loading-spinner {
top: 35%;
}
body.page-filemanager .photos-upload .el-upload-dragger {
width: 100%;
height: auto;
background-color: transparent;
transition: all 0.3s ease-in-out;
}
body.page-filemanager .photos-upload .el-upload-dragger:hover {
border-color: #8874d8;
}
body.page-filemanager .photos-upload .el-upload-dragger .el-icon-upload {
margin: 10px 0;
}
body.page-filemanager .photos-upload .el-upload-dragger .el-upload__text {
margin-bottom: 10px;
color: #aaa;
}
body.page-filemanager .photos-upload input[type=file] {
display: none;
}

3330
public/build/beike/admin/js/app.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2963
public/build/beike/shop/default/js/app.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
*
!.gitignore

View File

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

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