feat: support theme update and various improvements

- Add support for updating themes if a newer version is uploaded
- Hide config button for plugins without configuration items
- Auto refresh theme cache after panel update
- Fix issue where user used traffic cannot be set as a decimal
- Fix subscription issue for shadowrocket in v2board theme
This commit is contained in:
xboard
2025-07-15 01:26:14 +08:00
parent f6cf6706c7
commit 706ba5a7a9
7 changed files with 42 additions and 30 deletions
+3
View File
@@ -2,6 +2,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Services\ThemeService;
use App\Services\UpdateService; use App\Services\UpdateService;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
@@ -46,6 +47,8 @@ class XboardUpdate extends Command
Artisan::call('horizon:terminate'); Artisan::call('horizon:terminate');
$updateService = new UpdateService(); $updateService = new UpdateService();
$updateService->updateVersionCache(); $updateService->updateVersionCache();
$themeService = app(ThemeService::class);
$themeService->switch(admin_setting('current_theme'));
$this->info('更新完毕,队列服务已重启,你无需进行任何操作。'); $this->info('更新完毕,队列服务已重启,你无需进行任何操作。');
} }
} }
@@ -206,9 +206,10 @@ class UserController extends Controller
if (!$user) { if (!$user) {
return $this->fail([400202, '用户不存在']); return $this->fail([400202, '用户不存在']);
} }
// 检查邮箱是否被使用 if (isset($params['email'])) {
if (User::where('email', $params['email'])->first() && $user->email !== $params['email']) { if (User::where('email', $params['email'])->first() && $user->email !== $params['email']) {
return $this->fail([400201, '邮箱已被使用']); return $this->fail([400201, '邮箱已被使用']);
}
} }
// 处理密码 // 处理密码
if (isset($params['password'])) { if (isset($params['password'])) {
@@ -223,7 +224,6 @@ class UserController extends Controller
if (!$plan) { if (!$plan) {
return $this->fail([400202, '订阅计划不存在']); return $this->fail([400202, '订阅计划不存在']);
} }
// return json_encode($plan);
$params['group_id'] = $plan->group_id; $params['group_id'] = $plan->group_id;
} }
// 处理邀请用户 // 处理邀请用户
+2 -1
View File
@@ -14,7 +14,8 @@ class UserUpdate extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'email' => 'required|email:strict', 'id' => 'required|integer',
'email' => 'email:strict',
'password' => 'nullable|min:8', 'password' => 'nullable|min:8',
'transfer_enable' => 'numeric', 'transfer_enable' => 'numeric',
'expired_at' => 'nullable|integer', 'expired_at' => 'nullable|integer',
+21 -10
View File
@@ -156,7 +156,21 @@ class ThemeService
$targetPath = $userThemePath . $config['name']; $targetPath = $userThemePath . $config['name'];
if (File::exists($targetPath)) { if (File::exists($targetPath)) {
throw new Exception('主题已存在'); $oldConfigFile = $targetPath . '/config.json';
if (!File::exists($oldConfigFile)) {
throw new Exception('已存在主题缺少配置文件');
}
$oldConfig = json_decode(File::get($oldConfigFile), true);
$oldVersion = $oldConfig['version'] ?? '0.0.0';
$newVersion = $config['version'] ?? '0.0.0';
if (version_compare($newVersion, $oldVersion, '>')) {
File::deleteDirectory($targetPath);
File::copyDirectory($sourcePath, $targetPath);
$this->initConfig($config['name']);
return true;
} else {
throw new Exception('主题已存在且不是新版本');
}
} }
File::copyDirectory($sourcePath, $targetPath); File::copyDirectory($sourcePath, $targetPath);
@@ -180,9 +194,6 @@ class ThemeService
public function switch(string $theme): bool public function switch(string $theme): bool
{ {
$currentTheme = admin_setting('current_theme'); $currentTheme = admin_setting('current_theme');
if ($theme === $currentTheme) {
return true;
}
try { try {
// 验证主题是否存在 // 验证主题是否存在
@@ -196,12 +207,6 @@ class ThemeService
throw new Exception('主题视图文件不存在'); throw new Exception('主题视图文件不存在');
} }
// 复制主题文件到public目录
$targetPath = public_path('theme/' . $theme);
if (!File::copyDirectory($themePath, $targetPath)) {
throw new Exception('复制主题文件失败');
}
// 清理旧主题文件 // 清理旧主题文件
if ($currentTheme) { if ($currentTheme) {
$oldPath = public_path('theme/' . $currentTheme); $oldPath = public_path('theme/' . $currentTheme);
@@ -210,6 +215,12 @@ class ThemeService
} }
} }
// 复制主题文件到public目录
$targetPath = public_path('theme/' . $theme);
if (!File::copyDirectory($themePath, $targetPath)) {
throw new Exception('复制主题文件失败');
}
admin_setting(['current_theme' => $theme]); admin_setting(['current_theme' => $theme]);
return true; return true;
+11 -11
View File
File diff suppressed because one or more lines are too long
-3
View File
@@ -30,7 +30,6 @@ Route::get('/', function (Request $request) {
$themeService = new ThemeService(); $themeService = new ThemeService();
try { try {
// 检查主题是否存在,不存在则尝试切换到默认主题
if (!$themeService->exists($theme)) { if (!$themeService->exists($theme)) {
if ($theme !== 'Xboard') { if ($theme !== 'Xboard') {
Log::warning('Theme not found, switching to default theme', ['theme' => $theme]); Log::warning('Theme not found, switching to default theme', ['theme' => $theme]);
@@ -40,12 +39,10 @@ Route::get('/', function (Request $request) {
$themeService->switch($theme); $themeService->switch($theme);
} }
// 检查主题视图文件是否存在
if (!$themeService->getThemeViewPath($theme)) { if (!$themeService->getThemeViewPath($theme)) {
throw new Exception('主题视图文件不存在'); throw new Exception('主题视图文件不存在');
} }
// 检查主题是否已复制到public目录
$publicThemePath = public_path('theme/' . $theme); $publicThemePath = public_path('theme/' . $theme);
if (!File::exists($publicThemePath)) { if (!File::exists($publicThemePath)) {
$themePath = $themeService->getThemePath($theme); $themePath = $themeService->getThemePath($theme);
+1 -1
View File
File diff suppressed because one or more lines are too long