getQueryString()); $activeTab = $this->resolveTab($request); $payload = Cache::remember("tools_index_v4_{$querySignature}", now()->addMinutes(10), function () use ($request, $activeTab): array { $builder = Tool::query() ->published() ->with(['category', 'alternative']); $this->applyFilters($builder, $request); $this->applyTabSorting($builder, $activeTab); $categoryNavigation = 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(); $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(); return [ 'items' => $builder->paginate(24)->withQueryString(), 'categories' => $categoryNavigation, 'filters' => $request->only(['q', 'category', '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(), ]; }); return view('public.tools.index', $payload); } public function byCategory(string $slug, Request $request): View { $request->merge(['category' => $slug]); return $this->index($request); } public function show(string $slug): View { /** @var Tool $tool */ $tool = Tool::query() ->published() ->with(['category', 'source', 'alternative']) ->where('slug', $slug) ->firstOrFail(); $relatedTools = Tool::query() ->published() ->whereKeyNot($tool->id) ->when($tool->category_id !== null, fn (Builder $query): Builder => $query->where('category_id', $tool->category_id)) ->orderByDesc('published_at') ->limit(8) ->get(); $latestTools = Tool::query() ->published() ->whereKeyNot($tool->id) ->with('category') ->latest('published_at') ->limit(10) ->get(); return view('public.tools.show', [ 'item' => $tool, 'relatedTools' => $relatedTools, 'latestTools' => $latestTools, 'capabilityTags' => $this->extractCapabilityTags($tool), 'descriptionHtml' => $this->markdownRenderer->render($tool->description), 'showRiskNotice' => $this->containsRiskKeyword($tool->summary.' '.$tool->description), ]); } private function resolveTab(Request $request): string { $tab = (string) $request->string('tab'); $allowedTabs = array_column($this->tabOptions(), 'key'); return in_array($tab, $allowedTabs, true) ? $tab : 'recommended'; } /** * @return array */ private function tabOptions(): array { return [ ['key' => 'recommended', 'label' => '综合推荐'], ['key' => 'latest', 'label' => '最新收录'], ['key' => 'api', 'label' => 'API 优先'], ['key' => 'free', 'label' => '免费优先'], ]; } private function applyTabSorting(Builder $builder, string $activeTab): void { if ($activeTab === 'api') { $builder->where('has_api', true); } if ($activeTab === 'free') { $builder->whereIn('pricing_type', ['free', 'freemium']); } if ($activeTab === 'latest') { $builder->orderByDesc('published_at'); return; } $builder ->orderBy('is_stale') ->orderByDesc('has_api') ->orderByDesc('published_at'); } private function applyFilters(Builder $builder, Request $request): void { if ($request->filled('q')) { $keyword = trim((string) $request->string('q')); $builder->whereFullText(['name', 'summary', 'description'], $keyword); } if ($request->filled('category')) { $builder->whereHas('category', function (Builder $categoryQuery) use ($request): void { $categoryQuery->where('slug', (string) $request->string('category')); }); } if ($request->filled('pricing')) { $builder->where('pricing_type', (string) $request->string('pricing')); } if ($request->filled('api')) { $builder->where('has_api', $request->boolean('api')); } } private function containsRiskKeyword(string $text): bool { return str_contains($text, '医疗') || str_contains($text, '法律') || str_contains($text, '投资') || str_contains($text, 'medical') || str_contains($text, 'legal') || str_contains($text, 'investment'); } /** * @return array */ private function extractCapabilityTags(Tool $tool): array { $text = mb_strtolower($tool->name.' '.$tool->summary.' '.$tool->description); $tagMap = [ '写作' => '内容写作', '文案' => '文案生成', '翻译' => '多语翻译', '图像' => '图像生成', '图片' => '图像处理', '视频' => '视频生成', '语音' => '语音处理', '音频' => '音频处理', '客服' => '智能客服', '代码' => '代码助手', '编程' => '开发辅助', '搜索' => '知识检索', '自动化' => '流程自动化', 'agent' => 'Agent 工作流', 'api' => '开放 API', ]; $tags = []; foreach ($tagMap as $keyword => $label) { if (str_contains($text, $keyword)) { $tags[] = $label; } } if ($tool->has_api) { $tags[] = 'API 集成'; } if ($tool->pricing_type === 'free') { $tags[] = '完全免费'; } if ($tool->pricing_type === 'freemium') { $tags[] = '免费增值'; } if ($tool->pricing_type === 'paid') { $tags[] = '商业付费'; } if ($tool->category?->name !== null) { $tags[] = $tool->category->name; } return array_values(array_unique(array_slice($tags, 0, 8))); } }