页面优化,功能修复
This commit is contained in:
47
app/Http/Controllers/Admin/FeedbackController.php
Normal file
47
app/Http/Controllers/Admin/FeedbackController.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\FeedbackEntry;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FeedbackController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$builder = FeedbackEntry::query()->latest('id');
|
||||
|
||||
if ($request->filled('type')) {
|
||||
$builder->where('feedback_type', (string) $request->string('type'));
|
||||
}
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$builder->where('status', (string) $request->string('status'));
|
||||
}
|
||||
|
||||
return view('admin.feedback.index', [
|
||||
'items' => $builder->paginate(30)->withQueryString(),
|
||||
'filters' => $request->only(['type', 'status']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateStatus(FeedbackEntry $feedback, Request $request): RedirectResponse
|
||||
{
|
||||
$status = (string) $request->input('status', 'new');
|
||||
if (!in_array($status, ['new', 'reviewing', 'done'], true)) {
|
||||
$status = 'new';
|
||||
}
|
||||
|
||||
$feedback->update(['status' => $status]);
|
||||
|
||||
return redirect()
|
||||
->route('admin.feedback.index')
|
||||
->with('status', '反馈状态已更新');
|
||||
}
|
||||
}
|
||||
|
||||
76
app/Http/Controllers/Admin/SiteSettingController.php
Normal file
76
app/Http/Controllers/Admin/SiteSettingController.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\SiteSetting;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SiteSettingController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$defaults = $this->defaultModules();
|
||||
$stored = SiteSetting::query()->where('setting_key', 'home_modules')->value('setting_value');
|
||||
|
||||
$modules = $defaults;
|
||||
if (is_array($stored)) {
|
||||
$storedByKey = collect($stored)->keyBy('key');
|
||||
$modules = array_map(function (array $module) use ($storedByKey): array {
|
||||
$saved = $storedByKey->get($module['key']);
|
||||
if (is_array($saved)) {
|
||||
$module['enabled'] = (bool) ($saved['enabled'] ?? true);
|
||||
$module['limit'] = (int) ($saved['limit'] ?? $module['limit']);
|
||||
}
|
||||
|
||||
return $module;
|
||||
}, $defaults);
|
||||
}
|
||||
|
||||
return view('admin.settings.index', [
|
||||
'modules' => $modules,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$payload = [];
|
||||
foreach ($this->defaultModules() as $module) {
|
||||
$key = $module['key'];
|
||||
$payload[] = [
|
||||
'key' => $key,
|
||||
'label' => $module['label'],
|
||||
'enabled' => $request->boolean("modules.{$key}.enabled", true),
|
||||
'limit' => max(1, min(30, (int) $request->input("modules.{$key}.limit", $module['limit']))),
|
||||
];
|
||||
}
|
||||
|
||||
SiteSetting::query()->updateOrCreate(
|
||||
['setting_key' => 'home_modules'],
|
||||
['setting_value' => $payload],
|
||||
);
|
||||
|
||||
return redirect()
|
||||
->route('admin.settings.index')
|
||||
->with('status', '首页模块配置已更新');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{key:string,label:string,enabled:bool,limit:int}>
|
||||
*/
|
||||
private function defaultModules(): array
|
||||
{
|
||||
return [
|
||||
['key' => 'hot_tools', 'label' => '热门工具', 'enabled' => true, 'limit' => 18],
|
||||
['key' => 'latest_tools', 'label' => '最新收录', 'enabled' => true, 'limit' => 18],
|
||||
['key' => 'category_sections', 'label' => '分类分块', 'enabled' => true, 'limit' => 18],
|
||||
['key' => 'channel_cards', 'label' => '频道卡片区', 'enabled' => true, 'limit' => 1],
|
||||
['key' => 'promo_banners', 'label' => '横幅推荐区', 'enabled' => true, 'limit' => 1],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ class UploadController extends Controller
|
||||
Storage::disk('public')->put($mainPath, $mainBinary);
|
||||
Storage::disk('public')->put($thumbPath, $thumbBinary);
|
||||
|
||||
$mainUrl = Storage::disk('public')->url($mainPath);
|
||||
$thumbUrl = Storage::disk('public')->url($thumbPath);
|
||||
$mainUrl = '/storage/'.ltrim($mainPath, '/');
|
||||
$thumbUrl = '/storage/'.ltrim($thumbPath, '/');
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
||||
41
app/Http/Controllers/Site/FeedbackController.php
Normal file
41
app/Http/Controllers/Site/FeedbackController.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Site;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\FeedbackEntry;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FeedbackController extends Controller
|
||||
{
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'feedback_type' => ['required', 'in:tool,model,news,guide,other'],
|
||||
'title' => ['required', 'string', 'max:180'],
|
||||
'description' => ['required', 'string', 'max:4000'],
|
||||
'contact' => ['nullable', 'string', 'max:160'],
|
||||
], [
|
||||
'feedback_type.required' => '请选择反馈类型',
|
||||
'title.required' => '请填写标题',
|
||||
'description.required' => '请填写详细说明',
|
||||
]);
|
||||
|
||||
FeedbackEntry::query()->create([
|
||||
'feedback_type' => $validated['feedback_type'],
|
||||
'title' => $validated['title'],
|
||||
'description' => $validated['description'],
|
||||
'contact' => $validated['contact'] ?? null,
|
||||
'status' => 'new',
|
||||
'ip_address' => $request->ip(),
|
||||
]);
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('status', '反馈提交成功,感谢你的建议');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +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\Tool;
|
||||
use App\Support\MarkdownRenderer;
|
||||
use Illuminate\Contracts\View\View;
|
||||
@@ -59,15 +60,54 @@ 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,
|
||||
'hotTools' => $portalTools->take(18),
|
||||
'latestTools' => Tool::query()->published()->with('category')->latest('published_at')->limit(18)->get(),
|
||||
'hotTools' => $portalTools->take($hotToolsLimit),
|
||||
'latestTools' => Tool::query()->published()->with('category')->latest('published_at')->limit($latestToolsLimit)->get(),
|
||||
'filters' => $request->only(['q', 'pricing', 'api', 'tab']),
|
||||
'activeTab' => $activeTab,
|
||||
'tabOptions' => $this->tabOptions(),
|
||||
'toolStats' => $this->buildToolStats(),
|
||||
'modules' => $modules,
|
||||
];
|
||||
});
|
||||
|
||||
@@ -292,4 +332,3 @@ class ToolController extends Controller
|
||||
return array_values(array_unique(array_slice($tags, 0, 8)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
23
app/Models/FeedbackEntry.php
Normal file
23
app/Models/FeedbackEntry.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class FeedbackEntry extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'feedback_type',
|
||||
'title',
|
||||
'description',
|
||||
'contact',
|
||||
'status',
|
||||
'ip_address',
|
||||
];
|
||||
}
|
||||
|
||||
26
app/Models/SiteSetting.php
Normal file
26
app/Models/SiteSetting.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SiteSetting extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'setting_key',
|
||||
'setting_value',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'setting_value' => 'array',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,19 @@ class MarkdownRenderer
|
||||
return '';
|
||||
}
|
||||
|
||||
$markdown = $this->normalizeStorageLinks($markdown);
|
||||
|
||||
return (string) Str::markdown($markdown, [
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
private function normalizeStorageLinks(string $markdown): string
|
||||
{
|
||||
$pattern = '/https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?\/storage\/([^\s)"\'\<\>]+)/i';
|
||||
$normalized = preg_replace($pattern, '/storage/$1', $markdown);
|
||||
|
||||
return is_string($normalized) ? $normalized : $markdown;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user