diff --git a/app/Enums/UserConfigKey.php b/app/Enums/UserConfigKey.php index 96dc5881..7a1494c4 100644 --- a/app/Enums/UserConfigKey.php +++ b/app/Enums/UserConfigKey.php @@ -10,6 +10,9 @@ final class UserConfigKey /** @var string 默认策略 */ const DefaultStrategy = 'default_strategy'; + /** @var string 默认权限 */ + const DefaultPermission = 'default_permission'; + /** @var string 上传是否自动清除预览 */ const IsAutoClearPreview = 'is_auto_clear_preview'; } diff --git a/app/Http/Controllers/User/ImageController.php b/app/Http/Controllers/User/ImageController.php index 8e0564b9..4932cceb 100644 --- a/app/Http/Controllers/User/ImageController.php +++ b/app/Http/Controllers/User/ImageController.php @@ -19,7 +19,7 @@ class ImageController extends Controller { public function index(): View { - return view('images'); + return view('user.images'); } public function images(Request $request): Response diff --git a/app/Http/Controllers/User/ProfileController.php b/app/Http/Controllers/User/ProfileController.php index 331c3bd6..7b8bee89 100644 --- a/app/Http/Controllers/User/ProfileController.php +++ b/app/Http/Controllers/User/ProfileController.php @@ -3,9 +3,39 @@ namespace App\Http\Controllers\User; use App\Http\Controllers\Controller; -use Illuminate\Http\Request; +use App\Http\Requests\UserSettingRequest; +use App\Models\User; +use Illuminate\Auth\Events\PasswordReset; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; +use Illuminate\View\View; class ProfileController extends Controller { - // + public function settings(): View + { + return view('user.settings'); + } + + public function update(UserSettingRequest $request): Response + { + /** @var User $user */ + $user = Auth::user(); + $user->name = $request->validated('name'); + $user->configs = $user->configs->merge(collect($request->validated('configs'))->transform(function ($value) { + return (int)$value; + })); + if ($password = $request->validated('password')) { + $user->forceFill([ + 'password' => Hash::make($password), + 'remember_token' => Str::random(60), + ])->save(); + + event(new PasswordReset($user)); + } + $user->save(); + return $this->success('保存成功'); + } } diff --git a/app/Http/Controllers/User/UserController.php b/app/Http/Controllers/User/UserController.php index f7152928..d310d55b 100644 --- a/app/Http/Controllers/User/UserController.php +++ b/app/Http/Controllers/User/UserController.php @@ -23,6 +23,6 @@ class UserController extends Controller } $strategies = $user->group ? $user->group->strategies()->get() : []; - return view('dashboard', compact('strategies', 'configs', 'user')); + return view('user.dashboard', compact('strategies', 'configs', 'user')); } } diff --git a/app/Http/Requests/UserSettingRequest.php b/app/Http/Requests/UserSettingRequest.php new file mode 100644 index 00000000..dd3e76c4 --- /dev/null +++ b/app/Http/Requests/UserSettingRequest.php @@ -0,0 +1,63 @@ + 'required|between:2,20', + 'password' => 'nullable|between:6,32', + 'configs' => 'array', + 'configs.default_album' => 'required|numeric', + 'configs.default_strategy' => 'required|numeric', + 'configs.default_permission' => 'required|in:1,0', + 'configs.is_auto_clear_preview' => 'nullable|boolean' + ]; + } + + public function messages() + { + return [ + 'name.required' => '昵称不能为空', + 'name.between' => '昵称必须在 2-20 个字符之间', + 'password.between' => '昵称必须在 6-32 个字符之间', + 'configs.array' => '配置值不正确', + 'configs.default_album.required' => '默认相册选择错误', + 'configs.default_album.numeric' => '默认相册选择错误', + 'configs.default_strategy.required' => '默认策略选择错误', + 'configs.default_strategy.numeric' => '默认策略选择错误', + 'configs.default_permission.required' => '权限值选择错误', + 'configs.default_permission.in' => '权限值不正确', + 'configs.is_auto_clear_preview.boolean' => '是否自动清除预览选择错误' + ]; + } + + protected function failedValidation(Validator $validator) + { + throw (new HttpResponseException($this->error($validator->errors()->first()))); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 4fe014e9..f4bc8835 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Enums\ImagePermission; use App\Enums\UserConfigKey; use Carbon\Carbon; use Illuminate\Contracts\Auth\MustVerifyEmail; @@ -84,6 +85,7 @@ class User extends Authenticatable implements MustVerifyEmail $user->configs = collect([ UserConfigKey::DefaultAlbum => 0, UserConfigKey::DefaultStrategy => 0, + UserConfigKey::DefaultPermission => ImagePermission::Private, UserConfigKey::IsAutoClearPreview => 0, ])->merge($user->configs ?: []); }); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f0de9f00..bca99e3e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -28,7 +28,8 @@ class AppServiceProvider extends ServiceProvider { // 初始化视图中的默认数据 View::composer('*', function (\Illuminate\View\View $view) { - $view->with('groupConfigs', Auth::check() ? Auth::user()->group->configs : Group::getDefaultConfigs()); + $configs = Auth::check() && Auth::user()->group ? Auth::user()->group->configs : Group::getDefaultConfigs(); + $view->with('groupConfigs', $configs); }); } } diff --git a/public/css/app.css b/public/css/app.css index 573a4f2d..843edeec 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -709,6 +709,9 @@ select { .z-\[1\] { z-index: 1; } +.col-span-6 { + grid-column: span 6 / span 6; +} .m-2 { margin: 0.5rem; } @@ -798,6 +801,9 @@ select { .-mr-2 { margin-right: -0.5rem; } +.mt-5 { + margin-top: 1.25rem; +} .block { display: block; } @@ -991,6 +997,9 @@ select { .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } +.grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); +} .flex-row { flex-direction: row; } @@ -1003,6 +1012,9 @@ select { .flex-nowrap { flex-wrap: nowrap; } +.items-start { + align-items: flex-start; +} .items-center { align-items: center; } @@ -1018,6 +1030,9 @@ select { .justify-between { justify-content: space-between; } +.gap-6 { + gap: 1.5rem; +} .space-y-4 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); @@ -1073,6 +1088,11 @@ select { margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); } +.space-x-6 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1.5rem * var(--tw-space-x-reverse)); + margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse))); +} .divide-y > :not([hidden]) ~ :not([hidden]) { --tw-divide-y-reverse: 0; border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); @@ -1242,6 +1262,14 @@ select { --tw-bg-opacity: 1; background-color: rgb(96 165 250 / var(--tw-bg-opacity)); } +.bg-indigo-600 { + --tw-bg-opacity: 1; + background-color: rgb(79 70 229 / var(--tw-bg-opacity)); +} +.bg-blue-500 { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity)); +} .bg-opacity-75 { --tw-bg-opacity: 0.75; } @@ -1329,6 +1357,10 @@ select { padding-top: 2.5rem; padding-bottom: 2.5rem; } +.py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} .pb-6 { padding-bottom: 1.5rem; } @@ -1359,6 +1391,9 @@ select { .text-center { text-align: center; } +.text-right { + text-align: right; +} .font-sans { font-family: Nunito, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } @@ -1413,6 +1448,9 @@ select { .leading-7 { line-height: 1.75rem; } +.leading-6 { + line-height: 1.5rem; +} .tracking-wider { letter-spacing: 0.05em; } @@ -1641,6 +1679,14 @@ select { --tw-bg-opacity: 1; background-color: rgb(147 197 253 / var(--tw-bg-opacity)); } +.hover\:bg-indigo-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(67 56 202 / var(--tw-bg-opacity)); +} +.hover\:bg-blue-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} .hover\:text-gray-500:hover { --tw-text-opacity: 1; color: rgb(107 114 128 / var(--tw-text-opacity)); @@ -1684,6 +1730,10 @@ select { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); } +.focus\:border-indigo-500:focus { + --tw-border-opacity: 1; + border-color: rgb(99 102 241 / var(--tw-border-opacity)); +} .focus\:bg-gray-100:focus { --tw-bg-opacity: 1; background-color: rgb(243 244 246 / var(--tw-bg-opacity)); @@ -1718,6 +1768,10 @@ select { --tw-ring-opacity: 1; --tw-ring-color: rgb(199 210 254 / var(--tw-ring-opacity)); } +.focus\:ring-indigo-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity)); +} .focus\:ring-opacity-50:focus { --tw-ring-opacity: 0.5; } @@ -1762,6 +1816,18 @@ select { left: 0px; } + .sm\:col-span-3 { + grid-column: span 3 / span 3; + } + + .sm\:col-span-4 { + grid-column: span 4 / span 4; + } + + .sm\:col-span-6 { + grid-column: span 6 / span 6; + } + .sm\:-my-px { margin-top: -1px; margin-bottom: -1px; @@ -1779,6 +1845,10 @@ select { margin-left: 1.5rem; } + .sm\:mt-0 { + margin-top: 0px; + } + .sm\:block { display: block; } @@ -1827,6 +1897,14 @@ select { border-radius: 0.5rem; } + .sm\:rounded-md { + border-radius: 0.375rem; + } + + .sm\:p-6 { + padding: 1.5rem; + } + .sm\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; @@ -1837,6 +1915,11 @@ select { padding-right: 2.5rem; } + .sm\:px-0 { + padding-left: 0px; + padding-right: 0px; + } + .sm\:pt-0 { padding-top: 0px; } @@ -1846,6 +1929,11 @@ select { line-height: 2rem; } + .sm\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; + } + .sm\:shadow-none { --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; @@ -1854,6 +1942,14 @@ select { } @media (min-width: 768px) { + .md\:col-span-1 { + grid-column: span 1 / span 1; + } + + .md\:col-span-2 { + grid-column: span 2 / span 2; + } + .md\:my-10 { margin-top: 2.5rem; margin-bottom: 2.5rem; @@ -1891,10 +1987,18 @@ select { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .md\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .md\:flex-row { flex-direction: row; } + .md\:gap-6 { + gap: 1.5rem; + } + .md\:gap-x-4 { -moz-column-gap: 1rem; column-gap: 1rem; @@ -1931,6 +2035,10 @@ select { } @media (min-width: 1024px) { + .lg\:col-span-2 { + grid-column: span 2 / span 2; + } + .lg\:flex { display: flex; } diff --git a/resources/views/layouts/sidebar.blade.php b/resources/views/layouts/sidebar.blade.php index 3946f22a..abe43035 100644 --- a/resources/views/layouts/sidebar.blade.php +++ b/resources/views/layouts/sidebar.blade.php @@ -26,7 +26,7 @@ 我的图片 - + 设置 diff --git a/resources/views/dashboard.blade.php b/resources/views/user/dashboard.blade.php similarity index 100% rename from resources/views/dashboard.blade.php rename to resources/views/user/dashboard.blade.php diff --git a/resources/views/images.blade.php b/resources/views/user/images.blade.php similarity index 100% rename from resources/views/images.blade.php rename to resources/views/user/images.blade.php diff --git a/resources/views/user/settings.blade.php b/resources/views/user/settings.blade.php new file mode 100644 index 00000000..098e2326 --- /dev/null +++ b/resources/views/user/settings.blade.php @@ -0,0 +1,109 @@ +@section('title', '设置') + + +
+
+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+
+
+ 是否自动清除预览 +

设置上传时,文件上传完成以后是否自动清除预览图片

+
+
+
+ configs->get(\App\Enums\UserConfigKey::IsAutoClearPreview) ? 'checked' : '' }}> + +
+
+ configs->get(\App\Enums\UserConfigKey::IsAutoClearPreview) ? 'checked' : '' }}> + +
+
+
+
+
+ 图片默认权限 +

设置上传的图片默认的权限(公开还是私有,公开的图片将会出现在画廊中,你也可以通过图片管理单独设置权限)

+
+
+
+ configs->get(\App\Enums\UserConfigKey::DefaultPermission) == \App\Enums\ImagePermission::Private ? 'checked' : '' }}> + +
+
+ configs->get(\App\Enums\UserConfigKey::DefaultPermission) == \App\Enums\ImagePermission::Public ? 'checked' : '' }}> + +
+
+
+
+ +
+ +
+
+
+
+ + @push('scripts') + + @endpush + +
diff --git a/resources/views/upload.blade.php b/resources/views/user/upload.blade.php similarity index 100% rename from resources/views/upload.blade.php rename to resources/views/user/upload.blade.php diff --git a/routes/web.php b/routes/web.php index c661488c..fe0c8a2c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -16,12 +16,13 @@ use App\Http\Controllers\Controller; use App\Http\Controllers\User\UserController; use App\Http\Controllers\User\ImageController; use App\Http\Controllers\User\AlbumController; +use App\Http\Controllers\User\ProfileController; Route::get('/', fn () => view('welcome'))->name('/'); Route::post('/upload', [Controller::class, 'upload']); Route::group(['middleware' => ['auth']], function () { Route::get('/dashboard', [UserController::class, 'dashboard'])->name('dashboard'); - Route::get('/upload', fn () => view('upload'))->name('upload'); + Route::get('/upload', fn () => view('user.upload'))->name('upload'); Route::get('/images', [ImageController::class, 'index'])->name('images'); Route::group(['prefix' => 'user'], function () { Route::get('images', [ImageController::class, 'images'])->name('user.images'); @@ -35,6 +36,8 @@ Route::group(['middleware' => ['auth']], function () { Route::put('albums/{id}', [AlbumController::class, 'update'])->name('user.album.update'); Route::delete('albums/{id}', [AlbumController::class, 'delete'])->name('user.album.delete'); }); + Route::get('settings', [ProfileController::class, 'settings'])->name('settings'); + Route::put('settings', [ProfileController::class, 'update'])->name('settings.update'); }); require __DIR__.'/image.php';