Package::count(), 'plugins' => Package::where('type', 'plugin')->count(), 'themes' => Package::where('type', 'theme')->count(), 'versions' => Version::count(), 'downloads' => (int) Package::sum('download_count'), 'categories' => Category::count(), ]; $recentPackages = Package::query() ->with(['categories', 'latestStableVersion']) ->latest('updated_at') ->limit(6) ->get(); $recentVersions = Version::query() ->with('package') ->latest('published_at') ->limit(8) ->get(); return view('admin.dashboard', compact('stats', 'recentPackages', 'recentVersions')); } public function packages(Request $request): View { $query = Package::query()->with(['categories', 'latestStableVersion']); if ($type = $request->query('type')) { $query->where('type', $type); } if ($status = $request->query('status')) { $query->where('status', $status); } if ($keyword = trim((string) $request->query('keyword', ''))) { $query->where(function ($q) use ($keyword) { $q->where('name', 'like', '%' . $keyword . '%') ->orWhere('slug', 'like', '%' . $keyword . '%') ->orWhere('summary', 'like', '%' . $keyword . '%'); }); } $packages = $query->orderByDesc('sort_order')->orderByDesc('updated_at')->paginate(15)->withQueryString(); return view('admin.packages.index', [ 'packages' => $packages, 'filters' => [ 'type' => (string) $request->query('type', ''), 'status' => (string) $request->query('status', ''), 'keyword' => (string) $request->query('keyword', ''), ], ]); } public function storePackage(StorePackageRequest $request): RedirectResponse { $data = $request->validated(); $data['categories'] = $this->normalizeCategories($request); $package = $this->service->createPackage($data); return redirect()->route('webadmin.packages.show', [$package->type, $package->slug])->with('success', '扩展已创建'); } public function updatePackage(StorePackageRequest $request, string $type, string $slug): RedirectResponse { $package = $this->findPackage($type, $slug); $data = $request->validated(); $data['categories'] = $this->normalizeCategories($request); $this->service->updatePackage($package, $data); return redirect()->route('webadmin.packages.show', [$type, $slug])->with('success', '扩展已更新'); } public function updatePackageStatus(Request $request, string $type, string $slug): RedirectResponse { $validated = $request->validate([ 'status' => ['required', 'in:draft,published,hidden,deprecated'], ]); $package = $this->findPackage($type, $slug); $this->service->updateStatus($package, $validated['status']); return redirect()->route('webadmin.packages')->with('success', '状态已更新'); } public function categories(Request $request): View { $query = Category::query()->withCount('packages'); if ($type = $request->query('type')) { $query->where('type', $type); } if ($keyword = trim((string) $request->query('keyword', ''))) { $query->where(function ($q) use ($keyword) { $q->where('name', 'like', '%' . $keyword . '%') ->orWhere('slug', 'like', '%' . $keyword . '%') ->orWhere('description', 'like', '%' . $keyword . '%'); }); } $categories = $query->orderBy('type')->orderByDesc('sort_order')->paginate(20)->withQueryString(); return view('admin.categories.index', [ 'categories' => $categories, 'filters' => [ 'type' => (string) $request->query('type', ''), 'keyword' => (string) $request->query('keyword', ''), ], ]); } public function storeCategory(StoreCategoryRequest $request): RedirectResponse { Category::create($request->validated()); return redirect()->route('webadmin.categories')->with('success', '分类已创建'); } public function updateCategory(StoreCategoryRequest $request, int $id): RedirectResponse { $category = Category::findOrFail($id); $category->update($request->validated()); return redirect()->route('webadmin.categories')->with('success', '分类已更新'); } public function destroyCategory(int $id): RedirectResponse { $category = Category::findOrFail($id); $category->packages()->detach(); $category->delete(); return redirect()->route('webadmin.categories')->with('success', '分类已删除'); } public function showPackage(string $type, string $slug): View { $package = Package::query() ->with(['categories', 'screenshots', 'versions' => fn ($q) => $q->latest('published_at')]) ->where('type', $type) ->where('slug', $slug) ->firstOrFail(); return view('admin.packages.show', [ 'package' => $package, ]); } public function storeVersion(Request $request, string $type, string $slug): RedirectResponse { $package = $this->findPackage($type, $slug); $validated = $request->validate([ 'version' => ['required', 'string', 'max:32'], 'changelog' => ['nullable', 'string'], 'typecho_min' => ['nullable', 'string', 'max:32'], 'typecho_max' => ['nullable', 'string', 'max:32'], 'php_min' => ['nullable', 'string', 'max:32'], 'php_max' => ['nullable', 'string', 'max:32'], 'package_url' => ['nullable', 'url', 'max:512'], 'package_size' => ['nullable', 'integer', 'min:0'], 'sha256' => ['nullable', 'string', 'max:64'], 'is_stable' => ['nullable', 'boolean'], 'is_latest' => ['nullable', 'boolean'], 'published_at' => ['nullable', 'date'], 'php_extensions' => ['nullable', 'string'], ]); $payload = [ 'version' => $validated['version'], 'changelog' => $validated['changelog'] ?? '', 'typecho_min' => $validated['typecho_min'] ?? '1.2.0', 'typecho_max' => $validated['typecho_max'] ?? '', 'php_min' => $validated['php_min'] ?? '7.4', 'php_max' => $validated['php_max'] ?? '', 'php_extensions' => collect(explode(',', (string) ($validated['php_extensions'] ?? ''))) ->map(fn ($item) => trim($item)) ->filter() ->values() ->all(), 'package_url' => $validated['package_url'] ?? '', 'package_size' => (int) ($validated['package_size'] ?? 0), 'sha256' => $validated['sha256'] ?? str_repeat('0', 64), 'is_stable' => (bool) ($validated['is_stable'] ?? false), 'mark_as_latest' => (bool) ($validated['is_latest'] ?? false), 'published_at' => $validated['published_at'] ?? now(), ]; $version = $this->service->createVersion($package, $payload); return redirect()->route('webadmin.packages.show', [$type, $slug])->with('success', '版本已添加:v' . $version->version); } public function publishVersion(PublishZipVersionRequest $request, string $type, string $slug): RedirectResponse { $package = $this->findPackage($type, $slug); $validated = $request->validated(); try { $version = $this->publishService->publishFromZip($package, $request->file('package_file'), $validated); } catch (\Throwable $e) { return back()->withInput()->with('error', 'zip 发布失败:' . $e->getMessage()); } $msg = 'zip 发布成功:v' . $version->version; $msg .= $version->is_stable ? ' · stable' : ' · prerelease'; $msg .= $version->is_latest ? ' · latest' : ''; return redirect()->route('webadmin.packages.show', [$type, $slug])->with('success', $msg); } public function destroyVersion(string $type, string $slug, int $id): RedirectResponse { $package = $this->findPackage($type, $slug); $version = $package->versions()->where('id', $id)->firstOrFail(); $label = $version->version; $this->service->deleteVersion($version); return redirect()->route('webadmin.packages.show', [$type, $slug])->with('success', '版本已删除:v' . $label); } private function findPackage(string $type, string $slug): Package { return Package::query() ->where('type', $type) ->where('slug', $slug) ->firstOrFail(); } private function normalizeCategories(Request $request): array { $raw = (string) $request->input('categories_text', ''); return collect(preg_split('/[,,\s]+/u', $raw)) ->map(fn ($item) => trim((string) $item)) ->filter() ->values() ->all(); } }