From d35c397e8df2fc2a16fdba02eaeaf04800d69e2c Mon Sep 17 00:00:00 2001 From: "jiangdong.cheng" Date: Thu, 12 Feb 2026 10:57:53 +0800 Subject: [PATCH] =?UTF-8?q?=E7=95=8C=E9=9D=A2=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Site/ToolController.php | 98 +++-- resources/views/public/tools/index.blade.php | 425 +++++++++---------- resources/views/public/tools/list.blade.php | 109 +++++ routes/web.php | 4 +- 4 files changed, 376 insertions(+), 260 deletions(-) create mode 100644 resources/views/public/tools/list.blade.php diff --git a/app/Http/Controllers/Site/ToolController.php b/app/Http/Controllers/Site/ToolController.php index 049358b..08de3ef 100644 --- a/app/Http/Controllers/Site/ToolController.php +++ b/app/Http/Controllers/Site/ToolController.php @@ -27,15 +27,15 @@ class ToolController extends Controller $querySignature = sha1((string) $request->getQueryString()); $activeTab = $this->resolveTab($request); - $payload = Cache::remember("tools_index_v4_{$querySignature}", now()->addMinutes(10), function () use ($request, $activeTab): array { + $payload = Cache::remember("tools_portal_v2_{$querySignature}", now()->addMinutes(10), function () use ($request, $activeTab): array { $builder = Tool::query() ->published() - ->with(['category', 'alternative']); + ->with('category'); - $this->applyFilters($builder, $request); + $this->applyFilters($builder, $request, false); $this->applyTabSorting($builder, $activeTab); - $categoryNavigation = Category::query() + $categories = Category::query() ->where('type', 'tool') ->where('is_active', true) ->withCount([ @@ -45,37 +45,73 @@ class ToolController extends Controller ->orderBy('name') ->get(); - $totalPublished = Tool::query()->published()->count(); - $totalApiSupported = Tool::query()->published()->where('has_api', true)->count(); - $totalFreeAvailable = Tool::query()->published()->whereIn('pricing_type', ['free', 'freemium'])->count(); - $updatedInSevenDays = Tool::query()->published()->where('updated_at', '>=', now()->subDays(7))->count(); + $portalTools = $builder->limit(360)->get(); + $toolsByCategory = $portalTools->groupBy(fn (Tool $tool): string => $tool->category?->slug ?? 'uncategorized'); + + $categorySections = $categories->map(function (Category $category) use ($toolsByCategory): array { + $sectionTools = $toolsByCategory->get($category->slug, collect())->take(18); + + return [ + 'slug' => $category->slug, + 'name' => $category->name, + 'count' => (int) ($category->published_tools_count ?? 0), + 'tools' => $sectionTools, + ]; + })->values(); return [ - 'items' => $builder->paginate(24)->withQueryString(), - 'categories' => $categoryNavigation, - 'filters' => $request->only(['q', 'category', 'pricing', 'api', 'tab']), + 'categories' => $categories, + 'categorySections' => $categorySections, + 'hotTools' => $portalTools->take(18), + 'latestTools' => Tool::query()->published()->with('category')->latest('published_at')->limit(18)->get(), + 'filters' => $request->only(['q', 'pricing', 'api', 'tab']), 'activeTab' => $activeTab, 'tabOptions' => $this->tabOptions(), - 'toolStats' => [ - 'total' => $totalPublished, - 'api' => $totalApiSupported, - 'free' => $totalFreeAvailable, - 'updated_7d' => $updatedInSevenDays, - ], - 'featuredTools' => Tool::query()->published()->with('category')->orderByDesc('published_at')->limit(8)->get(), - 'sidebarModels' => AiModel::published()->orderByDesc('total_score')->limit(8)->get(), - 'sidebarNews' => Article::published()->latest('published_at')->limit(8)->get(), + 'toolStats' => $this->buildToolStats(), ]; }); return view('public.tools.index', $payload); } + public function list(Request $request): View + { + $activeTab = $this->resolveTab($request); + + $builder = Tool::query() + ->published() + ->with('category'); + + $this->applyFilters($builder, $request, true); + $this->applyTabSorting($builder, $activeTab); + + $categories = Category::query() + ->where('type', 'tool') + ->where('is_active', true) + ->withCount([ + 'tools as published_tools_count' => fn (Builder $query): Builder => $query->published(), + ]) + ->orderByDesc('published_tools_count') + ->orderBy('name') + ->get(); + + return view('public.tools.list', [ + 'items' => $builder->paginate(36)->withQueryString(), + 'categories' => $categories, + 'filters' => $request->only(['q', 'category', 'pricing', 'api', 'tab']), + 'activeTab' => $activeTab, + 'tabOptions' => $this->tabOptions(), + 'toolStats' => $this->buildToolStats(), + 'sidebarModels' => AiModel::query()->published()->orderByDesc('total_score')->limit(8)->get(), + 'sidebarNews' => Article::query()->published()->latest('published_at')->limit(8)->get(), + ]); + } + public function byCategory(string $slug, Request $request): View { $request->merge(['category' => $slug]); - return $this->index($request); + return $this->list($request); } public function show(string $slug): View @@ -129,7 +165,7 @@ class ToolController extends Controller return [ ['key' => 'recommended', 'label' => '综合推荐'], ['key' => 'latest', 'label' => '最新收录'], - ['key' => 'api', 'label' => 'API 优先'], + ['key' => 'api', 'label' => 'API优先'], ['key' => 'free', 'label' => '免费优先'], ]; } @@ -156,14 +192,14 @@ class ToolController extends Controller ->orderByDesc('published_at'); } - private function applyFilters(Builder $builder, Request $request): void + private function applyFilters(Builder $builder, Request $request, bool $withCategory): void { if ($request->filled('q')) { $keyword = trim((string) $request->string('q')); $builder->whereFullText(['name', 'summary', 'description'], $keyword); } - if ($request->filled('category')) { + if ($withCategory && $request->filled('category')) { $builder->whereHas('category', function (Builder $categoryQuery) use ($request): void { $categoryQuery->where('slug', (string) $request->string('category')); }); @@ -178,6 +214,19 @@ class ToolController extends Controller } } + /** + * @return array{total:int, api:int, free:int, updated_7d:int} + */ + private function buildToolStats(): array + { + return [ + 'total' => Tool::query()->published()->count(), + 'api' => Tool::query()->published()->where('has_api', true)->count(), + 'free' => Tool::query()->published()->whereIn('pricing_type', ['free', 'freemium'])->count(), + 'updated_7d' => Tool::query()->published()->where('updated_at', '>=', now()->subDays(7))->count(), + ]; + } + private function containsRiskKeyword(string $text): bool { return str_contains($text, '医疗') @@ -243,3 +292,4 @@ class ToolController extends Controller return array_values(array_unique(array_slice($tags, 0, 8))); } } + diff --git a/resources/views/public/tools/index.blade.php b/resources/views/public/tools/index.blade.php index e3a076e..9c84a62 100644 --- a/resources/views/public/tools/index.blade.php +++ b/resources/views/public/tools/index.blade.php @@ -1,63 +1,67 @@ @extends('layouts.site') @section('page_class', 'page-tools') -@section('title', 'AI 工具集 - AIWeb') -@section('meta_description', '参考门户站样式的 AI 工具导航页,支持分类浏览、搜索筛选与热门工具发现。') +@section('title', 'AI工具集 - AIWeb') +@section('meta_description', 'AI工具集首页,按分类分块浏览工具,左侧菜单可定位到对应区块。') @section('canonical', route('tools.index')) @section('head')