功能:字体颜色持久化、等级体系升级至99级、钓鱼小游戏、补充系统参数

- 字体颜色:s_color 改为 varchar,发消息时保存颜色,进入聊天室自动恢复
- 等级体系:maxlevel 15→99,superlevel 16→100,99级经验阶梯(幂次曲线)
- 管理权限等级按比例调整:禁言50、踢人60、设公告60、封号80、封IP90
- 钓鱼小游戏:FishingController(抛竿扣金币+收竿随机结果+广播)
- 补充6个缺失的 sysparam 参数 + 4个钓鱼参数
- 用户列表点击用户名后自动聚焦输入框
- Pint 格式化
This commit is contained in:
2026-02-26 21:10:34 +08:00
parent d884853968
commit ea06328885
652 changed files with 5013 additions and 1274 deletions
+137 -131
View File
@@ -1,13 +1,18 @@
<!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>
<!-- 引入 Alpine.js 用于简单的无代码弹窗切换 -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
@extends layouts.app
--}}
@extends('layouts.app')
@section('title', '聊天大厅 - 飘落流星')
@section('nav-icon', '🌟')
@section('nav-title', '星光大厅')
@section('head')
<style>
.custom-scrollbar::-webkit-scrollbar {
width: 8px;
@@ -22,102 +27,65 @@
background-color: transparent;
}
</style>
</head>
@endsection
<body class="bg-gray-100 min-h-screen text-gray-800" x-data="{
@section('nav-right')
{{-- admin 后台直达 --}}
@if (Auth::user()->user_level >= 15)
<a href="{{ route('admin.dashboard') }}" class="text-indigo-200 hover:text-white font-bold hidden sm:block">⚙️ 后台</a>
@endif
{{-- 个人资料 --}}
<button @click="showProfileModal = true" class="font-medium text-sm hover:text-indigo-200 transition flex items-center">
欢迎您,{{ Auth::user()->username }}
</button>
{{-- 新建房间 --}}
@if (Auth::user()->user_level >= 10)
<button @click="showCreateModal = true"
class="bg-emerald-500 hover:bg-emerald-400 px-4 py-2 rounded-md font-bold text-sm transition shadow-sm">
+ 新建房间
</button>
@endif
{{-- 退出登录 --}}
<form action="{{ route('logout') }}" method="POST" class="inline">
@csrf
<button type="submit"
class="text-sm border border-white/30 hover:bg-white/10 px-4 py-2 rounded-md transition font-medium">退出登录</button>
</form>
@endsection
@section('body-data',
"x-data=\"{
showCreateModal: false,
showEditModal: false,
showTransferModal: false,
showProfileModal: false,
showPasswordModal: false,
currentRoom: null
}">
}\"")
<!-- 顶部导航条 -->
<nav class="bg-indigo-600 px-6 py-4 shadow-md sticky top-0 z-50">
<div class="max-w-7xl mx-auto flex justify-between items-center text-white">
<div class="flex items-center space-x-4">
<span class="text-xl">🌟</span>
<h1 class="text-xl font-extrabold tracking-wider">星光大厅</h1>
</div>
<!-- 右侧操作区 -->
<div class="flex items-center space-x-3 text-sm">
<!-- 留言板入口 -->
<a href="{{ route('guestbook.index') }}"
class="text-indigo-100 hover:text-white font-bold flex items-center bg-indigo-800/40 px-3 py-1.5 rounded-full transition shadow-inner">
<span class="mr-1">✉️</span> 留言板
</a>
<!-- 风云排行榜入口 -->
<a href="{{ route('leaderboard.index') }}"
class="mr-4 text-yellow-400 hover:text-yellow-300 font-bold flex items-center bg-indigo-800/50 px-3 py-1.5 rounded-full transition shadow-inner">
<span class="mr-1">🏆</span> 风云榜
</a>
<!-- admin 后台直达 -->
@if (Auth::user()->user_level >= 15)
<a href="{{ route('admin.dashboard') }}"
class="mr-4 text-indigo-200 hover:text-white font-bold hidden sm:block">⚙️ 后台管理</a>
@endif
<!-- 点击直接在本页弹出资料卡修改 -->
<button @click="showProfileModal = true"
class="font-medium text-sm hover:text-indigo-200 transition flex items-center">
欢迎您,{{ Auth::user()->username }}
<span
class="bg-white/20 px-2 py-0.5 rounded-full text-xs ml-2 border border-white/10 shadow-sm">LV.{{ Auth::user()->user_level }}</span>
</button>
{{-- 权限按钮区 --}}
@if (Auth::user()->user_level >= 10)
<button @click="showCreateModal = true"
class="bg-emerald-500 hover:bg-emerald-400 px-4 py-2 rounded-md font-bold text-sm transition shadow-sm">
+ 新建房间
</button>
@endif
<form action="{{ route('logout') }}" method="POST" class="inline">
@csrf
<button type="submit"
class="text-sm border border-white/30 hover:bg-white/10 px-4 py-2 rounded-md transition font-medium">退出登录</button>
</form>
</div>
</div>
</nav>
<!-- 主展示区 -->
<main class="max-w-7xl mx-auto py-10 px-6">
<!-- 全局提示消息 -->
@if (session('success'))
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-8 rounded shadow-sm">
<p class="font-bold">操作成功</p>
<p>{{ session('success') }}</p>
</div>
@endif
@if (session('error'))
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-8 rounded shadow-sm">
<p class="font-bold">发生错误</p>
<p>{{ session('error') }}</p>
</div>
@endif
@if ($errors->any())
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-8 rounded shadow-sm">
@section('content')
{{-- 验证错误信息 --}}
@if ($errors->any())
<div class="max-w-7xl mx-auto px-6 mt-4">
<div class="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
</div>
@endif
{{-- 主展示区 --}}
<main class="max-w-7xl mx-auto py-10 px-6">
<div class="mb-6 flex justify-between items-end border-b pb-4">
<h2 class="text-xl font-bold text-gray-700">公开频段 (<span
class="text-indigo-600">{{ $rooms->count() }}</span>)</h2>
<h2 class="text-xl font-bold text-gray-700">公开频段 (<span class="text-indigo-600">{{ $rooms->count() }}</span>)
</h2>
</div>
<!-- 房间瀑布流网格 -->
{{-- 房间瀑布流网格 --}}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
@forelse($rooms as $room)
<div
@@ -145,7 +113,6 @@
{{-- 管理按钮组(仅房主或超管可见) --}}
<div class="flex space-x-2">
@if ($room->master == Auth::user()->username || Auth::user()->user_level >= 15)
<!-- 修改 -->
<button
@click="currentRoom = {id: {{ $room->id }}, name: '{{ addslashes($room->name) }}', description: '{{ addslashes($room->description) }}'}; showEditModal = true"
class="text-xs text-blue-600 hover:text-blue-800 font-semibold px-2 py-1 rounded hover:bg-blue-50 transition">
@@ -153,15 +120,12 @@
</button>
@if (!$room->is_system)
<!-- 转让 -->
<button
@click="currentRoom = {id: {{ $room->id }}, name: '{{ addslashes($room->name) }}'}; showTransferModal = true"
class="text-xs text-amber-600 hover:text-amber-800 font-semibold px-2 py-1 rounded hover:bg-amber-50 transition">
转让
</button>
<!-- 删除 -->
<form action="{{ route('rooms.destroy', $room->id) }}" method="POST"
class="inline"
<form action="{{ route('rooms.destroy', $room->id) }}" method="POST" class="inline"
onsubmit="return confirm('警告:确实要彻底解散「{{ $room->name }}」吗?此操作不可逆!');">
@csrf @method('delete')
<button type="submit"
@@ -172,7 +136,8 @@
</div>
{{-- 进入按钮 --}}
<a href="{{ route('chat.room', $room->id) }}"
<a href="#"
onclick="openChatRoom('{{ route('chat.room', $room->id) }}', '{{ $room->name }}'); return false;"
class="bg-indigo-600 text-white hover:bg-indigo-700 px-4 py-2 rounded-t-xl rounded-br-xl text-sm font-bold shadow-md hover:shadow-lg transition-all transform group-hover:-translate-y-0.5">
立刻进入 &rarr;
</a>
@@ -187,7 +152,7 @@
</div>
</main>
<!-- 新建房间 Modal (通过 Alpine.js 开关) -->
{{-- ═══════════ 新建房间 Modal ═══════════ --}}
<div x-show="showCreateModal" style="display: none;"
class="fixed inset-0 z-[100] bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
<div @click.away="showCreateModal = false"
@@ -221,7 +186,7 @@
</div>
</div>
<!-- 修改管理 Modal -->
{{-- ═══════════ 修改管理 Modal ═══════════ --}}
<div x-show="showEditModal" style="display: none;"
class="fixed inset-0 z-[100] bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
<div @click.away="showEditModal = false"
@@ -232,7 +197,6 @@
<button @click="showEditModal = false"
class="text-blue-400 hover:text-blue-600 font-bold text-xl">&times;</button>
</div>
<!-- 注意这里通过 Alpine 动态拼接 action 路径 -->
<form :action="'{{ url('rooms') }}/' + currentRoom?.id" method="POST" class="p-6">
@csrf @method('PUT')
<div class="mb-4">
@@ -256,7 +220,7 @@
</div>
</div>
<!-- 转让房主 Modal -->
{{-- ═══════════ 转让房主 Modal ═══════════ --}}
<div x-show="showTransferModal" style="display: none;"
class="fixed inset-0 z-[100] bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
<div @click.away="showTransferModal = false"
@@ -288,7 +252,7 @@
</div>
</div>
<!-- 个人资料设置 Modal -->
{{-- ═══════════ 个人资料设置 Modal ═══════════ --}}
<div x-show="showProfileModal" style="display: none;"
class="fixed inset-0 z-[100] bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
<div @click.away="showProfileModal = false"
@@ -310,16 +274,28 @@
isSaving: false,
async saveProfile() {
this.isSaving = true;
try {
const res = await fetch('{{ route('user.update_profile') }}', {
method: 'PUT',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type'
: 'application/json' , 'Accept' : 'application/json' }, body: JSON.stringify(this.profileData) }); const
data=await res.json(); if (res.ok && data.status === 'success') { alert(data.message);
window.location.reload(); } else { alert('保存失败: ' + (data.message || ' 输入有误')); } } catch (e) {
alert('网络异常'); } finally { this.isSaving=false; } } }">
this.isSaving = true;
try {
const res = await fetch('{{ route('user.update_profile') }}', {
method: 'PUT',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name=&quot;csrf-token&quot;]').getAttribute('content'),
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(this.profileData)
});
const
data = await res.json();
if (res.ok && data.status === 'success') {
alert(data.message);
window.location.reload();
} else { alert('保存失败: ' + (data.message || ' 输入有误')); }
} catch (e) {
alert('网络异常');
} finally { this.isSaving = false; }
}
}">
<form @submit.prevent="saveProfile">
<div class="mb-4">
<label class="block text-sm font-bold text-gray-700 mb-2">性别</label>
@@ -330,7 +306,6 @@
<option value="保密">保密</option>
</select>
</div>
<!-- 头像选择 (暂时写死输入框,后续可优化为网格选择) -->
<div class="mb-4">
<label class="block text-sm font-bold text-gray-700 mb-2">头像选择 (01.gif - 50.gif)</label>
<div class="flex items-center space-x-3">
@@ -363,7 +338,7 @@
</div>
</div>
<!-- 修改密码 Modal -->
{{-- ═══════════ 修改密码 Modal ═══════════ --}}
<div x-show="showPasswordModal" style="display: none;"
class="fixed inset-0 z-[110] bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
<div @click.away="showPasswordModal = false"
@@ -384,25 +359,38 @@
isSaving: false,
async savePassword() {
if (this.pwdData.new_password !== this.pwdData.new_password_confirmation) {
alert('两次输入的新密码不一致!');
return;
if (this.pwdData.new_password !== this.pwdData.new_password_confirmation) {
alert('两次输入的新密码不一致!');
return;
}
if (this.pwdData.new_password.length < 6) {
alert('新密码最少 6 位!');
return;
}
this.isSaving = true;
try {
const res = await fetch('{{ route('user.update_password') }}', {
method: 'PUT',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name=&quot;csrf-token&quot;]').getAttribute('content'),
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(this.pwdData)
});
const
data = await res.json();
if (res.ok && data.status === 'success') {
alert(data.message);
window.location.href = '{{ route('home') }}';
} else {
alert('密码修改失败: ' + (data.message || ' 请输入正确的旧密码'));
}
if (this.pwdData.new_password.length < 6) {
alert('新密码最少 6 位!');
return;
}
this.isSaving = true;
try {
const res = await fetch('{{ route('user.update_password') }}', {
method: 'PUT',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type'
: 'application/json' , 'Accept' : 'application/json' }, body: JSON.stringify(this.pwdData) }); const
data=await res.json(); if (res.ok && data.status === 'success') { alert(data.message);
window.location.href = '{{ route('home') }}'; // 改密成功重新登录 } else {
alert('密码修改失败: ' + (data.message || ' 请输入正确的旧密码')); } } catch (e) { alert('网络异常'); } finally {
this.isSaving=false; } } }">
} catch (e) { alert('网络异常'); } finally {
this.isSaving = false;
}
}
}">
<form @submit.prevent="savePassword">
<div class="mb-4">
<label class="block text-sm font-bold text-gray-700 mb-2">当前旧密码</label>
@@ -429,7 +417,25 @@
</div>
</div>
</div>
@endsection
</body>
</html>
@section('scripts')
{{-- 原版风格:弹出独立聊天窗口 --}}
<script>
/**
* 打开聊天室弹出窗口(复刻原版 DEFAULT.asp launchchat 函数)
*/
function openChatRoom(url, roomName) {
var chatWin = window.open(
url,
'chatroom_' + roomName,
'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes'
);
if (chatWin) {
chatWin.moveTo(0, 0);
chatWin.resizeTo(screen.availWidth, screen.availHeight);
chatWin.focus();
}
}
</script>
@endsection