商品属性功能后端代码
This commit is contained in:
parent
a91477bf25
commit
1613adc3fa
|
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AttributeController.php
|
||||||
|
*
|
||||||
|
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||||
|
* @link https://beikeshop.com
|
||||||
|
* @author TL <mengwb@guangda.work>
|
||||||
|
* @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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AttributeGroupController.php
|
||||||
|
*
|
||||||
|
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||||
|
* @link https://beikeshop.com
|
||||||
|
* @author TL <mengwb@guangda.work>
|
||||||
|
* @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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
namespace Beike\Admin\Http\Controllers;
|
namespace Beike\Admin\Http\Controllers;
|
||||||
|
|
||||||
|
use Beike\Admin\Http\Resources\ProductAttributeResource;
|
||||||
use Beike\Models\Product;
|
use Beike\Models\Product;
|
||||||
|
use Beike\Models\ProductAttribute;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Beike\Repositories\ProductRepo;
|
use Beike\Repositories\ProductRepo;
|
||||||
use Beike\Repositories\CategoryRepo;
|
use Beike\Repositories\CategoryRepo;
|
||||||
|
|
@ -107,13 +109,14 @@ class ProductController extends Controller
|
||||||
if ($product->id) {
|
if ($product->id) {
|
||||||
$descriptions = $product->descriptions->keyBy('locale');
|
$descriptions = $product->descriptions->keyBy('locale');
|
||||||
$categoryIds = $product->categories->pluck('id')->toArray();
|
$categoryIds = $product->categories->pluck('id')->toArray();
|
||||||
$product->load('brand');
|
$product->load('brand', 'attributes');
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'product' => $product,
|
'product' => $product,
|
||||||
'descriptions' => $descriptions ?? [],
|
'descriptions' => $descriptions ?? [],
|
||||||
'category_ids' => $categoryIds ?? [],
|
'category_ids' => $categoryIds ?? [],
|
||||||
|
'product_attributes' => ProductAttributeResource::collection($product->attributes),
|
||||||
'languages' => LanguageRepo::all(),
|
'languages' => LanguageRepo::all(),
|
||||||
'tax_classes' => TaxClassRepo::getList(),
|
'tax_classes' => TaxClassRepo::getList(),
|
||||||
'source' => [
|
'source' => [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class AttributeDetailResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'id' => $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class AttributeResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'id' => $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class AttributeValueResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'id' => $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class AutocompleteResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
|
*/
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->description->name ?? '',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class ProductAttributeResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return array
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AttributeGroupRepo.php
|
||||||
|
*
|
||||||
|
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||||
|
* @link https://beikeshop.com
|
||||||
|
* @author TL <mengwb@guangda.work>
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AttributeRepo.php
|
||||||
|
*
|
||||||
|
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||||
|
* @link https://beikeshop.com
|
||||||
|
* @author TL <mengwb@guangda.work>
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,24 @@ Route::prefix($adminName)
|
||||||
->group(function () {
|
->group(function () {
|
||||||
Route::get('/', [Controllers\HomeController::class, 'index'])->name('home.index');
|
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/names', [Controllers\BrandController::class, 'getNames'])->name('brands.names');
|
||||||
Route::middleware('can:brands_index')->get('brands/autocomplete', [Controllers\BrandController::class, 'autocomplete'])->name('brands.autocomplete');
|
Route::middleware('can:brands_index')->get('brands/autocomplete', [Controllers\BrandController::class, 'autocomplete'])->name('brands.autocomplete');
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ class ProductService
|
||||||
if ($isUpdating) {
|
if ($isUpdating) {
|
||||||
$product->skus()->delete();
|
$product->skus()->delete();
|
||||||
$product->descriptions()->delete();
|
$product->descriptions()->delete();
|
||||||
|
$product->attributes()->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
$descriptions = [];
|
$descriptions = [];
|
||||||
|
|
@ -45,6 +46,8 @@ class ProductService
|
||||||
}
|
}
|
||||||
$product->descriptions()->createMany($descriptions);
|
$product->descriptions()->createMany($descriptions);
|
||||||
|
|
||||||
|
$product->attributes()->createMany($data['attributes'] ?? []);
|
||||||
|
|
||||||
$skus = [];
|
$skus = [];
|
||||||
foreach ($data['skus'] as $index => $sku) {
|
foreach ($data['skus'] as $index => $sku) {
|
||||||
$sku['position'] = $index;
|
$sku['position'] = $index;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class Sidebar extends Component
|
||||||
foreach ($routes as $route) {
|
foreach ($routes as $route) {
|
||||||
$this->addLink($route['route'], $route['icon'] ?? '', $this->equalRoute($route['route']), (bool)($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
|
$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();
|
$routes = $this->getProductSubRoutes();
|
||||||
foreach ($routes as $route) {
|
foreach ($routes as $route) {
|
||||||
$this->addLink($route['route'], $route['icon'] ?? '', $this->equalRoute($route['route']), (bool)($route['blank'] ?? false), $route['hide_mobile'] ?? 0);
|
$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' => 'categories.index', 'icon' => 'fa fa-tachometer-alt'],
|
||||||
['route' => 'products.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' => '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'],
|
['route' => 'products.trashed', 'icon' => 'fa fa-tachometer-alt'],
|
||||||
];
|
];
|
||||||
return hook_filter('sidebar.product_routes', $routes);
|
return hook_filter('sidebar.product_routes', $routes);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Attribute.php
|
||||||
|
*
|
||||||
|
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||||
|
* @link https://beikeshop.com
|
||||||
|
* @author TL <mengwb@guangda.work>
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AttributeDescription.php
|
||||||
|
*
|
||||||
|
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||||
|
* @link https://beikeshop.com
|
||||||
|
* @author TL <mengwb@guangda.work>
|
||||||
|
* @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'];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AttributeGroup.php
|
||||||
|
*
|
||||||
|
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||||
|
* @link https://beikeshop.com
|
||||||
|
* @author TL <mengwb@guangda.work>
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AttributeGroupDescription.php
|
||||||
|
*
|
||||||
|
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||||
|
* @link https://beikeshop.com
|
||||||
|
* @author TL <mengwb@guangda.work>
|
||||||
|
* @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'];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AttributeValue.php
|
||||||
|
*
|
||||||
|
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||||
|
* @link https://beikeshop.com
|
||||||
|
* @author TL <mengwb@guangda.work>
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AttributeValueDescription.php
|
||||||
|
*
|
||||||
|
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||||
|
* @link https://beikeshop.com
|
||||||
|
* @author TL <mengwb@guangda.work>
|
||||||
|
* @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'];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Beike\Models;
|
namespace Beike\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
|
|
@ -44,6 +45,11 @@ class Product extends Base
|
||||||
return $this->hasMany(ProductSku::class);
|
return $this->hasMany(ProductSku::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function attributes() : HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(ProductAttribute::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function master_sku()
|
public function master_sku()
|
||||||
{
|
{
|
||||||
return $this->hasOne(ProductSku::Class)->where('is_default', 1);
|
return $this->hasOne(ProductSku::Class)->where('is_default', 1);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AttributeValueDescription.php
|
||||||
|
*
|
||||||
|
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||||
|
* @link https://beikeshop.com
|
||||||
|
* @author TL <mengwb@guangda.work>
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -34,6 +34,12 @@ class ProductDetail extends JsonResource
|
||||||
'thumb' => image_resize($image, 150, 150)
|
'thumb' => image_resize($image, 150, 150)
|
||||||
];
|
];
|
||||||
}, $this->images ?? []),
|
}, $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,
|
'category_id' => $this->category_id ?? null,
|
||||||
'variables' => $this->decodeVariables($this->variables),
|
'variables' => $this->decodeVariables($this->variables),
|
||||||
'skus' => SkuDetail::collection($this->skus)->jsonSerialize(),
|
'skus' => SkuDetail::collection($this->skus)->jsonSerialize(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('attributes', function (Blueprint $table) {
|
||||||
|
$table->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');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -48,6 +48,8 @@ return [
|
||||||
'access_frontend' => 'Frontend',
|
'access_frontend' => 'Frontend',
|
||||||
|
|
||||||
// sidebar
|
// sidebar
|
||||||
|
'attribute_groups_index' => 'Attribute Group',
|
||||||
|
'attributes_index' => 'Attributes',
|
||||||
'settings_index' => 'Setting',
|
'settings_index' => 'Setting',
|
||||||
'admin_users_index' => 'Admin Users',
|
'admin_users_index' => 'Admin Users',
|
||||||
'plugins_index' => 'Plugins',
|
'plugins_index' => 'Plugins',
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ return [
|
||||||
'access_frontend' => '访问前台',
|
'access_frontend' => '访问前台',
|
||||||
|
|
||||||
// sidebar
|
// sidebar
|
||||||
|
'attribute_groups_index' => '属性组',
|
||||||
|
'attributes_index' => '属性',
|
||||||
'settings_index' => '系统设置',
|
'settings_index' => '系统设置',
|
||||||
'admin_users_index' => '后台用户',
|
'admin_users_index' => '后台用户',
|
||||||
'plugins_index' => '插件列表',
|
'plugins_index' => '插件列表',
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ return [
|
||||||
'access_frontend' => '訪問前台',
|
'access_frontend' => '訪問前台',
|
||||||
|
|
||||||
// sidebar
|
// sidebar
|
||||||
|
'attribute_groups_index' => '屬性組',
|
||||||
|
'attributes_index' => '屬性',
|
||||||
'settings_index' => '系統設置',
|
'settings_index' => '系統設置',
|
||||||
'admin_users_index' => '後台用戶',
|
'admin_users_index' => '後台用戶',
|
||||||
'plugins_index' => '插件列表',
|
'plugins_index' => '插件列表',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue