init
commit
eaaafbb10e
|
|
@ -0,0 +1,7 @@
|
|||
.DS_Store
|
||||
phpunit.phar
|
||||
/vendor
|
||||
composer.phar
|
||||
composer.lock
|
||||
*.project
|
||||
.idea/
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# Dcat Admin Extension Goods
|
||||
|
||||
Dcat-admin 商品管理
|
||||
|
||||
## 安装
|
||||
|
||||
- 进入项目目录
|
||||
- `mkdir packages && cd packages`
|
||||
- `git clone https://gitea.peidikeji.cn/pdkj/dcat-admin-goods.git`
|
||||
- `composer config repositories.peidikeji/dcat-admin-user path ./packages/dcat-admin-goods`
|
||||
- `composer require peidikeji/dcat-admin-goods:dev-develop`
|
||||
- `php artisan vendor:publish --provider=Peidikeji\Goods\GoodsServiceProvider`
|
||||
|
||||
## 数据表
|
||||
|
||||
### 商品分类: goods_category
|
||||
|
||||
| column | type | nullable | default | comment |
|
||||
| - | - | - | - | - |
|
||||
| id | bigint | not null | - | 主键 |
|
||||
| name | varchar(191) | not null | - | 分类名称 |
|
||||
| image | varchar(191) | null | - | 分类图片 |
|
||||
| description | varchar(191) | null | - | 描述 |
|
||||
| parent_id | bigint | not null | 0 | 上级 id |
|
||||
| level | int | not null | 1 | 层级 |
|
||||
| sort | int | not null | 1 | 排序(asc) |
|
||||
| is_enable | int | not null | 1 | 是否可用(0, 1) |
|
||||
| path | varchar(191) | not null | '-' | 所有上级 id(1-2-3-, -) |
|
||||
| created_at | timestamp | null | - | 创建时间 |
|
||||
| updated_at | timestamp | null | - | 更新时间 |
|
||||
|
||||
### 商品类别: goods_type
|
||||
|
||||
| column | type | nullable | default | comment |
|
||||
| - | - | - | - | - |
|
||||
| id | bigint | not null | - | 主键 |
|
||||
| name | varchar(191) | not null | - | 名称 |
|
||||
| attr | json | null | - | 属性展示 |
|
||||
| spec | json | null | - | 规格筛选 |
|
||||
| part | json | null | - | 配件多选 |
|
||||
|
||||
> goods_type.attr 存储格式
|
||||
|
||||
```json
|
||||
[
|
||||
{"name": "主体", "values": ["入网型号", "上市年份"]},
|
||||
{"name": "基本信息", "values": ["尺寸", "颜色", "CPU型号", "重量"]}
|
||||
]
|
||||
```
|
||||
|
||||
> goods_type.spec 存储格式
|
||||
|
||||
```json
|
||||
[
|
||||
{"name": "颜色", "values": ["红色", "白色", "灰色"]},
|
||||
{"name": "版本", "values": ["128G", "256G", "512G", "1TB"]}
|
||||
]
|
||||
```
|
||||
|
||||
> goods_type.part 存储格式
|
||||
|
||||
```json
|
||||
[
|
||||
{"name": "配件", "values": ["耳机", "快充", "手机壳"]},
|
||||
]
|
||||
```
|
||||
|
||||
### 商品品牌: goods_brand
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
.grid-attr .group:not(:first-child) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.grid-attr .group-item {
|
||||
display: inline-block;vertical-align: top;
|
||||
}
|
||||
.grid-attr .group-title {
|
||||
min-width: 80px
|
||||
}
|
||||
.grid-attr .group-value:not(:first-child) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "peidikeji/dcat-admin-goods",
|
||||
"alias": "goods",
|
||||
"description": "基础商品管理",
|
||||
"type": "library",
|
||||
"keywords": ["dcat-admin", "extension"],
|
||||
"homepage": "https://github.com/peidikeji/goods",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "panliang",
|
||||
"email": "1163816051@qq.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1.0",
|
||||
"peidikeji/dcat-admin": "*",
|
||||
"tucker-eric/eloquentfilter": "^3.1",
|
||||
"laravel/framework": "^9.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Peidikeji\\Goods\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Peidikeji\\Goods\\GoodsServiceProvider"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateGoodsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('goods_category', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->comment('分类名称');
|
||||
$table->string('image')->nullable()->comment('封面图');
|
||||
$table->string('description')->nullable()->comment('描述');
|
||||
$table->unsignedBigInteger('parent_id')->default(0)->comment('父级ID');
|
||||
$table->unsignedInteger('level')->default(1)->comment('层级');
|
||||
$table->unsignedInteger('sort')->default(1)->comment('排序 asc');
|
||||
$table->unsignedTinyInteger('is_enable')->default(1)->comment('状态');
|
||||
$table->string('path')->default('-')->comment('所有的父级ID');
|
||||
|
||||
$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('merchant_id')->nullable()->comment('商户ID');
|
||||
$table->unsignedBigInteger('type_id')->nullable()->comment('所属类别');
|
||||
$table->unsignedBigInteger('brand_id')->nullable()->comment('所属品牌');
|
||||
$table->string('name')->comment('商品名称');
|
||||
$table->string('goods_sn')->comment('编号');
|
||||
$table->string('cover_image')->nullable()->comment('封面图');
|
||||
$table->json('images')->nullable()->comment('图片集');
|
||||
$table->string('description')->nullable()->comment('描述');
|
||||
$table->json('content')->nullable()->comment('详细');
|
||||
$table->unsignedInteger('on_sale')->default(0)->comment('是否上架');
|
||||
$table->unsignedInteger('is_recommend')->default(0)->comment('是否推荐');
|
||||
$table->unsignedInteger('stock')->default(0)->comment('库存');
|
||||
$table->unsignedInteger('sold_count')->default(0)->comment('销量');
|
||||
$table->decimal('price', 12, 2)->comment('售价');
|
||||
$table->decimal('vip_price', 12, 2)->comment('会员价');
|
||||
$table->decimal('score_max_amount', 12, 2)->default(0)->comment('积分抵扣最大值');
|
||||
$table->json('attr')->nullable()->comment('属性[{name, values: [{name, value}]}]');
|
||||
$table->json('spec')->nullable()->comment('规格[{name, values: [{name, value}]}]');
|
||||
$table->json('part')->nullable()->comment('配件[{name, values: [{name, value}]}]');
|
||||
|
||||
$table->unsignedInteger('check_status')->default(0)->comment('审核状态(0: 未提交, 1: 审核中, 2: 审核通过, 3: 审核不通过)');
|
||||
$table->string('check_remarks')->nullable()->comment('审核备注');
|
||||
$table->timestamp('check_at')->nullable()->comment('审核通过时间');
|
||||
$table->unsignedBigInteger('check_user_id')->nullable()->comment('审核人');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->comment('商品');
|
||||
});
|
||||
|
||||
Schema::create('goods_checks', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('goods_id')->comment('所属商品, 关联 goods.id');
|
||||
|
||||
$table->unsignedBigInteger('category_id')->comment('所属分类, 关联 goods_category.id');
|
||||
$table->unsignedBigInteger('merchant_id')->nullable()->comment('商户ID');
|
||||
$table->unsignedBigInteger('type_id')->nullable()->comment('所属类别');
|
||||
$table->unsignedBigInteger('brand_id')->nullable()->comment('所属品牌');
|
||||
$table->string('name')->comment('商品名称');
|
||||
$table->string('goods_sn')->comment('编号');
|
||||
$table->string('cover_image')->nullable()->comment('封面图');
|
||||
$table->json('images')->nullable()->comment('图片集');
|
||||
$table->string('description')->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('销量');
|
||||
$table->decimal('price', 12, 2)->comment('售价');
|
||||
$table->decimal('vip_price', 12, 2)->comment('会员价');
|
||||
$table->decimal('score_max_amount', 12, 2)->default(0)->comment('积分抵扣最大值');
|
||||
$table->json('attr')->nullable()->comment('属性[{name, values: [{name, value}]}]');
|
||||
$table->json('spec')->nullable()->comment('规格[{name, values: [{name, value}]}]');
|
||||
$table->json('part')->nullable()->comment('配件[{name, values: [{name, value}]}]');
|
||||
|
||||
$table->unsignedInteger('check_status')->default(0)->comment('审核状态(0: 未提交, 1: 审核中, 2: 审核通过, 3: 审核不通过)');
|
||||
$table->string('check_remarks')->nullable()->comment('审核备注');
|
||||
$table->timestamp('check_at')->nullable()->comment('审核通过时间');
|
||||
$table->unsignedBigInteger('check_user_id')->nullable()->comment('审核人');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->comment('商品-上架审核');
|
||||
});
|
||||
|
||||
Schema::create('goods_sku', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('sn')->comment('货号');
|
||||
$table->unsignedBigInteger('goods_id')->comment('所属商品, 关联 goods.id');
|
||||
$table->string('name')->comment('名称');
|
||||
$table->decimal('price', 12, 2)->comment('价格');
|
||||
$table->decimal('vip_price', 12, 2)->comment('会员价');
|
||||
$table->decimal('score_max_amount', 12, 2)->default(0)->comment('积分抵扣最大值');
|
||||
$table->unsignedInteger('stock')->comment('库存');
|
||||
$table->unsignedInteger('sold_count')->default(0)->comment('销量');
|
||||
$table->json('spec')->nullable()->comment('规格[{name, value, price}]');
|
||||
|
||||
$table->comment('商品-SKU');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('goods_sku');
|
||||
Schema::dropIfExists('goods_checks');
|
||||
Schema::dropIfExists('goods');
|
||||
Schema::dropIfExists('goods_category');
|
||||
Schema::dropIfExists('goods_type');
|
||||
Schema::dropIfExists('goods_brand');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?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('goods_cart', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedBigInteger('user_id')->comment('用户ID');
|
||||
$table->unsignedBigInteger('goods_id')->comment('商品ID');
|
||||
$table->unsignedBigInteger('merchant_id')->nullable()->comment('店铺 ID');
|
||||
|
||||
$table->string('goods_name');
|
||||
$table->string('goods_sn')->nullable();
|
||||
$table->string('goods_sku_sn')->nullable();
|
||||
$table->unsignedBigInteger('goods_sku_id')->nullable()->comment('商品sku ID');
|
||||
$table->string('cover_image')->nullable()->comment('封面图');
|
||||
$table->decimal('price', 12, 2)->comment('售价');
|
||||
$table->decimal('vip_price', 12, 2)->comment('售价');
|
||||
$table->json('attr')->nullable()->comment('属性');
|
||||
$table->json('spec')->nullable()->comment('规格');
|
||||
$table->json('part')->nullable()->comment('配件');
|
||||
|
||||
$table->unsignedInteger('amount')->comment('数量');
|
||||
$table->timestamps();
|
||||
|
||||
$table->comment('购物车');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('goods_cart');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
[
|
||||
{
|
||||
"table": "goods-type.spec",
|
||||
"name": "颜色",
|
||||
"values": [
|
||||
"白色",
|
||||
"红色",
|
||||
"蓝色"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "goods-type.attr",
|
||||
"name": "主体",
|
||||
"values": [
|
||||
"入网型号",
|
||||
"上市年份"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "goods-type.part",
|
||||
"name": "套餐",
|
||||
"values": [
|
||||
"套餐1",
|
||||
"套餐2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "goods.attr",
|
||||
"name": "主体",
|
||||
"values": [
|
||||
{ "name": "入网型号", "value": "5G" },
|
||||
{ "name": "上市年份", "value": "2020" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "goods.spec",
|
||||
"name": "颜色",
|
||||
"values": [
|
||||
{ "name": "白色", "value": 0 },
|
||||
{ "name": "红色", "value": 0 },
|
||||
{ "name": "蓝色", "value": 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "goods.part",
|
||||
"name": "套餐",
|
||||
"values": [
|
||||
{ "name": "套餐1", "value": 150 },
|
||||
{ "name": "套餐2", "value": 100 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "goods-sku.attr",
|
||||
"name": "主体",
|
||||
"values": [
|
||||
{ "name": "入网型号", "value": "5G" },
|
||||
{ "name": "上市年份", "value": "2020" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "goods-sku.spec",
|
||||
"name": "颜色",
|
||||
"value": "白色",
|
||||
"price": 0
|
||||
},
|
||||
{
|
||||
"table": "goods-sku.part",
|
||||
"name": "颜色",
|
||||
"values": [
|
||||
{ "name": "套餐1", "price": 150 },
|
||||
{ "name": "套餐2", "price": 100 }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
return [];
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
];
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
return [];
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
return [];
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
return [];
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'labels' => [
|
||||
'GoodsBrand' => '品牌管理',
|
||||
'goods' => '商品管理',
|
||||
'brand' => '品牌',
|
||||
'create' => '创建',
|
||||
'edit' => '修改',
|
||||
],
|
||||
'fields' => [
|
||||
'name' => '名称',
|
||||
'image' => '图片',
|
||||
],
|
||||
];
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'labels' => [
|
||||
'GoodsCategory' => '商品分类',
|
||||
'goods' => '商品管理',
|
||||
'category' => '分类管理',
|
||||
'root' => '无',
|
||||
'goods-category' => '商品分类',
|
||||
],
|
||||
'fields' => [
|
||||
'name' => '分类名称',
|
||||
'image' => '图片',
|
||||
'description' => '描述',
|
||||
'sort' => '排序(正序)',
|
||||
'parent_id' => '上级',
|
||||
'is_enable' => '开启',
|
||||
'parent' => [
|
||||
'name' => '上级',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'labels' => [
|
||||
'GoodsSku' => '货品管理',
|
||||
'goods' => '商品管理',
|
||||
'sku' => '货品管理',
|
||||
'create' => '添加',
|
||||
],
|
||||
'fields' => [
|
||||
'sn' => '货号',
|
||||
'name' => '名称',
|
||||
'price' => '售价',
|
||||
'vip_price' => '会员价',
|
||||
'weight' => '重量',
|
||||
'volume' => '体积',
|
||||
'shipping_tmp_id' => '运费模板',
|
||||
'stock' => '库存',
|
||||
'spec' => '规格',
|
||||
'origin_price' => '原价',
|
||||
'discount_price' => '折扣价',
|
||||
'score_max_amount' => '最大抵扣积分',
|
||||
],
|
||||
];
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'labels' => [
|
||||
'GoodsType' => '商品类别',
|
||||
'goods' => '商品管理',
|
||||
'type' => '商品类别',
|
||||
],
|
||||
'fields' => [
|
||||
'name' => '名称',
|
||||
'spec' => '规格',
|
||||
'attr' => '属性',
|
||||
'part' => '配件',
|
||||
'values' => '可选值',
|
||||
'group' => '分组',
|
||||
],
|
||||
];
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'labels' => [
|
||||
'Goods' => '商品信息',
|
||||
'goods' => '商品信息',
|
||||
'create' => '创建',
|
||||
'edit' => '修改',
|
||||
'attr' => '属性',
|
||||
'spec' => '规格',
|
||||
'part' => '配件',
|
||||
'GoodsCheck' => '上架审核',
|
||||
'goods-check' => '上架审核',
|
||||
],
|
||||
'fields' => [
|
||||
'merchant_id' => '店铺',
|
||||
'merchant' => [
|
||||
'name' => '店铺',
|
||||
],
|
||||
'category_id' => '分类',
|
||||
'category' => [
|
||||
'name' => '分类',
|
||||
],
|
||||
'brand_id' => '品牌',
|
||||
'brand' => [
|
||||
'name' => '品牌',
|
||||
],
|
||||
'type_id' => '类别',
|
||||
'type' => [
|
||||
'name' => '类别',
|
||||
],
|
||||
'name' => '名称',
|
||||
'goods_sn' => '编号',
|
||||
'cover_image' => '封面图',
|
||||
'price' => '售价',
|
||||
'vip_price' => '会员价',
|
||||
'score_max_amount' => '积分最大抵扣',
|
||||
'weight' => '重量',
|
||||
'volume' => '体积',
|
||||
'shipping_tmp_id' => '运费模板',
|
||||
'spec' => '规格',
|
||||
'attr' => '属性',
|
||||
'part' => '配件',
|
||||
'on_sale' => '上架',
|
||||
'stock' => '库存',
|
||||
'sold_count' => '销量',
|
||||
'images' => '详细图',
|
||||
'content' => '内容',
|
||||
'created_at' => '创建时间',
|
||||
'updated_at' => '更新时间',
|
||||
'check_remarks' => '未通过原因',
|
||||
'check_status' => '审核状态',
|
||||
'check_user_id' => '审核人',
|
||||
'check_user' => [
|
||||
'name' => '审核人',
|
||||
],
|
||||
'check_at' => '审核时间',
|
||||
'is_recommend' => '推荐',
|
||||
],
|
||||
];
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Admin;
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::group([
|
||||
'prefix' => config('admin.route.prefix'),
|
||||
'middleware' => config('admin.route.middleware'),
|
||||
], function () {
|
||||
Route::resource('goods-category', GoodsCategoryController::class)->names('dcat.admin.goods_category');
|
||||
Route::resource('goods-brand', GoodsBrandController::class)->names('dcat.admin.goods_brand');
|
||||
Route::resource('goods-type', GoodsTypeController::class)->names('dcat.admin.goods_type');
|
||||
|
||||
Route::resource('goods/{goods}/sku', GoodsSkuController::class)->names('dcat.admin.goods_sku');
|
||||
|
||||
Route::get('goods/{goods}/attr', [GoodsController::class, 'attr'])->name('dcat.admin.goods.attr');
|
||||
Route::get('goods/{goods}/spec', [GoodsController::class, 'spec'])->name('dcat.admin.goods.spec');
|
||||
Route::get('goods/{goods}/part', [GoodsController::class, 'part'])->name('dcat.admin.goods.part');
|
||||
Route::resource('goods-check', GoodsCheckController::class)->names('dcat.admin.goods_check');
|
||||
Route::resource('goods', GoodsController::class)->names('dcat.admin.goods');
|
||||
|
||||
Route::get('api/goods', [GoodsController::class, 'list'])->name('dcat.admin.api.goods');
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Api;
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::group([
|
||||
'middleware' => ['api'],
|
||||
'prefix' => 'api',
|
||||
], function () {
|
||||
Route::get('goods/category', [GoodsCategoryController::class, 'index']);
|
||||
|
||||
Route::get('goods/cart-by-merchant', [GoodsCartController::class, 'groupByMerchant']);
|
||||
Route::delete('goods/cart', [GoodsCartController::class, 'destroy']);
|
||||
Route::apiResource('goods/cart', GoodsCartController::class)->only(['index', 'store', 'update']);
|
||||
|
||||
Route::get('goods/{id}', [GoodsController::class, 'show']);
|
||||
Route::get('goods/{id}/skus', [GoodsController::class, 'skus']);
|
||||
Route::get('goods', [GoodsController::class, 'index']);
|
||||
});
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Action\Check;
|
||||
|
||||
use Dcat\Admin\Grid\RowAction;
|
||||
use Dcat\Admin\Widgets\Modal;
|
||||
use Peidikeji\Goods\Form\Check\HandleCheckForm;
|
||||
|
||||
class RowHandleCheck extends RowAction
|
||||
{
|
||||
protected $title = '审核';
|
||||
|
||||
protected function html()
|
||||
{
|
||||
$form = HandleCheckForm::make()->payload(['id' => $this->row('id')]);
|
||||
|
||||
return Modal::make()->lg()->body($form)->title($this->title())->button($this->title);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Action\Check;
|
||||
|
||||
use Dcat\Admin\Grid\RowAction;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Peidikeji\Goods\GoodsService;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
|
||||
class RowSubmitCheck extends RowAction
|
||||
{
|
||||
protected $title = '提交审核';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$goods = Goods::findOrFail($this->getKey());
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
GoodsService::make()->submitCheck($goods);
|
||||
DB::commit();
|
||||
|
||||
return $this->response()->success('操作成功')->refresh();
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return $this->response()->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function confirm()
|
||||
{
|
||||
return ['是否确定?'];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Action\Goods;
|
||||
|
||||
use Dcat\Admin\Grid\RowAction;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
|
||||
class RowGoodsSale extends RowAction
|
||||
{
|
||||
public function title()
|
||||
{
|
||||
return $this->row->on_sale ? '下架' : '上架';
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$info = Goods::findOrFail($this->getKey());
|
||||
Goods::where('id', $this->getKey())->update(['on_sale' => ! $info->on_sale]);
|
||||
|
||||
return $this->response()->success('操作成功')->refresh();
|
||||
}
|
||||
|
||||
public function confirm()
|
||||
{
|
||||
return ['是否确定?'];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Filters;
|
||||
|
||||
use EloquentFilter\ModelFilter;
|
||||
|
||||
class GoodsCategoryFilter extends ModelFilter
|
||||
{
|
||||
public function parent($v)
|
||||
{
|
||||
$this->where('parent_id', $v);
|
||||
}
|
||||
|
||||
public function level($v)
|
||||
{
|
||||
$this->where('level', $v);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Filters;
|
||||
|
||||
use EloquentFilter\ModelFilter;
|
||||
use Peidikeji\Merchant\Enums\CheckStatus;
|
||||
|
||||
class GoodsFilter extends ModelFilter
|
||||
{
|
||||
public function q($v)
|
||||
{
|
||||
$this->where('name', 'like', '%'.$v.'%');
|
||||
}
|
||||
|
||||
public function search($key)
|
||||
{
|
||||
$this->where(function ($q) use ($key) {
|
||||
$q->orWhere('name', 'like', "%$key%")->orWhere('description', 'like', "%$key%");
|
||||
});
|
||||
}
|
||||
|
||||
public function merchant($v)
|
||||
{
|
||||
if (! $v) {
|
||||
$this->whereNull('merchant_id');
|
||||
} else {
|
||||
$this->where('merchant_id', $v);
|
||||
}
|
||||
}
|
||||
|
||||
public function recommend($v)
|
||||
{
|
||||
$this->where('is_recommend', $v);
|
||||
}
|
||||
|
||||
public function category($v)
|
||||
{
|
||||
$this->where('category_id', $v);
|
||||
}
|
||||
|
||||
public function sort($v)
|
||||
{
|
||||
$this->orderBy($v, request('sort_by', 'asc'));
|
||||
}
|
||||
|
||||
public function state($state): GoodsFilter
|
||||
{
|
||||
return match ((int) $state) {
|
||||
default => $this->where('on_sale', true),
|
||||
2 => $this->where('check_status', CheckStatus::Processing),
|
||||
3 => $this->where('on_sale', false)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Form;
|
||||
|
||||
use Dcat\Admin\Form\Field;
|
||||
|
||||
class Attr extends Field
|
||||
{
|
||||
protected $view = 'dcat-admin-goods::form.attr';
|
||||
|
||||
protected $variables = [
|
||||
'headers' => [],
|
||||
'keys' => [],
|
||||
'type' => null,
|
||||
];
|
||||
|
||||
public function header(array $headers)
|
||||
{
|
||||
$this->addVariables([
|
||||
'headers' => $headers,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function keys($keys)
|
||||
{
|
||||
$this->addVariables([
|
||||
'keys' => $keys,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function type($type)
|
||||
{
|
||||
$this->addVariables(['type' => $type]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Form\Check;
|
||||
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Contracts\LazyRenderable;
|
||||
use Dcat\Admin\Traits\LazyWidget;
|
||||
use Dcat\Admin\Widgets\Form;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Peidikeji\Goods\GoodsService;
|
||||
use Peidikeji\Goods\Models\GoodsCheck;
|
||||
|
||||
class HandleCheckForm extends Form implements LazyRenderable
|
||||
{
|
||||
use LazyWidget;
|
||||
|
||||
public function handle(array $input)
|
||||
{
|
||||
$info = GoodsCheck::findOrFail($this->payload['id']);
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
GoodsService::make()->handleCheck($info, Admin::user(), (bool) $input['check_status'], $input['check_remarks']);
|
||||
DB::commit();
|
||||
|
||||
return $this->response()->success('操作成功')->refresh();
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return $this->response()->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function form()
|
||||
{
|
||||
$this->radio('check_status')->options([1 => '通过', 0 => '不通过'])->default(1);
|
||||
$this->textarea('check_remarks');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Form\Goods;
|
||||
|
||||
use Dcat\Admin\Contracts\LazyRenderable;
|
||||
use Dcat\Admin\Traits\LazyWidget;
|
||||
use Dcat\Admin\Widgets\Form;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
|
||||
class AttrForm extends Form implements LazyRenderable
|
||||
{
|
||||
use LazyWidget;
|
||||
|
||||
public function handle(array $input)
|
||||
{
|
||||
$goods = Goods::findOrFail($this->payload['goods_id']);
|
||||
$goods->update(['attr' => json_decode($input['attr'])]);
|
||||
|
||||
return $this->response()->success('保存成功');
|
||||
}
|
||||
|
||||
public function form()
|
||||
{
|
||||
$attr = $this->model()->type?->attr;
|
||||
$this->spec('attr')->header(['分组', '名称', '属性值'])->type($attr);
|
||||
}
|
||||
|
||||
protected function renderResetButton()
|
||||
{
|
||||
return '<a href="javascript:window.history.back()" class="btn btn-white pull-left"><i class="feather icon-arrow-left"></i> 返回</a>';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Form\Goods;
|
||||
|
||||
use Dcat\Admin\Contracts\LazyRenderable;
|
||||
use Dcat\Admin\Traits\LazyWidget;
|
||||
use Dcat\Admin\Widgets\Form;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
|
||||
class PartForm extends Form implements LazyRenderable
|
||||
{
|
||||
use LazyWidget;
|
||||
|
||||
public function handle(array $input)
|
||||
{
|
||||
$goods = Goods::findOrFail($this->payload['goods_id']);
|
||||
$part = json_decode($input['part'], true);
|
||||
foreach ($part as &$item) {
|
||||
foreach ($item['values'] as &$subItem) {
|
||||
}
|
||||
$subItem['value'] = floatval($subItem['value']);
|
||||
}
|
||||
$goods->update(['part' => $part]);
|
||||
|
||||
return $this->response()->success('保存成功');
|
||||
}
|
||||
|
||||
public function form()
|
||||
{
|
||||
$part = $this->model()->type?->part;
|
||||
|
||||
$this->spec('part')->header(['名称', '可选值', '价格'])->type($part);
|
||||
}
|
||||
|
||||
protected function renderResetButton()
|
||||
{
|
||||
return '<a href="javascript:window.history.back()" class="btn btn-white pull-left"><i class="feather icon-arrow-left"></i> 返回</a>';
|
||||
}
|
||||
|
||||
protected function getSubmitButtonLabel()
|
||||
{
|
||||
return '保存';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Form\Goods;
|
||||
|
||||
use Dcat\Admin\Contracts\LazyRenderable;
|
||||
use Dcat\Admin\Traits\LazyWidget;
|
||||
use Dcat\Admin\Widgets\Form;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
|
||||
class SpecForm extends Form implements LazyRenderable
|
||||
{
|
||||
use LazyWidget;
|
||||
|
||||
public function handle(array $input)
|
||||
{
|
||||
$goods = Goods::findOrFail($this->payload['goods_id']);
|
||||
$spec = json_decode($input['spec'], true) ?: [];
|
||||
foreach ($spec as &$item) {
|
||||
foreach ($item['values'] as &$subItem) {
|
||||
$subItem['value'] = floatval($subItem['value']);
|
||||
}
|
||||
}
|
||||
$goods->update(['spec' => $spec]);
|
||||
|
||||
return $this->response()->success('保存成功');
|
||||
}
|
||||
|
||||
public function form()
|
||||
{
|
||||
$spec = $this->model()->type?->spec;
|
||||
|
||||
$this->spec('spec')->header(['名称', '可选值', '价格'])->type($spec);
|
||||
}
|
||||
|
||||
protected function renderResetButton()
|
||||
{
|
||||
return '<a href="javascript:window.history.back()" class="btn btn-white pull-left"><i class="feather icon-arrow-left"></i> 返回</a>';
|
||||
}
|
||||
|
||||
protected function getSubmitButtonLabel()
|
||||
{
|
||||
return '保存';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Form\GoodsType;
|
||||
|
||||
use Dcat\Admin\Contracts\LazyRenderable;
|
||||
use Dcat\Admin\Traits\LazyWidget;
|
||||
use Dcat\Admin\Widgets\Form;
|
||||
use Peidikeji\Goods\Models\GoodsType;
|
||||
|
||||
class AttrForm extends Form implements LazyRenderable
|
||||
{
|
||||
use LazyWidget;
|
||||
|
||||
public function handle(array $input)
|
||||
{
|
||||
$info = GoodsType::findOrFail($this->payload['type_id']);
|
||||
$info->update(['attr' => json_decode($input['attr'])]);
|
||||
|
||||
return $this->response()->success('保存成功');
|
||||
}
|
||||
|
||||
public function form()
|
||||
{
|
||||
$this->fill(['attr' => $this->payload['attr']]);
|
||||
|
||||
$this->attr('attr')->header(['分组', '名称'])->keys(['name']);
|
||||
|
||||
$reset = data_get($this->payload, 'reset', true);
|
||||
$this->resetButton((bool) $reset);
|
||||
}
|
||||
|
||||
protected function renderResetButton()
|
||||
{
|
||||
return (! empty($this->buttons['reset'])) ? '<a href="javascript:window.history.back()" class="btn btn-white pull-left"><i class="feather icon-arrow-left"></i> 返回</a>' : '';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Form\GoodsType;
|
||||
|
||||
use Dcat\Admin\Contracts\LazyRenderable;
|
||||
use Dcat\Admin\Traits\LazyWidget;
|
||||
use Dcat\Admin\Widgets\Form;
|
||||
use Peidikeji\Goods\Models\GoodsType;
|
||||
|
||||
class PartForm extends Form implements LazyRenderable
|
||||
{
|
||||
use LazyWidget;
|
||||
|
||||
public function handle(array $input)
|
||||
{
|
||||
$info = GoodsType::findOrFail($this->payload['type_id']);
|
||||
$info->update(['part' => json_decode($input['part'])]);
|
||||
|
||||
return $this->response()->success('保存成功');
|
||||
}
|
||||
|
||||
public function form()
|
||||
{
|
||||
$this->fill(['part' => $this->payload['part']]);
|
||||
|
||||
$this->attr('part')->header(['名称', '可选值'])->keys(['name']);
|
||||
|
||||
$reset = data_get($this->payload, 'reset', true);
|
||||
$this->resetButton((bool) $reset);
|
||||
}
|
||||
|
||||
protected function renderResetButton()
|
||||
{
|
||||
return (! empty($this->buttons['reset'])) ? '<a href="javascript:window.history.back()" class="btn btn-white pull-left"><i class="feather icon-arrow-left"></i> 返回</a>' : '';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Form\GoodsType;
|
||||
|
||||
use Dcat\Admin\Contracts\LazyRenderable;
|
||||
use Dcat\Admin\Traits\LazyWidget;
|
||||
use Dcat\Admin\Widgets\Form;
|
||||
use Peidikeji\Goods\Models\GoodsType;
|
||||
|
||||
class SpecForm extends Form implements LazyRenderable
|
||||
{
|
||||
use LazyWidget;
|
||||
|
||||
public function handle(array $input)
|
||||
{
|
||||
$info = GoodsType::findOrFail($this->payload['type_id']);
|
||||
$info->update(['spec' => json_decode($input['spec'])]);
|
||||
|
||||
return $this->response()->success('保存成功');
|
||||
}
|
||||
|
||||
public function form()
|
||||
{
|
||||
$this->fill(['spec' => $this->payload['spec']]);
|
||||
|
||||
$this->attr('spec')->header(['名称', '可选值'])->keys(['name']);
|
||||
|
||||
$reset = data_get($this->payload, 'reset', true);
|
||||
$this->resetButton((bool) $reset);
|
||||
}
|
||||
|
||||
protected function renderResetButton()
|
||||
{
|
||||
return (! empty($this->buttons['reset'])) ? '<a href="javascript:window.history.back()" class="btn btn-white pull-left"><i class="feather icon-arrow-left"></i> 返回</a>' : '';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Form;
|
||||
|
||||
use Dcat\Admin\Form\Field;
|
||||
|
||||
class Spec extends Field
|
||||
{
|
||||
protected $view = 'dcat-admin-goods::form.spec';
|
||||
|
||||
protected $variables = [
|
||||
'headers' => [],
|
||||
'type' => null,
|
||||
];
|
||||
|
||||
public function header(array $headers)
|
||||
{
|
||||
$this->addVariables([
|
||||
'headers' => $headers,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function type($type)
|
||||
{
|
||||
$this->addVariables(['type' => $type]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function formatFieldData($data)
|
||||
{
|
||||
// 获取到当前字段值
|
||||
$value = parent::formatFieldData($data);
|
||||
|
||||
return $value ?: [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods;
|
||||
|
||||
use App\Exceptions\BizException;
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Models\Administrator;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
use Peidikeji\Goods\Models\GoodsCheck;
|
||||
use Peidikeji\Goods\Models\GoodsSku;
|
||||
use Peidikeji\Merchant\Enums\CheckStatus;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据规格生成SKU
|
||||
*
|
||||
* @param Goods $goods 商品
|
||||
* @param array $options {spec: 指定规格, price: 基础价格, name: 基础名称, stock: 默认库存, name_add: 是否在名称上面追加属性值, price_add: 是否在价格上面追加属性的加价}
|
||||
*/
|
||||
public function generateSku(Goods $goods, $options = [])
|
||||
{
|
||||
$spec = data_get($options, 'spec', $goods->spec);
|
||||
$price = data_get($options, 'price', $goods->price);
|
||||
$vipPrice = data_get($options, 'vip_price', $goods->vip_price);
|
||||
|
||||
$weight = $goods->weight;
|
||||
$volume = $goods->volume;
|
||||
$shippingTmpId = data_get($options, 'shipping_tmp_id', $goods->shipping_tmp_id);
|
||||
|
||||
$name = data_get($options, 'name', $goods->name);
|
||||
$stock = data_get($options, 'stock', $goods->stock);
|
||||
$nameAdd = (bool) data_get($options, 'name_add', false);
|
||||
$priceAdd = (bool) data_get($options, 'price_add', false);
|
||||
if ($spec) {
|
||||
$specList = [];
|
||||
foreach ($spec as $item) {
|
||||
$items = [];
|
||||
foreach ($item['values'] as $value) {
|
||||
array_push($items, [
|
||||
'name' => $item['name'],
|
||||
'value' => $value['name'],
|
||||
'price' => floatval($value['value']),
|
||||
]);
|
||||
}
|
||||
array_push($specList, $items);
|
||||
}
|
||||
$cartesianList = Arr::crossJoin(...$specList);
|
||||
foreach ($cartesianList as $items) {
|
||||
$specPrice = $priceAdd ? $price + array_sum(array_column($items, 'price')) : $price;
|
||||
$specVipPrice = $priceAdd ? $vipPrice + array_sum(array_column($items, 'price')) : $vipPrice;
|
||||
$specName = $nameAdd ? $name.' '.implode(' ', array_column($items, 'value')) : $name;
|
||||
$exists = $goods->skus()->jsonArray($items)->exists();
|
||||
$attributes = [
|
||||
'name' => $specName,
|
||||
'price' => $specPrice,
|
||||
'vip_price' => $specVipPrice,
|
||||
'stock' => $stock,
|
||||
'spec' => $items,
|
||||
'weight' => $weight,
|
||||
'volume' => $volume,
|
||||
'shipping_tmp_id' => $shippingTmpId,
|
||||
];
|
||||
if ($exists) {
|
||||
$goods->skus()->jsonArray($items)->update($attributes);
|
||||
} else {
|
||||
$attributes['sn'] = $this->generateSn();
|
||||
$goods->skus()->create($attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请审核
|
||||
*
|
||||
* @param Goods $goods
|
||||
* @param Administrator $user
|
||||
* @return GoodsCheck
|
||||
*/
|
||||
public function submitCheck(Goods $goods, Administrator $user = null)
|
||||
{
|
||||
$goods->update([
|
||||
'check_status' => CheckStatus::Processing,
|
||||
]);
|
||||
|
||||
$attributes = Arr::except($goods->toArray(), ['check_status', 'check_remarks', 'check_at', 'check_user_id']);
|
||||
$attributes['check_status'] = CheckStatus::Processing;
|
||||
|
||||
$check = $goods->checkLogs()->create($attributes);
|
||||
|
||||
// 如果当前用户拥有审核权限, 则自动通过审核
|
||||
$user = $user ?: Admin::user();
|
||||
if ($user->can('dcat.admin.goods.check')) {
|
||||
$this->handleCheck($check, $user, true, '自动通过审核');
|
||||
}
|
||||
|
||||
return $check;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核商品
|
||||
*
|
||||
* @param GoodsCheck $check
|
||||
* @param Administrator $admin
|
||||
* @param bool $status true: 通过, false: 不通过
|
||||
* @param string $remarks 审核备注
|
||||
*/
|
||||
public function handleCheck(GoodsCheck $check, Administrator $admin, bool $status, string $remarks = null)
|
||||
{
|
||||
if ($check->check_status === CheckStatus::Success || $check->check_status === CheckStatus::Fail) {
|
||||
throw new BizException('已经审核过了');
|
||||
}
|
||||
$goods = $check->goods;
|
||||
if ($goods->check_status !== CheckStatus::Processing) {
|
||||
throw new BizException('商品未申请审核');
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
'check_status' => $status ? CheckStatus::Success : CheckStatus::Fail,
|
||||
'check_at' => now(),
|
||||
'check_remarks' => $remarks,
|
||||
'check_user_id' => $admin->id,
|
||||
];
|
||||
$check->update($attributes);
|
||||
|
||||
$goods->update(array_merge($attributes, ['on_sale' => 1]));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class GoodsServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
$this->loadRoutesFrom(__DIR__.'/../routes/admin.php');
|
||||
$this->loadRoutesFrom(__DIR__.'/../routes/api.php');
|
||||
|
||||
// $this->loadMigrationsFrom(__DIR__.'/../database/');
|
||||
|
||||
$this->loadViewsFrom(__DIR__.'/../views', 'dcat-admin-goods');
|
||||
|
||||
$this->publishes([
|
||||
__DIR__.'/../assets' => public_path('vendor/dcat-admin-goods'),
|
||||
], 'dcat-admin-goods-assets');
|
||||
|
||||
$this->publishes([
|
||||
__DIR__.'/../database/' => database_path('migrations'),
|
||||
], 'dcat-admin-goods-migrations');
|
||||
|
||||
$this->loadTranslationsFrom(__DIR__.'/../lang', 'dcat-admin-goods');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Admin;
|
||||
|
||||
use Dcat\Admin\Form;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Http\Controllers\AdminController;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
use Peidikeji\Goods\Models\GoodsBrand;
|
||||
|
||||
class GoodsBrandController extends AdminController
|
||||
{
|
||||
protected $translation = 'dcat-admin-goods::goods-brand';
|
||||
|
||||
protected function grid()
|
||||
{
|
||||
return Grid::make(new GoodsBrand(), function (Grid $grid) {
|
||||
$grid->disableRowSelector();
|
||||
|
||||
$grid->disableViewButton();
|
||||
|
||||
$grid->column('name');
|
||||
$grid->column('image')->image('', 120);
|
||||
});
|
||||
}
|
||||
|
||||
protected function form()
|
||||
{
|
||||
return Form::make(new GoodsBrand(), function (Form $form) {
|
||||
$form->text('name');
|
||||
$form->image('image')
|
||||
->autoUpload()
|
||||
->saveFullUrl()
|
||||
->move('goods/brand');
|
||||
|
||||
$form->disableResetButton();
|
||||
$form->disableCreatingCheck();
|
||||
$form->disableViewCheck();
|
||||
$form->disableEditingCheck();
|
||||
|
||||
$form->deleting(function (Form $form) {
|
||||
$data = $form->model()->toArray();
|
||||
foreach ($data as $item) {
|
||||
$id = data_get($item, 'id');
|
||||
// 品牌下面包含商品, 阻止删除
|
||||
if (Goods::where('brand_id', $id)->exists()) {
|
||||
return $form->response()->error('请先删除关联的商品');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Admin;
|
||||
|
||||
use Dcat\Admin\Form;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Http\Controllers\AdminController;
|
||||
use Dcat\Admin\Show;
|
||||
use Peidikeji\Goods\Models\GoodsCategory;
|
||||
|
||||
class GoodsCategoryController extends AdminController
|
||||
{
|
||||
protected $translation = 'dcat-admin-goods::goods-category';
|
||||
|
||||
protected function grid()
|
||||
{
|
||||
$grid = Grid::make(new GoodsCategory());
|
||||
|
||||
$grid->disableRowSelector();
|
||||
|
||||
$grid->column('name')->tree(true, false);
|
||||
$grid->column('image')->image('', 100);
|
||||
$grid->column('sort')->editable(['mask' => '{alias:\'numeric\',min:0,max:999}']);
|
||||
$grid->column('is_enable')->switch();
|
||||
|
||||
return $grid;
|
||||
}
|
||||
|
||||
protected function form()
|
||||
{
|
||||
$form = Form::make(GoodsCategory::with(['parent']));
|
||||
|
||||
$form->select('parent_id')->help('不选默认为顶级')->options(GoodsCategory::selectOptions())->default(0);
|
||||
|
||||
$form->text('name')->required();
|
||||
$form->image('image')
|
||||
->uniqueName()
|
||||
->move('goods/category')
|
||||
->autoUpload();
|
||||
$form->number('sort')
|
||||
->min(0)
|
||||
->default(1)
|
||||
->help('数值越小, 越靠前');
|
||||
$form->switch('is_enable')->default(1);
|
||||
$form->text('description');
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
protected function detail($id)
|
||||
{
|
||||
$info = GoodsCategory::with(['parent'])->findOrFail($id);
|
||||
$show = Show::make($info);
|
||||
|
||||
$show->field('name');
|
||||
$show->field('parent.name');
|
||||
$show->field('image')->image('', 100);
|
||||
$show->field('description');
|
||||
$show->field('sort');
|
||||
|
||||
return $show;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Admin;
|
||||
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Grid\Displayers\Actions;
|
||||
use Dcat\Admin\Http\Controllers\AdminController;
|
||||
use Dcat\Admin\Show;
|
||||
use Peidikeji\Goods\Action\Check\RowHandleCheck;
|
||||
use Peidikeji\Goods\Models\GoodsCheck;
|
||||
use Peidikeji\Merchant\Enums\CheckStatus;
|
||||
|
||||
class GoodsCheckController extends AdminController
|
||||
{
|
||||
protected $translation = 'dcat-admin-goods::goods';
|
||||
|
||||
protected function grid()
|
||||
{
|
||||
return Grid::make(GoodsCheck::with(['merchant', 'category']), function (Grid $grid) {
|
||||
$grid->model()->sort();
|
||||
|
||||
$grid->column('merchant.name');
|
||||
$grid->column('category.name');
|
||||
$grid->column('name')->display(function () {
|
||||
return ($this->cover_image ? '<img src="'.$this->cover_image.'" width="60" class="img-thumbnail"/> ' : '').'<a href="'.admin_url('goods-check/'.$this->id).'">'.$this->name.'</a>';
|
||||
});
|
||||
$grid->column('price');
|
||||
$grid->column('vip_price');
|
||||
$grid->column('check_status')->display(fn () => $this->check_status->dot());
|
||||
$grid->column('created_at', '申请时间');
|
||||
|
||||
$grid->showViewButton();
|
||||
$grid->actions(function (Actions $actions) {
|
||||
$row = $actions->row;
|
||||
if ($row->check_status === CheckStatus::Processing) {
|
||||
$actions->append(new RowHandleCheck());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected function detail($id)
|
||||
{
|
||||
Admin::css([
|
||||
'vendor/dcat-admin-goods/goods.css',
|
||||
]);
|
||||
$info = GoodsCheck::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('vip_price');
|
||||
$show->field('score_discount_amount');
|
||||
$show->field('cover_image')->image('', 100);
|
||||
$show->field('images')->image('', 100);
|
||||
$show->field('content')->image('');
|
||||
$show->field('spec')->view('dcat-admin-goods::goods.grid-attr');
|
||||
$show->field('attr')->view('dcat-admin-goods::goods.grid-attr');
|
||||
$show->field('part')->view('dcat-admin-goods::goods.grid-attr');
|
||||
$show->field('on_sale')->bool();
|
||||
$show->field('sold_count');
|
||||
$show->field('check_status')->unescape()->as(fn () => $this->check_status->label());
|
||||
$show->field('check_at');
|
||||
$show->field('check_remarks');
|
||||
$show->field('check_user.name');
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Admin;
|
||||
|
||||
use App\Models\ShippingTmp;
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Form;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Grid\Displayers\Actions;
|
||||
use Dcat\Admin\Grid\Tools\Selector;
|
||||
use Dcat\Admin\Http\Controllers\AdminController;
|
||||
use Dcat\Admin\Layout\Content;
|
||||
use Dcat\Admin\Show;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Peidikeji\Goods\Action\Check\RowSubmitCheck;
|
||||
use Peidikeji\Goods\Action\Goods\RowGoodsSale;
|
||||
use Peidikeji\Goods\Form\Goods\AttrForm;
|
||||
use Peidikeji\Goods\Form\Goods\PartForm;
|
||||
use Peidikeji\Goods\Form\Goods\SpecForm;
|
||||
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;
|
||||
use Peidikeji\Merchant\Enums\CheckStatus;
|
||||
use Peidikeji\Merchant\Models\Merchant;
|
||||
use Peidikeji\Setting\Models\Setting;
|
||||
|
||||
class GoodsController extends AdminController
|
||||
{
|
||||
protected $translation = 'dcat-admin-goods::goods';
|
||||
|
||||
public function list(Request $request)
|
||||
{
|
||||
$query = Goods::filter($request->all());
|
||||
|
||||
$query->select(['id', 'name as text']);
|
||||
|
||||
if ($request->filled('_paginate') || $request->filled('amp;_paginate')) {
|
||||
$list = $query->paginate();
|
||||
} else {
|
||||
$list = $query->get();
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function attr($goods, Content $content)
|
||||
{
|
||||
$goods = Goods::with(['type'])->findOrFail($goods);
|
||||
$form = AttrForm::make([
|
||||
'type' => $goods->type,
|
||||
'attr' => $goods->attr,
|
||||
])->payload(['type_id' => $goods->type?->id, 'goods_id' => $goods->id])->appendHtmlAttribute('class', 'bg-white');
|
||||
|
||||
return $content
|
||||
->translation($this->translation())
|
||||
->title($goods->name)
|
||||
->description($goods->type?->name)
|
||||
->body($form);
|
||||
}
|
||||
|
||||
public function spec($goods, Content $content)
|
||||
{
|
||||
$goods = Goods::findOrFail($goods);
|
||||
$form = SpecForm::make([
|
||||
'type' => $goods->type,
|
||||
'spec' => $goods->spec,
|
||||
])->payload(['type_id' => $goods->type_id, 'goods_id' => $goods->id])->appendHtmlAttribute('class', 'bg-white');
|
||||
|
||||
return $content
|
||||
->translation($this->translation())
|
||||
->title($goods->name)
|
||||
->description($goods->type?->name)
|
||||
->body($form);
|
||||
}
|
||||
|
||||
public function part($goods, Content $content)
|
||||
{
|
||||
$goods = Goods::findOrFail($goods);
|
||||
$form = PartForm::make([
|
||||
'type' => $goods->type,
|
||||
'part' => $goods->part,
|
||||
])->payload(['type_id' => $goods->type_id, 'goods_id' => $goods->id])->appendHtmlAttribute('class', 'bg-white');
|
||||
|
||||
return $content
|
||||
->translation($this->translation())
|
||||
->title($goods->name)
|
||||
->description($goods->type?->name)
|
||||
->body($form);
|
||||
}
|
||||
|
||||
protected function grid()
|
||||
{
|
||||
return Grid::make(Goods::with(['category', 'brand', 'type', 'merchant', 'skus']), function (Grid $grid) {
|
||||
$grid->model()->sort();
|
||||
|
||||
$grid->selector(function (Selector $selector) {
|
||||
$brands = GoodsBrand::get();
|
||||
$types = GoodsType::get();
|
||||
$categories = GoodsCategory::where('level', 3)->where('path', 'like', '-1-%')->get();
|
||||
$prices = ['0-999', '1000-1999', '2000-4999', '5000+'];
|
||||
$merchants = Merchant::checked()->sort()->get();
|
||||
$selector->selectOne('merchant_id', __('dcat-admin-goods::goods.fields.merchant_id'), $merchants->pluck('name', 'id'));
|
||||
$selector->selectOne('category_id', __('dcat-admin-goods::goods.fields.category_id'), $categories->pluck('name', 'id'));
|
||||
$selector->selectOne('brand_id', __('dcat-admin-goods::goods.fields.brand_id'), $brands->pluck('name', 'id'));
|
||||
$selector->selectOne('type_id', __('dcat-admin-goods::goods.fields.type_id'), $types->pluck('name', 'id'));
|
||||
$selector->selectOne('price', __('dcat-admin-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('merchant.name');
|
||||
$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"/> ' : '').'<a href="'.admin_url(
|
||||
'goods/'.$this->id
|
||||
).'">'.$this->name.'</a>';
|
||||
});
|
||||
$grid->column('price');
|
||||
$grid->column('vip_price');
|
||||
$grid->column('stock')
|
||||
->if(fn () => $this->skus->count() > 0)
|
||||
->display(fn () => $this->skus->sum('stock'))
|
||||
->else()
|
||||
->editable();
|
||||
$grid->column('on_sale')->bool();
|
||||
$grid->column('is_recommend')->switch();
|
||||
$grid->column('check_status')->display(fn () => $this->check_status->dot());
|
||||
$grid->column('sold_count');
|
||||
|
||||
$grid->disableRowSelector();
|
||||
$grid->createMode(Grid::CREATE_MODE_DEFAULT);
|
||||
|
||||
$user = Admin::user();
|
||||
|
||||
$grid->showCreateButton($user->can('dcat.admin.goods.create'));
|
||||
|
||||
$grid->actions(function (Actions $actions) use ($user) {
|
||||
$row = $actions->row;
|
||||
|
||||
$actions->view($user->can('dcat.admin.goods.show'));
|
||||
|
||||
if ($user->can('dcat.admin.goods.edit') && ! $row->on_sale && $row->check_status !== CheckStatus::Processing) {
|
||||
$actions->edit();
|
||||
$actions->append('<a href="'.admin_route('goods.attr', ['goods' => $row->id]).'" class="">属性介绍</a>');
|
||||
$actions->append('<a href="'.admin_route('goods.spec', ['goods' => $row->id]).'" class="">商品规格</a>');
|
||||
$actions->append('<a href="'.admin_route('goods.part', ['goods' => $row->id]).'" class="">商品配件</a>');
|
||||
}
|
||||
|
||||
if ($row->spec) {
|
||||
$actions->append('<a href="'.admin_route('goods_sku.index', ['goods' => $row->id]).'" class="">货品列表</a>');
|
||||
}
|
||||
|
||||
if ($user->can('dcat.admin.goods.edit') && $row->check_status === CheckStatus::Success) {
|
||||
$actions->append(new RowGoodsSale());
|
||||
}
|
||||
|
||||
$actions->delete($user->can('dcat.admin.goods.destroy') && ! $row->on_sale);
|
||||
|
||||
if (! $row->on_sale && $row->check_status !== CheckStatus::Success && $row->check_status !== CheckStatus::Processing) {
|
||||
$actions->append(new RowSubmitCheck());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected function detail($id)
|
||||
{
|
||||
Admin::css([
|
||||
'vendor/dcat-admin-goods/goods.css',
|
||||
]);
|
||||
$info = Goods::with(['category', 'merchant', 'brand', 'type', 'checkUser'])->findOrFail($id);
|
||||
$show = Show::make($info);
|
||||
$show->field('merchant.name');
|
||||
$show->field('goods_sn');
|
||||
$show->field('category.name');
|
||||
$show->field('brand.name');
|
||||
$show->field('type.name');
|
||||
$show->field('name');
|
||||
$show->field('price');
|
||||
$show->field('vip_price');
|
||||
$show->field('score_max_amount');
|
||||
$show->field('cover_image')->image('', 100);
|
||||
$show->field('images')->image('', 100);
|
||||
$show->field('content')->image('');
|
||||
$show->field('spec')->view('dcat-admin-goods::goods.grid-attr');
|
||||
$show->field('attr')->view('dcat-admin-goods::goods.grid-attr');
|
||||
$show->field('part')->view('dcat-admin-goods::goods.grid-attr');
|
||||
$show->field('on_sale')->bool();
|
||||
$show->field('is_recommend')->bool();
|
||||
$show->field('sold_count');
|
||||
$show->field('check_status')->unescape()->as(fn () => $this->check_status->label());
|
||||
$show->field('check_at');
|
||||
$show->field('check_remarks');
|
||||
$show->field('check_user.name');
|
||||
$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;
|
||||
}
|
||||
|
||||
protected function form()
|
||||
{
|
||||
return Form::make(Goods::with(['merchant']), function (Form $form) {
|
||||
$model = $form->model();
|
||||
$isCreating = $form->isCreating();
|
||||
$unique = Rule::unique('goods', 'goods_sn');
|
||||
|
||||
if ($isCreating) {
|
||||
// $form->select('type_id')->options(GoodsType::pluck('name', 'id'));
|
||||
$form->select('merchant_id')->ajax('api/merchants?_paginate=1');
|
||||
} else {
|
||||
// $type = $model->type_id ? GoodsType::find($model->type_id) : null;
|
||||
// $form->display('type_id')->with(fn () => $model->type_id ? $type->name : '');
|
||||
$form->display('merchant.name', __('dcat-admin-goods::goods.fields.merchant_id'));
|
||||
$unique->ignore($model->id);
|
||||
}
|
||||
$form->select('category_id')->options(GoodsCategory::selectOptions(null, false))->required();
|
||||
// $form->select('brand_id')->options(GoodsBrand::pluck('name', 'id'));
|
||||
$form->text('name')->required();
|
||||
$form->text('goods_sn')->rules([$unique], [
|
||||
'unique' => '商品编号已经存在',
|
||||
]);
|
||||
$form->image('cover_image')
|
||||
->autoUpload()
|
||||
->saveFullUrl()
|
||||
->move('goods/cover-image')
|
||||
->required();
|
||||
$form->multipleImage('images')
|
||||
->autoUpload()
|
||||
->saveFullUrl()
|
||||
->move('goods/images');
|
||||
$form->multipleImage('content')
|
||||
->autoUpload()
|
||||
->saveFullUrl()
|
||||
->move('goods/content');
|
||||
|
||||
if ($isCreating || !$model->spec) {
|
||||
$form->number('price')->min(0)->attribute('step', 0.01);
|
||||
$form->number('vip_price')->min(0)->attribute('step', 0.01);
|
||||
|
||||
$discountRatio = Setting::where('slug', 'discount_profit_ratio')->value('value');
|
||||
$form->number('score_max_amount')->min(0)->help('购买商品时, 允许使用多少积分, 积分抵扣比例: 1 积分 = '.$discountRatio.' 元');
|
||||
|
||||
$form->currency('weight')->default(0)->symbol('克');
|
||||
$form->currency('volume')->default(0)->symbol('立方');
|
||||
|
||||
$form->select('shipping_tmp_id')->options(ShippingTmp::all()->pluck('name', 'id'))->help('运费模板,不选择默认免邮');
|
||||
} else {
|
||||
$form->display('help', '提示')->value('商品其他信息, 请到 <a href="'.admin_route('goods_sku.index', ['goods' => $model->id]).'" target="_blank">货品列表<a/> 去修改');
|
||||
}
|
||||
|
||||
$form->hidden('stock')->default(0);
|
||||
$form->hidden('is_recommend')->default(0);
|
||||
$form->disableResetButton();
|
||||
$form->disableCreatingCheck();
|
||||
$form->disableViewCheck();
|
||||
$form->disableEditingCheck();
|
||||
|
||||
$form->creating(function (Form $form) {
|
||||
if (! $form->goods_sn) {
|
||||
$form->goods_sn = GoodsService::make()->generateSn();
|
||||
}
|
||||
});
|
||||
|
||||
$form->deleting(function (Form $form) {
|
||||
$data = $form->model()->toArray();
|
||||
// 删除 SKU
|
||||
GoodsSku::whereIn('goods_id', array_column($data, 'id'))->delete();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Admin;
|
||||
|
||||
use App\Models\ShippingTmp;
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Form;
|
||||
use Dcat\Admin\Form\EmbeddedForm;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Grid\Tools\Selector;
|
||||
use Dcat\Admin\Layout\Content;
|
||||
use Dcat\Admin\Show;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Peidikeji\Goods\GoodsService;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
use Peidikeji\Goods\Models\GoodsSku;
|
||||
use Peidikeji\Setting\Models\Setting;
|
||||
|
||||
class GoodsSkuController extends Controller
|
||||
{
|
||||
protected $translation = 'dcat-admin-goods::goods-sku';
|
||||
|
||||
public function index($goods, Content $content)
|
||||
{
|
||||
$goods = Goods::findOrFail($goods);
|
||||
$grid = Grid::make(new GoodsSku(), function (Grid $grid) use ($goods) {
|
||||
$grid->model()->where('goods_id', $goods->id);
|
||||
|
||||
$grid->selector(function (Selector $selector) use ($goods) {
|
||||
$specs = $goods->spec;
|
||||
if ($specs) {
|
||||
foreach ($specs as $key => $item) {
|
||||
$values = array_column($item['values'], 'name');
|
||||
$selector->selectOne('spec_'.$key, $item['name'], array_column($item['values'], 'name'), function ($q, $value) use ($values, $item) {
|
||||
$selected = array_values(Arr::only($values, $value));
|
||||
if (count($selected) > 0) {
|
||||
$q->jsonArray([['name' => $item['name'], 'value' => $selected[0]]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$grid->column('id');
|
||||
$grid->column('sn');
|
||||
$grid->column('name');
|
||||
// $grid->column('reset')->display(fn() => $goods->price);
|
||||
$grid->column('price');
|
||||
$grid->column('stock');
|
||||
if ($goods->spec) {
|
||||
foreach ($goods->spec as $key => $item) {
|
||||
$grid->column('spec_'.$key, $item['name'])->display(function () use ($item) {
|
||||
$filtered = current(array_filter($this->spec, fn ($subItem) => $subItem['name'] === $item['name']));
|
||||
$value = data_get($filtered, 'value');
|
||||
$price = data_get($filtered, 'price');
|
||||
|
||||
return '<span class="label bg-info">'.$value.'</span>';
|
||||
});
|
||||
}
|
||||
}
|
||||
// $grid->column('spec')->view('dcat-admin-goods::grid.attr');
|
||||
|
||||
$user = Admin::user();
|
||||
$grid->showCreateButton($user->can('dcat.admin.goods_sku.create'));
|
||||
$grid->showDeleteButton($user->can('dcat.admin.goods_sku.destroy'));
|
||||
$grid->showEditButton($user->can('dcat.admin.goods_sku.edit'));
|
||||
});
|
||||
|
||||
return $content
|
||||
->translation($this->translation)
|
||||
->title($goods->name)
|
||||
->description(admin_trans_label())
|
||||
->body($grid);
|
||||
}
|
||||
|
||||
public function show($goods, $id, Content $content)
|
||||
{
|
||||
$goods = Goods::findOrFail($goods);
|
||||
$info = GoodsSku::findOrFail($id);
|
||||
$show = Show::make($info, function (Show $show) {
|
||||
$show->field('sn');
|
||||
$show->field('name');
|
||||
$show->field('price');
|
||||
$show->field('vip_price');
|
||||
$show->field('stock');
|
||||
$show->field('spec')->view('dcat-admin-goods::grid.spec');
|
||||
});
|
||||
|
||||
return $content
|
||||
->translation($this->translation)
|
||||
->title(admin_trans_label())
|
||||
->description(trans('admin.show'))
|
||||
->body($show);
|
||||
}
|
||||
|
||||
protected function editForm($goods)
|
||||
{
|
||||
return Form::make(new GoodsSku(), function (Form $form) use ($goods) {
|
||||
$unqiue = Rule::unique('goods_sku', 'sn')->ignore($form->model()->id);
|
||||
$form->text('sn')->rules([$unqiue], ['unique' => '货号已经存在'])->required();
|
||||
$form->text('name')->default($goods->name);
|
||||
$form->number('price')->min(0)->attribute('step', 0.01)->default($goods->price);
|
||||
$form->number('vip_price')->min(0)->attribute('step', 0.01)->default($goods->vip_price);
|
||||
$discountRatio = Setting::where('slug', 'discount_profit_ratio')->value('value');
|
||||
$form->number('score_max_amount')->min(0)->help('购买商品时, 允许使用多少积分, 积分抵扣比例: 1 积分 = '.$discountRatio.' 元');
|
||||
|
||||
$form->currency('weight')->default(0)->symbol('克')->saving(fn ($v) => ! empty($v) ?? null);
|
||||
$form->currency('volume')->default(0)->symbol('立方')->saving(fn ($v) => ! empty($v) ?? null);
|
||||
$form->select('shipping_tmp_id')->options(ShippingTmp::all()->pluck('name', 'id'))->help('运费模板,不选择默认免邮');
|
||||
|
||||
$form->number('stock')->min(0)->default($goods->stock);
|
||||
$form->hidden('spec')->customFormat(fn ($v) => json_encode($v));
|
||||
$form->hidden('goods_id')->default($goods->id);
|
||||
|
||||
$spec = $form->model()->spec;
|
||||
if ($goods->spec) {
|
||||
foreach ($goods->spec as $item) {
|
||||
$values = array_column($item['values'], 'name', 'name');
|
||||
$value = null;
|
||||
if ($spec) {
|
||||
$filtered = current(array_filter($spec, fn ($subItem) => $subItem['name'] === $item['name']));
|
||||
$value = array_search($filtered['value'], $values);
|
||||
}
|
||||
$form->radio($item['name'], $item['name'])->options($values)->value($value);
|
||||
}
|
||||
}
|
||||
|
||||
$form->saving(function (Form $form) use ($goods) {
|
||||
$info = $form->model();
|
||||
$spec = [];
|
||||
if ($goods->spec) {
|
||||
foreach ($goods->spec as $item) {
|
||||
array_push($spec, ['name' => $item['name'], 'value' => $form->input($item['name'])]);
|
||||
$form->deleteInput($item['name']);
|
||||
}
|
||||
}
|
||||
$form->input('spec', $spec);
|
||||
$query = GoodsSku::where('goods_id', $goods->id)->jsonArray($spec);
|
||||
if ($form->isEditing()) {
|
||||
$query->where('id', '!=', $info->id);
|
||||
}
|
||||
if ($query->exists()) {
|
||||
return $form->response()->error('该规格已经存在');
|
||||
}
|
||||
});
|
||||
|
||||
$form->disableCreatingCheck();
|
||||
$form->disableEditingCheck();
|
||||
$form->disableViewCheck();
|
||||
$form->disableResetButton();
|
||||
});
|
||||
}
|
||||
|
||||
public function edit($goods, $id, Content $content)
|
||||
{
|
||||
$goods = Goods::findOrFail($goods);
|
||||
|
||||
return $content
|
||||
->translation($this->translation)
|
||||
->title(admin_trans_label())
|
||||
->description(trans('admin.edit'))
|
||||
->body($this->editForm($goods)->edit($id));
|
||||
}
|
||||
|
||||
public function create($goods, Content $content)
|
||||
{
|
||||
$goods = Goods::findOrFail($goods);
|
||||
|
||||
return $content
|
||||
->translation($this->translation)
|
||||
->title(__('dcat-admin-goods::goods-sku.labels.sku'))
|
||||
->description(__('dcat-admin-goods::goods-sku.labels.create'))
|
||||
->body($this->createForm($goods));
|
||||
}
|
||||
|
||||
protected function createForm($goods)
|
||||
{
|
||||
return Form::make(new GoodsSku(), function (Form $form) use ($goods) {
|
||||
$form->text('name')->default($goods->name);
|
||||
if ($goods->spec) {
|
||||
$form->checkbox('name_append', '')->options([1 => '是否在名称上面追加属性值']);
|
||||
}
|
||||
$form->number('price')->min(0)->default($goods->price);
|
||||
if ($goods->spec) {
|
||||
$form->checkbox('price_append', '')->options([1 => '是否在价格上面追加属性的加价'])->default([1]);
|
||||
}
|
||||
|
||||
$discountRatio = Setting::where('slug', 'discount_profit_ratio')->value('value');
|
||||
$form->number('score_max_amount')->min(0)->help('购买商品时, 允许使用多少积分, 积分抵扣比例: 1 积分 = '.$discountRatio.' 元');
|
||||
|
||||
$form->select('shipping_tmp_id')->default($goods->shipping_tmp_id ?? 0)->options(ShippingTmp::all()->pluck('name', 'id'))->help('运费模板,不选择默认免邮');
|
||||
|
||||
$form->number('stock')->min(0)->default($goods->stock);
|
||||
|
||||
if ($goods->spec) {
|
||||
foreach ($goods->spec as $item) {
|
||||
$values = array_column($item['values'], 'name', 'name');
|
||||
$form->checkbox($item['name'], $item['name'])->options($values);
|
||||
// $form->embeds('input_'.$item['name'], '自定义' . $item['name'], function (EmbeddedForm $form) use ($item) {
|
||||
// $form->text('', '属性值')->setElementName('input_'.$item['name'].'_name');
|
||||
// $form->text('', '价格')->setElementName('input_'.$item['name'].'_price')->default(0);
|
||||
// });
|
||||
}
|
||||
|
||||
$form->checkbox('clear', '')->options([1 => '清空现有的货品'])->default(1);
|
||||
}
|
||||
|
||||
$form->disableCreatingCheck();
|
||||
$form->disableEditingCheck();
|
||||
$form->disableViewCheck();
|
||||
$form->disableResetButton();
|
||||
|
||||
$form->saving(function (Form $form) use ($goods) {
|
||||
$price = $form->price;
|
||||
$name = $form->name;
|
||||
$spec = [];
|
||||
if ($goods->spec) {
|
||||
$goodsSpecs = $goods->spec;
|
||||
foreach ($goodsSpecs as &$item) {
|
||||
// 自定义属性
|
||||
$add = ['name' => '', 'value' => ''];
|
||||
if ($form->input('input_'.$item['name'].'_name')) {
|
||||
$add = ['name' => $form->input('input_'.$item['name'].'_name'), 'value' => round($form->input('input_'.$item['name'].'_value'), 2, PHP_ROUND_HALF_DOWN)];
|
||||
}
|
||||
|
||||
$values = $form->input($item['name']);
|
||||
// 过滤空值
|
||||
$values = array_filter($values, fn ($v) => $v);
|
||||
// 补充自定义属性
|
||||
if ($add['name']) {
|
||||
array_push($item['values'], $add);
|
||||
array_push($values, $add['name']);
|
||||
}
|
||||
if (count($values) === 0) {
|
||||
return $form->response()->error('请勾选 '.$item['name']);
|
||||
}
|
||||
array_push($spec, ['name' => $item['name'], 'values' => array_filter($item['values'], fn ($v) => in_array($v['name'], $values))]);
|
||||
$form->deleteInput($item['name']);
|
||||
}
|
||||
$goods->update(['spec' => $goodsSpecs]);
|
||||
}
|
||||
$service = GoodsService::make();
|
||||
if ($form->clear) {
|
||||
$service->clearSku($goods);
|
||||
}
|
||||
$service->generateSku($goods, [
|
||||
'spec' => $spec,
|
||||
'price' => $price,
|
||||
'name' => $name,
|
||||
'name_add' => (bool) data_get($form->name_append, 0),
|
||||
'price_add' => (bool) data_get($form->price_append, 0),
|
||||
'stock' => $form->stock,
|
||||
'shipping_tmp_id' => $form->shipping_tmp_id ?? null,
|
||||
]);
|
||||
|
||||
return $form->response()->success('添加成功')->redirect(admin_route('goods_sku.index', ['goods' => $goods->id]));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public function update($goods, $id)
|
||||
{
|
||||
return $this->editForm(Goods::findOrFail($goods))->update($id);
|
||||
}
|
||||
|
||||
public function store($goods)
|
||||
{
|
||||
return $this->createForm(Goods::findOrFail($goods))->store();
|
||||
}
|
||||
|
||||
public function destroy($goods, $id)
|
||||
{
|
||||
GoodsSku::where('id', $id)->delete();
|
||||
|
||||
return $this->editForm($goods)->response()->success('删除成功');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Admin;
|
||||
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Form;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Grid\Displayers\Modal;
|
||||
use Dcat\Admin\Http\Controllers\AdminController;
|
||||
use Peidikeji\Goods\Form\GoodsType\AttrForm;
|
||||
use Peidikeji\Goods\Form\GoodsType\PartForm;
|
||||
use Peidikeji\Goods\Form\GoodsType\SpecForm;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
use Peidikeji\Goods\Models\GoodsType;
|
||||
|
||||
class GoodsTypeController extends AdminController
|
||||
{
|
||||
protected $translation = 'dcat-admin-goods::goods-type';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Admin::css([
|
||||
'vendor/dcat-admin-goods/goods.css',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function grid()
|
||||
{
|
||||
return Grid::make(new GoodsType(), function (Grid $grid) {
|
||||
$grid->disableRowSelector();
|
||||
$grid->disableViewButton();
|
||||
$grid->disableEditButton();
|
||||
|
||||
$grid->column('name')->editable();
|
||||
$grid->column('attr')->display(fn () => view('dcat-admin-goods::goods-type.grid-attr', ['value' => $this->attr]))->modal(__('dcat-admin-goods::goods-type.fields.attr'), function (Modal $modal) {
|
||||
$modal->icon('');
|
||||
|
||||
return AttrForm::make()->payload(['type_id' => $this->id, 'reset' => false, 'attr' => $this->attr]);
|
||||
});
|
||||
$grid->column('spec')->display(fn () => view('dcat-admin-goods::goods-type.grid-attr', ['value' => $this->spec]))->modal(__('dcat-admin-goods::goods-type.fields.spec'), function (Modal $modal) {
|
||||
$modal->icon('');
|
||||
|
||||
return SpecForm::make()->payload(['type_id' => $this->id, 'reset' => false, 'spec' => $this->spec]);
|
||||
});
|
||||
$grid->column('part')->display(fn () => view('dcat-admin-goods::goods-type.grid-attr', ['value' => $this->part]))->modal(__('dcat-admin-goods::goods-type.fields.part'), function (Modal $modal) {
|
||||
$modal->icon('');
|
||||
|
||||
return PartForm::make()->payload(['type_id' => $this->id, 'reset' => false, 'part' => $this->part]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected function form()
|
||||
{
|
||||
return Form::make(new GoodsType(), function (Form $form) {
|
||||
$form->text('name');
|
||||
|
||||
// $form->textarea('attr');
|
||||
// $form->textarea('spec');
|
||||
// $form->textarea('part');
|
||||
|
||||
$form->disableResetButton();
|
||||
$form->disableCreatingCheck();
|
||||
$form->disableViewCheck();
|
||||
$form->disableEditingCheck();
|
||||
|
||||
$form->deleting(function (Form $form) {
|
||||
$data = $form->model()->toArray();
|
||||
foreach ($data as $item) {
|
||||
$id = data_get($item, 'id');
|
||||
// 下面包含商品, 阻止删除
|
||||
if (Goods::where('type_id', $id)->exists()) {
|
||||
return $form->response()->error('请先删除关联的商品');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Peidikeji\Goods\Http\Resources\GoodsCartResource;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
use Peidikeji\Merchant\Http\Resources\MerchantTinyResource;
|
||||
use Peidikeji\Merchant\Models\Merchant;
|
||||
|
||||
class GoodsCartController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = auth('api')->user();
|
||||
|
||||
$query = $user->carts();
|
||||
|
||||
if ($request->filled('merchant_id')) {
|
||||
$id = $request->input('merchant_id');
|
||||
if ($id) {
|
||||
$query->where('merchant_id', $id);
|
||||
} else {
|
||||
$query->whereNull('merchant_id');
|
||||
}
|
||||
}
|
||||
|
||||
$list = $query->get();
|
||||
|
||||
return $this->json(GoodsCartResource::collection($list));
|
||||
}
|
||||
|
||||
public function groupByMerchant()
|
||||
{
|
||||
$user = auth('api')->user();
|
||||
$list = $user->carts()->get()->groupBy('merchant_id');
|
||||
|
||||
$data = [];
|
||||
foreach ($list as $id => $items) {
|
||||
$subData = GoodsCartResource::collection($items);
|
||||
$merchant = $id ? MerchantTinyResource::make(Merchant::findOrFail($id)) : ['id' => '', 'name' => '自营店铺'];
|
||||
array_push($data, [
|
||||
'merchant' => $merchant,
|
||||
'list' => $subData,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->json($data);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'goods_id' => 'required',
|
||||
]);
|
||||
$goods = Goods::show()->findOrFail($request->input('goods_id'));
|
||||
$useSku = $goods->spec && count($goods->spec) > 0;
|
||||
if ($useSku && ! $request->filled('spec')) {
|
||||
return $this->error('商品属性必选');
|
||||
}
|
||||
|
||||
$amount = $request->input('amount', 1);
|
||||
$user = auth('api')->user();
|
||||
|
||||
$sku = $useSku ? $goods->skus()->jsonArray($request->input('spec'))->first() : null;
|
||||
if ($useSku && ! $sku) {
|
||||
return $this->error('商品Sku 不存在');
|
||||
}
|
||||
|
||||
$info = $user->carts()->where('goods_id', $goods->id)->when($useSku, fn ($q) => $q->where('goods_sku_sn', $sku->sn))->first();
|
||||
if ($info) {
|
||||
$info->increment('amount', $amount, [
|
||||
'goods_sku_id' => $sku ? $sku->id : null,
|
||||
]);
|
||||
} else {
|
||||
$info = $user->carts()->create([
|
||||
'goods_id' => $goods->id,
|
||||
'goods_sn' => $goods->goods_sn,
|
||||
'merchant_id' => $goods->merchant_id,
|
||||
'amount' => $amount,
|
||||
'goods_sku_id' => $sku ? $sku->id : null,
|
||||
'goods_sku_sn' => $sku ? $sku->sn : null,
|
||||
'goods_name' => $sku ? $sku->name : $goods->name,
|
||||
'cover_image' => $goods->cover_image,
|
||||
'price' => $sku ? $sku->price : $goods->price,
|
||||
'vip_price' => $sku ? $sku->vip_price : $goods->vip_price,
|
||||
'attr' => $goods->attr,
|
||||
'spec' => $sku ? $sku->spec : null,
|
||||
'part' => $request->input('part'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->json(GoodsCartResource::make($info));
|
||||
}
|
||||
|
||||
public function update($id, Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'amount' => 'required',
|
||||
]);
|
||||
|
||||
$user = auth('api')->user();
|
||||
|
||||
$info = $user->carts()->findOrFail($id);
|
||||
|
||||
$info->update($request->only(['amount']));
|
||||
|
||||
return $this->json(GoodsCartResource::make($info));
|
||||
}
|
||||
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'id' => 'required|array',
|
||||
]);
|
||||
|
||||
$user = auth('api')->user();
|
||||
|
||||
$user->carts()->whereIn('id', $request->input('id'))->delete();
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Peidikeji\Goods\Http\Resources\GoodsCategoryResource;
|
||||
use Peidikeji\Goods\Models\GoodsCategory;
|
||||
|
||||
class GoodsCategoryController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$query = GoodsCategory::with([
|
||||
'children' => function ($q) {
|
||||
$q->show()->sort();
|
||||
},
|
||||
])->filter(['level' => 2])->show()->sort();
|
||||
|
||||
$list = $query->get();
|
||||
|
||||
return $this->json(GoodsCategoryResource::collection($list));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Peidikeji\Goods\Http\Resources\GoodsResource;
|
||||
use Peidikeji\Goods\Http\Resources\GoodsSkuResource;
|
||||
use Peidikeji\Goods\Http\Resources\GoodsTinyResource;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
|
||||
class GoodsController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Goods::with(['merchant'])->filter($request->all());
|
||||
|
||||
$list = $query->show()->sort()->paginate($request->input('per_page'));
|
||||
|
||||
return $this->json(GoodsTinyResource::collection($list));
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$info = Goods::with(['merchant', 'skus'])->show()->findOrFail($id);
|
||||
|
||||
return $this->json(GoodsResource::make($info));
|
||||
}
|
||||
|
||||
public function skus($id, Request $request)
|
||||
{
|
||||
$info = Goods::show()->findOrFail($id);
|
||||
|
||||
$query = $info->skus();
|
||||
|
||||
if ($request->filled('spec')) {
|
||||
$spec = explode(',', $request->input('spec'));
|
||||
$spec = array_map(function ($item) {
|
||||
$ex = explode(':', $item);
|
||||
|
||||
return ['name' => data_get($ex, 0), 'value' => data_get($ex, 1)];
|
||||
}, $spec);
|
||||
$query->jsonArray($spec);
|
||||
}
|
||||
|
||||
$list = $query->get();
|
||||
|
||||
return $this->json(GoodsSkuResource::collection($list));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Peidikeji\Merchant\Http\Resources\MerchantTinyResource;
|
||||
|
||||
class GoodsCartResource extends JsonResource
|
||||
{
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'user_id' => $this->user_id,
|
||||
|
||||
'merchant_id' => $this->merchant_id,
|
||||
'mercahnt' => MerchantTinyResource::make($this->whenLoaded('merchant')),
|
||||
|
||||
'goods_id' => $this->goods_id,
|
||||
'goods_sn' => $this->goods_sn,
|
||||
'goods' => GoodsTinyResource::make($this->whenLoaded('goods')),
|
||||
|
||||
'goods_sku_sn' => $this->goods_sku_sn,
|
||||
'goods_sku_id' => $this->goods_sku_id,
|
||||
'goods_sku' => GoodsSkuResource::make($this->whenLoaded('goodsSku')),
|
||||
|
||||
'goods_name' => $this->goods_name,
|
||||
'cover_image' => $this->cover_image,
|
||||
'attr' => $this->attr,
|
||||
'spec' => $this->spec,
|
||||
'part' => $this->part,
|
||||
|
||||
'price' => floatval($this->price),
|
||||
'vip_price' => floatval($this->vip_price),
|
||||
'amount' => $this->amount,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class GoodsCategoryResource extends JsonResource
|
||||
{
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'image' => $this->image,
|
||||
'parent_id' => $this->parent_id,
|
||||
'level' => $this->level,
|
||||
'sort' => $this->sort,
|
||||
'path' => $this->path,
|
||||
|
||||
'children' => static::collection($this->whenLoaded('children')),
|
||||
|
||||
'type' => Str::startsWith($this->path, '-2-') ? 'shop' : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Resources;
|
||||
|
||||
class GoodsResource extends GoodsTinyResource
|
||||
{
|
||||
public function toArray($request)
|
||||
{
|
||||
$data = parent::toArray($request);
|
||||
|
||||
return array_merge($data, [
|
||||
'content' => $this->content,
|
||||
'weight' => $this->weight,
|
||||
'volume' => $this->volume,
|
||||
'shipping_tmp_id' => $this->shipping_tmp_id,
|
||||
'attr' => $this->attr,
|
||||
'part' => $this->part,
|
||||
'spec' => $this->spec,
|
||||
'created_at' => $this->created_at->timestamp,
|
||||
'check_user_id' => $this->check_user_id,
|
||||
'check_at' => $this->check_at?->timestamp,
|
||||
'check_remarks' => $this->check_remarks,
|
||||
'check_status' => $this->check_status,
|
||||
'check_status_text' => $this->check_status->text(),
|
||||
|
||||
'skus' => GoodsSkuResource::collection($this->whenLoaded('skus')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class GoodsSkuResource extends JsonResource
|
||||
{
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'sn' => $this->sn,
|
||||
'goods_id' => $this->goods_id,
|
||||
'name' => $this->name,
|
||||
'price' => $this->price,
|
||||
'vip_price' => $this->vip_price,
|
||||
'stock' => $this->stock,
|
||||
'spec' => $this->spec,
|
||||
'weight' => $this->weight,
|
||||
'volume' => $this->volume,
|
||||
'shipping_tmp_id' => $this->shipping_tmp_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Peidikeji\Merchant\Http\Resources\MerchantTinyResource;
|
||||
|
||||
class GoodsTinyResource extends JsonResource
|
||||
{
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'merchant_id' => $this->merchant_id,
|
||||
'merchant' => MerchantTinyResource::make($this->whenLoaded('merchant')),
|
||||
'category_id' => $this->category_id,
|
||||
'type_id' => $this->category_id,
|
||||
'brand_id' => $this->brand_id,
|
||||
'goods_sn' => $this->goods_sn,
|
||||
'name' => $this->name,
|
||||
'cover_image' => $this->cover_image,
|
||||
'images' => $this->images,
|
||||
'description' => $this->description,
|
||||
'price' => floatval($this->price),
|
||||
'vip_price' => floatval($this->vip_price),
|
||||
'on_sale' => $this->on_sale,
|
||||
'sold_count' => $this->sold_count,
|
||||
'stock' => $this->stock,
|
||||
'created_at' => $this->created_at->timestamp,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Models;
|
||||
|
||||
use Dcat\Admin\Models\Administrator;
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
use EloquentFilter\Filterable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Peidikeji\Goods\Filters\GoodsFilter;
|
||||
use Peidikeji\Merchant\Enums\CheckStatus;
|
||||
use Peidikeji\Merchant\Models\Merchant;
|
||||
|
||||
class Goods extends Model
|
||||
{
|
||||
use Filterable, HasDateTimeFormatter;
|
||||
|
||||
protected $table = 'goods';
|
||||
|
||||
protected $fillable = [
|
||||
'merchant_id', 'category_id', 'type_id', 'brand_id',
|
||||
'goods_sn', 'name', 'cover_image', 'description', 'content',
|
||||
'price', 'vip_price', 'score_max_amount', 'on_sale', 'is_recommend', 'sold_count', 'stock',
|
||||
'weight', 'volume', 'shipping_tmp_id',
|
||||
'attr', 'part', 'spec',
|
||||
'check_user_id', 'check_at', 'check_remarks', 'check_status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'attr' => 'array',
|
||||
'spec' => 'array',
|
||||
'part' => 'array',
|
||||
'content' => 'array',
|
||||
'images' => 'array',
|
||||
'check_status' => CheckStatus::class,
|
||||
];
|
||||
|
||||
protected $dates = ['check_at'];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
if (! $model->goods_sn) {
|
||||
$model->goods_sn = Str::uuid();
|
||||
}
|
||||
});
|
||||
static::updating(function (Goods $model) {
|
||||
if ($model->isDirty(['merchant_id', 'category_id', 'brand_id', 'goods_sn', 'name', 'cover_image', 'description', 'content', 'price', 'vip_price']) && $model->check_status === CheckStatus::Success) {
|
||||
$model->check_status = CheckStatus::None;
|
||||
$model->check_at = null;
|
||||
$model->check_user_id = null;
|
||||
$model->check_remarks = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function modelFilter()
|
||||
{
|
||||
return GoodsFilter::class;
|
||||
}
|
||||
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(GoodsCategory::class, 'category_id');
|
||||
}
|
||||
|
||||
public function merchant()
|
||||
{
|
||||
return $this->belongsTo(Merchant::class, 'merchant_id');
|
||||
}
|
||||
|
||||
public function type()
|
||||
{
|
||||
return $this->belongsTo(GoodsType::class, 'type_id');
|
||||
}
|
||||
|
||||
public function brand()
|
||||
{
|
||||
return $this->belongsTo(GoodsBrand::class, 'brand_id');
|
||||
}
|
||||
|
||||
public function checkUser()
|
||||
{
|
||||
return $this->belongsTo(Administrator::class, 'check_user_id');
|
||||
}
|
||||
|
||||
public function skus()
|
||||
{
|
||||
return $this->hasMany(GoodsSku::class, 'goods_id');
|
||||
}
|
||||
|
||||
public function checkLogs()
|
||||
{
|
||||
return $this->hasMany(GoodsCheck::class, 'goods_id');
|
||||
}
|
||||
|
||||
public function scopeSort($q)
|
||||
{
|
||||
return $q->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
public function scopeShow($q)
|
||||
{
|
||||
return $q->where('on_sale', 1)->where('check_status', CheckStatus::Success);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?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;
|
||||
|
||||
public function goods()
|
||||
{
|
||||
return $this->hasMany(Goods::class, 'brand_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Peidikeji\Merchant\Models\Merchant;
|
||||
use Peidikeji\User\Models\User;
|
||||
|
||||
class GoodsCart extends Model
|
||||
{
|
||||
protected $table = 'goods_cart';
|
||||
|
||||
protected $fillable = ['amount', 'attr', 'cover_image', 'goods_id', 'merchant_id', 'goods_name', 'goods_sku_id', 'goods_sku_sn', 'goods_sn', 'part', 'price', 'spec', 'user_id', 'vip_price'];
|
||||
|
||||
protected $casts = [
|
||||
'attr' => 'json',
|
||||
'spec' => 'json',
|
||||
'part' => 'json',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function goods()
|
||||
{
|
||||
return $this->belongsTo(Goods::class, 'goods_id');
|
||||
}
|
||||
|
||||
public function merchant()
|
||||
{
|
||||
return $this->belongsTo(Merchant::class, 'merchant_id');
|
||||
}
|
||||
|
||||
public function goodsSku()
|
||||
{
|
||||
return $this->belongsTo(GoodsSku::class, 'goods_sku_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Models;
|
||||
|
||||
use Dcat\Admin\Traits\ModelTree;
|
||||
use EloquentFilter\Filterable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Peidikeji\Goods\Filters\GoodsCategoryFilter;
|
||||
|
||||
class GoodsCategory extends Model
|
||||
{
|
||||
use ModelTree;
|
||||
use Filterable;
|
||||
|
||||
protected $table = 'goods_category';
|
||||
|
||||
protected $fillable = ['name', 'image', 'description', 'parent_id', 'level', 'sort', 'path', 'is_enable'];
|
||||
|
||||
protected $titleColumn = 'name';
|
||||
|
||||
protected $orderColumn = 'sort';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
// 监听 Category 的创建事件,用于初始化 path 和 level 字段值
|
||||
static::creating(function ($category) {
|
||||
// 如果创建的是一个根类目
|
||||
if (! $category->parent_id) {
|
||||
// 将层级设为 1
|
||||
$category->level = 1;
|
||||
// 将 path 设为 -
|
||||
$category->path = '-';
|
||||
} else {
|
||||
// 将层级设为父类目的层级 + 1
|
||||
$category->level = $category->parent->level + 1;
|
||||
// 将 path 值设为父类目的 path 追加父类目 ID 以及最后跟上一个 - 分隔符
|
||||
$category->path = $category->parent->path.$category->parent_id.'-';
|
||||
}
|
||||
});
|
||||
|
||||
static::deleting(function ($category) {
|
||||
// 所有下级分类
|
||||
$ids = GoodsCategory::where('path', 'like', '%-'.$category->id.'-%')->pluck('id');
|
||||
// 检查下级分类是否包含商品
|
||||
if (Goods::whereIn('category_id', array_merge($ids, [$category->id]))->exists()) {
|
||||
// todo 阻止删除该分类
|
||||
}
|
||||
// 删除所有下级分类
|
||||
GoodsCategory::where('path', 'like', '%-'.$category->id.'-%')->delete();
|
||||
});
|
||||
}
|
||||
|
||||
public static function selectOptions(\Closure $closure = null, $rootText = null)
|
||||
{
|
||||
$options = (new static())->withQuery($closure)->buildSelectOptions(static::query()->where('path', 'like', '-1-%')->sort()->get()->toArray(), 1);
|
||||
|
||||
$list = collect($options);
|
||||
|
||||
if ($rootText !== false) {
|
||||
$rootText = $rootText ?: admin_trans_label('root');
|
||||
$list->prepend($rootText, 0);
|
||||
}
|
||||
|
||||
return $list->all();
|
||||
}
|
||||
|
||||
public function modelFilter()
|
||||
{
|
||||
return GoodsCategoryFilter::class;
|
||||
}
|
||||
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id');
|
||||
}
|
||||
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(self::class, 'parent_id');
|
||||
}
|
||||
|
||||
public function goods()
|
||||
{
|
||||
return $this->hasMany(Goods::class, 'category_id');
|
||||
}
|
||||
|
||||
public function scopeSort($q)
|
||||
{
|
||||
return $q->orderBy('sort');
|
||||
}
|
||||
|
||||
public function scopeShow($q)
|
||||
{
|
||||
return $q->where('is_enable', 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Models;
|
||||
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Peidikeji\Merchant\Enums\CheckStatus;
|
||||
use Peidikeji\Merchant\Models\Merchant;
|
||||
|
||||
class GoodsCheck extends Model
|
||||
{
|
||||
use HasDateTimeFormatter;
|
||||
|
||||
protected $fillable = [
|
||||
'goods_id',
|
||||
'merchant_id', 'category_id', 'type_id', 'brand_id',
|
||||
'goods_sn', 'name', 'cover_image', 'description', 'content',
|
||||
'price', 'vip_price', 'score_discount_amount', 'on_sale', 'sold_count', 'stock',
|
||||
'attr', 'part', 'spec',
|
||||
'check_status', 'check_remarks', 'check_at', 'check_user_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'attr' => 'array',
|
||||
'spec' => 'array',
|
||||
'part' => 'array',
|
||||
'content' => 'array',
|
||||
'images' => 'array',
|
||||
'check_status' => CheckStatus::class,
|
||||
];
|
||||
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(GoodsCategory::class, 'category_id');
|
||||
}
|
||||
|
||||
public function merchant()
|
||||
{
|
||||
return $this->belongsTo(Merchant::class, 'merchant_id');
|
||||
}
|
||||
|
||||
public function type()
|
||||
{
|
||||
return $this->belongsTo(GoodsType::class, 'type_id');
|
||||
}
|
||||
|
||||
public function brand()
|
||||
{
|
||||
return $this->belongsTo(GoodsBrand::class, 'brand_id');
|
||||
}
|
||||
|
||||
public function goods()
|
||||
{
|
||||
return $this->belongsTo(Goods::class, 'goods_id');
|
||||
}
|
||||
|
||||
public function scopeSort($q)
|
||||
{
|
||||
return $q->orderBy('check_status', 'asc')->orderBy('created_at', 'desc');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
class GoodsSku extends Model
|
||||
{
|
||||
protected $table = 'goods_sku';
|
||||
|
||||
protected $fillable = ['sn', 'goods_id', 'name', 'price', 'vip_price', 'stock', 'spec', 'weight', 'volume', 'shipping_tmp_id'];
|
||||
|
||||
protected $casts = [
|
||||
'spec' => 'array',
|
||||
];
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
public function goods()
|
||||
{
|
||||
return $this->belongsTo(Goods::class, 'goods_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mysql Json 查询
|
||||
* 数据库存储格式: [{"name": "颜色", "price": 0, "value": "白色"}, {"name": "内存", "price": 0, "value": "32G"}]
|
||||
*
|
||||
* @param Builder $q
|
||||
* @param array $params [{"name": "颜色", "value": "白色"}, {"name": "内存", "value": "32G"}]
|
||||
*/
|
||||
public function scopeJsonArray($q, $params)
|
||||
{
|
||||
foreach ($params as $item) {
|
||||
foreach ($item as $key => $value) {
|
||||
$value = is_string($value) ? '"'.$value.'"' : $value;
|
||||
$q->whereRaw('json_contains(spec->>"$[*].'.$key."\", '".$value."')");
|
||||
}
|
||||
}
|
||||
|
||||
return $q;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\Goods\Renderable;
|
||||
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Grid\LazyRenderable;
|
||||
use Peidikeji\Goods\Models\Goods;
|
||||
|
||||
class GoodsTable extends LazyRenderable
|
||||
{
|
||||
protected $translation = 'dcat-admin-goods::goods';
|
||||
|
||||
public function grid(): Grid
|
||||
{
|
||||
return Grid::make(Goods::with(['skus']), function (Grid $grid) {
|
||||
$grid->model()->sort()->show();
|
||||
|
||||
$grid->column('name')->display(function () {
|
||||
return ($this->cover_image ? '<img src="'.$this->cover_image.'" width="60" class="img-thumbnail"/> ' : '').'<a href="'.admin_url(
|
||||
'goods/'.$this->id
|
||||
).'">'.$this->name.'</a>';
|
||||
});
|
||||
$grid->column('price');
|
||||
$grid->column('vip_price');
|
||||
$grid->column('stock')
|
||||
->if(fn () => $this->skus->count() > 0)
|
||||
->display(fn () => $this->skus->sum('stock'))
|
||||
->else()
|
||||
->editable();
|
||||
$grid->column('sold_count');
|
||||
|
||||
$grid->disableActions();
|
||||
$grid->showRowSelector();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
<div class="table-responsive p-1" id="spec-{{$name}}">
|
||||
<input type="hidden" name="{{$name}}" value="{{ json_encode($value, JSON_UNESCAPED_UNICODE) }}">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
@foreach($headers as $item)
|
||||
<td>{{ $item }}</td>
|
||||
@endforeach
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($value as $index => $item)
|
||||
<tr data-id="{{ $item['name'] }}">
|
||||
<td rowspan="{{count($item['values']) + 2}}" class="editable">{{ $item['name'] }}</td>
|
||||
</tr>
|
||||
@foreach($item['values'] as $subItem)
|
||||
<tr data-pid="{{ $item['name'] }}">
|
||||
<td class="editable">{{ $subItem }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger delete-item-button">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
<tr data-pid="{{$item['name']}}">
|
||||
<td>
|
||||
<input type="text" class="form-control add-item-input" placeholder="填写 {{ $headers[$index] }}">
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary add-item-button">
|
||||
<i class="fa fa-plus"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
<tr>
|
||||
<td colspan="{{ count($headers) }}">
|
||||
<input type="text" class="form-control add-attr-input" placeholder="添加 {{ $headers[0] }}">
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-warning add-attr-button">
|
||||
<i class="fa fa-plus"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<style>
|
||||
.table {
|
||||
text-align: center;
|
||||
}
|
||||
.table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.editable {
|
||||
color: #586cb1;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var type = JSON.parse('{!! json_encode($type) !!}')
|
||||
var keys = JSON.parse('{!! json_encode($keys) !!}')
|
||||
var headers = JSON.parse('{!! json_encode($headers) !!}')
|
||||
var element = $('#spec-{{$name}}')
|
||||
|
||||
// 添加父级属性
|
||||
element.on('click', '.add-attr-button', function () {
|
||||
var value = $('.add-attr-input').val()
|
||||
if (!value) {
|
||||
return Dcat.swal.warning(`请填写 ${headers.name}`)
|
||||
}
|
||||
|
||||
addGroup(value)
|
||||
|
||||
$('.add-attr-input').val('')
|
||||
updateInputValue()
|
||||
})
|
||||
// 添加子属性
|
||||
.on('click', '.add-item-button', function () {
|
||||
var tr = $(this).parents('tr')
|
||||
var pid = tr.data('pid')
|
||||
var values = {}
|
||||
var inputs = tr.find('input.add-item-input')
|
||||
for(let i = 0; i < keys.length; i++) {
|
||||
let item = inputs.eq(i)[0]
|
||||
let key = keys[i]
|
||||
values[key] = inputs.eq(i).val()
|
||||
}
|
||||
addItem(pid, values)
|
||||
// 清空输入框
|
||||
inputs.each((key, item) => {
|
||||
item.value = ''
|
||||
})
|
||||
|
||||
updateInputValue()
|
||||
})
|
||||
// 删除子属性
|
||||
.on('click', '.delete-item-button', function () {
|
||||
var tr = $(this).parents('tr')
|
||||
var pid = tr.data('pid')
|
||||
var parent = $('tr[data-id="'+pid+'"]')
|
||||
var parentTd = parent.find('td').first()
|
||||
parentTd.attr('rowspan', parseInt(parentTd.attr('rowspan')) - 1)
|
||||
tr.remove()
|
||||
|
||||
// 子属性全部删除, 删除父级属性
|
||||
if (element.find(`tr[data-pid="${pid}"]`).length <= 1) {
|
||||
element.find(`tr[data-pid="${pid}"]`).remove()
|
||||
element.find(`tr[data-id="${pid}"]`).remove()
|
||||
}
|
||||
|
||||
updateInputValue()
|
||||
})
|
||||
// 修改
|
||||
.on('click', '.editable', function () {
|
||||
var td = $(this)
|
||||
var tr = td.parents('tr')
|
||||
var value = td.html()
|
||||
Dcat.swal.fire({
|
||||
input: 'text',
|
||||
inputValue: value,
|
||||
showCancelButton: true,
|
||||
cancelButtonText: '取消',
|
||||
confirmButtonText: '确定',
|
||||
}).then(result => {
|
||||
if (result.value !== undefined && result.value) {
|
||||
// 修改父级属性
|
||||
if (tr.attr('data-id')) {
|
||||
var pid = tr.attr('data-id')
|
||||
$('tr[data-pid="'+pid+'"]').attr('data-pid', result.value)
|
||||
tr.attr('data-id', result.value)
|
||||
}
|
||||
td.html(result.value)
|
||||
|
||||
updateInputValue()
|
||||
}
|
||||
})
|
||||
})
|
||||
// 重新生成
|
||||
.on('click', '.generate-button', function () {
|
||||
var trs = $(this).parents('table').find('tr[data-id]')
|
||||
for (let i = 0; i < type.length; i++) {
|
||||
let item = type[i]
|
||||
|
||||
// 生成父级属性
|
||||
if (element.find(`tr[data-id="${item.name}"]`).length === 0) {
|
||||
addGroup(item.name)
|
||||
}
|
||||
// 生成子级属性
|
||||
for (let k = 0; k < item.values.length; k++) {
|
||||
let subItem = item.values[k]
|
||||
let baseKey = keys[0]
|
||||
if (element.find(`tr[data-pid="${item.name}"]`).find(`td[data-name="${subItem[baseKey]}"]`).length == 0) {
|
||||
let values = {}
|
||||
for(let j = 0; j < keys.length; j++) {
|
||||
let key = keys[j]
|
||||
values[key] = subItem[key] ?? ''
|
||||
}
|
||||
addItem(item.name, values)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
updateInputValue()
|
||||
})
|
||||
|
||||
function addGroup(id) {
|
||||
var tr = element.find('.add-attr-button').parents('tr')
|
||||
var html = `<tr data-id="${id}">`
|
||||
html += `<td rowspan="${keys.length}" class="editable">${id}</td>`
|
||||
html += '</tr>'
|
||||
html += `<tr data-pid="${id}">`
|
||||
for (let i = 1; i < headers.length; i++) {
|
||||
html += `<td><input type="text" class="form-control add-item-input" placeholder="填写 ${headers[i]}"></td>`
|
||||
}
|
||||
html += '<td><button type="button" class="btn btn-sm btn-outline-primary add-item-button"><i class="fa fa-plus"></i></button></td>'
|
||||
html += '</tr>'
|
||||
tr.before(html)
|
||||
}
|
||||
|
||||
function addItem(id, values) {
|
||||
var tr = element.find(`tr[data-id="${id}"]`)
|
||||
|
||||
// 构造 html
|
||||
var html = `<tr data-pid="${id}">`;
|
||||
Object.keys(values).forEach(key => {
|
||||
let value = values[key]
|
||||
html += `<td class="editable" data-${key}="${value}">${value}</td>`
|
||||
})
|
||||
for(let i = 0; i < values.length; i++) {
|
||||
html += `<td class="editable">${values[i]}</td>`
|
||||
}
|
||||
html += '<td><button type="button" class="btn btn-sm btn-outline-danger delete-item-button"><i class="fa fa-trash"></i></button></td></tr>'
|
||||
|
||||
// 修改 rowspan
|
||||
var parentTd = tr.find('td').first()
|
||||
parentTd.attr('rowspan', parseInt(parentTd.attr('rowspan')) + 1)
|
||||
|
||||
// 追加 html
|
||||
$(`tr[data-pid="${id}"]:last`).before(html)
|
||||
}
|
||||
|
||||
// 整合表格里面的值
|
||||
function formatValue() {
|
||||
var values = []
|
||||
var tr = element.find('tr[data-id]')
|
||||
for (let i = 0; i < tr.length; i++) {
|
||||
var item = tr.eq(i)
|
||||
var id = item.data('id')
|
||||
var subTr = element.find(`[data-pid="${id}"]:not(:last)`)
|
||||
var subValues = []
|
||||
subTr.each((key, ele) => {
|
||||
subValues.push($(ele).find('td:first-child').html())
|
||||
})
|
||||
values.push({name: id, values: subValues})
|
||||
}
|
||||
console.log(values)
|
||||
return values
|
||||
}
|
||||
|
||||
function updateInputValue() {
|
||||
element.find('input[name="{{$name}}"]').val(JSON.stringify(formatValue()))
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
<div class="table-responsive p-1" id="spec-{{$name}}">
|
||||
<input type="hidden" name="{{$name}}" value="{{ json_encode($value, JSON_UNESCAPED_UNICODE) }}">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
@foreach($headers as $item)
|
||||
<td>{{ $item }}</td>
|
||||
@endforeach
|
||||
<td>
|
||||
@if($type)
|
||||
<button type="button" class="btn btn-sm btn-outline-primary generate-button">
|
||||
<i class="fa fa-plus-square"></i>
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($value?:[] as $item)
|
||||
<tr data-id="{{ $item['name'] }}">
|
||||
<td rowspan="{{count($item['values']) + 2}}" class="editable">{{ $item['name'] }}</td>
|
||||
</tr>
|
||||
@foreach($item['values'] as $subItem)
|
||||
<tr data-pid="{{ $item['name'] }}">
|
||||
<td class="editable" data-name="{{ $subItem['name'] }}">{{ $subItem['name'] }}</td>
|
||||
<td class="editable" data-value="{{ $subItem['value'] }}">{{ $subItem['value'] }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger delete-item-button">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
<tr data-pid="{{$item['name']}}">
|
||||
<td>
|
||||
<input type="text" class="form-control add-item-input" placeholder="填写 ">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="form-control add-item-input" placeholder="填写 ">
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary add-item-button">
|
||||
<i class="fa fa-plus"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
<tr>
|
||||
<td colspan="{{ count($headers) }}">
|
||||
<input type="text" class="form-control add-attr-input" placeholder="添加 {{ $headers[0] }}">
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-warning add-attr-button">
|
||||
<i class="fa fa-plus"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<style>
|
||||
.table {
|
||||
text-align: center;
|
||||
}
|
||||
.table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.editable {
|
||||
color: #586cb1;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var type = JSON.parse('{!! json_encode($type) !!}')
|
||||
var headers = JSON.parse('{!! json_encode($headers) !!}')
|
||||
var element = $('#spec-{{$name}}')
|
||||
|
||||
// 添加父级属性
|
||||
element.on('click', '.add-attr-button', function () {
|
||||
var value = $('.add-attr-input').val()
|
||||
if (!value) {
|
||||
return Dcat.swal.warning(`请填写 ${headers.name}`)
|
||||
}
|
||||
|
||||
addGroup(value)
|
||||
|
||||
$('.add-attr-input').val('')
|
||||
updateInputValue()
|
||||
})
|
||||
// 添加子属性
|
||||
.on('click', '.add-item-button', function () {
|
||||
var tr = $(this).parents('tr')
|
||||
var pid = tr.data('pid')
|
||||
var inputs = tr.find('.add-item-input')
|
||||
var values = {
|
||||
name: inputs.eq(0).val(),
|
||||
value: inputs.eq(1).val(),
|
||||
}
|
||||
addItem(pid, values)
|
||||
// 清空输入框
|
||||
inputs.each((key, item) => {
|
||||
item.value = ''
|
||||
})
|
||||
|
||||
updateInputValue()
|
||||
})
|
||||
// 删除子属性
|
||||
.on('click', '.delete-item-button', function () {
|
||||
var tr = $(this).parents('tr')
|
||||
var pid = tr.data('pid')
|
||||
var parent = $('tr[data-id="'+pid+'"]')
|
||||
var parentTd = parent.find('td').first()
|
||||
parentTd.attr('rowspan', parseInt(parentTd.attr('rowspan')) - 1)
|
||||
tr.remove()
|
||||
|
||||
// 子属性全部删除, 删除父级属性
|
||||
if (element.find(`tr[data-pid="${pid}"]`).length <= 1) {
|
||||
element.find(`tr[data-pid="${pid}"]`).remove()
|
||||
element.find(`tr[data-id="${pid}"]`).remove()
|
||||
}
|
||||
|
||||
updateInputValue()
|
||||
})
|
||||
// 修改
|
||||
.on('click', '.editable', function () {
|
||||
var td = $(this)
|
||||
var tr = td.parents('tr')
|
||||
var value = td.html()
|
||||
Dcat.swal.fire({
|
||||
input: 'text',
|
||||
inputValue: value,
|
||||
showCancelButton: true,
|
||||
cancelButtonText: '取消',
|
||||
confirmButtonText: '确定',
|
||||
}).then(result => {
|
||||
if (result.value !== undefined && result.value) {
|
||||
// 修改父级属性
|
||||
if (tr.attr('data-id')) {
|
||||
var pid = tr.attr('data-id')
|
||||
$('tr[data-pid="'+pid+'"]').attr('data-pid', result.value)
|
||||
tr.attr('data-id', result.value)
|
||||
}
|
||||
td.html(result.value)
|
||||
|
||||
updateInputValue()
|
||||
}
|
||||
})
|
||||
})
|
||||
// 重新生成
|
||||
.on('click', '.generate-button', function () {
|
||||
var trs = $(this).parents('table').find('tr[data-id]')
|
||||
for (let i = 0; i < type.length; i++) {
|
||||
let item = type[i]
|
||||
|
||||
// 生成父级属性
|
||||
if (element.find(`tr[data-id="${item.name}"]`).length === 0) {
|
||||
addGroup(item.name)
|
||||
}
|
||||
// 生成子级属性
|
||||
for (let k = 0; k < item.values.length; k++) {
|
||||
let subItem = item.values[k]
|
||||
if (element.find(`tr[data-pid="${item.name}"]`).find(`td[data-name="${subItem}"]`).length == 0) {
|
||||
addItem(item.name, {name: subItem, value: ''})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
updateInputValue()
|
||||
})
|
||||
|
||||
function addGroup(id) {
|
||||
var tr = element.find('.add-attr-button').parents('tr')
|
||||
var html = `<tr data-id="${id}">`
|
||||
html += `<td rowspan="2" class="editable">${id}</td>`
|
||||
html += '</tr>'
|
||||
html += `<tr data-pid="${id}">`
|
||||
for (let i = 1; i < headers.length; i++) {
|
||||
html += `<td><input type="text" class="form-control add-item-input" placeholder="填写 ${headers[i]}"></td>`
|
||||
}
|
||||
html += '<td><button type="button" class="btn btn-sm btn-outline-primary add-item-button"><i class="fa fa-plus"></i></button></td>'
|
||||
html += '</tr>'
|
||||
tr.before(html)
|
||||
}
|
||||
|
||||
function addItem(id, values) {
|
||||
var tr = element.find(`tr[data-id="${id}"]`)
|
||||
|
||||
// 构造 html
|
||||
var html = `<tr data-pid="${id}">`;
|
||||
html += `<td class="editable" data-name="${values.name}">${values.name}</td>`
|
||||
html += `<td class="editable" data-value="${values.value}">${values.value}</td>`
|
||||
for(let i = 0; i < values.length; i++) {
|
||||
html += `<td class="editable">${values[i]}</td>`
|
||||
}
|
||||
html += '<td><button type="button" class="btn btn-sm btn-outline-danger delete-item-button"><i class="fa fa-trash"></i></button></td></tr>'
|
||||
|
||||
// 修改 rowspan
|
||||
var parentTd = tr.find('td').first()
|
||||
parentTd.attr('rowspan', parseInt(parentTd.attr('rowspan')) + 1)
|
||||
|
||||
// 追加 html
|
||||
$(`tr[data-pid="${id}"]:last`).before(html)
|
||||
}
|
||||
|
||||
// 整合表格里面的值
|
||||
function formatValue() {
|
||||
var values = []
|
||||
var tr = element.find('tr[data-id]')
|
||||
for (let i = 0; i < tr.length; i++) {
|
||||
var item = tr.eq(i)
|
||||
var id = item.data('id')
|
||||
var subTr = element.find(`[data-pid="${id}"]:not(:last)`)
|
||||
var subValues = []
|
||||
subTr.each((key, item) => {
|
||||
subValues.push({name: $(item).find('td').eq(0).html(), value: $(item).find('td').eq(1).html()})
|
||||
})
|
||||
values.push({name: id, values: subValues})
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
function updateInputValue() {
|
||||
element.find('input[name="{{$name}}"]').val(JSON.stringify(formatValue()))
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<!-- $model 当前行数据 -->
|
||||
<!-- $name 字段名称 -->
|
||||
<!-- $value 为当前列的值 -->
|
||||
@if($value)
|
||||
<div class="grid-attr">
|
||||
@foreach($value as $item)
|
||||
<div class="group">
|
||||
<div class="group-item group-title">
|
||||
<span class="label bg-danger">{{ $item['name'] }}</span>
|
||||
</div>
|
||||
<div class="group-item">
|
||||
@foreach($item['values'] as $subItem)
|
||||
<div class="group-value">
|
||||
<span class="label bg-info">{{ $subItem }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<!-- $model 当前行数据 -->
|
||||
<!-- $name 字段名称 -->
|
||||
<!-- $value 为当前列的值 -->
|
||||
@if($value)
|
||||
<div class="grid-attr">
|
||||
@foreach($value as $item)
|
||||
<div class="group">
|
||||
<div class="group-item group-title">
|
||||
<span class="label bg-danger">{{ $item['name'] }}</span>
|
||||
</div>
|
||||
<div class="group-item">
|
||||
@foreach($item['values'] as $subItem)
|
||||
<div class="group-value">
|
||||
<span class="label bg-info">{{ $subItem['name'] }}</span>
|
||||
<span class="text-danger">{{ $subItem['value'] }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
Loading…
Reference in New Issue