diff --git a/app/Http/Controllers/IndexController.php b/app/Http/Controllers/IndexController.php index fd43383..d88c84b 100644 --- a/app/Http/Controllers/IndexController.php +++ b/app/Http/Controllers/IndexController.php @@ -12,6 +12,8 @@ use Modules\AnnoRoute\Attribute\PostRoute; use Modules\AnnoRoute\Attribute\RequestAttribute; use Modules\Common\Trait\RequestJson; use Modules\SystemTool\Models\SysCarouselModel; +use Modules\SystemTool\Models\SysCategoryItemModel; +use Modules\SystemTool\Models\SysCategoryModel; use Modules\SystemTool\Models\SysGridNavModel; #[RequestAttribute('/api', authGuard: 'users')] @@ -81,4 +83,28 @@ class IndexController return $this->success(compact('carousel', 'gridNav', 'site_title')); } + + /** + * 获取分类及分类下所有链接(公开接口) + * 前端点击轮播图/导航时,若 link_type 为分类则跳转分类项页,调用此接口 + */ + #[GetRoute('/category/{id}', false, where: ['id' => '[0-9]+'])] + public function category(int $id): JsonResponse + { + $category = SysCategoryModel::query() + ->where('status', 0) + ->find($id); + if (empty($category)) { + return $this->error('分类不存在'); + } + + $items = SysCategoryItemModel::query() + ->where('category_id', $id) + ->where('status', 0) + ->orderBy('sort', 'desc') + ->orderBy('id', 'desc') + ->get(); + + return $this->success($items->toArray()); + } } diff --git a/database/migrations/2026_07_13_085358_create_carousel_and_grid_nav_tables.php b/database/migrations/2026_07_13_085358_create_carousel_and_grid_nav_tables.php index b7f9789..31cb326 100644 --- a/database/migrations/2026_07_13_085358_create_carousel_and_grid_nav_tables.php +++ b/database/migrations/2026_07_13_085358_create_carousel_and_grid_nav_tables.php @@ -16,7 +16,7 @@ return new class extends Migration Schema::create('sys_carousel', function (Blueprint $table) { $table->increments('id'); $table->string('title', 100)->comment('轮播图标题'); - $table->integer('image_id')->comment('轮播图图片信息(JSON)'); + $table->integer('image_id')->comment('轮播图图片信息'); $table->string('link', 500)->nullable()->default('')->comment('跳转链接'); $table->unsignedTinyInteger('status')->default(0)->comment('状态:0=启用,1=禁用'); $table->integer('sort')->default(0)->comment('排序(数字越大越靠前)'); @@ -30,7 +30,7 @@ return new class extends Migration Schema::create('sys_grid_nav', function (Blueprint $table) { $table->increments('id'); $table->string('title', 100)->comment('导航标题'); - $table->integer('image_id')->comment('导航图标信息(JSON)'); + $table->integer('image_id')->comment('导航图标信息'); $table->string('link', 500)->nullable()->default('')->comment('跳转链接'); $table->unsignedTinyInteger('status')->default(0)->comment('状态:0=启用,1=禁用'); $table->integer('sort')->default(0)->comment('排序(数字越大越靠前)'); diff --git a/database/migrations/2026_08_09_000000_create_category_tables.php b/database/migrations/2026_08_09_000000_create_category_tables.php new file mode 100644 index 0000000..3e7015c --- /dev/null +++ b/database/migrations/2026_08_09_000000_create_category_tables.php @@ -0,0 +1,162 @@ +increments('id'); + $table->string('name', 100)->comment('分类名称'); + $table->unsignedTinyInteger('status')->default(0)->comment('状态:0=启用,1=禁用'); + $table->integer('sort')->default(0)->comment('排序(数字越大越靠前)'); + $table->timestamps(); + $table->comment('首页分类管理表'); + }); + } + + // 首页分类项表(字段与宫格导航表一致) + if (! Schema::hasTable('sys_category_item')) { + Schema::create('sys_category_item', function (Blueprint $table) { + $table->increments('id'); + $table->integer('category_id')->default(0)->comment('所属分类ID'); + $table->string('title', 100)->comment('导航标题'); + $table->integer('image_id')->comment('导航图标信息'); + $table->string('link', 500)->nullable()->default('')->comment('跳转链接'); + $table->unsignedTinyInteger('status')->default(0)->comment('状态:0=启用,1=禁用'); + $table->integer('sort')->default(0)->comment('排序(数字越大越靠前)'); + $table->timestamps(); + $table->index('category_id', 'idx_category_item_category_id'); + $table->comment('首页分类项管理表'); + }); + } + + // 轮播图表:新增跳转类型与分类ID + if (Schema::hasTable('sys_carousel') && ! Schema::hasColumn('sys_carousel', 'link_type')) { + Schema::table('sys_carousel', function (Blueprint $table) { + $table->unsignedTinyInteger('link_type')->default(0)->comment('跳转类型:0=链接,1=分类'); + $table->integer('category_id')->nullable()->default(0)->comment('分类ID(link_type=1 时生效)'); + }); + } + + // 宫格导航表:新增跳转类型与分类ID + if (Schema::hasTable('sys_grid_nav') && ! Schema::hasColumn('sys_grid_nav', 'link_type')) { + Schema::table('sys_grid_nav', function (Blueprint $table) { + $table->unsignedTinyInteger('link_type')->default(0)->comment('跳转类型:0=链接,1=分类'); + $table->integer('category_id')->nullable()->default(0)->comment('分类ID(link_type=1 时生效)'); + }); + } + + $this->insertMenuRules(); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('sys_carousel', function (Blueprint $table) { + if (Schema::hasColumn('sys_carousel', 'link_type')) { + $table->dropColumn(['link_type', 'category_id']); + } + }); + + Schema::table('sys_grid_nav', function (Blueprint $table) { + if (Schema::hasColumn('sys_grid_nav', 'link_type')) { + $table->dropColumn(['link_type', 'category_id']); + } + }); + + Schema::dropIfExists('sys_category_item'); + Schema::dropIfExists('sys_category'); + + $this->deleteMenuRules(); + } + + /** + * 向权限菜单表插入「分类管理」菜单(幂等,已存在则跳过) + */ + protected function insertMenuRules(): void + { + if (! Schema::hasTable('sys_rule')) { + return; + } + + $exists = DB::table('sys_rule')->where('key', 'system.category')->exists(); + if ($exists) { + return; + } + + $date = date('Y-m-d H:i:s'); + + $menuId = DB::table('sys_rule')->insertGetId([ + 'parent_id' => 0, + 'type' => 'route', + 'key' => 'system.category', + 'name' => '分类管理', + 'path' => '/system/category', + 'icon' => '', + 'order' => 0, + 'local' => 'menu.system.category', + 'status' => 1, + 'hidden' => 1, + 'link' => 0, + 'created_at' => $date, + 'updated_at' => $date, + ]); + + $children = [ + ['name' => '查询分类列表', 'key' => 'system.category.query'], + ['name' => '新增分类', 'key' => 'system.category.create'], + ['name' => '编辑分类', 'key' => 'system.category.update'], + ['name' => '删除分类', 'key' => 'system.category.delete'], + ['name' => '分类项列表', 'key' => 'system.category.item.query'], + ['name' => '分类项新增', 'key' => 'system.category.item.create'], + ['name' => '分类项编辑', 'key' => 'system.category.item.update'], + ['name' => '分类项删除', 'key' => 'system.category.item.delete'], + ]; + + foreach ($children as $i => $child) { + DB::table('sys_rule')->insert([ + 'parent_id' => $menuId, + 'type' => 'rule', + 'key' => $child['key'], + 'name' => $child['name'], + 'path' => '', + 'icon' => '', + 'order' => $i, + 'local' => '', + 'status' => 1, + 'hidden' => 1, + 'link' => 0, + 'created_at' => $date, + 'updated_at' => $date, + ]); + } + } + + /** + * 删除「分类管理」菜单及其子规则 + */ + protected function deleteMenuRules(): void + { + if (! Schema::hasTable('sys_rule')) { + return; + } + + $menuId = DB::table('sys_rule')->where('key', 'system.category')->value('id'); + if ($menuId) { + DB::table('sys_rule')->where('key', 'system.category')->delete(); + DB::table('sys_rule')->where('parent_id', $menuId)->delete(); + } + } +}; diff --git a/modules/SystemTool/Http/Controllers/SysCategoryController.php b/modules/SystemTool/Http/Controllers/SysCategoryController.php new file mode 100644 index 0000000..d8ec84f --- /dev/null +++ b/modules/SystemTool/Http/Controllers/SysCategoryController.php @@ -0,0 +1,87 @@ + '=', + ]; + + protected array $quickSearchField = ['name']; + + /** 查询分类列表 */ + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $query = SysCategoryModel::query(); + $data = $this->buildSearch($request->all(), $query) + ->orderBy('sort', 'desc') + ->orderBy('id', 'desc') + ->paginate($request->input('pageSize', 10)) + ->toArray(); + return $this->success($data); + } + + /** 创建分类 */ + #[PostRoute(authorize: 'create')] + public function create(SysCategoryFormRequest $request): JsonResponse + { + $validated = $request->validated(); + $model = SysCategoryModel::create($validated); + if (empty($model)) { + return $this->error(); + } + return $this->success(); + } + + /** 编辑分类 */ + #[PutRoute( + route: '/{id}', + authorize: 'update', + where: ['id' => '[0-9]+'] + )] + public function update(int $id, SysCategoryFormRequest $request): JsonResponse + { + $validated = $request->validated(); + $model = SysCategoryModel::find($id); + if (empty($model)) { + return $this->error('分类不存在'); + } + $model->update($validated); + return $this->success(); + } + + /** 删除分类 */ + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = SysCategoryModel::find($id); + if (empty($model)) { + return $this->error('分类不存在'); + } + // 级联删除该分类下的分类项 + $model->categoryItems()->delete(); + $model->delete(); + return $this->success(); + } +} diff --git a/modules/SystemTool/Http/Controllers/SysCategoryItemController.php b/modules/SystemTool/Http/Controllers/SysCategoryItemController.php new file mode 100644 index 0000000..bff61df --- /dev/null +++ b/modules/SystemTool/Http/Controllers/SysCategoryItemController.php @@ -0,0 +1,86 @@ + '=', + 'status' => '=', + ]; + + protected array $quickSearchField = ['title']; + + /** 查询分类项列表 */ + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $query = SysCategoryItemModel::query(); + $data = $this->buildSearch($request->all(), $query) + ->orderBy('sort', 'desc') + ->orderBy('id', 'desc') + ->paginate($request->input('pageSize', 10)) + ->toArray(); + return $this->success($data); + } + + /** 创建分类项 */ + #[PostRoute(authorize: 'create')] + public function create(SysCategoryItemFormRequest $request): JsonResponse + { + $validated = $request->validated(); + $model = SysCategoryItemModel::create($validated); + if (empty($model)) { + return $this->error(); + } + return $this->success(); + } + + /** 编辑分类项 */ + #[PutRoute( + route: '/{id}', + authorize: 'update', + where: ['id' => '[0-9]+'] + )] + public function update(int $id, SysCategoryItemFormRequest $request): JsonResponse + { + $validated = $request->validated(); + $model = SysCategoryItemModel::find($id); + if (empty($model)) { + return $this->error('分类项不存在'); + } + $model->update($validated); + return $this->success(); + } + + /** 删除分类项 */ + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = SysCategoryItemModel::find($id); + if (empty($model)) { + return $this->error('分类项不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/SystemTool/Http/Requests/SysCarouselFormRequest.php b/modules/SystemTool/Http/Requests/SysCarouselFormRequest.php index 5e1d78c..9b7f88f 100644 --- a/modules/SystemTool/Http/Requests/SysCarouselFormRequest.php +++ b/modules/SystemTool/Http/Requests/SysCarouselFormRequest.php @@ -2,8 +2,10 @@ namespace Modules\SystemTool\Http\Requests; +use Illuminate\Validation\Rule; use Illuminate\Validation\Rules\Exists; use Modules\Common\Http\Requests\BaseFormRequest; +use Modules\SystemTool\Models\SysCategoryModel; use Modules\SystemTool\Models\SysFileModel; class SysCarouselFormRequest extends BaseFormRequest @@ -15,7 +17,19 @@ class SysCarouselFormRequest extends BaseFormRequest return [ 'title' => 'required|string|max:100', 'image_id' => ['required', 'integer', new Exists(SysFileModel::class, 'id')], - 'link' => 'nullable|string|max:500', + 'link_type' => 'nullable|integer|in:0,1', + 'link' => [ + 'nullable', + 'string', + 'max:500', + Rule::requiredIf(fn () => $this->input('link_type', 0) == 0), + ], + 'category_id' => [ + 'nullable', + 'integer', + Rule::requiredIf(fn () => $this->input('link_type', 0) == 1), + new Exists(SysCategoryModel::class, 'id'), + ], 'status' => 'nullable|integer|in:0,1', 'sort' => 'nullable|integer', ]; @@ -27,7 +41,10 @@ class SysCarouselFormRequest extends BaseFormRequest 'title.required' => '轮播图标题是必填的', 'title.max' => '轮播图标题不能超过 :max 个字符', 'image_id.required' => '轮播图图片是必填的', + 'link_type.in' => '跳转类型只能是 0(链接)或 1(分类)', + 'link.required' => '链接模式下跳转链接是必填的', 'link.max' => '跳转链接不能超过 :max 个字符', + 'category_id.required' => '分类模式下请选择分类', 'status.in' => '状态值只能是 0(启用)或 1(禁用)', 'sort.integer' => '排序必须是整数', ]; diff --git a/modules/SystemTool/Http/Requests/SysCategoryFormRequest.php b/modules/SystemTool/Http/Requests/SysCategoryFormRequest.php new file mode 100644 index 0000000..6dd0dee --- /dev/null +++ b/modules/SystemTool/Http/Requests/SysCategoryFormRequest.php @@ -0,0 +1,29 @@ + 'required|string|max:100', + 'status' => 'nullable|integer|in:0,1', + 'sort' => 'nullable|integer', + ]; + } + + public function messages(): array + { + return [ + 'name.required' => '分类名称是必填的', + 'name.max' => '分类名称不能超过 :max 个字符', + 'status.in' => '状态值只能是 0(启用)或 1(禁用)', + 'sort.integer' => '排序必须是整数', + ]; + } +} diff --git a/modules/SystemTool/Http/Requests/SysCategoryItemFormRequest.php b/modules/SystemTool/Http/Requests/SysCategoryItemFormRequest.php new file mode 100644 index 0000000..ac82cf6 --- /dev/null +++ b/modules/SystemTool/Http/Requests/SysCategoryItemFormRequest.php @@ -0,0 +1,38 @@ + ['required', 'integer', new Exists(SysCategoryModel::class, 'id')], + 'title' => 'required|string|max:100', + 'image_id' => ['required', 'integer', new Exists(SysFileModel::class, 'id')], + 'link' => 'nullable|string|max:500', + 'status' => 'nullable|integer|in:0,1', + 'sort' => 'nullable|integer', + ]; + } + + public function messages(): array + { + return [ + 'category_id.required' => '所属分类是必填的', + 'title.required' => '分类项标题是必填的', + 'title.max' => '分类项标题不能超过 :max 个字符', + 'image_id.required' => '分类项图标是必填的', + 'link.max' => '跳转链接不能超过 :max 个字符', + 'status.in' => '状态值只能是 0(启用)或 1(禁用)', + 'sort.integer' => '排序必须是整数', + ]; + } +} diff --git a/modules/SystemTool/Http/Requests/SysGridNavFormRequest.php b/modules/SystemTool/Http/Requests/SysGridNavFormRequest.php index 9c84550..13dd43e 100644 --- a/modules/SystemTool/Http/Requests/SysGridNavFormRequest.php +++ b/modules/SystemTool/Http/Requests/SysGridNavFormRequest.php @@ -2,8 +2,10 @@ namespace Modules\SystemTool\Http\Requests; +use Illuminate\Validation\Rule; use Illuminate\Validation\Rules\Exists; use Modules\Common\Http\Requests\BaseFormRequest; +use Modules\SystemTool\Models\SysCategoryModel; use Modules\SystemTool\Models\SysFileModel; class SysGridNavFormRequest extends BaseFormRequest @@ -15,7 +17,19 @@ class SysGridNavFormRequest extends BaseFormRequest return [ 'title' => 'required|string|max:100', 'image_id' => ['required', 'integer', new Exists(SysFileModel::class, 'id')], - 'link' => 'nullable|string|max:500', + 'link_type' => 'nullable|integer|in:0,1', + 'link' => [ + 'nullable', + 'string', + 'max:500', + Rule::requiredIf(fn () => $this->input('link_type', 0) == 0), + ], + 'category_id' => [ + 'nullable', + 'integer', + Rule::requiredIf(fn () => $this->input('link_type', 0) == 1), + new Exists(SysCategoryModel::class, 'id'), + ], 'status' => 'nullable|integer|in:0,1', 'sort' => 'nullable|integer', ]; @@ -27,7 +41,10 @@ class SysGridNavFormRequest extends BaseFormRequest 'title.required' => '导航标题是必填的', 'title.max' => '导航标题不能超过 :max 个字符', 'image_id.required' => '导航图标是必填的', + 'link_type.in' => '跳转类型只能是 0(链接)或 1(分类)', + 'link.required' => '链接模式下跳转链接是必填的', 'link.max' => '跳转链接不能超过 :max 个字符', + 'category_id.required' => '分类模式下请选择分类', 'status.in' => '状态值只能是 0(启用)或 1(禁用)', 'sort.integer' => '排序必须是整数', ]; diff --git a/modules/SystemTool/Models/SysCarouselModel.php b/modules/SystemTool/Models/SysCarouselModel.php index cf659f2..963ac6c 100644 --- a/modules/SystemTool/Models/SysCarouselModel.php +++ b/modules/SystemTool/Models/SysCarouselModel.php @@ -3,6 +3,7 @@ namespace Modules\SystemTool\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasOne; /** @@ -16,17 +17,21 @@ class SysCarouselModel extends Model 'status' => 'int', 'sort' => 'int', 'image_id' => 'array', + 'link_type' => 'int', + 'category_id' => 'int', ]; protected $fillable = [ 'title', 'image_id', 'link', + 'link_type', + 'category_id', 'status', 'sort', ]; - protected $with = ['image']; + protected $with = ['image', 'category']; /** * 关联图片 @@ -37,4 +42,13 @@ class SysCarouselModel extends Model return $this->hasOne(SysFileModel::class, 'id', 'image_id'); } + /** + * 关联所属分类(link_type=1 时生效) + * @return BelongsTo + */ + public function category(): BelongsTo + { + return $this->belongsTo(SysCategoryModel::class, 'category_id', 'id'); + } + } diff --git a/modules/SystemTool/Models/SysCategoryItemModel.php b/modules/SystemTool/Models/SysCategoryItemModel.php new file mode 100644 index 0000000..6a95aca --- /dev/null +++ b/modules/SystemTool/Models/SysCategoryItemModel.php @@ -0,0 +1,52 @@ + 'int', + 'status' => 'int', + 'sort' => 'int', + 'image_id' => 'int', + ]; + + protected $fillable = [ + 'category_id', + 'title', + 'image_id', + 'link', + 'status', + 'sort', + ]; + + protected $with = ['image']; + + /** + * 关联图片 + * @return HasOne + */ + public function image(): HasOne + { + return $this->hasOne(SysFileModel::class, 'id', 'image_id'); + } + + /** + * 关联所属分类 + * @return BelongsTo + */ + public function category(): BelongsTo + { + return $this->belongsTo(SysCategoryModel::class, 'category_id', 'id'); + } + +} diff --git a/modules/SystemTool/Models/SysCategoryModel.php b/modules/SystemTool/Models/SysCategoryModel.php new file mode 100644 index 0000000..ed1d8cd --- /dev/null +++ b/modules/SystemTool/Models/SysCategoryModel.php @@ -0,0 +1,37 @@ + 'int', + 'sort' => 'int', + ]; + + protected $fillable = [ + 'name', + 'status', + 'sort', + ]; + + /** + * 关联分类项 + * @return HasMany + */ + public function categoryItems(): HasMany + { + return $this->hasMany(SysCategoryItemModel::class, 'category_id', 'id') + ->orderBy('sort', 'desc') + ->orderBy('id', 'desc'); + } + +} diff --git a/modules/SystemTool/Models/SysGridNavModel.php b/modules/SystemTool/Models/SysGridNavModel.php index e12894d..daa7dbb 100644 --- a/modules/SystemTool/Models/SysGridNavModel.php +++ b/modules/SystemTool/Models/SysGridNavModel.php @@ -3,6 +3,7 @@ namespace Modules\SystemTool\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasOne; /** @@ -16,17 +17,21 @@ class SysGridNavModel extends Model 'status' => 'int', 'sort' => 'int', 'image_id' => 'int', + 'link_type' => 'int', + 'category_id' => 'int', ]; protected $fillable = [ 'title', 'image_id', 'link', + 'link_type', + 'category_id', 'status', 'sort', ]; - protected $with = ['image']; + protected $with = ['image', 'category']; /** * 关联图片 @@ -37,4 +42,13 @@ class SysGridNavModel extends Model return $this->hasOne(SysFileModel::class, 'id', 'image_id'); } + /** + * 关联所属分类(link_type=1 时生效) + * @return BelongsTo + */ + public function category(): BelongsTo + { + return $this->belongsTo(SysCategoryModel::class, 'category_id', 'id'); + } + } diff --git a/routes/api.php b/routes/api.php index c6f5fed..bf74073 100644 --- a/routes/api.php +++ b/routes/api.php @@ -11,8 +11,8 @@ Route::controller(App\Http\Controllers\IndexController::class)->prefix('api')->g Route::get('/index', 'index')->middleware(['auth:sanctum', 'authGuard:users']); Route::post('/login', 'login')->middleware(['auth:sanctum', 'authGuard:users']); Route::post('/register', 'register')->middleware(['auth:sanctum', 'authGuard:users']); - // 首页公开接口 Route::get('/home', 'home'); + Route::get('/category/{id}', 'category'); }); // UserController @@ -54,6 +54,30 @@ Route::controller(Modules\SystemTool\Http\Controllers\SysAiController::class)->p Route::post('/test', 'testConnection')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.ai.test']); }); +// SysCarouselController +Route::controller(Modules\SystemTool\Http\Controllers\SysCarouselController::class)->prefix('system/carousel')->group(function () { + Route::get('/', 'query')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.carousel.query']); + Route::post('/', 'create')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.carousel.create']); + Route::put('/{id}', 'update')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.carousel.update']); + Route::delete('/{id}', 'delete')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.carousel.delete']); +}); + +// SysCategoryController +Route::controller(Modules\SystemTool\Http\Controllers\SysCategoryController::class)->prefix('system/category')->group(function () { + Route::get('/', 'query')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.category.query']); + Route::post('/', 'create')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.category.create']); + Route::put('/{id}', 'update')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.category.update']); + Route::delete('/{id}', 'delete')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.category.delete']); +}); + +// SysCategoryItemController +Route::controller(Modules\SystemTool\Http\Controllers\SysCategoryItemController::class)->prefix('system/category/item')->group(function () { + Route::get('/', 'query')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.category.item.query']); + Route::post('/', 'create')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.category.item.create']); + Route::put('/{id}', 'update')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.category.item.update']); + Route::delete('/{id}', 'delete')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.category.item.delete']); +}); + // SysDictController Route::controller(Modules\SystemTool\Http\Controllers\SysDictController::class)->prefix('system/dict/list')->group(function () { Route::get('/', 'query')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.dict.list.query']); @@ -97,6 +121,14 @@ Route::controller(Modules\SystemTool\Http\Controllers\SysFileGroupController::cl Route::delete('/{id}', 'delete')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.file.group.delete']); }); +// SysGridNavController +Route::controller(Modules\SystemTool\Http\Controllers\SysGridNavController::class)->prefix('system/grid-nav')->group(function () { + Route::get('/', 'query')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.grid-nav.query']); + Route::post('/', 'create')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.grid-nav.create']); + Route::put('/{id}', 'update')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.grid-nav.update']); + Route::delete('/{id}', 'delete')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.grid-nav.delete']); +}); + // SysIndexController Route::controller(Modules\SystemTool\Http\Controllers\SysIndexController::class)->group(function () { Route::get('/index', 'index'); @@ -178,22 +210,6 @@ Route::controller(Modules\SystemUser\Http\Controllers\SysRuleController::class)- Route::put('/status/{id}', 'status')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.rule.status']); }); -// SysCarouselController -Route::controller(Modules\SystemTool\Http\Controllers\SysCarouselController::class)->prefix('system/carousel')->group(function () { - Route::get('/', 'query')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.carousel.query']); - Route::post('/', 'create')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.carousel.create']); - Route::put('/{id}', 'update')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.carousel.update']); - Route::delete('/{id}', 'delete')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.carousel.delete']); -}); - -// SysGridNavController -Route::controller(Modules\SystemTool\Http\Controllers\SysGridNavController::class)->prefix('system/grid-nav')->group(function () { - Route::get('/', 'query')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.grid-nav.query']); - Route::post('/', 'create')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.grid-nav.create']); - Route::put('/{id}', 'update')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.grid-nav.update']); - Route::delete('/{id}', 'delete')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.grid-nav.delete']); -}); - // SysUserController Route::controller(Modules\SystemUser\Http\Controllers\SysUserController::class)->prefix('system/user')->group(function () { Route::get('/', 'query')->middleware(['auth:sanctum', 'authGuard', 'abilities:system.user.query']); diff --git a/web/domain/iCarousel.ts b/web/domain/iCarousel.ts index aab0819..37def6c 100644 --- a/web/domain/iCarousel.ts +++ b/web/domain/iCarousel.ts @@ -1,3 +1,4 @@ +import type { ICategory } from '@/domain/iCategory'; import type { ISysFileInfo } from '@/domain/iSysFile'; /** 首页轮播图 */ @@ -7,8 +8,14 @@ export interface ICarousel { title: string; /** 轮播图图片信息(ISysFileInfo 对象) */ image: ISysFileInfo | string; - /** 跳转链接 */ + /** 跳转类型:0=链接,1=分类 */ + link_type?: number; + /** 跳转链接(link_type=0 时生效) */ link?: string; + /** 分类ID(link_type=1 时生效) */ + category_id?: number; + /** 所属分类(后端关联返回) */ + category?: ICategory; /** 状态:0=启用,1=禁用 */ status: number; /** 排序 */ diff --git a/web/domain/iCategory.ts b/web/domain/iCategory.ts new file mode 100644 index 0000000..9205f4e --- /dev/null +++ b/web/domain/iCategory.ts @@ -0,0 +1,12 @@ +/** 首页分类 */ +export interface ICategory { + id?: number; + /** 分类名称 */ + name: string; + /** 状态:0=启用,1=禁用 */ + status: number; + /** 排序 */ + sort: number; + created_at?: string; + updated_at?: string; +} diff --git a/web/domain/iCategoryItem.ts b/web/domain/iCategoryItem.ts new file mode 100644 index 0000000..5b3a2f3 --- /dev/null +++ b/web/domain/iCategoryItem.ts @@ -0,0 +1,20 @@ +import type { ISysFileInfo } from '@/domain/iSysFile'; + +/** 首页分类项(字段与宫格导航一致) */ +export interface ICategoryItem { + id?: number; + /** 所属分类ID */ + category_id?: number; + /** 导航标题 */ + title: string; + /** 导航图标信息(ISysFileInfo 对象) */ + image: ISysFileInfo | string; + /** 跳转链接 */ + link?: string; + /** 状态:0=启用,1=禁用 */ + status: number; + /** 排序 */ + sort: number; + created_at?: string; + updated_at?: string; +} diff --git a/web/domain/iGridNav.ts b/web/domain/iGridNav.ts index d444ece..d3bc424 100644 --- a/web/domain/iGridNav.ts +++ b/web/domain/iGridNav.ts @@ -1,3 +1,4 @@ +import type { ICategory } from '@/domain/iCategory'; import type { ISysFileInfo } from '@/domain/iSysFile'; /** 首页宫格导航 */ @@ -7,8 +8,14 @@ export interface IGridNav { title: string; /** 导航图标信息(ISysFileInfo 对象) */ image: ISysFileInfo | string; - /** 跳转链接 */ + /** 跳转类型:0=链接,1=分类 */ + link_type?: number; + /** 跳转链接(link_type=0 时生效) */ link?: string; + /** 分类ID(link_type=1 时生效) */ + category_id?: number; + /** 所属分类(后端关联返回) */ + category?: ICategory; /** 状态:0=启用,1=禁用 */ status: number; /** 排序 */ diff --git a/web/locales/en_US/index.ts b/web/locales/en_US/index.ts index bf276b7..5c67b1d 100644 --- a/web/locales/en_US/index.ts +++ b/web/locales/en_US/index.ts @@ -18,6 +18,7 @@ import systemRule from "./system/rule"; import systemAi from "./system/ai"; import systemCarousel from "./system/carousel"; import systemGridNav from "./system/gridNav"; +import systemCategory from "./system/category"; import aiChat from "./ai/chat"; import aiConversation from "./ai/conversation"; @@ -50,6 +51,7 @@ export default { ...systemAi, ...systemCarousel, ...systemGridNav, + ...systemCategory, ...aiChat, ...aiConversation, ...aiAgent, diff --git a/web/locales/en_US/menu.ts b/web/locales/en_US/menu.ts index 67af172..758a415 100644 --- a/web/locales/en_US/menu.ts +++ b/web/locales/en_US/menu.ts @@ -46,5 +46,6 @@ export default { "menu.system.ai": "AI", "menu.system.carousel": "Carousel", "menu.system.grid-nav": "Grid Navigation", + "menu.system.category": "Categories", "menu.xin-admin": "XinAdmin", } diff --git a/web/locales/en_US/system/carousel.ts b/web/locales/en_US/system/carousel.ts index e60a7af..2b74912 100644 --- a/web/locales/en_US/system/carousel.ts +++ b/web/locales/en_US/system/carousel.ts @@ -9,8 +9,14 @@ export default { "system.carousel.title.required": "Carousel title is required", "system.carousel.image": "Carousel Image", "system.carousel.image.required": "Carousel image is required", + "system.carousel.linkType": "Link Type", + "system.carousel.linkType.required": "Link type is required", + "system.carousel.linkType.link": "Link", + "system.carousel.linkType.category": "Category", "system.carousel.link": "Redirect Link", "system.carousel.link.placeholder": "Enter redirect link", + "system.carousel.category": "Target Category", + "system.carousel.category.placeholder": "Select a category", "system.carousel.status": "Status", "system.carousel.status.required": "Status is required", "system.carousel.status.normal": "Active", diff --git a/web/locales/en_US/system/category.ts b/web/locales/en_US/system/category.ts new file mode 100644 index 0000000..6a68606 --- /dev/null +++ b/web/locales/en_US/system/category.ts @@ -0,0 +1,40 @@ +export default { + // Page Title + "system.category.page.title": "Category Management", + "system.category.page.description": "Manage homepage categories and category item links", + + // Fields + "system.category.id": "ID", + "system.category.name": "Category Name", + "system.category.name.required": "Category name is required", + "system.category.status": "Status", + "system.category.status.required": "Status is required", + "system.category.status.normal": "Active", + "system.category.status.disabled": "Disabled", + "system.category.sort": "Sort", + "system.category.createdAt": "Created At", + "system.category.updatedAt": "Updated At", + + // Category Items + "system.category.manageItems": "Manage Items", + "system.category.itemManagement": "Category Items", + "system.category.backToList": "Back to Categories", + "system.category.selectCategoryFirst": "Please select a category first", + + "system.category.item.id": "ID", + "system.category.item.title": "Item Title", + "system.category.item.title.required": "Item title is required", + "system.category.item.image": "Item Icon", + "system.category.item.image.required": "Item icon is required", + "system.category.item.link": "Redirect Link", + "system.category.item.link.placeholder": "Enter redirect link", + "system.category.item.status": "Status", + "system.category.item.status.required": "Status is required", + "system.category.item.status.normal": "Active", + "system.category.item.status.disabled": "Disabled", + "system.category.item.sort": "Sort", + "system.category.item.createdAt": "Created At", + "system.category.item.updatedAt": "Updated At", + "system.category.item.createSuccess": "Category item created successfully", + "system.category.item.updateSuccess": "Category item updated successfully", +}; diff --git a/web/locales/en_US/system/gridNav.ts b/web/locales/en_US/system/gridNav.ts index d3e194d..422904c 100644 --- a/web/locales/en_US/system/gridNav.ts +++ b/web/locales/en_US/system/gridNav.ts @@ -9,8 +9,14 @@ export default { "system.gridNav.title.required": "Navigation title is required", "system.gridNav.image": "Navigation Icon", "system.gridNav.image.required": "Navigation icon is required", + "system.gridNav.linkType": "Link Type", + "system.gridNav.linkType.required": "Link type is required", + "system.gridNav.linkType.link": "Link", + "system.gridNav.linkType.category": "Category", "system.gridNav.link": "Redirect Link", "system.gridNav.link.placeholder": "Enter redirect link", + "system.gridNav.category": "Target Category", + "system.gridNav.category.placeholder": "Select a category", "system.gridNav.status": "Status", "system.gridNav.status.required": "Status is required", "system.gridNav.status.normal": "Active", diff --git a/web/locales/zh_CN/index.ts b/web/locales/zh_CN/index.ts index bf276b7..5c67b1d 100644 --- a/web/locales/zh_CN/index.ts +++ b/web/locales/zh_CN/index.ts @@ -18,6 +18,7 @@ import systemRule from "./system/rule"; import systemAi from "./system/ai"; import systemCarousel from "./system/carousel"; import systemGridNav from "./system/gridNav"; +import systemCategory from "./system/category"; import aiChat from "./ai/chat"; import aiConversation from "./ai/conversation"; @@ -50,6 +51,7 @@ export default { ...systemAi, ...systemCarousel, ...systemGridNav, + ...systemCategory, ...aiChat, ...aiConversation, ...aiAgent, diff --git a/web/locales/zh_CN/menu.ts b/web/locales/zh_CN/menu.ts index efc0e3e..fe5963a 100644 --- a/web/locales/zh_CN/menu.ts +++ b/web/locales/zh_CN/menu.ts @@ -46,5 +46,6 @@ export default { "menu.system.ai": "AI 配置", "menu.system.carousel": "轮播图管理", "menu.system.grid-nav": "宫格导航管理", + "menu.system.category": "分类管理", "menu.xin-admin": "XinAdmin", }; diff --git a/web/locales/zh_CN/system/carousel.ts b/web/locales/zh_CN/system/carousel.ts index 98cb9be..406e5cc 100644 --- a/web/locales/zh_CN/system/carousel.ts +++ b/web/locales/zh_CN/system/carousel.ts @@ -9,8 +9,14 @@ export default { "system.carousel.title.required": "轮播图标题不能为空", "system.carousel.image": "轮播图片", "system.carousel.image.required": "轮播图片不能为空", + "system.carousel.linkType": "跳转类型", + "system.carousel.linkType.required": "跳转类型不能为空", + "system.carousel.linkType.link": "链接", + "system.carousel.linkType.category": "分类", "system.carousel.link": "跳转链接", "system.carousel.link.placeholder": "请输入跳转链接", + "system.carousel.category": "跳转分类", + "system.carousel.category.placeholder": "请选择分类", "system.carousel.status": "状态", "system.carousel.status.required": "状态不能为空", "system.carousel.status.normal": "启用", diff --git a/web/locales/zh_CN/system/category.ts b/web/locales/zh_CN/system/category.ts new file mode 100644 index 0000000..1d9566a --- /dev/null +++ b/web/locales/zh_CN/system/category.ts @@ -0,0 +1,40 @@ +export default { + // 页面标题 + "system.category.page.title": "首页分类管理", + "system.category.page.description": "管理首页分类及分类项跳转链接", + + // 字段 + "system.category.id": "ID", + "system.category.name": "分类名称", + "system.category.name.required": "分类名称不能为空", + "system.category.status": "状态", + "system.category.status.required": "状态不能为空", + "system.category.status.normal": "启用", + "system.category.status.disabled": "禁用", + "system.category.sort": "排序", + "system.category.createdAt": "创建时间", + "system.category.updatedAt": "更新时间", + + // 分类项 + "system.category.manageItems": "分类项管理", + "system.category.itemManagement": "分类项管理", + "system.category.backToList": "返回分类列表", + "system.category.selectCategoryFirst": "请先选择分类", + + "system.category.item.id": "ID", + "system.category.item.title": "导航标题", + "system.category.item.title.required": "导航标题不能为空", + "system.category.item.image": "导航图标", + "system.category.item.image.required": "导航图标不能为空", + "system.category.item.link": "跳转链接", + "system.category.item.link.placeholder": "请输入跳转链接", + "system.category.item.status": "状态", + "system.category.item.status.required": "状态不能为空", + "system.category.item.status.normal": "启用", + "system.category.item.status.disabled": "禁用", + "system.category.item.sort": "排序", + "system.category.item.createdAt": "创建时间", + "system.category.item.updatedAt": "更新时间", + "system.category.item.createSuccess": "分类项创建成功", + "system.category.item.updateSuccess": "分类项更新成功", +}; diff --git a/web/locales/zh_CN/system/gridNav.ts b/web/locales/zh_CN/system/gridNav.ts index a867d7f..1b076b4 100644 --- a/web/locales/zh_CN/system/gridNav.ts +++ b/web/locales/zh_CN/system/gridNav.ts @@ -9,8 +9,14 @@ export default { "system.gridNav.title.required": "导航标题不能为空", "system.gridNav.image": "导航图标", "system.gridNav.image.required": "导航图标不能为空", + "system.gridNav.linkType": "跳转类型", + "system.gridNav.linkType.required": "跳转类型不能为空", + "system.gridNav.linkType.link": "链接", + "system.gridNav.linkType.category": "分类", "system.gridNav.link": "跳转链接", "system.gridNav.link.placeholder": "请输入跳转链接", + "system.gridNav.category": "跳转分类", + "system.gridNav.category.placeholder": "请选择分类", "system.gridNav.status": "状态", "system.gridNav.status.required": "状态不能为空", "system.gridNav.status.normal": "启用", diff --git a/web/pages/system/carousel/index.tsx b/web/pages/system/carousel/index.tsx index 680cffb..b2cfdee 100644 --- a/web/pages/system/carousel/index.tsx +++ b/web/pages/system/carousel/index.tsx @@ -1,8 +1,11 @@ import XinTable from '@/components/XinTable'; -import { Badge, Image, Typography } from 'antd'; +import { Badge, Image, Tag, Typography } from 'antd'; +import { useEffect, useState } from 'react'; import type { ICarousel } from '@/domain/iCarousel'; +import type { ICategory } from '@/domain/iCategory'; import type { XinTableColumn } from '@/components/XinTable/typings'; import type { ISysFileInfo } from '@/domain/iSysFile'; +import { List } from '@/api/common/table'; import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; @@ -22,6 +25,20 @@ function getImageUrl(image: ISysFileInfo | string | null | undefined): string { export default function CarouselPage() { const { t } = useTranslation(); + // 分类下拉选项 + const [categoryOptions, setCategoryOptions] = useState<{ label: string; value: number }[]>([]); + useEffect(() => { + List('/system/category', { pageSize: 1000 }).then((res) => { + const list = res.data?.data?.data ?? []; + setCategoryOptions(list.map((item) => ({ + label: item.name, + value: item.id as number, + }))); + }).catch(() => { + // 分类加载失败不影响页面使用 + }); + }, []); + const columns: XinTableColumn[] = [ { title: t('system.carousel.id'), @@ -64,6 +81,25 @@ export default function CarouselPage() { ); }, }, + { + title: t('system.carousel.linkType'), + dataIndex: 'link_type', + valueType: 'select', + colProps: { span: 12 }, + initialValue: 0, + rules: [{ required: true, message: t('system.carousel.linkType.required') }], + fieldProps: { + options: [ + { label: t('system.carousel.linkType.link'), value: 0 }, + { label: t('system.carousel.linkType.category'), value: 1 }, + ], + }, + render: (value: number) => { + return value === 1 + ? {t('system.carousel.linkType.category')} + : {t('system.carousel.linkType.link')}; + }, + }, { title: t('system.carousel.link'), dataIndex: 'link', @@ -73,6 +109,35 @@ export default function CarouselPage() { fieldProps: { placeholder: t('system.carousel.link.placeholder'), }, + render: (_, record) => { + if(record.link_type === 1) { + const name = record.category?.name; + return name ? {name} : '-'; + } + return record.link || '-' + }, + dependency: { + dependencies: ['link_type'], + visible: (values) => values.link_type !== 1, + }, + }, + { + title: t('system.carousel.category'), + dataIndex: 'category_id', + valueType: 'select', + colProps: { span: 12 }, + hideInSearch: true, + hideInTable: true, + fieldProps: { + options: categoryOptions, + showSearch: true, + optionFilterProp: 'label', + placeholder: t('system.carousel.category.placeholder'), + }, + dependency: { + dependencies: ['link_type'], + visible: (values) => values.link_type === 1, + }, }, { title: t('system.carousel.status'), diff --git a/web/pages/system/category/index.tsx b/web/pages/system/category/index.tsx new file mode 100644 index 0000000..cefbfaa --- /dev/null +++ b/web/pages/system/category/index.tsx @@ -0,0 +1,121 @@ +import XinTable from '@/components/XinTable'; +import { Badge, Button, Tooltip, Typography } from 'antd'; +import { UnorderedListOutlined } from '@ant-design/icons'; +import type { ICategory } from '@/domain/iCategory'; +import type { XinTableColumn } from '@/components/XinTable/typings'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router'; +import dayjs from 'dayjs'; + +const { Title, Text } = Typography; + +/** 首页分类管理 */ +export default function CategoryPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const columns: XinTableColumn[] = [ + { + title: t('system.category.id'), + dataIndex: 'id', + hideInForm: true, + width: 80, + sorter: true, + align: 'center', + }, + { + title: t('system.category.name'), + dataIndex: 'name', + valueType: 'text', + colProps: { span: 12 }, + rules: [{ required: true, message: t('system.category.name.required') }], + }, + { + title: t('system.category.status'), + dataIndex: 'status', + valueType: 'select', + filters: [ + { text: t('system.category.status.normal'), value: 0 }, + { text: t('system.category.status.disabled'), value: 1 }, + ], + colProps: { span: 12 }, + rules: [{ required: true, message: t('system.category.status.required') }], + fieldProps: { + options: [ + { label: t('system.category.status.normal'), value: 0 }, + { label: t('system.category.status.disabled'), value: 1 }, + ], + }, + render: (value: number) => { + return value === 0 + ? + : ; + }, + }, + { + title: t('system.category.sort'), + dataIndex: 'sort', + valueType: 'digit', + colProps: { span: 12 }, + hideInSearch: true, + initialValue: 0, + fieldProps: { + min: 0, + style: { width: '100%' }, + }, + }, + { + title: t('system.category.createdAt'), + dataIndex: 'created_at', + render: (value: string) => (value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '-'), + hideInForm: true, + hideInSearch: true, + width: 160, + }, + ]; + + // 跳转到分类项管理页面 + const handleGoToItems = (record: ICategory) => { + navigate(`/system/category/item?categoryId=${record.id}&categoryName=${encodeURIComponent(record.name || '')}`); + }; + + return ( + <> +
+ {t('system.category.page.title')} + {t('system.category.page.description')} +
+ + api="/system/category" + columns={columns} + rowKey="id" + accessName="system.category" + formProps={{ + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }} + modalProps={{ width: 600 }} + searchProps={false} + operateProps={{ + fixed: 'right', + width: 180, + }} + scroll={{ x: 1000 }} + operateRender={(record, dom) => [ + + + + <span className={'mr-2'}>{t('system.category.itemManagement')} - {currentCategory.name}</span> + + + + api="/system/category/item" + columns={columns} + rowKey="id" + accessName="system.category.item" + formProps={{ + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }} + modalProps={{ width: 600 }} + requestParams={(params) => { + return { + ...params, + category_id: currentCategory.id, + }; + }} + searchShow={false} + handleFinish={async (values, mode, _form, defaultValue) => { + if (mode === 'create') { + await Create('/system/category/item', { + ...values, + category_id: currentCategory.id, + }); + window.$message?.success(t('system.category.item.createSuccess')); + } else { + await Update('/system/category/item/' + defaultValue?.id, { + ...values, + category_id: currentCategory.id, + }); + window.$message?.success(t('system.category.item.updateSuccess')); + } + return true; + }} + /> + + ); +} diff --git a/web/pages/system/grid-nav/index.tsx b/web/pages/system/grid-nav/index.tsx index 6564879..698dcac 100644 --- a/web/pages/system/grid-nav/index.tsx +++ b/web/pages/system/grid-nav/index.tsx @@ -1,8 +1,11 @@ import XinTable from '@/components/XinTable'; -import { Badge, Image, Typography } from 'antd'; +import { Badge, Image, Tag, Typography } from 'antd'; +import { useEffect, useState } from 'react'; import type { IGridNav } from '@/domain/iGridNav'; +import type { ICategory } from '@/domain/iCategory'; import type { XinTableColumn } from '@/components/XinTable/typings'; import type { ISysFileInfo } from '@/domain/iSysFile'; +import { List } from '@/api/common/table'; import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; @@ -22,6 +25,20 @@ function getImageUrl(image: ISysFileInfo | string | null | undefined): string { export default function GridNavPage() { const { t } = useTranslation(); + // 分类下拉选项 + const [categoryOptions, setCategoryOptions] = useState<{ label: string; value: number }[]>([]); + useEffect(() => { + List('/system/category', { pageSize: 1000 }).then((res) => { + const list = res.data?.data?.data ?? []; + setCategoryOptions(list.map((item) => ({ + label: item.name, + value: item.id as number, + }))); + }).catch(() => { + // 分类加载失败不影响页面使用 + }); + }, []); + const columns: XinTableColumn[] = [ { title: t('system.gridNav.id'), @@ -64,6 +81,25 @@ export default function GridNavPage() { ); }, }, + { + title: t('system.gridNav.linkType'), + dataIndex: 'link_type', + valueType: 'select', + colProps: { span: 12 }, + initialValue: 0, + rules: [{ required: true, message: t('system.gridNav.linkType.required') }], + fieldProps: { + options: [ + { label: t('system.gridNav.linkType.link'), value: 0 }, + { label: t('system.gridNav.linkType.category'), value: 1 }, + ], + }, + render: (value: number) => { + return value === 1 + ? {t('system.gridNav.linkType.category')} + : {t('system.gridNav.linkType.link')}; + }, + }, { title: t('system.gridNav.link'), dataIndex: 'link', @@ -73,6 +109,35 @@ export default function GridNavPage() { fieldProps: { placeholder: t('system.gridNav.link.placeholder'), }, + render: (_, record) => { + if(record.link_type === 1) { + const name = record.category?.name; + return name ? {name} : '-'; + } + return record.link || '-' + }, + dependency: { + dependencies: ['link_type'], + visible: (values) => values.link_type !== 1, + }, + }, + { + title: t('system.carousel.category'), + dataIndex: 'category_id', + valueType: 'select', + colProps: { span: 12 }, + hideInSearch: true, + hideInTable: true, + fieldProps: { + options: categoryOptions, + showSearch: true, + optionFilterProp: 'label', + placeholder: t('system.carousel.category.placeholder'), + }, + dependency: { + dependencies: ['link_type'], + visible: (values) => values.link_type === 1, + }, }, { title: t('system.gridNav.status'),