6
0
Fork 0

store-products

base
panliang 2023-02-10 14:54:56 +08:00
parent 1adcc15c7f
commit ce18e17186
14 changed files with 297 additions and 14 deletions

View File

@ -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;

View File

@ -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);
}

View File

@ -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');
}
}
}

View File

@ -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,
]);
}

View File

@ -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);
}
}

View File

@ -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,

View File

@ -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,
];
}
}

View File

@ -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,
];
}

View File

@ -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']);
// 门店下单

View File

@ -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',

View File

@ -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');
}
}

View File

@ -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');

View File

@ -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']);
});
}
}

View File

@ -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']);
});
}
}