4
0
Fork 0

删除文件 packages/goods

master
panliang 2022-08-17 08:31:12 +00:00 committed by Gitee
parent 57ae81b328
commit 718965740e
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
45 changed files with 0 additions and 2405 deletions

View File

@ -1,7 +0,0 @@
.DS_Store
phpunit.phar
/vendor
composer.phar
composer.lock
*.project
.idea/

View File

@ -1,9 +0,0 @@
# Dcat Admin Extension Goods
Dcat-admin 基础商品管理
## 安装
- 先安装 [dcat-admin](git@gitee.com:paddy_technology/dcat-admin.git)
- `composer config repositories.peidikeji/dcat-admin-extension-goods git git@gitee.com:paddy_technology/dcat-admin-extension-goods.git`
- `composer require peidikeji/dcat-admin-extension-goods`

View File

@ -1,31 +0,0 @@
{
"name": "peidikeji/dcat-admin-extension-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": ">=7.1.0"
},
"autoload": {
"psr-4": {
"Peidikeji\\Goods\\": "src/"
}
},
"extra": {
"dcat-admin": "Peidikeji\\Goods\\GoodsServiceProvider",
"laravel": {
"providers": [
"Peidikeji\\Goods\\GoodsServiceProvider"
]
}
}
}

View File

@ -1,74 +0,0 @@
[
{
"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 }
]
}
]

View File

@ -1,12 +0,0 @@
.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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +0,0 @@
<?php
return [
'labels' => [
'GoodsBrand' => '品牌管理',
'goods' => '商品管理',
'brand' => '品牌',
'create' => '创建',
'edit' => '修改',
],
'fields' => [
'name' => '名称',
'image' => '图片',
]
];

View File

@ -1,21 +0,0 @@
<?php
return [
'labels' => [
'GoodsCategory' => '商品分类',
'goods' => '商品管理',
'category' => '分类管理',
'root' => '无',
],
'fields' => [
'name' => '分类名称',
'image' => '图片',
'description' => '描述',
'sort' => '排序(正序)',
'parent_id' => '上级',
'is_enable' => '开启',
'parent' => [
'name' => '上级'
]
]
];

View File

@ -1,19 +0,0 @@
<?php
return [
'labels' => [
'GoodsSku' => '货品管理',
'goods' => '商品管理',
'sku' => '货品管理',
'create' => '添加',
],
'fields' => [
'sn' => '货号',
'name' => '名称',
'price' => '售价',
'stock' => '库存',
'spec' => '规格',
'origin_price' => '原价',
'discount_price' => '折扣价',
]
];

View File

@ -1,17 +0,0 @@
<?php
return [
'labels' => [
'GoodsType' => '商品类别',
'goods' => '商品管理',
'type' => '商品类别',
],
'fields' => [
'name' => '名称',
'spec' => '规格',
'attr' => '属性',
'part' => '配件',
'values' => '可选值',
'group' => '分组',
]
];

View File

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

View File

@ -1,227 +0,0 @@
<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>

View File

@ -1,225 +0,0 @@
<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($formatValue 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: item.name, value: subItem})
}
}
}
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>

View File

@ -1,21 +0,0 @@
<!-- $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

View File

@ -1,22 +0,0 @@
<!-- $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

View File

@ -1,10 +0,0 @@
<?php
namespace Peidikeji\Goods\Filters;
use EloquentFilter\ModelFilter;
class GoodsFilter extends ModelFilter
{
}

View File

@ -1,42 +0,0 @@
<?php
namespace Peidikeji\Goods\Form;
use Dcat\Admin\Form\Field;
use Illuminate\Support\Arr;
class Attr extends Field
{
protected $view = 'peidikeji.dcat-admin-extension-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;
}
}

View File

@ -1,32 +0,0 @@
<?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>";
}
}

View File

@ -1,42 +0,0 @@
<?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 '保存';
}
}

View File

@ -1,42 +0,0 @@
<?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 '保存';
}
}

View File

@ -1,36 +0,0 @@
<?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(!!$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>" : '';
}
}

View File

@ -1,36 +0,0 @@
<?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(!!$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>" : '';
}
}

View File

@ -1,36 +0,0 @@
<?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(!!$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>" : '';
}
}

View File

@ -1,60 +0,0 @@
<?php
namespace Peidikeji\Goods\Form;
use Dcat\Admin\Form\Field;
use Illuminate\Support\Arr;
class Spec extends Field
{
protected $view = 'peidikeji.dcat-admin-extension-goods::form.spec';
protected $variables = [
'headers' => [],
'type' => null,
'formatValue' => [],
];
public function header(array $headers)
{
$this->addVariables([
'headers' => $headers,
]);
return $this;
}
public function type($type)
{
$this->addVariables(['type' => $type]);
return $this;
}
public function render()
{
$value = $this->value;
$type = $this->variables['type'];
$newValue = [];
if ($type && !$value) {
foreach($type as $item) {
$itemValue = $value ? Arr::first($value, fn($i) => $i['name'] === $item['name']) : [];
$values = data_get($itemValue, 'values', []);
foreach($values as &$items) {
$subItem = Arr::first($item['values'], fn($i) => $i['name'] === $items['name']);
$items['options'] = data_get($subItem, 'values');
}
array_push($newValue, [
'name' => $item['name'],
'values' => $values
]);
}
} else {
$newValue = $value ?: [];
}
$this->addVariables([
'formatValue' => $newValue
]);
return parent::render();
}
}

View File

@ -1,127 +0,0 @@
<?php
namespace Peidikeji\Goods;
use Peidikeji\Goods\Models\{Goods, GoodsSku};
use Illuminate\Support\Str;
class GoodsService
{
public static function make(...$params)
{
return new static(...$params);
}
public function generateSn()
{
return (string)Str::uuid();
}
public function clearSku(Goods $goods)
{
GoodsSku::where('goods_id', $goods->id)->delete();
}
/**
* 根据规格生成SKU
*
* @param Goods $goods 商品
* @param array $options {spec: 指定规格, price: 基础价格, name: 基础名称, stock: 默认库存, name_add: 是否在名称上面追加属性值, price_add: 是否在价格上面追加属性的加价, force: 是否覆盖原有的货品}
*/
public function generateSku(Goods $goods, $options = [])
{
$spec = data_get($options, 'spec', $goods->spec);
$price = data_get($options, 'price', $goods->price);
$discountPrice = data_get($options, 'discount_price', $goods->discount_price);
$name = data_get($options, 'name', $goods->name);
$stock = data_get($options, 'stock', $goods->stock);
$nameAdd = !!data_get($options, 'name_add', false);
$priceAdd = !!data_get($options, 'price_add', false);
$force = !!data_get($options, 'force', 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 = $this->cartesianProduct($specList);
foreach($cartesianList as $items) {
$specPrice = $priceAdd ? $price + array_sum(array_column($items, 'price')) : $price;
$specDiscountPrice = $priceAdd ? $discountPrice + array_sum(array_column($items, 'price')) : $discountPrice;
$specName = $nameAdd ? $name . ' ' . implode(' ', array_column($items, 'value')) : $name;
$exists = $goods->skus()->jsonArray($items)->exists();
$attributes = [
'name' => $specName,
'price' => $specPrice,
'discount_price' => $specDiscountPrice,
'stock' => $stock,
'spec' => $items,
];
if ($exists) {
if ($force) {
$goods->skus()->jsonArray($items)->update($attributes);
}
} else {
$attributes['sn'] = $this->generateSn();
$goods->skus()->create($attributes);
}
}
}
}
protected function cartesianProduct($sets)
{
// 保存结果
$result = [];
if (count($sets) === 1) {
// 保存临时数据
$tmp = array();
// 结果与下一个集合计算笛卡尔积
foreach($sets[0] as $set) {
$item = [];
if (isset($set['value'])) {
array_push($item, $set);
} else {
$item = $set;
}
array_push($item, $set);
$tmp[] = $item;
}
// 将笛卡尔积写入结果
$result = $tmp;
return $result;
}
// 循环遍历集合数据
for($i = 0; $i < count($sets) -1 ; $i++) {
// 初始化
if($i==0){
$result = $sets[$i];
}
// 保存临时数据
$tmp = array();
// 结果与下一个集合计算笛卡尔积
foreach($result as $res) {
foreach($sets[$i+1] as $set) {
$item = [];
if (isset($res['value'])) {
array_push($item, $res);
} else {
$item = $res;
}
array_push($item, $set);
$tmp[] = $item;
}
}
// 将笛卡尔积写入结果
$result = $tmp;
}
return $result;
}
}

View File

@ -1,32 +0,0 @@
<?php
namespace Peidikeji\Goods;
use Dcat\Admin\Extend\ServiceProvider;
use Dcat\Admin\Admin;
use Dcat\Admin\Form;
use Peidikeji\Goods\Form\Attr;
use Peidikeji\Goods\Form\Spec;
class GoodsServiceProvider extends ServiceProvider
{
protected $css = [
'goods.css'
];
// protected $menu = [
// ['title' => '商品管理', 'uri' => '', 'icon' => ''],
// ['title' => '商品分类', 'uri' => 'goods/category', 'icon' => '', 'parent' => '商品管理'],
// ['title' => '品牌管理', 'uri' => 'goods/brand', 'icon' => '', 'parent' => '商品管理'],
// ['title' => '商品类别', 'uri' => 'goods/type', 'icon' => '', 'parent' => '商品管理'],
// ['title' => '商品信息', 'uri' => 'goods', 'icon' => '', 'parent' => '商品管理'],
// ];
public function init()
{
parent::init();
Form::extend('spec', Spec::class);
Form::extend('attr', Attr::class);
Admin::requireAssets(['@peidikeji.dcat-admin-extension-goods']);
}
}

View File

@ -1,53 +0,0 @@
<?php
namespace Peidikeji\Goods\Http\Controllers\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 = 'peidikeji.dcat-admin-extension-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('请先删除关联的商品');
}
}
});
});
}
}

View File

@ -1,63 +0,0 @@
<?php
namespace Peidikeji\Goods\Http\Controllers\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 = 'peidikeji.dcat-admin-extension-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;
}
}

View File

@ -1,193 +0,0 @@
<?php
namespace Peidikeji\Goods\Http\Controllers\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\Validation\Rule;
use Peidikeji\Goods\Form\Goods\AttrForm;
use Peidikeji\Goods\Form\Goods\PartForm;
use Peidikeji\Goods\Form\Goods\SpecForm;
use Peidikeji\Goods\Models\Goods;
use Peidikeji\Goods\Models\GoodsBrand;
use Peidikeji\Goods\Models\GoodsCategory;
use Peidikeji\Goods\Models\GoodsSku;
use Peidikeji\Goods\Models\GoodsType;
class GoodsController extends AdminController
{
protected $translation = 'peidikeji.dcat-admin-extension-goods::goods';
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']), function (Grid $grid) {
$grid->model()->sort();
$grid->selector(function (Selector $selector) {
$brands = GoodsBrand::get();
$types = GoodsType::get();
$categories = GoodsCategory::where('level', 3)->get();
$prices = ['0-999', '1000-1999', '2000-4999', '5000+'];
$selector->selectOne('category_id', __('peidikeji.dcat-admin-extension-goods::goods.fields.category_id'), $categories->pluck('name', 'id'));
$selector->selectOne('brand_id', __('peidikeji.dcat-admin-extension-goods::goods.fields.brand_id'), $brands->pluck('name', 'id'));
$selector->selectOne('type_id', __('peidikeji.dcat-admin-extension-goods::goods.fields.type_id'), $types->pluck('name', 'id'));
$selector->selectOne('price', __('peidikeji.dcat-admin-extension-goods::goods.fields.price'), $prices, function ($q, $value) use ($prices) {
$parsePrice = data_get($prices, $value);
if ($parsePrice) {
$parts = explode('-', $parsePrice);
$parts = array_map(fn($v) => (int)$v, $parts);
if (count($parts) > 1) {
$q->whereBetween('price', $parts);
} else {
$q->where('price', '>', $parts[0]);
}
}
});
});
$grid->column('goods_sn');
$grid->column('category.name');
$grid->column('brand.name');
$grid->column('type.name')->label();
$grid->column('name')->display(function () {
return ($this->cover_image ? '<img src="'.$this->cover_image.'" width="60" class="img-thumbnail"/>&nbsp;' : '') . '<a href="'.admin_url('goods/' . $this->id).'">'.$this->name.'</a>';
});
$grid->column('price')->editable();
$grid->column('spec')->view('peidikeji.dcat-admin-extension-goods::goods.grid-attr');
$grid->column('on_sale')->switch();
$grid->column('sold_count');
$grid->disableRowSelector();
$grid->actions(function (Actions $actions) {
$row = $actions->row;
$actions->append('<a href="'.admin_route('goods_sku.index', ['goods' => $row->id]).'" class="">货品信息</a>');
$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>');
});
});
}
protected function detail($id)
{
$info = Goods::with(['category', 'brand', 'type'])->findOrFail($id);
$show = Show::make($info);
$show->field('goods_sn');
$show->field('category.name');
$show->field('brand.name');
$show->field('type.name');
$show->field('name');
$show->field('price');
$show->field('discount_price');
$show->field('cover_image')->image('', 100);
$show->field('images')->image('', 100);
$show->field('content')->image('');
$show->field('spec')->view('peidikeji.dcat-admin-extension-goods::goods.grid-attr');
$show->field('attr')->view('peidikeji.dcat-admin-extension-goods::goods.grid-attr');
$show->field('part')->view('peidikeji.dcat-admin-extension-goods::goods.grid-attr');
$show->field('on_sale')->bool();
$show->field('sold_count');
$show->field('created_at')->as(fn($v) => $this->created_at->format('Y-m-d H:i:s'));
$show->field('updated_at')->as(fn($v) => $this->updated_at->format('Y-m-d H:i:s'));
return $show;
}
protected function form()
{
return Form::make(new Goods(), 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'));
} else {
$type = $model->type_id ? GoodsType::find($model->type_id) : null;
$form->display('type_id')->with(fn() => $model->type_id ? $type->name : '');
$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')->required()->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');
$form->number('price')->min(0)->attribute('step', 0.01);
$form->number('discount_price')->min(0)->attribute('step', 0.01);
$form->switch('on_sale');
$form->disableResetButton();
$form->disableCreatingCheck();
$form->disableViewCheck();
$form->disableEditingCheck();
$form->deleting(function (Form $form) {
$data = $form->model()->toArray();
// 删除 SKU
GoodsSku::whereIn('goods_id', array_column($data, 'id'))->delete();
});
});
}
}

View File

@ -1,227 +0,0 @@
<?php
namespace Peidikeji\Goods\Http\Controllers\Admin;
use Dcat\Admin\Form;
use Dcat\Admin\Grid;
use Dcat\Admin\Grid\Tools\Selector;
use Dcat\Admin\Http\Controllers\AdminController;
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;
class GoodsSkuController extends Controller
{
protected $translation = 'peidikeji.dcat-admin-extension-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'], 'value');
$selector->selectOne('spec_' . $key, $item['name'], array_column($item['values'], 'value'), 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('peidikeji.dcat-admin-extension-goods::grid.attr');
});
return $content
->translation($this->translation)
->title(admin_trans_label())
->description(trans('admin.list'))
->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('discount_price');
$show->field('stock');
$show->field('spec')->view('peidikeji.dcat-admin-extension-goods::grid.spec');
});
return $content
->translation($this->translation)
->title(admin_trans_label())
->description(trans('admin.show'))
->body($show);
}
protected function form($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('discount_price')->min(0)->attribute('step', 0.01)->default($goods->discount_price);
$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'], 'value', 'value');
$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->form($goods)->edit($id));
}
public function create($goods, Content $content)
{
$goods = Goods::findOrFail($goods);
return $content
->translation($this->translation)
->title(__('peidikeji.dcat-admin-extension-goods::goods-sku.labels.sku'))
->description(__('peidikeji.dcat-admin-extension-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]);
}
if ($goods->spec) {
foreach($goods->spec as $item) {
$values = array_column($item['values'], 'value', 'value');
$form->checkbox($item['name'], $item['name'])->options($values);
}
$form->checkbox('force', '')->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) {
foreach($goods->spec as $index => $item) {
$values = $form->input($item['name']);
$values = array_filter($values, fn($v) => $v);
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['value'], $values))]);
$form->deleteInput($item['name']);
}
}
GoodsService::make()->generateSku($goods, [
'spec' => $spec,
'price' => $price,
'name' => $name,
'name_add' => !!data_get($form->name_append, 0),
'price_add' => !!data_get($form->price_append, 0),
'force' => !!$form->force
]);
return $form->response()->success('添加成功')->redirect(admin_route('goods_sku.index', ['goods' => $goods->id]));
});
});
}
public function update($goods, $id)
{
return $this->form(Goods::findOrFail($goods))->update($id);
}
public function store($goods)
{
return $this->createForm(Goods::findOrFail($goods))->store();
}
public function destroy($goods, $id)
{
return $this->form(Goods::findOrFail($goods))->destroy($id);
}
}

View File

@ -1,69 +0,0 @@
<?php
namespace Peidikeji\Goods\Http\Controllers\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 = 'peidikeji.dcat-admin-extension-goods::goods-type';
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('peidikeji.dcat-admin-extension-goods::goods-type.grid-attr', ['value' => $this->attr]))->modal(__('peidikeji.dcat-admin-extension-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('peidikeji.dcat-admin-extension-goods::goods-type.grid-attr', ['value' => $this->spec]))->modal(__('peidikeji.dcat-admin-extension-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('peidikeji.dcat-admin-extension-goods::goods-type.grid-attr', ['value' => $this->part]))->modal(__('peidikeji.dcat-admin-extension-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('请先删除关联的商品');
}
}
});
});
}
}

View File

@ -1,16 +0,0 @@
<?php
namespace Peidikeji\Goods\Http\Controllers\Admin;
use Illuminate\Support\Facades\Route;
Route::resource('goods/category', GoodsCategoryController::class);
Route::resource('goods/brand', GoodsBrandController::class);
Route::resource('goods/type', GoodsTypeController::class);
Route::resource('goods/{goods}/sku', GoodsSkuController::class)->names('goods_sku');
Route::get('goods/{goods}/attr', [GoodsController::class, 'attr'])->name('goods.attr');
Route::get('goods/{goods}/spec', [GoodsController::class, 'spec'])->name('goods.spec');
Route::get('goods/{goods}/part', [GoodsController::class, 'part'])->name('goods.part');
Route::resource('goods', GoodsController::class);

View File

@ -1,55 +0,0 @@
<?php
namespace Peidikeji\Goods\Models;
use EloquentFilter\Filterable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Peidikeji\Goods\Filters\GoodsFilter;
class Goods extends Model
{
use Filterable;
protected $table = 'goods';
protected $fillable = ['attr', 'category_id', 'type_id', 'brand_id', 'content', 'cover_image', 'deleted_at', 'description', 'goods_sn', 'images', 'name', 'on_sale', 'part', 'price', 'discount_price', 'sold_count', 'spec', 'stock'];
protected $casts = [
'attr' => 'array',
'spec' => 'array',
'part' => 'array',
'content' => 'array',
'images' => 'array',
];
public function modelFilter()
{
return GoodsFilter::class;
}
public function category()
{
return $this->belongsTo(GoodsCategory::class, 'category_id');
}
public function type()
{
return $this->belongsTo(GoodsType::class, 'type_id');
}
public function brand()
{
return $this->belongsTo(GoodsBrand::class, 'brand_id');
}
public function skus()
{
return $this->hasMany(GoodsSku::class, 'goods_id');
}
public function scopeSort($q)
{
return $q->orderBy('created_at', 'desc');
}
}

View File

@ -1,19 +0,0 @@
<?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');
}
}

View File

@ -1,85 +0,0 @@
<?php
namespace Peidikeji\Goods\Models;
use Dcat\Admin\Traits\ModelTree;
use Illuminate\Database\Eloquent\Model;
class GoodsCategory extends Model
{
use ModelTree;
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();
$list = collect($options);
if ($rootText !== false) {
$rootText = $rootText ?: admin_trans_label('root');
$list->prepend($rootText, 0);
}
return $list->all();
}
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 scopeOrder($q)
{
return $q->orderBy('sort');
}
}

View File

@ -1,42 +0,0 @@
<?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', 'discount_price', 'stock', 'spec'];
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;
}
}

View File

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

View File

@ -1,14 +0,0 @@
<?php
namespace Peidikeji\Goods;
use Dcat\Admin\Extend\Setting as Form;
class Setting extends Form
{
public function form()
{
$this->text('key1')->required();
$this->text('key2')->required();
}
}

View File

@ -1,105 +0,0 @@
<?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()
{
$this->down();
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')->unique()->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('discount_price', 12, 2)->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->timestamps();
$table->foreign('category_id')->references('id')->on('goods_category');
$table->foreign('type_id')->references('id')->on('goods_type');
$table->foreign('brand_id')->references('id')->on('goods_brand');
$table->comment('商品');
});
Schema::create('goods_sku', function (Blueprint $table) {
$table->id();
$table->string('sn')->unique()->comment('货号');
$table->unsignedBigInteger('goods_id')->comment('所属商品, 关联 goods.id');
$table->string('name')->comment('名称');
$table->decimal('price', 12, 2)->comment('价格');
$table->decimal('discount_price', 12, 2)->comment('折扣价');
$table->unsignedInteger('stock')->comment('库存');
$table->json('spec')->nullable()->comment('规格[{name, value, price}]');
$table->foreign('goods_id')->references('id')->on('goods');
$table->comment('商品-SKU');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('goods_sku');
Schema::dropIfExists('goods');
Schema::dropIfExists('goods_category');
Schema::dropIfExists('goods_type');
Schema::dropIfExists('goods_brand');
}
};

View File

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

View File

@ -1,8 +0,0 @@
<?php
return [
'1.0.0' => [
'CreateGoodsTable.php',
'GoodsTableSeeder.php',
],
];