配置功能完善
This commit is contained in:
@@ -8,7 +8,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\AiModel;
|
||||
use App\Models\Article;
|
||||
use App\Models\Category;
|
||||
use App\Models\SiteSetting;
|
||||
use App\Models\HomeModule;
|
||||
use App\Models\Tool;
|
||||
use App\Support\MarkdownRenderer;
|
||||
use Illuminate\Contracts\View\View;
|
||||
@@ -28,7 +28,7 @@ class ToolController extends Controller
|
||||
$querySignature = sha1((string) $request->getQueryString());
|
||||
$activeTab = $this->resolveTab($request);
|
||||
|
||||
$payload = Cache::remember("tools_portal_v2_{$querySignature}", now()->addMinutes(10), function () use ($request, $activeTab): array {
|
||||
$payload = Cache::remember("tools_portal_v3_home_{$querySignature}", now()->addMinutes(10), function () use ($request, $activeTab): array {
|
||||
$builder = Tool::query()
|
||||
->published()
|
||||
->with('category');
|
||||
@@ -49,8 +49,15 @@ class ToolController extends Controller
|
||||
$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);
|
||||
$modules = $this->loadHomeModules();
|
||||
$moduleMap = collect($modules)->keyBy('module_key');
|
||||
|
||||
$hotToolsLimit = (int) data_get($moduleMap, 'hot_tools.limit', 18);
|
||||
$latestToolsLimit = (int) data_get($moduleMap, 'latest_tools.limit', 18);
|
||||
$sectionLimit = (int) data_get($moduleMap, 'category_sections.limit', 18);
|
||||
|
||||
$categorySections = $categories->map(function (Category $category) use ($toolsByCategory, $sectionLimit): array {
|
||||
$sectionTools = $toolsByCategory->get($category->slug, collect())->take($sectionLimit);
|
||||
|
||||
return [
|
||||
'slug' => $category->slug,
|
||||
@@ -60,44 +67,6 @@ class ToolController extends Controller
|
||||
];
|
||||
})->values();
|
||||
|
||||
$moduleConfig = SiteSetting::query()
|
||||
->where('setting_key', 'home_modules')
|
||||
->value('setting_value');
|
||||
|
||||
$modules = collect([
|
||||
'hot_tools' => ['enabled' => true, 'limit' => 18],
|
||||
'latest_tools' => ['enabled' => true, 'limit' => 18],
|
||||
'category_sections' => ['enabled' => true, 'limit' => 18],
|
||||
'channel_cards' => ['enabled' => true, 'limit' => 1],
|
||||
'promo_banners' => ['enabled' => true, 'limit' => 1],
|
||||
]);
|
||||
|
||||
if (is_array($moduleConfig)) {
|
||||
foreach ($moduleConfig as $module) {
|
||||
if (!is_array($module) || empty($module['key'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = (string) $module['key'];
|
||||
if ($modules->has($key)) {
|
||||
$modules[$key] = [
|
||||
'enabled' => (bool) ($module['enabled'] ?? true),
|
||||
'limit' => max(1, min(30, (int) ($module['limit'] ?? 18))),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hotToolsLimit = (int) ($modules['hot_tools']['limit'] ?? 18);
|
||||
$latestToolsLimit = (int) ($modules['latest_tools']['limit'] ?? 18);
|
||||
$sectionLimit = (int) ($modules['category_sections']['limit'] ?? 18);
|
||||
|
||||
$categorySections = $categorySections->map(function (array $section) use ($sectionLimit): array {
|
||||
$section['tools'] = $section['tools']->take($sectionLimit);
|
||||
|
||||
return $section;
|
||||
});
|
||||
|
||||
return [
|
||||
'categories' => $categories,
|
||||
'categorySections' => $categorySections,
|
||||
@@ -107,7 +76,7 @@ class ToolController extends Controller
|
||||
'activeTab' => $activeTab,
|
||||
'tabOptions' => $this->tabOptions(),
|
||||
'toolStats' => $this->buildToolStats(),
|
||||
'modules' => $modules,
|
||||
'modules' => $moduleMap,
|
||||
];
|
||||
});
|
||||
|
||||
@@ -299,7 +268,7 @@ class ToolController extends Controller
|
||||
'搜索' => '知识检索',
|
||||
'自动化' => '流程自动化',
|
||||
'agent' => 'Agent 工作流',
|
||||
'api' => '开放 API',
|
||||
'api' => '开放API',
|
||||
];
|
||||
|
||||
$tags = [];
|
||||
@@ -331,4 +300,244 @@ class ToolController extends Controller
|
||||
|
||||
return array_values(array_unique(array_slice($tags, 0, 8)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function loadHomeModules(): array
|
||||
{
|
||||
$defaults = $this->defaultHomeModules();
|
||||
|
||||
$records = HomeModule::query()
|
||||
->with(['items' => fn ($query) => $query->where('enabled', true)->orderBy('sort_order')->orderBy('id')])
|
||||
->orderBy('sort_order')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
if ($records->isEmpty()) {
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
$filled = [];
|
||||
foreach ($records as $record) {
|
||||
$default = collect($defaults)->firstWhere('module_key', $record->module_key);
|
||||
if (!is_array($default)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$items = $record->items->map(function ($item): array {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'title' => (string) ($item->title ?? ''),
|
||||
'subtitle' => (string) ($item->subtitle ?? ''),
|
||||
'image_path' => (string) ($item->image_path ?? ''),
|
||||
'link_type' => (string) ($item->link_type ?? 'route'),
|
||||
'link_target' => (string) ($item->link_target ?? ''),
|
||||
'url' => $this->resolveLink((string) ($item->link_type ?? 'route'), (string) ($item->link_target ?? '')),
|
||||
];
|
||||
})->values()->all();
|
||||
|
||||
$filled[] = [
|
||||
'module_key' => $record->module_key,
|
||||
'name' => $record->name,
|
||||
'title' => $record->title ?: $default['title'],
|
||||
'subtitle' => $record->subtitle ?: $default['subtitle'],
|
||||
'enabled' => (bool) $record->enabled,
|
||||
'sort_order' => (int) $record->sort_order,
|
||||
'limit' => max(1, min(30, (int) $record->limit)),
|
||||
'more_link_type' => $record->more_link_type,
|
||||
'more_link_target' => $record->more_link_target,
|
||||
'more_url' => $this->resolveLink((string) $record->more_link_type, (string) $record->more_link_target),
|
||||
'extra' => is_array($record->extra) ? $record->extra : [],
|
||||
'items' => $items,
|
||||
];
|
||||
}
|
||||
|
||||
if ($filled === []) {
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
return $this->ensureModuleDefaults($filled, $defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function defaultHomeModules(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'module_key' => 'channel_cards',
|
||||
'name' => '频道卡片',
|
||||
'title' => '频道入口',
|
||||
'subtitle' => '模型、资讯、教程等入口合集',
|
||||
'enabled' => true,
|
||||
'sort_order' => 20,
|
||||
'limit' => 5,
|
||||
'more_link_type' => null,
|
||||
'more_link_target' => null,
|
||||
'more_url' => null,
|
||||
'extra' => [
|
||||
'side_title' => '频道导航',
|
||||
'side_subtitle' => '快速直达',
|
||||
],
|
||||
'items' => [
|
||||
[
|
||||
'title' => '每日快讯',
|
||||
'subtitle' => 'AI 资讯',
|
||||
'image_path' => '',
|
||||
'link_type' => 'route',
|
||||
'link_target' => 'news.index',
|
||||
'url' => route('news.index'),
|
||||
],
|
||||
[
|
||||
'title' => '最新模型',
|
||||
'subtitle' => '模型推荐',
|
||||
'image_path' => '',
|
||||
'link_type' => 'route',
|
||||
'link_target' => 'models.index',
|
||||
'url' => route('models.index'),
|
||||
],
|
||||
[
|
||||
'title' => '热门教程',
|
||||
'subtitle' => '教程学习',
|
||||
'image_path' => '',
|
||||
'link_type' => 'route',
|
||||
'link_target' => 'guides.index',
|
||||
'url' => route('guides.index'),
|
||||
],
|
||||
[
|
||||
'title' => '工具列表',
|
||||
'subtitle' => '查看全部',
|
||||
'image_path' => '',
|
||||
'link_type' => 'route',
|
||||
'link_target' => 'tools.list',
|
||||
'url' => route('tools.list'),
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'module_key' => 'promo_banners',
|
||||
'name' => '横幅推荐',
|
||||
'title' => '精选推荐',
|
||||
'subtitle' => '运营位横幅可配置',
|
||||
'enabled' => true,
|
||||
'sort_order' => 30,
|
||||
'limit' => 2,
|
||||
'more_link_type' => null,
|
||||
'more_link_target' => null,
|
||||
'more_url' => null,
|
||||
'extra' => [],
|
||||
'items' => [
|
||||
[
|
||||
'title' => '超全图像视频模板一键复制',
|
||||
'subtitle' => '全球顶尖模型',
|
||||
'image_path' => '',
|
||||
'link_type' => 'route',
|
||||
'link_target' => 'tools.list',
|
||||
'url' => route('tools.list'),
|
||||
],
|
||||
[
|
||||
'title' => '一站式 AI 创作平台',
|
||||
'subtitle' => '免费试用',
|
||||
'image_path' => '',
|
||||
'link_type' => 'route',
|
||||
'link_target' => 'tools.list',
|
||||
'url' => route('tools.list'),
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'module_key' => 'hot_tools',
|
||||
'name' => '热门工具',
|
||||
'title' => '热门工具',
|
||||
'subtitle' => '根据推荐排序筛选',
|
||||
'enabled' => true,
|
||||
'sort_order' => 40,
|
||||
'limit' => 18,
|
||||
'more_link_type' => 'route',
|
||||
'more_link_target' => 'tools.list',
|
||||
'more_url' => route('tools.list', ['tab' => 'recommended']),
|
||||
'extra' => ['side_title' => '热门工具'],
|
||||
'items' => [],
|
||||
],
|
||||
[
|
||||
'module_key' => 'latest_tools',
|
||||
'name' => '最新收录',
|
||||
'title' => '最新收录',
|
||||
'subtitle' => '按发布时间倒序',
|
||||
'enabled' => true,
|
||||
'sort_order' => 50,
|
||||
'limit' => 18,
|
||||
'more_link_type' => 'route',
|
||||
'more_link_target' => 'tools.list',
|
||||
'more_url' => route('tools.list', ['tab' => 'latest']),
|
||||
'extra' => ['side_title' => '最新收录'],
|
||||
'items' => [],
|
||||
],
|
||||
[
|
||||
'module_key' => 'category_sections',
|
||||
'name' => '分类分块',
|
||||
'title' => '分类分块',
|
||||
'subtitle' => '按分类浏览工具',
|
||||
'enabled' => true,
|
||||
'sort_order' => 60,
|
||||
'limit' => 18,
|
||||
'more_link_type' => null,
|
||||
'more_link_target' => null,
|
||||
'more_url' => null,
|
||||
'extra' => ['side_title' => '分类导航'],
|
||||
'items' => [],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $loaded
|
||||
* @param array<int, array<string, mixed>> $defaults
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function ensureModuleDefaults(array $loaded, array $defaults): array
|
||||
{
|
||||
$loadedByKey = collect($loaded)->keyBy('module_key');
|
||||
$merged = [];
|
||||
|
||||
foreach ($defaults as $default) {
|
||||
$existing = $loadedByKey->get($default['module_key']);
|
||||
if (!is_array($existing)) {
|
||||
$merged[] = $default;
|
||||
continue;
|
||||
}
|
||||
|
||||
$merged[] = array_merge($default, $existing, [
|
||||
'items' => $existing['items'] ?? $default['items'],
|
||||
'extra' => array_merge($default['extra'] ?? [], $existing['extra'] ?? []),
|
||||
]);
|
||||
}
|
||||
|
||||
usort($merged, fn (array $a, array $b): int => (int) $a['sort_order'] <=> (int) $b['sort_order']);
|
||||
|
||||
return $merged;
|
||||
}
|
||||
|
||||
private function resolveLink(string $type, string $target): ?string
|
||||
{
|
||||
if ($type === 'route') {
|
||||
if ($target === '' || !\Illuminate\Support\Facades\Route::has($target)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return route($target);
|
||||
}
|
||||
|
||||
if ($target === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str_starts_with($target, '/')) {
|
||||
return $target;
|
||||
}
|
||||
|
||||
return filter_var($target, FILTER_VALIDATE_URL) ? $target : null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user