前端极简黑白重设计,并纳入客户端注册、站点设置与后台相关改动
- storefront 全站改为极简黑白配色:直角、细线分隔、卡片 hover 浅底、深色模式(跟随系统+手动切换) - 抽出 storefront/partials/card 复用卡片 - 一并提交此前暂存的客户端注册 API、站点设置、后台页面与测试 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -50,4 +50,57 @@ class AuthController extends Controller
|
||||
|
||||
return redirect()->route('webadmin.login')->with('success', '已退出后台');
|
||||
}
|
||||
|
||||
public function showAccount(Request $request): View
|
||||
{
|
||||
return view('admin.account', [
|
||||
'user' => $request->user(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updatePassword(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'current_password' => ['required', 'current_password'],
|
||||
'new_password' => ['required', 'string', 'min:8', 'confirmed', 'different:current_password'],
|
||||
], [
|
||||
'current_password.required' => '请输入当前密码',
|
||||
'current_password.current_password' => '当前密码不正确',
|
||||
'new_password.required' => '请输入新密码',
|
||||
'new_password.min' => '新密码至少需要 8 位',
|
||||
'new_password.confirmed' => '两次输入的新密码不一致',
|
||||
'new_password.different' => '新密码不能与当前密码相同',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
$user->forceFill([
|
||||
'password' => $validated['new_password'],
|
||||
])->save();
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->route('webadmin.account')->with('success', '密码已更新');
|
||||
}
|
||||
|
||||
public function updateProfile(Request $request): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => ['required', 'string', 'max:120'],
|
||||
'email' => ['required', 'email', 'max:255', 'unique:users,email,' . $user->id],
|
||||
], [
|
||||
'name.required' => '请输入用户名',
|
||||
'email.required' => '请输入邮箱地址',
|
||||
'email.email' => '邮箱格式不正确',
|
||||
'email.unique' => '该邮箱已被其它用户使用',
|
||||
]);
|
||||
|
||||
$user->forceFill([
|
||||
'name' => trim($validated['name']),
|
||||
'email' => trim($validated['email']),
|
||||
])->save();
|
||||
|
||||
return redirect()->route('webadmin.account')->with('success', '账户资料已更新');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@ use App\Http\Requests\Admin\StoreCategoryRequest;
|
||||
use App\Http\Requests\Admin\StorePackageRequest;
|
||||
use App\Models\Category;
|
||||
use App\Models\Package;
|
||||
use App\Models\StoreClient;
|
||||
use App\Models\Version;
|
||||
use App\Services\AdminPackageService;
|
||||
use App\Services\VersionPublishService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class DashboardController extends Controller
|
||||
@@ -32,7 +34,7 @@ class DashboardController extends Controller
|
||||
'versions' => Version::count(),
|
||||
'downloads' => (int) Package::sum('download_count'),
|
||||
'categories' => Category::count(),
|
||||
];
|
||||
] + $this->buildClientStats();
|
||||
|
||||
$recentPackages = Package::query()
|
||||
->with(['categories', 'latestStableVersion'])
|
||||
@@ -46,7 +48,57 @@ class DashboardController extends Controller
|
||||
->limit(8)
|
||||
->get();
|
||||
|
||||
return view('admin.dashboard', compact('stats', 'recentPackages', 'recentVersions'));
|
||||
$recentClients = StoreClient::query()
|
||||
->orderByDesc('last_seen_at')
|
||||
->orderByDesc('registered_at')
|
||||
->limit(6)
|
||||
->get();
|
||||
|
||||
return view('admin.dashboard', compact('stats', 'recentPackages', 'recentVersions', 'recentClients'));
|
||||
}
|
||||
|
||||
public function clients(Request $request): View
|
||||
{
|
||||
$query = StoreClient::query();
|
||||
$status = (string) $request->query('status', '');
|
||||
$keyword = trim((string) $request->query('keyword', ''));
|
||||
$onlineThreshold = now()->subMinutes((int) config('store.client_online_window_minutes', 15));
|
||||
|
||||
if ($status === 'online') {
|
||||
$query->whereNotNull('last_seen_at')
|
||||
->where('last_seen_at', '>=', $onlineThreshold);
|
||||
} elseif ($status === 'offline') {
|
||||
$query->where(function ($q) use ($onlineThreshold) {
|
||||
$q->whereNull('last_seen_at')
|
||||
->orWhere('last_seen_at', '<', $onlineThreshold);
|
||||
});
|
||||
}
|
||||
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$q->where('site_url', 'like', '%' . $keyword . '%')
|
||||
->orWhere('site_name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('typecho_version', 'like', '%' . $keyword . '%')
|
||||
->orWhere('php_version', 'like', '%' . $keyword . '%')
|
||||
->orWhere('plugin_version', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$clients = $query
|
||||
->orderByDesc('last_seen_at')
|
||||
->orderByDesc('registered_at')
|
||||
->paginate(20)
|
||||
->withQueryString();
|
||||
|
||||
return view('admin.clients.index', [
|
||||
'clients' => $clients,
|
||||
'stats' => $this->buildClientStats(),
|
||||
'onlineWindowMinutes' => (int) config('store.client_online_window_minutes', 15),
|
||||
'filters' => [
|
||||
'status' => $status,
|
||||
'keyword' => $keyword,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function packages(Request $request): View
|
||||
@@ -112,6 +164,15 @@ class DashboardController extends Controller
|
||||
return redirect()->route('webadmin.packages')->with('success', '状态已更新');
|
||||
}
|
||||
|
||||
public function destroyPackage(string $type, string $slug): RedirectResponse
|
||||
{
|
||||
$package = $this->findPackage($type, $slug);
|
||||
$label = $package->name;
|
||||
$this->service->deletePackage($package);
|
||||
|
||||
return redirect()->route('webadmin.packages', ['type' => $type])->with('success', '扩展已删除:' . $label);
|
||||
}
|
||||
|
||||
public function categories(Request $request): View
|
||||
{
|
||||
$query = Category::query()->withCount('packages');
|
||||
@@ -176,6 +237,34 @@ class DashboardController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeScreenshot(Request $request, string $type, string $slug): RedirectResponse
|
||||
{
|
||||
$package = $this->findPackage($type, $slug);
|
||||
|
||||
$validated = $request->validate([
|
||||
'image_url' => ['required', 'url', 'max:1024'],
|
||||
'caption' => ['nullable', 'string', 'max:255'],
|
||||
'sort_order' => ['nullable', 'integer'],
|
||||
]);
|
||||
|
||||
$package->screenshots()->create([
|
||||
'image_url' => $validated['image_url'],
|
||||
'caption' => trim((string) ($validated['caption'] ?? '')),
|
||||
'sort_order' => (int) ($validated['sort_order'] ?? 0),
|
||||
]);
|
||||
|
||||
return redirect()->route('webadmin.packages.show', [$type, $slug])->with('success', '截图已添加');
|
||||
}
|
||||
|
||||
public function destroyScreenshot(string $type, string $slug, int $id): RedirectResponse
|
||||
{
|
||||
$package = $this->findPackage($type, $slug);
|
||||
$screenshot = $package->screenshots()->whereKey($id)->firstOrFail();
|
||||
$screenshot->delete();
|
||||
|
||||
return redirect()->route('webadmin.packages.show', [$type, $slug])->with('success', '截图已删除');
|
||||
}
|
||||
|
||||
public function storeVersion(Request $request, string $type, string $slug): RedirectResponse
|
||||
{
|
||||
$package = $this->findPackage($type, $slug);
|
||||
@@ -261,10 +350,24 @@ class DashboardController extends Controller
|
||||
{
|
||||
$raw = (string) $request->input('categories_text', '');
|
||||
|
||||
return collect(preg_split('/[,,\s]+/u', $raw))
|
||||
$segments = preg_match('/[,,、;\r\n]+/u', $raw)
|
||||
? preg_split('/[,,、;\r\n]+/u', $raw)
|
||||
: preg_split('/\s+/u', $raw);
|
||||
|
||||
return collect($segments)
|
||||
->map(fn ($item) => trim((string) $item))
|
||||
->filter()
|
||||
->unique(fn (string $item) => mb_strtolower($item, 'UTF-8'))
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function buildClientStats(): array
|
||||
{
|
||||
return [
|
||||
'registered_sites' => StoreClient::query()->count(),
|
||||
'online_sites' => StoreClient::query()->online()->count(),
|
||||
'tracked_users' => (int) StoreClient::query()->sum('user_count'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\WebAdmin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\StoreSettings;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Throwable;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
public function __construct(private readonly StoreSettings $settings)
|
||||
{
|
||||
}
|
||||
|
||||
public function show(): View
|
||||
{
|
||||
return view('admin.settings', [
|
||||
'settings' => $this->settings->all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'site_name' => ['required', 'string', 'max:120'],
|
||||
'site_tagline' => ['required', 'string', 'max:160'],
|
||||
'home_title_suffix' => ['required', 'string', 'max:120'],
|
||||
'home_eyebrow' => ['required', 'string', 'max:80'],
|
||||
'home_headline' => ['required', 'string', 'max:160'],
|
||||
'home_lede' => ['required', 'string', 'max:1000'],
|
||||
'home_aside_kicker' => ['required', 'string', 'max:80'],
|
||||
'home_aside_title' => ['required', 'string', 'max:160'],
|
||||
'home_feature_one_title' => ['required', 'string', 'max:120'],
|
||||
'home_feature_one_body' => ['required', 'string', 'max:255'],
|
||||
'home_feature_two_title' => ['required', 'string', 'max:120'],
|
||||
'home_feature_two_body' => ['required', 'string', 'max:255'],
|
||||
'home_feature_three_title' => ['required', 'string', 'max:120'],
|
||||
'home_feature_three_body' => ['required', 'string', 'max:255'],
|
||||
'home_plugins_title' => ['required', 'string', 'max:120'],
|
||||
'home_plugins_subtitle' => ['required', 'string', 'max:255'],
|
||||
'home_themes_title' => ['required', 'string', 'max:120'],
|
||||
'home_themes_subtitle' => ['required', 'string', 'max:255'],
|
||||
]);
|
||||
|
||||
$payload = collect($validated)
|
||||
->map(fn ($value) => trim((string) $value))
|
||||
->all();
|
||||
|
||||
try {
|
||||
$this->settings->setMany($payload);
|
||||
} catch (Throwable $e) {
|
||||
return back()->withInput()->with('error', '站点设置保存失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('webadmin.settings')->with('success', '站点设置已更新');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user