feat: 新增产品属性增/删/改/查接口

This commit is contained in:
2025-02-12 14:33:39 +08:00
parent 50603510d1
commit def30f7c5e
7 changed files with 314 additions and 1 deletions

View File

@@ -0,0 +1,191 @@
<?php
declare (strict_types = 1);
namespace app\admin\controller\v1;
use app\admin\model\v1\ProductAttrModel;
use app\admin\model\v1\ProductAttrPropModel;
use app\admin\validate\v1\ProductAttrValidate;
class ProductAttr
{
/**
* 属性列表
*/
public function index()
{
$params = request()->param([
'keywords' => '',
'page/d' => 1,
'size/d' => 10
]);
$attrs = ProductAttrModel::withoutField([
'language_id',
'created_at',
'updated_at',
'deleted_at',
])
->with(['props' => function ($query) {
$query->withoutField(['created_at'])->hidden(['attr_id']);
}])
->language(request()->lang_id)
->withSearch(['attr_name_nullable'], [
'attr_name_nullable' => $params['keywords']??null,
])
->paginate([
'list_rows' => $params['size'],
'page' => $params['page'],
]);
return success("获取成功", $attrs);
}
/**
* 属性详情
*/
public function read()
{
$id = request()->param('id');
$attr = ProductAttrModel::withoutField([
'language_id',
'created_at',
'updated_at',
'deleted_at',
])
->with(['props' => function ($query) {
$query->withoutField(['created_at'])->hidden(['attr_id']);
}])
->find($id);
if (empty($attr)) {
return error("获取失败");
}
return success("获取成功", $attr);
}
/**
* 属性新增
*/
public function save()
{
$post = request()->post([
'attr_name' => '',
'is_system' => 0,
]);
$attr = array_merge($post, ['language_id' => request()->lang_id]);
// $[*].prop_type
// $[*].prop_name
// $[*].prop_value
$props = json_decode(request()->post('props/s', ''), true);
// 检验参数
$validate = new ProductAttrValidate;
$check_data = array_merge($attr, ['props' => $props]);
if (!$validate->check($check_data)) {
return error($validate->getError());
}
ProductAttrModel::startTrans();
try {
// 添加属性
$attr_ret = ProductAttrModel::create($attr);
if ($attr_ret->isEmpty()) {
throw new \Exception("操作失败");
}
// 添加属性特征
foreach ($props as &$prop) {
$prop['attr_id'] = $attr_ret->id;
}
unset($prop);
$props_ret = (new ProductAttrPropModel)->saveAll($props);
if ($props_ret->isEmpty()) {
throw new \Exception("操作失败");
}
ProductAttrModel::commit();
} catch (\Throwable $th) {
ProductAttrModel::rollback();
return error($th->getMessage());
}
return success("操作成功");
}
/**
* 属性更新
*/
public function update()
{
$id = request()->param('id');
$put = request()->put([
'attr_name' => '',
'is_system' => 0,
]);
$attr = array_merge($put, ['language_id' => request()->lang_id]);
// $[*].prop_type
// $[*].prop_name
// $[*].prop_value
$props = json_decode(request()->post('props/s', ''), true);
// 检验参数
$validate = new ProductAttrValidate;
$check_data = array_merge($attr, ['id' => $id], ['props' => $props]);
if (!$validate->check($check_data)) {
return error($validate->getError());
}
ProductAttrModel::startTrans();
try {
// 更新属性
$attr_ret = ProductAttrModel::bypk($id)->find();
if ($attr_ret->isEmpty()) {
throw new \Exception("请确认操作对象是否正确");
}
if (!$attr_ret->save($attr)) {
throw new \Exception("操作失败");
}
// 删除旧属性特征
ProductAttrPropModel::attrId($attr_ret->id)->delete();
// 保存新属性特征
foreach ($props as &$prop) {
$prop['attr_id'] = $attr_ret->id;
}
unset($prop);
$props_ret = (new ProductAttrPropModel)->saveAll($props);
if ($props_ret->isEmpty()) {
throw new \Exception("操作失败");
}
ProductAttrModel::commit();
} catch (\Throwable $th) {
ProductAttrModel::rollback();
return error($th->getMessage());
}
return success("操作成功");
}
/**
* 属性删除
*/
public function delete()
{
$id = request()->param('id');
// 删除属性
$attr = ProductAttrModel::bypk($id)->find();
if (empty($attr)) {
return error("请确认操作对象是否正确");
}
if (!$attr->delete()) {
return error("操作失败");
}
return success("操作成功");
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare (strict_types = 1);
namespace app\admin\model\v1;
use app\common\model\ProductAttrBaseModel;
use think\model\concern\SoftDelete;
/**
* 产品 - 产品属性模型
* @mixin \think\Model
*/
class ProductAttrModel extends ProductAttrBaseModel
{
// 启用软删除
use SoftDelete;
// 软删除字段
protected $deleteTime = 'deleted_at';
// 修改自动写入时间格式
protected $autoWriteTimestamp = 'datetime';
// 关联属性特征
public function props()
{
return $this->hasMany(ProductAttrPropModel::class, 'attr_id', 'id');
}
// 所属语言查询
public function scopeLanguage($query, $value)
{
$query->where('language_id', '=', $value);
}
// 属性名称模糊搜索
public function searchAttrNameNullableAttr($query, $value, $data)
{
$query->where('attr_name', 'like', "%{$value}%");
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare (strict_types = 1);
namespace app\admin\model\v1;
use app\common\model\ProductAttrPropBaseModel;
/**
* 产品 - 产品属性特征模型
* @mixin \think\Model
*/
class ProductAttrPropModel extends ProductAttrPropBaseModel
{
// 所属属性查询
public function scopeAttrId($query, $value)
{
$query->where('attr_id', '=', $value);
}
}

View File

@@ -136,6 +136,24 @@ Route::group('v1', function () {
Route::delete('delete/:id', 'ProductCategory/delete');
});
// 产品属性
Route::group('attr', function () {
// 属性列表
Route::get('index', 'ProductAttr/index');
// 属性详情
Route::get('read/:id', 'ProductAttr/read');
// 属性新增
Route::post('save', 'ProductAttr/save');
// 属性更新
Route::put('update/:id', 'ProductAttr/update');
// 属性删除
Route::delete('delete/:id', 'ProductAttr/delete');
});
// 产品分页列表
Route::get('index', 'Product/index');

View File

@@ -0,0 +1,43 @@
<?php
declare (strict_types = 1);
namespace app\admin\validate\v1;
use think\Validate;
class ProductAttrValidate extends Validate
{
/**
* 定义验证规则
* 格式:'字段名' => ['规则1','规则2'...]
*
* @var array
*/
protected $rule = [
'language_id' => 'require|number',
'attr_name' => 'require|max:64',
'is_system' => 'in:0,1',
'props.*.prop_type' => 'in:1,2',
'props.*.prop_name' => 'require|max:64',
'props.*.prop_value' => 'require|max:64',
];
/**
* 定义错误信息
* 格式:'字段名.规则名' => '错误信息'
*
* @var array
*/
protected $message = [
'language_id.require' => '语言ID不能为空',
'language_id.number' => '语言ID必须是数字',
'attr_name.require' => '属性名称不能为空',
'attr_name.max' => '属性名称不能超过64个字符',
'is_system.in' => '是否系统属性只能是0或1',
'props.*.prop_type.in' => '属性特征类型只能是1或2',
'props.*.prop_name.require' => '属性特征名称不能为空',
'props.*.prop_name.max' => '属性特征名称不能超过64个字符',
'props.*.prop_value.require' => '属性特征值不能为空',
'props.*.prop_value.max' => '属性特征值不能超过64个字符',
];
}

View File

@@ -17,7 +17,8 @@ class ProductAttrPropBaseModel extends BaseModel
// 字段信息
protected $schema = [
'id' => 'int',
'attr_id' => 'int',
'prop_type' => 'int',
'prop_name' => 'string',
'prop_value' => 'string',
'created_at' => 'datetime',

View File

@@ -1,5 +1,6 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
use think\migration\Migrator;
class CreateProductAttrProp extends Migrator
@@ -29,6 +30,7 @@ class CreateProductAttrProp extends Migrator
{
$table = $this->table('product_attr_prop', ['id' => false, 'engine' => 'InnoDB', 'comment' => '商品属性特征表']);
$table->addColumn('attr_id', 'integer', ['signed' => false, 'null' => false, 'comment' => '属性ID'])
->addColumn('prop_type', MysqlAdapter::INT_TINY, ['limit' => 3, 'null' => false, 'default' => 2, 'comment' => '类型:1为选项类型2为文本输入类型'])
->addColumn('prop_name', 'string', ['limit' => 64, 'null' => false, 'comment' => '特征名'])
->addColumn('prop_value', 'string', ['limit' => 64, 'null' => false, 'comment' => '特征值'])
->addColumn('created_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP', 'comment' => '新增时间'])