Files
chatroom/resources/js/chat-room/pat.js
T
pllx 495efdf9e0 feat: 新增 /拍一拍 功能 + 斜杠命令菜单
- 输入框输入 / 弹出命令菜单,当前支持 /拍一拍
- 选择对象后输入 /拍一拍 发送拍一拍通知
- 所有在线用户屏幕抖动 + 正常聊天样式显示消息
- 命令注册表可扩展,后续新增命令只需 push 到数组
2026-04-28 22:59:16 +08:00

155 lines
5.2 KiB
JavaScript

// 拍一拍功能模块
// 拦截输入框中的 /拍一拍 命令,向所选对象发送拍一拍通知并触发屏幕抖动。
import { pruneMessageContainer } from "./message-renderer.js";
function csrf() {
return document.querySelector('meta[name="csrf-token"]')?.content ?? "";
}
/**
* 判断输入是否为 /拍一拍 命令。
*/
function isPatCommand(text) {
return /^\/拍一拍\s*$/.test(text);
}
/**
* 获取当前选中的聊天对象。
*/
function getSelectedTarget() {
const toUserSelect = document.getElementById("to_user");
if (!toUserSelect) return null;
const val = toUserSelect.value?.trim();
return val || null;
}
/**
* 执行拍一拍请求。
*/
async function executePat() {
const targetUser = getSelectedTarget();
if (!targetUser || targetUser === "大家") {
window.chatDialog?.alert("请先选择一个聊天对象(不能为大家),再进行拍一拍。", "拍一拍", "#f472b6");
return false;
}
const roomId = window.chatContext?.roomId;
if (!roomId) return false;
try {
const response = await fetch(`/room/${roomId}/pat`, {
method: "POST",
headers: {
"X-CSRF-TOKEN": csrf(),
"Content-Type": "application/json",
"Accept": "application/json",
},
body: JSON.stringify({ target_user: targetUser }),
});
const data = await response.json();
if (data.status === "success") {
// 清空输入并触发本机抖动
const contentInput = document.getElementById("content");
if (contentInput) {
contentInput.value = "";
if (typeof window.persistChatDraft === "function") {
window.persistChatDraft("");
}
contentInput.focus();
}
triggerPatShake();
return true;
}
window.chatDialog?.alert(data.message || "拍一拍失败", "拍一拍", "#f472b6");
return false;
} catch (error) {
console.error("拍一拍请求失败:", error);
window.chatDialog?.alert("网络错误,拍一拍发送失败。", "拍一拍", "#f472b6");
return false;
}
}
/**
* 触发屏幕抖动动画。
*/
function triggerPatShake() {
const layout = document.querySelector(".chat-layout");
if (!layout) return;
layout.classList.remove("chat-shake");
// 强制回流后重新添加动画
void layout.offsetWidth;
layout.classList.add("chat-shake");
// 动画结束后移除 class
setTimeout(() => {
layout.classList.remove("chat-shake");
}, 500);
}
/**
* 添加拍一拍消息到聊天窗口(使用正常聊天样式渲染)。
*/
function appendPatMessage(displayText, fromUserHeadface, fromUser, targetUser) {
const state = window.chatState;
const container = state?.container;
if (!container) return;
const now = new Date();
const timeStr = now.getHours().toString().padStart(2, "0") + ":" +
now.getMinutes().toString().padStart(2, "0") + ":" +
now.getSeconds().toString().padStart(2, "0");
const fromUserSafe = fromUser || "";
const targetUserSafe = targetUser || "";
// 获取发送者的在线数据,获取正确头像
const senderInfo = state.onlineUsers[fromUserSafe];
const senderHead = (senderInfo && senderInfo.headface) || "1.gif";
let headImgSrc = senderHead.startsWith("storage/") ? "/" + senderHead : "/images/headface/" + senderHead;
const headImg = '<img src="' + headImgSrc + '" style="display:inline;width:16px;height:16px;vertical-align:middle;margin-right:2px;mix-blend-mode: multiply;" onerror="this.src=\'/images/headface/1.gif\'">';
// 可点击用户名(与正常消息一致)
const fromHtml = '<span class="msg-user" data-chat-message-user data-u="' + fromUserSafe + '" style="color: #000099; cursor: pointer;">' + fromUserSafe + '</span>';
const toHtml = '<span class="msg-user" data-chat-message-user data-u="' + targetUserSafe + '" style="color: #000099; cursor: pointer;">' + targetUserSafe + '</span>';
const div = document.createElement("div");
div.className = "msg-line";
if (fromUserSafe) {
div.dataset.fromUser = fromUserSafe;
}
div.innerHTML = headImg + fromHtml + "对" + toHtml + "说:<span class=\"msg-content\" style=\"color: #000000\">👋 我刚拍了拍你</span> <span class=\"msg-time\">(" + timeStr + ")</span>";
container.appendChild(div);
pruneMessageContainer(container, 600);
if (state?.autoScroll) {
container.scrollTop = container.scrollHeight;
}
// 同时在包厢窗口(say2)也显示
const container2 = state?.container2;
if (container2) {
const div2 = div.cloneNode(true);
container2.appendChild(div2);
pruneMessageContainer(container2, 300);
if (state?.autoScroll) {
container2.scrollTop = container2.scrollHeight;
}
}
}
// ── 导出 ──
export { isPatCommand, executePat, triggerPatShake, appendPatMessage };
// 挂载到 window 供其他模块使用
window.isPatCommand = isPatCommand;
window.executePat = executePat;
window.triggerPatShake = triggerPatShake;
window.appendPatMessage = appendPatMessage;