store-products
parent
1adcc15c7f
commit
ce18e17186
|
|
@ -118,14 +118,17 @@ class ProductController extends AdminController
|
||||||
$form = Form::make(StoreProductSku::with(['productSku']));
|
$form = Form::make(StoreProductSku::with(['productSku']));
|
||||||
$form->select('store_id')->options(Store::pluck('title', 'id'));
|
$form->select('store_id')->options(Store::pluck('title', 'id'));
|
||||||
$form->select('product_sku_id')->options(ProductSku::class)->ajax('api/product-skus');
|
$form->select('product_sku_id')->options(ProductSku::class)->ajax('api/product-skus');
|
||||||
|
$form->hidden('product_spu_id');
|
||||||
$form->switch('status', '状态')->default(1);
|
$form->switch('status', '状态')->default(1);
|
||||||
$form->saving(function (Form $form) {
|
$form->saving(function (Form $form) {
|
||||||
if ($form->isCreating()) {
|
if ($form->isCreating()) {
|
||||||
$store_id = $form->input('store_id');
|
$store_id = $form->input('store_id');
|
||||||
$product_sku_id = $form->input('product_sku_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()) {
|
if (StoreProductSku::where(compact('store_id', 'product_sku_id'))->exists()) {
|
||||||
return $form->response()->error('该商品已经存在');
|
return $form->response()->error('该商品已经存在');
|
||||||
}
|
}
|
||||||
|
$form->product_spu_id = $sku->spu_id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return $form;
|
return $form;
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ class StoreProduct
|
||||||
$skuList = $product->skus()->get();
|
$skuList = $product->skus()->get();
|
||||||
$skus = [];
|
$skus = [];
|
||||||
foreach($skuList as $sku) {
|
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);
|
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\{Order, OrderPre, ProductSku};
|
||||||
use App\Models\Store\Store;
|
use App\Models\Store\Store;
|
||||||
use App\Services\OrderService;
|
use App\Services\OrderService;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\{DB, Storage};
|
||||||
use App\Exceptions\BizException;
|
use App\Exceptions\BizException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use EasyWeChat\Kernel\Http\StreamResponse;
|
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([
|
$scene = http_build_query([
|
||||||
'o' => $order_pre->id,
|
'o' => $order_pre->id,
|
||||||
|
|
@ -92,9 +100,18 @@ class OrderPreController extends Controller
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($response instanceof StreamResponse) {
|
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([
|
return response()->json([
|
||||||
'order_pre' => $order_pre->id,
|
'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 Illuminate\Http\Request;
|
||||||
use App\Models\Store\Store;
|
use App\Models\Store\Store;
|
||||||
use App\Models\ProductSku;
|
use App\Models\{ProductSku, ProductSpu};
|
||||||
use App\Helpers\Paginator;
|
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
|
class StoreController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -29,7 +31,7 @@ class StoreController extends Controller
|
||||||
return StoreResource::make($info);
|
return StoreResource::make($info);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function products($id, Request $request)
|
public function productSpus($id, Request $request)
|
||||||
{
|
{
|
||||||
$store = Store::findOrFail($id);
|
$store = Store::findOrFail($id);
|
||||||
|
|
||||||
|
|
@ -39,15 +41,109 @@ class StoreController extends Controller
|
||||||
$input['sort'] = '-id';
|
$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)
|
->select($fields)
|
||||||
|
->distinct('store_product_skus.product_spu_id')
|
||||||
->filter($input)
|
->filter($input)
|
||||||
// ->online()
|
// ->online()
|
||||||
->wherePivot('status', 1)
|
->wherePivot('status', 1)
|
||||||
->simplePaginate(Paginator::resolvePerPage('per_page', 20, 50));
|
->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,
|
'id' => $this->id,
|
||||||
'store_id' => $this->store_id,
|
'store_id' => $this->store_id,
|
||||||
'user_id' => $this->user_id,
|
'user_id' => $this->user_id,
|
||||||
|
'qrcode' => $this->qrcode,
|
||||||
'products' => $this->products,
|
'products' => $this->products,
|
||||||
'others' => $this->others,
|
'others' => $this->others,
|
||||||
'created_at' => $this->created_at->timestamp,
|
'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;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
class StoreProductResource extends JsonResource
|
class StoreProductSpuResource extends JsonResource
|
||||||
{
|
{
|
||||||
public function toArray($request)
|
public function toArray($request)
|
||||||
{
|
{
|
||||||
|
|
@ -16,6 +16,7 @@ class StoreProductResource extends JsonResource
|
||||||
'vip_price' => (string) $this->vip_price_format,
|
'vip_price' => (string) $this->vip_price_format,
|
||||||
'market_price' => (string) $this->market_price_format,
|
'market_price' => (string) $this->market_price_format,
|
||||||
'amount' => (int) $this->pivot->amount,
|
'amount' => (int) $this->pivot->amount,
|
||||||
|
'stock' => (int) $this->pivot->amount,
|
||||||
'specs' => (array) $this->specs,
|
'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']);
|
Route::apiResource('store', StoreController::class)->only(['index', 'show']);
|
||||||
|
|
||||||
// 门店下单
|
// 门店下单
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class OrderPre extends Model
|
||||||
{
|
{
|
||||||
use HasFactory, HasDateTimeFormatter;
|
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 = [
|
protected $casts = [
|
||||||
'products' => 'array',
|
'products' => 'array',
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ class ProductSku extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'store_product_skus';
|
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 = [
|
protected $attributes = [
|
||||||
'status' => 0,
|
'status' => 0,
|
||||||
|
|
@ -26,4 +26,9 @@ class ProductSku extends Model
|
||||||
{
|
{
|
||||||
return $this->belongsTo(\App\Models\ProductSku::class, 'product_sku_id');
|
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');
|
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()
|
public function stockLogs()
|
||||||
{
|
{
|
||||||
return $this->hasMany(StockLog::class, 'store_id');
|
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