diff --git a/app/Http/Controllers/Auth/Admin/User.php b/app/Http/Controllers/Auth/Admin/User.php index 21e8b52..23ddaef 100644 --- a/app/Http/Controllers/Auth/Admin/User.php +++ b/app/Http/Controllers/Auth/Admin/User.php @@ -91,6 +91,7 @@ class User extends Controller 'real_name' => 'nullable|string|max:50', 'email' => 'nullable|email|unique:auth_users,email,' . $id, 'phone' => 'nullable|string|max:20', + 'avatar' => 'nullable|string|max:500', 'department_id' => 'nullable|integer|exists:auth_departments,id', 'role_ids' => 'nullable|array', 'role_ids.*' => 'integer|exists:auth_roles,id', diff --git a/app/Services/System/DictionaryService.php b/app/Services/System/DictionaryService.php index 68a5cab..0dea17d 100644 --- a/app/Services/System/DictionaryService.php +++ b/app/Services/System/DictionaryService.php @@ -20,7 +20,8 @@ class DictionaryService }); } - if (isset($params['status']) && $params['status'] !== '') { + // 处理状态筛选:只接受布尔值或数字1/0 + if (array_key_exists('status', $params) && is_bool($params['status'])) { $query->where('status', $params['status']); } @@ -37,20 +38,48 @@ class DictionaryService public function getAll(): array { - return Dictionary::where('status', true) - ->orderBy('sort') - ->get() - ->toArray(); + $cacheKey = 'system:dictionaries:all'; + $dictionaries = Cache::get($cacheKey); + + if ($dictionaries === null) { + $dictionaries = Dictionary::where('status', true) + ->orderBy('sort') + ->get() + ->toArray(); + Cache::put($cacheKey, $dictionaries, 3600); + } + + return $dictionaries; } public function getById(int $id): ?Dictionary { - return Dictionary::with('items')->find($id); + $cacheKey = 'system:dictionaries:' . $id; + $dictionary = Cache::get($cacheKey); + + if ($dictionary === null) { + $dictionary = Dictionary::with('items')->find($id); + if ($dictionary) { + Cache::put($cacheKey, $dictionary->toArray(), 3600); + } + } + + return $dictionary ? $dictionary : null; } public function getByCode(string $code): ?Dictionary { - return Dictionary::where('code', $code)->first(); + $cacheKey = 'system:dictionaries:code:' . $code; + $dictionary = Cache::get($cacheKey); + + if ($dictionary === null) { + $dictionary = Dictionary::where('code', $code)->first(); + if ($dictionary) { + Cache::put($cacheKey, $dictionary->toArray(), 3600); + } + } + + return $dictionary; } public function getItemsByCode(string $code): array @@ -125,11 +154,26 @@ class DictionaryService return true; } - private function clearCache(): void + private function clearCache($dictionaryId = null): void { - $codes = Dictionary::pluck('code')->toArray(); - foreach ($codes as $code) { - Cache::forget('system:dictionary:' . $code); + // 清理所有字典列表缓存 + Cache::forget('system:dictionaries:all'); + + if ($dictionaryId) { + // 清理特定字典的缓存 + $dictionary = Dictionary::find($dictionaryId); + if ($dictionary) { + Cache::forget('system:dictionaries:' . $dictionaryId); + Cache::forget('system:dictionaries:code:' . $dictionary->code); + Cache::forget('system:dictionary:' . $dictionary->code); + } + } else { + // 清理所有字典缓存 + $codes = Dictionary::pluck('code')->toArray(); + foreach ($codes as $code) { + Cache::forget('system:dictionary:' . $code); + Cache::forget('system:dictionaries:code:' . $code); + } } } @@ -141,7 +185,8 @@ class DictionaryService $query->where('dictionary_id', $params['dictionary_id']); } - if (isset($params['status']) && $params['status'] !== '') { + // 处理状态筛选:只接受布尔值或数字1/0 + if (array_key_exists('status', $params) && is_bool($params['status'])) { $query->where('status', $params['status']); } @@ -167,7 +212,7 @@ class DictionaryService ])->validate(); $item = DictionaryItem::create($data); - $this->clearCache(); + $this->clearCache($data['dictionary_id']); return $item; } @@ -182,29 +227,46 @@ class DictionaryService ])->validate(); $item->update($data); - $this->clearCache(); + $this->clearCache($item->dictionary_id); return $item; } public function deleteItem(int $id): bool { $item = DictionaryItem::findOrFail($id); + $dictionaryId = $item->dictionary_id; $item->delete(); - $this->clearCache(); + $this->clearCache($dictionaryId); return true; } public function batchDeleteItems(array $ids): bool { + $items = DictionaryItem::whereIn('id', $ids)->get(); + $dictionaryIds = $items->pluck('dictionary_id')->unique()->toArray(); + DictionaryItem::whereIn('id', $ids)->delete(); - $this->clearCache(); + + // 清理相关字典的缓存 + foreach ($dictionaryIds as $dictionaryId) { + $this->clearCache($dictionaryId); + } + return true; } public function batchUpdateItemsStatus(array $ids, bool $status): bool { + $items = DictionaryItem::whereIn('id', $ids)->get(); + $dictionaryIds = $items->pluck('dictionary_id')->unique()->toArray(); + DictionaryItem::whereIn('id', $ids)->update(['status' => $status]); - $this->clearCache(); + + // 清理相关字典的缓存 + foreach ($dictionaryIds as $dictionaryId) { + $this->clearCache($dictionaryId); + } + return true; } } diff --git a/app/Services/System/LogService.php b/app/Services/System/LogService.php index e27b201..36ec5df 100644 --- a/app/Services/System/LogService.php +++ b/app/Services/System/LogService.php @@ -66,6 +66,10 @@ class LogService $query->where('action', $params['action']); } + if (!empty($params['method'])) { + $query->where('method', $params['method']); + } + if (!empty($params['status'])) { $query->where('status', $params['status']); } diff --git a/app/Services/System/UploadService.php b/app/Services/System/UploadService.php index 4bb8382..8ae3d9d 100644 --- a/app/Services/System/UploadService.php +++ b/app/Services/System/UploadService.php @@ -5,7 +5,8 @@ namespace App\Services\System; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; -use Intervention\Image\Facades\Image; +use Intervention\Image\ImageManager; +use Intervention\Image\Drivers\Gd\Driver; class UploadService { @@ -17,6 +18,7 @@ class UploadService public function __construct() { $this->disk = Storage::disk('public'); + $this->imageManager = new ImageManager(new Driver()); } public function upload(UploadedFile $file, string $directory = 'uploads', array $options = []): array @@ -135,17 +137,14 @@ class UploadService $width = $options['width'] ?? null; $height = $options['height'] ?? null; - $image = Image::make($file); + $image = $this->imageManager->read($file); if ($width || $height) { - $image->resize($width, $height, function ($constraint) { - $constraint->aspectRatio(); - $constraint->upsize(); - }); + $image->scale($width, $height); } - $image->encode(null, $quality); - $this->disk->put($filePath, (string) $image); + $encoded = $image->toJpeg(quality: $quality); + $this->disk->put($filePath, (string) $encoded); } public function getFileUrl(string $path): string diff --git a/composer.json b/composer.json index d789b18..ff967b8 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "require": { "php": "^8.2", "hhxsv5/laravel-s": "^3.8", + "intervention/image": "^3.11", "laravel/framework": "^12.0", "laravel/tinker": "^2.10.1", "nwidart/laravel-modules": "^12.0", diff --git a/database/seeders/SystemSeeder.php b/database/seeders/SystemSeeder.php index ee71cad..da14fae 100644 --- a/database/seeders/SystemSeeder.php +++ b/database/seeders/SystemSeeder.php @@ -576,6 +576,13 @@ class SystemSeeder extends Seeder 'sort' => 7, 'status' => 1, ], + [ + 'name' => '配置分组', + 'code' => 'config_group', + 'description' => '系统配置分组类型', + 'sort' => 8, + 'status' => 1, + ], ]; foreach ($dictionaries as $dictionary) { @@ -645,6 +652,14 @@ class SystemSeeder extends Seeder ['label' => '否', 'value' => 0, 'sort' => 2, 'status' => 1], ]; break; + + case 'config_group': + $items = [ + ['label' => '网站设置', 'value' => 'site', 'sort' => 1, 'status' => 1], + ['label' => '上传设置', 'value' => 'upload', 'sort' => 2, 'status' => 1], + ['label' => '系统设置', 'value' => 'system', 'sort' => 3, 'status' => 1], + ]; + break; } foreach ($items as $item) { diff --git a/resources/admin/src/pages/system/config/components/ConfigDialog.vue b/resources/admin/src/pages/system/config/components/ConfigDialog.vue new file mode 100644 index 0000000..88d584e --- /dev/null +++ b/resources/admin/src/pages/system/config/components/ConfigDialog.vue @@ -0,0 +1,307 @@ + + + + + diff --git a/resources/admin/src/pages/system/config/components/ConfigForm.vue b/resources/admin/src/pages/system/config/components/ConfigForm.vue new file mode 100644 index 0000000..a4cac35 --- /dev/null +++ b/resources/admin/src/pages/system/config/components/ConfigForm.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/resources/admin/src/pages/system/dictionaries/components/DictionaryDialog.vue b/resources/admin/src/pages/system/dictionaries/components/DictionaryDialog.vue new file mode 100644 index 0000000..68e6ea1 --- /dev/null +++ b/resources/admin/src/pages/system/dictionaries/components/DictionaryDialog.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/resources/admin/src/pages/system/dictionaries/components/ItemDialog.vue b/resources/admin/src/pages/system/dictionaries/components/ItemDialog.vue new file mode 100644 index 0000000..0330636 --- /dev/null +++ b/resources/admin/src/pages/system/dictionaries/components/ItemDialog.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/resources/admin/src/pages/system/dictionaries/index.vue b/resources/admin/src/pages/system/dictionaries/index.vue new file mode 100644 index 0000000..09d98b5 --- /dev/null +++ b/resources/admin/src/pages/system/dictionaries/index.vue @@ -0,0 +1,600 @@ + + + + + diff --git a/resources/admin/src/pages/system/tasks/components/TaskDetailDialog.vue b/resources/admin/src/pages/system/tasks/components/TaskDetailDialog.vue new file mode 100644 index 0000000..90cb6da --- /dev/null +++ b/resources/admin/src/pages/system/tasks/components/TaskDetailDialog.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/resources/admin/src/pages/system/tasks/components/TaskDialog.vue b/resources/admin/src/pages/system/tasks/components/TaskDialog.vue new file mode 100644 index 0000000..1a96afa --- /dev/null +++ b/resources/admin/src/pages/system/tasks/components/TaskDialog.vue @@ -0,0 +1,409 @@ + + + + + diff --git a/resources/admin/src/pages/system/tasks/index.vue b/resources/admin/src/pages/system/tasks/index.vue new file mode 100644 index 0000000..a4b53fe --- /dev/null +++ b/resources/admin/src/pages/system/tasks/index.vue @@ -0,0 +1,376 @@ + + + + + diff --git a/resources/admin/src/utils/dictionaryCache.js b/resources/admin/src/utils/dictionaryCache.js new file mode 100644 index 0000000..9424665 --- /dev/null +++ b/resources/admin/src/utils/dictionaryCache.js @@ -0,0 +1,147 @@ +/** + * 数据字典缓存工具 + * 用于缓存和管理数据字典数据 + */ + +import systemApi from '@/api/system' + +// 缓存存储 +const cacheStorage = new Map() + +// 缓存过期时间(毫秒)默认1小时 +const CACHE_EXPIRE_TIME = 3600 * 1000 + +/** + * 字典缓存管理类 + */ +class DictionaryCacheManager { + /** + * 获取所有字典(带缓存) + */ + async getAll() { + const cacheKey = 'all' + const cached = this.get(cacheKey) + + if (cached) { + return cached + } + + const res = await systemApi.dictionaries.all.get() + if (res.code === 200) { + const data = res.data || [] + this.set(cacheKey, data) + return data + } + + return [] + } + + /** + * 根据编码获取字典项(带缓存) + */ + async getItemsByCode(code) { + const cacheKey = `items:${code}` + const cached = this.get(cacheKey) + + if (cached) { + return cached + } + + const res = await systemApi.public.dictionaries.code.get({ code }) + if (res.code === 200) { + const data = res.data || [] + this.set(cacheKey, data) + return data + } + + return [] + } + + /** + * 根据编码获取字典(带缓存) + */ + async getByCode(code) { + const cacheKey = `code:${code}` + const cached = this.get(cacheKey) + + if (cached) { + return cached + } + + const all = await this.getAll() + const dictionary = all.find((item) => item.code === code) + + if (dictionary) { + this.set(cacheKey, dictionary) + } + + return dictionary + } + + /** + * 根据编码获取字典项的标签 + */ + async getLabelByCode(code, value) { + const items = await this.getItemsByCode(code) + const item = items.find((item) => item.value === value) + return item ? item.label : value + } + + /** + * 清除所有缓存 + */ + clear() { + cacheStorage.clear() + } + + /** + * 清除指定缓存 + */ + delete(key) { + cacheStorage.delete(key) + } + + /** + * 清除字典相关缓存 + */ + clearDictionary(dictionaryId) { + // 清除特定字典的缓存(暂时清除所有,因为前端不知道字典ID对应的code) + this.clear() + } + + /** + * 获取缓存 + */ + get(key) { + const item = cacheStorage.get(key) + + if (!item) { + return null + } + + // 检查是否过期 + if (Date.now() > item.expires) { + cacheStorage.delete(key) + return null + } + + return item.data + } + + /** + * 设置缓存 + */ + set(key, data) { + const item = { + data, + expires: Date.now() + CACHE_EXPIRE_TIME + } + + cacheStorage.set(key, item) + } +} + +// 创建单例实例 +const dictionaryCache = new DictionaryCacheManager() + +export default dictionaryCache