store-products
parent
1adcc15c7f
commit
ce18e17186
|
|
@ -118,14 +118,17 @@ class ProductController extends AdminController
|
|||
$form = Form::make(StoreProductSku::with(['productSku']));
|
||||
$form->select('store_id')->options(Store::pluck('title', 'id'));
|
||||
$form->select('product_sku_id')->options(ProductSku::class)->ajax('api/product-skus');
|
||||
$form->hidden('product_spu_id');
|
||||
$form->switch('status', '状态')->default(1);
|
||||
$form->saving(function (Form $form) {
|
||||
if ($form->isCreating()) {
|
||||
$store_id = $form->input('store_id');
|
||||
$product_sku_id = $form->input('product_sku_id');
|
||||
$sku = ProductSku::findOrFail($product_sku_id);
|
||||
if (StoreProductSku::where(compact('store_id', 'product_sku_id'))->exists()) {
|
||||
return $form->response()->error('该商品已经存在');
|
||||
}
|
||||
$form->product_spu_id = $sku->spu_id;
|
||||
}
|
||||
});
|
||||
return $form;
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class StoreProduct
|
|||
$skuList = $product->skus()->get();
|
||||
$skus = [];
|
||||
foreach($skuList as $sku) {
|
||||
array_push($skus, ['amount' => $stock, 'product_sku_id' => $sku->id, 'status' => 1, 'store_id' => $store->id]);
|
||||
array_push($skus, ['amount' => $stock, 'product_sku_id' => $sku->id, 'product_spu_id' => $product->id, 'status' => 1, 'store_id' => $store->id]);
|
||||
}
|
||||
StoreProductSku::insert($skus);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace App\Endpoint\Api\Filters;
|
||||
|
||||
use App\Models\ProductCategory;
|
||||
use EloquentFilter\ModelFilter;
|
||||
|
||||
class ProductSpuFilter extends ModelFilter
|
||||
{
|
||||
/**
|
||||
* 按商品分类搜索商品
|
||||
*
|
||||
* @param int $id
|
||||
*/
|
||||
public function category($id)
|
||||
{
|
||||
$category = ProductCategory::showable()->find($id);
|
||||
|
||||
if ($category === null) {
|
||||
return $this->whereRaw('1=0');
|
||||
}
|
||||
|
||||
$ids = $category->descendants()->showable()->pluck('id');
|
||||
|
||||
if ($ids->isEmpty()) {
|
||||
$this->where('category_id', $id);
|
||||
} else {
|
||||
$this->whereIn('category_id', $ids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关键字搜索商品
|
||||
*
|
||||
* @param string $keyword
|
||||
*/
|
||||
public function keyword($keyword)
|
||||
{
|
||||
$this->where(function ($q) use ($keyword) {
|
||||
$q->whereLike('name', $keyword)->orWhere('subtitle', 'like', '%'.$keyword.'%');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品排序
|
||||
*
|
||||
* @param string $sort
|
||||
*/
|
||||
public function sort($sort)
|
||||
{
|
||||
$column = str_ireplace('-', '', $sort);
|
||||
|
||||
if (in_array($column, ['id', 'price', 'sales', 'release_at'])) {
|
||||
if ($column === 'price') {
|
||||
$column = 'sell_price';
|
||||
}
|
||||
|
||||
$this->orderBy($column, strpos($sort, '-') === 0 ? 'desc' : 'asc');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ use Illuminate\Http\Request;
|
|||
use App\Models\{Order, OrderPre, ProductSku};
|
||||
use App\Models\Store\Store;
|
||||
use App\Services\OrderService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\{DB, Storage};
|
||||
use App\Exceptions\BizException;
|
||||
use Throwable;
|
||||
use EasyWeChat\Kernel\Http\StreamResponse;
|
||||
|
|
@ -75,8 +75,16 @@ class OrderPreController extends Controller
|
|||
]
|
||||
]);
|
||||
|
||||
$app = $this->getWechatApp();
|
||||
if (config('app.env') == 'local') {
|
||||
$url = 'https://ui-avatars.com/api/?name=pdkj';
|
||||
$order_pre->update(['qrcode' => $url]);
|
||||
return response()->json([
|
||||
'order_pre' => $order_pre->id,
|
||||
'image' => $url,
|
||||
]);
|
||||
}
|
||||
|
||||
$app = $this->getWechatApp();
|
||||
|
||||
$scene = http_build_query([
|
||||
'o' => $order_pre->id,
|
||||
|
|
@ -92,9 +100,18 @@ class OrderPreController extends Controller
|
|||
]);
|
||||
|
||||
if ($response instanceof StreamResponse) {
|
||||
// 保存小程序码
|
||||
$disk = Storage::disk('public');
|
||||
$filepath = 'order-pre';
|
||||
$filename = $order_pre->id . '.png';
|
||||
$response->saveAs($disk->path($filepath), $filename);
|
||||
|
||||
$url = $disk->url($filepath . '/' . $filename);
|
||||
|
||||
$order_pre->update(['qrcode' => $url]);
|
||||
return response()->json([
|
||||
'order_pre' => $order_pre->id,
|
||||
'image' => 'data:image/png;base64,'.base64_encode($response->getBody()),
|
||||
'image' => $url,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ namespace App\Endpoint\Api\Http\Controllers;
|
|||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Store\Store;
|
||||
use App\Models\ProductSku;
|
||||
use App\Models\{ProductSku, ProductSpu};
|
||||
use App\Helpers\Paginator;
|
||||
use App\Endpoint\Api\Http\Resources\{StoreResource, StoreProductResource};
|
||||
use App\Endpoint\Api\Http\Resources\{StoreResource, StoreProductSkuResource, StoreProductSpuResource};
|
||||
use App\Endpoint\Api\Http\Resources\ProductFeatureResource;
|
||||
use App\Events\ProductSkuViewed;
|
||||
|
||||
class StoreController extends Controller
|
||||
{
|
||||
|
|
@ -29,7 +31,7 @@ class StoreController extends Controller
|
|||
return StoreResource::make($info);
|
||||
}
|
||||
|
||||
public function products($id, Request $request)
|
||||
public function productSpus($id, Request $request)
|
||||
{
|
||||
$store = Store::findOrFail($id);
|
||||
|
||||
|
|
@ -39,15 +41,109 @@ class StoreController extends Controller
|
|||
$input['sort'] = '-id';
|
||||
}
|
||||
|
||||
$fields = ['product_skus.id', 'product_skus.name', 'product_skus.cover', 'product_skus.sell_price', 'product_skus.vip_price', 'product_skus.market_price', 'store_product_skus.amount'];
|
||||
$fields = ['product_spus.id', 'product_spus.name', 'product_spus.cover', 'product_spus.sell_price', 'product_spus.vip_price', 'product_spus.market_price', 'store_product_skus.amount'];
|
||||
|
||||
$skus = $store->productSkus()
|
||||
$list = $store->productSpus()
|
||||
->select($fields)
|
||||
->distinct('store_product_skus.product_spu_id')
|
||||
->filter($input)
|
||||
// ->online()
|
||||
->wherePivot('status', 1)
|
||||
->simplePaginate(Paginator::resolvePerPage('per_page', 20, 50));
|
||||
|
||||
return StoreProductResource::collection($skus);
|
||||
return StoreProductSpuResource::collection($list);
|
||||
}
|
||||
|
||||
public function productSpu($store, $id, Request $request)
|
||||
{
|
||||
$store = Store::findOrFail($store);
|
||||
|
||||
$user = $request->user();
|
||||
$spu = $store->productSpus()->with(['specs', 'features'])->findOrFail($id);
|
||||
|
||||
$sku = $store->productSkus()->where('product_spu_id', $spu->id)->first();
|
||||
if (!$sku) {
|
||||
throw new BizException('商品sku未上架');
|
||||
}
|
||||
// 主商品的规格
|
||||
$spuSpecs = [];
|
||||
|
||||
if (count($original = (array) $sku->specs) > 0) {
|
||||
$skus = $spu->skus()->get(['id', 'specs', 'stock', 'release_at']);
|
||||
|
||||
$mapSkus = $skus->mapWithKeys(function ($item) {
|
||||
$key = implode('_', $item->specs) ?: $item->id;
|
||||
|
||||
return [$key => $item];
|
||||
});
|
||||
|
||||
foreach ($spu->specs as $spec) {
|
||||
$spuSpecItems = [];
|
||||
|
||||
foreach ($spec->items as $value) {
|
||||
// 根据当前 SKU 的规格,组装可能出现的其它规格组合
|
||||
$jSpecs = $original;
|
||||
$jSpecs[$spec->id] = $value['name'];
|
||||
|
||||
$key = implode('_', $jSpecs);
|
||||
$mapSku = $mapSkus->get($key);
|
||||
if ($mapSku) {
|
||||
$spuSpecItems[] = [
|
||||
'name' => $value['name'],
|
||||
'selected' => $sku->is($mapSku),
|
||||
'sku_id' => (int) $mapSku?->id,
|
||||
'sku_stock' => (int) $mapSku?->saleable_stock,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$spuSpecs[] = [
|
||||
'name' => $spec->name,
|
||||
'items' => $spuSpecItems,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
ProductSkuViewed::dispatch($user, $sku, now());
|
||||
|
||||
// 是否收藏商品
|
||||
$isCollected = (bool) $user?->skuFavorites()->where('sku_id', $sku->id)->exists();
|
||||
|
||||
return response()->json([
|
||||
'spu_specs' => $spuSpecs,
|
||||
'sku' => StoreProductSkuResource::make($sku),
|
||||
'is_collected' => $isCollected,
|
||||
'features' => ProductFeatureResource::collection($spu->features),
|
||||
'is_pre_sale' => !!$spu->is_pre_sale
|
||||
]);
|
||||
}
|
||||
|
||||
public function productSkus($id, Request $request)
|
||||
{
|
||||
$store = Store::findOrFail($id);
|
||||
$query = $store->productSkus()
|
||||
->select(['product_skus.id', 'product_skus.specs', 'product_skus.name', 'product_skus.cover', 'product_skus.sell_price', 'product_skus.vip_price', 'product_skus.market_price', 'store_product_skus.amount'])
|
||||
->filter($request->all())
|
||||
->when($request->filled('id'), function ($q) use ($request) {
|
||||
$id = explode(',', $request->input('id'));
|
||||
$q->whereIn('product_skus.id', $id);
|
||||
})
|
||||
->wherePivot('status', 1);
|
||||
|
||||
if ($request->input('per_page') == -1) {
|
||||
$list = $query->get();
|
||||
} else {
|
||||
$list = $query->simplePaginate(Paginator::resolvePerPage('per_page', 20, 50));
|
||||
}
|
||||
return StoreProductSkuResource::collection($list);
|
||||
}
|
||||
|
||||
public function productSku($store, $id, Request $request)
|
||||
{
|
||||
$store = Store::findOrFail($store);
|
||||
$sku = $store->productSkus()->findOrFail($id);
|
||||
|
||||
return StoreProductSkuResource::make($sku);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class OrderPreResource extends JsonResource
|
|||
'id' => $this->id,
|
||||
'store_id' => $this->store_id,
|
||||
'user_id' => $this->user_id,
|
||||
'qrcode' => $this->qrcode,
|
||||
'products' => $this->products,
|
||||
'others' => $this->others,
|
||||
'created_at' => $this->created_at->timestamp,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Endpoint\Api\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class StoreProductSkuResource extends JsonResource
|
||||
{
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'spu_id' => $this->spu_id,
|
||||
'name' => $this->name,
|
||||
'cover' => (string) $this->cover,
|
||||
'sell_price' => (string) $this->sell_price_format,
|
||||
'vip_price' => (string) $this->vip_price_format,
|
||||
'market_price' => (string) $this->market_price_format,
|
||||
'amount' => (int) $this->pivot->amount,
|
||||
'stock' => (int) $this->pivot->amount,
|
||||
'specs' => (array) $this->specs,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ namespace App\Endpoint\Api\Http\Resources;
|
|||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class StoreProductResource extends JsonResource
|
||||
class StoreProductSpuResource extends JsonResource
|
||||
{
|
||||
public function toArray($request)
|
||||
{
|
||||
|
|
@ -16,6 +16,7 @@ class StoreProductResource extends JsonResource
|
|||
'vip_price' => (string) $this->vip_price_format,
|
||||
'market_price' => (string) $this->market_price_format,
|
||||
'amount' => (int) $this->pivot->amount,
|
||||
'stock' => (int) $this->pivot->amount,
|
||||
'specs' => (array) $this->specs,
|
||||
];
|
||||
}
|
||||
|
|
@ -227,7 +227,10 @@ Route::group([
|
|||
});
|
||||
|
||||
// 门店提货
|
||||
Route::get('store/{id}/products', [StoreController::class, 'products']);
|
||||
Route::get('store/{id}/product-spus', [StoreController::class, 'productSpus']);
|
||||
Route::get('store/{id}/product-skus', [StoreController::class, 'productSkus']);
|
||||
Route::get('store/{store}/product-spu/{id}', [StoreController::class, 'productSpu']);
|
||||
Route::get('store/{store}/product-sku/{id}', [StoreController::class, 'productSku']);
|
||||
Route::apiResource('store', StoreController::class)->only(['index', 'show']);
|
||||
|
||||
// 门店下单
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class OrderPre extends Model
|
|||
{
|
||||
use HasFactory, HasDateTimeFormatter;
|
||||
|
||||
protected $fillable = ['id', 'store_id', 'user_id', 'remarks', 'products', 'others'];
|
||||
protected $fillable = ['id', 'store_id', 'user_id', 'remarks', 'products', 'others', 'qrcode'];
|
||||
|
||||
protected $casts = [
|
||||
'products' => 'array',
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class ProductSku extends Model
|
|||
{
|
||||
protected $table = 'store_product_skus';
|
||||
|
||||
protected $fillable = ['amount', 'product_sku_id', 'status', 'store_id'];
|
||||
protected $fillable = ['amount', 'product_sku_id', 'status', 'store_id', 'product_spu_id'];
|
||||
|
||||
protected $attributes = [
|
||||
'status' => 0,
|
||||
|
|
@ -26,4 +26,9 @@ class ProductSku extends Model
|
|||
{
|
||||
return $this->belongsTo(\App\Models\ProductSku::class, 'product_sku_id');
|
||||
}
|
||||
|
||||
public function productSpu()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\ProductSpu::class, 'product_spu_id');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ class Store extends Model
|
|||
return $this->belongsToMany(\App\Models\ProductSku::class, 'store_product_skus', 'store_id', 'product_sku_id')->withPivot('amount', 'status');
|
||||
}
|
||||
|
||||
public function productSpus()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\ProductSpu::class, 'store_product_skus', 'store_id', 'product_spu_id')->withPivot('amount', 'status');
|
||||
}
|
||||
|
||||
public function stockLogs()
|
||||
{
|
||||
return $this->hasMany(StockLog::class, 'store_id');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AddSpuIdToStoreProducts extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('store_product_skus', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('product_spu_id');
|
||||
});
|
||||
|
||||
DB::statement('update `store_product_skus` set `product_spu_id` = (select `spu_id` from `product_skus` where `product_skus`.`id` = `store_product_skus`.`product_sku_id`);');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('store_product_skus', function (Blueprint $table) {
|
||||
$table->dropColumn(['product_spu_id']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddQrcodeToOrderPres extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('order_pres', function (Blueprint $table) {
|
||||
$table->string('qrcode')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('order_pres', function (Blueprint $table) {
|
||||
$table->dropColumn(['qrcode']);
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue