Files
chatroom/resources/views/rooms/index.blade.php
T

436 lines
25 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{--
文件功能:聊天大厅 - 房间列表页
展示所有公开聊天房间,支持创建、编辑、转让、删除房间
以及修改个人资料和密码
@extends layouts.app
--}}
@extends('layouts.app')
@section('title', '聊天大厅 - 飘落流星')
@section('nav-icon', '🌟')
@section('nav-title', '星光大厅')
@section('head')
<style>
.custom-scrollbar::-webkit-scrollbar {
width: 8px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: #cbd5e1;
border-radius: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background-color: transparent;
}
</style>
@endsection
@section('body-data')
x-data="{
showCreateModal: false,
showEditModal: false,
showTransferModal: false,
showProfileModal: false,
showPasswordModal: false,
currentRoom: null
}"
@endsection
@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>
</div>
@endif
{{-- 主展示区 --}}
<main class="max-w-7xl mx-auto py-10 px-4 sm:px-6 lg:px-8">
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between border-b pb-4 gap-4">
<h2 class="text-xl font-bold text-gray-700">
公开频段 (<span class="text-indigo-600">{{ $rooms->count() }}</span>)
</h2>
<div class="flex flex-wrap gap-2 text-xs">
<a href="{{ route('baccarat.history-page') }}"
class="inline-flex items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-red-600 to-indigo-600 hover:from-red-700 hover:to-indigo-700 text-white rounded-xl font-bold shadow-md hover:shadow-lg transition transform hover:-translate-y-0.5">
🎲 百家乐开奖走势
</a>
<button @click="showCreateModal = true"
class="inline-flex items-center gap-1.5 px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl font-bold shadow-md hover:shadow-lg transition transform hover:-translate-y-0.5 cursor-pointer">
新增私人频道
</button>
<button @click="showProfileModal = true"
class="inline-flex items-center gap-1.5 px-4 py-2 bg-gray-800 hover:bg-gray-900 text-white rounded-xl font-bold shadow-md hover:shadow-lg transition transform hover:-translate-y-0.5 cursor-pointer">
👤 个人设置
</button>
</div>
</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
class="bg-white rounded-xl shadow-sm hover:shadow-lg transition-shadow border border-gray-100 overflow-hidden flex flex-col group {{ $room->is_system ? 'ring-2 ring-indigo-500/20' : '' }}">
{{-- 卡片头部 --}}
<div class="p-5 flex-1 relative">
@if ($room->is_system)
<span
class="absolute top-4 right-4 bg-indigo-100 text-indigo-700 text-xs font-bold px-2 py-1 rounded-sm">官方驻地</span>
@endif
<h3 class="text-xl font-bold text-gray-800 mb-2 truncate" title="{{ $room->name }}">
{{ $room->name }}</h3>
<p class="text-sm text-gray-500 line-clamp-2 h-10 mb-4">
{{ $room->description ?: '房主很懒,什么都没写...' }}</p>
<div class="flex items-center text-xs text-gray-400 font-medium">
<span>房主:<span
class="{{ $room->master == Auth::user()->username ? 'text-indigo-600 font-bold' : 'text-gray-600' }}">{{ $room->master }}</span></span>
</div>
</div>
{{-- 底部操作区 --}}
<div class="bg-gray-50 p-4 border-t border-gray-100 flex items-center justify-between">
{{-- 管理按钮组(仅房主或超管可见) --}}
<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">
管理
</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"
data-front-confirm="警告:确实要彻底解散「{{ $room->name }}」吗?此操作不可逆!">
@csrf @method('delete')
<button type="submit"
class="text-xs text-red-600 hover:text-red-800 font-semibold px-2 py-1 rounded hover:bg-red-50 transition">解散</button>
</form>
@endif
@endif
</div>
{{-- 进入按钮 --}}
<a href="#"
data-chat-room-window-url="{{ route('chat.room', $room->id) }}"
data-chat-room-window-name="{{ $room->name }}"
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>
</div>
</div>
@empty
<div class="col-span-full py-20 text-center text-gray-400">
<p class="text-xl font-bold mb-2">大厅空空如也</p>
<p>目前还没有人创建任何房间。</p>
</div>
@endforelse
</div>
</main>
{{-- ═══════════ 新建房间 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"
class="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden transform transition-all"
x-transition.scale.95>
<div class="bg-gray-50 border-b px-6 py-4 flex justify-between items-center">
<h3 class="font-bold text-lg text-gray-800">新建私人聊天频道</h3>
<button @click="showCreateModal = false"
class="text-gray-400 hover:text-gray-600 font-bold text-xl">&times;</button>
</div>
<form action="{{ route('rooms.store') }}" method="POST" class="p-6">
@csrf
<div class="mb-4">
<label class="block text-sm font-bold text-gray-700 mb-2">频道名称 <span
class="text-red-500">*</span></label>
<input type="text" name="name" required maxlength="50" placeholder="例如:技术交流水群"
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">
</div>
<div class="mb-6">
<label class="block text-sm font-bold text-gray-700 mb-2">频道主题描述</label>
<textarea name="description" rows="3" maxlength="255" placeholder="一句话介绍这里的规矩..."
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 resize-none"></textarea>
</div>
<div class="flex justify-end space-x-3">
<button type="button" @click="showCreateModal = false"
class="px-4 py-2 bg-white border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 font-medium">取消</button>
<button type="submit"
class="px-6 py-2 bg-emerald-600 text-white rounded-md font-bold hover:bg-emerald-700 shadow-sm">立即创建</button>
</div>
</form>
</div>
</div>
{{-- ═══════════ 修改管理 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"
class="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden transform transition-all"
x-transition.scale.95>
<div class="bg-blue-50 border-b border-blue-100 px-6 py-4 flex justify-between items-center">
<h3 class="font-bold text-lg text-blue-900" x-text="'管理频道: ' + currentRoom?.name"></h3>
<button @click="showEditModal = false"
class="text-blue-400 hover:text-blue-600 font-bold text-xl">&times;</button>
</div>
<form :action="'{{ url('rooms') }}/' + currentRoom?.id" method="POST" class="p-6">
@csrf @method('PUT')
<div class="mb-4">
<label class="block text-sm font-bold text-gray-700 mb-2">频道新名称 <span
class="text-red-500">*</span></label>
<input type="text" name="name" :value="currentRoom?.name" required maxlength="50"
class="w-full border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 p-2.5 bg-gray-50 border">
</div>
<div class="mb-6">
<label class="block text-sm font-bold text-gray-700 mb-2">更新主题描述</label>
<textarea name="description" x-text="currentRoom?.description" rows="3" maxlength="255"
class="w-full border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 p-2.5 bg-gray-50 border resize-none"></textarea>
</div>
<div class="flex justify-end space-x-3">
<button type="button" @click="showEditModal = false"
class="px-4 py-2 bg-white border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 font-medium">关闭</button>
<button type="submit"
class="px-6 py-2 bg-blue-600 text-white rounded-md font-bold hover:bg-blue-700 shadow-sm">保存并广播</button>
</div>
</form>
</div>
</div>
{{-- ═══════════ 转让房主 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"
class="bg-white rounded-2xl shadow-2xl w-full max-w-sm overflow-hidden transform transition-all"
x-transition.scale.95>
<div class="bg-amber-50 border-b border-amber-100 px-6 py-4 flex justify-between items-center">
<h3 class="font-bold text-lg text-amber-900" x-text="'转让授权: ' + currentRoom?.name"></h3>
<button @click="showTransferModal = false"
class="text-amber-400 hover:text-amber-600 font-bold text-xl">&times;</button>
</div>
<form :action="'{{ url('rooms') }}/' + currentRoom?.id + '/transfer'" method="POST" class="p-6">
@csrf
<div class="bg-amber-100 text-amber-800 text-xs p-3 rounded mb-4">
转让后您将立即失去对该房间的管理权限,请谨慎操作。
</div>
<div class="mb-6">
<label class="block text-sm font-bold text-gray-700 mb-2">接收人昵称 <span
class="text-red-500">*</span></label>
<input type="text" name="target_username" required placeholder="请输入对方准确用户名"
class="w-full border-gray-300 rounded-md shadow-sm focus:border-amber-500 focus:ring-amber-500 p-2.5 bg-gray-50 border">
</div>
<div class="flex justify-end space-x-3">
<button type="button" @click="showTransferModal = false"
class="px-4 py-2 bg-white border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 font-medium">取消</button>
<button type="submit"
class="px-6 py-2 bg-amber-600 text-white rounded-md font-bold hover:bg-amber-700 shadow-sm">确认转让</button>
</div>
</form>
</div>
</div>
{{-- ═══════════ 个人资料设置 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"
class="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden transform transition-all"
x-transition.scale.95>
<div
class="bg-gradient-to-r from-indigo-500 to-purple-600 px-6 py-4 flex justify-between items-center text-white">
<h3 class="font-bold text-lg">个人资料设置</h3>
<button @click="showProfileModal = false"
class="text-white/80 hover:text-white font-bold text-xl">&times;</button>
</div>
<div class="p-6" x-data="{
profileData: {
sex: '{{ Auth::user()->sex }}',
sign: '{{ Auth::user()->sign }}',
headface: '{{ Auth::user()->headface ?? '01.gif' }}'
},
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=&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">
@php
$currentSignIdentity = Auth::user()->currentSignInIdentity();
$latestSignIn = Auth::user()->dailySignIns()->first();
@endphp
<div class="mb-4 rounded-lg border border-teal-100 bg-teal-50 px-3 py-2 text-sm text-teal-800">
<div class="font-bold">每日签到</div>
<div class="mt-1 flex flex-wrap items-center gap-2 text-xs">
<span>连续签到 {{ (int) ($latestSignIn?->streak_days ?? 0) }} </span>
@if ($currentSignIdentity)
<span class="inline-flex items-center gap-1 rounded-full border border-teal-200 bg-white px-2 py-0.5 font-bold"
style="color: {{ $currentSignIdentity->badge_color ?? '#0f766e' }}">
<span>{{ $currentSignIdentity->badge_icon ?? '✅' }}</span>
<span>{{ $currentSignIdentity->badge_name }}</span>
</span>
@else
<span class="text-teal-600">暂无签到身份</span>
@endif
</div>
</div>
<div class="mb-4">
<label class="block text-sm font-bold text-gray-700 mb-2">性别</label>
<select x-model="profileData.sex"
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">
<option value="">男生</option>
<option value="">女生</option>
<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">
<div class="w-12 h-12 rounded bg-gray-200 shrink-0 overflow-hidden border">
<img :src="profileData.headface.startsWith('storage/') ? '/' + profileData.headface : '/images/headface/' + profileData.headface"
@@error="$el.style.display='none'"
class="w-full h-full object-cover">
</div>
<input type="text" x-model="profileData.headface" required
class="flex-1 border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 p-2.5 bg-gray-50 border">
</div>
</div>
<div class="mb-6">
<label class="block text-sm font-bold text-gray-700 mb-2">个性签名</label>
<textarea x-model="profileData.sign" rows="2" maxlength="255" placeholder="写点什么吧..."
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 resize-none"></textarea>
</div>
<div class="flex justify-between items-center border-t pt-4">
<button type="button" @click="showProfileModal = false; showPasswordModal = true"
class="text-sm text-indigo-600 hover:text-indigo-800 font-bold hover:underline">修改安全密码</button>
<div class="space-x-2">
<button type="button" @click="showProfileModal = false"
class="px-4 py-2 bg-white border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 font-medium cursor-pointer">取消</button>
<button type="submit" :disabled="isSaving"
class="px-6 py-2 bg-indigo-600 text-white rounded-md font-bold hover:bg-indigo-700 shadow-sm disabled:opacity-50 cursor-pointer">保存资料</button>
</div>
</div>
</form>
</div>
</div>
</div>
{{-- ═══════════ 修改密码 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"
class="bg-white rounded-2xl shadow-2xl w-full max-w-sm overflow-hidden transform transition-all"
x-transition.scale.95>
<div class="bg-gray-800 px-6 py-4 flex justify-between items-center text-white">
<h3 class="font-bold text-lg">修改安全密码</h3>
<button @click="showPasswordModal = false"
class="text-gray-400 hover:text-white font-bold text-xl">&times;</button>
</div>
<div class="p-6" x-data="{
pwdData: {
old_password: '',
new_password: '',
new_password_confirmation: ''
},
isSaving: false,
async savePassword() {
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 || ' 请输入正确的旧密码'));
}
} 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>
<input type="password" x-model="pwdData.old_password" required
class="w-full border-gray-300 rounded-md shadow-sm focus:border-gray-500 focus:ring-gray-500 p-2.5 bg-gray-50 border">
</div>
<div class="mb-4">
<label class="block text-sm font-bold text-gray-700 mb-2">新密码</label>
<input type="password" x-model="pwdData.new_password" required placeholder="至少 6 位字母或数字"
class="w-full border-gray-300 rounded-md shadow-sm focus:border-gray-500 focus:ring-gray-500 p-2.5 bg-gray-50 border">
</div>
<div class="mb-6">
<label class="block text-sm font-bold text-gray-700 mb-2">确认新密码</label>
<input type="password" x-model="pwdData.new_password_confirmation" required
class="w-full border-gray-300 rounded-md shadow-sm focus:border-gray-500 focus:ring-gray-500 p-2.5 bg-gray-50 border">
</div>
<div class="flex justify-end space-x-3 border-t pt-4">
<button type="button" @click="showPasswordModal = false"
class="px-4 py-2 bg-white border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 font-medium cursor-pointer">取消</button>
<button type="submit" :disabled="isSaving"
class="px-6 py-2 bg-gray-800 text-white rounded-md font-bold hover:bg-gray-900 shadow-sm disabled:opacity-50 cursor-pointer">确定修改</button>
</div>
</form>
</div>
</div>
</div>
@endsection