feat: 好友系统全实现
后端:
- FriendController:add/remove/status/index 四个接口
- FriendAdded / FriendRemoved 广播事件(私有频道)
- channels.php 注册 user.{username} 私有频道鉴权
- routes/web.php 注册好友路由
- ChatController::init() 修复 DutyLog 在 return 后执行的 bug
- ChatController::notifyFriendsOnline() 上线时悄悄话通知好友
前端:
- user-actions:写私信 → 加好友/删好友按钮(动态状态)
- toggleFriend() 方法 + fetchUser 后加载好友状态
- scripts:监听私有频道 FriendAdded/FriendRemoved
- showFriendToast() 右下角浮窗通知(5秒自动消失)
- global-dialog 加 fdSlideIn 动画
This commit is contained in:
59
app/Events/FriendAdded.php
Normal file
59
app/Events/FriendAdded.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:好友添加广播事件
|
||||
*
|
||||
* 当用户 A 添加用户 B 为好友时,向 B 的私有频道广播此事件,
|
||||
* B 的客户端收到后展示弹窗通知。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class FriendAdded implements ShouldBroadcast
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* 构造好友添加事件。
|
||||
*
|
||||
* @param string $fromUsername 发起添加的用户名
|
||||
* @param string $toUsername 被添加的用户名(接收通知方)
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $fromUsername,
|
||||
public readonly string $toUsername,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 广播到被添加用户的私有频道,仅本人可见。
|
||||
*/
|
||||
public function broadcastOn(): Channel
|
||||
{
|
||||
return new PrivateChannel('user.'.$this->toUsername);
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播负载:包含发起人信息,供前端弹窗使用。
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
return [
|
||||
'from_username' => $this->fromUsername,
|
||||
'to_username' => $this->toUsername,
|
||||
'type' => 'friend_added',
|
||||
];
|
||||
}
|
||||
}
|
||||
59
app/Events/FriendRemoved.php
Normal file
59
app/Events/FriendRemoved.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:好友删除广播事件
|
||||
*
|
||||
* 当用户 A 删除用户 B 为好友时,向 B 的私有频道广播此事件,
|
||||
* B 的客户端收到后展示弹窗通知。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class FriendRemoved implements ShouldBroadcast
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* 构造好友删除事件。
|
||||
*
|
||||
* @param string $fromUsername 发起删除的用户名
|
||||
* @param string $toUsername 被删除的用户名(接收通知方)
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $fromUsername,
|
||||
public readonly string $toUsername,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 广播到被删除用户的私有频道,仅本人可见。
|
||||
*/
|
||||
public function broadcastOn(): Channel
|
||||
{
|
||||
return new PrivateChannel('user.'.$this->toUsername);
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播负载:包含发起人信息,供前端弹窗使用。
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
return [
|
||||
'from_username' => $this->fromUsername,
|
||||
'to_username' => $this->toUsername,
|
||||
'type' => 'friend_removed',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ use App\Events\UserLeft;
|
||||
use App\Http\Requests\SendMessageRequest;
|
||||
use App\Jobs\SaveMessageJob;
|
||||
use App\Models\Autoact;
|
||||
use App\Models\FriendRequest;
|
||||
use App\Models\Gift;
|
||||
use App\Models\PositionDutyLog;
|
||||
use App\Models\Room;
|
||||
@@ -182,17 +183,7 @@ class ChatController extends Controller
|
||||
return $fromUser === $username || $toUser === $username;
|
||||
}));
|
||||
|
||||
// 渲染主聊天框架视图
|
||||
return view('chat.frame', [
|
||||
'room' => $room,
|
||||
'user' => $user,
|
||||
'weekEffect' => $this->shopService->getActiveWeekEffect($user),
|
||||
'newbieEffect' => $newbieEffect,
|
||||
'historyMessages' => $historyMessages,
|
||||
]);
|
||||
|
||||
// 最后:如果用户有在职职务,开始记录这次入场的在职登录
|
||||
// 此时用户局部变量已初始化,可以安全读取 in_time
|
||||
// 7. 如果用户有在职職务,开始记录这次入场的在职登录
|
||||
$activeUP = $user->activePosition;
|
||||
if ($activeUP) {
|
||||
PositionDutyLog::create([
|
||||
@@ -203,6 +194,58 @@ class ChatController extends Controller
|
||||
'room_id' => $id,
|
||||
]);
|
||||
}
|
||||
|
||||
// 8. 好友上线通知:向此房间内在线的好友推送慧慧话
|
||||
$this->notifyFriendsOnline($id, $user->username);
|
||||
|
||||
// 渲染主聊天框架视图
|
||||
return view('chat.frame', [
|
||||
'room' => $room,
|
||||
'user' => $user,
|
||||
'weekEffect' => $this->shopService->getActiveWeekEffect($user),
|
||||
'newbieEffect' => $newbieEffect,
|
||||
'historyMessages' => $historyMessages,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当用户进入房间时,向该房间内在线的所有好友推送慧慧话通知。
|
||||
*
|
||||
* @param int $roomId 当前房间 ID
|
||||
* @param string $username 上线的用户名
|
||||
*/
|
||||
private function notifyFriendsOnline(int $roomId, string $username): void
|
||||
{
|
||||
// 获取所有把我加为好友的人(他们是将我加为好友的关注者)
|
||||
$friendUsernames = FriendRequest::where('towho', $username)->pluck('who');
|
||||
if ($friendUsernames->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 当前房间在线用户列表
|
||||
$onlineUsers = $this->chatState->getRoomUsers($roomId);
|
||||
|
||||
foreach ($friendUsernames as $friendName) {
|
||||
// 好友就在这个房间里,才发通知
|
||||
if (! isset($onlineUsers[$friendName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$msg = [
|
||||
'id' => $this->chatState->nextMessageId($roomId),
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '系统',
|
||||
'to_user' => $friendName,
|
||||
'content' => "🟢 你的好友 <b>{$username}</b> 上线啊!",
|
||||
'is_secret' => true,
|
||||
'font_color' => '#16a34a',
|
||||
'action' => '',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$this->chatState->pushMessage($roomId, $msg);
|
||||
broadcast(new MessageSent($roomId, $msg));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
234
app/Http/Controllers/FriendController.php
Normal file
234
app/Http/Controllers/FriendController.php
Normal file
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:好友系统控制器
|
||||
*
|
||||
* 处理聊天室内的好友关系管理:
|
||||
* 1. 添加好友(addFriend)
|
||||
* 2. 删除好友(removeFriend)
|
||||
* 3. 查询与指定用户的好友关系(status)
|
||||
* 4. 查询当前用户的好友列表(index)
|
||||
*
|
||||
* 好友关系模型:单向存储,互相添加才构成双向好友。
|
||||
* 使用原版 friend_requests 表(字段:who / towho / sub_time)。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\FriendAdded;
|
||||
use App\Events\FriendRemoved;
|
||||
use App\Models\FriendRequest;
|
||||
use App\Models\User;
|
||||
use App\Services\ChatStateService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class FriendController extends Controller
|
||||
{
|
||||
/**
|
||||
* 注入 Redis 状态服务,用于推送悄悄话通知。
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ChatStateService $chatState,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 查询当前用户与目标用户的好友关系状态。
|
||||
*
|
||||
* 返回:
|
||||
* - is_friend: 当前用户是否已将对方加为好友
|
||||
* - mutual: 是否互相添加(双向好友)
|
||||
*
|
||||
* @param string $username 目标用户名
|
||||
*/
|
||||
public function status(string $username): JsonResponse
|
||||
{
|
||||
$me = Auth::user();
|
||||
|
||||
// 我是否已将对方加为好友
|
||||
$iAdded = FriendRequest::where('who', $me->username)
|
||||
->where('towho', $username)
|
||||
->exists();
|
||||
|
||||
// 对方是否也将我加为好友
|
||||
$theyAdded = FriendRequest::where('who', $username)
|
||||
->where('towho', $me->username)
|
||||
->exists();
|
||||
|
||||
return response()->json([
|
||||
'is_friend' => $iAdded,
|
||||
'mutual' => $iAdded && $theyAdded,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加好友。
|
||||
*
|
||||
* 流程:
|
||||
* 1. 校验目标用户存在、且不是自己
|
||||
* 2. 检查是否已经添加过
|
||||
* 3. 写入 friend_requests 记录
|
||||
* 4. 广播 FriendAdded 事件通知对方
|
||||
* 5. 若对方当前在线(Redis),向对方发送悄悄话
|
||||
*
|
||||
* @param string $username 目标用户名
|
||||
*/
|
||||
public function addFriend(Request $request, string $username): JsonResponse
|
||||
{
|
||||
$me = Auth::user();
|
||||
|
||||
// 不能加自己
|
||||
if ($me->username === $username) {
|
||||
return response()->json(['status' => 'error', 'message' => '不能将自己加为好友'], 422);
|
||||
}
|
||||
|
||||
// 检查目标用户是否存在
|
||||
$target = User::where('username', $username)->first();
|
||||
if (! $target) {
|
||||
return response()->json(['status' => 'error', 'message' => '用户不存在'], 404);
|
||||
}
|
||||
|
||||
// 是否已添加
|
||||
$exists = FriendRequest::where('who', $me->username)->where('towho', $username)->exists();
|
||||
if ($exists) {
|
||||
return response()->json(['status' => 'error', 'message' => '已是好友,无需重复添加'], 422);
|
||||
}
|
||||
|
||||
// 写入好友关系
|
||||
FriendRequest::create([
|
||||
'who' => $me->username,
|
||||
'towho' => $username,
|
||||
'sub_time' => now(),
|
||||
]);
|
||||
|
||||
// 广播给对方(仅对方可见)
|
||||
broadcast(new FriendAdded($me->username, $username));
|
||||
|
||||
// 若对方在线,推送聊天区悄悄话
|
||||
$this->notifyOnlineUser($username, $me->username, 'added', $request->input('room_id'));
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => '已成功添加 '.$username.' 为好友 🎉',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除好友。
|
||||
*
|
||||
* 流程:
|
||||
* 1. 删除 friend_requests 中「我 → 对方」的记录
|
||||
* 2. 广播 FriendRemoved 事件通知对方
|
||||
* 3. 若对方在线,向对方发送悄悄话
|
||||
*
|
||||
* @param string $username 目标用户名
|
||||
*/
|
||||
public function removeFriend(Request $request, string $username): JsonResponse
|
||||
{
|
||||
$me = Auth::user();
|
||||
|
||||
$deleted = FriendRequest::where('who', $me->username)
|
||||
->where('towho', $username)
|
||||
->delete();
|
||||
|
||||
if (! $deleted) {
|
||||
return response()->json(['status' => 'error', 'message' => '好友关系不存在'], 404);
|
||||
}
|
||||
|
||||
// 广播给对方
|
||||
broadcast(new FriendRemoved($me->username, $username));
|
||||
|
||||
// 若对方在线,推送聊天区悄悄话
|
||||
$this->notifyOnlineUser($username, $me->username, 'removed', $request->input('room_id'));
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => '已将 '.$username.' 从好友列表移除',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户的好友列表(我添加的 + 对方也添加我的 = 双向好友标记)。
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$me = Auth::user();
|
||||
|
||||
// 我添加的所有人
|
||||
$myAdded = FriendRequest::where('who', $me->username)->pluck('towho');
|
||||
|
||||
// 也把我加了的
|
||||
$addedMe = FriendRequest::where('towho', $me->username)->pluck('who');
|
||||
|
||||
$friends = User::whereIn('username', $myAdded)->get(['username', 'usersf', 'user_level', 'sex'])->map(function ($u) use ($addedMe) {
|
||||
return [
|
||||
'username' => $u->username,
|
||||
'headface' => $u->headface,
|
||||
'user_level' => $u->user_level,
|
||||
'sex' => $u->sex,
|
||||
'mutual' => $addedMe->contains($u->username), // 是否互相添加
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json(['status' => 'success', 'friends' => $friends]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 若目标用户在线,向其发送系统悄悄话通知。
|
||||
*
|
||||
* 好友上线/下线使用此方法,不公开广播,只有本人可见。
|
||||
*
|
||||
* @param string $targetUsername 接收通知的用户名
|
||||
* @param string $fromUsername 发起操作的用户名
|
||||
* @param string $action 'added' | 'removed' | 'online'
|
||||
* @param int|null $roomId 当前房间 ID(用于推送到对应房间频道)
|
||||
*/
|
||||
private function notifyOnlineUser(
|
||||
string $targetUsername,
|
||||
string $fromUsername,
|
||||
string $action,
|
||||
?int $roomId = null
|
||||
): void {
|
||||
if (! $roomId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查对方是否在该房间在线
|
||||
$onlineUsers = $this->chatState->getRoomUsers($roomId);
|
||||
if (! isset($onlineUsers[$targetUsername])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = match ($action) {
|
||||
'added' => "💚 <b>{$fromUsername}</b> 将你加为好友了!你们现在是好友了 🎉",
|
||||
'removed' => "💔 <b>{$fromUsername}</b> 已将你从好友列表移除。",
|
||||
'online' => "🟢 你的好友 <b>{$fromUsername}</b> 上线啦!",
|
||||
default => '',
|
||||
};
|
||||
|
||||
if (! $content) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建系统悄悄话消息
|
||||
$msg = [
|
||||
'id' => $this->chatState->nextMessageId($roomId),
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '系统',
|
||||
'to_user' => $targetUsername,
|
||||
'content' => $content,
|
||||
'is_secret' => true,
|
||||
'font_color' => '#16a34a',
|
||||
'action' => '',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$this->chatState->pushMessage($roomId, $msg);
|
||||
broadcast(new \App\Events\MessageSent($roomId, $msg));
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fdSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(16px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
#global-dialog-cancel-btn:hover {
|
||||
background: #e5e7eb !important;
|
||||
}
|
||||
|
||||
@@ -650,6 +650,64 @@
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', setupChangelogPublishedListener);
|
||||
|
||||
// ── 好友系统私有频道监听(仅本人可见) ────────────────
|
||||
/**
|
||||
* 监听当前用户的私有频道 `user.{username}`,
|
||||
* 收到 FriendAdded / FriendRemoved 事件时用任务弹窗通知。
|
||||
*/
|
||||
function setupFriendNotification() {
|
||||
if (!window.Echo || !window.chatContext) {
|
||||
setTimeout(setupFriendNotification, 500);
|
||||
return;
|
||||
}
|
||||
const myName = window.chatContext.username;
|
||||
window.Echo.private(`user.${myName}`)
|
||||
.listen('.FriendAdded', (e) => {
|
||||
showFriendToast(
|
||||
`💚 <b>${e.from_username}</b> 将你加为好友了!`,
|
||||
'#16a34a'
|
||||
);
|
||||
})
|
||||
.listen('.FriendRemoved', (e) => {
|
||||
showFriendToast(
|
||||
`💔 <b>${e.from_username}</b> 已将你从好友列表移除。`,
|
||||
'#6b7280'
|
||||
);
|
||||
});
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', setupFriendNotification);
|
||||
|
||||
/**
|
||||
* 显示好友事件通知浮窗(类似任务弹窗,右下角淡入淡出)。
|
||||
*
|
||||
* @param {string} html 通知内容(支持 HTML)
|
||||
* @param {string} color 左边框颜色
|
||||
*/
|
||||
function showFriendToast(html, color = '#16a34a') {
|
||||
const toast = document.createElement('div');
|
||||
toast.style.cssText = `
|
||||
position: fixed; bottom: 24px; right: 24px; z-index: 999999;
|
||||
background: #fff; border-left: 4px solid ${color};
|
||||
border-radius: 8px; padding: 14px 18px; min-width: 260px; max-width: 320px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,.18);
|
||||
font-size: 13px; color: #374151; line-height: 1.5;
|
||||
animation: fdSlideIn .3s ease; cursor: pointer;
|
||||
`;
|
||||
toast.innerHTML = `
|
||||
<div style="font-weight:bold; margin-bottom:4px; color:${color};">💬 好友通知</div>
|
||||
<div>${html}</div>
|
||||
`;
|
||||
// 点击关闭
|
||||
toast.addEventListener('click', () => toast.remove());
|
||||
document.body.appendChild(toast);
|
||||
// 5秒后自动消失
|
||||
setTimeout(() => {
|
||||
toast.style.transition = 'opacity .5s';
|
||||
toast.style.opacity = '0';
|
||||
setTimeout(() => toast.remove(), 500);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// ── 全屏特效事件监听(烟花/下雨/雷电/下雪)─────────
|
||||
window.addEventListener('chat:effect', (e) => {
|
||||
const type = e.detail?.type;
|
||||
|
||||
@@ -86,6 +86,8 @@
|
||||
whisperList: [],
|
||||
showAnnounce: false,
|
||||
announceText: '',
|
||||
is_friend: false, // 当前用户是否已将对方加为好友
|
||||
friendLoading: false, // 好友操作加载状态
|
||||
gifts: window.__gifts || [],
|
||||
selectedGiftId: window.__defaultGiftId || 0,
|
||||
giftCount: 1,
|
||||
@@ -107,6 +109,51 @@
|
||||
$alert: (...args) => window.chatDialog.alert(...args),
|
||||
$confirm: (...args) => window.chatDialog.confirm(...args),
|
||||
|
||||
/** 切换好友关系(加好友 / 删好友) */
|
||||
async toggleFriend() {
|
||||
if (this.friendLoading) return;
|
||||
this.friendLoading = true;
|
||||
const username = this.userInfo.username;
|
||||
const roomId = window.chatContext.roomId;
|
||||
const removing = this.is_friend;
|
||||
|
||||
try {
|
||||
let res;
|
||||
if (removing) {
|
||||
// 删除好友
|
||||
res = await fetch(`/friend/${encodeURIComponent(username)}/remove`, {
|
||||
method: 'DELETE',
|
||||
headers: this._headers(),
|
||||
body: JSON.stringify({
|
||||
room_id: roomId
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
// 添加好友
|
||||
res = await fetch(`/friend/${encodeURIComponent(username)}/add`, {
|
||||
method: 'POST',
|
||||
headers: this._headers(),
|
||||
body: JSON.stringify({
|
||||
room_id: roomId
|
||||
}),
|
||||
});
|
||||
}
|
||||
const data = await res.json();
|
||||
const ok = data.status === 'success';
|
||||
this.$alert(
|
||||
data.message,
|
||||
ok ? (removing ? '已删除好友' : '添加成功 🎉') : '操作失败',
|
||||
ok ? (removing ? '#6b7280' : '#16a34a') : '#cc4444'
|
||||
);
|
||||
if (ok) {
|
||||
this.is_friend = !this.is_friend;
|
||||
}
|
||||
} catch (e) {
|
||||
this.$alert('网络异常', '错误', '#cc4444');
|
||||
}
|
||||
this.friendLoading = false;
|
||||
},
|
||||
|
||||
/** 获取用户资料 */
|
||||
async fetchUser(username) {
|
||||
try {
|
||||
@@ -126,6 +173,19 @@
|
||||
const data = await res.json();
|
||||
if (data.status === 'success') {
|
||||
this.userInfo = data.data;
|
||||
this.showPositionHistory = false;
|
||||
|
||||
// 加载好友状态(仅对非自己的用户查询)
|
||||
if (data.data.username !== window.chatContext.username) {
|
||||
fetch(`/friend/${encodeURIComponent(data.data.username)}/status`, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
}).then(r => r.json()).then(s => {
|
||||
this.is_friend = s.is_friend ?? false;
|
||||
});
|
||||
}
|
||||
this.showUserModal = true;
|
||||
this.isMuting = false;
|
||||
this.showWhispers = false;
|
||||
@@ -554,13 +614,15 @@
|
||||
<div x-data="{ showGiftPanel: false }" x-show="userInfo.username !== window.chatContext.username">
|
||||
|
||||
<div class="modal-actions" style="margin-bottom: 0;">
|
||||
{{-- 写私信 --}}
|
||||
<a class="btn-mail"
|
||||
:href="'{{ route('guestbook.index', ['tab' => 'outbox']) }}&to=' + encodeURIComponent(userInfo
|
||||
.username)"
|
||||
target="_blank">
|
||||
写私信
|
||||
</a>
|
||||
{{-- 加好友 / 删好友(替代写私信) --}}
|
||||
<button x-on:click="toggleFriend()" :disabled="friendLoading"
|
||||
:style="is_friend
|
||||
?
|
||||
'background: #f1f5f9; color: #6b7280; border: 1px solid #d1d5db;' :
|
||||
'background: linear-gradient(135deg,#16a34a,#22c55e); color:#fff; border:none;'"
|
||||
style="padding: 7px 14px; border-radius: 5px; font-size: 12px;
|
||||
cursor: pointer; font-weight: bold; transition: opacity .15s;"
|
||||
x-text="friendLoading ? '处理中…' : (is_friend ? '✅ 已是好友 (点击删除)' : '➕ 加好友')"></button>
|
||||
{{-- 送花按鈕(与写私信并列) --}}
|
||||
<button class="btn-whisper" x-on:click="showGiftPanel = !showGiftPanel">
|
||||
🎁 送礼物
|
||||
|
||||
@@ -26,3 +26,9 @@ Broadcast::channel('room.{roomId}', function ($user, $roomId) {
|
||||
'is_admin' => $user->user_level >= $superLevel,
|
||||
];
|
||||
});
|
||||
|
||||
// 用户私有频道鉴权(好友通知:FriendAdded / FriendRemoved)
|
||||
// 只有用户名匹配的本人才能订阅
|
||||
Broadcast::channel('user.{username}', function ($user, string $username) {
|
||||
return $user->username === $username;
|
||||
});
|
||||
|
||||
@@ -66,6 +66,12 @@ Route::middleware(['chat.auth'])->group(function () {
|
||||
Route::post('/user/{username}/ban', [UserController::class, 'ban'])->name('user.ban');
|
||||
Route::post('/user/{username}/banip', [UserController::class, 'banIp'])->name('user.banip');
|
||||
|
||||
// ---- 好友系统 ----
|
||||
Route::get('/friends', [\App\Http\Controllers\FriendController::class, 'index'])->name('friend.index');
|
||||
Route::get('/friend/{username}/status', [\App\Http\Controllers\FriendController::class, 'status'])->name('friend.status');
|
||||
Route::post('/friend/{username}/add', [\App\Http\Controllers\FriendController::class, 'addFriend'])->name('friend.add');
|
||||
Route::delete('/friend/{username}/remove', [\App\Http\Controllers\FriendController::class, 'removeFriend'])->name('friend.remove');
|
||||
|
||||
// ---- 第五阶段:具体房间内部聊天核心 ----
|
||||
// 进入具体房间界面的初始化
|
||||
Route::get('/room/{id}', [ChatController::class, 'init'])->name('chat.room');
|
||||
|
||||
Reference in New Issue
Block a user