From bc825157c94c2b57310281c137b831a54217bb04 Mon Sep 17 00:00:00 2001 From: lkddi Date: Sun, 12 Apr 2026 16:16:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BC=9A=E5=91=98=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Admin/VipController.php | 68 ++++++++- resources/views/admin/vip/index.blade.php | 6 +- resources/views/admin/vip/members.blade.php | 143 ++++++++++++++++++ routes/web.php | 1 + .../Feature/AdminVipControllerTest.php | 82 ++++++++++ 5 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 resources/views/admin/vip/members.blade.php create mode 100644 tests/Feature/Feature/AdminVipControllerTest.php diff --git a/app/Http/Controllers/Admin/VipController.php b/app/Http/Controllers/Admin/VipController.php index f1f07c1..1c7bc80 100644 --- a/app/Http/Controllers/Admin/VipController.php +++ b/app/Http/Controllers/Admin/VipController.php @@ -13,11 +13,16 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Models\User; use App\Models\VipLevel; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\View\View; +/** + * 后台 VIP 会员等级管理控制器 + * 负责会员等级维护,以及查看各等级下的会员名单。 + */ class VipController extends Controller { /** @@ -51,7 +56,10 @@ class VipController extends Controller */ public function index(): View { - $levels = VipLevel::orderBy('sort_order')->get(); + $levels = VipLevel::query() + ->withCount('users') + ->orderBy('sort_order') + ->get(); return view('admin.vip.index', [ 'levels' => $levels, @@ -60,6 +68,64 @@ class VipController extends Controller ]); } + /** + * 查看某个会员等级下的会员名单。 + * + * @param Request $request 当前筛选请求 + * @param VipLevel $vip 当前会员等级 + */ + public function members(Request $request, VipLevel $vip): View + { + $query = User::query()->where('vip_level_id', $vip->id); + $now = now(); + + if ($request->filled('keyword')) { + $keyword = trim((string) $request->input('keyword')); + + // 支持后台按用户名快速筛选某个等级下的会员。 + $query->where('username', 'like', '%'.$keyword.'%'); + } + + if ($request->input('status') === 'active') { + // 当前有效会员:永久会员或到期时间仍在未来。 + $query->where(function ($builder) use ($now): void { + $builder->whereNull('hy_time')->orWhere('hy_time', '>', $now); + }); + } + + if ($request->input('status') === 'expired') { + // 已过期会员:到期时间存在且已经早于当前时间。 + $query->whereNotNull('hy_time')->where('hy_time', '<=', $now); + } + + $members = $query + ->select(['id', 'username', 'sex', 'vip_level_id', 'hy_time', 'created_at']) + ->orderByRaw('CASE WHEN hy_time IS NULL THEN 0 WHEN hy_time > ? THEN 1 ELSE 2 END', [$now]) + ->orderByRaw('hy_time IS NULL DESC') + ->orderByDesc('hy_time') + ->orderBy('username') + ->paginate(20) + ->withQueryString(); + + $totalAssignedCount = User::query() + ->where('vip_level_id', $vip->id) + ->count(); + + $activeCount = User::query() + ->where('vip_level_id', $vip->id) + ->where(function ($builder) use ($now): void { + $builder->whereNull('hy_time')->orWhere('hy_time', '>', $now); + }) + ->count(); + + return view('admin.vip.members', [ + 'vip' => $vip, + 'members' => $members, + 'totalAssignedCount' => $totalAssignedCount, + 'activeCount' => $activeCount, + ]); + } + /** * 新增会员等级 */ diff --git a/resources/views/admin/vip/index.blade.php b/resources/views/admin/vip/index.blade.php index ccd8d0e..c81dbbd 100644 --- a/resources/views/admin/vip/index.blade.php +++ b/resources/views/admin/vip/index.blade.php @@ -125,7 +125,7 @@
当前会员 - {{ $level->users()->count() }} 人 + {{ $level->users_count }} 人
进场特效 @@ -143,6 +143,10 @@
+ + 查看会员 + + + 重置 + +
+ + + +
+
+ + + + + + + + + + + + + @forelse ($members as $member) + @php + $status = $member->hy_time === null ? 'permanent' : ($member->hy_time->isFuture() ? 'active' : 'expired'); + $statusLabel = match ($status) { + 'permanent' => '永久会员', + 'active' => '当前有效', + default => '已过期', + }; + $statusClass = match ($status) { + 'permanent' => 'bg-purple-50 text-purple-600', + 'active' => 'bg-emerald-50 text-emerald-600', + default => 'bg-rose-50 text-rose-600', + }; + $sexLabel = match ((int) $member->sex) { + 1 => '男', + 2 => '女', + default => '未知', + }; + @endphp + + + + + + + + + @empty + + + + @endforelse + +
用户ID用户名性别会员状态到期日期注册时间
#{{ $member->id }} +
{{ $member->username }}
+
{{ $sexLabel }} + + {{ $statusLabel }} + + + @if ($member->hy_time) + {{ $member->hy_time->format('Y-m-d H:i') }} + @else + 永久 + @endif + {{ $member->created_at?->format('Y-m-d H:i') }}
+ 当前条件下暂无该等级会员记录 +
+
+ + @if ($members->hasPages()) +
+ {{ $members->links() }} +
+ @endif +
+ +@endsection diff --git a/routes/web.php b/routes/web.php index dcaa3cb..3adbd4e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -434,6 +434,7 @@ Route::middleware(['chat.auth', 'chat.has_position'])->prefix('admin')->name('ad // VIP 会员等级(含新增/编辑/删除) Route::get('/vip', [\App\Http\Controllers\Admin\VipController::class, 'index'])->name('vip.index'); + Route::get('/vip/{vip}/members', [\App\Http\Controllers\Admin\VipController::class, 'members'])->name('vip.members'); Route::post('/vip', [\App\Http\Controllers\Admin\VipController::class, 'store'])->name('vip.store'); Route::put('/vip/{vip}', [\App\Http\Controllers\Admin\VipController::class, 'update'])->name('vip.update'); Route::delete('/vip/{vip}', [\App\Http\Controllers\Admin\VipController::class, 'destroy'])->name('vip.destroy'); diff --git a/tests/Feature/Feature/AdminVipControllerTest.php b/tests/Feature/Feature/AdminVipControllerTest.php new file mode 100644 index 0000000..741b41a --- /dev/null +++ b/tests/Feature/Feature/AdminVipControllerTest.php @@ -0,0 +1,82 @@ +create([ + 'user_level' => 100, + ]); + + $vipLevel = VipLevel::create([ + 'name' => '黄金会员', + 'icon' => '👑', + 'color' => '#f59e0b', + 'exp_multiplier' => 1.5, + 'jjb_multiplier' => 1.2, + 'sort_order' => 1, + 'price' => 100, + 'duration_days' => 30, + 'join_effect' => 'none', + 'leave_effect' => 'none', + 'join_banner_style' => 'aurora', + 'leave_banner_style' => 'farewell', + 'allow_custom_messages' => true, + ]); + + User::factory()->create([ + 'username' => 'active_member', + 'vip_level_id' => $vipLevel->id, + 'hy_time' => now()->addDays(10), + ]); + + User::factory()->create([ + 'username' => 'permanent_member', + 'vip_level_id' => $vipLevel->id, + 'hy_time' => null, + ]); + + User::factory()->create([ + 'username' => 'expired_member', + 'vip_level_id' => $vipLevel->id, + 'hy_time' => now()->subDay(), + ]); + + User::factory()->create([ + 'username' => 'other_level_member', + ]); + + $response = $this->actingAs($admin)->get(route('admin.vip.members', $vipLevel->id)); + + $response->assertOk(); + $response->assertSee('黄金会员 会员列表'); + $response->assertSee('active_member'); + $response->assertSee('permanent_member'); + $response->assertSee('expired_member'); + $response->assertDontSee('other_level_member'); + $response->assertSee('当前有效'); + $response->assertSee('永久会员'); + $response->assertSee('已过期'); + } +}