商品多规格表单
This commit is contained in:
parent
42830e4938
commit
274918cb8f
|
|
@ -48,9 +48,11 @@ class ProductsController extends Controller
|
|||
return view('admin.pages.products.form.form', $data);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
public function update(Request $request, Product $product)
|
||||
{
|
||||
//
|
||||
$product = (new ProductService)->update($product, $request->all());
|
||||
|
||||
return redirect()->route('admin.products.index')->with('success', 'product updated');
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@ class Product extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['image', 'video', 'sort_order', 'status', 'variable'];
|
||||
protected $fillable = ['image', 'video', 'position', 'active', 'variables'];
|
||||
|
||||
public function skus()
|
||||
{
|
||||
return $this->hasMany(ProductSku::class);
|
||||
}
|
||||
|
||||
public function getVariableDecodedAttribute()
|
||||
public function getVariablesDecodedAttribute()
|
||||
{
|
||||
return json_decode($this->variable, true);
|
||||
return json_decode($this->variables, true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,5 +9,9 @@ class ProductSku extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['product_id', 'image', 'model', 'sku', 'price', 'quantity', 'is_default'];
|
||||
protected $fillable = ['product_id', 'variants', 'position', 'image', 'model', 'sku', 'price', 'origin_price', 'cost_price', 'quantity', 'is_default'];
|
||||
|
||||
protected $casts = [
|
||||
'variants' => 'array',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,24 +7,35 @@ use Illuminate\Support\Facades\DB;
|
|||
|
||||
class ProductService
|
||||
{
|
||||
public function create($data)
|
||||
public function create(array $data): Product
|
||||
{
|
||||
$product = new Product;
|
||||
return $this->createOrUpdate($product, $data);
|
||||
}
|
||||
|
||||
public function update(Product $product, array $data): Product
|
||||
{
|
||||
return $this->createOrUpdate($product, $data);
|
||||
}
|
||||
|
||||
protected function createOrUpdate(Product $product, array $data): Product
|
||||
{
|
||||
$isUpdating = $product->id > 0;
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$product = new Product($data);
|
||||
if (isset($data['variant'])) {
|
||||
$product->variable = json_encode($data['variant']);
|
||||
}
|
||||
$product->fill($data);
|
||||
$product->saveOrFail();
|
||||
|
||||
$skus = [];
|
||||
foreach ($data['skus'] as $index => $rawSku) {
|
||||
$sku = $rawSku;
|
||||
$sku['is_default'] = $index == 0;
|
||||
$skus[] = $sku;
|
||||
$skus[] = $rawSku;
|
||||
}
|
||||
|
||||
if ($isUpdating) {
|
||||
$product->skus()->delete();
|
||||
}
|
||||
$product->skus()->createMany($skus);
|
||||
|
||||
DB::commit();
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ class CreateTables extends Migration
|
|||
{
|
||||
Schema::create('products', function (Blueprint $table) {
|
||||
$table->id()->startingValue(100_000);
|
||||
$table->string('image');
|
||||
$table->string('video');
|
||||
$table->integer('sort_order');
|
||||
$table->boolean('status');
|
||||
$table->json('variable');
|
||||
$table->string('image')->default('');
|
||||
$table->string('video')->default('');
|
||||
$table->integer('position')->default(0);
|
||||
$table->boolean('active');
|
||||
$table->json('variables')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
|
@ -27,11 +27,15 @@ class CreateTables extends Migration
|
|||
Schema::create('product_skus', function (Blueprint $table) {
|
||||
$table->id()->startingValue(100_000);
|
||||
$table->unsignedBigInteger('product_id');
|
||||
$table->string('image');
|
||||
$table->string('model');
|
||||
$table->string('sku');
|
||||
$table->double('price');
|
||||
$table->integer('quantity');
|
||||
$table->string('variants')->default(0);
|
||||
$table->integer('position')->default(0);
|
||||
$table->string('image')->default('');
|
||||
$table->string('model')->default('');
|
||||
$table->string('sku')->default('');
|
||||
$table->double('price')->default(0);
|
||||
$table->double('origin_price')->default(0);
|
||||
$table->double('cost_price')->default(0);
|
||||
$table->integer('quantity')->default(0);
|
||||
$table->boolean('is_default');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,14 +6,15 @@
|
|||
|
||||
@section('content')
|
||||
<h2>product</h2>
|
||||
<form action="{{ route('admin.products.store') }}" method="POST" id="app">
|
||||
<form action="{{ ($product ?? null) ? route('admin.products.update', $product) : route('admin.products.store') }}" method="POST" id="app">
|
||||
@csrf
|
||||
@method(($product ?? null) ? 'PUT' : 'POST')
|
||||
|
||||
<input type="text" name="name" placeholder="Name" value="{{ old('name', $product->name ?? '') }}">
|
||||
<input type="text" name="image" placeholder="image" value="{{ old('image', $product->image ?? '') }}">
|
||||
<input type="text" name="video" placeholder="video" value="{{ old('video', $product->video ?? '') }}">
|
||||
<input type="text" name="sort_order" placeholder="sort_order" value="{{ old('sort_order', $product->sort_order ?? 0) }}">
|
||||
<input type="text" name="status" placeholder="status" value="{{ old('status', $product->status ?? 1) }}">
|
||||
<input type="text" name="position" placeholder="position" value="{{ old('position', $product->position ?? 0) }}">
|
||||
<input type="text" name="active" placeholder="active" value="{{ old('active', $product->active ?? 1) }}">
|
||||
|
||||
<div>
|
||||
<h2>skus</h2>
|
||||
|
|
@ -21,35 +22,72 @@
|
|||
<input type="radio" v-model="editing.isVariable" :value="true"> 多规格
|
||||
<div v-if="editing.isVariable">
|
||||
<div>
|
||||
<div v-for="variant in form.variable">
|
||||
<input type="text" v-model="variant.name" name="variant[0][name]" placeholder="variant name">
|
||||
|
||||
<input v-for="(value, valueIndex) in variant.values" v-model="variant.values[valueIndex]" type="text" name="variant[0][values][0]" placeholder="variant value name">
|
||||
<div v-for="(variant, variantIndex) in source.variables">
|
||||
<div>
|
||||
<input type="text" v-model="variant.name" placeholder="variant name">
|
||||
|
||||
<div v-for="(value, valueIndex) in variant.values">
|
||||
<input v-model="variant.values[valueIndex].name" type="text" placeholder="variant value name">
|
||||
</div>
|
||||
<button type="button" @click="addVariantValue(variantIndex)">Add value</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" @click="addVariant">Add variant</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div v-if="form.skus.length">
|
||||
<input v-if="form.skus.length" type="hidden" name="variables" :value="JSON.stringify(form.variables)">
|
||||
<table>
|
||||
<tr v-for="(sku, skuIndex) in form.skus">
|
||||
<td><input type="text" v-model="sku.image" name="skus[0][image]" placeholder="image"></td>
|
||||
<td><input type="text" v-model="sku.model" name="skus[0][model]" placeholder="model"></td>
|
||||
<td><input type="text" v-model="sku.sku" name="skus[0][sku]" placeholder="sku"></td>
|
||||
<td><input type="text" v-model="sku.price" name="skus[0][price]" placeholder="price" value="10"></td>
|
||||
<td><input type="text" v-model="sku.quantity" name="skus[0][quantity]" placeholder="quantity" value="10"></td>
|
||||
<td><input type="text" v-model="sku.is_default" name="skus[0][is_default]" placeholder="is_default" value="1"></td>
|
||||
</tr>
|
||||
<thead>
|
||||
<th v-for="(variant, index) in form.variables" :key="'pv-header-'+index">
|
||||
@{{ variant.name || 'No name' }}
|
||||
</th>
|
||||
<th>image</th>
|
||||
<th>model</th>
|
||||
<th>sku</th>
|
||||
<th>price</th>
|
||||
<th>orgin price</th>
|
||||
<th>cost price</th>
|
||||
<th>quantity</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(sku, skuIndex) in form.skus">
|
||||
<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 || 'No name' }}</span>
|
||||
</td>
|
||||
</template>
|
||||
<td>
|
||||
<input type="text" v-model="sku.image" :name="'skus[' + skuIndex + '][image]'" placeholder="image">
|
||||
<input type="hidden" :name="'skus[' + skuIndex + '][position]'" :value="skuIndex">
|
||||
<input type="hidden" :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" v-model="sku.model" :name="'skus[' + skuIndex + '][model]'" placeholder="model"></td>
|
||||
<td><input type="text" v-model="sku.sku" :name="'skus[' + skuIndex + '][sku]'" placeholder="sku"></td>
|
||||
<td><input type="text" v-model="sku.price" :name="'skus[' + skuIndex + '][price]'" placeholder="price"></td>
|
||||
<td><input type="text" v-model="sku.origin_price" :name="'skus[' + skuIndex + '][origin_price]'" placeholder="origin_price"></td>
|
||||
<td><input type="text" v-model="sku.cost_price" :name="'skus[' + skuIndex + '][cost_price]'" placeholder="cost_price"></td>
|
||||
<td><input type="text" v-model="sku.quantity" :name="'skus[' + skuIndex + '][quantity]'" placeholder="quantity"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!editing.isVariable">
|
||||
<div>
|
||||
<input type="text" name="skus[0][image]" placeholder="image">
|
||||
<input type="text" name="skus[0][model]" placeholder="model">
|
||||
<input type="text" name="skus[0][sku]" placeholder="sku">
|
||||
<input type="text" name="skus[0][price]" placeholder="price" value="10">
|
||||
<input type="text" name="skus[0][quantity]" placeholder="quantity" value="10">
|
||||
<input type="text" name="skus[0][is_default]" placeholder="is_default" value="1">
|
||||
<input type="text" name="skus[0][image]" placeholder="image" value="{{ old('skus.0.image', $product->skus[0]->image ?? '') }}">
|
||||
<input type="text" name="skus[0][model]" placeholder="model" value="{{ old('skus.0.model', $product->skus[0]->model ?? '') }}">
|
||||
<input type="text" name="skus[0][sku]" placeholder="sku" value="{{ old('skus.0.sku', $product->skus[0]->sku ?? '') }}">
|
||||
<input type="text" name="skus[0][price]" placeholder="price" value="{{ old('skus.0.price', $product->skus[0]->price ?? '') }}">
|
||||
<input type="text" name="skus[0][origin_price]" placeholder="origin_price" value="{{ old('skus.0.origin_price', $product->skus[0]->origin_price ?? '') }}">
|
||||
<input type="text" name="skus[0][cost_price]" placeholder="cost_price" value="{{ old('skus.0.cost_price', $product->skus[0]->cost_price ?? '') }}">
|
||||
<input type="text" name="skus[0][quantity]" placeholder="quantity" value="{{ old('skus.0.quantity', $product->skus[0]->quantity ?? '') }}">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -60,17 +98,132 @@
|
|||
|
||||
@push('footer')
|
||||
<script>
|
||||
new Vue({
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
form: {
|
||||
variable: @json($product->variable_decoded ?? []),
|
||||
variables: @json($product->variables_decoded ?? []),
|
||||
skus: @json($product->skus ?? []),
|
||||
},
|
||||
source: {
|
||||
variables: @json($product->variables_decoded ?? []),
|
||||
},
|
||||
editing: {
|
||||
isVariable: @json(($product->variable ?? null) != null),
|
||||
isVariable: @json(($product->variables ?? null) != null),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// variant value 重复次数
|
||||
variantValueRepetitions() {
|
||||
var repeats = [];
|
||||
var repeat = 1;
|
||||
for (var index = this.form.variables.length - 2; index >= 0; index--) {
|
||||
repeat *= this.form.variables[index + 1].values.length;
|
||||
repeats[index] = repeat;
|
||||
}
|
||||
// 最后一组只重复1次
|
||||
repeats.push(1);
|
||||
return repeats;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'source.variables': {
|
||||
deep: true,
|
||||
handler: function () {
|
||||
// 原始规格数据变动,过滤有效规格并同步至 form.variables
|
||||
let variants = [];
|
||||
const sourceVariants = JSON.parse(JSON.stringify(this.source.variables));
|
||||
for (var i = 0; i < sourceVariants.length; i++) {
|
||||
const sourceVariant = sourceVariants[i];
|
||||
// 排除掉没有规格值的
|
||||
if (sourceVariant.values.length > 0) {
|
||||
variants.push(sourceVariant);
|
||||
}
|
||||
}
|
||||
this.form.variables = variants;
|
||||
this.remakeSkus();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addVariant() {
|
||||
this.source.variables.push({name: '', values: []});
|
||||
},
|
||||
|
||||
addVariantValue(variantIndex) {
|
||||
this.source.variables[variantIndex].values.push({name: '', image: ''});
|
||||
},
|
||||
|
||||
remakeSkus() {
|
||||
const combos = makeVariableIndexes();
|
||||
|
||||
if (combos.length < 1) {
|
||||
this.form.skus = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// 找出已存在的组合
|
||||
const productVariantCombos = 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());
|
||||
if (index > -1) {
|
||||
skus.push(this.form.skus[index]);
|
||||
} else {
|
||||
skus.push({
|
||||
product_sku_id: 0,
|
||||
position: i,
|
||||
variants: combo,
|
||||
image: '',
|
||||
model: '',
|
||||
sku: '',
|
||||
price: null,
|
||||
quantity: null,
|
||||
is_default: i == 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
// 第一个子商品用主商品的值
|
||||
skus[0].model = this.form.model;
|
||||
skus[0].sku = this.form.sku;
|
||||
skus[0].price = this.form.price;
|
||||
skus[0].quantity = this.form.quantity;
|
||||
skus[0].status = this.form.status;
|
||||
|
||||
this.form.skus = skus;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
function makeVariableIndexes() {
|
||||
// 每组值重复次数
|
||||
var repeats = app.variantValueRepetitions;
|
||||
var results = [];
|
||||
|
||||
if (app.form.variables.length < 1) {
|
||||
return results;
|
||||
}
|
||||
|
||||
for (let i = 0; i < repeats[0] * app.form.variables[0].values.length; i++) {
|
||||
results.push([]);
|
||||
}
|
||||
for (let xIndex = 0; xIndex < repeats.length; xIndex++) { // 0 - 3
|
||||
let repeat = 0;
|
||||
let itemIndex = 0;
|
||||
for (let yIndex = 0; yIndex < results.length; yIndex++) { // 0 - 36
|
||||
results[yIndex].push(itemIndex);
|
||||
repeat++;
|
||||
if (repeat >= repeats[xIndex]) {
|
||||
repeat = 0;
|
||||
itemIndex++;
|
||||
if (itemIndex >= app.form.variables[xIndex].values.length) {
|
||||
itemIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
@foreach ($products as $product)
|
||||
<tr>
|
||||
<td>{{ $product->id }}</td>
|
||||
<td>{{ $product->variable ? '多规格' : '单规格' }}</td>
|
||||
<td>{{ $product->variables ? '多规格' : '单规格' }}</td>
|
||||
<td>
|
||||
<a href="{{ route('admin.products.edit', $product) }}">编辑</a>
|
||||
</td>
|
||||
|
|
|
|||
Loading…
Reference in New Issue