This commit is contained in:
liqianjin 2023-05-29 19:59:31 +08:00
parent 5880fbaa0a
commit bb57d646db
34 changed files with 1276 additions and 20 deletions

4
.env
View File

@ -1,7 +1,7 @@
APP_NAME='BeikeShop'
APP_NAME='wyyl'
APP_ENV=
APP_KEY=base64:PItUypiY6FmV8oQTVIcIHyNQJAuuS36FmEs8exQbYAw=
APP_DEBUG=false
APP_DEBUG=true
APP_LOG_LEVEL=
APP_URL=http://43.153.17.83

View File

@ -0,0 +1,145 @@
<?php
/**
* PagesController.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-08 15:07:33
* @modified 2022-08-08 15:07:33
*/
namespace Beike\Admin\Http\Controllers;
use Beike\Admin\Http\Requests\PageRequest;
use Beike\Admin\Repositories\InquiryRepo;
use Beike\Models\Page;
use Beike\Shop\Http\Resources\InquiryDetail;
use Beike\Shop\Http\Resources\ProductSimple;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class InquiryController extends Controller
{
/**
* 显示单页列表
*
* @return mixed
*/
public function index()
{
$pageList = InquiryRepo::getList();
$data = [
'pages' => $pageList,
'pages_format' => InquiryDetail::collection($pageList)->jsonSerialize(),
];
return view('admin::pages.inquiry.index', $data);
}
/**
* 创建页面
*
* @return mixed
*/
public function create()
{
return view('admin::pages.pages.form', ['page' => new Page()]);
}
/**
* 保存新建
*
* @param PageRequest $request
* @return RedirectResponse
*/
public function store(PageRequest $request)
{
try {
$requestData = $request->all();
$page = PageRepo::createOrUpdate($requestData);
hook_action('admin.page.store.after', ['request_data' => $requestData, 'page' => $page]);
return redirect(admin_route('pages.index'));
} catch (\Exception $e) {
return redirect(admin_route('pages.index'))->withErrors(['error' => $e->getMessage()]);
}
}
/**
* @param Request $request
* @param Page $page
* @return mixed
*/
public function edit(Request $request, Page $page)
{
$page->load(['products.description', 'category.description']);
$data = [
'page' => $page,
'products' => ProductSimple::collection($page->products)->jsonSerialize(),
'descriptions' => PageRepo::getDescriptionsByLocale($page->id),
];
return view('admin::pages.pages.form', $data);
}
/**
* 保存更新
*
* @param PageRequest $request
* @param int $pageId
* @return RedirectResponse
*/
public function update(PageRequest $request, int $pageId)
{
try {
$requestData = $request->all();
$requestData['id'] = $pageId;
$page = PageRepo::createOrUpdate($requestData);
hook_action('admin.page.update.after', ['request_data' => $requestData, 'page' => $page]);
return redirect()->to(admin_route('pages.index'));
} catch (\Exception $e) {
return redirect(admin_route('pages.index'))->withErrors(['error' => $e->getMessage()]);
}
}
/**
* 删除单页
*
* @param Request $request
* @param int $pageId
* @return array
*/
public function destroy(Request $request, int $pageId): array
{
InquiryRepo::deleteById($pageId);
return json_success(trans('common.deleted_success'));
}
/**
* 搜索页面标题自动完成
* @param Request $request
* @return array
*/
public function autocomplete(Request $request): array
{
$products = PageRepo::autocomplete($request->get('name') ?? '');
return json_success(trans('common.get_success'), $products);
}
/**
* 获取单页名称
* @param Page $page
* @return array
*/
public function name(Page $page): array
{
$name = $page->description->title ?? '';
return json_success(trans('common.get_success'), $name);
}
}

View File

@ -0,0 +1,131 @@
<?php
/**
* PageRepo.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-07-26 21:08:07
* @modified 2022-07-26 21:08:07
*/
namespace Beike\Admin\Repositories;
use Beike\Models\Inquiry;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
class InquiryRepo
{
/**
* 获取列表页数据
*
* @return LengthAwarePaginator
*/
public static function getList(): LengthAwarePaginator
{
$builder = Inquiry::query()->with([
'productsku',
])->orderByDesc('updated_at');
return $builder->paginate(perPage());
}
public static function findByPageId($pageId)
{
$page = Inquiry::query()->findOrFail($pageId);
$page->load(['descriptions']);
return $page;
}
public static function getDescriptionsByLocale($pageId)
{
$page = self::findByPageId($pageId);
return $page->descriptions->keyBy('locale')->toArray();
}
public static function createOrUpdate($data)
{
try {
DB::beginTransaction();
$region = self::pushPage($data);
DB::commit();
return $region;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
public static function pushPage($data)
{
$id = $data['id'] ?? 0;
if ($id) {
$page = Inquiry::query()->findOrFail($id);
} else {
$page = new Inquiry();
}
$page->fill([
'page_category_id' => (int) ($data['page_category_id'] ?? 0),
'position' => (int) ($data['position'] ?? 0),
'active' => (bool) ($data['active'] ?? true),
'author' => $data['author'] ?? '',
'views' => (int) ($data['views'] ?? 0),
]);
$page->saveOrFail();
$page->descriptions()->delete();
$page->descriptions()->createMany($data['descriptions']);
$products = $data['products'] ?? [];
if ($products) {
$items = [];
foreach ($products as $item) {
$items[] = [
'product_id' => $item,
];
}
$page->pageProducts()->delete();
$page->pageProducts()->createMany($items);
}
$page->load(['descriptions', 'pageProducts']);
return $page;
}
public static function deleteById($id)
{
$page = Inquiry::query()->findOrFail($id);
$page->delete();
}
/**
* 页面内容自动完成
*
* @param $name
* @return array
*/
public static function autocomplete($name): array
{
$pages = Inquiry::query()->with('description')
->whereHas('description', function ($query) use ($name) {
$query->where('title', 'like', "{$name}%");
})->limit(10)->get();
$results = [];
foreach ($pages as $page) {
$results[] = [
'id' => $page->id,
'name' => $page->description->title,
'status' => $page->active,
];
}
return $results;
}
}

View File

@ -169,6 +169,10 @@ Route::prefix($adminName)
Route::middleware('can:pages_update')->put('pages/{page}', [Controllers\PagesController::class, 'update'])->name('pages.update');
Route::middleware('can:pages_delete')->delete('pages/{page}', [Controllers\PagesController::class, 'destroy'])->name('pages.destroy');
// 文章
Route::middleware('can:inquiry_index')->get('inquiry', [Controllers\InquiryController::class, 'index'])->name('inquiry.index');
Route::middleware('can:inquiry_delete')->delete('inquiry/{page}', [Controllers\InquiryController::class, 'destroy'])->name('inquiry.destroy');
// 文章分类
Route::middleware('can:page_categories_index')->get('page_categories', [Controllers\PageCategoryController::class, 'index'])->name('page_categories.index');
Route::middleware('can:page_categories_index')->get('page_categories/autocomplete', [Controllers\PageCategoryController::class, 'autocomplete'])->name('page_categories.autocomplete');

View File

@ -35,6 +35,7 @@ class ProductService
$product->skus()->delete();
$product->descriptions()->delete();
$product->attributes()->delete();
$product->numPrices()->delete();
}
$descriptions = [];
@ -58,6 +59,8 @@ class ProductService
}
$product->skus()->createMany($skus);
$product->numPrices()->createMany(list_sort_by($data['numPrices'],'num') ?? []);
$product->categories()->sync($data['categories'] ?? []);
$product->relations()->sync($data['relations'] ?? []);

View File

@ -718,3 +718,53 @@ function perPage(): int
{
return (int) system_setting('base.product_per_page', 20);
}
/**
* php多维数组指定列排序
* @param $list
* @param $field
* @param $sortby
* @return array|false
*/
function list_sort_by($list, $field, $sortby='asc') {
if(is_array($list)){ //判断是否数组
$refer = $resultSet = array(); //初始化数组变量
foreach ($list as $i => $data) //foreach数组
$refer[$i] = &$data[$field]; //存储要排序的数组字段键和值
switch ($sortby) {//进行排序
case 'asc': // 正向排序
asort($refer);
break;
case 'desc':// 逆向排序
arsort($refer);
break;
case 'nat': // 自然排序
natcasesort($refer);
break;
}
foreach ( $refer as $key=> $val)//重新组合排序后的数组
$resultSet[] = &$list[$key];
return $resultSet;
}
return false;
}

21
beike/Models/Inquiry.php Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace Beike\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Inquiry extends Base
{
use HasFactory;
protected $fillable = ['product_sku_id', 'contacts', 'email', 'content'];
public function productsku()
{
return $this->belongsTo(ProductSku::class, 'product_sku_id', 'id');
}
}

View File

@ -11,14 +11,26 @@ class Product extends Base
use HasFactory;
use SoftDeletes;
protected $fillable = ['images', 'video', 'position', 'brand_id', 'tax_class_id', 'active', 'variables'];
protected $fillable = ['images', 'video', 'position', 'brand_id', 'tax_class_id', 'active', 'variables', 'price_setting'];
protected $casts = [
'active' => 'boolean',
'variables' => 'array',
'images' => 'array',
'numPrices' => 'array',
];
public function getNumPricesByNum($num)
{
$descNumPrices = array_reverse($this->numprices->toArray());
foreach($descNumPrices as $numprice){
if($num >= $numprice['num']){
return $numprice['price'];
}
}
return FALSE;
}
protected $appends = ['image'];
public function categories()
@ -46,6 +58,11 @@ class Product extends Base
return $this->hasMany(ProductSku::class);
}
public function numPrices()
{
return $this->hasMany(ProductNumPrice::class);
}
public function attributes(): HasMany
{
return $this->hasMany(ProductAttribute::class);

View File

@ -0,0 +1,68 @@
<?php
/**
* AddressRepo.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-06-28 15:22:05
* @modified 2022-06-28 15:22:05
*/
namespace Beike\Repositories;
use Beike\Models\Inquiry;
class InquiryRepo
{
/**
* 创建一个address记录
* @param $data
* @return mixed
*/
public static function create($data)
{
return Inquiry::query()->create($data);
}
/**
* @param $address
* @param $data
* @return mixed
* @throws \Exception
*/
public static function update($address, $data)
{
if (! $address instanceof Inquiry) {
$address = Inquiry::query()->find($address);
}
if (! $address) {
throw new \Exception("地址id {$address} 不存在");
}
$address->update($data);
return $address;
}
/**
* @param $id
* @return mixed
*/
public static function find($id)
{
return Inquiry::query()->find($id);
}
/**
* @param $id
* @return void
*/
public static function delete($id)
{
$address = Inquiry::query()->find($id);
if ($address) {
$address->delete();
}
}
}

View File

@ -200,10 +200,6 @@ class OrderRepo
$shippingAddress = Address::query()->findOrFail($shippingAddressId);
$paymentAddress = Address::query()->findOrFail($paymentAddressId);
$shippingAddress->country = $shippingAddress->country->name ?? '';
$shippingAddress->country_id = $shippingAddress->country->id ?? 0;
$paymentAddress->country = $paymentAddress->country->name ?? '';
$paymentAddress->country_id = $paymentAddress->country->id ?? 0;
$email = $customer->email;
} else {
$shippingAddress = new Address($current['guest_shipping_address'] ?? []);
@ -211,6 +207,11 @@ class OrderRepo
$email = $current['guest_shipping_address']['email'];
}
$shippingAddress->country = $shippingAddress->country->name ?? '';
$shippingAddress->country_id = $shippingAddress->country->id ?? 0;
$paymentAddress->country = $paymentAddress->country->name ?? '';
$paymentAddress->country_id = $paymentAddress->country->id ?? 0;
$shippingMethodCode = $current['shipping_method_code'] ?? '';
$paymentMethodCode = $current['payment_method_code'] ?? '';

View File

@ -39,7 +39,7 @@ class ProductRepo
if (is_int($product)) {
$product = Product::query()->findOrFail($product);
}
$product->load('description', 'skus', 'masterSku', 'brand', 'relations');
$product->load('description', 'skus', 'masterSku', 'brand', 'relations', 'numPrices');//, 'price_setting', 'numPrices');
hook_filter('repo.product.get_detail', $product);

View File

@ -85,6 +85,7 @@ class CartController extends Controller
*/
public function update(CartRequest $request, $cartId): array
{
try {
$customer = current_customer();
$quantity = (int) $request->get('quantity');
CartService::updateQuantity($customer, $cartId, $quantity);
@ -94,6 +95,10 @@ class CartController extends Controller
$data = hook_filter('cart.update.data', $data);
return json_success(trans('common.updated_success'), $data);
} catch (\Exception $e) {
return json_fail($e->getMessage());
}
}
/**

View File

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

View File

@ -19,7 +19,11 @@ class CartDetail extends JsonResource
{
$sku = $this->sku;
$product = $sku->product;
$price = $sku->price;
if($product->price_setting == 'num'){
$price = $product->getNumPricesByNum($this->quantity);
}else{
$price = $sku->price;
};
$skuCode = $sku->sku;
$description = $product->description;
$productName = $description->name;

View File

@ -0,0 +1,39 @@
<?php
/**
* PageDetail.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-11 18:45:02
* @modified 2022-08-11 18:45:02
*/
namespace Beike\Shop\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class InquiryDetail extends JsonResource
{
/**
* @param Request $request
* @return array
* @throws \Exception
*/
public function toArray($request): array
{
$productsku = $this->productsku;
return [
'id' => $this->id,
'product_sku_id' => $this->product_sku_id,
'contacts' => $this->contacts,
'email' => $this->email,
'content' => $this->content,
'product_sku_sku' => $productsku->sku,
'created_at' => time_format($this->created_at),
'updated_at' => time_format($this->updated_at),
];
}
}

View File

@ -52,6 +52,8 @@ class ProductDetail extends JsonResource
'skus' => SkuDetail::collection($this->skus)->jsonSerialize(),
'in_wishlist' => $this->inCurrentWishlist->id ?? 0,
'active' => (bool) $this->active,
'price_setting' => $this->price_setting ?? '',
'numPrices' => numPricesDetail::collection($this->numprices)->jsonSerialize() ?? '',
];
}

View File

@ -18,6 +18,7 @@ use Beike\Shop\Http\Controllers\CheckoutController;
use Beike\Shop\Http\Controllers\CurrencyController;
use Beike\Shop\Http\Controllers\FileController;
use Beike\Shop\Http\Controllers\HomeController;
use Beike\Shop\Http\Controllers\InquiryController;
use Beike\Shop\Http\Controllers\LanguageController;
use Beike\Shop\Http\Controllers\PageCategoryController;
use Beike\Shop\Http\Controllers\PageController;
@ -68,6 +69,8 @@ Route::prefix('/')
Route::get('plugin/{code}/{path}', [PluginController::class, 'asset'])->where('path', '(.*)')->name('plugin.asset');
Route::post('inquiry', [InquiryController::class, 'store'])->name('brands.index');
Route::middleware('checkout_auth:' . Customer::AUTH_GUARD)
->group(function () {
Route::get('carts', [CartController::class, 'index'])->name('carts.index');

View File

@ -0,0 +1,24 @@
<?php
/**
* CheckoutService.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-06-30 19:37:05
* @modified 2022-06-30 19:37:05
*/
namespace Beike\Shop\Services;
use Beike\Repositories\InquiryRepo;
class InquiryService
{
public static function create($data)
{
$inquiry = InquiryRepo::create($data);
return $inquiry;
}
}

67
package-lock.json generated
View File

@ -2097,6 +2097,14 @@
"integrity": "sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==",
"dev": true
},
"async-validator": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.8.5.tgz",
"integrity": "sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==",
"requires": {
"babel-runtime": "6.x"
}
},
"autoprefixer": {
"version": "10.4.14",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
@ -2120,6 +2128,11 @@
"follow-redirects": "^1.14.0"
}
},
"babel-helper-vue-jsx-merge-props": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz",
"integrity": "sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg=="
},
"babel-loader": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz",
@ -2170,6 +2183,22 @@
"@babel/helper-define-polyfill-provider": "^0.4.0"
}
},
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
"requires": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
}
}
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -2874,6 +2903,11 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"dev": true
},
"core-js": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
},
"core-js-compat": {
"version": "3.30.2",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz",
@ -3137,6 +3171,11 @@
"ms": "2.1.2"
}
},
"deepmerge": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz",
"integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ=="
},
"default-gateway": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz",
@ -3324,6 +3363,19 @@
"integrity": "sha512-5VXLW4Qw89vM2WTICHua/y8v7fKGDRVa2VPOtBB9IpLvW316B+xd8yD1wTmLPY2ot/00P/qt87xdolj4aG/Lzg==",
"dev": true
},
"element-ui": {
"version": "2.15.13",
"resolved": "https://registry.npmjs.org/element-ui/-/element-ui-2.15.13.tgz",
"integrity": "sha512-LJoatEYX6WV74FqXBss8Xfho9fh9rjDSzrDrTyREdGb1h1R3uRvmLh5jqp2JU137aj4/BgqA3K06RQpQBX33Bg==",
"requires": {
"async-validator": "~1.8.1",
"babel-helper-vue-jsx-merge-props": "^2.0.0",
"deepmerge": "^1.2.0",
"normalize-wheel": "^1.0.1",
"resize-observer-polyfill": "^1.5.0",
"throttle-debounce": "^1.0.1"
}
},
"elliptic": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
@ -5151,6 +5203,11 @@
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
"dev": true
},
"normalize-wheel": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
"integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA=="
},
"npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@ -6059,6 +6116,11 @@
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"dev": true
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
},
"resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@ -6865,6 +6927,11 @@
}
}
},
"throttle-debounce": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-1.1.0.tgz",
"integrity": "sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg=="
},
"thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",

View File

@ -20,5 +20,8 @@
"resolve-url-loader": "^4.0.0",
"sass": "^1.38.1",
"sass-loader": "^12.1.0"
},
"dependencies": {
"element-ui": "^2.15.13"
}
}

View File

@ -2332,6 +2332,10 @@ $(function () {
});
});
function updateMiniCartData(res) {
var cart_item_price = $('.offcanvas-right-cart-item-price');
for (var i = 0; i < cart_item_price.length; i++) {
cart_item_price.contents()[i].nodeValue = ' ' + res.data.carts[i].price_format + ' x ';
}
$('.offcanvas-right-cart-count').text(res.data.quantity);
$('.offcanvas-right-cart-amount').text(res.data.amount_format);
}

View File

@ -0,0 +1,212 @@
@extends('admin::layouts.master')
@section('title', __('admin/page.index'))
@section('body-class', 'page-pages-form')
@push('header')
<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>
@endpush
@section('page-title-right')
<x-admin::form.row title="">
<button type="button" class="mt-3 btn btn-primary submit-form">{{ __('common.save') }}</button>
</x-admin::form.row>
@endsection
@section('content')
<ul class="nav nav-tabs nav-bordered mb-3" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tab-content" type="button" >{{ __('admin/product.basic_information') }}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-data" type="button">{{ __('common.data') }}</button>
</li>
</ul>
<div id="app" class="card h-min-600">
<div class="card-body">
<form novalidate class="needs-validation" action="{{ $page->id ? admin_route('pages.update', [$page->id]) : admin_route('pages.store') }}" method="POST">
<div class="tab-content">
<div class="tab-pane fade show active" id="tab-content">
@csrf
@method($page->id ? 'PUT' : 'POST')
<ul class="nav nav-tabs mb-3" role="tablist">
@foreach (locales() as $language)
<li class="nav-item" role="presentation">
<button class="nav-link {{ $loop->first ? 'active' : '' }}" data-bs-toggle="tab" data-bs-target="#tab-{{ $language['code'] }}" type="button" >{{ $language['name'] }}</button>
</li>
@endforeach
</ul>
<div class="tab-content">
@foreach (locales() as $language)
<div class="tab-pane fade {{ $loop->first ? 'show active' : '' }}" id="tab-{{ $language['code'] }}">
@php
$error_title = $errors->first("descriptions.{$language['code']}.title");
@endphp
<x-admin-form-input
error="{{ $error_title }}"
name="descriptions[{{ $language['code'] }}][title]"
title="{{ __('admin/page.info_title') }}"
:required="true"
value="{{ old('descriptions.' . $language['code'] . '.title', $descriptions[$language['code']]['title'] ?? '') }}"
/>
<x-admin::form.row title="{{ __('page_category.text_summary') }}">
<div class="input-group w-max-400">
<textarea rows="3" type="text" name="descriptions[{{ $language['code'] }}][summary]" class="form-control wp-400" placeholder="{{ __('page_category.text_summary') }}">{{ old('descriptions.' . $language['code'] . '.summary', $descriptions[$language['code']]['summary'] ?? '') }}</textarea>
</div>
</x-admin::form.row>
<x-admin::form.row title="{{ __('admin/page.info_content') }}">
<div class="w-max-1000">
<textarea name="descriptions[{{ $language['code'] }}][content]" data-tinymce-height="600" class="form-control tinymce">
{{ old('descriptions.' . $language['code'] . '.content', $descriptions[$language['code']]['content'] ?? '') }}
</textarea>
</div>
@if ($errors->has("descriptions.{$language['code']}.content"))
<span class="invalid-feedback d-block" role="alert">{{ $errors->first("descriptions.{$language['code']}.content") }}</span>
@endif
</x-admin::form.row>
<input type="hidden" name="descriptions[{{ $language['code'] }}][locale]" value="{{ $language['code'] }}">
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_title]" title="{{ __('admin/setting.meta_title') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_title', $descriptions[$language['code']]['meta_title'] ?? '') }}" />
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_description]" title="{{ __('admin/setting.meta_description') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_description', $descriptions[$language['code']]['meta_description'] ?? '') }}" />
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_keywords]" title="{{ __('admin/setting.meta_keywords') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_keywords', $descriptions[$language['code']]['meta_keywords'] ?? '') }}" />
</div>
@endforeach
</div>
</div>
<div class="tab-pane fade" id="tab-data">
@hook('admin.page.data.before')
<x-admin-form-input name="author" title="{{ __('page_category.author') }}" value="{{ old('author', $page->author ?? '') }}" />
<x-admin::form.row title="{{ __('admin/page_category.index') }}">
<div class="wp-400">
<el-autocomplete
v-model="page_category_name"
value-key="name"
size="small"
name="category_name"
class="w-100"
:fetch-suggestions="(keyword, cb) => {relationsQuerySearch(keyword, cb, 'page_categories')}"
placeholder="{{ __('common.input') }}"
@select="(e) => {handleSelect(e, 'page_categories')}"
></el-autocomplete>
<input type="hidden" name="page_category_id" :value="page_category_name ? page_category_id : ''" />
</div>
</x-admin::form.row>
<x-admin-form-input name="views" title="{{ __('page_category.views') }}" value="{{ old('views', $page->views ?? '') }}" />
<x-admin::form.row title="{{ __('admin/product.product_relations') }}">
<div class="module-edit-group wp-600">
<div class="autocomplete-group-wrapper">
<el-autocomplete
class="inline-input"
v-model="relations.keyword"
value-key="name"
size="small"
:fetch-suggestions="(keyword, cb) => {relationsQuerySearch(keyword, cb, 'products')}"
placeholder="{{ __('admin/builder.modules_keywords_search') }}"
@select="(e) => {handleSelect(e, 'product_relations')}"
></el-autocomplete>
<div class="item-group-wrapper" v-loading="relations.loading">
<template v-if="relations.products.length">
<draggable
ghost-class="dragabble-ghost"
:list="relations.products"
:options="{animation: 330}"
>
<div v-for="(item, index) in relations.products" :key="index" class="item">
<div>
<i class="el-icon-s-unfold"></i>
<span>@{{ item.name }}</span>
</div>
<i class="el-icon-delete right" @click="relationsRemoveProduct(index)"></i>
<input type="text" :name="'products['+ index +']'" v-model="item.id" class="form-control d-none">
</div>
</draggable>
</template>
<template v-else>{{ __('admin/builder.modules_please_products') }}</template>
</div>
</div>
</div>
</x-admin::form.row>
@hook('admin.page.data.after')
<x-admin-form-switch name="active" title="{{ __('common.status') }}" value="{{ old('active', $page->active ?? 1) }}" />
</div>
</div>
<button type="submit" class="d-none">{{ __('common.save') }}</button>
</form>
</div>
</div>
@hook('admin.page.form.footer')
@endsection
@push('footer')
<script>
$('.submit-form').click(function () {
$('.needs-validation').find('button[type="submit"]').click()
})
var app = new Vue({
el: '#app',
data: {
relations: {
keyword: '',
products: [],
loading: null,
},
page_category_name: '{{ old('category_name', $page->category->description->title ?? '') }}',
page_category_id: '{{ old('categories_id', $page->category->id ?? '') }}',
},
created() {
const products = @json($page['products'] ?? []);
if (products.length) {
this.relations.products = products.map(v => {
return {
id: v.id,
name: v.description.name,
}
})
}
},
methods: {
relationsQuerySearch(keyword, cb, url) {
$http.get(url + '/autocomplete?name=' + encodeURIComponent(keyword), null, {hload:true}).then((res) => {
cb(res.data);
})
},
handleSelect(item, key) {
if (key == 'product_relations') {
if (!this.relations.products.find(v => v == item.id)) {
this.relations.products.push(item);
}
this.relations.keyword = ""
} else {
this.page_category_name = item.name
this.page_category_id = item.id
}
},
relationsRemoveProduct(index) {
this.relations.products.splice(index, 1);
},
}
})
</script>
@endpush

View File

@ -0,0 +1,97 @@
@extends('admin::layouts.master')
@section('title', __('admin/page.inquiry'))
@section('content')
@if ($errors->has('error'))
<x-admin-alert type="danger" msg="{{ $errors->first('error') }}" class="mt-4" />
@endif
<div class="card">
<div class="card-body h-min-600">
{{-- <div class="d-flex justify-content-between mb-4">--}}
{{-- <a href="{{ admin_route('pages.create') }}" class="btn btn-primary">{{ __('common.add') }}</a>--}}
{{-- </div>--}}
<div class="table-push">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>{{ __('common.sku') }}</th>
<th>{{ __('common.contacts') }}</th>
<th>{{ __('common.email') }}</th>
<th>{{ __('common.content') }}</th>
<th>{{ __('common.created_at') }}</th>
<th>{{ __('common.updated_at') }}</th>
@hook('admin.page.list.column')
<th class="text-end">{{ __('common.action') }}</th>
</tr>
</thead>
<tbody>
@if (count($pages_format))
@foreach ($pages_format as $page)
<tr>
<td>{{ $page['id'] }}</td>
<td>
<div >{{ $page['product_sku_sku'] ?? '' }}</div>
</td>
<td>
<div title="">{{ $page['contacts'] ?? '' }}</div><!-- <a class="text-dark" href="{/{ shop_route('pages.show', $page['id']) }}" target="_blank">{/{ $page['title_format'] ?? '' }}</a> -->
</td>
{{-- <td class="{{ $page['active'] ? 'text-success' : 'text-secondary' }}">--}}
{{-- {{ $page['active'] ? __('common.enable') : __('common.disable') }}--}}
{{-- </td>--}}
<td>
<div title="">{{ $page['email'] ?? '' }}</div>
</td>
<td>
<div title="">{{ $page['content'] ?? '' }}</div>
</td>
<td>{{ $page['created_at'] }}</td>
<td>{{ $page['updated_at'] }}</td>
@hook('admin.page.list.column_value')
<td class="text-end">
{{-- <a href="{{ admin_route('pages.edit', [$page['id']]) }}"--}}
{{-- class="btn btn-outline-secondary btn-sm">{{ __('common.edit') }}</a>--}}
<button class="btn btn-outline-danger btn-sm delete-btn" type='button'
data-id="{{ $page['id'] }}">{{ __('common.delete') }}</button>
@hook('admin.page.list.action')
</td>
</tr>
@endforeach
@else
<tr><td colspan="5" class="border-0"><x-admin-no-data /></td></tr>
@endif
</tbody>
</table>
</div>
{{ $pages->links('admin::vendor/pagination/bootstrap-4') }}
</div>
</div>
@hook('admin.page.list.content.footer')
@endsection
@push('footer')
<script>
$('.delete-btn').click(function(event) {
const id = $(this).data('id');
const self = $(this);
layer.confirm('{{ __('common.confirm_delete') }}', {
title: "{{ __('common.text_hint') }}",
btn: ['{{ __('common.cancel') }}', '{{ __('common.confirm') }}'],
area: ['400px'],
btn2: () => {
$http.delete(`inquiry/${id}`).then((res) => {
layer.msg(res.message);
window.location.reload();
})
}
})
});
</script>
@endpush

View File

@ -107,12 +107,26 @@
<div>
<h5 class="border-bottom pb-3 mb-4">{{ __('admin/product.stocks') }}</h5>
<x-admin::form.row :title="__('admin/product.enable_multi_spec')">
<el-switch v-model="editing.isVariable" @change="isVariableChange" class="mt-2"></el-switch>
<x-admin::form.row title="{{ __('admin/product.price_setting') }}">
<div class="mb-1 mt-2">
<div class="form-check form-check-inline">
<input v-model="form.price_setting" class="form-check-input" id="price_setting-sku" type="radio" name="price_setting" id="price_setting-sku" value="sku" {{ $product->price_setting == 'sku' ? 'checked' : '' }}>
<label class="form-check-label" for="price_setting-sku">{{ __('admin/product.price_setting_by.sku') }}</label>
</div>
<div class="form-check form-check-inline">
<input v-model="form.price_setting" class="form-check-input" id="price_setting-num" type="radio" name="price_setting" id="price_setting-num" value="num" {{ $product->price_setting == 'num' ? 'checked' : '' }}>
<label class="form-check-label" for="price_setting-num">{{ __('admin/product.price_setting_by.num') }}</label>
</div>
</div>
</x-admin::form.row>
<input type="hidden" name="variables" :value="JSON.stringify(form.variables)">
<span v-if="form.price_setting === 'sku'">
<x-admin::form.row :title="__('admin/product.enable_multi_spec')">
<el-switch v-model="editing.isVariable" @change="isVariableChange" class="mt-2"></el-switch>
</x-admin::form.row>
<div class="row g-3 mb-3" v-if="editing.isVariable">
<label for="" class="wp-200 col-form-label text-end"></label>
<div class="col-auto wp-200-">
@ -277,6 +291,68 @@
<input type="hidden" name="skus[0][position]" placeholder="position" value="0">
<input type="hidden" name="skus[0][is_default]" placeholder="is_default" value="1">
</template>
</span>
<span v-if="form.price_setting === 'num'">
{{-- 阶梯价格设置表格 (最小起订量,产品价格),预览,可以增加删除价格区间--}}
<div class="price_setting_by_num">
<div class="left">
<div class="head">
<div class="num" >最小起订量</div>
<div class="price">产品价格</div>
<div class="delete">删除</div>
</div>
<div class="body">
<div class="item" v-for="(item,index) in form.numPrices">
<div class="num">
<div class="top">
<div class="title"></div>
<input type="number" :name="'numPrices[' + index + '][num]'" v-model="item.num" value="" placeholder="数量" required="required" class="form-control wp-100">
</div>
<div class="tip" ><span style="visibility: hidden;">_</span><span v-if="item.num">@{{ item.num }}</span></div>
</div>
<div class="price">
<input type="number" :name="'numPrices[' + index + '][price]'" v-model="item.price" value="" placeholder="价格" required="required" step="any" class="form-control wp-100">
<div class="tip" v-if="item.price">@{{ item.num }}件等于价格:@{{ item.num * item.price }}</div>
</div>
<div class="delete" @click="removeNumPrices(index)"><a style="cursor: pointer;color: #0072ff;">删除</a></div>
</div>
</div>
<div class="bottom">
<a style="cursor: pointer;color: #0072ff;" @click="addNumPrices"> {{ __('admin/product.add_variable') }}</a>
</div>
</div>
<div class="right">
<div class="head">
预览
</div>
<div class="body">
<div class="item" v-for="(item,index) in form.numPrices">
<div class="num" v-if="index < form.numPrices.length - 1">@{{ item.num }} ~ @{{ form.numPrices[index + 1].num - 1 }}</div>
<div class="num" v-else-if="item.num">@{{ item.num }}</div>
<div class="price" v-if="item.price">价格@{{ item.price }}</div>
</div>
</div>
</div>
</div>
<input class="form-control d-none" :value="numPricesIsEmpty" required>
<div class="invalid-feedback" style="font-size: 16px;margin-left: 200px;"><i class="bi bi-exclamation-circle-fill"></i> 新增价格区间</div>
<template>
<input type="hidden" value="{{ old('skus.0.image', $product->skus[0]->image ?? '') }}" name="skus[0][image]">
<x-admin-form-input name="skus[0][model]" :title="__('admin/product.model')" :value="old('skus.0.model', $product->skus[0]->model ?? '')" />
<x-admin-form-input name="skus[0][sku]" title="sku" :value="old('skus.0.sku', $product->skus[0]->sku ?? '')" required />
{{-- <x-admin-form-input name="skus[0][price]" type="number" :title="__('admin/product.price')" :value="old('skus.0.price', $product->skus[0]->price ?? '')" step="any" required />--}}
{{-- <x-admin-form-input name="skus[0][origin_price]" type="number" :title="__('admin/product.origin_price')" :value="old('skus.0.origin_price', $product->skus[0]->origin_price ?? '')" step="any" required />--}}
<x-admin-form-input name="skus[0][cost_price]" type="number" :title="__('admin/product.cost_price')" :value="old('skus.0.cost_price', $product->skus[0]->cost_price ?? '')" />
<x-admin-form-input name="skus[0][quantity]" type="number" :title="__('admin/product.quantity')" :value="old('skus.0.quantity', $product->skus[0]->quantity ?? '')" />
<input type="hidden" name="skus[0][price]" placeholder="variants" :value="form.numPrices.length !== 0 ? form.numPrices[0].price : ''">
<input type="hidden" name="skus[0][origin_price]" placeholder="position" :value="form.numPrices.length !== 0 ? form.numPrices[form.numPrices.length - 1].price : ''">
<input type="hidden" name="skus[0][variants]" placeholder="variants" value="">
<input type="hidden" name="skus[0][position]" placeholder="position" value="0">
<input type="hidden" name="skus[0][is_default]" placeholder="is_default" value="1">
</template>
</span>
</div>
</div>
<div class="tab-pane fade" id="tab-descriptions">
@ -490,6 +566,8 @@
status: @json($product->skus[0]['status'] ?? false),
variables: @json($product->variables ?? []),
skus: @json(old('skus', $product->skus) ?? []),
price_setting: @json(old('price_setting', $product->price_setting) ?? 'sku'),
numPrices: @json(old('numPrices', $product->numprices) ?? []),
},
variablesBatch: {
@ -555,6 +633,10 @@
skuIsEmpty() {
return (this.form.skus.length && this.form.skus[0].variants.length) || ''
},
numPricesIsEmpty() {
return (this.form.numPrices.length && this.form.price_setting === 'num') || this.form.price_setting === 'sku' || ''
}
},
@ -589,10 +671,24 @@
}
},
created() {
if(this.form.numPrices.length === 0){
this.addNumPrices();
}
},
methods: {
addNumPrices() {
this.form.numPrices.push({num: '', price: ''});
},
removeNumPrices(index) {
this.form.numPrices.splice(index, 1)
},
// 表单提交,检测是否开启多规格 做处理
productsSubmit() {
if (!this.editing.isVariable) {
if (!this.editing.isVariable || this.form.price_setting === 'num') {
this.source.variables = [];
}
},
@ -948,5 +1044,104 @@
}
});
});
</script>
</script lang="scss">
<style>
.price_setting_by_num{
display: flex;
margin-left: 200px;
border: 1px solid #aaaaaa;
border-radius: 15px;
margin-bottom: 20px;
}
.left{
flex: 4;
border-right: 1px solid #aaaaaa;
.head{
display: flex;
border-bottom: 1px solid #aaaaaa;
padding: 8px 0px 8px 30px;
.num{
flex: 4;
}
.price{
flex: 4;
}
.delete{
visibility: hidden;
flex: 1;
}
}
.body{
.item {
display: flex;
border-bottom: 1px solid #aaaaaa;
padding: 20px 0px 20px 30px;
.num {
flex: 4;
.top {
display: flex;
.title {
line-height: 33px;
margin-right: 10px;
}
}
.tip {
font-size: 12px;
color: #aaaaaa;
margin: 10px 0 0 20px;
}
}
.price {
flex: 4;
.tip {
font-size: 12px;
color: #aaaaaa;
margin: 10px 0 0 0;
}
}
.delete {
flex: 1;
}
}
}
.bottom {
padding: 8px 0px 8px 30px;
}
}
.right{
flex: 1;
.head{
border-bottom: 1px solid #aaaaaa;
padding: 8px 0px 8px 10px;
}
.body{
.item{
padding: 8px 0px 8px 10px;
display: flex;
.num{
flex: 1;
}
.price{
flex: 1;
}
}
}
}
}
</style>
@endpush

View File

@ -71,6 +71,10 @@ $(function () {
})
function updateMiniCartData(res) {
let cart_item_price = $('.offcanvas-right-cart-item-price');
for(var i = 0 ; i< cart_item_price.length ;i++){
cart_item_price.contents()[i].nodeValue =' ' + res.data.carts[i].price_format + ' x ';
}
$('.offcanvas-right-cart-count').text(res.data.quantity);
$('.offcanvas-right-cart-amount').text(res.data.amount_format);
}

View File

@ -19,4 +19,5 @@ return [
'pages_show' => 'Detail',
'pages_update' => 'Edit',
'pages_delete' => 'Delete',
'inquiry' => 'Inquiry',
];

View File

@ -86,6 +86,10 @@ return [
'error_page' => 'The data you accessed does not exist or has been deleted~',
'error_page_btn' => 'Return to previous page',
'contacts' => 'Contacts',
'content' => 'Content',
'sku' => 'Sku',
'order' => [
'unpaid' => 'Unpaid',
'paid' => 'Paid',

View File

@ -18,4 +18,9 @@ return [
'in_stock' => 'In Stock',
'out_stock' => 'Out Stock',
'model' => 'Model',
'quantity_error' => 'Quantity Error',
'inquiry' => 'Inquiry',
'enter_contacts' => 'Please enter contacts',
'enter_email' => 'Please enter email',
'enter_content' => 'Please enter content',
];

View File

@ -19,4 +19,6 @@ return [
'pages_show' => '文章详情',
'pages_update' => '文章编辑',
'pages_delete' => '删除文章',
'inquiry' => '询盘管理',
];

View File

@ -31,7 +31,7 @@ return [
'price' => '价格',
'origin_price' => '原价',
'cost_price' => '成本价',
'quantity' => '数量',
'quantity' => '库存数量',
'enable_multi_spec' => '启用多规格',
'image_help' => '第一张图片将作为商品主图,支持同时上传多张图片,多张图片之间可随意调整位置',
'add_variable' => '添加规格',
@ -44,4 +44,9 @@ return [
'confirm_batch_status' => '确认要批量修改选中的商品的状态吗?',
'confirm_batch_restore' => '确认要恢复选中的商品吗?',
'confirm_delete_restore' => '确认要清空回收站吗?',
'price_setting' => '价格设置',
'price_setting_by' =>[
'sku' => '根据规格设置价格',
'num' => '根据数量设置价格',
]
];

View File

@ -85,6 +85,11 @@ return [
'error_page' => '您访问的数据不存在或已被删除~',
'error_page_btn' => '返回上一页',
'contacts' => '联系人',
'content' => '内容',
'sku' => 'Sku',
'order' => [
'unpaid' => '待支付',
'paid' => '已支付',

View File

@ -18,4 +18,9 @@ return [
'in_stock' => '有货',
'out_stock' => '缺货',
'model' => '型号',
'quantity_error' => '数量错误',
'inquiry' => '咨询',
'enter_contacts' => '请输入联系人',
'enter_email' => '请输入邮件',
'enter_content' => '请输入内容',
];

View File

@ -13,13 +13,13 @@
<div class="select-wrap">
<i class="bi {{ $cart['selected'] ? 'bi-check-circle-fill' : 'bi-circle' }}" data-id="{{ $cart['cart_id'] }}"></i>
</div>
<div class="product-info d-flex align-items-center">
<div class="product-info d-flex align-items-center" style="width: 100%;">
<div class="left"><a href="{{ shop_route('products.show', $cart['product_id']) }}" class="d-flex justify-content-between align-items-center h-100"><img src="{{ $cart['image'] }}" class="img-fluid"></a></div>
<div class="right flex-grow-1">
<a href="{{ shop_route('products.show', $cart['product_id']) }}" class="name fs-sm fw-bold mb-2 text-dark text-truncate-2" title="{{ $cart['name'] }}">{{ $cart['name'] }}</a>
<div class="text-muted mb-1 text-size-min">{{ $cart['variant_labels'] }}</div>
<div class="product-bottom d-flex justify-content-between align-items-center">
<div class="price d-flex align-items-center">
<div class="price d-flex align-items-center offcanvas-right-cart-item-price">
{{ $cart['price_format'] }} x
<input type="text" onkeyup="this.value=this.value.replace(/\D/g,'')" onafterpaste="this.value=this.value.replace(/\D/g,'')"
data-id="{{ $cart['cart_id'] }}" data-sku="{{ $cart['sku_id'] }}" class="form-control p-1" value="{{ $cart['quantity'] }}">

View File

@ -9,6 +9,10 @@
<script src="{{ asset('vendor/swiper/swiper-bundle.min.js') }}"></script>
<script src="{{ asset('vendor/zoom/jquery.zoom.min.js') }}"></script>
<link rel="stylesheet" href="{{ asset('vendor/swiper/swiper-bundle.min.css') }}">
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
@endpush
@section('content')
@ -55,12 +59,33 @@
@hookwrapper('product.detail.name')
<h1 class="mb-4 product-name">{{ $product['name'] }}</h1>
@endhookwrapper
<div class="price-wrap d-flex align-items-end">
<div class="new-price fs-1 lh-1 fw-bold me-2">@{{ product.price_format }}@{{ price_setting }}</div>
<div class="price-wrap d-flex align-items-end" v-if="price_setting === 'sku'">
<div class="new-price fs-1 lh-1 fw-bold me-2">@{{ product.price_format }}</div>
<div class="old-price text-muted text-decoration-line-through" v-if="product.price != product.origin_price && product.origin_price !== 0">
@{{ product.origin_price_format }}
</div>
</div>
<div class="price-wrap d-flex align-items-end" v-if="price_setting === 'num'" style="
border-top: 1px solid #aaaaaa;
border-bottom: 1px solid #aaaaaa;
padding: 10px;">
<div class="item" v-for="(item,index) in numPrices" style="flex: 1">
<div class="num" v-if="index < numPrices.length - 1" style="
color: #777777;
font-size: 18px;
margin-bottom: 5px;
">@{{ item.num }} - @{{ numPrices[index + 1].num - 1 }} sets</div>
<div class="num" v-else-if="item.num" style="
color: #777777;
font-size: 18px;
margin-bottom: 5px;
">>= @{{ item.num }} sets</div>
<div class="price">
<div class="new-price fs-3 lh-1 fw-bold me-2">@{{ item.price_format }}</div>
</div>
</div>
</div>
<div class="stock-and-sku mb-4">
@hookwrapper('product.detail.quantity')
<div class="d-flex">
@ -139,6 +164,12 @@
@click="addCart(true, this)"
><i class="bi bi-bag-fill me-1"></i>{{ __('shop/products.buy_now') }}
</button>
<button
class="btn btn-outline-dark ms-md-3 add-cart fw-bold"
:disabled="!product.quantity"
@click="centerDialogVisable = true"
><i class="bi bi-globe me-1"></i>{{ __('shop/products.inquiry') }}
</button>
@hook('product.detail.buy.after')
</div>
<div class="add-wishlist">
@ -152,7 +183,42 @@
</div>
</div>
</div>
<!-- 弹出层 -->
<el-dialog title="Inquiry" :visible.sync="centerDialogVisable" width="400px" center>
<el-form :model="registerForm" ref="registerForm" label-width="90px" :rules="rules">
<!-- 弹出层歌手名列 -->
<el-form-item prop="contacts" label="Contacts" size="mini">
<el-input v-model="registerForm.contacts" placeholder="Contacts"></el-input>
</el-form-item>
<!-- 弹出层歌手性别列 -->
{{-- <el-form-item prop="sex" label="歌手性别" size="mini">--}}
{{-- <el-radio-group v-model="registerForm.sex">--}}
{{-- <el-radio :label="0"></el-radio>--}}
{{-- <el-radio :label="1"></el-radio>--}}
{{-- <el-radio :label="2">组合</el-radio>--}}
{{-- <el-radio :label="3">不明</el-radio>--}}
{{-- </el-radio-group>--}}
{{-- </el-form-item>--}}
<!-- <el-form-item prop="pic" label="歌手头像" size="mini">
<el-input v-model="registerForm.pic" placeholder="歌手头像"></el-input>
</el-form-item> -->
<!-- 弹出层歌手生日列 -->
{{-- <el-form-item prop="birth" label="生日" size="mini">--}}
{{-- <el-date-picker type="date" placeholder="选择日期" v-model="registerForm.birth" style="width: 100%;"></el-date-picker>--}}
{{-- </el-form-item>--}}
<el-form-item prop="email" label="E-mail" size="mini">
<el-input v-model="registerForm.email" placeholder="E-mail"></el-input>
</el-form-item>
<el-form-item prop="content" label="Content" size="mini">
<el-input v-model="registerForm.content" placeholder="Content" type="textarea"></el-input>
</el-form-item>
</el-form>
<!-- 取消,确定按钮点击事件 -->
<span slot="footer">
{{-- <el-button size="mini" @click="centerDialogVisable = false">Cancel</el-button>--}}
<el-button size="mini" @click="addSinger('registerForm')">Submit</el-button>
</span>
</el-dialog>
<div class="product-description">
<div class="nav nav-tabs nav-overflow justify-content-start justify-content-md-center border-bottom mb-3">
<a class="nav-link fw-bold active fs-5" data-bs-toggle="tab" href="#product-description">
@ -239,6 +305,39 @@
skus: @json($product['skus']),
variables: @json($product['variables'] ?? []),
},
price_setting: @json($product['price_setting'] ?? 'sku'),
numPrices: @json($product['numPrices'] ?? []),
centerDialogVisable:false, // 设置显示框的状态
registerForm:{ // 添加的信息
contacts:'',
email:'',
content:'',
product_sku_id:'',
},
rules: {
contacts: [{
required: true,
message: '{{ __('shop/products.enter_contacts') }}',
trigger: 'blur'
}, ],
email: [{
required: true,
type: 'email',
message: '{{ __('shop/products.enter_email') }}',
trigger: 'blur'
}, ],
content: [{
required: true,
message: '{{ __('shop/products.enter_content') }}',
trigger: 'blur'
}, ],
},
},
created() {
if(this.price_setting === 'num'){
this.quantity = this.numPrices[0].num;
}
},
computed: {
@ -269,6 +368,28 @@
},
methods: {
addSinger(form){ // 点击确定按钮添加方法
this.$refs[form].validate((valid) => {
if (!valid) {
this.$message.error('{{ __('shop/checkout.check_form') }}');
return;
}
this.registerForm.product_sku_id = this.product.id
$http.post(`/inquiry`, this.registerForm).then((res) => {
layer.msg('Success')
this.registerForm = {
contacts:'',
email:'',
content:'',
product_sku_id:'',
}
})
this.centerDialogVisable=false
});
},
checkedVariableValue(variable_idnex, value_index, value) {
$('.product-image .swiper .swiper-slide').eq(0).addClass('active').siblings().removeClass('active');
this.source.variables[variable_idnex].values.forEach((v, i) => {
@ -305,6 +426,11 @@
},
addCart(isBuyNow = false) {
if(this.price_setting === 'num' && this.quantity < this.numPrices[0].num){
layer.msg( '{{ __('shop/products.quantity_error') }}' );
return;
}
bk.addCart({sku_id: this.product.id, quantity: this.quantity, isBuyNow});
},