优化功能
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled

This commit is contained in:
jiangdong.cheng
2026-02-12 17:10:36 +08:00
parent 56c685b579
commit a795b2c896
29 changed files with 2155 additions and 884 deletions

View File

@@ -11,135 +11,79 @@ use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route as RouteFacade;
use Illuminate\Validation\Rule;
class SiteSettingController extends Controller
{
public function index(): View
{
$this->ensureDefaultModules();
$modules = HomeModule::query()
->with(['items' => fn ($query) => $query->orderBy('sort_order')->orderBy('id')])
->withCount('items')
->orderBy('sort_order')
->orderBy('id')
->get();
if ($modules->isEmpty()) {
$this->ensureDefaultModules();
$modules = HomeModule::query()
->with(['items' => fn ($query) => $query->orderBy('sort_order')->orderBy('id')])
->orderBy('sort_order')
->orderBy('id')
->get();
}
return view('admin.settings.index', [
'modules' => $modules,
]);
}
public function show(HomeModule $module): View
{
$this->ensureDefaultModules();
$module->load([
'items' => fn ($query) => $query->orderBy('sort_order')->orderBy('id'),
]);
return view('admin.settings.show', [
'module' => $module,
'moduleNav' => $this->moduleNavigation(),
'routeOptions' => $this->routeOptions(),
]);
}
public function update(Request $request): RedirectResponse
public function update(Request $request, HomeModule $module): RedirectResponse
{
$moduleData = $request->input('modules', []);
if (!is_array($moduleData) || $moduleData === []) {
return redirect()
->route('admin.settings.index')
->with('status', '未提交有效的模块配置。');
}
$validated = $request->validate([
'name' => ['required', 'string', 'max:120'],
'title' => ['nullable', 'string', 'max:160'],
'subtitle' => ['nullable', 'string', 'max:255'],
'enabled' => ['nullable', 'boolean'],
'sort_order' => ['nullable', 'integer', 'min:0', 'max:9999'],
'limit' => ['nullable', 'integer', 'min:1', 'max:30'],
'more_link_type' => ['nullable', Rule::in(['route', 'url'])],
'more_link_target' => ['nullable', 'string', 'max:255'],
'extra.side_title' => ['nullable', 'string', 'max:120'],
'extra.side_subtitle' => ['nullable', 'string', 'max:255'],
]);
foreach ($moduleData as $moduleId => $moduleInput) {
if (!is_numeric($moduleId) || !is_array($moduleInput)) {
continue;
}
$module->fill([
'name' => $validated['name'],
'title' => $validated['title'] ?? null,
'subtitle' => $validated['subtitle'] ?? null,
'enabled' => (bool) ($validated['enabled'] ?? false),
'sort_order' => (int) ($validated['sort_order'] ?? $module->sort_order),
'limit' => (int) ($validated['limit'] ?? $module->limit),
'more_link_type' => $validated['more_link_type'] ?? null,
'more_link_target' => $validated['more_link_target'] ?? null,
'extra' => [
'side_title' => data_get($validated, 'extra.side_title'),
'side_subtitle' => data_get($validated, 'extra.side_subtitle'),
],
]);
$module = HomeModule::query()->find((int) $moduleId);
if (! $module instanceof HomeModule) {
continue;
}
$validatedModule = validator($moduleInput, [
'name' => ['required', 'string', 'max:120'],
'title' => ['nullable', 'string', 'max:160'],
'subtitle' => ['nullable', 'string', 'max:255'],
'enabled' => ['nullable', 'boolean'],
'sort_order' => ['nullable', 'integer', 'min:0', 'max:9999'],
'limit' => ['nullable', 'integer', 'min:1', 'max:30'],
'more_link_type' => ['nullable', Rule::in(['route', 'url'])],
'more_link_target' => ['nullable', 'string', 'max:255'],
'extra.side_title' => ['nullable', 'string', 'max:120'],
'extra.side_subtitle' => ['nullable', 'string', 'max:255'],
])->validate();
$module->fill([
'name' => $validatedModule['name'],
'title' => $validatedModule['title'] ?? null,
'subtitle' => $validatedModule['subtitle'] ?? null,
'enabled' => (bool) ($validatedModule['enabled'] ?? false),
'sort_order' => (int) ($validatedModule['sort_order'] ?? $module->sort_order),
'limit' => (int) ($validatedModule['limit'] ?? $module->limit),
'more_link_type' => $validatedModule['more_link_type'] ?? null,
'more_link_target' => $validatedModule['more_link_target'] ?? null,
'extra' => [
'side_title' => data_get($validatedModule, 'extra.side_title'),
'side_subtitle' => data_get($validatedModule, 'extra.side_subtitle'),
],
]);
$this->validateModuleLink($module->module_key, $module->more_link_type, $module->more_link_target);
$module->save();
$itemsInput = $moduleInput['items'] ?? [];
if (!is_array($itemsInput)) {
continue;
}
foreach ($itemsInput as $itemId => $itemInput) {
if (!is_numeric($itemId) || !is_array($itemInput)) {
continue;
}
$item = HomeModuleItem::query()
->where('home_module_id', $module->id)
->find((int) $itemId);
if (! $item instanceof HomeModuleItem) {
continue;
}
$validatedItem = validator($itemInput, [
'title' => ['nullable', 'string', 'max:160'],
'subtitle' => ['nullable', 'string', 'max:255'],
'image_path' => ['nullable', 'string', 'max:255'],
'link_type' => ['nullable', Rule::in(['route', 'url'])],
'link_target' => ['nullable', 'string', 'max:255'],
'sort_order' => ['nullable', 'integer', 'min:0', 'max:9999'],
'enabled' => ['nullable', 'boolean'],
])->validate();
$linkType = $validatedItem['link_type'] ?? 'route';
$linkTarget = $validatedItem['link_target'] ?? null;
$this->validateItemRules($module->module_key, $validatedItem);
$this->validateItemLink($linkType, $linkTarget);
$item->fill([
'title' => $validatedItem['title'] ?? null,
'subtitle' => $validatedItem['subtitle'] ?? null,
'image_path' => $validatedItem['image_path'] ?? null,
'link_type' => $linkType,
'link_target' => $linkTarget,
'sort_order' => (int) ($validatedItem['sort_order'] ?? $item->sort_order),
'enabled' => (bool) ($validatedItem['enabled'] ?? false),
])->save();
}
}
$this->validateModuleLink($module->module_key, $module->more_link_type, $module->more_link_target);
$module->save();
$this->clearHomeCaches();
return redirect()
->route('admin.settings.index')
->with('status', '首页模块配置已更新。');
->route('admin.settings.show', $module)
->with('status', '模块配置已更新。');
}
public function storeItem(Request $request, HomeModule $module): RedirectResponse
@@ -155,7 +99,7 @@ class SiteSettingController extends Controller
]);
$this->validateItemRules($module->module_key, $validated);
$this->validateItemLink($validated['link_type'], $validated['link_target'] ?? null);
$this->validateItemLink($module->module_key, $validated['link_type'], $validated['link_target'] ?? null);
$module->items()->create([
'title' => $validated['title'] ?? null,
@@ -170,10 +114,60 @@ class SiteSettingController extends Controller
$this->clearHomeCaches();
return redirect()
->route('admin.settings.index')
->route('admin.settings.show', $module)
->with('status', '模块条目已新增。');
}
public function editItem(HomeModule $module, HomeModuleItem $item): View
{
if ($item->home_module_id !== $module->id) {
abort(404);
}
return view('admin.settings.item-form', [
'module' => $module,
'item' => $item,
'moduleNav' => $this->moduleNavigation(),
'routeOptions' => $this->routeOptions(),
]);
}
public function updateItem(Request $request, HomeModule $module, HomeModuleItem $item): RedirectResponse
{
if ($item->home_module_id !== $module->id) {
abort(404);
}
$validated = $request->validate([
'title' => ['nullable', 'string', 'max:160'],
'subtitle' => ['nullable', 'string', 'max:255'],
'image_path' => ['nullable', 'string', 'max:255'],
'link_type' => ['required', Rule::in(['route', 'url'])],
'link_target' => ['nullable', 'string', 'max:255'],
'sort_order' => ['nullable', 'integer', 'min:0', 'max:9999'],
'enabled' => ['nullable', 'boolean'],
]);
$this->validateItemRules($module->module_key, $validated);
$this->validateItemLink($module->module_key, $validated['link_type'], $validated['link_target'] ?? null);
$item->fill([
'title' => $validated['title'] ?? null,
'subtitle' => $validated['subtitle'] ?? null,
'image_path' => $validated['image_path'] ?? null,
'link_type' => $validated['link_type'],
'link_target' => $validated['link_target'] ?? null,
'sort_order' => (int) ($validated['sort_order'] ?? $item->sort_order),
'enabled' => (bool) ($validated['enabled'] ?? false),
])->save();
$this->clearHomeCaches();
return redirect()
->route('admin.settings.show', $module)
->with('status', '模块条目已更新。');
}
public function destroyItem(HomeModule $module, HomeModuleItem $item): RedirectResponse
{
if ($item->home_module_id !== $module->id) {
@@ -184,7 +178,7 @@ class SiteSettingController extends Controller
$this->clearHomeCaches();
return redirect()
->route('admin.settings.index')
->route('admin.settings.show', $module)
->with('status', '模块条目已删除。');
}
@@ -193,13 +187,72 @@ class SiteSettingController extends Controller
*/
private function routeOptions(): array
{
return [
['name' => '工具集首页', 'value' => 'tools.index'],
['name' => '工具列表页', 'value' => 'tools.list'],
['name' => '模型推荐', 'value' => 'models.index'],
['name' => '文章资讯', 'value' => 'news.index'],
['name' => '教程学习', 'value' => 'guides.index'],
$allRouteNames = collect(RouteFacade::getRoutes()->getRoutesByName())
->filter(function ($route, $name): bool {
if (!is_string($name) || $name === '') {
return false;
}
if (! method_exists($route, 'parameterNames')) {
return false;
}
return count($route->parameterNames()) === 0;
})
->keys()
->map(fn ($name) => (string) $name)
->filter(fn (string $name) => ! str_starts_with($name, 'admin.'))
->filter(fn (string $name) => ! str_starts_with($name, 'debugbar.'))
->filter(fn (string $name) => ! str_starts_with($name, 'ignition.'))
->filter(fn (string $name) => ! str_starts_with($name, '_'))
->values();
$priority = [
'home',
'tools.index',
'tools.list',
'models.index',
'news.index',
'guides.index',
];
$priorityRoutes = collect($priority)
->filter(fn (string $name) => $allRouteNames->contains($name));
$remainingRoutes = $allRouteNames
->reject(fn (string $name) => in_array($name, $priority, true))
->sort()
->values();
return $priorityRoutes
->concat($remainingRoutes)
->map(fn (string $name) => [
'name' => $this->routeLabel($name),
'value' => $name,
])
->values()
->all();
}
private function routeLabel(string $name): string
{
return match ($name) {
'home' => '首页',
'tools.index' => '工具首页',
'tools.list' => '工具列表',
'models.index' => '模型推荐',
'news.index' => '文章资讯',
'guides.index' => '教程学习',
default => $name,
};
}
private function moduleNavigation()
{
return HomeModule::query()
->orderBy('sort_order')
->orderBy('id')
->get(['id', 'name', 'module_key']);
}
private function ensureDefaultModules(): void
@@ -231,11 +284,17 @@ class SiteSettingController extends Controller
return;
}
if ($type === null && $target === null) {
$target = trim((string) $target);
if ($type === null && $target === '') {
return;
}
if ($type === 'route') {
if ($target === '') {
abort(422, '请选择有效的“更多链接”路由。');
}
if (! in_array($target, array_column($this->routeOptions(), 'value'), true)) {
abort(422, '模块“更多链接”路由不在允许范围内。');
}
@@ -243,7 +302,7 @@ class SiteSettingController extends Controller
return;
}
if ($type === 'url' && $target !== null && ! $this->isValidUrlOrPath($target)) {
if ($type === 'url' && $target !== '' && ! $this->isValidUrlOrPath($target)) {
abort(422, '模块“更多链接”URL格式不正确。');
}
}
@@ -263,17 +322,36 @@ class SiteSettingController extends Controller
}
}
private function validateItemLink(string $type, ?string $target): void
private function validateItemLink(string $moduleKey, string $type, ?string $target): void
{
$target = trim((string) $target);
$isStrictModule = in_array($moduleKey, ['channel_cards', 'promo_banners'], true);
if ($type === 'route') {
if (! in_array((string) $target, array_column($this->routeOptions(), 'value'), true)) {
abort(422, '条目路由不在允许范围内。');
if ($target === '') {
if ($isStrictModule) {
abort(422, '频道卡片/横幅推荐条目必须填写链接目标。');
}
return;
}
if (! in_array($target, array_column($this->routeOptions(), 'value'), true)) {
abort(422, '请选择有效的前台路由,或将链接类型改为 URL。');
}
return;
}
if ($target === null || ! $this->isValidUrlOrPath($target)) {
if ($target === '') {
if ($isStrictModule) {
abort(422, '频道卡片/横幅推荐条目必须填写链接目标。');
}
return;
}
if (! $this->isValidUrlOrPath($target)) {
abort(422, '条目URL格式不正确。');
}
}
@@ -292,4 +370,3 @@ class SiteSettingController extends Controller
Cache::flush();
}
}