diff --git a/app/Admin/Actions/Form/ProductImportForm.php b/app/Admin/Actions/Form/ProductImportForm.php new file mode 100644 index 00000000..047e6c49 --- /dev/null +++ b/app/Admin/Actions/Form/ProductImportForm.php @@ -0,0 +1,33 @@ +load($file); + DB::commit(); + return $this->response()->success('数据导入成功')->refresh(); + } catch (\Exception $e) { + DB::rollback(); + dd($e); + return $this->response()->error($e->getMessage()); + } + } + + public function form() + { + $this->text('file', '上传数据(Excel)') + ->attribute('type', 'file') + ->attribute('accept', 'application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + ->rules('required', ['required' => '文件不能为空']); + } +} diff --git a/app/Admin/Actions/Modal/ProductImport.php b/app/Admin/Actions/Modal/ProductImport.php new file mode 100644 index 00000000..97529c7a --- /dev/null +++ b/app/Admin/Actions/Modal/ProductImport.php @@ -0,0 +1,36 @@ +商品分类是否录入?', + '商品图片是否上传?', + '商品规格属性是否录入?', + '运费模板是否录入?', + ]; + $html = '
'; + foreach($items as $item) { + $html .= '
  • '.$item.'
  • '; + } + $html .= '
    '; + $alert = Alert::make($html, '请先检查数据是否完整'); + $alert->warning(); + return Modal::make() + ->lg() + ->title($this->title) + ->body($alert->render() . $form->render()) + ->button($this->html()); + } +} diff --git a/app/Admin/Controllers/ProductSpuController.php b/app/Admin/Controllers/ProductSpuController.php index 655a0060..2e15e579 100644 --- a/app/Admin/Controllers/ProductSpuController.php +++ b/app/Admin/Controllers/ProductSpuController.php @@ -74,6 +74,11 @@ class ProductSpuController extends AdminController } }); + $grid->tools(function (Grid\Tools $tools) { + // Excel导入 + $tools->append(new \App\Admin\Actions\Modal\ProductImport()); + }); + /** 查询 **/ $grid->filter(function (Grid\Filter $filter) { $filter->panel(); diff --git a/app/Admin/Controllers/Store/ProductController.php b/app/Admin/Controllers/Store/ProductController.php index a2dc96d3..0b66ebe2 100644 --- a/app/Admin/Controllers/Store/ProductController.php +++ b/app/Admin/Controllers/Store/ProductController.php @@ -10,6 +10,7 @@ use Dcat\Admin\Models\Administrator; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Dcat\Admin\Traits\HasFormResponse; +use App\Models\ProductSku; class ProductController extends Controller { @@ -29,11 +30,7 @@ class ProductController extends Controller { $store = Store::findOrFail($store_id); $form = Form::make(); - $form->selectTable('product', '商品') - ->from(\App\Admin\Renderable\ProductSkuSimpleTable::make()) - ->model(\App\Models\ProductSku::class, 'id', 'name') - ->required(); - // $form->number('amount', '库存')->min(0); + $form->select('product', '商品')->options(ProductSku::class)->ajax('api/product-skus'); $form->switch('status', '上架')->default(1); return (new Content())->title('新增')->body($form); } diff --git a/app/Admin/Imports/Product.php b/app/Admin/Imports/Product.php new file mode 100644 index 00000000..db20599a --- /dev/null +++ b/app/Admin/Imports/Product.php @@ -0,0 +1,173 @@ +open($file); + + foreach ($reader->getSheetIterator() as $sheet) { + foreach ($sheet->getRowIterator() as $num => $row) { + if ($num === 1) { + continue; + } + $cells = $row->toArray(); + + $goods = [ + 'name' => $cells[0], + 'subtitle' => $cells[1], + 'shipping_template_id' => 1, + ]; + $category = ProductCategory::where('name', $cells[2])->firstOrFail(); + $goods['category_id'] = $category->id; + + if ($path = $cells[3]) { + $images = $this->getImageUrlFromPath($path); + if (count($images) > 0) { + $goods['cover'] = $images[0]; + $goods['images'] = $images; + } + } + + // 运费模板 + if ($cell_11 = data_get($cells, 11)) { + $goods['shipping_template_id'] = $cell_11; + } + // 重量 + if ($cell_12 = data_get($cells, 12)) { + $goods['weight'] = $cell_12; + } + // 详细描述 + if ($cell_13 = data_get($cells, 13)) { + $description = ''; + foreach(explode(',', $cell_13) as $item) { + $description .= '

    '; + } + $goods['description'] = $description; + } + + $goods = array_merge($goods, [ + 'sell_price' => $cells[4] * 100, + 'market_price' => $cells[5] * 100, + 'cost_price' => $cells[6] * 100, + 'vip_price' => $cells[7] * 100, + 'sales_value' => $cells[8] * 100, + ]); + + $product = ProductSpu::updateOrCreate(['name' => $goods['name']],$goods); + + // 属性规格 + $coll_9 = data_get($cells, 9); + $cell_10 = data_get($cells, 10); + if ($coll_9) { + $format_specs = $this->formatAttr($coll_9); + $format_attrs = $this->formatAttr($cell_10); + $spec_group = $this->getSpec($cells[0], $coll_9, $cell_10); + + $attrs = []; + foreach($spec_group->attrs as $index => $item) { + $attr = ['name' => $item['title'], 'attrs' => []]; + foreach(explode(PHP_EOL, $item['value']) as $value) { + array_push($attr['attrs'], ['name' => $value, 'value' => data_get($format_attrs, "$index.values.$value")]); + } + array_push($attrs, $attr); + } + $product->update(['attrs' => $attrs]); + + $specs = []; + $product->specs()->delete(); + foreach($spec_group->specs as $index => $item) { + $spec = ['name' => $item['title'], 'items' => []]; + foreach(explode(PHP_EOL, $item['value']) as $value) { + array_push($spec['items'], ['name' => $value, 'value' => data_get($format_specs, "$index.values.$value")]); + } + array_push($specs, $spec); + } + $product->specs()->createMany($specs); + } + } + } + + $reader->close(); + } + + protected function getImageUrlFromPath($dir) + { + $disk = Storage::disk('public'); + $files = $disk->files('goods/' . $dir); + $images = []; + + foreach($files as $filename) { + array_push($images, $disk->url($filename)); + } + + return $images; + } + + protected function getSpec($name, $specs, $attrs) + { + if (Str::contains($specs, ':')) { + // 创建新的规格 + $spec = ProductGroup::updateOrCreate([ + 'name' => $name + ], [ + 'attrs' => $this->formatGroupAttr($this->formatAttr($attrs)), + 'specs' => $this->formatGroupAttr($this->formatAttr($specs)) + ]); + } else { + $spec = ProductGroup::where('name', $specs)->firstOrFail(); + } + + return $spec; + } + + /** + * 颜色[灰色:0,银色:0] + * 尺寸[13寸:0,14寸:500,16寸:1000] + * 处理器[M1:0,M1 Pro:1000,M1 Max:1500] + * 内存[8G:0,16G:1000,32G:2000] + * 硬盘[256G:0,512G:1000,1TB:2000] + * + * [ + * ['name' => '颜色', 'values' => ['灰色' => 0, '银色' => 0]], + * ['name' => '尺寸', 'values' => ['13寸' => 0, '14寸' => 500, '16寸' => '1000']] + * ... + * ] + */ + protected function formatAttr($attrs) + { + $attr_data = []; + if (!Str::contains($attrs, ':')) { + return $attr_data; + } + foreach(explode("\n", $attrs) as $item) { + $name = explode('[', $item)[0]; + $attr_values = []; + foreach(explode(',', Str::between($item, '[', ']')) as $item1) { + $item1_explode = explode(':', $item1); + $attr_values[$item1_explode[0]] = $item1_explode[1]; + } + array_push($attr_data, ['name' => $name, 'values' => $attr_values]); + } + + return $attr_data; + } + + protected function formatGroupAttr($data) + { + $list = []; + foreach($data as $item) { + array_push($list, ['title' => $item['name'], 'value' => implode(PHP_EOL, array_keys($item['values']))]); + } + return $list; + } +} diff --git a/app/Models/ProductGroup.php b/app/Models/ProductGroup.php index 993263ac..0282c4e5 100644 --- a/app/Models/ProductGroup.php +++ b/app/Models/ProductGroup.php @@ -10,6 +10,8 @@ class ProductGroup extends Model { use HasDateTimeFormatter; + protected $fillable = ['id', 'name', 'attrs', 'specs', 'created_at', 'updated_at']; + protected $casts = [ 'attrs' => JsonArray::class, 'specs' => JsonArray::class, diff --git a/app/Models/ProductSpu.php b/app/Models/ProductSpu.php index 3c40be52..2772ae73 100644 --- a/app/Models/ProductSpu.php +++ b/app/Models/ProductSpu.php @@ -53,6 +53,8 @@ class ProductSpu extends Model 'sales', 'release_at', 'sales_value', + 'cover', + 'description' ]; public function skus() diff --git a/composer.lock b/composer.lock index ef988496..72554070 100644 --- a/composer.lock +++ b/composer.lock @@ -12092,5 +12092,5 @@ "php": "^8.0" }, "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.3.0" } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 4313682f..cd89ae51 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -27,7 +27,8 @@ class DatabaseSeeder extends Seeder ProductPartSeeder::class, ProductCategorySeeder::class, AdAddressSeeder::class, - ShippingSeeder::class + ShippingSeeder::class, + ProductAttrSeeder::class ]); } } diff --git a/database/seeders/ProductAttrSeeder.php b/database/seeders/ProductAttrSeeder.php new file mode 100644 index 00000000..39cbcec1 --- /dev/null +++ b/database/seeders/ProductAttrSeeder.php @@ -0,0 +1,28 @@ + '华为智慧屏', + 'attrs' => [ + ['title' => '属性组', 'value' => implode(PHP_EOL, ['分辨率', '摄像头', '能效等级'])] + ], + 'specs' => [ + ['title' => '尺寸', 'value' => implode(PHP_EOL, ['55英寸', '65英寸', '75英寸'])] + ] + ]); + } +}