【增加】物流及可视化
This commit is contained in:
parent
bfb1ea283c
commit
02516b7825
|
|
@ -57,4 +57,16 @@ class CountryController extends Controller
|
|||
|
||||
return json_success(trans('common.deleted_success'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function autocomplete(Request $request): array
|
||||
{
|
||||
$brands = CountryRepo::autocomplete($request->get('name') ?? '', 0);
|
||||
|
||||
return json_success(trans('common.get_success'), $brands);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use Beike\Admin\Http\Resources\LogisticsAttributeResource;
|
|||
use Beike\Admin\Http\Resources\LogisticsResource;
|
||||
use Beike\Admin\Repositories\TaxClassRepo;
|
||||
use Beike\Admin\Services\LogisticsService;
|
||||
use Beike\Admin\View\DesignBuilders\Product;
|
||||
use Beike\Libraries\Weight;
|
||||
use Beike\Models\Logistics;
|
||||
use Beike\Repositories\CategoryRepo;
|
||||
|
|
@ -24,24 +25,23 @@ class LogisticsController extends Controller
|
|||
if (! isset($requestData['sort'])) {
|
||||
$requestData['sort'] = 'logistics.updated_at';
|
||||
}
|
||||
$productList = LogisticsRepo::list($requestData);
|
||||
$products = LogisticsResource::collection($productList);
|
||||
$productsFormat = $products->jsonSerialize();
|
||||
$logisticsList = LogisticsRepo::list($requestData);
|
||||
$logistics = LogisticsResource::collection($logisticsList);
|
||||
$logisticsFormat = $logistics->jsonSerialize();
|
||||
|
||||
$data = [
|
||||
'categories' => CategoryRepo::flatten(locale()),
|
||||
'products_format' => $productsFormat,
|
||||
'products' => $products,
|
||||
'type' => 'products',
|
||||
'logistics_format' => $logisticsFormat,
|
||||
'logistics' => $logistics,
|
||||
'type' => 'logistics',
|
||||
];
|
||||
|
||||
$data = hook_filter('admin.product.index.data', $data);
|
||||
$data = hook_filter('admin.logistics.index.data', $data);
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return $productsFormat;
|
||||
return $logisticsFormat;
|
||||
}
|
||||
|
||||
return view('admin::pages.products.index', $data);
|
||||
return view('admin::pages.logistics.index', $data);
|
||||
}
|
||||
|
||||
public function trashed(Request $request)
|
||||
|
|
@ -77,40 +77,40 @@ class LogisticsController extends Controller
|
|||
{
|
||||
try {
|
||||
$requestData = $request->all();
|
||||
$product = (new LogisticsService)->create($requestData);
|
||||
$logistics = (new LogisticsService)->create($requestData);
|
||||
|
||||
$data = [
|
||||
'request_data' => $requestData,
|
||||
'product' => $product,
|
||||
'logistics' => $logistics,
|
||||
];
|
||||
|
||||
hook_action('admin.product.store.after', $data);
|
||||
hook_action('admin.logistics.store.after', $data);
|
||||
|
||||
return redirect()->to(admin_route('products.index'))
|
||||
return redirect()->to(admin_route('logistics.index'))
|
||||
->with('success', trans('common.created_success'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect(admin_route('products.create'))
|
||||
return redirect(admin_route('logistics.create'))
|
||||
->withInput()
|
||||
->withErrors(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function edit(Request $request, Logistics $product)
|
||||
public function edit(Request $request, Logistics $logistics)
|
||||
{
|
||||
return $this->form($request, $product);
|
||||
return $this->form($request, $logistics);
|
||||
}
|
||||
|
||||
public function update(LogisticsRequest $request, Logistics $product)
|
||||
public function update(LogisticsRequest $request, Logistics $logistics)
|
||||
{
|
||||
try {
|
||||
$requestData = $request->all();
|
||||
$product = (new LogisticsService)->update($product, $requestData);
|
||||
$logistics = (new LogisticsService)->update($logistics, $requestData);
|
||||
|
||||
$data = [
|
||||
'request_data' => $requestData,
|
||||
'product' => $product,
|
||||
'logistics' => $logistics,
|
||||
];
|
||||
hook_action('admin.product.update.after', $data);
|
||||
hook_action('admin.logistics.update.after', $data);
|
||||
|
||||
return redirect()->to($this->getRedirect())->with('success', trans('common.updated_success'));
|
||||
} catch (\Exception $e) {
|
||||
|
|
@ -134,10 +134,10 @@ class LogisticsController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
public function destroy(Request $request, Logistics $product)
|
||||
public function destroy(Request $request, Logistics $logistics)
|
||||
{
|
||||
$product->delete();
|
||||
hook_action('admin.product.destroy.after', $product);
|
||||
$logistics->delete();
|
||||
hook_action('admin.logistics.destroy.after', $logistics);
|
||||
|
||||
return json_success(trans('common.deleted_success'));
|
||||
}
|
||||
|
|
@ -155,12 +155,15 @@ class LogisticsController extends Controller
|
|||
protected function form(Request $request, Logistics $logistics)
|
||||
{
|
||||
|
||||
$logistics = hook_filter('admin.logistics.form.product', $logistics);
|
||||
|
||||
$logistics = hook_filter('admin.logistics.form.logistics', $logistics);
|
||||
$types = [
|
||||
['title' => 'weight','name'=>trans('admin/logistics.type_weight')],
|
||||
['title' => 'num','name'=>trans('admin/logistics.type_num')],
|
||||
['title' => 'free','name'=>trans('admin/logistics.type_free')],
|
||||
];
|
||||
$data = [
|
||||
'logistics' => $logistics,
|
||||
'languages' => LanguageRepo::all(),
|
||||
'weight_classes' => Weight::getWeightUnits(),
|
||||
'types' => $types,
|
||||
'_redirect' => $this->getRedirect(),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/**
|
||||
* PageRequest.php
|
||||
*
|
||||
* @copyright 2022 beikeshop.com - All Rights Reserved
|
||||
* @link https://beikeshop.com
|
||||
* @author Edward Yang <yangjin@guangda.work>
|
||||
* @created 2022-08-19 21:58:20
|
||||
* @modified 2022-08-19 21:58:20
|
||||
*/
|
||||
|
||||
namespace Beike\Admin\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class LogisticsRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'string',
|
||||
'warehouse_name' => 'string',
|
||||
'country_id' => 'int',
|
||||
'type' => 'string',
|
||||
'first_weight' => 'numeric',
|
||||
'first_weight_fee' => 'numeric',
|
||||
'continuation_weight_max' => 'numeric',
|
||||
'add_weight' => 'numeric',
|
||||
'continuation_weight_fee' => 'numeric',
|
||||
'num_fee' => 'numeric',
|
||||
'throwing_ratio' => 'int',
|
||||
'day_min' => 'int',
|
||||
'day_max' => 'int',
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes()
|
||||
{
|
||||
return [
|
||||
'name' => trans('logistics.name'),
|
||||
'warehouse_name' => trans('logistics.warehouse_name'),
|
||||
'country_id' => trans('logistics.country_id'),
|
||||
'type' => trans('logistics.type'),
|
||||
'first_weight' => trans('logistics.first_weight'),
|
||||
'first_weight_fee' => trans('logistics.first_weight_fee'),
|
||||
'continuation_weight_max' => trans('logistics.continuation_weight_max'),
|
||||
'add_weight' => trans('logistics.add_weight'),
|
||||
'continuation_weight_fee' => trans('logistics.continuation_weight_fee'),
|
||||
'num_fee' => trans('logistics.num_fee'),
|
||||
'throwing_ratio' => trans('logistics.throwing_ratio'),
|
||||
'day_min' => trans('logistics.day_min'),
|
||||
'day_max' => trans('logistics.day_max'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Beike\Admin\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class LogisticsResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function toArray($request): array
|
||||
{
|
||||
|
||||
$data = [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name ?? '',
|
||||
'warehouse_name' => $this->warehouse_name ?? '',
|
||||
'country_id' => $this->country_id,
|
||||
'country' => $this->country ? $this->country->name : '',
|
||||
'type' => $this->type,
|
||||
'first_weight' => $this->first_weight,
|
||||
|
||||
'first_weight_fee' => $this->first_weight_fee,
|
||||
'continuation_weight_max' => $this->continuation_weight_max,
|
||||
'add_weight' => $this->add_weight,
|
||||
'continuation_weight_fee' => $this->continuation_weight_fee,
|
||||
'num_fee' => $this->num_fee,
|
||||
'throwing_ratio' => $this->throwing_ratio,
|
||||
'day_min' => $this->day_min,
|
||||
'day_max' => $this->day_max,
|
||||
'position' => $this->position,
|
||||
'created_at' => time_format($this->created_at),
|
||||
'deleted_at' => $this->deleted_at ? time_format($this->deleted_at) : '',
|
||||
'url_edit' => admin_route('logistics.edit', $this->id),
|
||||
];
|
||||
|
||||
return hook_filter('resource.product', $data);
|
||||
}
|
||||
}
|
||||
|
|
@ -71,6 +71,7 @@ Route::prefix($adminName)
|
|||
Route::middleware('can:countries_create')->post('countries', [Controllers\CountryController::class, 'store'])->name('countries.store');
|
||||
Route::middleware('can:countries_update')->put('countries/{id}', [Controllers\CountryController::class, 'update'])->name('countries.update');
|
||||
Route::middleware('can:countries_delete')->delete('countries/{id}', [Controllers\CountryController::class, 'destroy'])->name('countries.destroy');
|
||||
Route::middleware('can:countries_index')->get('countries/autocomplete', [Controllers\CountryController::class, 'autocomplete'])->name('countries.autocomplete');
|
||||
|
||||
// 省份
|
||||
Route::middleware('can:zones_index')->get('zones', [Controllers\ZoneController::class, 'index'])->name('zones.index');
|
||||
|
|
@ -222,6 +223,10 @@ Route::prefix($adminName)
|
|||
// 物流
|
||||
Route::middleware('can:logistics_index')->get('logistics', [Controllers\LogisticsController::class, 'index'])->name('logistics.index');
|
||||
Route::middleware('can:logistics_create')->get('logistics/create', [Controllers\LogisticsController::class, 'create'])->name('logistics.create');
|
||||
Route::middleware('can:logistics_create')->post('logistics', [Controllers\LogisticsController::class, 'store'])->name('logistics.store');
|
||||
Route::middleware('can:logistics_show')->get('logistics/{logistics}/edit', [Controllers\LogisticsController::class, 'edit'])->name('logistics.edit');
|
||||
Route::middleware('can:logistics_update')->put('logistics/{logistics}', [Controllers\LogisticsController::class, 'update'])->name('logistics.update');
|
||||
Route::middleware('can:logistics_delete')->delete('logistics/{logistics}', [Controllers\LogisticsController::class, 'destroy'])->name('logistics.destroy');
|
||||
|
||||
// 区域组
|
||||
Route::middleware('can:regions_index')->get('regions', [Controllers\RegionController::class, 'index'])->name('regions.index');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace Beike\Admin\Services;
|
||||
|
||||
use Beike\Models\Logistics;
|
||||
use Beike\Models\Product;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class LogisticsService
|
||||
{
|
||||
public function create(array $data): Logistics
|
||||
{
|
||||
$logistics = new Logistics;
|
||||
|
||||
return $this->createOrUpdate($logistics, $data);
|
||||
}
|
||||
|
||||
public function update(Logistics $logistics, array $data): Logistics
|
||||
{
|
||||
return $this->createOrUpdate($logistics, $data);
|
||||
}
|
||||
|
||||
public function copy(Product $oldProduct): Product
|
||||
{
|
||||
$product = new Product;
|
||||
$data = $oldProduct->toArray();
|
||||
$data['id'] = 0;
|
||||
$data['created_at'] = now();
|
||||
$data['variables'] = json_encode($data['variables'],JSON_UNESCAPED_UNICODE);
|
||||
$data['descriptions'] = $oldProduct->descriptions()->get()->toArray();
|
||||
foreach ($data['descriptions'] as $locale => $description) {
|
||||
$data['descriptions'][$description['locale']] = $description;
|
||||
unset($data['descriptions'][$locale]);
|
||||
}
|
||||
$data['attributes'] = $oldProduct->attributes()->get()->toArray();
|
||||
$data['skus'] = $oldProduct->skus()->get()->toArray();
|
||||
$data['numPrices'] = $oldProduct->numPrices()->get()->toArray();
|
||||
$data['categories'] = $oldProduct->categories()->get()->toArray();
|
||||
$data['relations'] = $oldProduct->relations()->get()->toArray();
|
||||
$data['categories'] = array_column($data['categories'],'id');
|
||||
$data['relations'] = array_column($data['relations'],'id');
|
||||
return $this->createOrUpdate($product, $data);
|
||||
}
|
||||
|
||||
protected function createOrUpdate(Logistics $logistics, array $data): Logistics
|
||||
{
|
||||
$isUpdating = $logistics->id > 0;
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$data['country_id'] = (int) ($data['country_id'] ?? 0);
|
||||
$data['throwing_ratio'] = (int) ($data['throwing_ratio'] ?? 0);
|
||||
$data['day_min'] = (int) ($data['day_min'] ?? 0);
|
||||
$data['day_max'] = (int) ($data['day_max'] ?? 0);
|
||||
$data['first_weight'] = (float) ($data['first_weight'] ?? 0);
|
||||
$data['first_weight_fee'] = (float) ($data['first_weight_fee'] ?? 0);
|
||||
$data['continuation_weight_max'] = (float) ($data['continuation_weight_max'] ?? 0);
|
||||
$data['add_weight'] = (float) ($data['add_weight'] ?? 0);
|
||||
$data['continuation_weight_fee'] = (float) ($data['continuation_weight_fee'] ?? 0);
|
||||
$data['num_fee'] = (float) ($data['num_fee'] ?? 0);
|
||||
$data['variables'] = json_decode($data['variables'] ?? '[]');
|
||||
$logistics->fill($data);
|
||||
$logistics->updated_at = now();
|
||||
$logistics->save();
|
||||
|
||||
if ($isUpdating) {
|
||||
}
|
||||
|
||||
|
||||
DB::commit();
|
||||
|
||||
return $logistics;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ class Logistics extends Base
|
|||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = ['name', 'warehouse_name', 'country_id', 'type', 'first_weight', 'first_weight_fee', 'continuation_weight_max', 'add_weight', 'continuation_weight_fee', 'throwing_ratio', 'num_fee', 'day_min', 'day_max'];
|
||||
protected $fillable = ['name', 'warehouse_name', 'country_id', 'type', 'first_weight', 'first_weight_fee', 'continuation_weight_max', 'add_weight', 'continuation_weight_fee', 'throwing_ratio', 'num_fee', 'day_min', 'day_max','position'];
|
||||
|
||||
public function country()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -121,4 +121,16 @@ class CountryRepo
|
|||
{
|
||||
return Country::query()->select('id', 'name')->get();
|
||||
}
|
||||
|
||||
public static function autocomplete($name, $onlyActive = 1)
|
||||
{
|
||||
$builder = Country::query()
|
||||
->where('name', 'like', "$name%")
|
||||
->select('id', 'name', 'status');
|
||||
// if ($onlyActive) {
|
||||
// $builder->where('status', 1);
|
||||
// }
|
||||
|
||||
return $builder->limit(10)->get();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,18 +81,12 @@ class LogisticsRepo
|
|||
*/
|
||||
public static function getBuilder(array $filters = []): Builder
|
||||
{
|
||||
$builder = Logistics::query()->with('description', 'skus', 'masterSku', 'attributes');
|
||||
$builder = Logistics::query()->with('country');
|
||||
|
||||
$builder->leftJoin('product_descriptions as pd', function ($build) {
|
||||
$build->whereColumn('pd.product_id', 'products.id')
|
||||
->where('locale', locale());
|
||||
$builder->leftJoin('countries as c', function ($build) {
|
||||
$build->whereColumn('c.id', 'logistics.id');
|
||||
});
|
||||
$builder->leftJoin('product_skus', function ($build) {
|
||||
$build->whereColumn('product_skus.product_id', 'products.id')
|
||||
->where('is_default', 1)
|
||||
->where('product_skus.deleted_at', null);
|
||||
});
|
||||
$builder->select(['products.*', 'pd.name', 'pd.content', 'pd.meta_title', 'pd.meta_description', 'pd.meta_keywords', 'pd.name', 'product_skus.price']);
|
||||
$builder->select(['logistics.*', 'c.name as country_name']);
|
||||
|
||||
if (isset($filters['category_id'])) {
|
||||
$builder->whereHas('categories', function ($query) use ($filters) {
|
||||
|
|
@ -104,12 +98,6 @@ class LogisticsRepo
|
|||
});
|
||||
}
|
||||
|
||||
$productIds = $filters['product_ids'] ?? [];
|
||||
if ($productIds) {
|
||||
$builder->whereIn('products.id', $productIds);
|
||||
$productIds = implode(',', $productIds);
|
||||
$builder->orderByRaw("FIELD(products.id, {$productIds})");
|
||||
}
|
||||
|
||||
// attr 格式:attr=10:10,13|11:34,23|3:4
|
||||
if (isset($filters['attr']) && $filters['attr']) {
|
||||
|
|
@ -145,54 +133,22 @@ class LogisticsRepo
|
|||
});
|
||||
}
|
||||
|
||||
if (isset($filters['name'])) {
|
||||
$builder->where('pd.name', 'like', "%{$filters['name']}%");
|
||||
}
|
||||
|
||||
$keyword = trim($filters['keyword'] ?? '');
|
||||
if ($keyword) {
|
||||
$keywords = explode(' ', $keyword);
|
||||
$keywords = array_unique($keywords);
|
||||
$keywords = array_diff($keywords, ['']);
|
||||
$builder->where(function (Builder $query) use ($keywords) {
|
||||
$query->whereHas('skus', function (Builder $query) use ($keywords) {
|
||||
$keywordFirst = array_shift($keywords);
|
||||
$query->where('sku', 'like', "%{$keywordFirst}%")
|
||||
->orWhere('model', 'like', "%{$keywordFirst}%");
|
||||
|
||||
foreach ($keywords as $keyword) {
|
||||
$query->orWhere('sku', 'like', "%{$keyword}%")
|
||||
->orWhere('model', 'like', "%{$keyword}%");
|
||||
}
|
||||
});
|
||||
foreach ($keywords as $keyword) {
|
||||
$query->orWhere('pd.name', 'like', "%{$keyword}%");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($filters['created_start'])) {
|
||||
$builder->where('products.created_at', '>', $filters['created_start']);
|
||||
$builder->where('logistics.created_at', '>', $filters['created_start']);
|
||||
}
|
||||
|
||||
if (isset($filters['created_end'])) {
|
||||
$builder->where('products.created_at', '>', $filters['created_end']);
|
||||
$builder->where('logistics.created_at', '>', $filters['created_end']);
|
||||
}
|
||||
|
||||
if (isset($filters['active'])) {
|
||||
$builder->where('active', (int) $filters['active']);
|
||||
}
|
||||
|
||||
// 回收站
|
||||
if (isset($filters['trashed']) && $filters['trashed']) {
|
||||
$builder->onlyTrashed();
|
||||
}
|
||||
|
||||
$sort = $filters['sort'] ?? 'products.position';
|
||||
$sort = $filters['sort'] ?? 'logistics.position';
|
||||
$order = $filters['order'] ?? 'desc';
|
||||
$builder->orderBy($sort, $order);
|
||||
|
||||
return hook_filter('repo.product.builder', $builder);
|
||||
return hook_filter('repo.logistics.builder', $builder);
|
||||
}
|
||||
|
||||
public static function parseFilterParamsAttr($attr)
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ class ProductDetail extends JsonResource
|
|||
return array_map(function ($item) use ($lang) {
|
||||
return [
|
||||
'name' => $item['name'][$lang] ?? '',
|
||||
'isNumSelect' => $item['isNumSelect'] ?? FALSE,
|
||||
'values' => array_map(function ($item) use ($lang) {
|
||||
return [
|
||||
'name' => $item['name'][$lang] ?? '',
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
@extends('admin::layouts.master')
|
||||
|
||||
@section('title', __('admin/product.logistics_show'))
|
||||
@section('title', __('admin/logistics.logistics_show'))
|
||||
|
||||
@section('body-class', 'page-product-form')
|
||||
@section('body-class', 'page-logistics-form')
|
||||
|
||||
@push('header')
|
||||
<script src="{{ asset('vendor/vue/Sortable.min.js') }}"></script>
|
||||
|
|
@ -28,591 +28,96 @@
|
|||
|
||||
<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-basic" 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-descriptions" type="button">{{ __('admin/product.product_details') }}</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-attribute" type="button">{{ __('admin/attribute.index') }}</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-seo" type="button" >SEO</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-relations" type="button">{{ __('admin/product.product_relations') }}</button>
|
||||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tab-basic" type="button" >{{ __('admin/logistics.basic_information') }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body h-min-600">
|
||||
<form novalidate class="needs-validation" action="{{ $product->id ? admin_route('logistics.update', $product) : admin_route('logistics.store') }}"
|
||||
<form novalidate class="needs-validation" action="{{ $logistics->id ? admin_route('logistics.update', $logistics) : admin_route('logistics.store') }}"
|
||||
method="POST" id="app">
|
||||
@csrf
|
||||
@method($product->id ? 'PUT' : 'POST')
|
||||
@method($logistics->id ? 'PUT' : 'POST')
|
||||
<input type="hidden" name="_redirect" value="{{ $_redirect }}" />
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tab-basic">
|
||||
<h6 class="border-bottom pb-3 mb-4">{{ __('common.data') }}</h6>
|
||||
<x-admin-form-input-locale
|
||||
:width="600" name="descriptions.*.name" title="{{ __('common.name') }}"
|
||||
:value="$descriptions" :required="true" />
|
||||
@hook('admin.product.name.after')
|
||||
<x-admin::form.row title="{{ __('common.image') }}">
|
||||
<draggable
|
||||
element="div"
|
||||
ghost-class="dragabble-ghost"
|
||||
class="product-images d-flex flex-wrap"
|
||||
:list="form.images"
|
||||
:options="{animation: 200, handle: '.product-item'}"
|
||||
>
|
||||
<div v-for="image, index in form.images" :key="index" class="wh-80 rounded-2 product-item position-relative me-2 mb-2 border d-flex justify-content-center align-items-center max-h-100 overflow-hidden">
|
||||
<div class="position-absolute top-0 end-0">
|
||||
<button class="btn btn-danger btn-sm wh-20 p-0" @click="removeImages(index)" type="button"><i class="bi bi-trash"></i></button>
|
||||
</div>
|
||||
<img :src="thumbnail(image)" class="img-fluid rounded-2">
|
||||
<input type="hidden" name="images[]" :value="image">
|
||||
</div>
|
||||
<div v-if="!form.images.length" class="d-none"><input type="hidden" name="images[]" value=""></div>
|
||||
<div class="set-product-img wh-80 rounded-2" @click="addProductImages"><i class="bi bi-plus fs-1 text-muted"></i></div>
|
||||
</draggable>
|
||||
<div class="help-text mb-1 mt-1">{{ __('admin/product.image_help') }}</div>
|
||||
</x-admin::form.row>
|
||||
<h5 class="border-bottom pb-3 mb-4">{{ __('admin/logistics.basic_information') }}</h5>
|
||||
|
||||
<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="name" :title="__('admin/logistics.logistics_name')" :value="old('name', $logistics->name ?? '')" />
|
||||
<x-admin-form-input name="warehouse_name" :title="__('admin/logistics.warehouse_name')" :value="old('name', $logistics->warehouse_name ?? '仓到门')" />
|
||||
|
||||
<x-admin-form-input name="position" :title="__('common.sort_order')" :value="old('position', $product->position ?? '0')" />
|
||||
|
||||
<x-admin::form.row :title="__('admin/product.length_width_height')">
|
||||
<div class="d-flex wp-400">
|
||||
<input type="text" name="length" placeholder="{{ __('admin/product.length') }}" value="{{ old('length', $product->length ?? '') }}" class="form-control" style="flex: 0 0 95px" />
|
||||
<div style="width: 40px;line-height: 34px;text-align:center">CM X</div>
|
||||
<input type="text" name="width" placeholder="{{ __('admin/product.width') }}" value="{{ old('width', $product->width ?? '') }}" class="form-control" style="flex: 0 0 95px" />
|
||||
<div style="width: 40px;line-height: 34px;text-align:center">CM X</div>
|
||||
<input type="text" name="height" placeholder="{{ __('admin/product.height') }}" value="{{ old('height', $product->height ?? '') }}" class="form-control" style="flex: 0 0 95px" />
|
||||
<div style="width: 30px;line-height: 34px;text-align:center">CM</div>
|
||||
</div>
|
||||
</x-admin::form.row>
|
||||
|
||||
<x-admin::form.row :title="__('admin/product.weight_text')">
|
||||
<div class="d-flex wp-400">
|
||||
<input type="text" name="weight" placeholder="{{ __('admin/product.weight_text') }}" value="{{ old('weight', $product->weight ?? '') }}" class="form-control" style="flex: 0 0 260px" />
|
||||
<select class="form-select ms-4 bg-white" name="weight_class">
|
||||
@foreach ($weight_classes as $item)
|
||||
<option value="{{ $item }}" {{ $product->weight_class == $item ? 'selected' : '' }}>{{ __('product.' . $item) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</x-admin::form.row>
|
||||
|
||||
@hookwrapper('admin.product.edit.brand')
|
||||
<x-admin::form.row :title="__('admin/brand.index')">
|
||||
<input type="text" value="{{ $product->brand->name ?? '' }}" id="brand-autocomplete" class="form-control wp-400 " />
|
||||
<input type="hidden" name="brand_id" value="{{ old('brand_id', $product->brand_id ?? '') }}" />
|
||||
@hookwrapper('admin.logistics.edit.country')
|
||||
<x-admin::form.row :title="__('admin/country.index')">
|
||||
<input type="text" value="{{ $logistics->country->name ?? '' }}" id="country-autocomplete" class="form-control wp-400 " />
|
||||
<input type="hidden" name="country_id" value="{{ old('country_id', $logistics->country_id ?? '') }}" />
|
||||
</x-admin::form.row>
|
||||
@endhookwrapper
|
||||
|
||||
<x-admin-form-select :title="__('admin/tax_class.index')" name="tax_class_id" :value="old('tax_class_id', $product->tax_class_id ?? '')" :options="$tax_classes" key="id" label="title" />
|
||||
{{-- <x-admin-form-select :title="__('admin/logistics.type')" name="type" :value="old('type', $logistics->type ?? '')" :options="$types" key="title" label="name" @change="isVariableChange"/>--}}
|
||||
|
||||
<x-admin::form.row :title="__('admin/category.index')">
|
||||
<div class="wp-400 form-control" style="max-height: 240px;overflow-y: auto">
|
||||
@foreach ($source['categories'] as $_category)
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="categories[]" value="{{ $_category->id }}"
|
||||
id="category-{{ $_category->id }}" {{ in_array($_category->id, old('categories', $category_ids)) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="category-{{ $_category->id }}">
|
||||
{{ $_category->name }}
|
||||
</label>
|
||||
</div>
|
||||
<x-admin::form.row :title="__('admin/logistics.type')">
|
||||
<select class="form-select me-3 wp-400" v-model="form.type" name="type">
|
||||
@foreach ($types as $option)
|
||||
<option value="{{ $option['title'] }}" {{ $option['title'] == old('type', $logistics->type ?? '') ? 'selected': '' }}>{{ $option['name'] }}</option>
|
||||
@endforeach
|
||||
</div>
|
||||
</select>
|
||||
</x-admin::form.row>
|
||||
|
||||
<x-admin-form-switch-status name="active" :title="__('common.status')" :value="old('active', $product->active ?? 1)" />
|
||||
|
||||
@hook('admin.product.edit.extra')
|
||||
|
||||
<div>
|
||||
<h5 class="border-bottom pb-3 mb-4">{{ __('admin/product.stocks') }}</h5>
|
||||
|
||||
<x-admin::form.row title="{{ __('admin/product.price_setting_by.num') }}">
|
||||
<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">{{ __('common.disable') }}</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">{{ __('common.enable') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</x-admin::form.row>
|
||||
|
||||
<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 v-if="form.type !== 'free'">
|
||||
<span v-if="form.type === 'weight'">
|
||||
<x-admin-form-input name="first_weight" type="number" :title="__('admin/logistics.first_weight')"
|
||||
:value="old('first_weight', $logistics->first_weight ?? '')" step="any" required/>
|
||||
<x-admin-form-input name="first_weight_fee" type="number" :title="__('admin/logistics.first_weight_fee')"
|
||||
:value="old('first_weight_fee', $logistics->first_weight_fee ?? '')" step="any"
|
||||
required/>
|
||||
<x-admin-form-input name="add_weight" type="number" :title="__('admin/logistics.add_weight')"
|
||||
:value="old('add_weight', $logistics->add_weight ?? '')" step="any" required/>
|
||||
<x-admin-form-input name="continuation_weight_fee" type="number"
|
||||
:title="__('admin/logistics.continuation_weight_fee')"
|
||||
:value="old('continuation_weight_fee', $logistics->continuation_weight_fee ?? '')"
|
||||
step="any" required/>
|
||||
<x-admin-form-input name="throwing_ratio" type="number" :title="__('admin/logistics.throwing_ratio')"
|
||||
:value="old('throwing_ratio', $logistics->throwing_ratio ?? '')" step="any" required/>
|
||||
{{-- <x-admin-form-input name="continuation_weight_max" type="number" :title="__('admin/logistics.continuation_weight_max')" :value="old('continuation_weight_max', $logistics->continuation_weight_max ?? '')" step="any" required />--}}
|
||||
</span>
|
||||
<span v-if="form.type === 'num'">
|
||||
<x-admin-form-input name="first_weight" type="number" :title="__('admin/logistics.first_num')"
|
||||
:value="old('first_weight', $logistics->first_weight ?? '')" step="any" required/>
|
||||
<x-admin-form-input name="first_weight_fee" type="number" :title="__('admin/logistics.first_num_fee')"
|
||||
:value="old('first_weight_fee', $logistics->first_weight_fee ?? '')" step="any"
|
||||
required/>
|
||||
<x-admin-form-input name="add_weight" type="number" :title="__('admin/logistics.add_num')"
|
||||
:value="old('add_weight', $logistics->add_weight ?? '')" step="any" required/>
|
||||
<x-admin-form-input name="continuation_weight_fee" type="number"
|
||||
:title="__('admin/logistics.continuation_num_fee')"
|
||||
:value="old('continuation_weight_fee', $logistics->continuation_weight_fee ?? '')"
|
||||
step="any" required/>
|
||||
{{-- <x-admin-form-input name="num_fee" type="number" :title="__('admin/logistics.num_fee')" :value="old('num_fee', $logistics->continuation_weight_fee ?? '')" step="any" required />--}}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<input type="hidden" name="variables" :value="JSON.stringify(form.variables)">
|
||||
|
||||
<input type="hidden" name="numPrices" value="" v-if="form.price_setting === 'sku'">
|
||||
|
||||
|
||||
@hookwrapper('admin.product.edit.switch')
|
||||
<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>
|
||||
@endhookwrapper
|
||||
|
||||
<input type="hidden" name="variables" :value="JSON.stringify(form.variables)">
|
||||
|
||||
<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-">
|
||||
<div class="selectable-variants">
|
||||
<div>
|
||||
<draggable :list="source.variables" :options="{animation: 100}">
|
||||
<div v-for="(variant, variantIndex) in source.variables" :id="'selectable-variant-' + variantIndex">
|
||||
<div class="title">
|
||||
<div>
|
||||
<b>@{{ variant.name[current_language_code] }}</b>
|
||||
<el-link type="primary" @click="modalVariantOpenButtonClicked(variantIndex, null)">{{ __('common.edit') }}</el-link>
|
||||
<el-link type="danger" class="ms-2" @click="removeSourceVariant(variantIndex)">{{ __('common.delete') }}</el-link>
|
||||
</div>
|
||||
<div>
|
||||
<el-checkbox v-model="variant.isImage" @change="(e) => {variantIsImage(e, variantIndex)}" border size="mini" class="me-2 bg-white">{{ __('admin/product.add_variable_image') }}</el-checkbox>
|
||||
<el-button type="primary" plain size="mini" @click="modalVariantOpenButtonClicked(variantIndex, -1)">{{ __('admin/product.add_variable_value') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="variant.values.length">
|
||||
<draggable
|
||||
element="div"
|
||||
@start="isMove = true"
|
||||
class="variants-wrap"
|
||||
@update="(e) => {swapSourceVariantValue(e, variantIndex)}"
|
||||
@end="isMove = false"
|
||||
ghost-class="dragabble-ghost"
|
||||
:list="variant.values"
|
||||
:options="{animation: 100}"
|
||||
>
|
||||
<div v-for="(value, value_index) in variant.values" :key="value_index" class="variants-item" @dblclick="modalVariantOpenButtonClicked(variantIndex, value_index)">
|
||||
<div class="open-file-manager variant-value-img" v-if="variant.isImage">
|
||||
<div>
|
||||
<img :src="thumbnail(value.image)" class="img-fluid">
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" v-model="value.image">
|
||||
|
||||
<div class="btn-remove" @click="removeSourceVariantValue(variantIndex, value_index)"><i class="el-icon-error"></i></div>
|
||||
<div class="name">
|
||||
@{{ value.name[current_language_code] }}
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
<div class="ps-2 mt-2 mb-3 opacity-50"><i class="bi bi-exclamation-circle"></i> {{ __('admin/product.modify_order') }}</div>
|
||||
</template>
|
||||
<div v-else class="d-flex justify-content-center align-items-center">
|
||||
<div class="p-4 fs-5 btn" @click="modalVariantOpenButtonClicked(variantIndex, -1)"><i class="bi bi-plus-square-dotted"></i> {{ __('admin/product.add_variable_value') }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</draggable>
|
||||
|
||||
<el-button type="primary" plain size="small" @click="modalVariantOpenButtonClicked(-1, null)" class="btn btn-xs mr-1 mb-1">{{ __('admin/product.add_variable') }}</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="form.skus.length && form.variables.length" class="mt-3 table-push table-responsive">
|
||||
<div class="batch-setting d-flex align-items-center mb-3 p-2 bg-body">
|
||||
<div v-for="(variant, index) in form.variables" :key="index" class="me-2">
|
||||
<select class="form-select me-2 bg-white" aria-label="Default select example" v-model="variablesBatch.variables[index]">
|
||||
<option selected value="">@{{ variant.name[current_language_code] || 'No name' }}</option>
|
||||
<option :value="value_index" v-for="value, value_index in variant.values" :key="index+'-'+value_index" >
|
||||
@{{ value.name[current_language_code]}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div role="button" class="border d-flex justify-content-center align-items-center border-dashed bg-light wh-40 me-2 bg-white" @click="batchSettingVariantImage">
|
||||
<img :src="thumbnail(variablesBatch.image)" class="img-fluid" v-if="variablesBatch.image" style="max-height: 40px;">
|
||||
<i class="bi bi-plus fs-3 text-muted" v-else></i>
|
||||
</div>
|
||||
<input type="text" class="form-control me-2 bg-white" v-model="variablesBatch.model" placeholder="{{ __('admin/product.model') }}">
|
||||
<input type="text" class="form-control me-2 bg-white" v-model="variablesBatch.sku" placeholder="sku">
|
||||
<input type="number" v-if="form.price_setting === 'sku'" class="form-control me-2 bg-white" v-model="variablesBatch.price" placeholder="{{ __('admin/product.price') }}">
|
||||
<input type="number" v-if="form.price_setting === 'sku'" class="form-control me-2 bg-white" v-model="variablesBatch.origin_price" placeholder="{{ __('admin/product.origin_price') }}">
|
||||
<input type="number" class="form-control me-2 bg-white" v-model="variablesBatch.cost_price" placeholder="{{ __('admin/product.cost_price') }}">
|
||||
<input type="number" class="form-control me-2 bg-white" v-model="variablesBatch.quantity" placeholder="{{ __('admin/product.quantity') }}">
|
||||
<button type="button" class="btn btn-primary text-nowrap" @click="batchSettingVariant">{{ __('common.batch_setting') }}</button>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<th v-for="(variant, index) in form.variables" :key="'pv-header-' + index">
|
||||
@{{ variant.name[current_language_code] || 'No name' }}
|
||||
</th>
|
||||
<th width="106px">{{ __('common.image') }}</th>
|
||||
<th class="w-min-100">{{ __('admin/product.model') }}</th>
|
||||
<th class="w-min-100">sku</th>
|
||||
<th v-if="form.price_setting === 'sku'" class="w-min-100">{{ __('admin/product.price') }}</th>
|
||||
<th v-if="form.price_setting === 'sku'" class="w-min-100">{{ __('admin/product.origin_price') }}</th>
|
||||
<th class="w-min-100">{{ __('admin/product.cost_price') }}</th>
|
||||
<th class="w-min-100">{{ __('admin/product.quantity') }}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(sku, skuIndex) in form.skus" :key="skuIndex">
|
||||
<template v-for="(variantValueIndex, j) in sku.variants">
|
||||
<td v-if="skuIndex % variantValueRepetitions[j] == 0" :key="'pvv' + skuIndex + '-' + j"
|
||||
:rowspan="variantValueRepetitions[j]">
|
||||
<span>@{{ form.variables[j].values[variantValueIndex].name[current_language_code] || 'No name' }}</span>
|
||||
</td>
|
||||
</template>
|
||||
<td>
|
||||
<div class="product-images d-flex flex-wrap" style="margin-right: -8px">
|
||||
<div v-for="image, index in sku.images" :key="index" class="product-item wh-40 border d-flex justify-content-center align-items-center me-2 mb-2 position-relative">
|
||||
<div class="position-absolute top-0 end-0">
|
||||
<button class="btn btn-danger btn-sm wh-20 p-0" @click="removeSkuImages(skuIndex, index)" type="button"><i class="bi bi-trash"></i></button>
|
||||
</div>
|
||||
<img :src="thumbnail(image)" class="img-fluid" style="max-height: 40px;">
|
||||
<input type="hidden" class="form-control" v-model="sku.images[index]" :name="'skus[' + skuIndex + '][images][]'"
|
||||
placeholder="image">
|
||||
</div>
|
||||
<div class="border d-flex justify-content-center align-items-center border-dashed bg-light wh-40" role="button" @click="addProductImages(skuIndex)"><i class="bi bi-plus fs-3 text-muted"></i></div>
|
||||
</div>
|
||||
<input type="hidden" class="form-control" :name="'skus[' + skuIndex + '][is_default]'" :value="skuIndex == 0 ? 1 : 0">
|
||||
<input v-for="(variantValueIndex, j) in sku.variants" type="hidden"
|
||||
:name="'skus[' + skuIndex + '][variants][' + j + ']'" :value="variantValueIndex">
|
||||
</td>
|
||||
<td><input type="text" class="form-control" v-model="sku.model" :name="'skus[' + skuIndex + '][model]'"
|
||||
placeholder="{{ __('admin/product.model') }}"></td>
|
||||
<td>
|
||||
<input type="text" class="form-control" v-model="sku.sku" :name="'skus[' + skuIndex + '][sku]'" placeholder="sku" :style="sku.is_default ? 'margin-top: 19px;' : ''" required>
|
||||
<span role="alert" class="invalid-feedback">{{ __('common.error_required', ['name' => 'sku']) }}</span>
|
||||
<span v-if="sku.is_default" class="text-success">{{ __('admin/product.default_main_product') }}</span>
|
||||
</td>
|
||||
<td v-if="form.price_setting === 'sku'">
|
||||
<input type="number" class="form-control" v-model="sku.price" :name="'skus[' + skuIndex + '][price]'" step="any"
|
||||
placeholder="{{ __('admin/product.price') }}" required>
|
||||
<span role="alert" class="invalid-feedback">{{ __('common.error_required', ['name' => __('admin/product.price')]) }}</span>
|
||||
</td>
|
||||
<td v-if="form.price_setting === 'sku'">
|
||||
<input type="number" class="form-control" v-model="sku.origin_price" :name="'skus[' + skuIndex + '][origin_price]'" step="any"
|
||||
placeholder="{{ __('admin/product.origin_price') }}" required>
|
||||
<span role="alert" class="invalid-feedback">{{ __('common.error_required', ['name' => __('admin/product.origin_price')]) }}</span>
|
||||
</td>
|
||||
<input type="hidden" :name="'skus[' + skuIndex + '][price]'" v-if="form.price_setting === 'num'" placeholder="variants" :value="form.numPrices.length !== 0 ? form.numPrices[0].price : ''">
|
||||
<input type="hidden" :name="'skus[' + skuIndex + '][origin_price]'" v-if="form.price_setting === 'num'" placeholder="position" :value="form.numPrices.length !== 0 ? form.numPrices[form.numPrices.length - 1].price : ''">
|
||||
|
||||
<td><input type="number" class="form-control" v-model="sku.cost_price" :name="'skus[' + skuIndex + '][cost_price]'"
|
||||
placeholder="{{ __('admin/product.cost_price') }}">
|
||||
</td>
|
||||
<td><input type="number" class="form-control" v-model="sku.quantity" :name="'skus[' + skuIndex + '][quantity]'"
|
||||
placeholder="{{ __('admin/product.quantity') }}"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<input class="form-control d-none" :value="skuIsEmpty" required>
|
||||
<div class="invalid-feedback" style="font-size: 16px"><i class="bi bi-exclamation-circle-fill"></i> {{ __('admin/product.add_variable') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@hookwrapper('admin.product.edit.variable')
|
||||
<div v-if="!editing.isVariable">
|
||||
<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 />
|
||||
<span v-if="form.price_setting === 'sku'">
|
||||
<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 />
|
||||
</span>
|
||||
<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]" v-if="form.price_setting === 'num'" placeholder="variants" :value="form.numPrices.length !== 0 ? form.numPrices[0].price : ''">
|
||||
<input type="hidden" name="skus[0][origin_price]" v-if="form.price_setting === 'num'" 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">
|
||||
</div>
|
||||
@endhookwrapper
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="tab-descriptions">
|
||||
<h6 class="border-bottom pb-3 mb-4">{{ __('admin/product.product_details') }}</h6>
|
||||
<x-admin::form.row :title="__('admin/product.product_details')">
|
||||
|
||||
<ul class="nav nav-tabs mb-3" role="tablist">
|
||||
@foreach ($languages as $language)
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link {{ $loop->first ? 'active' : '' }}" data-bs-toggle="tab" data-bs-target="#tab-descriptions-{{ $language->code }}" type="button" >{{ $language->name }}</button>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
@foreach ($languages as $language)
|
||||
<div class="tab-pane fade {{ $loop->first ? 'show active' : '' }}" id="tab-descriptions-{{ $language->code }}">
|
||||
<textarea name="descriptions[{{ $language->code }}][content]" class="form-control tinymce">
|
||||
{{ old('content', $product->descriptions->keyBy('locale')[$language->code]->content ?? '') }}
|
||||
</textarea>
|
||||
</div>
|
||||
@endforeach
|
||||
@hook('admin.product.content.after')
|
||||
</div>
|
||||
</x-admin::form.row>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="tab-attribute">
|
||||
<h6 class="border-bottom pb-3 mb-4">{{ __('admin/attribute.index') }}</h6>
|
||||
<x-admin::form.row title="{{ __('admin/attribute.set_attribute') }}">
|
||||
<div class="pdf-table">
|
||||
<table class="table table-bordered w-max-600">
|
||||
<thead><th>{{ __('admin/attribute.index') }}</th><th>{{ __('admin/attribute.attribute_value') }}</th><th width="50px"></th></thead>
|
||||
<tbody>
|
||||
<tr v-for="item, index in form.attributes" :key="index">
|
||||
<td>
|
||||
<el-autocomplete
|
||||
v-model="item.attribute.name"
|
||||
:fetch-suggestions="attributeQuerySearch"
|
||||
placeholder="{{ __('admin/builder.modules_keywords_search') }}"
|
||||
value-key="name"
|
||||
class="w-100"
|
||||
size="small"
|
||||
@select="(e) => {attributeHandleSelect(e, index, 'attribute')}"
|
||||
></el-autocomplete>
|
||||
<input type="text" required :name="'attributes['+ index +'][attribute_id]'" v-model="item.attribute.id" class="form-control d-none">
|
||||
<div class="invalid-feedback">{{ __('common.error_required', ['name' => __('admin/attribute.index')]) }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<el-autocomplete
|
||||
v-model="item.attribute_value.name"
|
||||
:fetch-suggestions="((query, cb) => {attributeValueQuerySearch(query, cb, index)})"
|
||||
size="small"
|
||||
:disabled="item.attribute.id == ''"
|
||||
value-key="name"
|
||||
class="w-100"
|
||||
:placeholder="item.attribute.id == '' ? '{{ __('admin/attribute.before_attribute') }}' : '{{ __('admin/builder.modules_keywords_search') }}'"
|
||||
@select="(e) => {attributeHandleSelect(e, index, 'attribute_value')}"
|
||||
></el-autocomplete>
|
||||
<input type="text" required :name="'attributes['+ index +'][attribute_value_id]'" v-model="item.attribute_value.id" class="form-control d-none">
|
||||
<div class="invalid-feedback">{{ __('common.error_required', ['name' => __('admin/attribute.attribute_value')]) }}</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<i @click="form.attributes.splice(index, 1)" class="bi bi-x-circle fs-4 text-danger cursor-pointer"></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
<td class="text-end"><i class="bi bi-plus-circle cursor-pointer fs-4" @click="addAttribute"></i></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</x-admin::form.row>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="tab-seo">
|
||||
<h6 class="border-bottom pb-3 mb-4">SEO</h6>
|
||||
|
||||
@hook('admin.product.seo.before')
|
||||
|
||||
<x-admin-form-input-locale :width="600" name="descriptions.*.meta_title" title="Meta title" :value="$descriptions"/>
|
||||
<x-admin::form.row title="Meta keywords">
|
||||
<div class="input-locale-wrap">
|
||||
@foreach ($languages as $language)
|
||||
<div class="input-group w-max-600">
|
||||
<span class="input-group-text wp-100">{{ $language['name'] }}</span>
|
||||
<textarea rows="2" type="text" name="descriptions[{{ $language['code'] }}][meta_keywords]" class="form-control wp-400" placeholder="Meta keywords">{{ old('meta_keywords', $product->descriptions->keyBy('locale')[$language->code]->meta_keywords ?? '') }}</textarea>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-admin::form.row>
|
||||
<x-admin::form.row title="Meta description">
|
||||
<div class="input-locale-wrap">
|
||||
@foreach ($languages as $language)
|
||||
<div class="input-group w-max-600">
|
||||
<span class="input-group-text wp-100">{{ $language['name'] }}</span>
|
||||
<textarea rows="2" type="text" name="descriptions[{{ $language['code'] }}][meta_description]" class="form-control wp-400" placeholder="Meta description">{{ old('meta_description', $product->descriptions->keyBy('locale')[$language->code]->meta_description ?? '') }}</textarea>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-admin::form.row>
|
||||
|
||||
@hook('admin.product.seo.after')
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="tab-relations">
|
||||
<h6 class="border-bottom pb-3 mb-4">{{ __('admin/product.product_relations') }}</h6>
|
||||
|
||||
<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="relationsQuerySearch"
|
||||
placeholder="{{ __('admin/builder.modules_keywords_search') }}"
|
||||
@select="relationsHandleSelect"
|
||||
></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="'relations['+ index +']'" v-model="item.id" class="form-control d-none">
|
||||
</div>
|
||||
</draggable>
|
||||
</template>
|
||||
<template v-else>{{ __('admin/builder.modules_please_products') }}</template>
|
||||
</div>
|
||||
</div>
|
||||
<x-admin::form.row :title="__('admin/logistics.day')">
|
||||
<div class="d-flex wp-400">
|
||||
<input type="number" name="day_min" placeholder="{{ __('admin/logistics.day_min') }}" value="{{ old('day_min', $logistics->day_min ?? '') }}" class="form-control" style="flex: 0 0 95px" /><div style="width: 40px;line-height: 34px;text-align:center"> - </div>
|
||||
<input type="number" name="day_max" placeholder="{{ __('admin/logistics.day_max') }}" value="{{ old('day_max', $logistics->day_max ?? '') }}" class="form-control" style="flex: 0 0 95px" /><div style="width: 40px;line-height: 34px;text-align:center">{{ __('admin/logistics.work_day') }}</div>
|
||||
</div>
|
||||
</x-admin::form.row>
|
||||
</div>
|
||||
<x-admin-form-input name="position" :title="__('common.sort_order')" :value="old('position', $logistics->position ?? '0')" />
|
||||
</div>
|
||||
|
||||
<x-admin::form.row title="">
|
||||
<button type="submit" @click="productsSubmit" class="btn d-none btn-primary btn-submit mt-3 btn-lg">{{ __('common.save') }}</button>
|
||||
<button type="submit" @click="logisticsSubmit" class="btn d-none btn-primary btn-submit mt-3 btn-lg">{{ __('common.save') }}</button>
|
||||
</x-admin::form.row>
|
||||
|
||||
<el-dialog
|
||||
title="{{ __('common.edit') }}"
|
||||
:visible.sync="dialogVariables.show"
|
||||
width="400"
|
||||
@close="closedialogVariablesFormDialog('form')"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="form" :rules="rules" :model="dialogVariables.form" label-width="100px">
|
||||
<el-form-item label="{{ __('common.name') }}" required class="language-inputs">
|
||||
<el-form-item :prop="'name.' + lang.code" :inline-message="true" v-for="lang, lang_i in source.languages" :key="lang_i"
|
||||
:rules="[
|
||||
{ required: true, message: '{{ __('common.error_input_required') }}', trigger: 'blur' },
|
||||
]"
|
||||
>
|
||||
<el-input size="mini" v-model="dialogVariables.form.name[lang.code]" placeholder="{{ __('common.name') }}"><template slot="prepend">@{{lang.name}}</template></el-input>
|
||||
</el-form-item>
|
||||
@hook('admin.product.sku.edit.item.after')
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="dialogVariablesFormSubmit('form')">{{ __('common.save') }}</el-button>
|
||||
<el-button @click="closedialogVariablesFormDialog('form')">{{ __('common.cancel') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="{{ __('admin/attribute.attribute_value') }}" :visible.sync="attributeDialog.show" width="670px"
|
||||
@close="attributeCloseDialog('attribute_form')" :close-on-click-modal="false">
|
||||
|
||||
<el-form ref="attribute_form" :model="attributeDialog.form" label-width="155px">
|
||||
<el-form-item label="{{ __('common.name') }}" required class="language-inputs">
|
||||
<el-form-item :prop="'name.' + lang.code" :inline-message="true" v-for="lang, lang_i in source.languages" :key="lang_i"
|
||||
:rules="[
|
||||
{ required: true, message: '{{ __('common.error_required', ['name' => __('common.name')]) }}', trigger: 'blur' },
|
||||
]"
|
||||
>
|
||||
<el-input size="mini" v-model="attributeDialog.form.name[lang.code]" placeholder="{{ __('common.name') }}"><template slot="prepend">@{{lang.name}}</template></el-input>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<div class="d-flex d-lg-block mt-4">
|
||||
<el-button type="primary" @click="attributeSubmit('attribute_form')">{{ __('common.save') }}</el-button>
|
||||
<el-button @click="attributeCloseDialog('attribute_form')">{{ __('common.cancel') }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@hook('admin.product.form.footer')
|
||||
@hook('admin.logistics.form.footer')
|
||||
@endsection
|
||||
|
||||
@push('footer')
|
||||
<script>
|
||||
$('.submit-form').on('click', function () {
|
||||
// 关闭多规格提交 清空 variables
|
||||
if (!app.editing.isVariable) {
|
||||
app.source.variables = [];
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
$(`form#app`).find('button[type="submit"]')[0].click();
|
||||
|
|
@ -625,21 +130,19 @@
|
|||
current_language_code: '{{ locale() }}',
|
||||
isMove: false,
|
||||
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'] ?? ''),
|
||||
sku: @json($product->skus[0]['sku'] ?? ''),
|
||||
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) ?? []),
|
||||
name: @json($logistics->name ?? ''),
|
||||
warehouse_name: @json($logistics->warehouse_name ?? ''),
|
||||
country_id: @json($logistics->country_id ?? ''),
|
||||
type: '{{$logistics->type ?? "weight"}}',
|
||||
first_weight: @json($logistics->first_weight ?? ''),
|
||||
first_weight_fee: @json($logistics->first_weight_fee ?? ''),
|
||||
continuation_weight_max: @json($logistics->continuation_weight_max ?? ''),
|
||||
add_weight: @json($logistics->add_weight ?? ''),
|
||||
continuation_weight_fee: @json($logistics->continuation_weight_fee ?? ''),
|
||||
num_fee: @json($logistics->num_fee ?? ''),
|
||||
throwing_ratio: @json($logistics->throwing_ratio ?? ''),
|
||||
day_min: @json($logistics->day_min ?? ''),
|
||||
day_max: @json($logistics->day_max ?? ''),
|
||||
},
|
||||
|
||||
variablesBatch: {
|
||||
|
|
@ -656,17 +159,17 @@
|
|||
|
||||
relations: {
|
||||
keyword: '',
|
||||
products: @json($relations ?? []),
|
||||
logistics: @json($relations ?? []),
|
||||
loading: null,
|
||||
},
|
||||
|
||||
source: {
|
||||
variables: @json($product->variables ?? []),
|
||||
variables: @json($logistics->variables ?? []),
|
||||
languages: @json($languages ?? []),
|
||||
},
|
||||
|
||||
editing: {
|
||||
isVariable: @json(($product->variables ?? null) != null),
|
||||
isVariable: @json(($logistics->variables ?? NULL) != NULL),
|
||||
},
|
||||
|
||||
dialogVariables: {
|
||||
|
|
@ -713,9 +216,7 @@
|
|||
},
|
||||
|
||||
beforeMount() {
|
||||
if (this.form.variables.length) {
|
||||
this.variablesBatch.variables = this.form.variables.map((v, i) => '');
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
|
@ -744,12 +245,16 @@
|
|||
},
|
||||
|
||||
created() {
|
||||
if(this.form.numPrices.length === 0){
|
||||
this.addNumPrices();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
dev(){
|
||||
console.log(1);
|
||||
return true;
|
||||
},
|
||||
logisticsType(e){
|
||||
console.log(1);
|
||||
},
|
||||
addNumPrices() {
|
||||
this.form.numPrices.push({num: '', price: ''});
|
||||
},
|
||||
|
|
@ -759,27 +264,27 @@
|
|||
},
|
||||
|
||||
// 表单提交,检测是否开启多规格 做处理
|
||||
productsSubmit() {
|
||||
logisticsSubmit() {
|
||||
if (!this.editing.isVariable) {
|
||||
this.source.variables = [];
|
||||
}
|
||||
},
|
||||
|
||||
relationsQuerySearch(keyword, cb) {
|
||||
$http.get('products/autocomplete?name=' + encodeURIComponent(keyword), null, {hload:true}).then((res) => {
|
||||
$http.get('logistics/autocomplete?name=' + encodeURIComponent(keyword), null, {hload:true}).then((res) => {
|
||||
cb(res.data);
|
||||
})
|
||||
},
|
||||
|
||||
relationsHandleSelect(item) {
|
||||
if (!this.relations.products.find(v => v == item.id)) {
|
||||
this.relations.products.push(item);
|
||||
if (!this.relations.logistics.find(v => v == item.id)) {
|
||||
this.relations.logistics.push(item);
|
||||
}
|
||||
this.relations.keyword = ""
|
||||
},
|
||||
|
||||
relationsRemoveProduct(index) {
|
||||
this.relations.products.splice(index, 1);
|
||||
this.relations.logistics.splice(index, 1);
|
||||
},
|
||||
|
||||
isVariableChange(e) {
|
||||
|
|
@ -974,7 +479,7 @@
|
|||
this.source.variables.splice(variantIndex, 1);
|
||||
if (!this.source.variables.length) {
|
||||
setTimeout(() => { // 等 remakeSkus 完成 this.form.skus = [];
|
||||
this.form.skus = [{product_sku_id: 0,position: 1,variants: [],image: '',model: '',sku: '',price: null,quantity: null,is_default: 1}];
|
||||
this.form.skus = [{logistics_sku_id: 0,position: 1,variants: [],image: '',model: '',sku: '',price: null,quantity: null,is_default: 1}];
|
||||
this.editing.isVariable = false;
|
||||
}, 0);
|
||||
}
|
||||
|
|
@ -1054,16 +559,16 @@
|
|||
}
|
||||
|
||||
// 找出已存在的组合
|
||||
const productVariantCombos = this.form.skus.map(v => v.variants.join()); // ['0,0,0', '0,0,1']
|
||||
const logisticsVariantCombos = this.form.skus.map(v => v.variants.join()); // ['0,0,0', '0,0,1']
|
||||
let skus = [];
|
||||
for (var i = 0; i < combos.length; i++) {
|
||||
const combo = combos[i]; // 0,0,0
|
||||
const index = productVariantCombos.indexOf(combo.join());
|
||||
const index = logisticsVariantCombos.indexOf(combo.join());
|
||||
if (index > -1) {
|
||||
skus.push(this.form.skus[index]);
|
||||
} else {
|
||||
skus.push({
|
||||
product_sku_id: 0,
|
||||
logistics_sku_id: 0,
|
||||
position: i,
|
||||
variants: combo,
|
||||
images: [],
|
||||
|
|
@ -1145,6 +650,22 @@
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function ($) {
|
||||
$('#country-autocomplete').autocomplete({
|
||||
'source': function(request, response) {
|
||||
$http.get(`countries/autocomplete?name=${encodeURIComponent(request)}`, null, {hload: true}).then((res) => {
|
||||
response($.map(res.data, function(item) {
|
||||
return {label: item['name'], value: item['id']}
|
||||
}));
|
||||
})
|
||||
},
|
||||
'select': function(item) {
|
||||
$(this).val(item['label']);
|
||||
$('input[name="country_id"]').val(item['value']);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script lang="scss">
|
||||
<style>
|
||||
.price_setting_by_num{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@extends('admin::layouts.master')
|
||||
|
||||
@section('title', __('admin/common.product'))
|
||||
@section('title', __('admin/common.logistics'))
|
||||
|
||||
@section('content')
|
||||
@if ($errors->has('error'))
|
||||
|
|
@ -14,154 +14,82 @@
|
|||
<div id="product-app">
|
||||
<div class="card h-min-600">
|
||||
<div class="card-body">
|
||||
<div class="bg-light p-4">
|
||||
<div class="row">
|
||||
<div class="col-xxl-20 col-xl-3 col-lg-4 col-md-4 d-flex align-items-center mb-3">
|
||||
<label class="filter-title">{{ __('product.name') }}</label>
|
||||
<input @keyup.enter="search" type="text" v-model="filter.name" class="form-control" placeholder="{{ __('product.name') }}">
|
||||
</div>
|
||||
<div class="col-xxl-20 col-xl-3 col-lg-4 col-md-4 d-flex align-items-center mb-3">
|
||||
<label class="filter-title">{{ __('product.sku') }}</label>
|
||||
<input @keyup.enter="search" type="text" v-model="filter.sku" class="form-control" placeholder="{{ __('product.sku') }}">
|
||||
</div>
|
||||
|
||||
<div class="col-xxl-20 col-xl-3 col-lg-4 col-md-4 d-flex align-items-center mb-3">
|
||||
<label class="filter-title">{{ __('product.model') }}</label>
|
||||
<input @keyup.enter="search" type="text" v-model="filter.model" class="form-control" placeholder="{{ __('product.model') }}">
|
||||
</div>
|
||||
|
||||
<div class="col-xxl-20 col-xl-3 col-lg-4 col-md-4 d-flex align-items-center mb-3">
|
||||
<label class="filter-title">{{ __('product.category') }}</label>
|
||||
<select v-model="filter.category_id" class="form-select">
|
||||
<option value="">{{ __('common.all') }}</option>
|
||||
@foreach ($categories as $_category)
|
||||
<option :value="{{ $_category->id }}">{{ $_category->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-xxl-20 col-xl-3 col-lg-4 col-md-4 d-flex align-items-center mb-3">
|
||||
<label class="filter-title">{{ __('common.status') }}</label>
|
||||
<select v-model="filter.active" class="form-select">
|
||||
<option value="">{{ __('common.all') }}</option>
|
||||
variant-value-img <option value="1">{{ __('product.active') }}</option>
|
||||
<option value="0">{{ __('product.inactive') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@hook('admin.product.list.filter')
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="filter-title"></label>
|
||||
<div class="col-auto">
|
||||
<button type="button" @click="search" class="btn btn-outline-primary btn-sm">{{ __('common.filter') }}</button>
|
||||
<button type="button" @click="resetSearch" class="btn btn-outline-secondary btn-sm">{{ __('common.reset') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between my-4">
|
||||
@if ($type != 'trashed')
|
||||
<a href="{{ admin_route('products.create') }}" class="me-1 nowrap">
|
||||
<button class="btn btn-primary">{{ __('admin/product.products_create') }}</button>
|
||||
</a>
|
||||
<a href="{{ admin_route('logistics.create') }}" class="me-1 nowrap">
|
||||
<button class="btn btn-primary">{{ __('admin/logistics.logistics_create') }}</button>
|
||||
</a>
|
||||
@else
|
||||
@if ($products->total())
|
||||
<button class="btn btn-primary" @click="clearRestore">{{ __('admin/product.clear_restore') }}</button>
|
||||
@if ($logistics->total())
|
||||
<button class="btn btn-primary" @click="clearRestore">{{ __('admin/logistics.clear_restore') }}</button>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if ($type != 'trashed' && $products->total())
|
||||
<div class="right nowrap">
|
||||
<button class="btn btn-outline-secondary" :disabled="!selectedIds.length" @click="batchDelete">{{ __('admin/product.batch_delete') }}</button>
|
||||
<button class="btn btn-outline-secondary" :disabled="!selectedIds.length"
|
||||
@click="batchActive(true)">{{ __('admin/product.batch_active') }}</button>
|
||||
<button class="btn btn-outline-secondary" :disabled="!selectedIds.length"
|
||||
@click="batchActive(false)">{{ __('admin/product.batch_inactive') }}</button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($products->total())
|
||||
@if ($logistics->total())
|
||||
<div class="table-push">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" v-model="allSelected" /></th>
|
||||
<th>{{ __('common.id') }}</th>
|
||||
<th>{{ __('product.image') }}</th>
|
||||
<th>{{ __('product.name') }}</th>
|
||||
<th>{{ __('product.price') }}</th>
|
||||
<th>
|
||||
<div class="d-flex align-items-center">
|
||||
{{ __('common.created_at') }}
|
||||
<div class="d-flex flex-column ml-1 order-by-wrap">
|
||||
<i class="el-icon-caret-top" @click="checkedOrderBy('created_at:asc')"></i>
|
||||
<i class="el-icon-caret-bottom" @click="checkedOrderBy('created_at:desc')"></i>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<tr>
|
||||
<th><input type="checkbox" v-model="allSelected" /></th>
|
||||
<th>{{ __('common.id') }}</th>
|
||||
<th>{{ __('admin/logistics.logistics_name') }}</th>
|
||||
<th>{{ __('admin/logistics.warehouse_name') }}</th>
|
||||
<th>{{ __('admin/country.index') }}</th>
|
||||
<th>{{ __('admin/logistics.type') }}</th>
|
||||
<th>{{ __('admin/logistics.first_weight') }}</th>
|
||||
<th>{{ __('admin/logistics.first_weight_fee') }}</th>
|
||||
<th>{{ __('admin/logistics.add_weight') }}</th>
|
||||
<th>{{ __('admin/logistics.continuation_weight_fee') }}</th>
|
||||
<th>{{ __('admin/logistics.throwing_ratio') }}</th>
|
||||
<th>{{ __('admin/logistics.day') }}</th>
|
||||
<th>{{ __('common.sort_order') }}</th>
|
||||
<th>{{ __('common.created_at') }}</th>
|
||||
|
||||
<th class="d-flex align-items-center">
|
||||
<div class="d-flex align-items-center">
|
||||
{{ __('common.sort_order') }}
|
||||
<div class="d-flex flex-column ml-1 order-by-wrap">
|
||||
<i class="el-icon-caret-top" @click="checkedOrderBy('position:asc')"></i>
|
||||
<i class="el-icon-caret-bottom" @click="checkedOrderBy('position:desc')"></i>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
@if ($type != 'trashed')
|
||||
<th>{{ __('common.status') }}</th>
|
||||
@endif
|
||||
@hook('admin.product.list.column')
|
||||
<th class="text-end">{{ __('common.action') }}</th>
|
||||
</tr>
|
||||
@hook('admin.product.list.column')
|
||||
<th class="text-end">{{ __('common.action') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($products_format as $product)
|
||||
@foreach ($logistics_format as $logistic)
|
||||
<tr>
|
||||
<td><input type="checkbox" :value="{{ $product['id'] }}" v-model="selectedIds" /></td>
|
||||
<td>{{ $product['id'] }}</td>
|
||||
<td>
|
||||
<div class="wh-60 border d-flex rounded-2 justify-content-center align-items-center"><img src="{{ $product['images'][0] ?? 'image/placeholder.png' }}" class="img-fluid max-h-100"></div>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ $product['url'] }}" target="_blank" title="{{ $product['name'] }}" class="text-dark">{{ $product['name'] }}</a>
|
||||
</td>
|
||||
<td>{{ $product['price_formatted'] }}</td>
|
||||
<td>{{ $product['created_at'] }}</td>
|
||||
<td>{{ $product['position'] }}</td>
|
||||
@if ($type != 'trashed')
|
||||
<td>
|
||||
{{-- <div class="form-check form-switch">--}}
|
||||
{{-- <input class="form-check-input cursor-pointer" type="checkbox" role="switch" data-active="{{ $product['active'] ? true : false }}" data-id="{{ $product['id'] }}" @change="turnOnOff($event)" {{ $product['active'] ? 'checked' : '' }}>--}}
|
||||
{{-- </div>--}}
|
||||
<span class="{{ $product['active'] ? 'text-success' : 'text-secondary' }}">
|
||||
{{ $product['active'] ? __('common.enable_status') : __('common.disable_status') }}
|
||||
</span>
|
||||
</td>
|
||||
@endif
|
||||
@hook('admin.product.list.column_value')
|
||||
<td><input type="checkbox" :value="{{ $logistic['id'] }}" v-model="selectedIds" /></td>
|
||||
<td>{{ $logistic['id'] }}</td>
|
||||
<td>{{ $logistic['name'] }}</td>
|
||||
<td>{{ $logistic['warehouse_name'] }}</td>
|
||||
<td>{{ $logistic['country'] }}</td>
|
||||
<td>{{ __('admin/logistics.type_' . $logistic['type']) }}</td>
|
||||
<td>{{ $logistic['first_weight'] }}</td>
|
||||
<td>{{ $logistic['first_weight_fee'] }}</td>
|
||||
{{-- <td>{{ $logistic['continuation_weight_max'] }}</td>--}}
|
||||
<td>{{ $logistic['add_weight'] }}</td>
|
||||
<td>{{ $logistic['continuation_weight_fee'] }}</td>
|
||||
{{-- <td>{{ $logistic['num_fee'] }}</td>--}}
|
||||
<td>{{ $logistic['throwing_ratio'] }}</td>
|
||||
<td>{{ $logistic['day_min'] }} - {{ $logistic['day_max'] }} {{ __('admin/logistics.work_day') }}</td>
|
||||
<td>{{ $logistic['position'] }}</td>
|
||||
<td>{{ $logistic['created_at'] }}</td>
|
||||
|
||||
@hook('admin.logistic.list.column_value')
|
||||
<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-secondary btn-sm" @click.prevent="copyProduct({{ $loop->index }})">{{ __('common.copy') }}</a>
|
||||
@if ($logistic['deleted_at'] == '')
|
||||
<a href="{{ admin_route('logistics.edit', [$logistic['id']]) }}" class="btn btn-outline-secondary btn-sm">{{ __('common.edit') }}</a>
|
||||
{{-- <a href="javascript:void(0)" class="btn btn-outline-secondary btn-sm" @click.prevent="copyProduct({{ $loop->index }})">{{ __('common.copy') }}</a>--}}
|
||||
<a href="javascript:void(0)" class="btn btn-outline-danger btn-sm" @click.prevent="deleteProduct({{ $loop->index }})">{{ __('common.delete') }}</a>
|
||||
@hook('admin.product.list.action')
|
||||
@else
|
||||
<a href="javascript:void(0)" class="btn btn-outline-secondary btn-sm" @click.prevent="restoreProduct({{ $loop->index }})">{{ __('common.restore') }}</a>
|
||||
@hook('admin.products.trashed.action')
|
||||
@hook('admin.logistics.trashed.action')
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{ $products->withQueryString()->links('admin::vendor/pagination/bootstrap-4') }}
|
||||
{{ $logistics->withQueryString()->links('admin::vendor/pagination/bootstrap-4') }}
|
||||
@else
|
||||
<x-admin-no-data />
|
||||
@endif
|
||||
|
|
@ -188,7 +116,7 @@
|
|||
order_by: bk.getQueryString('order_by', ''),
|
||||
},
|
||||
selectedIds: [],
|
||||
productIds: @json($products->pluck('id')),
|
||||
productIds: @json($logistics->pluck('id')),
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
@ -224,7 +152,7 @@
|
|||
cancelButtonText: '{{ __('common.cancel') }}',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
$http.delete('products/delete', {ids: this.selectedIds}).then((res) => {
|
||||
$http.delete('logistics/delete', {ids: this.selectedIds}).then((res) => {
|
||||
layer.msg(res.message)
|
||||
location.reload();
|
||||
})
|
||||
|
|
@ -277,7 +205,7 @@
|
|||
this.$confirm('{{ __('common.confirm_delete') }}', '{{ __('common.text_hint') }}', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
$http.delete('products/' + id).then((res) => {
|
||||
$http.delete('logistics/' + id).then((res) => {
|
||||
this.$message.success(res.message);
|
||||
location.reload();
|
||||
})
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@
|
|||
<el-link type="danger" class="ms-2" @click="removeSourceVariant(variantIndex)">{{ __('common.delete') }}</el-link>
|
||||
</div>
|
||||
<div>
|
||||
<el-checkbox v-model="variant.isNumSelect" @change="(e) => {variantIsNumSelect(e, variantIndex)}" border size="mini" class="me-2 bg-white">{{ __('admin/product.num_select') }}</el-checkbox>
|
||||
<el-checkbox v-model="variant.isImage" @change="(e) => {variantIsImage(e, variantIndex)}" border size="mini" class="me-2 bg-white">{{ __('admin/product.add_variable_image') }}</el-checkbox>
|
||||
<el-button type="primary" plain size="mini" @click="modalVariantOpenButtonClicked(variantIndex, -1)">{{ __('admin/product.add_variable_value') }}</el-button>
|
||||
</div>
|
||||
|
|
@ -792,6 +793,13 @@
|
|||
})
|
||||
}
|
||||
},
|
||||
variantIsNumSelect(e, index) {
|
||||
this.source.variables.forEach(v => {
|
||||
v.isNumSelect = false;
|
||||
})
|
||||
this.source.variables[index].isNumSelect = true;
|
||||
}
|
||||
,
|
||||
|
||||
batchSettingVariantImage() {
|
||||
bk.fileManagerIframe(images => {
|
||||
|
|
|
|||
|
|
@ -234,6 +234,58 @@ body.page-product {
|
|||
}
|
||||
}
|
||||
}
|
||||
.variable-info-select {
|
||||
.variable-info-select-num {
|
||||
.variable-info-select-num-show {
|
||||
> div {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
margin-left: 0;
|
||||
min-width: 3rem;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
flex-direction: column;
|
||||
// border-radius: 2rem;
|
||||
transition: all .1s ease-in-out;
|
||||
|
||||
&:hover, &.selected {
|
||||
border-color: #222;
|
||||
}
|
||||
|
||||
> span.image {
|
||||
width: 50px;
|
||||
margin-top: -0.5rem;
|
||||
margin-left: -0.5rem;
|
||||
margin-right: -0.5rem;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
&:not(.selected) {
|
||||
&.disabled {
|
||||
border: 1px dashed #bfbfbf;
|
||||
color: #999;
|
||||
font-weight: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.quantity-wrap {
|
||||
height: 43px;
|
||||
@media (max-width: 768px) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.peoduct-info {
|
||||
|
|
@ -278,6 +330,36 @@ body.page-product {
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.totals {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
padding-bottom: .3rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 1px solid #E6E6E6;
|
||||
|
||||
> li {
|
||||
display: flex;
|
||||
align-items: center; // flex-start | center
|
||||
justify-content: space-between; // flex-end | center | space-between
|
||||
margin-bottom: 14px;
|
||||
&:last-of-type {
|
||||
font-weight: bold;
|
||||
|
||||
> span {
|
||||
&:last-of-type {
|
||||
color: #dc3545;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
&:first-of-type {
|
||||
font-size: .8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -326,4 +408,4 @@ body.page-product {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
*/
|
||||
return [
|
||||
'country_name' => 'Country Name',
|
||||
'country' => 'Country',
|
||||
|
||||
'countries_index' => 'Country List',
|
||||
'countries_create' => 'Create Country',
|
||||
|
|
|
|||
|
|
@ -55,4 +55,25 @@ return [
|
|||
'confirm_batch_status' => 'Confirm to modify the status of the selected logistics in batches? ',
|
||||
'confirm_batch_restore' => 'Confirm to restore the selected product? ',
|
||||
'confirm_delete_restore' => 'Are you sure you want to empty the recycle bin? ',
|
||||
'warehouse_name' => 'Warehouse name',
|
||||
'type'=>'Type',
|
||||
|
||||
'first_weight'=>'First weight',
|
||||
'first_weight_fee'=>'First weight fee',
|
||||
'continuation_weight_max'=>'Continuation weight max',
|
||||
'add_weight'=>'Add weight',
|
||||
'continuation_weight_fee'=>'Continuation weight fee',
|
||||
'num_fee'=>'Num fee',
|
||||
'throwing_ratio'=>'Throwing ratio',
|
||||
'day_min'=>'Day min',
|
||||
'day_max'=>'Day max',
|
||||
'day'=>'Day',
|
||||
'work_day'=>'Work day',
|
||||
'type_weight'=>'Billing by weight',
|
||||
'type_num'=>'Quantity based billing',
|
||||
'type_free'=>'Merchant package shipping',
|
||||
'first_num'=>'First num',
|
||||
'first_num_fee'=>'First num fee',
|
||||
'add_num'=>'Add num',
|
||||
'continuation_num_fee'=>'Continuation num fee',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -20,4 +20,5 @@ return [
|
|||
'pages_update' => 'Edit',
|
||||
'pages_delete' => 'Delete',
|
||||
'inquiry' => 'Inquiry',
|
||||
'logistics' => 'Logistics',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -55,4 +55,5 @@ return [
|
|||
'confirm_batch_status' => 'Confirm to modify the status of the selected products in batches? ',
|
||||
'confirm_batch_restore' => 'Confirm to restore the selected product? ',
|
||||
'confirm_delete_restore' => 'Are you sure you want to empty the recycle bin? ',
|
||||
'num_select' => 'Num select ',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
*/
|
||||
return [
|
||||
'country_name' => '国家名称',
|
||||
'index' => '国家',
|
||||
|
||||
'countries_index' => '国家列表',
|
||||
'countries_create' => '创建国家',
|
||||
|
|
|
|||
|
|
@ -59,5 +59,25 @@ return [
|
|||
'price_setting_by' =>[
|
||||
'sku' => '根据规格设置价格',
|
||||
'num' => '根据数量设置价格',
|
||||
]
|
||||
],
|
||||
'warehouse_name' => '仓库名称',
|
||||
'type'=>'类型',
|
||||
'first_weight'=>'首重',
|
||||
'first_weight_fee'=>'首重运费',
|
||||
'continuation_weight_max'=>'续重范围',
|
||||
'add_weight'=>'每增加重量',
|
||||
'continuation_weight_fee'=>'续重运费',
|
||||
'num_fee'=>'一件价格',
|
||||
'throwing_ratio'=>'计抛比',
|
||||
'day_min'=>'最短时间',
|
||||
'day_max'=>'最长时间',
|
||||
'day'=>'时效',
|
||||
'work_day'=>'工作天',
|
||||
'type_weight'=>'按重量计费',
|
||||
'type_num'=>'按数量计费',
|
||||
'type_free'=>'商家包邮',
|
||||
'first_num'=>'首件数量',
|
||||
'first_num_fee'=>'首件运费',
|
||||
'add_num'=>'每增加件',
|
||||
'continuation_num_fee'=>'续件运费',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -21,4 +21,5 @@ return [
|
|||
'pages_delete' => '删除文章',
|
||||
|
||||
'inquiry' => '询盘管理',
|
||||
'logistics' => '物流管理',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -59,5 +59,6 @@ return [
|
|||
'price_setting_by' =>[
|
||||
'sku' => '根据规格设置价格',
|
||||
'num' => '根据数量设置价格',
|
||||
]
|
||||
],
|
||||
'num_select' => '数量选择',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@
|
|||
<div class="variables-wrap mb-4" v-if="source.variables.length">
|
||||
<div class="variable-group mb-2" v-for="variable, variable_index in source.variables" :key="variable_index">
|
||||
<p class="mb-2">@{{ variable.name }}</p>
|
||||
<div class="variable-info">
|
||||
<div class="variable-info" v-if="!variable.isNumSelect">
|
||||
<div
|
||||
v-for="value, value_index in variable.values"
|
||||
@click="checkedVariableValue(variable_index, value_index, value)"
|
||||
|
|
@ -154,6 +154,28 @@
|
|||
@{{ value.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="variable-info-select" v-if="variable.isNumSelect">
|
||||
<div class="variable-info-select-num" v-for="value, value_index in variable.values" style="display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 500px;">
|
||||
<div class="variable-info-select-num-show">
|
||||
<div
|
||||
@click="checkedVariableValue(variable_index, value_index, value)"
|
||||
:key="value_index">
|
||||
<span class="image" v-if="value.image"><img :src="value.image" class="img-fluid"></span>
|
||||
@{{ value.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="quantity-wrap">
|
||||
<input type="text" @change="quantityChange" class="form-control" :disabled="!product.quantity" onkeyup="this.value=this.value.replace(/\D/g,'')" v-model="value.quantity" value="0" :name="'variables['+ variable_index +'].value['+value_index+'].quantity'">
|
||||
<div class="right">
|
||||
<i class="bi bi-chevron-up" @click="quantityChange"></i>
|
||||
<i class="bi bi-chevron-down" @click="quantityChange"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -162,8 +184,8 @@
|
|||
@hook('product.detail.buy.before')
|
||||
@hookwrapper('product.detail.quantity.input')
|
||||
<div class="quantity-wrap">
|
||||
<input type="text" class="form-control" :disabled="!product.quantity" onkeyup="this.value=this.value.replace(/\D/g,'')" v-model="quantity" name="quantity">
|
||||
<div class="right">
|
||||
<input type="text" readonly class="form-control" :disabled="!product.quantity" onkeyup="this.value=this.value.replace(/\D/g,'')" v-model="quantity" name="quantity">
|
||||
<div class="right" v-if="!isNumSelect">
|
||||
<i class="bi bi-chevron-up"></i>
|
||||
<i class="bi bi-chevron-down"></i>
|
||||
</div>
|
||||
|
|
@ -208,7 +230,7 @@
|
|||
@hookwrapper('product.detail.quantity.input')
|
||||
<div class="quantity-wrap">
|
||||
<input type="text" class="form-control" :disabled="!product.quantity" onkeyup="this.value=this.value.replace(/\D/g,'')" v-model="quantity" name="quantity">
|
||||
<div class="right">
|
||||
<div class="right" v-if="!isNumSelect">
|
||||
<i class="bi bi-chevron-up"></i>
|
||||
<i class="bi bi-chevron-down"></i>
|
||||
</div>
|
||||
|
|
@ -226,6 +248,11 @@
|
|||
@endhookwrapper
|
||||
</div>
|
||||
@endif
|
||||
<ul class="totals">
|
||||
<li><span>Product Total</span><span>@{{ product_total }}</span></li>
|
||||
<li><span>Shipping Fee</span><span>@{{ shipping_fee }}</span></li>
|
||||
<li><span>Order Total</span><span>@{{ order_tota }}</span></li>
|
||||
</ul>
|
||||
<button
|
||||
style="width: 16em;"
|
||||
class="btn btn-outline-dark my-lg-2 add-cart fw-bold"
|
||||
|
|
@ -370,6 +397,10 @@
|
|||
el: '#product-app',
|
||||
|
||||
data: {
|
||||
product_total:0,
|
||||
shipping_fee:0,
|
||||
order_tota:0,
|
||||
isNumSelect:false,
|
||||
selectedVariantsIndex: [], // 选中的变量索引
|
||||
images: [],
|
||||
product: {
|
||||
|
|
@ -384,7 +415,7 @@
|
|||
quantity: 0,
|
||||
sku: "",
|
||||
},
|
||||
quantity: 1,
|
||||
quantity: 0,
|
||||
source: {
|
||||
skus: @json($product['skus']),
|
||||
variables: @json($product['variables'] ?? []),
|
||||
|
|
@ -418,10 +449,37 @@
|
|||
},
|
||||
},
|
||||
|
||||
watch:{
|
||||
quantity: function(newval,oldval){
|
||||
let price = 0;
|
||||
if(this.numPrices.light == 0){
|
||||
price = this.product.price
|
||||
}else {
|
||||
price = this.numPrices[0].price
|
||||
this.numPrices.forEach(v => {
|
||||
if(this.quantity > v.num){}
|
||||
price = v.price
|
||||
})
|
||||
}
|
||||
|
||||
this.product_total = (price * this.quantity).toFixed(2)
|
||||
this.shipping_fee = (this.product_total * 0.1 ).toFixed(2)
|
||||
this.order_tota = (this.product_total *1 + this.shipping_fee*1).toFixed(2)
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if(this.price_setting === 'num'){
|
||||
this.quantity = this.numPrices[0].num;
|
||||
// this.quantity = this.numPrices[0].num;
|
||||
}
|
||||
this.source.variables.forEach(variable => {
|
||||
if(variable.isNumSelect == true){
|
||||
this.isNumSelect = true;
|
||||
}
|
||||
variable.values.forEach(value => {
|
||||
value.quantity = 0
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
beforeMount () {
|
||||
|
|
@ -449,6 +507,19 @@
|
|||
},
|
||||
|
||||
methods: {
|
||||
quantityChange(){
|
||||
setTimeout(()=>{
|
||||
|
||||
let num = 0;
|
||||
this.source.variables.forEach(variable => {
|
||||
variable.values.forEach(value => {
|
||||
num += parseInt(value.quantity);
|
||||
})
|
||||
})
|
||||
this.quantity = num;
|
||||
console.log(this.quantity,num)
|
||||
},200);
|
||||
},
|
||||
addSinger(form){ // 点击确定按钮添加方法
|
||||
|
||||
this.$refs[form].validate((valid) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue