Conflicts:
	themes/default/checkout/success.blade.php
	themes/default/product/product.blade.php
This commit is contained in:
tanxiaoyong 2023-06-14 11:20:17 +08:00
commit b86cee3b3e
325 changed files with 6894 additions and 3889 deletions

View File

@ -63,7 +63,7 @@ class ProductController
* 创建商品
*
* @param ProductRequest $request
* @return array
* @return mixed
*/
public function store(ProductRequest $request)
{
@ -88,7 +88,7 @@ class ProductController
*
* @param ProductRequest $request
* @param Product $product
* @return array
* @return mixed
*/
public function update(ProductRequest $request, Product $product)
{

View File

@ -126,7 +126,7 @@ class FileManagerController extends Controller
$savePath = $request->get('path');
$originName = $file->getClientOriginalName();
$filePath = $file->storeAs($savePath, $originName, 'catalog');
$filePath = (new FileManagerService)->uploadFile($file, $savePath, $originName);
return [
'name' => $originName,

View File

@ -19,7 +19,7 @@ class MultiFilterController extends Controller
{
public function index()
{
$multiFilter = system_setting('base.multi_filter');
$multiFilter = system_setting('base.multi_filter') ?: [];
$multiFilter['attribute'] = $multiFilter['attribute'] ?? [];
if ($attributeIds = $multiFilter['attribute'] ?? []) {

View File

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

View File

@ -158,7 +158,7 @@ class ProductController extends Controller
'tax_classes' => $taxClasses,
'weight_classes' => Weight::getWeightUnits(),
'source' => [
'categories' => CategoryRepo::flatten(locale()),
'categories' => CategoryRepo::flatten(locale(), false),
],
'_redirect' => $this->getRedirect(),
];

View File

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

View File

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

View File

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

View File

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

View File

@ -13,11 +13,13 @@ namespace Beike\Admin\Services;
class FileManagerService
{
private $fileBasePath = '';
protected $fileBasePath = '';
protected $basePath = '';
public function __construct()
{
$this->fileBasePath = public_path('catalog');
$this->fileBasePath = public_path('catalog') . $this->basePath;
}
/**
@ -26,6 +28,7 @@ class FileManagerService
public function getDirectories($baseFolder = '/'): array
{
$currentBasePath = rtrim($this->fileBasePath . $baseFolder, '/');
$directories = glob("{$currentBasePath}/*", GLOB_ONLYDIR);
$result = [];
@ -84,7 +87,7 @@ class FileManagerService
if ($baseName == 'index.html') {
continue;
}
$fileName = str_replace($this->fileBasePath, '', $file);
$fileName = str_replace(public_path('catalog'), '', $file);
if (is_file($file)) {
$images[] = $this->handleImage($fileName, $baseName);
}
@ -114,7 +117,7 @@ class FileManagerService
*/
public function createDirectory($folderName)
{
$catalogFolderPath = "catalog/{$folderName}";
$catalogFolderPath = "catalog{$this->basePath}/{$folderName}";
$folderPath = public_path($catalogFolderPath);
if (is_dir($folderPath)) {
throw new \Exception(trans('admin/file_manager.directory_already_exist'));
@ -130,7 +133,7 @@ class FileManagerService
*/
public function deleteDirectoryOrFile($filePath)
{
$filePath = public_path("catalog/{$filePath}");
$filePath = public_path("catalog{$this->basePath}/{$filePath}");
if (is_dir($filePath)) {
$files = glob($filePath . '/*');
if ($files) {
@ -154,7 +157,7 @@ class FileManagerService
return;
}
foreach ($files as $file) {
$filePath = public_path("catalog/{$basePath}/$file");
$filePath = public_path("catalog{$this->basePath}/{$basePath}/$file");
if (file_exists($filePath)) {
@unlink($filePath);
}
@ -170,7 +173,7 @@ class FileManagerService
*/
public function updateName($originPath, $newPath)
{
$originPath = public_path("catalog/{$originPath}");
$originPath = public_path("catalog{$this->basePath}/{$originPath}");
if (! is_dir($originPath) && ! file_exists($originPath)) {
throw new \Exception(trans('admin/file_manager.target_not_exist'));
}
@ -182,6 +185,13 @@ class FileManagerService
@rename($originPath, $newPath);
}
public function uploadFile($file, $savePath, $originName)
{
$savePath = $this->basePath . $savePath;
return $file->storeAs($savePath, $originName, 'catalog');
}
/**
* 处理文件夹
*
@ -205,7 +215,7 @@ class FileManagerService
*/
private function hasSubFolders($folderPath): bool
{
$path = public_path("catalog/{$folderPath}");
$path = public_path("catalog{$this->basePath}/{$folderPath}");
$subFiles = glob($path . '/*');
foreach ($subFiles as $subFile) {
if (is_dir($subFile)) {
@ -226,12 +236,19 @@ class FileManagerService
*/
private function handleImage($filePath, $baseName): array
{
$path = "catalog{$filePath}";
$path = "catalog{$filePath}";
$realPath = $this->fileBasePath . $filePath;
$mime = '';
if(file_exists($realPath)) {
$mime = mime_content_type($realPath);
}
return [
'path' => $path,
'name' => $baseName,
'origin_url' => image_origin($path),
'mime' => $mime,
'selected' => false,
];
}

View File

@ -11,8 +11,8 @@
return [
'api_url' => env('BEIKE_API_URL', 'https://beikeshop.com'),
'version' => '1.3.6',
'build' => '20230510',
'version' => '1.3.7',
'build' => '20230614',
'admin_name' => env('ADMIN_NAME'),
'force_url_https' => env('APP_FORCE_HTTPS', false),

View File

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

View File

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

View File

@ -11,6 +11,7 @@ use Beike\Repositories\CurrencyRepo;
use Beike\Repositories\LanguageRepo;
use Beike\Services\CurrencyService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Session;
@ -520,25 +521,28 @@ function quantity_format($quantity)
/**
* 返回json序列化结果
*/
function json_success($message, $data = []): array
function json_success($message, $data = [])
{
return [
'status' => 'success',
'message' => $message,
'data' => $data,
];
}
/**
* 返回json序列化结果
*/
function json_fail($message, $data = []): array
function json_fail($message, $data = [], $status = 422): JsonResponse
{
return [
$data = [
'status' => 'fail',
'message' => $message,
'data' => $data,
];
return response()->json($data, $status);
}
if (! function_exists('sub_string')) {

View File

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

View File

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

View File

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

View File

@ -171,6 +171,13 @@ class Plugin implements Arrayable, \ArrayAccess
$item['label'] = trans($languageKey);
}
$descriptionKey = $item['description_key'] ?? '';
$description = $item['description'] ?? '';
if (empty($description) && $descriptionKey) {
$languageKey = "{$this->dirName}::{$descriptionKey}";
$item['description'] = trans($languageKey);
}
return $item;
}

View File

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

View File

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

View File

@ -31,7 +31,7 @@ class OrderRepo
*/
public static function filterAll(array $filters = [])
{
$builder = self::getListBuilder($filters)->orderByDesc('created_at');
$builder = static::getListBuilder($filters)->orderByDesc('created_at');
return $builder->get();
}
@ -44,7 +44,7 @@ class OrderRepo
*/
public static function getListByCustomer($customer): LengthAwarePaginator
{
$builder = self::getListBuilder(['customer' => $customer])->orderByDesc('created_at');
$builder = static::getListBuilder(['customer' => $customer])->orderByDesc('created_at');
return $builder->paginate(perPage());
}
@ -56,7 +56,7 @@ class OrderRepo
*/
public static function getLatestOrders($customer, $limit)
{
return self::getListBuilder(['customer' => $customer])
return static::getListBuilder(['customer' => $customer])
->orderByDesc('created_at')
->take($limit)
->get();
@ -68,7 +68,7 @@ class OrderRepo
*/
public static function filterOrders(array $filters = []): LengthAwarePaginator
{
$builder = self::getListBuilder($filters)->orderByDesc('created_at');
$builder = static::getListBuilder($filters)->orderByDesc('created_at');
return $builder->paginate(perPage());
}
@ -79,7 +79,7 @@ class OrderRepo
*/
public static function getListBuilder(array $filters = []): Builder
{
$builder = Order::query()->with(['orderProducts']);
$builder = Order::query()->with(['orderProducts'])->where('status', '<>', StateMachineService::CREATED);
$number = $filters['number'] ?? 0;
if ($number) {
@ -266,6 +266,8 @@ class OrderRepo
OrderProductRepo::createOrderProducts($order, $carts['carts']);
OrderTotalRepo::createTotals($order, $totals);
hook_filter('repository.order.create.after', ['order' => $order, 'data' => $data]);
return $order;
}

View File

@ -154,13 +154,17 @@ class PageCategoryRepo
}
/**
* @param $page
* @param $pageCategory
* @return string
*/
public static function getName($pageCategoryId)
public static function getName($pageCategory): string
{
// 根据 pageCategoryId 获取 name判断是否存在
$pageCategory = PageCategory::query()->whereHas('description', function ($query) use ($pageCategoryId) {
if ($pageCategory instanceof PageCategory) {
return $pageCategory->description->title ?? '';
}
$pageCategoryId = (int) $pageCategory;
$pageCategory = PageCategory::query()->whereHas('description', function ($query) use ($pageCategoryId) {
$query->where('page_category_id', $pageCategoryId);
})->first();

View File

@ -55,7 +55,7 @@ class ProductRepo
*/
public static function getProductsByCategory($categoryId, $filterData)
{
$builder = self::getBuilder(array_merge(['category_id' => $categoryId, 'active' => 1], $filterData));
$builder = static::getBuilder(array_merge(['category_id' => $categoryId, 'active' => 1], $filterData));
return $builder->with('inCurrentWishlist')
->paginate($filterData['per_page'] ?? perPage())
@ -72,7 +72,7 @@ class ProductRepo
if (! $productIds) {
return ProductSimple::collection(new Collection());
}
$builder = self::getBuilder(['product_ids' => $productIds])->whereHas('masterSku');
$builder = static::getBuilder(['product_ids' => $productIds])->whereHas('masterSku');
$products = $builder->with('inCurrentWishlist')->get();
return ProductSimple::collection($products);
@ -220,7 +220,7 @@ class ProductRepo
public static function getFilterAttribute($data): array
{
$builder = self::getBuilder(array_diff_key($data, ['attr' => '', 'price' => '']))
$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')
@ -274,7 +274,7 @@ class ProductRepo
{
$selectPrice = $data['price'] ?? '-';
// unset($data['price']);
$builder = self::getBuilder(['category_id' => $data['category_id']])->leftJoin('product_skus as ps', 'products.id', 'ps.product_id')
$builder = static::getBuilder(['category_id' => $data['category_id']])->leftJoin('product_skus as ps', 'products.id', 'ps.product_id')
->where('ps.is_default', 1);
$min = $builder->min('ps.price');
$max = $builder->max('ps.price');
@ -293,7 +293,7 @@ class ProductRepo
public static function list($data = [])
{
return self::getBuilder($data)->paginate($data['per_page'] ?? 20);
return static::getBuilder($data)->paginate($data['per_page'] ?? 20);
}
public static function autocomplete($name)
@ -353,7 +353,7 @@ class ProductRepo
}
$items = [];
$products = self::getBuilder()->select('id')->get();
$products = static::getBuilder()->select('id')->get();
foreach ($products as $product) {
$items[$product->id] = [
'id' => $product->id,

View File

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

View File

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

View File

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

View File

@ -33,9 +33,9 @@ class CheckoutController extends Controller
* 更改结算信息
*
* @param Request $request
* @return array
* @return mixed
*/
public function update(Request $request): array
public function update(Request $request): mixed
{
try {
$requestData = $request->all();
@ -56,9 +56,13 @@ class CheckoutController extends Controller
*/
public function confirm()
{
$data = (new CheckoutService)->confirm();
try {
$data = (new CheckoutService)->confirm();
return hook_filter('checkout.confirm.data', $data);
return hook_filter('checkout.confirm.data', $data);
} catch (\Exception $e) {
return json_fail($e->getMessage());
}
}
public function success()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,12 +30,8 @@ class ProductSimple extends JsonResource
throw new \Exception("invalid master sku for product {$this->id}");
}
$name = $this->description->name ?? '';
if ($masterSku && $masterSku->images) {
$images = $masterSku->images;
} else {
$images = $this->images ?? [];
}
$name = $this->description->name ?? '';
$images = $this->images;
$data = [
'id' => $this->id,

View File

@ -56,6 +56,8 @@ Route::prefix('/')
Route::post('login', [LoginController::class, 'store'])->name('login.store');
Route::get('logout', [LogoutController::class, 'index'])->name('logout');
Route::get('orders/{number}', [\Beike\Shop\Http\Controllers\OrderController::class, 'show'])->name('orders.show');
Route::get('page_categories', [PageCategoryController::class, 'home'])->name('page_categories.home');
Route::get('page_categories/{page_category}', [PageCategoryController::class, 'show'])->name('page_categories.show');
Route::get('pages/{page}', [PageController::class, 'show'])->name('pages.show');
@ -80,7 +82,6 @@ Route::prefix('/')
Route::put('checkout', [CheckoutController::class, 'update'])->name('checkout.update');
Route::get('checkout/success', [CheckoutController::class, 'success'])->name('checkout.success');
Route::post('checkout/confirm', [CheckoutController::class, 'confirm'])->name('checkout.confirm');
Route::get('orders/{number}/success', [OrderController::class, 'success'])->name('orders.success');
Route::get('orders/{number}/pay', [OrderController::class, 'pay'])->name('orders.pay');
Route::post('orders/{number}/cancel', [OrderController::class, 'cancel'])->name('orders.cancel');
Route::post('orders/{number}/complete', [OrderController::class, 'complete'])->name('orders.complete');

View File

@ -18,6 +18,8 @@ use Exception;
class CartService
{
private static $cartList = null;
/**
* 获取购物车商品列表
*
@ -27,6 +29,10 @@ class CartService
*/
public static function list($customer, bool $selected = false): array
{
if (self::$cartList !== null) {
return self::$cartList;
}
$cartBuilder = CartRepo::allCartProductsBuilder($customer->id ?? 0);
if ($selected) {
$cartBuilder->where('selected', true);
@ -50,7 +56,9 @@ class CartService
return $description && $product;
});
return CartDetail::collection($cartItems)->jsonSerialize();
$cartList = CartDetail::collection($cartItems)->jsonSerialize();
return self::$cartList = hook_filter('service.cart.list', $cartList);
}
/**
@ -183,6 +191,6 @@ class CartService
'amount_format' => currency_format($amount),
];
return hook_filter('cart.data', $data);
return hook_filter('service.cart.data', $data);
}
}

View File

@ -11,10 +11,10 @@
namespace Beike\Shop\Services;
use Beike\Exceptions\CartException;
use Beike\Libraries\Weight;
use Beike\Models\Address;
use Beike\Models\Country;
use Beike\Models\Customer;
use Beike\Models\Order;
use Beike\Models\Zone;
use Beike\Repositories\AddressRepo;
@ -46,13 +46,11 @@ class CheckoutService
if (is_int($customer) || empty($customer)) {
$this->customer = current_customer();
}
// if (empty($this->customer) || !($this->customer instanceof Customer)) {
// // throw new \Exception(trans('shop/carts.invalid_customer'));
// }
$this->cart = CartRepo::createCart($this->customer);
$this->selectedProducts = CartRepo::selectedCartProducts($this->customer->id ?? 0);
if ($this->selectedProducts->count() == 0) {
throw new \Exception(trans('shop/carts.empty_selected_products'));
throw new CartException(trans('shop/carts.empty_selected_products'));
}
}
@ -96,9 +94,10 @@ class CheckoutService
$this->updateGuestPaymentAddress($guestPaymentAddress);
}
hook_action('service.checkout.update.after', ['request_data' => $requestData, 'cart' => $this->cart]);
hook_action('service.checkout.update.after', ['request_data' => $requestData, 'checkout' => $this]);
$data = $this->checkoutData();
return $this->checkoutData();
return $data;
}
/**
@ -219,6 +218,14 @@ class CheckoutService
$this->cart->update(['payment_method_code' => $paymentMethodCode]);
}
public function initTotalService()
{
$customer = $this->customer;
$totalClass = hook_filter('service.checkout.total_service', 'Beike\Shop\Services\TotalService');
$totalService = (new $totalClass($this->cart, CartService::list($customer, true)));
$this->totalService = $totalService;
}
/**
* 获取结账页数据
*
@ -232,8 +239,10 @@ class CheckoutService
$cartList = CartService::list($customer, true);
$carts = CartService::reloadData($cartList);
$totalService = (new TotalService($currentCart, $cartList));
$this->totalService = $totalService;
if (! $this->totalService) {
$this->initTotalService();
}
$addresses = AddressRepo::listByCustomer($customer);
$shipments = ShippingMethodService::getShippingMethods($this);
@ -265,10 +274,10 @@ class CheckoutService
'shipping_methods' => $shipments,
'payment_methods' => $payments,
'carts' => $carts,
'totals' => $totalService->getTotals($this),
'totals' => $this->totalService->getTotals($this),
];
return hook_filter('checkout.data', $data);
return hook_filter('service.checkout.data', $data);
}
public static function formatAddress($address)

View File

@ -17,17 +17,16 @@ use Illuminate\Support\Str;
class TotalService
{
private const TOTAL_CODES = [
protected const TOTAL_CODES = [
'subtotal',
'tax',
'shipping',
'customer_discount',
'order_total',
];
public Cart $currentCart;
protected Cart $currentCart;
public array $cartProducts;
protected array $cartProducts;
public array $taxes = [];
@ -35,7 +34,7 @@ class TotalService
public float $amount = 0;
public string $shippingMethod = '';
protected string|array $shippingMethod = '';
public function __construct($currentCart, $cartProducts)
{
@ -55,6 +54,11 @@ class TotalService
return $this;
}
public function getShippingMethod(): string
{
return $this->shippingMethod;
}
/**
* 获取税费数据
*
@ -112,11 +116,14 @@ class TotalService
{
$maps = [];
foreach (self::TOTAL_CODES as $code) {
$serviceName = Str::studly($code) . 'Service';
$maps[$code] = "\Beike\\Shop\\Services\\TotalServices\\{$serviceName}";
$serviceName = Str::studly($code) . 'Service';
$maps[$code] = "\Beike\\Shop\\Services\\TotalServices\\{$serviceName}";
}
return hook_filter('service.total.maps', $maps);
$maps = hook_filter('service.total.maps', $maps);
$maps['order_total'] = "\Beike\\Shop\\Services\\TotalServices\\OrderTotalService";
return $maps;
}
/**
@ -130,4 +137,36 @@ class TotalService
return collect($carts)->sum('subtotal');
}
/**
* Get Cart Products
*
* @return array
*/
public function getCartProducts(): array
{
return $this->cartProducts;
}
/**
* Get Current Cart
*
* @return Cart
*/
public function getCurrentCart()
{
return $this->currentCart;
}
/**
* Get Cart Product Amount
*
* @return mixed
*/
public function countProducts(): mixed
{
$cartProducts = $this->getCartProducts();
return collect($cartProducts)->sum('quantity');
}
}

View File

@ -25,7 +25,7 @@ class ShippingService
public static function getTotal(CheckoutService $checkout): ?array
{
$totalService = $checkout->totalService;
$shippingMethod = $totalService->shippingMethod;
$shippingMethod = $totalService->getShippingMethod();
if (empty($shippingMethod)) {
return null;
}

View File

@ -10,6 +10,7 @@
"ext-fileinfo": "*",
"ext-iconv": "*",
"ext-json": "*",
"ext-openssl": "*",
"ext-pdo": "*",
"ext-simplexml": "*",
"ext-zip": "*",

493
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('order_payments', function (Blueprint $table) {
$table->id();
$table->integer('order_id');
$table->string('transaction_id')->nullable();
$table->string('request')->nullable();
$table->string('response')->nullable();
$table->string('callback')->nullable();
$table->string('receipt')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('order_payments');
}
};

View File

@ -15,6 +15,7 @@
namespace Plugin\Paypal\Controllers;
use Beike\Repositories\OrderPaymentRepo;
use Beike\Repositories\OrderRepo;
use Beike\Services\StateMachineService;
use Illuminate\Http\JsonResponse;
@ -96,8 +97,10 @@ class PaypalController
$customer = current_customer();
$order = OrderRepo::getOrderByNumber($orderNumber, $customer);
OrderPaymentRepo::createOrUpdatePayment($order->id, ['request' => $data]);
$paypalOrderId = $data['paypalOrderId'];
$result = $this->paypalClient->capturePaymentOrder($paypalOrderId);
OrderPaymentRepo::createOrUpdatePayment($order->id, ['response' => $result]);
try {
DB::beginTransaction();

View File

@ -11,6 +11,7 @@
namespace Plugin\Stripe\Controllers;
use Beike\Repositories\OrderPaymentRepo;
use Beike\Repositories\OrderRepo;
use Beike\Services\StateMachineService;
use Beike\Shop\Http\Controllers\Controller;
@ -24,6 +25,7 @@ class StripeController extends Controller
*
* @param Request $request
* @return array
* @throws \Throwable
*/
public function capture(Request $request): array
{
@ -32,14 +34,18 @@ class StripeController extends Controller
$customer = current_customer();
$order = OrderRepo::getOrderByNumber($number, $customer);
$creditCardData = $request->all();
$result = (new StripePaymentService($order))->capture($creditCardData);
OrderPaymentRepo::createOrUpdatePayment($order->id, ['request' => $creditCardData]);
$result = (new StripePaymentService($order))->capture($creditCardData);
OrderPaymentRepo::createOrUpdatePayment($order->id, ['response' => $result]);
if ($result) {
StateMachineService::getInstance($order)->changeStatus(StateMachineService::PAID);
StateMachineService::getInstance($order)->setShipment()->changeStatus(StateMachineService::PAID);
return json_success(trans('Stripe::common.capture_success'));
}
return json_success(trans('Stripe::common.capture_fail'));
return json_success(trans('Stripe::common.capture_fail'));
} catch (\Exception $e) {
return json_fail($e->getMessage());

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/vendor/element-ui/index.css vendored Normal file

File diff suppressed because one or more lines are too long

1
public/vendor/element-ui/index.js vendored Normal file

File diff suppressed because one or more lines are too long

2
public/vendor/laydate/laydate.js vendored Executable file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,45 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2013-9-30: Created.
-->
<svg>
<metadata>
Created by iconfont
</metadata>
<defs>
<font id="laydate-icon" horiz-adv-x="1024" >
<font-face
font-family="laydate-icon"
font-weight="500"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="x" unicode="x" horiz-adv-x="1001"
d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
<glyph glyph-name="youyou" unicode="&#58882;" d="M283.648 721.918976 340.873216 780.926976 740.352 383.997952 340.876288-12.925952 283.648 46.077952 619.52 383.997952Z" horiz-adv-x="1024" />
<glyph glyph-name="zuozuo" unicode="&#58883;" d="M740.352 721.918976 683.126784 780.926976 283.648 383.997952 683.123712-12.925952 740.352 46.077952 404.48 383.997952Z" horiz-adv-x="1024" />
<glyph glyph-name="xiayiye" unicode="&#58970;" d="M62.573 384.103l423.401 423.662c18.985 18.985 49.757 18.985 68.727 0 18.982-18.972 18.985-49.746 0-68.729l-355.058-355.067 356.796-356.796c18.977-18.971 18.976-49.746 0-68.727-18.982-18.976-49.751-18.976-68.727 0l-39.753 39.753 0.269 0.246-385.655 385.661zM451.365 384.103l423.407 423.662c18.985 18.985 49.757 18.985 68.727 0 18.982-18.972 18.985-49.746 0-68.729l-355.058-355.067 356.796-356.796c18.977-18.971 18.976-49.746 0-68.727-18.982-18.976-49.757-18.977-68.727 0l-39.762 39.754 0.273 0.249-385.662 385.661zM451.365 384.103z" horiz-adv-x="1024" />
<glyph glyph-name="xiayiye1" unicode="&#58971;" d="M948.066926 382.958838l-411.990051-412.24426c-18.47333-18.47333-48.417689-18.47333-66.875207 0-18.47333 18.461167-18.47333 48.405526 0 66.875207L814.691135 383.088983 467.512212 730.269123c-18.466032 18.458735-18.466032 48.405526 0 66.873991 18.468465 18.464816 48.410391 18.464816 66.872774 0l38.682336-38.682336-0.261507-0.239614 375.259894-375.265975v0.003649m-378.312834 0L157.756743-29.285422c-18.47333-18.47333-48.415256-18.47333-66.872775 0-18.47333 18.461167-18.47333 48.405526 0 66.875207L436.369787 383.088983 89.19208 730.269123c-18.4636 18.458735-18.4636 48.405526 0 66.873991 18.470898 18.464816 48.415256 18.464816 66.872774 0l38.692067-38.682336-0.266372-0.239614 375.267191-375.265975-0.004865 0.003649m0 0z" horiz-adv-x="1024" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

1
public/vendor/video/video-js.min.css vendored Normal file

File diff suppressed because one or more lines are too long

40
public/vendor/video/video.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -171,26 +171,21 @@ body.page-filemanager {
.content-center {
height: calc(100% - 56px);
// display: flex;
// align-items: flex-start;
// flex-wrap: wrap;
// align-items: center; // flex-start | center
background: #f7f9fc;
padding: 16px 6px;
// justify-content: space-between; // flex-end | center | space-between
// flex-wrap: wrap;
// margin-right: -20px;
overflow-y: auto;
align-content: flex-start;
&::-webkit-scrollbar {
width: 4px;
width: 8px;
height: 1px;
}
&::-webkit-scrollbar-thumb {
border-radius: 2px;
background: $primary;
border-radius: 4px;
background: #ccc;
}
&::-webkit-scrollbar-track {
background: transparent;
}
@ -199,27 +194,10 @@ body.page-filemanager {
// display: flex;
// flex-direction: column;
display: inline-block;
// align-items: center;
// margin-bottom: 20px;
background: #fff;
margin: 0 8px 16px;
box-shadow: 0 0 2px 1px rgba(0, 0, 0, .07);
cursor: pointer;
// width: calc(20% - 20px);
// @media (min-width: 600px) {
// width: calc(25% - 20px);
// }
// @media (min-width: 1000px) {
// width: calc(20% - 20px);
// }
// @media (min-width: 1300px) {
// width: calc(16.666% - 20px);
// }
// border: 1px solid transparent;
.img {
width: 137px;
@ -232,6 +210,12 @@ body.page-filemanager {
max-width: 100%;
max-height: 100%;
}
i {
font-size: 86px;
color: #333;
font-weight: 400;
}
}
&.active {
@ -295,22 +279,34 @@ body.page-filemanager {
}
.upload-image {
height: 200px;
min-height: 200px;
max-height: 300px;
overflow-y: auto;
margin-right: -4px;
padding-right: 6px;
// 滚动条 透明背景
&::-webkit-scrollbar {
width: 8px;
height: 1px;
}
&::-webkit-scrollbar-thumb {
border-radius: 4px;
background: #ddd;
}
.list {
background-color: #f2f2f2;
padding: 6px 10px;
border-radius: 4px;
margin-bottom: 8px;
margin-bottom: 12px;
padding-bottom: 14px;
font-size: 12px;
border-bottom: 1px solid #f1f1f1;
.info {
display: flex;
align-items: center; // flex-start | center
justify-content: space-between; // flex-end | center | space-between
margin-bottom: 6px;
}
.name {

View File

@ -3,7 +3,7 @@
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-08-26 18:18:22
* @LastEditTime 2023-02-08 15:35:25
* @LastEditTime 2023-06-08 19:18:24
*/
import http from "../../../js/http";
@ -92,23 +92,20 @@ const tinymceInit = () => {
toolbar_mode: 'wrap',
font_formats:
"微软雅黑='Microsoft YaHei';黑体=黑体;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Georgia=georgia,palatino;Helvetica=helvetica;Times New Roman=times new roman,times;Verdana=verdana,geneva",
fontsize_formats: "10px 12px 14px 18px 24px 36px",
fontsize_formats: "10px 12px 14px 18px 24px 36px 48px 56px 72px 96px",
lineheight_formats: "1 1.1 1.2 1.3 1.4 1.5 1.7 2.4 3 4",
setup: function(ed) {
const height = ed.getElement().dataset.tinymceHeight;
// console.log(ed);
// 修改 tinymce 的高度
// if (height) {
// ed.theme.resizeTo(null, height);
// }
ed.ui.registry.addButton('toolbarImageButton',{
// text: '',
icon: 'image',
onAction:function() {
bk.fileManagerIframe(images => {
if (images.length) {
images.forEach(e => {
ed.insertContent(`<img src='/${e.path}' class="img-fluid" />`);
if (e.mime == 'video/mp4') {
ed.insertContent(`<video src='/${e.path}' controls loop muted class="img-fluid" />`);
} else {
ed.insertContent(`<img src='/${e.path}' class="img-fluid" />`);
}
});
}
})

View File

@ -3,7 +3,7 @@
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-08-17 15:42:46
* @LastEditTime 2023-04-11 09:28:34
* @LastEditTime 2023-06-13 17:27:49
*/
// Example starter JavaScript for disabling form submissions if there are invalid fields
@ -33,12 +33,13 @@ $(function () {
form.classList.add("was-validated");
$('.nav-link, .nav-item').removeClass('error-invalid');
$('.invalid-feedback').hide();
// 如果错误输入框在 tab 页面,则高亮显示对应的选项卡
$('.invalid-feedback').each(function(index, el) {
if ($(el).css('display') == 'block') {
layer.msg(lang.error_form, () => {});
// 兼容使用 element ui input、autocomplete 组件在传统提交报错ui显示
if ($(el).siblings('div[class^="el-"]')) {
$(el).siblings('div[class^="el-"]').find('.el-input__inner').addClass('error-invalid-input')

View File

@ -3,13 +3,14 @@
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-08-22 18:32:26
* @LastEditTime 2023-04-19 15:26:41
* @LastEditTime 2023-06-09 08:53:52
*/
export default {
// 打开文件管理器
fileManagerIframe(callback) {
fileManagerIframe(callback, params) {
const base = document.querySelector('base').href;
params = params ? `?${Object.keys(params).map(key => `${key}=${params[key]}`).join('&')}` : '';
layer.open({
type: 2,
@ -19,7 +20,7 @@ export default {
scrollbar: false,
shade: 0.4,
area: ['1060px', '680px'],
content: `${base}/file_manager`,
content: `${base}/file_manager${params}`,
success: function(layerInstance, index) {
var iframeWindow = window[layerInstance.find("iframe")[0]["name"]];
iframeWindow.callback = function(images) {
@ -145,7 +146,7 @@ export default {
setVipUi(data)
function setVipUi(data) {
if (data.vip) {
if (data && data.vip) {
$('.vip-serve').addClass('active');
if (data.expiring) {

View File

@ -1,5 +1,5 @@
<div class="row g-3 mb-3">
<label for="" class="wp-200 col-form-label text-end {{ isset($required) && $required ? 'required' : '' }}">{{ $title ?? '' }}</label>
<label class="wp-200 col-form-label text-end {{ isset($required) && $required ? 'required' : '' }}">{{ $title ?? '' }}</label>
<div class="col-auto wp-200-">
{{ $slot }}
</div>

View File

@ -9,13 +9,13 @@
<meta name="asset" content="{{ asset('/') }}">
<meta name="editor_language" content="{{ locale() }}">
<script src="{{ asset('vendor/vue/2.7/vue' . (!config('app.debug') ? '.min' : '') . '.js') }}"></script>
<script src="{{ asset('vendor/element-ui/2.15.6/js.js') }}"></script>
<script src="{{ asset('vendor/element-ui/index.js') }}"></script>
<script src="{{ asset('vendor/jquery/jquery-3.6.0.min.js') }}"></script>
<script src="{{ asset('vendor/layer/3.5.1/layer.js') }}"></script>
<script src="{{ asset('vendor/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<script src="{{ asset('vendor/cookie/js.cookie.min.js') }}"></script>
<link href="{{ mix('/build/beike/admin/css/bootstrap.css') }}" rel="stylesheet">
<link rel="stylesheet" href="{{ asset('vendor/element-ui/2.15.6/css.css') }}">
<link rel="stylesheet" href="{{ asset('vendor/element-ui/index.css') }}">
@if (locale() != 'zh_cn')
<script src="{{ asset('vendor/element-ui/language/' . locale() . '.js') }}"></script>
@endif

View File

@ -67,7 +67,7 @@
</el-form-item>
<el-form-item label="{{ __('shop/login.password') }}" :prop="dialog.form.id === null || dialog.form.id == '' ? 'password' : ''">
<el-input v-model="dialog.form.password" placeholder="{{ __('common.password') }}"></el-input>
<el-input v-model="dialog.form.password" placeholder="{{ __('shop/login.password') }}"></el-input>
</el-form-item>
<el-form-item label="{{ __('common.language') }}">

View File

@ -21,7 +21,7 @@
<x-admin-form-input-locale name="descriptions.*.name" title="{{ __('common.name') }}" :value="$descriptions" :required="true" />
<x-admin-form-input-locale name="descriptions.*.content" title="{{ __('admin/builder.modules_content') }}" :value="$descriptions" />
{{-- <x-admin-form-select title="上级分类" name="parent_id" :value="old('parent_id', $category->parent_id ?? 0)" :options="$categories->toArray()" key="id" label="name" /> --}}
<x-admin-form-input name="position" title="{{ __('common.sort_order') }}" :value="old('position', $category->position ?? 0)" />
<x-admin::form.row title="{{ __('admin/category.parent_category') }}">
@php

View File

@ -40,7 +40,7 @@
<thead>
<tr>
<th>ID</th>
<th>{{ __('common.name') }}</th>
<th>{{ __('address.name') }}</th>
<th>{{ __('common.phone') }}</th>
<th>{{ __('common.created_at') }}</th>
<th>{{ __('common.action') }}</th>
@ -76,7 +76,7 @@
<el-dialog title="{{ __('admin/customer.edit_address') }}" :visible.sync="dialogAddress.show" width="650px"
@close="closeAddressDialog('addressForm')">
<el-form ref="addressForm" :rules="addressRules" :model="dialogAddress.form" label-width="100px">
<el-form-item label="{{ __('common.name') }}" prop="name">
<el-form-item label="{{ __('address.name') }}" prop="name">
<el-input v-model="dialogAddress.form.name"></el-input>
</el-form-item>
<el-form-item label="{{ __('common.phone') }}" prop="phone">

View File

@ -104,21 +104,21 @@ Vue.component('rich-text-i18n', {
toolbar_mode: 'wrap',
font_formats:
"微软雅黑='Microsoft YaHei';黑体=黑体;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Georgia=georgia,palatino;Helvetica=helvetica;Times New Roman=times new roman,times;Verdana=verdana,geneva",
fontsize_formats: "10px 12px 14px 18px 24px 36px",
fontsize_formats: "10px 12px 14px 18px 24px 36px 48px 56px 72px 96px",
lineheight_formats: "1 1.1 1.2 1.3 1.4 1.5 1.7 2.4 3 4",
relative_urls : true,
// init_instance_callback: function (ed) {
// let code = ed.getElement().dataset.code
// ed.setContent(self.value[code])
// },
setup: function(ed) {
ed.ui.registry.addButton('toolbarImageButton', {
// text: '',
icon: 'image',
onAction:function() {
bk.fileManagerIframe(images => {
if (images.length) {
images.forEach(e => {
ed.insertContent(`<img src='/${e.path}' class="img-fluid" />`);
if (e.mime == 'video/mp4') {
ed.insertContent(`<video src='/${e.path}' controls loop muted class="img-fluid" />`);
} else {
ed.insertContent(`<img src='/${e.path}' class="img-fluid" />`);
}
});
}
})

View File

@ -17,8 +17,8 @@
<script src="{{ asset('vendor/vue/Sortable.min.js') }}"></script>
<script src="{{ asset('vendor/vue/vuedraggable.js') }}"></script>
<script src="{{ asset('vendor/tinymce/5.9.1/tinymce.min.js') }}"></script>
<script src="{{ asset('vendor/element-ui/2.15.6/js.js') }}"></script>
<link rel="stylesheet" href="{{ asset('vendor/element-ui/2.15.6/css.css') }}">
<script src="{{ asset('vendor/element-ui/index.js') }}"></script>
<link rel="stylesheet" href="{{ asset('vendor/element-ui/index.css') }}">
<link rel="stylesheet" type="text/css" href="{{ asset('/build/beike/admin/css/design.css') }}">
@stack('header')
<script>

View File

@ -18,8 +18,8 @@
<script src="{{ asset('vendor/vue/Sortable.min.js') }}"></script>
<script src="{{ asset('vendor/vue/vuedraggable.js') }}"></script>
<script src="{{ asset('vendor/tinymce/5.9.1/tinymce.min.js') }}"></script>
<script src="{{ asset('vendor/element-ui/2.15.6/js.js') }}"></script>
<link rel="stylesheet" href="{{ asset('vendor/element-ui/2.15.6/css.css') }}">
<script src="{{ asset('vendor/element-ui/index.js') }}"></script>
<link rel="stylesheet" href="{{ asset('vendor/element-ui/index.css') }}">
@if (locale() != 'zh_cn')
<script src="{{ asset('vendor/element-ui/language/' . locale() . '.js') }}"></script>
@endif

View File

@ -8,14 +8,13 @@
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="asset" content="{{ asset('/') }}">
<script src="{{ asset('vendor/vue/2.7/vue.js') }}"></script>
<script src="{{ asset('vendor/element-ui/2.15.9/index.js') }}"></script>
{{-- <script src="{{ asset('vendor/element-ui/2.15.6/js.js') }}"></script> --}}
<script src="{{ asset('vendor/element-ui/index.js') }}"></script>
<script src="{{ asset('vendor/cookie/js.cookie.min.js') }}"></script>
<script src="{{ asset('vendor/jquery/jquery-3.6.0.min.js') }}"></script>
<script src="{{ asset('vendor/layer/3.5.1/layer.js') }}"></script>
<script src="{{ asset('vendor/vue/batch_select.js') }}"></script>
<link href="{{ mix('/build/beike/admin/css/bootstrap.css') }}" rel="stylesheet">
<link rel="stylesheet" href="{{ asset('vendor/element-ui/2.15.9/index.css') }}">
<link rel="stylesheet" href="{{ asset('vendor/element-ui/index-blue.css') }}">
<link href="{{ mix('build/beike/admin/css/filemanager.css') }}" rel="stylesheet">
<script src="{{ mix('build/beike/admin/js/app.js') }}"></script>
@if (locale() != 'zh_cn')
@ -74,8 +73,8 @@
<div class="content-head">
<div class="left d-lg-flex">
<el-button class="me-5 mb-1 mb-lg-0" size="small" icon="el-icon-check" type="primary" @click="fileChecked" :disabled="!!!selectImageIndex.length">{{ __('admin/builder.modules_choose') }}</el-button>
<el-link :underline="false" :disabled="!!!selectImageIndex.length" icon="el-icon-download"
@click="downloadImages">{{ __('admin/file_manager.download') }}</el-link>
<el-link :underline="false" :disabled="!!!selectImageIndex.length" icon="el-icon-view"
@click="viewImages">{{ __('common.view') }}</el-link>
<el-link :underline="false" :disabled="!!!selectImageIndex.length" @click="deleteFile"
icon="el-icon-delete">{{ __('common.delete') }}</el-link>
<el-link :underline="false" :disabled="selectImageIndex.length == 1 ? false : true"
@ -115,7 +114,10 @@
v-batch-select="{ className: '.image-list', selectImageIndex, setSelectStatus: updateSelectStatus }">
<div :class="['image-list', file.selected ? 'active' : '']" v-for="file, index in images"
:key="index" @click="checkedImage(index)">
<div class="img"><img :src="file.url"></div>
<div class="img">
<i class="el-icon-video-play" v-if="file.mime == 'video/mp4'"></i>
<img v-else :src="file.url">
</div>
<div class="text">
<span :title="file.name">@{{ file.name }}</span>
<i v-if="file.selected" class="el-icon-check"></i>
@ -144,14 +146,13 @@
</div>
@else
<div class="text-center mt-5 w-100 fs-4">{{ __('admin/file_manager.show_pc') }}</div>
@endif
<el-dialog title="{{ __('admin/file_manager.upload_files') }}" top="12vh" :visible.sync="uploadFileDialog.show" width="500px"
@close="uploadFileDialogClose" custom-class="upload-wrap">
<el-upload class="photos-upload" target="photos-upload" id="photos-upload" element-loading-text="{{ __('admin/file_manager.image_uploading') }}..."
element-loading-background="rgba(0, 0, 0, 0.6)" drag action="" :show-file-list="false"
accept=".jpg,.jpeg,.png,.JPG,.JPEG,.PNG,.mp4,.MP4" :before-upload="beforePhotoUpload"
accept=".jpg,.jpeg,.png,.JPG,.JPEG,.PNG,.mp4,.MP4,.gif,.webp" :before-upload="beforePhotoUpload"
:on-success="handlePhotoSuccess" :on-change="handleUploadChange" :http-request="uploadFile"
:multiple="true">
<i class="el-icon-upload"></i>
@ -162,11 +163,13 @@
<div class="info">
<div class="name">@{{ index + 1 }}. @{{ image.name }}</div>
<div class="status">
<span v-if="image.status == 'complete'">{{ __('admin/file_manager.finish') }}</span>
<span v-if="image.status == 'complete'" class="text-success">{{ __('admin/file_manager.finish') }}</span>
<span v-else-if="image.status == 'fail'" class="text-danger">{{ __('admin/file_manager.upload_fail') }}</span>
<span v-else>{{ __('admin/file_manager.uploading') }}</span>
</div>
</div>
<el-progress :percentage="image.progre" :show-text="false" :stroke-width="4"></el-progress>
<el-progress :percentage="image.progre" :status="image.status == 'fail' ? 'exception' : 'success'" :show-text="false" :stroke-width="4"></el-progress>
<div v-if="image.fail_text" class="mt-1 text-danger" v-text="image.fail_text"></div>
</div>
</div>
</el-dialog>
@ -187,13 +190,10 @@
paneLengthPercent: 26,
triggerLength: 10,
isShift: false,
ssss: [],
mime: @json(request('mime')),
loading: false,
isBatchSelect: false,
selectImageIndex: [],
filter: {
sort: 'created',
order: 'desc'
@ -299,10 +299,6 @@
this.uploadFileDialog.show = true
},
openFileSort() {
console.log(11);
},
beforePhotoUpload(file) {
// this.editing.photoLoading = true;
},
@ -319,9 +315,6 @@
uploadFile(file) {
const that = this;
let newFile = {};
if (file.file.type != 'image/png' && file.file.type != 'image/jpeg') {
return;
}
var formData = new FormData();
formData.append("file", file.file, file.file.name);
@ -338,12 +331,16 @@
let index = this.uploadFileDialog.images.length - 1;
$http.post('file_manager/upload', formData).then((res) => {
$http.post('file_manager/upload', formData, {hmsg: true}).then((res) => {
this.uploadFileDialog.images[index].status = 'complete';
this.uploadFileDialog.images[index].progre = 100;
}).catch((err) => {
this.uploadFileDialog.images[index].status = 'fail';
this.uploadFileDialog.images[index].progre = 80;
this.uploadFileDialog.images[index].fail_text = err.response.data.message;
}).finally(() => {
index += 1
})
});
},
handleUploadChange(e) {
@ -450,6 +447,20 @@
fileChecked() {
let typedFiles = this.images.filter(e => e.selected)
if (this.mime) {
// 判断 typedFiles 数组内 mime 是否有不是 image 开头的
if (this.mime == 'image' && typedFiles.some(e => !e.mime.startsWith('image'))) {
layer.msg('{{ __('admin/file_manager.verify_select_image') }}', () => {});
return;
}
// 判断 typedFiles 数组内 mime 是否有不是 video 开头的
if (this.mime == 'video' && typedFiles.some(e => !e.mime.startsWith('video'))) {
layer.msg('{{ __('admin/file_manager.verify_select_video') }}', () => {});
return;
}
}
if (callback !== null) {
callback(typedFiles);
}
@ -521,6 +532,13 @@
});
},
viewImages() {
const selectedImages = this.images.filter(e => e.selected);
selectedImages.forEach(e => {
window.open(e.origin_url);
});
},
openInputBox(type, node, data) {
let fileSuffix, fileName = '';

View File

@ -20,7 +20,7 @@ $data = $plugin['data'];
</div>
<div class="card-body">
<div class="d-lg-flex plugin-info">
<div class="d-flex justify-content-between align-items-center plugin-icon-wrap">
<div class="d-flex justify-content-between align-items-center plugin-icon-wrap w-max-400">
<img src="{{ $data['icon_big'] }}" class="img-fluid plugin-icon">
<img src="{{ $data['icon_big'] }}" class="img-fluid plugin-icon-shadow">
</div>

View File

@ -121,7 +121,7 @@
<el-form-item label="{{ __('order.current_status') }}">
{{ $order->status_format }}
</el-form-item>
@if ($order->status != 'completed')
@if (count($statuses))
<el-form-item label="{{ __('order.change_to_status') }}" prop="status">
<el-select class="wp-200" size="small" v-model="form.status" placeholder="{{ __('common.please_choose') }}">
<el-option
@ -184,7 +184,7 @@
<td>{{ $product->product_id }}</td>
<td>
<div class="d-flex align-items-center">
<div class="wh-60 me-2"><img src="{{ $product->image }}" class="img-fluid"></div>{{ $product->name }}
<div class="wh-60 me-2"><img src="{{ $product->image }}" class="img-fluid max-h-100"></div>{{ $product->name }}
</div>
</td>
<td class="">{{ $product->product_sku }}</td>
@ -208,6 +208,50 @@
</div>
@endhookwrapper
@if ($order->orderPayments)
@hookwrapper('admin.order.form.payments')
<div class="card mb-4">
<div class="card-header"><h6 class="card-title">{{ __('admin/order.payments_history') }}</h6></div>
<div class="card-body">
<div class="table-push">
<table class="table ">
<thead class="">
<tr>
<th>{{ __('admin/order.order_id') }}</th>
<th>{{ __('admin/order.text_transaction_id') }}</th>
<th>{{ __('admin/order.text_request') }}</th>
<th>{{ __('admin/order.text_response') }}</th>
<th>{{ __('admin/order.text_callback') }}</th>
<th>{{ __('admin/order.text_receipt') }}</th>
<th>{{ __('order.created_at') }}</th>
<th>{{ __('order.updated_at') }}</th>
</tr>
</thead>
<tbody>
@foreach ($order->orderPayments as $payment)
<tr>
<td>{{ $payment->order_id }}</td>
<td>{{ $payment->transaction_id }}</td>
<td>{{ $payment->request }}</td>
<td>{{ $payment->response }}</td>
<td>{{ $payment->callback }}</td>
<td>
@if ($payment->receipt)
<a href="{{ image_origin($payment->receipt) }}" target="_blank">{{ __('admin/order.text_click_view') }}</a>
@endif
</td>
<td>{{ $payment->created_at }}</td>
<td>{{ $payment->updated_at }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endhookwrapper
@endif
@if ($order->orderShipments)
@hookwrapper('admin.order.form.shipments')
<div class="card mb-4">
@ -219,7 +263,7 @@
<tr>
<th>{{ __('order.express_company') }}</th>
<th>{{ __('order.express_number') }}</th>
<th>{{ __('order.history_created_at') }}</th>
<th>{{ __('order.updated_at') }}</th>
</tr>
</thead>
<tbody>
@ -265,7 +309,7 @@
<tr>
<th>{{ __('order.history_status') }}</th>
<th>{{ __('order.history_comment') }}</th>
<th>{{ __('order.history_created_at') }}</th>
<th>{{ __('order.updated_at') }}</th>
</tr>
</thead>
<tbody>

View File

@ -32,7 +32,7 @@
<td>{{ $item['id'] }}</td>
<td>
<div title="{{ $item['title'] ?? '' }}"><a class="text-dark"
href="{{ shop_route('pages.show', $item['id']) }}" target="_blank">{{ $item['title_format'] ?? ''
href="{{ shop_route('page_categories.show', $item['id']) }}" target="_blank">{{ $item['title_format'] ?? ''
}}</a></div>
</td>
<td class="{{ $item['active'] ? 'text-success' : 'text-secondary' }}">

View File

@ -79,7 +79,19 @@
</draggable>
<div class="help-text mb-1 mt-1">{{ __('admin/product.image_help') }}</div>
</x-admin::form.row>
{{-- <x-admin-form-input name="video" title="视频" :value="old('video', $product->video ?? '')" /> --}}
<x-admin::form.row title="{{ __('product.video') }}">
<div class="d-flex align-items-end">
<div class="set-product-img wh-80 rounded-2 me-2" @click="addProductVideo">
<i v-if="form.video.path" class="bi bi-play-circle fs-1"></i>
<i v-else class="bi bi-plus fs-1 text-muted"></i>
</div>
<input type="hidden" name="video" :value="form.video.path">
<a v-if="form.video.path" target="_blank" :href="form.video.url">{{ __('common.view') }}</a>
</div>
<div class="help-text mb-1 mt-1">{{ __('admin/product.video_help') }}</div>
</x-admin::form.row>
<x-admin-form-input name="position" :title="__('common.sort_order')" :value="old('position', $product->position ?? '0')" />
<x-admin::form.row :title="__('admin/product.weight_text')">
@ -516,6 +528,10 @@
form: {
attributes: @json(old('pickups', $product_attributes) ?? []),
images: @json(old('images', $product->images) ?? []),
video: {
path: @json(old('video', $product->video ?? '')),
url: @json(image_origin(old('video', $product->video ?? ''))),
},
model: @json($product->skus[0]['model'] ?? ''),
price: @json($product->skus[0]['price'] ?? ''),
quantity: @json($product->skus[0]['quantity'] ?? ''),
@ -678,7 +694,13 @@
return;
}
this.form.images.push(...images.map(e => e.path))
})
}, {mime: 'image'})
},
addProductVideo() {
bk.fileManagerIframe(images => {
this.form.video = {path: images[0].path, url: images[0].url}
}, {mime: 'video'})
},
removeImages(index) {

View File

@ -141,7 +141,7 @@
</td>
@endif
@hook('admin.product.list.column_value')
<td class="text-end">
<td class="text-end text-nowrap">
@if ($product['deleted_at'] == '')
<a href="{{ admin_route('products.edit', [$product['id']]) }}" class="btn btn-outline-secondary btn-sm">{{ __('common.edit') }}</a>
<a href="javascript:void(0)" class="btn btn-outline-danger btn-sm" @click.prevent="deleteProduct({{ $loop->index }})">{{ __('common.delete') }}</a>

View File

@ -35,7 +35,7 @@
<td>{{ $rma['type'] }}</td>
<td>{{ $rma['status'] }}</td>
<td><a href="{{ admin_route('rmas.show', [$rma['id']]) }}"
class="btn btn-outline-secondary btn-sm">{{ __('common.view') }}</a>
class="btn btn-outline-secondary btn-sm text-nowrap">{{ __('common.view') }}</a>
</td>
</tr>
@endforeach

View File

@ -7,11 +7,11 @@
<div class="card-header"><h6 class="card-title">{{ __('admin/rma.rma_details') }}</h6></div>
<div class="card-body">
<div class="row">
<div class="col-lg-4 col-12">
<div class="col-lg-4 col-12 order-top-info">
<table class="table table-borderless">
<tbody>
<tr>
<td style="width:40%">ID</td>
<td>ID</td>
<td>{{ $rma['id'] }}</td>
</tr>
<tr>
@ -25,11 +25,11 @@
</tbody>
</table>
</div>
<div class="col-lg-4 col-12">
<div class="col-lg-4 col-12 order-top-info">
<table class="table table-borderless">
<tbody>
<tr>
<td style="width:40%">{{ __('admin/builder.modules_product') }}</td>
<td>{{ __('admin/builder.modules_product') }}</td>
<td>{{ $rma['product_name'] }}</td>
</tr>
<tr>

View File

@ -10,8 +10,8 @@
<script src="{{ asset('vendor/jquery/jquery-3.6.0.min.js') }}"></script>
<script src="{{ asset('vendor/layer/3.5.1/layer.js') }}"></script>
<script src="{{ asset('vendor/vue/2.7/vue' . (!config('app.debug') ? '.min' : '') . '.js') }}"></script>
<script src="{{ asset('vendor/element-ui/2.15.6/js.js') }}"></script>
<link rel="stylesheet" href="{{ asset('vendor/element-ui/2.15.6/css.css') }}">
<script src="{{ asset('vendor/element-ui/index.js') }}"></script>
<link rel="stylesheet" href="{{ asset('vendor/element-ui/index.css') }}">
{{-- <link href="{{ mix('build/css/admin/login.css') }}" rel="stylesheet"> --}}
<script src="{{ mix('build/beike/admin/js/app.js') }}"></script>
<link href="{{ mix('build/beike/admin/css/app.css') }}" rel="stylesheet">

View File

@ -100,6 +100,7 @@ h1,h2,h3, h4, h5, h6, b, strong {
img {
max-width: 100%;
max-height: 100%;
height: auto;
}
@ -321,4 +322,25 @@ img {
padding-top: .8rem;
padding-bottom: .8rem;
}
}
}
// 溢出隐藏 显示省略号
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
// 加一个 line
&.line-2 {
white-space: normal;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
&.line-3 {
white-space: normal;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
}

View File

@ -9,32 +9,25 @@
*/
header {
// box-shadow: 0px 1px 10px rgba(0, 0, 0, .04);
background: #fff;
body:not(.page-home) & {
box-shadow: 0 6px 12px 0 rgba(0, 0, 0, .04);
// border-bottom: 1px solid #e5e5e5;
}
.top-wrap {
// padding: 10px;
height: 40px;
// max-height: 50px;
background: #F7F8FA;
display: flex;
}
.top-wrap, .header-content {
.dropdown {
&:hover {
background-color: #fff;
.dropdown-menu {
margin: 0;
display: block;
box-shadow: 0 0 15px rgb(0, 0, 0, .1);
border: none;
box-shadow: 0 0 15px rgb(0, 0, 0, .1);
&.dropdown-menu-end {
right: 0;
@ -44,6 +37,38 @@ header {
}
}
.header-content {
.nav-item {
&:hover {
> a {
color: $primary;
}
}
}
.dropdown {
.dropdown-menu {
border: none;
left: 50%;
visibility: hidden;
opacity: 0;
display: block;
transform-origin: top center;
transition: all .2s ease-in-out;
transform: translate(-50%, 0.5rem);
box-shadow: 0 0 15px rgb(0, 0, 0, .1);
}
&:hover {
.dropdown-menu {
opacity: 1;
visibility: visible;
transform: translate(-50%);
}
}
}
}
.header-content {
position: relative;
background-color: #fff;
@ -59,36 +84,6 @@ header {
max-width: 1140px;
}
@media (min-width: 1200px) {
.navbar-nav {
.dropdown {
&.position-static >.dropdown-menu {
// top: 100%;
}
&:hover {
.dropdown-menu {
opacity: 1;
visibility: visible;
transform: translate(-50%);
}
}
>.dropdown-menu {
left: 50%;
transform: translate(-50%, 0.5rem);
transition: all .2s ease-in-out;
transition-property: visibility,transform,opacity;
visibility: hidden;
opacity: 0;
display: block;
transform-origin: top center;
}
}
}
}
> .navbar-nav {
> .nav-item {
background-color: transparent;
@ -384,12 +379,17 @@ header {
.children-group {
.children-title {
height: 44px;
span {
width: 26px;
height: 26px;
margin-right: -10px;
width: 44px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
&:active {
background-color: #eee;
}
&[aria-expanded="true"] {
i::before {

View File

@ -12,14 +12,15 @@ body.page-account-address, body.page-checkout {
.addresses-wrap {
.item {
position: relative;
padding: 14px 14px 14px 18px;
padding: 14px;
margin-bottom: 1.3rem;
border: 1px solid #e8e8e8;
height: 130px;
border: 1px solid #e5e5e5;
height: 140px;
cursor: pointer;
@media (max-width: 768px) {
padding: 10px 10px 10px 14px;
padding: 10px;
height: auto;
}
&:hover {
@ -58,6 +59,7 @@ body.page-account-address, body.page-checkout {
.zipcode {
margin-bottom: .3rem;
min-height: 20px;
}
.address-info {
@ -67,13 +69,11 @@ body.page-account-address, body.page-checkout {
}
.address-bottom {
min-height: 28px;
display: flex;
align-items: center; // flex-start | center
justify-content: space-between; // flex-end | center | space-between
// flex-wrap: wrap;
a {
color: #2d68a8;
}
}
}
}

View File

@ -170,9 +170,8 @@ body.page-checkout, body.page-bk-stripe {
align-items: center; // flex-start | center
padding-right: 4px;
img {
width: 40px;
margin-right: 10px;
.img {
flex: 0 0 40px;
}
.quantity {

View File

@ -23,6 +23,8 @@ body.page-product {
}
.product-image {
position: relative;
#swiper {
height: 250px;
@media (min-width: 480px) {
@ -117,6 +119,68 @@ body.page-product {
.right {
border: 1px solid #eee;
position: relative;
}
#product-video {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 99;
display: none;
}
.open-video {
position: absolute;
bottom: 10px;
z-index: 99;
line-height: 1;
cursor: pointer;
@media (min-width: 768px) {
transform: translateX(-50%);
left: 50%;
}
@media (max-width: 768px) {
right: 10px;
}
&:hover {
i {
color: #fff;
background-color: rgba(0, 0, 0, 0.648);
}
}
i {
font-size: 3.5rem;
line-height: 1;
border-radius: 50%;
font-weight: 400;
display: inline-block;
color: rgba(255, 255, 255, 0.948);
background-color: rgba(0, 0, 0, 0.348);
@media (max-width: 768px) {
font-size: 2.5rem;
}
}
}
.close-video {
position: absolute;
top: 6px;
right: 10px;
z-index: 9999;
color: #aaa;
font-size: 30px;
cursor: pointer;
&:hover {
color: #fff;
}
}
}

View File

@ -3,7 +3,7 @@
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-08-29 17:32:51
* @LastEditTime 2023-02-02 11:06:01
* @LastEditTime 2023-05-18 10:18:09
*/
import http from "../../../../js/http";
@ -19,22 +19,6 @@ import './header'
import './bootstrap-validation'
$(document).ready(function ($) {
$(document).on('click', '.offcanvas-products-delete', function () {
const id = $(this).data('id');
$http.delete(`carts/${id}`).then((res) => {
$(this).parents('.product-list').remove();
if (!res.data.quantity) {
$('.cart-badge-quantity').hide();
} else {
$('.cart-badge-quantity').show().html(res.data.quantity > 99 ? '99+' : res.data.quantity);
}
$('.offcanvas-right-cart-count').text(res.data.quantity);
$('.offcanvas-right-cart-amount').text(res.data.amount_format);
})
})
if ($(window).width() > 992 && $('.x-fixed-top').length) {
$('.x-fixed-top').scrollToFixed({
zIndex: 999,

View File

@ -3,7 +3,7 @@
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-09-09 19:16:39
* @LastEditTime 2023-02-13 09:26:05
* @LastEditTime 2023-05-18 09:02:44
*/
export default {
@ -31,7 +31,7 @@ export default {
* @param {*} isBuyNow 是否立即购买
* @return {*} 返回Promise
*/
addCart({sku_id, quantity = 1, isBuyNow = false}, event) {
addCart({sku_id, quantity = 1, isBuyNow = false}, event, callback) {
if (!config.isLogin && !config.guestCheckout) {
this.openLogin()
return;
@ -46,8 +46,9 @@ export default {
$http.post('/carts', {sku_id, quantity, buy_now: isBuyNow}, {hload: !!event}).then((res) => {
this.getCarts();
layer.msg(res.message)
if (isBuyNow) {
location.href = 'checkout'
if (callback) {
callback(res)
}
}).finally(() => {$btn.html(btnHtml).prop('disabled', false)})
},
@ -96,6 +97,18 @@ export default {
});
},
productQuickView(id, callback) {
layer.open({
type: 2,
title: '',
shadeClose: true,
scrollbar: false,
area: ['1000px', '600px'],
skin: 'login-pop-box',
content: `products/${id}?iframe=true`
});
},
getQueryString(name, defaultValue) {
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
const r = window.location.search.substr(1).match(reg);
@ -129,4 +142,35 @@ export default {
+ ',width=' + iWidth + ',innerWidth=' + iWidth + ',top=' + iTop + ',left=' + iLeft
+ ',toolbar=no,menubar=no,scrollbars=auto,resizeable=no,location=no,status=no');
},
// 判断 js 插件是否加载,如果未加载则往页面添加 script 标签
loadScript(url, callback) {
// 判断页面中是否已经存在指定的 js 插件
if (!document.querySelector(`script[src="${url}"]`)) {
// 创建一个新的 script 标签
const script = document.createElement('script');
script.src = url;
// 将 script 标签添加到 head 标签中
document.head.appendChild(script);
// 监听 js 插件加载完成事件
script.onload = function () {
callback && callback();
}
} else {
callback && callback();
}
},
// 判断 css 插件是否加载,如果未加载则往页面添加 link 标签
loadStyle(url) {
// 判断页面中是否已经存在指定的 css 插件
if (!document.querySelector(`link[href="${url}"]`)) {
// 创建一个新的 link 标签
const link = document.createElement('link');
link.href = url;
link.rel = 'stylesheet';
// 将 link 标签添加到 head 标签中
document.head.appendChild(link);
}
}
}

View File

@ -3,7 +3,7 @@
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-08-16 18:47:18
* @LastEditTime 2023-03-16 17:30:20
* @LastEditTime 2023-05-18 10:27:58
*/
$(function () {
@ -22,6 +22,35 @@ $(function () {
});
}
// 购物车侧边栏弹出
$(document).on("click", ".btn-right-cart", function () {
const currentUrl = window.location.pathname;
if (currentUrl == '/checkout' || currentUrl == '/carts') {
return;
}
const offcanvasRightCart = new bootstrap.Offcanvas('#offcanvas-right-cart')
offcanvasRightCart.show()
});
// 侧边栏购物车删除商品
$(document).on('click', '.offcanvas-products-delete', function () {
const id = $(this).data('id');
$http.delete(`carts/${id}`).then((res) => {
$(this).parents('.product-list').remove();
if (!res.data.quantity) {
$('.cart-badge-quantity').hide();
$('.empty-cart').removeClass('d-none');
} else {
$('.cart-badge-quantity').show().html(res.data.quantity > 99 ? '99+' : res.data.quantity);
}
$('.offcanvas-right-cart-count').text(res.data.quantity);
$('.offcanvas-right-cart-amount').text(res.data.amount_format);
})
})
// 响应式下弹窗菜单交互
$(document).on("click", ".mobile-open-menu", function () {
const offcanvasMobileMenu = new bootstrap.Offcanvas('#offcanvas-mobile-menu')

24
resources/js/http.js vendored
View File

@ -3,7 +3,7 @@
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-08-02 19:19:52
* @LastEditTime 2022-09-16 20:58:16
* @LastEditTime 2023-05-29 18:56:35
*/
window.axios = require('axios');
@ -25,8 +25,8 @@ export default {
* @param url 接口路由
* @returns {AxiosPromise<any>}
*/
get (url, params, {hmsg, hload}={}) {
return this.request('get', url, params = params, {hmsg, hload});
get (url, params, {hmsg, hload, base}={}) {
return this.request('get', url, params = params, {hmsg, hload, base});
},
/**
@ -37,8 +37,8 @@ export default {
* @returns {AxiosPromise<any>}
*/
post (url, params, {hmsg, hload}={}) {
return this.request('post', url, params, {hmsg, hload});
post (url, params, {hmsg, hload, base}={}) {
return this.request('post', url, params, {hmsg, hload, base});
},
/**
@ -48,8 +48,8 @@ export default {
* @returns {Promise}
*/
delete (url, params, {hmsg, hload}={}) {
return this.request('delete', url, params, {hmsg, hload});
delete (url, params, {hmsg, hload, base}={}) {
return this.request('delete', url, params, {hmsg, hload, base});
},
/**
@ -59,8 +59,8 @@ export default {
* @returns {Promise}
*/
put (url, params, {hmsg, hload}={}) {
return this.request('put', url, params, {hmsg, hload});
put (url, params, {hmsg, hload, base}={}) {
return this.request('put', url, params, {hmsg, hload, base});
},
@ -73,11 +73,15 @@ export default {
* @returns {Promise<any>}
*/
// 错误和失败信息都在这里进行处理,界面中调用的时候只处理正确数据即可
request(method, url, params = {}, {hmsg, hload} = {}) {
request(method, url, params = {}, {hmsg, hload, base} = {}) {
if (!hload) {
layer.load(2, {shade: [0.3,'#fff'] })
}
if (base) {
axios.defaults.baseURL = base;
}
return new Promise((resolve, reject) => {
axios({method: method, url: url, [method == 'get' ? 'params' : 'data']: params}).then((res) => {
if (res) {

View File

@ -11,7 +11,7 @@
return [
'index' => 'Edit Address',
'name' => 'Name',
'name' => 'Full name',
'phone' => 'Phone',
'country_id' => 'Country ID',
'zone' => 'Zone',

View File

@ -33,6 +33,8 @@ return [
'no_file' => 'No File',
'picture_space' => 'Picture Space',
'show_pc' => 'Please go to the PC side to operate',
'verify_select_image' => 'Please select a picture',
'verify_select_video' => 'Please select video',
'confirm_delete_file' => 'Do you want to delete the selected file',
'confirm_delete_folder' => 'The folder deletion operation is in progress, all files in the folder will be deleted, do you want to confirm?',
@ -40,5 +42,6 @@ return [
'can_empty' => 'Can not be empty',
'finish' => 'Finish',
'uploading' => 'loading...',
'upload_fail' => 'Upload failed',
'file_manager' => 'File Manager',
];

View File

@ -12,15 +12,24 @@
return [
'list' => 'Order List',
'order_quantity' => 'Order Quantity',
'order_amount' => 'Order Amount',
'orders_index' => 'Index',
'orders_create' => 'Create',
'orders_show' => 'Detail',
'orders_export' => 'Export',
'orders_update' => 'Update',
'orders_delete' => 'Delete',
'notify' => 'Whether to remind',
'orders_update_status' => 'Update Status',
'error_status' => 'Please select a status',
'order_quantity' => 'Order Quantity',
'order_amount' => 'Order Amount',
'orders_index' => 'Index',
'orders_create' => 'Create',
'orders_show' => 'Detail',
'orders_export' => 'Export',
'orders_update' => 'Update',
'orders_delete' => 'Delete',
'notify' => 'Whether to remind',
'orders_update_status' => 'Update Status',
'error_status' => 'Please select a status',
'payments_history' => 'Payment History',
'text_response' => 'Pesponse',
'text_request' => 'Pequest',
'text_callback' => 'Callback',
'text_callback' => 'Callback',
'text_receipt' => 'Receipt',
'order_id' => 'Order Id',
'text_transaction_id' => 'Transaction Id',
'text_click_view' => 'Click To View',
];

View File

@ -36,6 +36,7 @@ return [
'quantity' => 'Quantity',
'enable_multi_spec' => 'Enable multi-spec',
'image_help' => 'The first picture will be used as the main picture of the product, and multiple pictures can be uploaded at the same time, and the position of multiple pictures can be adjusted at will',
'video_help' => 'If the prompt exceeds the system size limit, please modify the php.ini parameter post_max_size',
'add_variable' => 'Add Specs',
'add_variable_value' => 'Add Specification Value',
'add_variable_image' => 'Add Spec Image',

View File

@ -81,6 +81,7 @@ return [
'get_more' => 'Get More',
'view_more' => 'View more',
'view_details' => 'Check Details',
'quick_view' => 'Quick View',
'id' => 'ID',
'created_at' => 'Created At',

View File

@ -23,6 +23,7 @@ return [
'g' => 'Gram',
'oz' => 'Ounce',
'lb' => 'Pound',
'video' => 'Video',
'active' => 'Active',
'inactive' => 'Inactive',

View File

@ -100,7 +100,7 @@ return [
'addresses' => [
'index' => 'Addresses',
'add_address' => 'Add New Address',
'default_address' => 'Default Address',
'default_address' => 'Default',
'delete' => 'Delete',
'edit' => 'Edit',
'enter_name' => 'Please type in your name',

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