309 lines
20 KiB
PHP
309 lines
20 KiB
PHP
@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>
|