功能:婚姻系统第9步(后台管理页面)

Admin/MarriageManagerController:
- index() 总览统计卡片
- list() 婚姻列表(筛选/强制离婚/取消求婚)
- proposals() 求婚记录
- ceremonies() 婚礼红包记录
- claimDetail() 红包领取明细
- intimacyLogs() 亲密度日志(来源筛选)
- configs/updateConfigs 参数配置(批量保存)
- tiers/updateTier 婚礼档位管理

Views(7个页面):admin/marriages/{index|list|configs|tiers|ceremonies|claim-detail|proposals|intimacy-logs}
侧边栏:superlevel 区块新增「💒 婚姻管理」入口
This commit is contained in:
2026-03-01 15:15:03 +08:00
parent 4f49fb7ce8
commit d2797d5b59
10 changed files with 1108 additions and 1 deletions

View File

@@ -1,10 +1,249 @@
<?php
/**
* 文件功能:后台婚姻系统管理控制器
*
* 提供总览统计、婚姻/求婚明细查询、婚礼档位管理、
* 参数配置、亲密度日志审计、强制离婚等管理操作。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Marriage;
use App\Models\MarriageIntimacyLog;
use App\Models\WeddingCeremony;
use App\Models\WeddingEnvelopeClaim;
use App\Models\WeddingTier;
use App\Services\MarriageConfigService;
use App\Services\MarriageService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class MarriageManagerController extends Controller
{
//
public function __construct(
private readonly MarriageConfigService $config,
private readonly MarriageService $marriageService,
) {}
/**
* 婚姻管理总览(统计卡片 + 最近记录)。
*/
public function index(): View
{
$stats = [
'total_married' => Marriage::where('status', 'married')->count(),
'total_pending' => Marriage::where('status', 'pending')->count(),
'total_divorced' => Marriage::where('status', 'divorced')->count(),
'total_weddings' => WeddingCeremony::whereIn('status', ['active', 'completed'])->count(),
'total_envelopes' => WeddingEnvelopeClaim::sum('amount'),
'claimed_amount' => WeddingEnvelopeClaim::where('claimed', true)->sum('amount'),
];
$recentMarriages = Marriage::with(['user:id,username', 'partner:id,username', 'ringItem:id,name,icon'])
->where('status', 'married')
->orderByDesc('married_at')
->limit(10)
->get();
$recentDivorces = Marriage::with(['user:id,username', 'partner:id,username'])
->where('status', 'divorced')
->orderByDesc('divorced_at')
->limit(8)
->get();
return view('admin.marriages.index', compact('stats', 'recentMarriages', 'recentDivorces'));
}
/**
* 婚姻列表(支持按状态/用户名筛选)。
*/
public function list(Request $request): View
{
$query = Marriage::with(['user:id,username', 'partner:id,username', 'ringItem:id,name,icon'])
->orderByDesc('id');
if ($status = $request->get('status')) {
$query->where('status', $status);
}
if ($search = $request->get('search')) {
$query->where(function ($q) use ($search) {
$q->whereHas('user', fn ($u) => $u->where('username', 'like', "%{$search}%"))
->orWhereHas('partner', fn ($u) => $u->where('username', 'like', "%{$search}%"));
});
}
$marriages = $query->paginate(20)->withQueryString();
return view('admin.marriages.list', compact('marriages'));
}
/**
* 求婚记录列表(含 pending/expired/rejected
*/
public function proposals(Request $request): View
{
$proposals = Marriage::with(['user:id,username', 'partner:id,username', 'ringItem:id,name,icon'])
->whereIn('status', ['pending', 'expired', 'rejected'])
->orderByDesc('proposed_at')
->paginate(20)
->withQueryString();
return view('admin.marriages.proposals', compact('proposals'));
}
/**
* 婚礼红包记录。
*/
public function ceremonies(Request $request): View
{
$ceremonies = WeddingCeremony::with([
'marriage.user:id,username',
'marriage.partner:id,username',
'tier:id,name,tier,icon',
])
->orderByDesc('id')
->paginate(20)
->withQueryString();
return view('admin.marriages.ceremonies', compact('ceremonies'));
}
/**
* 红包领取明细(某场婚礼)。
*/
public function claimDetail(WeddingCeremony $ceremony): View
{
$ceremony->load(['marriage.user:id,username', 'marriage.partner:id,username', 'tier:id,name,icon']);
$claims = WeddingEnvelopeClaim::with('user:id,username,headface')
->where('ceremony_id', $ceremony->id)
->orderBy('amount', 'desc')
->paginate(30);
return view('admin.marriages.claim-detail', compact('ceremony', 'claims'));
}
/**
* 亲密度日志列表(支持按用户筛选)。
*/
public function intimacyLogs(Request $request): View
{
$query = MarriageIntimacyLog::with(['marriage.user:id,username', 'marriage.partner:id,username'])
->orderByDesc('id');
if ($search = $request->get('search')) {
$query->whereHas('marriage', function ($q) use ($search) {
$q->whereHas('user', fn ($u) => $u->where('username', 'like', "%{$search}%"))
->orWhereHas('partner', fn ($u) => $u->where('username', 'like', "%{$search}%"));
});
}
if ($source = $request->get('source')) {
$query->where('source', $source);
}
$logs = $query->paginate(30)->withQueryString();
return view('admin.marriages.intimacy-logs', compact('logs'));
}
/**
* 参数配置页面(读取所有分组配置)。
*/
public function configs(): View
{
$groups = $this->config->allGrouped();
return view('admin.marriages.configs', compact('groups'));
}
/**
* 批量保存参数配置。
*/
public function updateConfigs(Request $request): RedirectResponse
{
$data = $request->validate([
'configs' => 'required|array',
'configs.*' => 'required|integer',
]);
$this->config->batchSet($data['configs']);
return redirect()->route('admin.marriages.configs')->with('success', '婚姻参数配置已保存!');
}
/**
* 婚礼档位配置页面。
*/
public function tiers(): View
{
$tiers = WeddingTier::orderBy('tier')->get();
return view('admin.marriages.tiers', compact('tiers'));
}
/**
* 更新婚礼档位。
*/
public function updateTier(Request $request, WeddingTier $tier): RedirectResponse
{
$data = $request->validate([
'name' => 'required|string|max:30',
'icon' => 'required|string|max:20',
'amount' => 'required|integer|min:1',
'description' => 'nullable|string|max:100',
'is_active' => 'boolean',
]);
$data['is_active'] = $request->boolean('is_active', true);
$tier->update($data);
return redirect()->route('admin.marriages.tiers')->with('success', "档位【{$tier->name}】已更新!");
}
/**
* 管理员强制离婚。
*/
public function forceDissolve(Request $request, Marriage $marriage): RedirectResponse
{
$data = $request->validate([
'admin_note' => 'required|string|max:200',
]);
if ($marriage->status !== 'married') {
return back()->with('error', '该婚姻不是已婚状态,无法操作。');
}
$admin = $request->user();
$result = $this->marriageService->forceDissolve($marriage, $admin, true);
// 写入管理员备注
$marriage->update(['admin_note' => $data['admin_note']]);
$msg = $result['ok'] ? '强制离婚已完成。' : $result['message'];
$type = $result['ok'] ? 'success' : 'error';
return back()->with($type, $msg);
}
/**
* 管理员取消求婚(释放戒指 退还状态 active
*/
public function cancelProposal(Request $request, Marriage $marriage): RedirectResponse
{
if ($marriage->status !== 'pending') {
return back()->with('error', '该求婚不是进行中状态,无法取消。');
}
$this->marriageService->expireProposal($marriage);
$marriage->update(['admin_note' => '管理员手动取消求婚:'.($request->input('reason', ''))]);
return back()->with('success', '求婚已取消,戒指标记遗失。');
}
}

View File

@@ -67,6 +67,10 @@
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.vip.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '👑 VIP 会员等级' . $ro !!}
</a>
<a href="{{ route('admin.marriages.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.marriages.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '💒 婚姻管理' . $ro !!}
</a>
<a href="{{ route('admin.departments.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.departments.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '🏛️ 部门管理' . $ro !!}

View File

@@ -0,0 +1,104 @@
{{--
文件功能:婚礼仪式记录页(含红包分发详情链接)
@author ChatRoom Laravel
@version 1.0.0
--}}
@extends('admin.layouts.app')
@section('title', '婚礼红包记录')
@section('content')
<div class="flex items-center justify-between mb-5">
<h2 class="text-xl font-bold text-gray-800">🎊 婚礼红包记录</h2>
<a href="{{ route('admin.marriages.index') }}" class="text-sm text-indigo-600 hover:underline"> 返回总览</a>
</div>
<div class="bg-white rounded-xl shadow-sm border overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 text-gray-600 text-xs uppercase">
<tr>
<th class="px-4 py-3 text-left">ID</th>
<th class="px-4 py-3 text-left">新人</th>
<th class="px-4 py-3 text-left">档位</th>
<th class="px-4 py-3 text-center">总金额</th>
<th class="px-4 py-3 text-center">在线人数</th>
<th class="px-4 py-3 text-center">领取进度</th>
<th class="px-4 py-3 text-center">状态</th>
<th class="px-4 py-3 text-left">时间</th>
<th class="px-4 py-3 text-center">详情</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse ($ceremonies as $c)
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 text-gray-400 font-mono text-xs">{{ $c->id }}</td>
<td class="px-4 py-3">
<span class="font-bold">{{ $c->marriage?->user?->username }}</span>
<span class="text-gray-400 mx-1">×</span>
<span class="font-bold">{{ $c->marriage?->partner?->username }}</span>
</td>
<td class="px-4 py-3">
{{ $c->tier?->icon }} {{ $c->tier?->name ?? '—' }}
</td>
<td class="px-4 py-3 text-center font-bold text-amber-600">
{{ number_format($c->total_amount) }}
</td>
<td class="px-4 py-3 text-center text-gray-600">
{{ $c->online_count ?? '—' }}
</td>
<td class="px-4 py-3 text-center">
@if ($c->total_amount > 0)
<div class="text-xs text-gray-600">
{{ $c->claimed_count }} / {{ $c->online_count ?? '?' }}
</div>
<div class="text-xs text-emerald-600 font-bold">
{{ number_format($c->claimed_amount) }} / {{ number_format($c->total_amount) }}
</div>
@else
<span class="text-gray-300 text-xs">无红包</span>
@endif
</td>
<td class="px-4 py-3 text-center">
<span
class="px-2 py-0.5 rounded-full text-xs font-bold
{{ match ($c->status) {
'completed' => 'bg-green-100 text-green-700',
'active' => 'bg-blue-100 text-blue-700',
'pending' => 'bg-amber-100 text-amber-700',
'expired' => 'bg-gray-100 text-gray-500',
'cancelled' => 'bg-red-100 text-red-600',
default => 'bg-gray-100 text-gray-600',
} }}">
{{ ['completed' => '已完成', 'active' => '进行中', 'pending' => '待触发', 'expired' => '已过期', 'cancelled' => '已取消'][$c->status] ?? $c->status }}
</span>
</td>
<td class="px-4 py-3 text-xs text-gray-500">
{{ $c->ceremony_at?->format('Y-m-d H:i') }}
</td>
<td class="px-4 py-3 text-center">
@if ($c->total_amount > 0)
<a href="{{ route('admin.marriages.claim-detail', $c->id) }}"
class="text-xs text-indigo-600 hover:underline font-bold">明细</a>
@else
<span class="text-gray-300 text-xs"></span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="9" class="px-4 py-12 text-center text-gray-400">暂无婚礼记录</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if ($ceremonies->hasPages())
<div class="px-4 py-4 border-t">{{ $ceremonies->links() }}</div>
@endif
</div>
@endsection

View File

@@ -0,0 +1,85 @@
{{--
文件功能:婚礼红包领取明细页
@author ChatRoom Laravel
@version 1.0.0
--}}
@extends('admin.layouts.app')
@section('title', '婚礼红包明细')
@section('content')
<div class="flex items-center justify-between mb-5">
<div>
<h2 class="text-xl font-bold text-gray-800">🎁 红包领取明细</h2>
<p class="text-sm text-gray-500 mt-1">
{{ $ceremony->marriage?->user?->username }} × {{ $ceremony->marriage?->partner?->username }}
{{ $ceremony->tier?->icon }} {{ $ceremony->tier?->name ?? '婚礼' }}
· 总额 <strong class="text-amber-600">{{ number_format($ceremony->total_amount) }} </strong>
</p>
</div>
<a href="{{ route('admin.marriages.ceremonies') }}" class="text-sm text-indigo-600 hover:underline"> 返回婚礼列表</a>
</div>
{{-- 汇总 --}}
<div class="grid grid-cols-3 gap-4 mb-5">
<div class="bg-white rounded-xl border shadow-sm p-4 text-center">
<div class="text-2xl font-bold text-indigo-600">{{ $ceremony->online_count ?? 0 }}</div>
<div class="text-xs text-gray-500 mt-1">在线人数</div>
</div>
<div class="bg-white rounded-xl border shadow-sm p-4 text-center">
<div class="text-2xl font-bold text-green-600">{{ $ceremony->claimed_count }}</div>
<div class="text-xs text-gray-500 mt-1">已领取人数</div>
</div>
<div class="bg-white rounded-xl border shadow-sm p-4 text-center">
<div class="text-2xl font-bold text-amber-600">{{ number_format($ceremony->claimed_amount) }}</div>
<div class="text-xs text-gray-500 mt-1">已领取金额</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 text-xs uppercase text-gray-600">
<tr>
<th class="px-4 py-3 text-left">用户</th>
<th class="px-4 py-3 text-center">分配金额</th>
<th class="px-4 py-3 text-center">状态</th>
<th class="px-4 py-3 text-left">领取时间</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse ($claims as $claim)
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 font-bold">{{ $claim->user?->username }}</td>
<td class="px-4 py-3 text-center font-bold text-amber-600">
{{ number_format($claim->amount) }}
</td>
<td class="px-4 py-3 text-center">
@if ($claim->claimed)
<span class="px-2 py-0.5 rounded-full text-xs bg-green-100 text-green-700 font-bold">
已领取</span>
@else
<span class="px-2 py-0.5 rounded-full text-xs bg-gray-100 text-gray-500">未领取</span>
@endif
</td>
<td class="px-4 py-3 text-xs text-gray-500">
{{ $claim->claimed_at?->format('H:i:s') ?? '—' }}
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-4 py-12 text-center text-gray-400">暂无领取记录</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if ($claims->hasPages())
<div class="px-4 py-4 border-t">{{ $claims->links() }}</div>
@endif
</div>
@endsection

View File

@@ -0,0 +1,75 @@
{{--
文件功能:婚姻参数配置页(所有参数按分组展示,可批量保存)
@author ChatRoom Laravel
@version 1.0.0
--}}
@extends('admin.layouts.app')
@section('title', '婚姻参数配置')
@section('content')
<div class="flex items-center justify-between mb-5">
<div>
<h2 class="text-xl font-bold text-gray-800">⚙️ 婚姻参数配置</h2>
<p class="text-sm text-gray-500 mt-1">修改参数后点击「保存」生效,参数变更会自动清除缓存</p>
</div>
<a href="{{ route('admin.marriages.index') }}" class="text-sm text-indigo-600 hover:underline"> 返回总览</a>
</div>
@if (session('success'))
<div class="bg-green-50 border border-green-200 text-green-700 rounded-lg px-4 py-3 mb-4 text-sm">
{{ session('success') }}
</div>
@endif
<form action="{{ route('admin.marriages.configs.update') }}" method="POST">
@csrf
@foreach ($groups as $groupName => $configs)
<div class="bg-white rounded-xl shadow-sm border overflow-hidden mb-5">
<div class="px-5 py-3 bg-gray-50 border-b flex items-center gap-2">
<span class="font-bold text-gray-700">{{ $groupName }}</span>
<span class="text-xs text-gray-400">{{ $configs->count() }} 项)</span>
</div>
<div class="divide-y">
@foreach ($configs as $cfg)
<div class="px-5 py-4 flex items-center gap-4">
<div class="flex-1 min-w-0">
<div class="font-bold text-gray-800 text-sm">{{ $cfg->label }}</div>
@if ($cfg->description)
<div class="text-xs text-gray-400 mt-0.5">{{ $cfg->description }}</div>
@endif
<div class="text-xs font-mono text-gray-300 mt-0.5">{{ $cfg->key }}</div>
</div>
<div class="flex items-center gap-2 shrink-0">
@if ($cfg->min !== null)
<span class="text-xs text-gray-400">最小: {{ $cfg->min }}</span>
@endif
<input type="number" name="configs[{{ $cfg->key }}]" value="{{ $cfg->value }}"
@if ($cfg->min !== null) min="{{ $cfg->min }}" @endif
@if ($cfg->max !== null) max="{{ $cfg->max }}" @endif required
class="border rounded-lg px-3 py-1.5 text-sm w-28 text-center font-bold focus:outline-none focus:ring-2 focus:ring-indigo-300">
@if ($cfg->max !== null)
<span class="text-xs text-gray-400">最大: {{ $cfg->max }}</span>
@endif
</div>
</div>
@endforeach
</div>
</div>
@endforeach
<div class="flex justify-end gap-3 mt-2 pb-6">
<a href="{{ route('admin.marriages.index') }}"
class="px-5 py-2.5 border rounded-lg text-sm text-gray-600 hover:bg-gray-50">取消</a>
<button type="submit"
class="px-6 py-2.5 bg-indigo-600 text-white rounded-lg font-bold text-sm hover:bg-indigo-700 shadow-sm transition">
💾 保存所有配置
</button>
</div>
</form>
@endsection

View File

@@ -0,0 +1,145 @@
{{--
文件功能:后台婚姻管理总览页
统计卡片 + 最近结婚记录 + 最近离婚记录
@author ChatRoom Laravel
@version 1.0.0
--}}
@extends('admin.layouts.app')
@section('title', '婚姻管理 - 总览')
@section('content')
{{-- 页面标题 --}}
<div class="flex items-center justify-between mb-6">
<div>
<h2 class="text-xl font-bold text-gray-800">💒 婚姻管理</h2>
<p class="text-sm text-gray-500 mt-1">婚姻系统总览,管理求婚、结婚、婚礼及参数配置</p>
</div>
<div class="flex gap-2">
<a href="{{ route('admin.marriages.configs') }}"
class="bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-bold hover:bg-indigo-700 transition">
⚙️ 参数配置
</a>
<a href="{{ route('admin.marriages.tiers') }}"
class="bg-pink-600 text-white px-4 py-2 rounded-lg text-sm font-bold hover:bg-pink-700 transition">
🎊 婚礼档位
</a>
</div>
</div>
{{-- 统计卡片 --}}
<div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-6 gap-4 mb-8">
<div class="bg-white rounded-xl shadow-sm border p-4 text-center">
<div class="text-2xl mb-1">💑</div>
<div class="text-2xl font-bold text-indigo-600">{{ $stats['total_married'] }}</div>
<div class="text-xs text-gray-500 mt-1">当前已婚</div>
</div>
<div class="bg-white rounded-xl shadow-sm border p-4 text-center">
<div class="text-2xl mb-1">💍</div>
<div class="text-2xl font-bold text-amber-500">{{ $stats['total_pending'] }}</div>
<div class="text-xs text-gray-500 mt-1">求婚中</div>
</div>
<div class="bg-white rounded-xl shadow-sm border p-4 text-center">
<div class="text-2xl mb-1">💔</div>
<div class="text-2xl font-bold text-red-500">{{ $stats['total_divorced'] }}</div>
<div class="text-xs text-gray-500 mt-1">已离婚</div>
</div>
<div class="bg-white rounded-xl shadow-sm border p-4 text-center">
<div class="text-2xl mb-1">🎊</div>
<div class="text-2xl font-bold text-pink-500">{{ $stats['total_weddings'] }}</div>
<div class="text-xs text-gray-500 mt-1">婚礼场次</div>
</div>
<div class="bg-white rounded-xl shadow-sm border p-4 text-center">
<div class="text-2xl mb-1">🎁</div>
<div class="text-2xl font-bold text-green-600">{{ number_format($stats['total_envelopes']) }}</div>
<div class="text-xs text-gray-500 mt-1">红包总额()</div>
</div>
<div class="bg-white rounded-xl shadow-sm border p-4 text-center">
<div class="text-2xl mb-1"></div>
<div class="text-2xl font-bold text-emerald-600">{{ number_format($stats['claimed_amount']) }}</div>
<div class="text-xs text-gray-500 mt-1">已领取()</div>
</div>
</div>
{{-- 快捷入口 --}}
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-8">
@foreach ([['route' => 'admin.marriages.list', 'icon' => '📋', 'label' => '婚姻列表', 'color' => 'blue'], ['route' => 'admin.marriages.proposals', 'icon' => '💌', 'label' => '求婚记录', 'color' => 'amber'], ['route' => 'admin.marriages.ceremonies', 'icon' => '🎊', 'label' => '婚礼红包', 'color' => 'pink'], ['route' => 'admin.marriages.intimacy-logs', 'icon' => '💞', 'label' => '亲密度日志', 'color' => 'purple']] as $item)
<a href="{{ route($item['route']) }}"
class="bg-white rounded-xl border shadow-sm p-5 flex flex-col items-center gap-2 hover:shadow-md hover:border-{{ $item['color'] }}-300 transition group">
<span class="text-3xl">{{ $item['icon'] }}</span>
<span
class="text-sm font-bold text-gray-700 group-hover:text-{{ $item['color'] }}-600">{{ $item['label'] }}</span>
</a>
@endforeach
</div>
{{-- 最近已婚 --}}
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
<div class="bg-white rounded-xl shadow-sm border overflow-hidden">
<div class="px-5 py-4 flex justify-between items-center border-b bg-gray-50">
<h3 class="font-bold text-gray-700">💑 最近结婚</h3>
<a href="{{ route('admin.marriages.list') }}" class="text-xs text-indigo-600 hover:underline">查看全部</a>
</div>
<div class="divide-y">
@forelse ($recentMarriages as $m)
<div class="px-5 py-3 flex items-center justify-between text-sm">
<div class="flex items-center gap-2">
<span class="text-lg">{{ $m->ringItem?->icon ?? '💍' }}</span>
<div>
<span class="font-bold">{{ $m->user?->username }}</span>
<span class="text-gray-400 mx-1">×</span>
<span class="font-bold">{{ $m->partner?->username }}</span>
</div>
</div>
<div class="text-right">
<div class="text-gray-500">{{ $m->married_at?->format('m/d') }}</div>
<div class="text-xs text-indigo-500">
{{ \App\Services\MarriageIntimacyService::levelName($m->level) }}</div>
</div>
</div>
@empty
<div class="px-5 py-8 text-center text-gray-400">暂无已婚记录</div>
@endforelse
</div>
</div>
{{-- 最近离婚 --}}
<div class="bg-white rounded-xl shadow-sm border overflow-hidden">
<div class="px-5 py-4 flex justify-between items-center border-b bg-gray-50">
<h3 class="font-bold text-gray-700">💔 最近离婚</h3>
<a href="{{ route('admin.marriages.list', ['status' => 'divorced']) }}"
class="text-xs text-red-600 hover:underline">查看全部</a>
</div>
<div class="divide-y">
@forelse ($recentDivorces as $m)
<div class="px-5 py-3 flex items-center justify-between text-sm">
<div>
<span class="font-bold">{{ $m->user?->username }}</span>
<span class="text-gray-400 mx-1">×</span>
<span class="font-bold">{{ $m->partner?->username }}</span>
</div>
<div class="text-right">
<span
class="text-xs px-2 py-0.5 rounded-full font-bold
{{ match ($m->divorce_type) {
'forced' => 'bg-red-100 text-red-600',
'auto' => 'bg-orange-100 text-orange-600',
'admin' => 'bg-purple-100 text-purple-600',
default => 'bg-gray-100 text-gray-600',
} }}">
{{ match ($m->divorce_type) {'forced' => '强制','auto' => '超时','admin' => '管理员',default => '协议'} }}
</span>
<div class="text-xs text-gray-400 mt-0.5">{{ $m->divorced_at?->format('m/d') }}</div>
</div>
</div>
@empty
<div class="px-5 py-8 text-center text-gray-400">暂无离婚记录</div>
@endforelse
</div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,97 @@
{{--
文件功能:亲密度变更日志审计页
@author ChatRoom Laravel
@version 1.0.0
--}}
@extends('admin.layouts.app')
@section('title', '亲密度日志')
@section('content')
<div class="flex items-center justify-between mb-5">
<h2 class="text-xl font-bold text-gray-800">💞 亲密度变更日志</h2>
<a href="{{ route('admin.marriages.index') }}" class="text-sm text-indigo-600 hover:underline"> 返回总览</a>
</div>
{{-- 筛选 --}}
<form method="GET" class="bg-white rounded-xl border shadow-sm p-4 mb-5 flex flex-wrap gap-3 items-end">
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">搜索用户名</label>
<input type="text" name="search" value="{{ request('search') }}" placeholder="双方任一用户名..."
class="border rounded-lg px-3 py-2 text-sm w-44 focus:outline-none">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">来源Source</label>
<select name="source" class="border rounded-lg px-3 py-2 text-sm focus:outline-none">
<option value="">全部</option>
@foreach (\App\Enums\IntimacySource::cases() as $src)
<option value="{{ $src->value }}" @selected(request('source') === $src->value)>
{{ $src->label() }}
</option>
@endforeach
</select>
</div>
<button type="submit"
class="bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-bold hover:bg-indigo-700 transition">
筛选
</button>
<a href="{{ route('admin.marriages.intimacy-logs') }}" class="text-sm text-gray-500 hover:underline py-2">重置</a>
</form>
<div class="bg-white rounded-xl shadow-sm border overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 text-xs uppercase text-gray-600">
<tr>
<th class="px-4 py-3 text-left">ID</th>
<th class="px-4 py-3 text-left">婚姻双方</th>
<th class="px-4 py-3 text-center">变动量</th>
<th class="px-4 py-3 text-center">变后余额</th>
<th class="px-4 py-3 text-left">来源</th>
<th class="px-4 py-3 text-left">备注</th>
<th class="px-4 py-3 text-left">时间</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse ($logs as $log)
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 text-gray-400 font-mono text-xs">{{ $log->id }}</td>
<td class="px-4 py-3">
<span class="font-bold">{{ $log->marriage?->user?->username }}</span>
<span class="text-gray-400 mx-1">×</span>
<span class="font-bold">{{ $log->marriage?->partner?->username }}</span>
</td>
<td
class="px-4 py-3 text-center font-bold {{ $log->amount >= 0 ? 'text-green-600' : 'text-red-500' }}">
{{ $log->amount >= 0 ? '+' : '' }}{{ $log->amount }}
</td>
<td class="px-4 py-3 text-center text-indigo-600 font-bold">
{{ number_format($log->balance_after) }}
</td>
<td class="px-4 py-3">
<span class="text-xs bg-purple-50 text-purple-700 px-2 py-0.5 rounded-full font-mono">
{{ \App\Enums\IntimacySource::tryFrom($log->source)?->label() ?? $log->source }}
</span>
</td>
<td class="px-4 py-3 text-xs text-gray-500">{{ $log->remark ?? '—' }}</td>
<td class="px-4 py-3 text-xs text-gray-400">
{{ $log->created_at?->format('m-d H:i') }}
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-12 text-center text-gray-400">暂无记录</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if ($logs->hasPages())
<div class="px-4 py-4 border-t">{{ $logs->links() }}</div>
@endif
</div>
@endsection

View File

@@ -0,0 +1,162 @@
{{--
文件功能:后台婚姻列表页(支持状态筛选/用户名搜索)
@author ChatRoom Laravel
@version 1.0.0
--}}
@extends('admin.layouts.app')
@section('title', '婚姻列表')
@section('content')
<div class="flex items-center justify-between mb-5">
<h2 class="text-xl font-bold text-gray-800">📋 婚姻列表</h2>
<a href="{{ route('admin.marriages.index') }}" class="text-sm text-indigo-600 hover:underline"> 返回总览</a>
</div>
{{-- 筛选栏 --}}
<form method="GET" class="bg-white rounded-xl border shadow-sm p-4 mb-5 flex flex-wrap gap-3 items-end">
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">状态</label>
<select name="status" class="border rounded-lg px-3 py-2 text-sm focus:outline-none">
<option value="">全部</option>
@foreach (['married' => '已婚', 'pending' => '求婚中', 'divorced' => '已离婚', 'rejected' => '被拒', 'expired' => '已过期'] as $val => $label)
<option value="{{ $val }}" @selected(request('status') === $val)>{{ $label }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">搜索用户名</label>
<input type="text" name="search" value="{{ request('search') }}" placeholder="双方任一用户名..."
class="border rounded-lg px-3 py-2 text-sm w-48 focus:outline-none">
</div>
<button type="submit"
class="bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-bold hover:bg-indigo-700 transition">
筛选
</button>
<a href="{{ route('admin.marriages.list') }}" class="text-sm text-gray-500 hover:underline py-2">重置</a>
</form>
{{-- 婚姻列表 --}}
<div class="bg-white rounded-xl shadow-sm border overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 text-gray-600 text-xs uppercase">
<tr>
<th class="px-4 py-3 text-left">ID</th>
<th class="px-4 py-3 text-left">双方</th>
<th class="px-4 py-3 text-left">戒指</th>
<th class="px-4 py-3 text-center">状态</th>
<th class="px-4 py-3 text-center">亲密度/等级</th>
<th class="px-4 py-3 text-center">结婚天数</th>
<th class="px-4 py-3 text-left">时间</th>
<th class="px-4 py-3 text-center">操作</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse ($marriages as $m)
<tr class="hover:bg-gray-50 transition">
<td class="px-4 py-3 text-gray-400 font-mono text-xs">{{ $m->id }}</td>
<td class="px-4 py-3">
<div class="font-bold">{{ $m->user?->username }}</div>
<div class="text-gray-400 text-xs">× {{ $m->partner?->username }}</div>
</td>
<td class="px-4 py-3">
{{ $m->ringItem?->icon }} {{ $m->ringItem?->name ?? '—' }}
</td>
<td class="px-4 py-3 text-center">
<span
class="px-2 py-0.5 rounded-full text-xs font-bold
{{ match ($m->status) {
'married' => 'bg-green-100 text-green-700',
'pending' => 'bg-amber-100 text-amber-700',
'divorced' => 'bg-red-100 text-red-700',
'rejected' => 'bg-gray-100 text-gray-600',
'expired' => 'bg-slate-100 text-slate-500',
default => 'bg-gray-100 text-gray-600',
} }}">
{{ ['married' => '💑 已婚', 'pending' => '💍 求婚中', 'divorced' => '💔 已离婚', 'rejected' => '❌ 被拒', 'expired' => '⏰ 已过期'][$m->status] ?? $m->status }}
</span>
</td>
<td class="px-4 py-3 text-center">
<div class="font-bold text-indigo-600">{{ number_format($m->intimacy) }}</div>
<div class="text-xs text-gray-400">
{{ \App\Services\MarriageIntimacyService::levelIcon($m->level) }}
Lv{{ $m->level }}</div>
</td>
<td class="px-4 py-3 text-center text-gray-600">
{{ $m->married_at ? $m->married_at->diffInDays(now()) . ' 天' : '—' }}
</td>
<td class="px-4 py-3 text-xs text-gray-500">
@if ($m->married_at)
结婚:{{ $m->married_at->format('Y-m-d') }}
@elseif ($m->proposed_at)
求婚:{{ $m->proposed_at->format('Y-m-d') }}
@endif
@if ($m->divorced_at)
<div>离婚:{{ $m->divorced_at->format('Y-m-d') }}</div>
@endif
</td>
<td class="px-4 py-3 text-center" x-data="{ showDissolve: false }">
@if ($m->status === 'married')
<button @click="showDissolve = true"
class="text-xs bg-red-50 text-red-600 font-bold px-3 py-1.5 rounded hover:bg-red-600 hover:text-white transition">
强制离婚
</button>
{{-- 强制离婚确认弹窗 --}}
<div x-show="showDissolve" style="display:none"
class="fixed inset-0 z-50 bg-black/60 flex items-center justify-center p-4">
<div @click.away="showDissolve = false"
class="bg-white rounded-xl shadow-2xl w-full max-w-sm p-6 text-left">
<h4 class="font-bold text-red-600 mb-3">⚠️ 强制解除婚姻</h4>
<p class="text-sm text-gray-600 mb-4">
确认解除 <strong>{{ $m->user?->username }}</strong>
<strong>{{ $m->partner?->username }}</strong> 的婚姻?<br>
<span class="text-red-500 text-xs">管理员强制离婚不扣魅力、不转移财产。</span>
</p>
<form action="{{ route('admin.marriages.force-dissolve', $m->id) }}"
method="POST">
@csrf
<input type="text" name="admin_note" required placeholder="请填写操作原因(必填)"
class="w-full border rounded px-3 py-2 text-sm mb-3">
<div class="flex gap-2 justify-end">
<button type="button" @click="showDissolve = false"
class="px-3 py-1.5 border rounded text-sm text-gray-600">取消</button>
<button type="submit"
class="px-3 py-1.5 bg-red-600 text-white rounded text-sm font-bold">确认解除</button>
</div>
</form>
</div>
</div>
@elseif ($m->status === 'pending')
<form action="{{ route('admin.marriages.cancel-proposal', $m->id) }}" method="POST"
onsubmit="return confirm('确定取消该求婚吗?')">
@csrf
<button type="submit"
class="text-xs bg-amber-50 text-amber-600 font-bold px-3 py-1.5 rounded hover:bg-amber-600 hover:text-white transition">
取消求婚
</button>
</form>
@else
<span class="text-xs text-gray-300"></span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="8" class="px-4 py-12 text-center text-gray-400">暂无记录</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- 分页 --}}
@if ($marriages->hasPages())
<div class="px-4 py-4 border-t">{{ $marriages->links() }}</div>
@endif
</div>
@endsection

View File

@@ -0,0 +1,89 @@
{{--
文件功能:求婚记录页(含取消操作)
@author ChatRoom Laravel
@version 1.0.0
--}}
@extends('admin.layouts.app')
@section('title', '求婚记录')
@section('content')
<div class="flex items-center justify-between mb-5">
<h2 class="text-xl font-bold text-gray-800">💌 求婚记录</h2>
<a href="{{ route('admin.marriages.index') }}" class="text-sm text-indigo-600 hover:underline"> 返回总览</a>
</div>
<div class="bg-white rounded-xl shadow-sm border overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 text-xs uppercase text-gray-600">
<tr>
<th class="px-4 py-3 text-left">ID</th>
<th class="px-4 py-3 text-left">发起方</th>
<th class="px-4 py-3 text-left">目标</th>
<th class="px-4 py-3 text-left">戒指</th>
<th class="px-4 py-3 text-center">状态</th>
<th class="px-4 py-3 text-left">求婚时间</th>
<th class="px-4 py-3 text-left">过期时间</th>
<th class="px-4 py-3 text-center">操作</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse ($proposals as $p)
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 text-gray-400 font-mono text-xs">{{ $p->id }}</td>
<td class="px-4 py-3 font-bold">{{ $p->user?->username }}</td>
<td class="px-4 py-3 font-bold">{{ $p->partner?->username }}</td>
<td class="px-4 py-3">{{ $p->ringItem?->icon }} {{ $p->ringItem?->name ?? '—' }}</td>
<td class="px-4 py-3 text-center">
<span
class="px-2 py-0.5 rounded-full text-xs font-bold
{{ match ($p->status) {
'pending' => 'bg-amber-100 text-amber-700',
'rejected' => 'bg-red-100 text-red-600',
'expired' => 'bg-gray-100 text-gray-500',
default => 'bg-gray-100 text-gray-600',
} }}">
{{ ['pending' => '⏳ 等待中', 'rejected' => '❌ 已拒绝', 'expired' => '⏰ 已过期'][$p->status] ?? $p->status }}
</span>
</td>
<td class="px-4 py-3 text-xs text-gray-500">{{ $p->proposed_at?->format('Y-m-d H:i') }}</td>
<td
class="px-4 py-3 text-xs {{ $p->expires_at?->isPast() ? 'text-red-400' : 'text-gray-500' }}">
{{ $p->expires_at?->format('Y-m-d H:i') }}
@if ($p->status === 'pending' && $p->expires_at?->isPast())
<span class="text-red-500">(已超时)</span>
@endif
</td>
<td class="px-4 py-3 text-center">
@if ($p->status === 'pending')
<form action="{{ route('admin.marriages.cancel-proposal', $p->id) }}" method="POST"
onsubmit="return confirm('确定取消该求婚吗?')">
@csrf
<button type="submit"
class="text-xs bg-amber-50 text-amber-600 font-bold px-3 py-1.5 rounded hover:bg-amber-600 hover:text-white transition">
取消求婚
</button>
</form>
@else
<span class="text-gray-300 text-xs"></span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="8" class="px-4 py-12 text-center text-gray-400">暂无求婚记录</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if ($proposals->hasPages())
<div class="px-4 py-4 border-t">{{ $proposals->links() }}</div>
@endif
</div>
@endsection

View File

@@ -0,0 +1,107 @@
{{--
文件功能婚礼档位配置页5档固定可修改名称/图标/金额/描述/启用状态)
@author ChatRoom Laravel
@version 1.0.0
--}}
@extends('admin.layouts.app')
@section('title', '婚礼档位配置')
@section('content')
<div class="flex items-center justify-between mb-5">
<div>
<h2 class="text-xl font-bold text-gray-800">🎊 婚礼档位配置</h2>
<p class="text-sm text-gray-500 mt-1">5个固定档位,可修改名称、金额及启用状态(不可增删)</p>
</div>
<a href="{{ route('admin.marriages.index') }}" class="text-sm text-indigo-600 hover:underline"> 返回总览</a>
</div>
@if (session('success'))
<div class="bg-green-50 border border-green-200 text-green-700 rounded-lg px-4 py-3 mb-5 text-sm">
{{ session('success') }}
</div>
@endif
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5">
@foreach ($tiers as $tier)
<div class="bg-white rounded-xl shadow-sm border overflow-hidden {{ $tier->is_active ? '' : 'opacity-60' }}"
x-data="{ editing: false }">
{{-- 档位头部 --}}
<div class="px-5 py-4 border-b flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="text-2xl">{{ $tier->icon }}</span>
<div>
<div class="font-bold text-gray-800">{{ $tier->name }}</div>
<div class="text-xs text-gray-400"> {{ $tier->tier }} </div>
</div>
</div>
<div class="flex items-center gap-2">
@if (!$tier->is_active)
<span class="text-xs bg-gray-100 text-gray-500 px-2 py-0.5 rounded-full">已禁用</span>
@endif
<button @click="editing = !editing"
class="text-xs bg-indigo-50 text-indigo-600 font-bold px-3 py-1.5 rounded hover:bg-indigo-600 hover:text-white transition"
x-text="editing ? '收起' : '编辑'">
</button>
</div>
</div>
{{-- 当前值展示 --}}
<div class="px-5 py-4 text-sm" x-show="!editing">
<div class="flex justify-between mb-2">
<span class="text-gray-500">金币总额</span>
<span class="font-bold text-amber-600">{{ number_format($tier->amount) }} </span>
</div>
@if ($tier->description)
<p class="text-xs text-gray-400">{{ $tier->description }}</p>
@endif
</div>
{{-- 编辑表单 --}}
<div x-show="editing" style="display:none" class="px-5 py-4">
<form action="{{ route('admin.marriages.tiers.update', $tier->id) }}" method="POST" class="space-y-3">
@csrf @method('PUT')
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">档位名称</label>
<input type="text" name="name" value="{{ $tier->name }}" required maxlength="30"
class="w-full border rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-300">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">图标 (Emoji)</label>
<input type="text" name="icon" value="{{ $tier->icon }}" required maxlength="20"
class="w-full border rounded-lg px-3 py-1.5 text-sm focus:outline-none">
</div>
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">金币总额</label>
<input type="number" name="amount" value="{{ $tier->amount }}" required min="1"
class="w-full border rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-300">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">描述</label>
<input type="text" name="description" value="{{ $tier->description }}" maxlength="100"
class="w-full border rounded-lg px-3 py-1.5 text-sm focus:outline-none">
</div>
<div class="flex items-center gap-2">
<input type="hidden" name="is_active" value="0">
<input type="checkbox" name="is_active" value="1" id="active_{{ $tier->id }}"
@checked($tier->is_active) class="rounded">
<label for="active_{{ $tier->id }}" class="text-sm text-gray-700">启用此档位</label>
</div>
<div class="flex justify-end gap-2 pt-1">
<button type="button" @click="editing = false"
class="px-3 py-1.5 border rounded text-sm text-gray-600">取消</button>
<button type="submit"
class="px-4 py-1.5 bg-indigo-600 text-white rounded text-sm font-bold hover:bg-indigo-700 transition">保存</button>
</div>
</form>
</div>
</div>
@endforeach
</div>
@endsection