feat: 实现挂机修仙、排行榜、大厅重构与全站留言板系统
- (Phase 8) 后台各维度管理与配置 - (Phase 9) 全自动静默挂机修仙升级 - (Phase 9) 四大维度风云排行榜页面 - (Phase 10) 全站留言板与悄悄话私信功能 - 运行 Pint 代码格式化
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '仪表盘')
|
||||
|
||||
@section('content')
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<h3 class="text-gray-500 text-sm font-medium mb-1">总计注册用户数</h3>
|
||||
<p class="text-3xl font-bold text-gray-800">{{ $stats['total_users'] }}</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<h3 class="text-gray-500 text-sm font-medium mb-1">总计聊天频道数</h3>
|
||||
<p class="text-3xl font-bold text-gray-800">{{ $stats['total_rooms'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="p-6 border-b border-gray-100 flex justify-between items-center">
|
||||
<h2 class="text-lg font-bold text-gray-800">系统信息摘要</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-center font-mono text-sm">
|
||||
<span class="w-32 text-gray-500 inline-block font-sans">Laravel 版本:</span>
|
||||
<span class="text-indigo-600">{{ app()->version() }}</span>
|
||||
</li>
|
||||
<li class="flex items-center font-mono text-sm">
|
||||
<span class="w-32 text-gray-500 inline-block font-sans">PHP 版本:</span>
|
||||
<span class="text-indigo-600">{{ PHP_VERSION }}</span>
|
||||
</li>
|
||||
<li class="flex items-center text-sm font-mono mt-4 pt-4 border-t">
|
||||
<span class="mr-4 text-gray-500 inline-block font-sans items-center flex">队列监控面板</span>
|
||||
<!-- Laravel Horizon 的默认路由前缀由开发者确认或自己改。这里默认是 /horizon -->
|
||||
<a href="{{ url('/horizon') }}" target="_blank"
|
||||
class="text-blue-600 hover:text-blue-800 hover:underline flex items-center">
|
||||
<span>打开 Horizon 控制台</span>
|
||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>后台管理 - 飘落流星</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-100 flex h-screen text-gray-800">
|
||||
<!-- 左侧侧边栏 -->
|
||||
<aside class="w-64 bg-slate-900 text-white flex flex-col">
|
||||
<div class="p-6 text-center border-b border-white/10">
|
||||
<h2 class="text-2xl font-extrabold tracking-widest uppercase">Admin</h2>
|
||||
<p class="text-xs text-slate-400 mt-2">飘落流星 控制台</p>
|
||||
</div>
|
||||
<nav class="flex-1 px-4 py-6 space-y-2 overflow-y-auto">
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.dashboard') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
|
||||
📊 仪表盘
|
||||
</a>
|
||||
<a href="{{ route('admin.system.edit') }}"
|
||||
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.system.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
|
||||
⚙️ 系统参数参数
|
||||
</a>
|
||||
<a href="{{ route('admin.users.index') }}"
|
||||
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.users.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
|
||||
👥 用户管理
|
||||
</a>
|
||||
<a href="{{ route('admin.sql.index') }}"
|
||||
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.sql.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
|
||||
💾 SQL 探针
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-white/10">
|
||||
<a href="{{ route('rooms.index') }}"
|
||||
class="block w-full text-center px-4 py-2 bg-slate-800 hover:bg-slate-700 rounded transition text-sm">
|
||||
返回前台大厅
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 右侧主体内容 -->
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative">
|
||||
<!-- 顶栏 -->
|
||||
<header class="bg-white shadow relative z-20 flex items-center justify-between px-6 py-4">
|
||||
<h1 class="text-xl font-bold text-gray-700">@yield('title', '控制台')</h1>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="text-sm font-medium">当前操作人: <span
|
||||
class="text-indigo-600">{{ Auth::user()->username }}</span></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 内容滚动区 -->
|
||||
<div class="flex-1 overflow-y-auto p-6 relative z-10">
|
||||
@if (session('success'))
|
||||
<div class="mb-6 bg-emerald-100 border-l-4 border-emerald-500 text-emerald-700 p-4 rounded shadow-sm">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
@if (session('error'))
|
||||
<div class="mb-6 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded shadow-sm">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
@if ($errors->any())
|
||||
<div class="mb-6 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded shadow-sm">
|
||||
<ul class="list-disc list-inside text-sm">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@yield('content')
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,98 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', 'SQL 战术沙盒探针')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="mb-6 bg-red-50 border-l-4 border-red-500 p-4 rounded text-red-700 shadow-sm text-sm">
|
||||
<p class="font-bold flex items-center">
|
||||
<span class="mr-2">⚠️</span> 顶级安全警告
|
||||
</p>
|
||||
<p class="mt-1 ml-6">
|
||||
此操作直接连通底层 MySQL 数据库。为杜绝《删库跑路》等生产事故,本控制台已硬编码拦截过滤:只会放行以 <code>SELECT</code>, <code>SHOW</code>,
|
||||
<code>EXPLAIN</code> 等起手的<strong>纯只读语句</strong>。所有的增删改一律阻断。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mb-6">
|
||||
<div class="p-6">
|
||||
<form action="{{ route('admin.sql.execute') }}" method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-bold text-gray-700 mb-2">输入原始只读 SQL 语句</label>
|
||||
<textarea name="query" rows="5" required placeholder="SELECT * FROM users ORDER BY id DESC LIMIT 10;"
|
||||
class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 p-4 bg-gray-50 border font-mono resize-y">{{ old('query', $query ?? '') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-2">
|
||||
<button type="submit"
|
||||
class="px-6 py-2 bg-slate-800 text-white rounded-md font-bold hover:bg-slate-900 shadow-sm transition flex items-center">
|
||||
<span>🔥 探 针 发 射</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 结果展示区 --}}
|
||||
@isset($error)
|
||||
<div
|
||||
class="bg-red-50 border border-red-200 text-red-700 p-6 rounded-xl shadow-sm mb-6 overflow-x-auto font-mono text-sm whitespace-pre-wrap">
|
||||
{{ $error }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@isset($results)
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="bg-gray-50 px-6 py-3 border-b flex justify-between items-center text-sm font-bold text-gray-700">
|
||||
<span>查询结果 (共 {{ count($results) }} 条)</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto p-4 max-h-[600px] custom-scrollbar overflow-y-auto">
|
||||
@if (empty($results))
|
||||
<div class="text-center text-gray-400 py-10 font-bold">SQL 执行成功,但返回了空结果集 (0 rows)</div>
|
||||
@else
|
||||
<table class="w-full text-left border-collapse text-sm">
|
||||
<thead>
|
||||
<tr class="border-b-2 border-indigo-500">
|
||||
@foreach ($columns as $col)
|
||||
<th
|
||||
class="p-3 font-bold text-gray-600 whitespace-nowrap bg-indigo-50/50 sticky top-0 z-10 shadow-sm">
|
||||
{{ $col }}</th>
|
||||
@endforeach
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 font-mono">
|
||||
@foreach ($results as $row)
|
||||
<tr class="hover:bg-amber-50 transition">
|
||||
@foreach ($columns as $col)
|
||||
<td class="p-3 whitespace-nowrap text-gray-700">{{ $row->$col ?? 'NULL' }}</td>
|
||||
@endforeach
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endisset
|
||||
|
||||
<style>
|
||||
/* 针对该表格页加深一点滚动条以便查看超长字段 */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: #94a3b8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
</style>
|
||||
|
||||
@endsection
|
||||
@@ -0,0 +1,44 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '系统参数配置')
|
||||
|
||||
@section('content')
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="p-6 border-b border-gray-100 flex justify-between items-center bg-gray-50">
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-gray-800">修改系统运行参数</h2>
|
||||
<p class="text-xs text-gray-500 mt-1">保存后会同步更新 Redis 缓存,前台实时生效。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<form action="{{ route('admin.system.update') }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="space-y-6 max-w-2xl">
|
||||
@foreach ($params as $alias => $body)
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-2">
|
||||
{{ $descriptions[$alias] ?? $alias }}
|
||||
<span class="text-gray-400 font-normal ml-2">[{{ $alias }}]</span>
|
||||
</label>
|
||||
@if (strlen($body) > 50 || str_contains($body, "\n") || str_contains($body, '<'))
|
||||
<textarea name="{{ $alias }}" rows="4"
|
||||
class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 p-2.5 bg-gray-50 border whitespace-pre-wrap">{{ $body }}</textarea>
|
||||
@else
|
||||
<input type="text" name="{{ $alias }}" value="{{ $body }}"
|
||||
class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 p-2.5 bg-gray-50 border">
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t flex space-x-3">
|
||||
<button type="submit"
|
||||
class="px-6 py-2 bg-indigo-600 text-white rounded-md font-bold hover:bg-indigo-700 shadow-sm transition">保存并发布</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,138 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '用户检索与管理')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mb-6" x-data="{ showEditModal: false, editingUser: {} }">
|
||||
<div class="p-6 border-b border-gray-100 bg-gray-50 flex items-center justify-between">
|
||||
<form action="{{ route('admin.users.index') }}" method="GET" class="flex gap-2">
|
||||
<input type="text" name="username" value="{{ request('username') }}" placeholder="搜索用户名..."
|
||||
class="px-3 py-1.5 border border-gray-300 rounded shadow-sm focus:ring-indigo-500 focus:border-indigo-500 text-sm">
|
||||
<button type="submit"
|
||||
class="bg-indigo-600 text-white px-4 py-1.5 rounded hover:bg-indigo-700 font-bold shadow-sm transition">搜索</button>
|
||||
<a href="{{ route('admin.users.index') }}"
|
||||
class="px-4 py-1.5 bg-white border border-gray-300 rounded text-gray-700 hover:bg-gray-50">重置</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 用户表格 -->
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr
|
||||
class="bg-gray-50 border-b border-gray-100 text-xs text-gray-500 uppercase font-bold tracking-wider">
|
||||
<th class="p-4">ID</th>
|
||||
<th class="p-4">注册名</th>
|
||||
<th class="p-4">性别</th>
|
||||
<th class="p-4">等级</th>
|
||||
<th class="p-4">个性签名</th>
|
||||
<th class="p-4">注册时间</th>
|
||||
<th class="p-4 text-right">管理操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
@foreach ($users as $user)
|
||||
<tr class="hover:bg-gray-50 transition">
|
||||
<td class="p-4 font-mono text-xs text-gray-500">{{ $user->id }}</td>
|
||||
<td class="p-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<img src="/images/headface/{{ $user->headface ?? '01.gif' }}"
|
||||
class="w-8 h-8 rounded border object-cover">
|
||||
<span class="font-bold text-gray-800">{{ $user->username }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-4 text-sm">{{ $user->sex }}</td>
|
||||
<td class="p-4">
|
||||
<span
|
||||
class="px-2 py-0.5 rounded-full text-xs {{ $user->user_level >= 15 ? 'bg-red-100 text-red-700 font-bold' : 'bg-gray-100 text-gray-600' }}">
|
||||
LV.{{ $user->user_level }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="p-4 text-sm text-gray-500 truncate max-w-[200px]" title="{{ $user->sign }}">
|
||||
{{ $user->sign ?: '-' }}</td>
|
||||
<td class="p-4 text-sm font-mono text-gray-500">{{ $user->created_at->format('Y/m/d H:i') }}
|
||||
</td>
|
||||
<td class="p-4 text-right space-x-2 relative" x-data>
|
||||
<button
|
||||
@click="editingUser = { id: {{ $user->id }}, username: '{{ addslashes($user->username) }}', user_level: {{ $user->user_level }}, sex: '{{ $user->sex }}', requestUrl: '{{ route('admin.users.update', $user->id) }}' }; showEditModal = true"
|
||||
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 cursor-pointer">
|
||||
详细 / 修改
|
||||
</button>
|
||||
|
||||
<form action="{{ route('admin.users.destroy', $user->id) }}" method="POST" class="inline"
|
||||
onsubmit="return confirm('危险:确定彻底物理清除用户 [{{ $user->username }}] 吗?数据不可恢复!')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit"
|
||||
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 cursor-pointer">
|
||||
强杀
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页链接 -->
|
||||
@if ($users->hasPages())
|
||||
<div class="p-4 border-t border-gray-100">
|
||||
{{ $users->links() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 弹出的修改框 -->
|
||||
<div x-show="showEditModal" style="display: none;"
|
||||
class="fixed inset-0 z-50 bg-black/60 flex items-center justify-center p-4">
|
||||
<div @click.away="showEditModal = false"
|
||||
class="bg-white rounded-xl shadow-2xl w-full max-w-md transform transition-all" x-transition>
|
||||
<div
|
||||
class="bg-indigo-900 border-b border-indigo-800 px-6 py-4 flex justify-between items-center rounded-t-xl text-white">
|
||||
<h3 class="font-bold text-lg">全量修改:<span x-text="editingUser.username" class="text-indigo-300"></span>
|
||||
</h3>
|
||||
<button @click="showEditModal = false" class="text-gray-400 hover:text-white">×</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<!-- 依靠 Alpine 绑定的 AJAX 或者 Form 提交 -->
|
||||
<form :action="editingUser.requestUrl" method="POST" id="adminUserUpdateForm">
|
||||
@csrf @method('PUT')
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-bold text-gray-700 mb-2">安全等级 (0-99)</label>
|
||||
<input type="number" name="user_level" x-model="editingUser.user_level" required
|
||||
class="w-full border-gray-300 rounded shadow-sm focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-bold text-gray-700 mb-2">性别</label>
|
||||
<select name="sex" x-model="editingUser.sex"
|
||||
class="w-full border-gray-300 rounded shadow-sm focus:ring-indigo-500">
|
||||
<option value="男">男</option>
|
||||
<option value="女">女</option>
|
||||
<option value="保密">保密</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label
|
||||
class="block text-sm font-bold pl-1 text-red-600 border-l-4 border-red-500 bg-red-50 p-2 mb-2">强制重算密码
|
||||
(留空则不修改)</label>
|
||||
<input type="text" name="password" placeholder="强行输入新密码覆盖"
|
||||
class="w-full border-red-300 rounded shadow-sm focus:border-red-500 focus:ring-red-500 placeholder-red-300">
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 pt-4 border-t border-gray-100">
|
||||
<button type="button" @click="showEditModal = false"
|
||||
class="px-4 py-2 border rounded font-medium text-gray-600 hover:bg-gray-50">取消</button>
|
||||
<button type="submit"
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded font-bold hover:bg-indigo-700 shadow-sm">提交强制改写</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
Reference in New Issue
Block a user