Files
2026-04-25 13:36:51 +08:00

309 lines
20 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@php
$holidayEvent = $event ?? null;
$isEdit = (bool) $holidayEvent;
$inputClass = 'w-full rounded-lg border border-gray-300 p-2.5 text-sm text-gray-700 focus:border-amber-400 focus:ring-amber-400';
$panelClass = 'rounded-xl border border-gray-100 bg-white p-6 shadow-sm';
$fieldValue = static fn(string $key, mixed $default = null): mixed => old($key, data_get($holidayEvent, $key, $default));
$sendAtValue = old('send_at');
if ($sendAtValue === null && $holidayEvent?->send_at) {
$sendAtValue = $holidayEvent->send_at->format('Y-m-d\TH:i');
}
$repeatType = (string) $fieldValue('repeat_type', 'once');
$distributeType = (string) $fieldValue('distribute_type', 'random');
$targetType = (string) $fieldValue('target_type', 'all');
$targetValue = old('target_value', data_get($holidayEvent, 'target_value', $targetType === 'level' ? 1 : null));
$scheduleMonth = old('schedule_month', data_get($holidayEvent, 'schedule_month', $holidayEvent?->send_at?->format('n') ?? now()->format('n')));
$scheduleDay = old('schedule_day', data_get($holidayEvent, 'schedule_day', $holidayEvent?->send_at?->format('j') ?? now()->format('j')));
$scheduleTime = old('schedule_time', data_get($holidayEvent, 'schedule_time', $holidayEvent?->send_at?->format('H:i') ?? '20:00'));
$durationDays = old('duration_days', data_get($holidayEvent, 'duration_days', 1));
$dailyOccurrences = old('daily_occurrences', data_get($holidayEvent, 'daily_occurrences', 1));
$occurrenceIntervalMinutes = old('occurrence_interval_minutes', data_get($holidayEvent, 'occurrence_interval_minutes', 60));
@endphp
<div class="max-w-4xl mx-auto space-y-6">
<div class="{{ $panelClass }}">
<div class="mb-6 flex items-center justify-between gap-4">
<div class="flex items-center gap-3">
<a href="{{ route('admin.holiday-events.index') }}" class="text-sm text-gray-400 transition hover:text-gray-600"> 返回列表</a>
<div>
<h2 class="text-lg font-bold text-gray-800">{{ $pageTitle }}</h2>
<p class="mt-1 text-xs text-gray-500">{{ $pageDescription }}</p>
</div>
</div>
@if ($isEdit && data_get($holidayEvent, 'status'))
<span class="rounded-full bg-gray-100 px-3 py-1 text-xs font-medium text-gray-500">
当前状态:{{ data_get($holidayEvent, 'status') }}
</span>
@endif
</div>
@if ($errors->any())
<div class="mb-6 rounded-lg border border-red-200 bg-red-50 p-4">
<div class="mb-2 text-sm font-bold text-red-700">提交失败,请检查以下字段:</div>
<ul class="list-inside list-disc space-y-1 text-sm text-red-700">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ $action }}" method="POST"
x-data="holidayEventForm(@js([
'repeatType' => $repeatType,
'distributeType' => $distributeType,
'targetType' => $targetType,
'scheduleMonth' => (string) $scheduleMonth,
'scheduleDay' => (string) $scheduleDay,
'scheduleTime' => (string) $scheduleTime,
'durationDays' => (string) $durationDays,
'dailyOccurrences' => (string) $dailyOccurrences,
'occurrenceIntervalMinutes' => (string) $occurrenceIntervalMinutes,
]))"
class="space-y-6">
@csrf
@isset($method)
@method($method)
@endisset
<div>
<h3 class="mb-3 border-b pb-2 text-sm font-bold text-gray-700">📋 基础信息</h3>
<div class="grid grid-cols-1 gap-4">
<div>
<label for="name" class="mb-1 block text-xs font-bold text-gray-600">活动名称 <span class="text-red-500">*</span></label>
<input id="name" type="text" name="name" value="{{ $fieldValue('name') }}" required
placeholder="例:元旦快乐🎊" class="{{ $inputClass }}">
</div>
<div>
<label for="description" class="mb-1 block text-xs font-bold text-gray-600">活动描述 <span
class="font-normal text-gray-400">(可选,公屏广播时显示)</span></label>
<textarea id="description" name="description" rows="3" placeholder="例:新年快乐!感谢大家一直以来的陪伴,送上新年礼物!"
class="{{ $inputClass }}">{{ $fieldValue('description') }}</textarea>
</div>
</div>
</div>
<div>
<h3 class="mb-3 border-b pb-2 text-sm font-bold text-gray-700">💰 奖励配置</h3>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<label for="total_amount" class="mb-1 block text-xs font-bold text-gray-600">总金币奖池 <span class="text-red-500">*</span></label>
<input id="total_amount" type="number" name="total_amount"
value="{{ $fieldValue('total_amount', 100000) }}" min="1" required class="{{ $inputClass }}">
</div>
<div>
<label for="max_claimants" class="mb-1 block text-xs font-bold text-gray-600">可领取人数上限 <span
class="font-normal text-gray-400">0 = 不限)</span></label>
<input id="max_claimants" type="number" name="max_claimants"
value="{{ $fieldValue('max_claimants', 0) }}" min="0" class="{{ $inputClass }}">
</div>
<div class="md:col-span-2">
<label class="mb-2 block text-xs font-bold text-gray-600">分配方式 <span class="text-red-500">*</span></label>
<div class="grid gap-3 md:grid-cols-2">
<label
class="flex cursor-pointer items-start gap-3 rounded-xl border border-purple-100 bg-purple-50 px-4 py-3 transition hover:border-purple-200">
<input type="radio" name="distribute_type" value="random" x-model="distributeType"
class="mt-1" />
<span>
<span class="block text-sm font-bold text-gray-800">🎲 随机分配</span>
<span class="mt-1 block text-xs text-gray-500">二倍均值算法,每位用户领取金额不同。</span>
</span>
</label>
<label
class="flex cursor-pointer items-start gap-3 rounded-xl border border-blue-100 bg-blue-50 px-4 py-3 transition hover:border-blue-200">
<input type="radio" name="distribute_type" value="fixed" x-model="distributeType"
class="mt-1" />
<span>
<span class="block text-sm font-bold text-gray-800">📏 定额发放</span>
<span class="mt-1 block text-xs text-gray-500">每位用户领取固定金额,更方便控制总量。</span>
</span>
</label>
</div>
</div>
<div x-show="distributeType === 'random'" x-transition.opacity class="md:col-span-2">
<div class="grid gap-4 rounded-xl border border-purple-100 bg-purple-50 p-4 md:grid-cols-2">
<div>
<label for="min_amount" class="mb-1 block text-xs font-bold text-gray-600">最低保底金额</label>
<input id="min_amount" type="number" name="min_amount"
value="{{ $fieldValue('min_amount', 100) }}" min="1" class="{{ $inputClass }}">
</div>
<div>
<label for="max_amount" class="mb-1 block text-xs font-bold text-gray-600">单人最高上限 <span
class="font-normal text-gray-400">(可选)</span></label>
<input id="max_amount" type="number" name="max_amount" value="{{ $fieldValue('max_amount') }}"
min="1" placeholder="不填则由后端自动计算" class="{{ $inputClass }}">
</div>
</div>
</div>
<div x-show="distributeType === 'fixed'" x-transition.opacity class="md:col-span-2">
<div class="rounded-xl border border-blue-100 bg-blue-50 p-4">
<label for="fixed_amount" class="mb-1 block text-xs font-bold text-gray-600">每人固定金额 <span
class="text-red-500">*</span></label>
<input id="fixed_amount" type="number" name="fixed_amount"
value="{{ $fieldValue('fixed_amount', 500) }}" min="1" class="{{ $inputClass }}">
<p class="mt-2 text-xs text-gray-500">总发放金额 = 固定金额 × 领取人数(仍受领取人数上限控制)。</p>
</div>
</div>
</div>
</div>
<div>
<h3 class="mb-3 border-b pb-2 text-sm font-bold text-gray-700"> 时间配置</h3>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<label for="send_at" class="mb-1 block text-xs font-bold text-gray-600"
x-text="repeatType === 'once' ? '触发时间 *' : '首次触发 / 基准时间 *'"></label>
<input id="send_at" type="datetime-local" name="send_at" value="{{ $sendAtValue }}" required
class="{{ $inputClass }}">
<p class="mt-1 text-xs text-gray-400">
`once/daily/weekly/monthly` 使用该时间直接调度;`yearly` 则把它作为首次触发与兼容基准值。
</p>
</div>
<div>
<label for="expire_minutes" class="mb-1 block text-xs font-bold text-gray-600">领取有效期(分钟)</label>
<input id="expire_minutes" type="number" name="expire_minutes"
value="{{ $fieldValue('expire_minutes', 30) }}" min="1" max="1440"
class="{{ $inputClass }}">
</div>
<div class="md:col-span-2">
<label for="repeat_type" class="mb-1 block text-xs font-bold text-gray-600">重复方式</label>
<select id="repeat_type" name="repeat_type" x-model="repeatType" class="{{ $inputClass }}">
<option value="once">仅一次</option>
<option value="daily">每天(相同时间)</option>
<option value="weekly">每周(相同时间)</option>
<option value="monthly">每月(相同日期时间)</option>
<option value="yearly">每年节日(高级调度)</option>
<option value="cron">高级 CRON</option>
</select>
<div class="mt-3 grid gap-3 text-xs text-gray-500 md:grid-cols-2">
<div class="rounded-lg border border-gray-200 bg-gray-50 px-4 py-3" x-show="repeatType === 'once'">
活动只会在指定 `send_at` 触发一次。
</div>
<div class="rounded-lg border border-gray-200 bg-gray-50 px-4 py-3" x-show="repeatType === 'daily'">
`send_at` 的时分为基准,每天自动重复一次。
</div>
<div class="rounded-lg border border-gray-200 bg-gray-50 px-4 py-3" x-show="repeatType === 'weekly'">
`send_at` 的星期与时间为基准,每周自动重复。
</div>
<div class="rounded-lg border border-gray-200 bg-gray-50 px-4 py-3" x-show="repeatType === 'monthly'">
`send_at` 的日期与时间为基准,每月自动重复。
</div>
<div class="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-amber-700"
x-show="repeatType === 'yearly'">
每年同一节日重复,可配置连续多天与每天多次发送。
</div>
<div class="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-slate-600"
x-show="repeatType === 'cron'">
兼容旧版 CRON 配置活动,适合保留现有高级规则。
</div>
</div>
</div>
<div class="md:col-span-2" x-show="repeatType === 'yearly'" x-transition.opacity>
<div class="space-y-4 rounded-2xl border border-amber-200 bg-gradient-to-br from-amber-50 via-orange-50 to-white p-5">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h4 class="text-sm font-bold text-amber-700">🎯 年度节日高级调度</h4>
<p class="mt-1 text-xs text-amber-700/80">用于 yearly:指定每年节日日期、连续天数以及每天的多次发送频率。</p>
</div>
<div class="rounded-full bg-white/80 px-3 py-1 text-xs font-medium text-amber-700 shadow-sm"
x-text="yearlySummary()"></div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
<div>
<label for="schedule_month" class="mb-1 block text-xs font-bold text-gray-600">节日月份</label>
<input id="schedule_month" type="number" name="schedule_month" min="1" max="12"
x-model="scheduleMonth" value="{{ $scheduleMonth }}" class="{{ $inputClass }}"
placeholder="1-12">
</div>
<div>
<label for="schedule_day" class="mb-1 block text-xs font-bold text-gray-600">节日日期</label>
<input id="schedule_day" type="number" name="schedule_day" min="1" max="31"
x-model="scheduleDay" value="{{ $scheduleDay }}" class="{{ $inputClass }}"
placeholder="1-31">
</div>
<div>
<label for="schedule_time" class="mb-1 block text-xs font-bold text-gray-600">首个发送时刻</label>
<input id="schedule_time" type="time" name="schedule_time" x-model="scheduleTime"
value="{{ $scheduleTime }}" class="{{ $inputClass }}">
</div>
<div>
<label for="duration_days" class="mb-1 block text-xs font-bold text-gray-600">连续天数</label>
<input id="duration_days" type="number" name="duration_days" min="1" max="31"
x-model="durationDays" value="{{ $durationDays }}" class="{{ $inputClass }}">
</div>
<div>
<label for="daily_occurrences" class="mb-1 block text-xs font-bold text-gray-600">每天发送次数</label>
<input id="daily_occurrences" type="number" name="daily_occurrences" min="1" max="24"
x-model="dailyOccurrences" value="{{ $dailyOccurrences }}" class="{{ $inputClass }}">
</div>
<div>
<label for="occurrence_interval_minutes"
class="mb-1 block text-xs font-bold text-gray-600">发送间隔(分钟)</label>
<input id="occurrence_interval_minutes" type="number" name="occurrence_interval_minutes"
min="1" max="1440" x-model="occurrenceIntervalMinutes"
value="{{ $occurrenceIntervalMinutes }}" class="{{ $inputClass }}">
</div>
</div>
</div>
</div>
<div class="md:col-span-2" x-show="repeatType === 'cron'" x-transition.opacity>
<div class="rounded-xl border border-slate-200 bg-slate-50 p-4">
<label for="cron_expr" class="mb-1 block text-xs font-bold text-gray-600">CRON 表达式</label>
<input id="cron_expr" type="text" name="cron_expr" value="{{ $fieldValue('cron_expr') }}"
class="{{ $inputClass }}" placeholder="例:0 9 * * 1">
</div>
</div>
</div>
</div>
<div>
<h3 class="mb-3 border-b pb-2 text-sm font-bold text-gray-700">🎯 目标用户</h3>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<label for="target_type" class="mb-1 block text-xs font-bold text-gray-600">用户范围</label>
<select id="target_type" name="target_type" x-model="targetType" class="{{ $inputClass }}">
<option value="all">全部在线用户</option>
<option value="vip"> VIP 用户</option>
<option value="level">指定等级以上</option>
</select>
</div>
<div x-show="targetType !== 'all'" x-transition.opacity>
<label for="target_value" class="mb-1 block text-xs font-bold text-gray-600"
x-text="targetType === 'vip' ? 'VIP 目标值 / 备注' : '最低用户等级'"></label>
<input id="target_value" x-bind:type="targetType === 'level' ? 'number' : 'text'" name="target_value"
value="{{ $targetValue }}" x-bind:min="targetType === 'level' ? 1 : null"
class="{{ $inputClass }}" :placeholder="targetType === 'vip' ? '例:gold / 3 / 高级档位' : '例:10'">
</div>
<div x-show="targetType === 'vip'" x-transition.opacity class="md:col-span-2">
<div class="rounded-lg border border-indigo-100 bg-indigo-50 px-4 py-3 text-xs text-indigo-700">
当前仍沿用后端 `target_type=vip` 逻辑,若主线后端升级为指定 VIP 档位,可继续复用 `target_value`
</div>
</div>
</div>
</div>
<div class="flex flex-wrap gap-3 border-t pt-4">
<button type="submit"
class="rounded-lg bg-amber-500 px-8 py-2.5 font-bold text-white shadow-sm transition hover:bg-amber-600">
{{ $submitLabel }}
</button>
<a href="{{ route('admin.holiday-events.index') }}"
class="rounded-lg bg-gray-100 px-6 py-2.5 font-bold text-gray-600 transition hover:bg-gray-200">
取消
</a>
</div>
</form>
</div>
</div>