diff --git a/beike/Admin/Http/Controllers/AttributeController.php b/beike/Admin/Http/Controllers/AttributeController.php new file mode 100644 index 00000000..a884b162 --- /dev/null +++ b/beike/Admin/Http/Controllers/AttributeController.php @@ -0,0 +1,104 @@ + + * @created 2023-01-04 19:45:41 + * @modified 2023-01-04 19:45:41 + */ + +namespace Beike\Admin\Http\Controllers; + +use Beike\Admin\Http\Resources\AttributeDetailResource; +use Beike\Admin\Http\Resources\AttributeResource; +use Beike\Admin\Http\Resources\AttributeValueResource; +use Beike\Admin\Http\Resources\AutocompleteResource; +use Beike\Admin\Repositories\AttributeGroupRepo; +use Beike\Admin\Repositories\AttributeRepo; +use Illuminate\Http\Request; + +class AttributeController extends Controller +{ + public function index(Request $request) + { + $attributes = AttributeRepo::getList(); + $data = [ + 'attribute_list' => $attributes, + 'attribute_list_format' => AttributeResource::collection($attributes), + 'attribute_group' => AttributeGroupRepo::getList(), + ]; + + if ($request->expectsJson()) { + return json_success(trans('success'), $data); + } + + return view('admin::pages.attributes.index', $data); + } + + public function show(Request $request, int $id) + { + $data = [ + 'attribute' => (new AttributeDetailResource(AttributeRepo::find($id)))->jsonSerialize(), + 'attribute_group' => AttributeGroupRepo::getList(), + ]; + + return view('admin::pages.attributes.form', $data); + } + + public function store(Request $request) + { + $requestData = json_decode($request->getContent(), true); + $item = AttributeRepo::create($requestData); + return json_success(trans('common.created_success'), $item); + } + + public function update(Request $request, int $id) + { + $requestData = json_decode($request->getContent(), true); + $item = AttributeRepo::update($id, $requestData); + return json_success(trans('common.updated_success'), $item); + } + + + public function storeValue(Request $request, int $id) + { + $requestData = json_decode($request->getContent(), true); + $item = AttributeRepo::createValue(array_merge($requestData, ['attribute_id' => $id])); + return json_success(trans('common.created_success'), new AttributeValueResource($item)); + } + + public function updateValue(Request $request, int $id, int $value_id) + { + $requestData = json_decode($request->getContent(), true); + $item = AttributeRepo::updateValue($value_id, $requestData); + return json_success(trans('common.updated_success'), new AttributeValueResource($item)); + } + + public function destroyValue(Request $request, int $id, int $value_id) + { + AttributeRepo::deleteValue($value_id); + return json_success(trans('common.deleted_success')); + } + + public function destroy(Request $request, int $id) + { + AttributeRepo::delete($id); + return json_success(trans('common.deleted_success')); + } + + public function autocomplete(Request $request): array + { + $items = AttributeRepo::autocomplete($request->get('name') ?? '', 0); + + return json_success(trans('common.get_success'), AutocompleteResource::collection($items)); + } + + public function autocompleteValue(Request $request, int $id): array + { + $items = AttributeRepo::autocompleteValue($id, $request->get('name') ?? ''); + + return json_success(trans('common.get_success'), AutocompleteResource::collection($items)); + } +} diff --git a/beike/Admin/Http/Controllers/AttributeGroupController.php b/beike/Admin/Http/Controllers/AttributeGroupController.php new file mode 100644 index 00000000..c3b24905 --- /dev/null +++ b/beike/Admin/Http/Controllers/AttributeGroupController.php @@ -0,0 +1,47 @@ + + * @created 2023-01-04 19:45:41 + * @modified 2023-01-04 19:45:41 + */ + +namespace Beike\Admin\Http\Controllers; + +use Beike\Admin\Repositories\AttributeGroupRepo; +use Illuminate\Http\Request; + +class AttributeGroupController extends Controller +{ + public function index() + { + $data = [ + 'attribute_groups' => AttributeGroupRepo::getList(), + ]; + + return view('admin::pages.attribute_group.index', $data); + } + + public function store(Request $request) + { + $requestData = json_decode($request->getContent(), true); + $item = AttributeGroupRepo::create($requestData); + return json_success(trans('common.created_success'), $item); + } + + public function update(Request $request, int $id) + { + $requestData = json_decode($request->getContent(), true); + $item = AttributeGroupRepo::update($id, $requestData); + return json_success(trans('common.updated_success'), $item); + } + + public function destroy(Request $request, int $id) + { + AttributeGroupRepo::delete($id); + return json_success(trans('common.deleted_success')); + } +} diff --git a/beike/Admin/Http/Controllers/ProductController.php b/beike/Admin/Http/Controllers/ProductController.php index 2f8a34b4..444a2e23 100644 --- a/beike/Admin/Http/Controllers/ProductController.php +++ b/beike/Admin/Http/Controllers/ProductController.php @@ -2,7 +2,9 @@ namespace Beike\Admin\Http\Controllers; +use Beike\Admin\Http\Resources\ProductAttributeResource; use Beike\Models\Product; +use Beike\Models\ProductAttribute; use Illuminate\Http\Request; use Beike\Repositories\ProductRepo; use Beike\Repositories\CategoryRepo; @@ -107,13 +109,14 @@ class ProductController extends Controller if ($product->id) { $descriptions = $product->descriptions->keyBy('locale'); $categoryIds = $product->categories->pluck('id')->toArray(); - $product->load('brand'); + $product->load('brand', 'attributes'); } $data = [ 'product' => $product, 'descriptions' => $descriptions ?? [], 'category_ids' => $categoryIds ?? [], + 'product_attributes' => ProductAttributeResource::collection($product->attributes), 'languages' => LanguageRepo::all(), 'tax_classes' => TaxClassRepo::getList(), 'source' => [ diff --git a/beike/Admin/Http/Resources/AttributeDetailResource.php b/beike/Admin/Http/Resources/AttributeDetailResource.php new file mode 100644 index 00000000..ffbdec08 --- /dev/null +++ b/beike/Admin/Http/Resources/AttributeDetailResource.php @@ -0,0 +1,30 @@ + $this->id, + 'attribute_group_id' => $this->attribute_group_id, + 'name' => $this->description->name ?? '', + 'sort_order' => $this->sort_order, + 'attribute_group_name' => $this->attributeGroup->description->name ?? '', + 'created_at' => time_format($this->created_at), + 'values' => AttributeValueResource::collection($this->values), + 'descriptions' => $this->descriptions + ]; + + return $data; + } +} diff --git a/beike/Admin/Http/Resources/AttributeResource.php b/beike/Admin/Http/Resources/AttributeResource.php new file mode 100644 index 00000000..f6d98ff2 --- /dev/null +++ b/beike/Admin/Http/Resources/AttributeResource.php @@ -0,0 +1,27 @@ + $this->id, + 'name' => $this->description->name ?? '', + 'sort_order' => $this->sort_order, + 'attribute_group_name' => $this->attributeGroup->description->name ?? '', + 'created_at' => time_format($this->created_at), + ]; + + return $data; + } +} diff --git a/beike/Admin/Http/Resources/AttributeValueResource.php b/beike/Admin/Http/Resources/AttributeValueResource.php new file mode 100644 index 00000000..2aa0225f --- /dev/null +++ b/beike/Admin/Http/Resources/AttributeValueResource.php @@ -0,0 +1,28 @@ + $this->id, + 'attribute_id' => $this->attribute_id, + 'name' => $this->description->name, + 'description' => $this->description, + 'descriptions' => $this->descriptions, + 'created_at' => time_format($this->created_at), + ]; + + return $data; + } +} diff --git a/beike/Admin/Http/Resources/AutocompleteResource.php b/beike/Admin/Http/Resources/AutocompleteResource.php new file mode 100644 index 00000000..81e5cc4c --- /dev/null +++ b/beike/Admin/Http/Resources/AutocompleteResource.php @@ -0,0 +1,24 @@ + $this->id, + 'name' => $this->description->name ?? '', + ]; + + return $data; + } +} diff --git a/beike/Admin/Http/Resources/ProductAttributeResource.php b/beike/Admin/Http/Resources/ProductAttributeResource.php new file mode 100644 index 00000000..f08575d6 --- /dev/null +++ b/beike/Admin/Http/Resources/ProductAttributeResource.php @@ -0,0 +1,33 @@ +load('attribute', 'attributeValue'); + $data = [ + 'attribute' => [ + 'id' => $this->attribute_id, + 'name' => $this->attribute->description->name, + ], + 'attribute_value' => [ + 'id' => $this->attribute_value_id, + 'name' => $this->attributeValue->description->name, + ], + ]; + + return $data; + } +} diff --git a/beike/Admin/Repositories/AttributeGroupRepo.php b/beike/Admin/Repositories/AttributeGroupRepo.php new file mode 100644 index 00000000..7f89fbfc --- /dev/null +++ b/beike/Admin/Repositories/AttributeGroupRepo.php @@ -0,0 +1,72 @@ + + * @created 2023-01-04 19:45:41 + * @modified 2023-01-04 19:45:41 + */ + +namespace Beike\Admin\Repositories; + +use Beike\Models\AttributeGroup; + +class AttributeGroupRepo +{ + public static function getList() + { + return AttributeGroup::query()->orderByDesc('id')->with('description', 'descriptions')->get(); + } + + public static function create($data) + { + $attributeGroup = AttributeGroup::query()->create([ + 'sort_order' => $data['sort_order'], + ]); + + $descriptions = []; + foreach ($data['name'] as $locale => $name) { + $descriptions[] = [ + 'locale' => $locale, + 'name' => $name, + ]; + } + $attributeGroup->descriptions()->createMany($descriptions); + + $attributeGroup->load('description', 'descriptions'); + + return $attributeGroup; + } + + public static function update($id, $data) + { + $attributeGroup = AttributeGroup::query()->updateOrCreate(['id' => $id], [ + 'sort_order' => $data['sort_order'], + ]); + + $descriptions = []; + foreach ($data['name'] as $locale => $name) { + $descriptions[] = [ + 'locale' => $locale, + 'name' => $name, + ]; + } + $attributeGroup->descriptions()->delete(); + $attributeGroup->descriptions()->createMany($descriptions); + + return $attributeGroup; + } + + public static function find($id) + { + return AttributeGroup::query()->find($id); + } + + public static function delete($id) + { + $group = AttributeGroup::query()->findOrFail($id); + $group->delete(); + } +} diff --git a/beike/Admin/Repositories/AttributeRepo.php b/beike/Admin/Repositories/AttributeRepo.php new file mode 100644 index 00000000..d4769a06 --- /dev/null +++ b/beike/Admin/Repositories/AttributeRepo.php @@ -0,0 +1,138 @@ + + * @created 2023-01-04 19:45:41 + * @modified 2023-01-04 19:45:41 + */ + +namespace Beike\Admin\Repositories; + +use Beike\Admin\Http\Resources\TaxClassDetail; +use Beike\Models\Attribute; +use Beike\Models\AttributeValue; +use Beike\Models\TaxClass; + +class AttributeRepo +{ + public static function getList() + { + return Attribute::query()->orderByDesc('id')->paginate()->withQueryString(); + } + + public static function create($data) + { + $attribute = Attribute::query()->create([ + 'attribute_group_id' => $data['attribute_group_id'], + 'sort_order' => $data['sort_order'], + ]); + + $descriptions = []; + foreach ($data['name'] as $locale => $name) { + $descriptions[] = [ + 'locale' => $locale, + 'name' => $name, + ]; + } + $attribute->descriptions()->createMany($descriptions); + + return $attribute; + } + + public static function update($id, $data) + { + $attribute = Attribute::query()->updateOrCreate(['id' => $id], [ + 'attribute_group_id' => $data['attribute_group_id'], + 'sort_order' => $data['sort_order'], + ]); + + $descriptions = []; + foreach ($data['name'] as $locale => $name) { + $descriptions[] = [ + 'locale' => $locale, + 'name' => $name, + ]; + } + $attribute->descriptions()->delete(); + $attribute->descriptions()->createMany($descriptions); + + return $attribute; + } + + public static function createValue($data) + { + $attributeValue = AttributeValue::query()->create([ + 'attribute_id' => $data['attribute_id'], + ]); + + $descriptions = []; + foreach ($data['name'] as $locale => $name) { + $descriptions[] = [ + 'locale' => $locale, + 'name' => $name, + ]; + } + $attributeValue->descriptions()->createMany($descriptions); + + return $attributeValue; + } + + public static function updateValue($id, $data) + { + $attributeValue = AttributeValue::query()->findOrFail($id); + + $descriptions = []; + foreach ($data['name'] as $locale => $name) { + $descriptions[] = [ + 'locale' => $locale, + 'name' => $name, + ]; + } + $attributeValue->descriptions()->delete(); + $attributeValue->descriptions()->createMany($descriptions); + + return $attributeValue; + } + + public function deleteValue($id) + { + AttributeValue::query()->findOrFail($id)->delete(); + } + + public static function find($id) + { + return Attribute::query()->with('values.descriptions')->find($id); + } + + public static function delete($id) + { + $attribute = Attribute::query()->findOrFail($id); + $attribute->values()->delete(); + $attribute->delete(); + } + + + public static function autocomplete($name) + { + $builder = Attribute::query()->with('description') + ->whereHas('description', function ($query) use ($name) { + $query->where('name', 'like', "{$name}%"); + }); + + return $builder->limit(10)->get(); + } + + public static function autocompleteValue($attributeId, $name) + { + $builder = AttributeValue::query()->with('description') + ->where('attribute_id', $attributeId) + ->whereHas('description', function ($query) use ($name) { + $query->where('name', 'like', "{$name}%"); + }); + + return $builder->limit(10)->get(); + } +} diff --git a/beike/Admin/Routes/admin.php b/beike/Admin/Routes/admin.php index 5ac6ce03..262d2056 100644 --- a/beike/Admin/Routes/admin.php +++ b/beike/Admin/Routes/admin.php @@ -20,6 +20,24 @@ Route::prefix($adminName) ->group(function () { Route::get('/', [Controllers\HomeController::class, 'index'])->name('home.index'); + // 属性 + Route::middleware('can:attributes_update')->post('attributes/{id}/values', [Controllers\AttributeController::class, 'storeValue'])->name('attributes.values.store'); + Route::middleware('can:attributes_show')->get('attributes/{id}/values/autocomplete', [Controllers\AttributeController::class, 'autocompleteValue'])->name('attributes.values.autocomplete'); + Route::middleware('can:attributes_update')->put('attributes/{id}/values/{value_id}', [Controllers\AttributeController::class, 'updateValue'])->name('attributes.values.update'); + Route::middleware('can:attributes_update')->delete('attributes/{id}/values/{value_id}', [Controllers\AttributeController::class, 'destroyValue'])->name('attributes.values.destroy'); + Route::middleware('can:attributes_index')->get('attributes', [Controllers\AttributeController::class, 'index'])->name('attributes.index'); + Route::middleware('can:attributes_show')->get('attributes/autocomplete', [Controllers\AttributeController::class, 'autocomplete'])->name('attributes.autocomplete'); + Route::middleware('can:attributes_show')->get('attributes/{id}', [Controllers\AttributeController::class, 'show'])->name('attributes.show'); + Route::middleware('can:attributes_create')->post('attributes', [Controllers\AttributeController::class, 'store'])->name('attributes.store'); + Route::middleware('can:attributes_update')->put('attributes/{id}', [Controllers\AttributeController::class, 'update'])->name('attributes.update'); + Route::middleware('can:attributes_delete')->delete('attributes/{id}', [Controllers\AttributeController::class, 'destroy'])->name('attributes.destroy'); + + // 属性组 + Route::middleware('can:attribute_groups_index')->get('attribute_groups', [Controllers\AttributeGroupController::class, 'index'])->name('attribute_groups.index'); + Route::middleware('can:attribute_groups_create')->post('attribute_groups', [Controllers\AttributeGroupController::class, 'store'])->name('attribute_groups.store'); + Route::middleware('can:attribute_groups_update')->put('attribute_groups/{id}', [Controllers\AttributeGroupController::class, 'update'])->name('attribute_groups.update'); + Route::middleware('can:attribute_groups_delete')->delete('attribute_groups/{id}', [Controllers\AttributeGroupController::class, 'destroy'])->name('attribute_groups.destroy'); + // 商品品牌 Route::middleware('can:brands_index')->get('brands/names', [Controllers\BrandController::class, 'getNames'])->name('brands.names'); Route::middleware('can:brands_index')->get('brands/autocomplete', [Controllers\BrandController::class, 'autocomplete'])->name('brands.autocomplete'); diff --git a/beike/Admin/Services/ProductService.php b/beike/Admin/Services/ProductService.php index 036c2d2b..d065754e 100644 --- a/beike/Admin/Services/ProductService.php +++ b/beike/Admin/Services/ProductService.php @@ -34,6 +34,7 @@ class ProductService if ($isUpdating) { $product->skus()->delete(); $product->descriptions()->delete(); + $product->attributes()->delete(); } $descriptions = []; @@ -45,6 +46,8 @@ class ProductService } $product->descriptions()->createMany($descriptions); + $product->attributes()->createMany($data['attributes'] ?? []); + $skus = []; foreach ($data['skus'] as $index => $sku) { $sku['position'] = $index; diff --git a/beike/Admin/View/Components/Sidebar.php b/beike/Admin/View/Components/Sidebar.php index 692f606a..8988ea74 100644 --- a/beike/Admin/View/Components/Sidebar.php +++ b/beike/Admin/View/Components/Sidebar.php @@ -41,7 +41,7 @@ class Sidebar extends Component foreach ($routes as $route) { $this->addLink($route['route'], $route['icon'] ?? '', $this->equalRoute($route['route']), (bool)($route['blank'] ?? false), $route['hide_mobile'] ?? 0); } - } elseif (Str::startsWith($routeName, ['products.', 'categories.', 'brands.'])) { + } elseif (Str::startsWith($routeName, ['products.', 'categories.', 'brands.', 'attribute_groups.', 'attributes.'])) { $routes = $this->getProductSubRoutes(); foreach ($routes as $route) { $this->addLink($route['route'], $route['icon'] ?? '', $this->equalRoute($route['route']), (bool)($route['blank'] ?? false), $route['hide_mobile'] ?? 0); @@ -126,6 +126,8 @@ class Sidebar extends Component ['route' => 'categories.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'products.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'brands.index', 'icon' => 'fa fa-tachometer-alt','hide_mobile' => 1], + ['route' => 'attribute_groups.index', 'icon' => 'fa fa-tachometer-alt'], + ['route' => 'attributes.index', 'icon' => 'fa fa-tachometer-alt'], ['route' => 'products.trashed', 'icon' => 'fa fa-tachometer-alt'], ]; return hook_filter('sidebar.product_routes', $routes); diff --git a/beike/Models/Attribute.php b/beike/Models/Attribute.php new file mode 100644 index 00000000..53ad748d --- /dev/null +++ b/beike/Models/Attribute.php @@ -0,0 +1,44 @@ + + * @created 2023-01-03 20:22:18 + * @modified 2023-01-03 20:22:18 + */ + +namespace Beike\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; + +class Attribute extends Base +{ + use HasFactory; + + protected $fillable = ['attribute_group_id', 'sort_order']; + + public function attributeGroup() : BelongsTo + { + return $this->belongsTo(AttributeGroup::Class); + } + + public function values() :HasMany + { + return $this->hasMany(AttributeValue::Class); + } + + public function description() + { + return $this->hasOne(AttributeDescription::class)->where('locale', locale()); + } + + public function descriptions() :HasMany + { + return $this->hasMany(AttributeDescription::Class); + } +} + diff --git a/beike/Models/AttributeDescription.php b/beike/Models/AttributeDescription.php new file mode 100644 index 00000000..60916846 --- /dev/null +++ b/beike/Models/AttributeDescription.php @@ -0,0 +1,25 @@ + + * @created 2023-01-03 20:22:18 + * @modified 2023-01-03 20:22:18 + */ + +namespace Beike\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; + +class AttributeDescription extends Base +{ + use HasFactory; + + protected $fillable = ['attribute_id', 'locale', 'name']; + +} + diff --git a/beike/Models/AttributeGroup.php b/beike/Models/AttributeGroup.php new file mode 100644 index 00000000..b10bfb6a --- /dev/null +++ b/beike/Models/AttributeGroup.php @@ -0,0 +1,38 @@ + + * @created 2023-01-03 20:22:18 + * @modified 2023-01-03 20:22:18 + */ + +namespace Beike\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasMany; + +class AttributeGroup extends Base +{ + use HasFactory; + + protected $fillable = ['sort_order']; + + public function description() + { + return $this->hasOne(AttributeGroupDescription::class)->where('locale', locale()); + } + + public function descriptions() :HasMany + { + return $this->hasMany(AttributeGroupDescription::Class); + } + + public function attributes() :HasMany + { + return $this->hasMany(Attribute::Class); + } +} + diff --git a/beike/Models/AttributeGroupDescription.php b/beike/Models/AttributeGroupDescription.php new file mode 100644 index 00000000..2dc25790 --- /dev/null +++ b/beike/Models/AttributeGroupDescription.php @@ -0,0 +1,24 @@ + + * @created 2023-01-03 20:22:18 + * @modified 2023-01-03 20:22:18 + */ + +namespace Beike\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasMany; + +class AttributeGroupDescription extends Base +{ + use HasFactory; + + protected $fillable = ['attribute_group_id', 'locale', 'name']; + +} + diff --git a/beike/Models/AttributeValue.php b/beike/Models/AttributeValue.php new file mode 100644 index 00000000..3b55dff8 --- /dev/null +++ b/beike/Models/AttributeValue.php @@ -0,0 +1,39 @@ + + * @created 2023-01-03 20:22:18 + * @modified 2023-01-03 20:22:18 + */ + +namespace Beike\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; + +class AttributeValue extends Base +{ + use HasFactory; + + protected $fillable = ['attribute_id']; + + public function description() + { + return $this->hasOne(AttributeValueDescription::class)->where('locale', locale()); + } + + public function descriptions() :HasMany + { + return $this->hasMany(AttributeValueDescription::Class); + } + + public function attribute() :BelongsTo + { + return $this->belongsTo(Attribute::Class); + } +} + diff --git a/beike/Models/AttributeValueDescription.php b/beike/Models/AttributeValueDescription.php new file mode 100644 index 00000000..6b870816 --- /dev/null +++ b/beike/Models/AttributeValueDescription.php @@ -0,0 +1,25 @@ + + * @created 2023-01-03 20:22:18 + * @modified 2023-01-03 20:22:18 + */ + +namespace Beike\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasMany; + +class AttributeValueDescription extends Base +{ + use HasFactory; + + protected $fillable = ['attribute_value_id', 'locale', 'name']; + + +} + diff --git a/beike/Models/Product.php b/beike/Models/Product.php index 146abd34..e718ffa4 100644 --- a/beike/Models/Product.php +++ b/beike/Models/Product.php @@ -2,6 +2,7 @@ namespace Beike\Models; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -44,6 +45,11 @@ class Product extends Base return $this->hasMany(ProductSku::class); } + public function attributes() : HasMany + { + return $this->hasMany(ProductAttribute::class); + } + public function master_sku() { return $this->hasOne(ProductSku::Class)->where('is_default', 1); diff --git a/beike/Models/ProductAttribute.php b/beike/Models/ProductAttribute.php new file mode 100644 index 00000000..1d2b045d --- /dev/null +++ b/beike/Models/ProductAttribute.php @@ -0,0 +1,34 @@ + + * @created 2023-01-03 20:22:18 + * @modified 2023-01-03 20:22:18 + */ + +namespace Beike\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; + +class ProductAttribute extends Base +{ + use HasFactory; + + protected $fillable = ['product_id', 'attribute_id', 'attribute_value_id']; + + public function attribute() : BelongsTo + { + return $this->belongsTo(Attribute::Class); + } + + public function attributeValue() : BelongsTo + { + return $this->belongsTo(AttributeValue::Class); + } +} + diff --git a/beike/Shop/Http/Resources/ProductDetail.php b/beike/Shop/Http/Resources/ProductDetail.php index 5f0d9260..a6828f8e 100644 --- a/beike/Shop/Http/Resources/ProductDetail.php +++ b/beike/Shop/Http/Resources/ProductDetail.php @@ -34,6 +34,12 @@ class ProductDetail extends JsonResource 'thumb' => image_resize($image, 150, 150) ]; }, $this->images ?? []), + 'attributes' => $this->attributes->map(function ($attribute) { + return [ + 'attribute' => $attribute->attribute->description->name, + 'attribute_value' => $attribute->attributeValue->description->name, + ]; + })->toArray(), 'category_id' => $this->category_id ?? null, 'variables' => $this->decodeVariables($this->variables), 'skus' => SkuDetail::collection($this->skus)->jsonSerialize(), diff --git a/database/migrations/2023_01_03_111459_product_attribute.php b/database/migrations/2023_01_03_111459_product_attribute.php new file mode 100644 index 00000000..8412c9f4 --- /dev/null +++ b/database/migrations/2023_01_03_111459_product_attribute.php @@ -0,0 +1,81 @@ +id(); + $table->unsignedInteger('attribute_group_id')->comment('属性组 ID')->index('attribute_group_id'); + $table->integer('sort_order')->comment('排序'); + $table->timestamps(); + }); + Schema::create('attribute_descriptions', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('attribute_id')->comment('属性 ID')->index('attribute_id'); + $table->string('locale')->default('')->comment('语言'); + $table->string('name')->default('')->comment('名称'); + $table->index(['attribute_id', 'locale'], 'attribute_id_locale'); + $table->timestamps(); + }); + Schema::create('attribute_values', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('attribute_id')->comment('属性 ID')->index('attribute_id'); + $table->timestamps(); + }); + Schema::create('attribute_value_descriptions', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('attribute_value_id')->comment('属性值 ID')->index('attribute_value_id'); + $table->string('locale')->default('')->comment('语言'); + $table->string('name')->default('')->comment('名称'); + $table->index(['attribute_value_id', 'locale'], 'attribute_value_id_locale'); + $table->timestamps(); + }); + Schema::create('attribute_groups', function (Blueprint $table) { + $table->id(); + $table->integer('sort_order')->comment('排序'); + $table->timestamps(); + }); + Schema::create('attribute_group_descriptions', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('attribute_group_id')->comment('属性组 ID')->index('attribute_group_id'); + $table->string('locale')->default('')->comment('语言'); + $table->string('name')->default('')->comment('名称'); + $table->index(['attribute_group_id', 'locale'], 'attribute_group_id_locale'); + $table->timestamps(); + }); + Schema::create('product_attributes', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('product_id')->comment('商品 ID')->index('product_id'); + $table->unsignedInteger('attribute_id')->comment('属性 ID')->index('attribute_id'); + $table->unsignedInteger('attribute_value_id')->comment('属性值 ID')->index('attribute_value_id'); + $table->index(['product_id', 'attribute_id'], 'product_id_attribute_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('attributes'); + Schema::dropIfExists('attribute_descriptions'); + Schema::dropIfExists('attribute_values'); + Schema::dropIfExists('attribute_value_descriptions'); + Schema::dropIfExists('attribute_groups'); + Schema::dropIfExists('attribute_group_descriptions'); + Schema::dropIfExists('product_attributes'); + } +}; diff --git a/resources/lang/en/admin/common.php b/resources/lang/en/admin/common.php index 9a1a3df0..fd5d0ea6 100644 --- a/resources/lang/en/admin/common.php +++ b/resources/lang/en/admin/common.php @@ -48,6 +48,8 @@ return [ 'access_frontend' => 'Frontend', // sidebar + 'attribute_groups_index' => 'Attribute Group', + 'attributes_index' => 'Attributes', 'settings_index' => 'Setting', 'admin_users_index' => 'Admin Users', 'plugins_index' => 'Plugins', diff --git a/resources/lang/zh_cn/admin/common.php b/resources/lang/zh_cn/admin/common.php index a0245abf..7d8eefda 100644 --- a/resources/lang/zh_cn/admin/common.php +++ b/resources/lang/zh_cn/admin/common.php @@ -48,6 +48,8 @@ return [ 'access_frontend' => '访问前台', // sidebar + 'attribute_groups_index' => '属性组', + 'attributes_index' => '属性', 'settings_index' => '系统设置', 'admin_users_index' => '后台用户', 'plugins_index' => '插件列表', diff --git a/resources/lang/zh_hk/admin/common.php b/resources/lang/zh_hk/admin/common.php index 7c917d4c..9bcfe62f 100644 --- a/resources/lang/zh_hk/admin/common.php +++ b/resources/lang/zh_hk/admin/common.php @@ -47,6 +47,8 @@ return [ 'access_frontend' => '訪問前台', // sidebar + 'attribute_groups_index' => '屬性組', + 'attributes_index' => '屬性', 'settings_index' => '系統設置', 'admin_users_index' => '後台用戶', 'plugins_index' => '插件列表',