完善文章分类

main
vine_liutk 2024-01-25 16:33:52 +08:00
parent 57568e93ca
commit 3e354ad31f
12 changed files with 402 additions and 6 deletions

View File

@ -0,0 +1,68 @@
<?php
namespace App\Admin\Controllers;
use Slowlyo\OwlAdmin\Renderers\Page;
use Slowlyo\OwlAdmin\Renderers\Form;
use Slowlyo\OwlAdmin\Controllers\AdminController;
use App\Services\Admin\ArticleCategoryService;
use App\Admin\Components;
use Illuminate\Http\Request;
class ArticleCategoryController extends AdminController
{
protected string $serviceName = ArticleCategoryService::class;
public function list(): Page
{
$crud = $this->baseCRUD()
//去掉分页-start
->loadDataOnce(true)
->footerToolbar([])
//去掉分页-end
->headerToolbar([
$this->createButton(true, 'md'),
amis('reload')->align('right'),
amis('filter-toggler')->align('right'),
])
->filter($this->baseFilter()->body([
]
))
->columns([
amis()->TableColumn()->make()->name('id')->label('ID')->sortable(true),
amis()->TableColumn('name', __('admin.article_categories.name')),
amis()->TableColumn('key', __('admin.article_categories.key'))->copyable(true),
// amis()->TableColumn('cover', __('admin.article_categories.cover'))->type('image')->height('50px')->width('150px')->enlargeAble(true),
amis()->TableColumn('is_enable', __('admin.article_categories.is_enable'))->type('switch'),
amis()->TableColumn('is_show', __('admin.article_categories.is_show'))->type('switch'),
amis()->TableColumn('is_recommend', __('admin.article_categories.is_recommend'))->type('switch'),
amis()->TableColumn('sort', __('admin.article_categories.sort')),
amis()->TableColumn('created_at', __('admin.created_at'))->type('datetime')->sortable(true),
amisMake()->Operation()->label(__('admin.actions'))->buttons([
$this->rowEditButton(true, 'md'),
$this->rowDeleteButton(),
]),
]);
return $this->baseList($crud);
}
public function form(): Form
{
return $this->baseForm()->body([
Components::make()->parentControl(admin_url('api/article_categories/tree-list')),
amis()->TextControl('name', __('admin.article_categories.name'))->required(true),
// amis()->TextControl('key', __('admin.article_categories.key'))->required(true),
Components::make()->cropImageControl('cover', __('admin.article_categories.cover')),
Components::make()->sortControl('sort', __('admin.article_categories.sort')),
amis()->SwitchControl('is_enable', __('admin.article_categories.is_enable'))->value(false),
amis()->SwitchControl('is_show', __('admin.article_categories.is_show'))->value(false),
amis()->SwitchControl('is_recommend', __('admin.article_categories.is_recommend'))->value(false),
]);
}
public function getTreeList(Request $request){
return $this->service->getTree();
}
}

View File

@ -59,9 +59,7 @@ class ArticleController extends AdminController
->columns([
amis()->TableColumn('id', __('admin.id'))->sortable(true),
amis()->TableColumn('title', __('admin.articles.title'))->width('300px'),
amis()->TableColumn('category', __('admin.articles.category'))->type('mapping')
->map(Keyword::allChildrenOfKey('article_category')->pluck('name', 'key')->toArray())
->itemSchema(amis()->Tag()->label('${item}')->color(Admin::setting()->get('system_theme_setting')['theme_color'] ?? '#1677ff')),
amis()->TableColumn('cate.name', __('admin.articles.category')),
amis()->TableColumn('tags', __('admin.articles.tags'))->type('mapping')->map(Keyword::tagsMap('article_tag')),
amis()->TableColumn('cover', __('admin.articles.cover'))->type('image')->height('50px')->width('50px')->enlargeAble(true),
amis()->TableColumn('published_at', __('admin.articles.published_at'))->remark(__('admin.articles.published_at_remark')),
@ -83,14 +81,14 @@ class ArticleController extends AdminController
amis()->Grid()->columns([
amis()->Wrapper()->body([
amis()->TextControl('title', __('admin.articles.title'))->required(true),
Components::make()->parentControl(admin_url('api/keywords/tree-list?parent_name=article_category&has_owner=0'), 'category', __('admin.articles.category'), 'name', 'key'),
Components::make()->parentControl(admin_url('api/article_categories/tree-list'), 'category', __('admin.articles.category')),
Components::make()->keywordsTagControl('t_ids', __('admin.articles.tags'), 'article_tag'),
Components::make()->cropImageControl('cover', __('admin.articles.cover')),
Components::make()->sortControl('sort', __('admin.articles.sort')),
amis()->DateTimeControl('published_at', __('admin.articles.published_at'))->format('YYYY-MM-DD HH:mm:ss')->description(__('admin.articles.published_at_remark')),
amis()->SwitchControl('is_enable', __('admin.articles.is_enable'))->value(false),
amis()->SwitchControl('is_recommend', __('admin.articles.is_recommend'))->value(false),
Components::make()->fileControl('appendixes', __('admin.articles.appendixes'), '.xsl,.xlsx,.txt,.doc,.docx,.pdf,.pptx'),
Components::make()->fileControl('appendixes', __('admin.articles.appendixes'), '.xsl,.xlsx,.txt,.doc,.docx,.pdf,.pptx', true),
])->md(4),
amis()->Wrapper()->body([
Components::make()->fuEditorControl('content', __('admin.articles.content')),

View File

@ -14,6 +14,7 @@ Route::group([
], function (Router $router) {
$router->get('keywords/tree-list', '\App\Admin\Controllers\KeywordController@getTreeList')->name('api.keywords.tree-list');
$router->get('institutions/tree-list', '\App\Admin\Controllers\InstitutionController@getTreeList')->name('api.institution.tree-list');
$router->get('article_categories/tree-list', '\App\Admin\Controllers\ArticleCategoryController@getTreeList')->name('api.article_categories.tree-list');
});
$router->resource('index', \App\Admin\Controllers\HomeController::class);
@ -35,6 +36,8 @@ Route::group([
$router->resource('friend_links', \App\Admin\Controllers\FriendLinkController::class);
$router->resource('article_categories', \App\Admin\Controllers\ArticleCategoryController::class);
//数据管理
$router->resource('financial_cate', \App\Admin\Controllers\KeywordController::class);

View File

@ -60,4 +60,9 @@ class Article extends Model
get: fn($value) => $this->t_ids ? explode(',', $this->t_ids) : [],
);
}
public function cate(){
return $this->belongsTo(ArticleCategory::class, 'category');
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use EloquentFilter\Filterable;
class ArticleCategory extends Model
{
use HasFactory;
use Filterable;
protected function serializeDate(\DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
protected $fillable = [
'name', 'key', 'cover',
'parent_id', 'parent_key', 'lv', 'path',
'is_enable', 'is_show', 'is_recommend',
'sort',
];
protected static function boot()
{
parent::boot();
// 监听 Keyword 的创建事件,用于初始化 path 和 lv 字段值
static::saving(function ($keyword) {
// 如果创建的是一个根类目
if (! $keyword->parent_id) {
// 将层级设为 1
$keyword->lv = 1;
// 将 path 设为 -
$keyword->path = '-';
} else {
// 将层级设为父类目的层级 + 1
$keyword->lv = $keyword->parent->lv +1;
$keyword->parent_key = $keyword->parent->key;
// 将 path 值设为父类目的 path 追加父类目 ID 以及最后跟上一个 - 分隔符
$keyword->path = $keyword->parent->path.$keyword->parent_id.'-';
}
});
}
public function parent()
{
return $this->belongsTo(static::class, 'parent_id');
}
public function children()
{
return $this->hasMany(static::class, 'parent_id');
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Models\Filters;
use EloquentFilter\ModelFilter;
use App\Models\ArticleCategory;
class ArticleCategoryFilter extends ModelFilter
{
/**
* 关键字
*/
public function name($name)
{
return $this->where('name','like', '%'.$name.'%')
->orWhere('key','like', '%'.$name.'%');
}
public function parentName($parent_name)
{
if(request('has_owner', 1)){
$this->where(function($q) use ($parent_name){
$q->where('name','like', '%'.$parent_name.'%')
->orWhere('key','like', '%'.$parent_name.'%');
});
}
return $this->orWhere('path','like', '%-'.
ArticleCategory::where('name','like', '%'.$parent_name.'%')->orWhere('key','like', '%'.$parent_name.'%')->value('id')
. '-%' ?? '');
}
}

View File

@ -0,0 +1,125 @@
<?php
namespace App\Services\Admin;
use Illuminate\Support\Arr;
use App\Models\ArticleCategory;
use App\Models\Filters\ArticleCategoryFilter;
use App\Traits\UploadTrait;
/**
* @method ArticleCategory getModel()
* @method ArticleCategory|\Illuminate\Database\Query\Builder query()
*/
class ArticleCategoryService extends BaseService
{
protected string $modelName = ArticleCategory::class;
protected string $modelFilterName = ArticleCategoryFilter::class;
use UploadTrait;
public function getTree()
{
$list = $this->query()->filter(request()->all(), $this->modelFilterName)->get();
$minNum = $list->min('parent_id');
return !$list->isEmpty() ? array2tree($list->toArray(), $minNum) :[];
}
public function parentIsChild($id, $pid): bool
{
$parent = $this->query()->find($pid);
do {
if ($parent->parent_id == $id) {
return true;
}
// 如果没有parent 则为顶级 退出循环
$parent = $parent->parent;
} while ($parent);
return false;
}
public function list()
{
return ['items' => $this->getTree()];
}
public function store($data): bool
{
if ($this->hasRepeated($data)) {
return false;
}
$columns = $this->getTableColumns();
$model = $this->getModel();
$data['cover'] = $this->saveImage('cover', 'article_category/cover')[0] ?? '';
foreach ($data as $k => $v) {
if (!in_array($k, $columns)) {
continue;
}
$model->setAttribute($k, $v);
}
return $model->save();
}
public function update($primaryKey, $data): bool
{
if ($this->hasRepeated($data, $primaryKey)) {
return false;
}
$columns = $this->getTableColumns();
$pid = Arr::get($data, 'parent_id');
if ($pid != 0) {
if ($this->parentIsChild($primaryKey, $pid)) {
$this->setError('父级不允许设置为当前子级');
return false;
}
}
$model = $this->query()->whereKey($primaryKey)->first();
if(isset($data['cover'])){
$data['cover'] = $this->saveImage('cover', 'article_category/cover')[0] ?? '';
}
foreach ($data as $k => $v) {
if (!in_array($k, $columns)) {
continue;
}
$model->setAttribute($k, $v);
}
return $model->save();
}
public function hasRepeated($data, $id = 0): bool
{
$query = $this->query()->when($id, fn($query) => $query->where('id', '<>', $id));
if (isset($data['key']) && (clone $query)->where('key', $data['key'])->exists()) {
$this->setError('KEY重复');
return true;
}
return false;
}
public function delete(string $ids): mixed
{
$ids = explode(',', $ids);
if(count($ids) == 1){
$this->query()->where('path', 'like', '%-'.$ids[0].'-%')->delete();
}
return $this->query()->whereIn('id', $ids)->delete();
}
}

View File

@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('article_categories', function (Blueprint $table) {
$table->id();
$table->string('name')->comment('名称');
$table->string('key')->nullable()->unique();
$table->string('cover')->nullable()->comment('封面');
$table->unsignedBigInteger('parent_id')->default(0)->comment('上级ID');
$table->string('parent_key')->nullable('上级key');
$table->unsignedInteger('lv')->default(1)->comment('层级');
$table->string('path')->default('-')->comment('所有的父级ID');
$table->unsignedTinyInteger('is_enable')->default(1)->comment('启用开关');
$table->unsignedTinyInteger('is_show')->default(0)->comment('显示开关');
$table->unsignedTinyInteger('is_recommend')->default(0)->comment('推荐开关');
$table->unsignedInteger('sort')->default(0)->comment('排序');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('article_categories');
}
};

View File

@ -35,6 +35,8 @@ class AdminMenuSeeder extends Seeder
'children' => [
['title'=>'ads', 'icon'=>'lets-icons:img-box','url'=>'/ads', 'order'=>0],
['title'=>'friend_links', 'icon'=>'mdi:link-variant','url'=>'/friend_links', 'order'=>1],
['title'=>'article_categories', 'icon'=>'tabler:category-2', 'url'=>'/article_categories', 'order'=>2],
['title'=>'articles', 'icon'=>'ic:outline-article','url'=>'/articles', 'order'=>3],
]
],
['title' => 'data_content', 'icon' => 'ph:codesandbox-logo-light', 'url' => '/data_content', 'order'=>3, //数据管理

View File

@ -0,0 +1,56 @@
<?php
namespace Database\Seeders;
use Slowlyo\OwlAdmin\Models\AdminMenu;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Throwable;
class ArticleCategorySeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$oldCates = DB::table('t_article_type')->get()->sortBy('pid');
$newCategories = [];
foreach($oldCates as $cate){
if(empty($cate->name)){
continue;
}
$_category = [
'id' => $cate -> id,
'parent_id' => $cate -> pid ?? 0,
'name' => $cate->name,
'cover' => $cate->icon ?? null,
'sort' => $cate -> sorted,
'is_recommend' => $cate->recommend,
'is_show' => $cate->showInOut,
'lv' => !empty($cate->pid) ? $newCategories[$cate->pid]['lv'] + 1:1,
'path' => !empty($cate->pid) ? $newCategories[$cate->pid]['path'] . $cate->pid .'-':'-',
'created_at' => now(),
'updated_at' => now(),
];
$newCategories[$cate->id] = $_category;
}
if(count($newCategories) > 0){
DB::table('article_categories')->truncate();
try {
DB::begintransaction();
DB::table('article_categories')->insert($newCategories);
DB::commit();
} catch (Throwable $th) {
DB::rollBack();
report($th);
}
}
}
}

View File

@ -326,5 +326,16 @@ return [
'cover'=> '图片',
'sort' => '排序',
'is_enable'=>'显示',
]
],
'article_categories' => [
'name' => '名称',
'parent' => '上级分类',
'parent_id' => '上级分类',
'key' => 'KEY',
'cover'=>'图标',
'sort'=>'排序',
'is_enable' => '启用',
'is_show' => '展示',
'is_recommend' => '推荐'
],
];

View File

@ -34,4 +34,5 @@ return [
'welfare_cate' => '福利类型',
'job_cate' => '工种管理',
'friend_links' => '友情链接',
'article_categories'=> '文章分类',
];