我们需要先执行数据库迁移
然后会在数据库创建出对应的表
然后我们需要创建出对应的模型跟数据填充文件 我们可以看一分类表的数据
平时的分类基本都市这样的
水果(0)
|--外国水果(1)
| |--栗子(2)
|
|--国产水果(1)
|--栗子(2)
注意如果我们使用上面那种数据表的结构的话会存在这问题。如果数据量很多的时候就会出现。
1. 场景一:从上级开始查找它所有的子级,这个时候就需要使用递归的算法去查询所有的子级内容,同时会产生很多的SQL查询,从而影响性能。
2. 场景二:从最小的子级开始查找所有的上级,这个时候同样的也是递归的方式查找,同样也有很多的查询SQL
3. 场景三:这个情景可能会在多级分销中出现,就是判断哪个内容是否存在上下级的关系
应对与这个三个问题的解决可以通过新增一个字段possess来记录一个内容中的所有上级id,可以通过任意分割符号进行分 比如在本次项目中可以使用 ‘-’
php artisan make:migration add_path_to_goods_categories_table --table=goods_categories
加了这个字段之后对于上面的三个问题的解决
1. 从上往下查找的时候字只需要取出那个字段的内容 ,然后where ‘possess’ like ‘-1-%’ 即可
2.从下往上查找的时候只需要获取那个字段,然后同样的字符串处理就好了
3.判断是否存在关系,先获取level最大的那个信息中的possess的值。然后再获取了一个possess同时在这个值上跟上这条数据的id-。 然后判断第一个值的内容是否以第二个值的内容开头如果是就存在关系,否则相反
修改模型
定义了一个关联关系,在category定义一个与上级以及子级的模型关联。然后添加三个获取器;1.获取所有的上级id,2.根据获取的id获取子级的所有上级,并且根据level排序。 至于之后一个只是获取所有的上级名称
源码:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* 商品分类
*/
class GoodsCategory extends Model
{
protected $fillable = ['name', 'category_image'];
public function parent()
{
//反向关联
return $this->belongsTo(GoodsCategory::class);
}
public function children() {
//一对多
return $this->hasMany(GoodsCategory::class, 'parent_id');
}
//定义一个访问器,获取所有祖先类目的ID值
public function getPossessIdsAttribute()
{
//array_filter 将数组中的空值移除
return array_filter(explode('-', trim($this->possess, '-')));
}
//定义一个访问器,获取祖先类目并按层级排序
public function getAncestorsAttribute()
{
return GoodsCategory::query()
->whereIn('id', $this->possess_ids)
//按层级排序
->orderBy('level')->get();
}
//定义一个访问器,获取以 - 为分隔的所有祖先类目的名称以及当前类目的名称
public function getFullNameAttribute()
{
return $this->ancestors //获取所有祖先类
->pluck('name') //获取祖先类目的name 字段为一个数组
->push($this->name)//获取当前类目的 name 字段加到数组的末尾
->implode(' - '); //用 - 符合将数组的值组成一个字符串
}
// public function getLevelAttribute($value) {
// $data = [
// '0' => '根目录',
// '1' => '二级',
// '2' => '三级',
// ];
// return (is_null($value)) ? $data : $data[$value];
// }
/**
* 测试方法
* @return [type] [description]
*/
public function test() {
$category = GoodsCategory::where('id', 10)->first();
$data = $category->ancestors->toArray();
return $data;
}
}
然后添加一个观察者针对于数据新增的时候操作
php artisan make:observer GoodsCategoryObserver --model=GoodsCategory
在里面主要是判断,是否存在上级的;如果存在上级就操作一下;设计一下possess与level,这里的方法一定要注意,不了解的去看文档,否则你会进入无限的报错中
然后注册观察者
php artisan make:provider ModelObserverServiceProvider
我们可以看一下文档怎么进行注册
然后把ModelObserverProvider注册到服务里边
最后就是数据填充
创建GoodsCategoryTableSeeder数据填充文件
php artisan make:seeder GoodsCategoryTableSeeder
源码:
<?php
use App\Models\GoodsCategory;
use Illuminate\Database\Seeder;
class GoodsCategoryTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$categories = [
[
'name' => '手机配件',
'sort' => '0',
'children' => [
[
'name' => '手机壳',
'sort' => '0',
'children' => [
[
'name' => '华为V10手机',
'sort' => '0',
],
[
'name' => '小米',
'sort' => '1',
],
],
],
[
'name' => '数据线',
'sort' => '4',
'children' => [
[
'name' => '苹果数据线',
'sort' => '0',
],
[
'name' => '安卓数据线',
'sort' => '1',
],
],
],
[
'name' => '耳机',
'sort' => '0',
'children' => [
[
'name' => '有线耳机',
'sort' => '1',
],
[
'name' => '蓝牙耳机',
'sort' => '0',
],
],
],
],
],
[
'name' => '六星果园',
'sort' => '0',
'children' => [
[
'name' => '国产水果',
'sort' => '0',
'children' => [
[
'name' => '苹果',
'sort' => '0',
],
[
'name' => '梨',
'sort' => '1',
],
],
],
]
]
];
foreach ($categories as $data) {
$this->createCategory($data);
}
}
public function createCategory($data, $parent = null)
{
// 创建一个分类
$category = new GoodsCategory([
'name' => $data['name'],
'sort' => $data['sort'],
]);
// 如果有父级参数,代表有父类目
if (!is_null($parent)) {
// 将模型实例与给定的父实例关联。
$category->parent()->associate($parent);
}
// 保存到数据库
$category->save();
// 如果有children字段并且 children字段是一个数组
if (isset($data['children']) && is_array($data['children'])) {
foreach ($data['children'] as $child) {
$this->createCategory($child, $category);
}
}
}
}
然后执行这个填充数据
php artisan db:seed --class=GoodsCategoryTableSeeder
到这里我们先不着急,我们引入一个单元测试类
控制器:
<?php
namespace App\Http\Controllers\Test;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class TestController extends Controller
{
//
public function create() {
return view('test.create');
}
public function store(Request $request) {
$namespace = $request->input('namespace');
$className = $request->input('className');
$action = $request->input('action');
$param = $request->input('param');
$class = ($className == "") ? $namespace : $namespace.'\\'.$className;
$class = str_replace('/' , '\\', $class);
$object = new $class();
$param = ($param == "")? [] : explode("|",$param);
$data = call_user_func_array([$object, $action], $param);
return (is_array($data)) ? json_encode($data) : dd($data);
}
}
视图:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>不区分大小写(可以自行完善)</h1>
<br>
<form class="" action="{{route('test.store')}}" method="post">
@csrf()
命名空间:<input type="text" value='' style="width:300px" name='namespace' placeholder="如:app\index\controller 或app\index\controller\Index">可以写全,然后下面类名不用些<br>
类名:<input type="text" name='className' placeholder="如:index ">命名空间全可以不用写<br>
测试方法名:<input type="text" name='action' placeholder="index"><br>
传递参数以 | 分割:<input type="text" placeholder="如: 1|2|3" name='param'><br>
<input type="submit" name="" value="测试"/>
</form>
</body>
然后在 GoodsCategory里边添加一个测试方法
然后咱们访问一下单元测试方法
然后给后台创建一个商品管理的菜单
创建一个GoodsCategoryController
php artisan admin:make GoodsCategoryController --model=App\Models\GoodsCategory
给创建的GoodsCategoryController设置一个路由
这个时候直接访问即可,这就是我们最终的商品分类显示