4
0
Fork 0

goods list

master
panliang 2022-08-01 14:50:37 +08:00
parent 183ec96d4f
commit 54bd8cc1a3
15 changed files with 553 additions and 3 deletions

View File

@ -0,0 +1,3 @@
<?php
return [];

View File

@ -0,0 +1,36 @@
<?php
return [
'labels' => [
'Goods' => '商品信息',
'goods' => '商品信息',
],
'fields' => [
'category_id' => '分类',
'category' => [
'name' => '分类'
],
'brand_id' => '品牌',
'brand' => [
'name' => '品牌'
],
'type_id' => '类别',
'type' => [
'name' => '类别'
],
'name' => '名称',
'goods_sn' => '编号',
'cover_image' => '封面图',
'price' => '售价',
'spec' => '规格',
'attr' => '属性',
'part' => '配件',
'on_sale' => '上架',
'stock' => '库存',
'sold_count' => '销量',
'images' => '详细图',
'content' => '内容',
'created_at' => '创建时间',
'updated_at' => '更新时间',
]
];

View File

@ -0,0 +1,11 @@
<!-- $model 当前行数据 -->
<!-- $name 字段名称 -->
<!-- $value 为当前列的值 -->
@if($value)
@foreach($value as $item)
<div class="mt-1">
<span class="label bg-info">{{ $item['name'] }}</span>
<span>{{ $item['value'] }}</span>
</div>
@endforeach
@endif

View File

@ -0,0 +1,13 @@
<!-- $model 当前行数据 -->
<!-- $name 字段名称 -->
<!-- $value 为当前列的值 -->
@if($value)
@foreach($value as $item)
<div class="mt-1">
<span class="label bg-info">{{ $item['name'] }}</span>
@foreach($item['values'] as $value)
<span>{{ $value['value'] }}({{ $value['price'] }})</span>
@endforeach
</div>
@endforeach
@endif

View File

@ -0,0 +1,109 @@
<?php
namespace Peidikeji\Goods;
use Peidikeji\Goods\Models\{Goods, GoodsSku};
use Illuminate\Support\Str;
class GoodsService
{
public static function make(...$params)
{
return new static(...$params);
}
public function generateSn()
{
return (string)Str::uuid();
}
public function clearSku(Goods $goods)
{
GoodsSku::where('goods_id', $goods->id)->delete();
}
public function generateSku(Goods $goods)
{
$this->clearSku($goods);
if ($goods->spec) {
$spec = $goods->spec;
$goods_price = $goods->price;
$goods_name = $goods->name;
$specList = [];
foreach ($spec as $item) {
$items = [];
foreach($item['values'] as $value) {
array_push($items, [
'name' => $item['name'],
'value' => $value['value'],
'price' => $value['price'],
]);
}
array_push($specList, $items);
}
$cartesianList = $this->cartesianProduct($specList);
foreach($cartesianList as $items) {
$sub_goods_name = $goods_name;
$sub_price = $goods_price;
$goods->skus()->create([
'sn' => $this->generateSn(),
'name' => $sub_goods_name,
'price' => $sub_price,
'stock' => $goods->stock,
'spec' => $items,
]);
}
}
}
protected function cartesianProduct($sets)
{
// 保存结果
$result = [];
if (count($sets) === 1) {
// 保存临时数据
$tmp = array();
// 结果与下一个集合计算笛卡尔积
foreach($sets[0] as $set) {
$item = [];
if (isset($set['value'])) {
array_push($item, $set);
} else {
$item = $set;
}
array_push($item, $set);
$tmp[] = $item;
}
// 将笛卡尔积写入结果
$result = $tmp;
return $result;
}
// 循环遍历集合数据
for($i = 0; $i < count($sets) -1 ; $i++) {
// 初始化
if($i==0){
$result = $sets[$i];
}
// 保存临时数据
$tmp = array();
// 结果与下一个集合计算笛卡尔积
foreach($result as $res) {
foreach($sets[$i+1] as $set) {
$item = [];
if (isset($res['value'])) {
array_push($item, $res);
} else {
$item = $res;
}
array_push($item, $set);
$tmp[] = $item;
}
}
// 将笛卡尔积写入结果
$result = $tmp;
}
return $result;
}
}

View File

@ -10,5 +10,6 @@ class GoodsServiceProvider extends ServiceProvider
protected $menu = [
['title' => '商品管理', 'uri' => '', 'icon' => ''],
['title' => '商品分类', 'uri' => 'goods/category', 'icon' => '', 'parent' => '商品管理'],
['title' => '商品信息', 'uri' => 'goods', 'icon' => '', 'parent' => '商品管理'],
];
}

View File

@ -0,0 +1,83 @@
<?php
namespace Peidikeji\Goods\Http\Controllers\Admin;
use Dcat\Admin\Grid;
use Dcat\Admin\Grid\Tools\Selector;
use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\Show;
use Peidikeji\Goods\Models\Goods;
use Peidikeji\Goods\Models\GoodsBrand;
use Peidikeji\Goods\Models\GoodsCategory;
use Peidikeji\Goods\Models\GoodsType;
class GoodsController extends AdminController
{
protected $translation = 'peidikeji.dcat-admin-extension-goods::goods';
protected function grid()
{
return Grid::make(Goods::with(['category', 'brand', 'type']), function (Grid $grid) {
$grid->model()->sort();
$grid->selector(function (Selector $selector) {
$brands = GoodsBrand::get();
$types = GoodsType::get();
$categories = GoodsCategory::where('level', 3)->get();
$prices = ['0-999', '1000-1999', '2000-4999', '5000+'];
$selector->selectOne('category_id', __('peidikeji.dcat-admin-extension-goods::goods.fields.category_id'), $categories->pluck('name', 'id'));
$selector->selectOne('brand_id', __('peidikeji.dcat-admin-extension-goods::goods.fields.brand_id'), $brands->pluck('name', 'id'));
$selector->selectOne('type_id', __('peidikeji.dcat-admin-extension-goods::goods.fields.type_id'), $types->pluck('name', 'id'));
$selector->selectOne('price', __('peidikeji.dcat-admin-extension-goods::goods.fields.price'), $prices, function ($q, $value) use ($prices) {
$parsePrice = data_get($prices, $value);
if ($parsePrice) {
$parts = explode('-', $parsePrice);
$parts = array_map(fn($v) => (int)$v, $parts);
if (count($parts) > 1) {
$q->whereBetween('price', $parts);
} else {
$q->where('price', '>', $parts[0]);
}
}
});
});
$grid->column('goods_sn');
$grid->column('category.name');
$grid->column('brand.name');
$grid->column('type.name')->label();
$grid->column('name')->display(function () {
return ($this->cover_image ? '<img src="'.$this->cover_image.'" width="60" class="img-thumbnail"/>&nbsp;' : '') . '<a href="'.admin_url('goods/' . $this->id).'">'.$this->name.'</a>';
});
$grid->column('price');
$grid->column('spec')->view('peidikeji.dcat-admin-extension-goods::grid.spec');
$grid->column('on_sale')->switch();
$grid->column('sold_count');
$grid->disableRowSelector();
});
}
protected function detail($id)
{
$info = Goods::with(['category', 'brand', 'type'])->findOrFail($id);
$show = Show::make($info);
$show->field('goods_sn');
$show->field('category.name');
$show->field('brand.name');
$show->field('type.name');
$show->field('name');
$show->field('price');
$show->field('cover_image')->image('', 100);
$show->field('images')->image('', 100);
$show->field('content')->image('');
$show->field('spec')->view('peidikeji.dcat-admin-extension-goods::grid.spec');
$show->field('attr')->view('peidikeji.dcat-admin-extension-goods::grid.attr');
$show->field('part')->view('peidikeji.dcat-admin-extension-goods::grid.spec');
$show->field('on_sale')->bool();
$show->field('sold_count');
$show->field('created_at')->as(fn($v) => $this->created_at->format('Y-m-d H:i:s'));
$show->field('updated_at')->as(fn($v) => $this->updated_at->format('Y-m-d H:i:s'));
return $show;
}
}

View File

@ -5,3 +5,5 @@ namespace Peidikeji\Goods\Http\Controllers\Admin;
use Illuminate\Support\Facades\Route;
Route::resource('goods/category', GoodsCategoryController::class);
Route::resource('goods', GoodsController::class);

View File

@ -11,5 +11,38 @@ class Goods extends Model
protected $table = 'goods';
protected $fillable = ['attr', 'category_id', 'content', 'cover_image', 'deleted_at', 'description', 'goods_sn', 'images', 'name', 'on_sale', 'part', 'price', 'sold_count', 'spec', 'stock'];
protected $fillable = ['attr', 'category_id', 'type_id', 'brand_id', 'content', 'cover_image', 'deleted_at', 'description', 'goods_sn', 'images', 'name', 'on_sale', 'part', 'price', 'sold_count', 'spec', 'stock'];
protected $casts = [
'attr' => 'array',
'spec' => 'array',
'part' => 'array',
'content' => 'array',
'images' => 'array',
];
public function category()
{
return $this->belongsTo(GoodsCategory::class, 'category_id');
}
public function type()
{
return $this->belongsTo(GoodsType::class, 'type_id');
}
public function brand()
{
return $this->belongsTo(GoodsBrand::class, 'brand_id');
}
public function skus()
{
return $this->hasMany(GoodsSku::class, 'goods_id');
}
public function scopeSort($q)
{
return $q->orderBy('created_at', 'desc');
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Peidikeji\Goods\Models;
use Illuminate\Database\Eloquent\Model;
class GoodsBrand extends Model
{
protected $table = 'goods_brand';
protected $fillable = ['name', 'image'];
public $timestamps = false;
}

View File

@ -0,0 +1,23 @@
<?php
namespace Peidikeji\Goods\Models;
use Illuminate\Database\Eloquent\Model;
class GoodsSku extends Model
{
protected $table = 'goods_sku';
protected $fillable = ['sn', 'goods_id', 'name', 'price', 'stock', 'spec'];
protected $casts = [
'spec' => 'array',
];
public $timestamps = false;
public function goods()
{
return $this->belongsTo(Goods::class, 'goods_id');
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Peidikeji\Goods\Models;
use Illuminate\Database\Eloquent\Model;
class GoodsType extends Model
{
protected $table = 'goods_type';
protected $fillable = ['name', 'attr', 'spec', 'part'];
protected $casts = [
'attr' => 'array',
'spec' => 'array',
'part' => 'array',
];
public $timestamps = false;
}

View File

@ -25,18 +25,36 @@ class CreateGoodsTable extends Migration
$table->unsignedTinyInteger('is_enable')->default(1)->comment('状态');
$table->string('path')->default('-')->comment('所有的父级ID');
$table->comment('商品分类');
$table->comment('商品-分类');
});
Schema::create('goods_type', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->json('attr')->nullable()->comment('属性[{name: "属性名", values: [可选值]}]');
$table->json('spec')->nullable()->comment('规格[{name: "属性名", values: [可选值]}]');
$table->json('part')->nullable()->comment('配件[{name: "属性名", values: [可选值]}]');
$table->comment('商品-类型');
});
Schema::create('goods_brand', function (Blueprint $table) {
$table->id();
$table->string('name')->comment('名称');
$table->string('image')->nullable()->comment('图标');
$table->comment('商品-品牌');
});
Schema::create('goods', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('category_id')->comment('所属分类, 关联 goods_category.id');
$table->unsignedBigInteger('type_id')->nullable()->comment('所属类别');
$table->unsignedBigInteger('brand_id')->nullable()->comment('所属品牌');
$table->string('name')->comment('商品名称');
$table->string('goods_sn')->unique()->comment('编号');
$table->string('cover_image')->nullable()->comment('封面图');
$table->json('images')->nullable()->comment('图片集');
$table->string('description')->nullable()->comment('描述');
$table->text('content')->nullable()->comment('详细');
$table->json('content')->nullable()->comment('详细');
$table->unsignedInteger('on_sale')->default(0)->comment('是否上架');
$table->unsignedInteger('stock')->default(0)->comment('库存');
$table->unsignedInteger('sold_count')->default(0)->comment('销量');
@ -48,6 +66,8 @@ class CreateGoodsTable extends Migration
$table->softDeletes();
$table->foreign('category_id')->references('id')->on('goods_category');
$table->foreign('type_id')->references('id')->on('goods_type');
$table->foreign('brand_id')->references('id')->on('goods_brand');
$table->comment('商品');
});
@ -77,5 +97,7 @@ class CreateGoodsTable extends Migration
Schema::dropIfExists('goods_sku');
Schema::dropIfExists('goods');
Schema::dropIfExists('goods_category');
Schema::dropIfExists('goods_type');
Schema::dropIfExists('goods_brand');
}
};

View File

@ -0,0 +1,179 @@
<?php
use Illuminate\Database\Seeder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Peidikeji\Goods\GoodsService;
use Peidikeji\Goods\Models\Goods;
use Peidikeji\Goods\Models\GoodsBrand;
use Peidikeji\Goods\Models\GoodsCategory;
use Peidikeji\Goods\Models\GoodsSku;
use Peidikeji\Goods\Models\GoodsType;
class GoodsTableSeeder extends Seeder
{
public function run()
{
DB::statement('SET FOREIGN_KEY_CHECKS = 0;');
GoodsCategory::truncate();
$categoryList = [
['name' => '1', 'children' => [
['name' => '1-1', 'children' => [
['name' => '1-1-1']
]]
]],
['name' => '2', 'children' => [
['name' => '2-2', 'children' => [
['name' => '2-2-2']
]]
]],
];
$this->createCategory($categoryList);
GoodsType::truncate();
$types = [
[
'name' => '手机',
'attr' => [
['name' => '入网型号', 'values' => ['5G', '4G']],
['name' => '上市年份', 'values' => null],
['name' => '品牌', 'values' => null]
],
'spec' => [
['name' => '颜色', 'values' => ['白色', '红色', '黑色']],
['name' => '内存', 'values' => ['32G', '64G', '128G']],
],
'part' => [
['name' => '套餐', 'values' => ['套餐1', '套餐2', '套餐3']]
]
],
[
'name' => '笔记本电脑',
'attr' => [
['name' => '屏幕类型', 'values' => ['LED 背光显示屏']],
['name' => '物理分辨率', 'values' => ['3072 x 1920 (226 ppi)']],
],
'spec' => [
['name' => '颜色', 'values' => ['白色', '灰色']],
['name' => '内存', 'values' => ['16G', '32G', '64G']],
],
'part' => [
['name' => '套餐', 'values' => ['优惠套装1', '优惠套装2', '优惠套装3']]
]
],
];
foreach($types as $item) {
GoodsType::create($item);
}
GoodsBrand::truncate();
GoodsBrand::insert([
['name' => '三星', 'image' => 'https://img20.360buyimg.com/popshop/jfs/t1/1534/38/9873/3556/5bc93df2E73c40121/74dc92d16e483509.jpg'],
['name' => 'Apple', 'image' => 'https://img20.360buyimg.com/popshop/jfs/t2989/240/151377693/3895/30ad9044/574d36dbN262ef26d.jpg'],
]);
Goods::truncate();
GoodsSku::truncate();
$goodsList = [
[
'category_id' => 3,
'type_id' => 1,
'brand_id' => 1,
'goods_sn' => '1016',
'name' => '三星Galaxy Noet10+ 5G(SM-N9760)',
'cover_image' => 'https://img14.360buyimg.com/n5/s54x54_jfs/t1/85701/3/3164/116271/5ddcffaeEd7924f35/013d69c48b507982.jpg',
'content' => ['https://img30.360buyimg.com/sku/jfs/t1/91355/34/4028/288919/5de4c653Ed267b5d0/b67ac088ded04947.jpg'],
'images' => [
'https://img14.360buyimg.com/n0/jfs/t1/138249/34/51/266266/5edaed2fE2d4d4050/297b76afaff928bb.jpg',
'https://img14.360buyimg.com/n0/jfs/t1/85701/3/3164/116271/5ddcffaeEd7924f35/013d69c48b507982.jpg',
'https://img14.360buyimg.com/n0/jfs/t1/43997/21/12754/274595/5d5f87f1Ec419d2f9/358032d0a7a2ccd7.jpg'
],
'stock' => 100,
'price' => 6499.00,
'attr' => [
['name' => '入网型号', 'value' => '5G'],
['name' => '品牌', 'value' => '三星Galaxy'],
['name' => '上市年份', 'value' => '2020'],
],
'spec' => [
['name' => '颜色', 'values' => [
['value' => '白色', 'price' => 0],
['value' => '红色', 'price' => 800],
['value' => '黑色', 'price' => 0],
]],
['name' => '内存', 'values' => [
['value' => '32G', 'price' => 0],
['value' => '64G', 'price' => 1000],
['value' => '128G', 'price' => 2000],
]]
],
'part' => [
['name' => '套餐', 'values' => [
['value' => '套餐1', 'price' => 850],
['value' => '套餐2', 'price' => 1200],
['value' => '套餐3', 'price' => 1800],
]]
],
],
[
'category_id' => 6,
'type_id' => 2,
'brand_id' => 2,
'goods_sn' => '1017',
'name' => 'MacBook Pro 16英寸',
'cover_image' => 'https://img14.360buyimg.com/n0/jfs/t1/64979/31/15492/115459/5dd3d4f2E75b0a9a6/95c273eda00e67c0.jpg',
'description' => '',
'content' => ['https://img11.360buyimg.com/cms/jfs/t1/77779/20/15834/638477/5dd3d469Eca9fa4a7/26ff2bd661580a86.jpg'],
'images' => [
'https://img14.360buyimg.com/n0/jfs/t1/64979/31/15492/115459/5dd3d4f2E75b0a9a6/95c273eda00e67c0.jpg',
'https://img14.360buyimg.com/n0/jfs/t1/50902/13/16242/169086/5dd3d4f2E19e1994f/ff8ecd5a61c1bebb.jpg',
'https://img14.360buyimg.com/n0/jfs/t1/104429/27/2676/303491/5dd3d4f3E6fd2b80a/b7213eaf5be44b49.jpg'
],
'stock' => 150,
'price' => 17999.00,
'attr' => [
['name' => '屏幕类型', 'value' => 'LED 背光显示屏'],
['name' => '物理分辨率', 'value' => '3072 x 1920 (226 ppi)'],
],
'spec' => [
['name' => '颜色', 'values' => [
['value' => '白色', 'price' => 0],
['value' => '灰色', 'price' => 0]
]],
['name' => '内存', 'values' => [
['value' => '16G', 'price' => 0],
['value' => '32G', 'price' => 3000],
['value' => '64G', 'price' => 6000],
]]
],
'part' => [
['name' => '套餐', 'values' => [
['value' => '优惠套装1', 'price' => 850],
['value' => '优惠套装2', 'price' => 650],
['value' => '优惠套装3', 'price' => 1000],
]]
],
]
];
$service = GoodsService::make();
foreach($goodsList as $item) {
$goods = Goods::create($item);
$service->generateSku($goods);
}
}
protected function createCategory($list, $pid = 0)
{
foreach($list as $key => $item) {
$attributes = Arr::except($item, ['children']);
$attributes['parent_id'] = $pid;
$attributes['sort'] = $key + 1;
$category = GoodsCategory::create($attributes);
if ($children = data_get($item, 'children')) {
$this->createCategory($children, $category->id);
}
}
}
}

View File

@ -3,5 +3,6 @@
return [
'1.0.0' => [
'CreateGoodsTable.php',
'GoodsTableSeeder.php',
],
];