前端极简黑白重设计,并纳入客户端注册、站点设置与后台相关改动

- storefront 全站改为极简黑白配色:直角、细线分隔、卡片 hover 浅底、深色模式(跟随系统+手动切换)
- 抽出 storefront/partials/card 复用卡片
- 一并提交此前暂存的客户端注册 API、站点设置、后台页面与测试

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
浪子
2026-09-22 08:35:49 +08:00
parent 62cffc6f5f
commit 04a2034975
41 changed files with 3447 additions and 658 deletions
@@ -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'),
];
}
}