*/ private const EFFECT_LABELS = [ 'none' => '无特效', 'fireworks' => '烟花', 'rain' => '下雨', 'lightning' => '闪电', 'snow' => '下雪', 'sakura' => '樱花飘落', 'meteors' => '流星', 'gold-rain' => '金币雨', 'hearts' => '爱心飘落', 'confetti' => '彩带庆典', 'fireflies' => '萤火虫', ]; /** * 会员主题支持的横幅风格下拉选项。 * * @var array */ private const BANNER_STYLE_LABELS = [ 'aurora' => '鎏光星幕', 'storm' => '雷霆风暴', 'royal' => '王者金辉', 'cosmic' => '星穹幻彩', 'farewell' => '告别暮光', ]; /** * 会员等级管理列表页 */ public function index(): View { $levels = VipLevel::query() ->withCount('users') ->orderBy('sort_order') ->get(); return view('admin.vip.index', [ 'levels' => $levels, 'effectOptions' => self::EFFECT_LABELS, 'bannerStyleOptions' => self::BANNER_STYLE_LABELS, ]); } /** * 查看某个会员等级下的会员名单。 * * @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, ]); } /** * 新增会员等级 */ public function store(Request $request): RedirectResponse { $data = $this->validatedPayload($request); VipLevel::create($data); return redirect()->route('admin.vip.index')->with('success', '会员等级创建成功!'); } /** * 更新会员等级 * * @param VipLevel $vip 路由模型自动注入 */ public function update(Request $request, VipLevel $vip): RedirectResponse { $level = $vip; $data = $this->validatedPayload($request); $level->update($data); return redirect()->route('admin.vip.index')->with('success', '会员等级更新成功!'); } /** * 删除会员等级(关联用户的 vip_level_id 会自动置 null) * * @param VipLevel $vip 路由模型自动注入 */ public function destroy(VipLevel $vip): RedirectResponse { $vip->delete(); return redirect()->route('admin.vip.index')->with('success', '会员等级已删除!'); } /** * 将多行文本转为 JSON 数组字符串 * 每行一个模板,空行忽略 * * @param string $text 多行文本 * @return string|null JSON 字符串 */ private function textToJson(string $text): ?string { $lines = array_filter( array_map('trim', explode("\n", $text)), fn ($line) => $line !== '' ); if (empty($lines)) { return null; } return json_encode(array_values($lines), JSON_UNESCAPED_UNICODE); } /** * 统一整理后台提交的会员等级主题配置数据。 * * @return array */ private function validatedPayload(Request $request): array { $data = $request->validate([ 'name' => 'required|string|max:50', 'icon' => 'required|string|max:20', 'color' => 'required|string|max:10', 'exp_multiplier' => 'required|numeric|min:1|max:99', 'jjb_multiplier' => 'required|numeric|min:1|max:99', 'sort_order' => 'required|integer|min:0', 'price' => 'required|integer|min:0', 'duration_days' => 'required|integer|min:0', 'join_templates' => 'nullable|string', 'leave_templates' => 'nullable|string', 'join_effect' => ['required', 'string', Rule::in(VipLevel::EFFECT_OPTIONS)], 'leave_effect' => ['required', 'string', Rule::in(VipLevel::EFFECT_OPTIONS)], 'join_banner_style' => 'required|in:aurora,storm,royal,cosmic,farewell', 'leave_banner_style' => 'required|in:aurora,storm,royal,cosmic,farewell', 'allow_custom_messages' => 'nullable|boolean', ]); // 将多行文本框内容转为 JSON 数组,便于后续随机抽取模板。 $data['join_templates'] = $this->textToJson($data['join_templates'] ?? ''); $data['leave_templates'] = $this->textToJson($data['leave_templates'] ?? ''); $data['allow_custom_messages'] = $request->boolean('allow_custom_messages'); return $data; } }