327 lines
19 KiB
PHP
327 lines
19 KiB
PHP
{{-- 文件功能:后台节日福利活动列表页,展示节日福利模板并提供启用、立即触发、编辑、删除等操作。 --}}
|
||
@extends('admin.layouts.app')
|
||
|
||
@section('title', '节日福利管理')
|
||
|
||
@section('content')
|
||
@php
|
||
$visibleEvents = $events->getCollection();
|
||
$enabledCount = $visibleEvents->where('enabled', true)->count();
|
||
$yearlyCount = $visibleEvents->where('repeat_type', 'yearly')->count();
|
||
$statusMap = [
|
||
'pending' => ['待触发', 'bg-amber-100 text-amber-700'],
|
||
'active' => ['领取中', 'bg-emerald-100 text-emerald-700'],
|
||
'completed' => ['已结束', 'bg-slate-100 text-slate-600'],
|
||
'cancelled' => ['已取消', 'bg-rose-100 text-rose-700'],
|
||
];
|
||
$repeatLabels = [
|
||
'once' => '一次',
|
||
'daily' => '每天',
|
||
'weekly' => '每周',
|
||
'monthly' => '每月',
|
||
'yearly' => '每年',
|
||
'cron' => 'CRON',
|
||
];
|
||
@endphp
|
||
|
||
<div class="space-y-6">
|
||
{{-- 页头 --}}
|
||
<div class="rounded-xl border border-gray-100 bg-white p-5 shadow-sm">
|
||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||
<div class="space-y-2">
|
||
<div>
|
||
<h2 class="text-xl font-black tracking-tight text-slate-800">节日福利管理</h2>
|
||
<p class="mt-1 text-sm text-slate-500">统一维护节日福利模板与触发计划。</p>
|
||
</div>
|
||
<div class="flex flex-wrap gap-2 text-xs font-semibold">
|
||
<span class="rounded-full bg-slate-100 px-3 py-1 text-slate-600">当前页 {{ $visibleEvents->count() }} 个活动</span>
|
||
<span class="rounded-full bg-emerald-100 px-3 py-1 text-emerald-700">已启用 {{ $enabledCount }} 个</span>
|
||
<span class="rounded-full bg-amber-100 px-3 py-1 text-amber-700">年度模板 {{ $yearlyCount }} 个</span>
|
||
</div>
|
||
</div>
|
||
|
||
<a href="{{ route('admin.holiday-events.create') }}"
|
||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-amber-500 px-4 py-2.5 text-sm font-bold text-white shadow-sm transition hover:bg-amber-600">
|
||
<span class="text-base leading-none">+</span>
|
||
<span>创建活动</span>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- 列表 --}}
|
||
<div class="overflow-hidden rounded-xl border border-gray-100 bg-white shadow-sm">
|
||
<div class="border-b border-gray-100 px-6 py-3">
|
||
<div class="flex flex-col gap-1 lg:flex-row lg:items-center lg:justify-between">
|
||
<div>
|
||
<h3 class="text-base font-bold text-slate-800">福利模板列表</h3>
|
||
<p class="mt-1 text-xs text-slate-500">列表按关键信息压缩展示,适合批量查看。</p>
|
||
</div>
|
||
<div class="text-xs text-slate-400">年度模板额外显示月日、持续天数、轮次与轮次间隔</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="overflow-x-auto">
|
||
<table class="min-w-[1120px] w-full table-fixed text-sm">
|
||
<thead class="bg-slate-50 text-left text-xs font-bold uppercase tracking-[0.16em] text-slate-500">
|
||
<tr>
|
||
<th class="px-4 py-3 w-14">编号</th>
|
||
<th class="px-4 py-3 w-[25%]">活动信息</th>
|
||
<th class="px-4 py-3 w-[10%]">奖池</th>
|
||
<th class="px-4 py-3 w-[10%]">分配</th>
|
||
<th class="px-4 py-3 w-[11%]">限额 / 重复</th>
|
||
<th class="px-4 py-3 w-[15%]">触发计划</th>
|
||
<th class="px-4 py-3 w-[13%]">运行状态</th>
|
||
<th class="px-4 py-3 w-[16%] text-right">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-100">
|
||
@forelse ($events as $event)
|
||
@php
|
||
[$statusLabel, $statusClass] = $statusMap[$event->status] ?? ['未知', 'bg-slate-100 text-slate-600'];
|
||
$repeatLabel = $repeatLabels[$event->repeat_type] ?? '-';
|
||
$isRandomDistribute = $event->distribute_type === 'random';
|
||
$yearlyScheduleText =
|
||
data_get($event, 'schedule_month', $event->send_at?->format('n')) .
|
||
'月' .
|
||
data_get($event, 'schedule_day', $event->send_at?->format('j')) .
|
||
'日 · ' .
|
||
data_get($event, 'duration_days', 1) .
|
||
'天 · 每天' .
|
||
data_get($event, 'daily_occurrences', 1) .
|
||
'次 · 间隔' .
|
||
data_get($event, 'occurrence_interval_minutes', 60) .
|
||
'分钟';
|
||
@endphp
|
||
<tr class="align-top transition hover:bg-slate-50/70 {{ !$event->enabled ? 'opacity-60' : '' }}">
|
||
<td class="px-4 py-4">
|
||
<div class="flex h-9 w-9 items-center justify-center rounded-lg bg-slate-100 text-sm font-bold text-slate-600">
|
||
{{ $event->id }}
|
||
</div>
|
||
</td>
|
||
|
||
<td class="px-4 py-4">
|
||
<div class="space-y-1.5">
|
||
<div class="text-base font-bold tracking-tight text-slate-800">{{ $event->name }}</div>
|
||
<div class="flex flex-wrap gap-2 text-[11px] font-semibold">
|
||
<span class="rounded-full bg-slate-100 px-2.5 py-1 text-slate-600">{{ $repeatLabel }}</span>
|
||
@if ($event->repeat_type === 'yearly')
|
||
<span class="rounded-full bg-amber-50 px-2.5 py-1 text-amber-700">年度固定节日模板</span>
|
||
@endif
|
||
</div>
|
||
<div class="max-w-md text-xs leading-6 text-slate-500">
|
||
{{ Str::limit($event->description ?: '未填写节日祝福语,当前活动将广播默认福利提示。', 32) }}
|
||
</div>
|
||
</div>
|
||
</td>
|
||
|
||
<td class="px-4 py-4">
|
||
<div class="font-black text-2xl tracking-tight text-amber-600">{{ number_format($event->total_amount) }}</div>
|
||
<div class="mt-1 text-xs text-slate-500">金币奖池</div>
|
||
</td>
|
||
|
||
<td class="px-4 py-4">
|
||
<div class="space-y-1.5">
|
||
<span
|
||
class="inline-flex rounded-full px-2.5 py-1 text-xs font-bold {{ $isRandomDistribute ? 'bg-fuchsia-100 text-fuchsia-700' : 'bg-sky-100 text-sky-700' }}">
|
||
{{ $isRandomDistribute ? '随机分配' : '固定金额' }}
|
||
</span>
|
||
<div class="text-xs leading-5 text-slate-500">
|
||
@if ($isRandomDistribute)
|
||
按本轮人数拆分
|
||
@else
|
||
每人 {{ number_format($event->fixed_amount) }} 金币
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</td>
|
||
|
||
<td class="px-4 py-4">
|
||
<div class="space-y-2 text-xs leading-5 text-slate-500">
|
||
<div>
|
||
<div class="font-medium text-slate-700">限额人数</div>
|
||
<div class="mt-0.5 text-sm font-bold text-slate-800">
|
||
{{ $event->max_claimants === 0 ? '不限人数' : $event->max_claimants . ' 人' }}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div class="font-medium text-slate-700">重复策略</div>
|
||
<div class="mt-0.5">{{ $repeatLabel }}</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
|
||
<td class="px-4 py-4">
|
||
<div class="space-y-2 text-xs leading-5 text-slate-500">
|
||
<div>
|
||
<div class="font-medium text-slate-700">最近计划时间</div>
|
||
<div class="mt-0.5 text-xl font-black tracking-tight text-slate-800">{{ $event->send_at->format('m-d H:i') }}</div>
|
||
<div class="mt-0.5">{{ $event->send_at->format('Y-m-d') }}</div>
|
||
</div>
|
||
@if ($event->repeat_type === 'yearly')
|
||
<div class="rounded-lg bg-amber-50 px-2.5 py-2 text-amber-700">
|
||
{{ Str::limit($yearlyScheduleText, 22) }}
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</td>
|
||
|
||
<td class="px-4 py-4">
|
||
<div class="space-y-2">
|
||
<div class="flex flex-wrap gap-2">
|
||
<span class="inline-flex rounded-full px-2.5 py-1 text-xs font-bold {{ $statusClass }}">
|
||
{{ $statusLabel }}
|
||
</span>
|
||
<span data-role="enabled-badge"
|
||
class="inline-flex rounded-full px-2.5 py-1 text-xs font-bold {{ $event->enabled ? 'bg-emerald-100 text-emerald-700' : 'bg-slate-100 text-slate-500' }}">
|
||
{{ $event->enabled ? '已启用' : '已停用' }}
|
||
</span>
|
||
</div>
|
||
|
||
<button type="button"
|
||
onclick="toggleHoliday({{ $event->id }}, this)"
|
||
class="inline-flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-bold transition {{ $event->enabled ? 'bg-emerald-500 text-white hover:bg-emerald-600' : 'bg-slate-200 text-slate-600 hover:bg-slate-300' }}"
|
||
data-enabled="{{ $event->enabled ? '1' : '0' }}">
|
||
<span data-role="toggle-dot"
|
||
class="h-2 w-2 rounded-full {{ $event->enabled ? 'bg-white' : 'bg-slate-500' }}"></span>
|
||
<span data-role="toggle-label">{{ $event->enabled ? '切换为停用' : '重新启用模板' }}</span>
|
||
</button>
|
||
|
||
<div class="text-xs leading-5 text-slate-500">
|
||
@if ($event->triggered_at)
|
||
最近触发:{{ $event->triggered_at->format('m-d H:i') }}
|
||
@else
|
||
尚未产生发放批次
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</td>
|
||
|
||
<td class="px-4 py-4">
|
||
<div class="flex flex-wrap justify-end gap-2">
|
||
@if ($event->status === 'pending')
|
||
<form action="{{ route('admin.holiday-events.trigger-now', $event) }}"
|
||
method="POST" onsubmit="return confirm('确定立即触发此活动吗?')">
|
||
@csrf
|
||
<button type="submit"
|
||
class="inline-flex items-center justify-center rounded-lg bg-amber-500 px-3 py-2 text-xs font-bold text-white transition hover:bg-amber-600">
|
||
▶ 立即触发
|
||
</button>
|
||
</form>
|
||
@endif
|
||
|
||
<a href="{{ route('admin.holiday-events.edit', $event) }}"
|
||
class="inline-flex items-center justify-center rounded-lg bg-indigo-50 px-3 py-2 text-xs font-bold text-indigo-700 transition hover:bg-indigo-100">
|
||
编辑
|
||
</a>
|
||
|
||
<form action="{{ route('admin.holiday-events.destroy', $event) }}" method="POST"
|
||
onsubmit="return confirm('确定删除此活动吗?')">
|
||
@csrf
|
||
@method('DELETE')
|
||
<button type="submit"
|
||
class="inline-flex items-center justify-center rounded-lg bg-rose-50 px-3 py-2 text-xs font-bold text-rose-600 transition hover:bg-rose-100">
|
||
删除
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@empty
|
||
<tr>
|
||
<td colspan="8" class="px-6 py-16 text-center text-slate-400">
|
||
暂无节日福利活动,<a href="{{ route('admin.holiday-events.create') }}"
|
||
class="font-bold text-amber-500 hover:text-amber-600">立即创建一个</a>。
|
||
</td>
|
||
</tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
@if ($events->hasPages())
|
||
<div class="border-t border-gray-100 px-6 py-4">
|
||
{{ $events->links() }}
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
/**
|
||
* 根据启用状态返回开关按钮类名。
|
||
*/
|
||
function holidayToggleButtonClasses(enabled) {
|
||
return enabled ?
|
||
'inline-flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-bold transition bg-emerald-500 text-white hover:bg-emerald-600' :
|
||
'inline-flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-bold transition bg-slate-200 text-slate-600 hover:bg-slate-300';
|
||
}
|
||
|
||
/**
|
||
* 根据启用状态返回开关文案。
|
||
*/
|
||
function holidayToggleButtonLabel(enabled) {
|
||
return enabled ? '切换为停用' : '重新启用模板';
|
||
}
|
||
|
||
/**
|
||
* 根据启用状态返回状态徽标类名。
|
||
*/
|
||
function holidayEnabledBadgeClasses(enabled) {
|
||
return enabled ? 'inline-flex rounded-full px-2.5 py-1 text-xs font-bold bg-emerald-100 text-emerald-700' :
|
||
'inline-flex rounded-full px-2.5 py-1 text-xs font-bold bg-slate-100 text-slate-500';
|
||
}
|
||
|
||
/**
|
||
* 根据启用状态返回状态徽标文案。
|
||
*/
|
||
function holidayEnabledBadgeLabel(enabled) {
|
||
return enabled ? '已启用' : '已停用';
|
||
}
|
||
|
||
/**
|
||
* 切换节日活动启用/禁用状态,并同步刷新当前行文案与样式。
|
||
*/
|
||
function toggleHoliday(id, btn) {
|
||
fetch(`/admin/holiday-events/${id}/toggle`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||
'Accept': 'application/json',
|
||
},
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (!data.ok) {
|
||
return;
|
||
}
|
||
|
||
const enabled = Boolean(data.enabled);
|
||
const row = btn.closest('tr');
|
||
const dot = btn.querySelector('[data-role="toggle-dot"]');
|
||
const label = btn.querySelector('[data-role="toggle-label"]');
|
||
const enabledBadge = row?.querySelector('[data-role="enabled-badge"]');
|
||
|
||
btn.dataset.enabled = enabled ? '1' : '0';
|
||
btn.className = holidayToggleButtonClasses(enabled);
|
||
|
||
if (dot) {
|
||
dot.className = enabled ? 'h-2 w-2 rounded-full bg-white' : 'h-2 w-2 rounded-full bg-slate-500';
|
||
}
|
||
|
||
if (label) {
|
||
label.textContent = holidayToggleButtonLabel(enabled);
|
||
}
|
||
|
||
if (enabledBadge) {
|
||
enabledBadge.className = holidayEnabledBadgeClasses(enabled);
|
||
enabledBadge.textContent = holidayEnabledBadgeLabel(enabled);
|
||
}
|
||
|
||
if (row) {
|
||
row.classList.toggle('opacity-60', !enabled);
|
||
}
|
||
});
|
||
}
|
||
</script>
|
||
@endsection
|