功能:VIP 赞助会员系统
- 新建 vip_levels 表(名称、图标、颜色、经验/金币倍率、专属进入/离开模板) - 默认4个等级种子:白银🥈(×1.5)、黄金🥇(×2.0)、钻石💎(×3.0)、至尊👑(×5.0) - 后台 VIP 等级 CRUD 管理(新增/编辑/删除,配置模板和倍率) - 后台用户编辑弹窗支持设置 VIP 等级和到期时间 - ChatController 心跳经验按 VIP 倍率加成 - FishingController 正向奖励按 VIP 倍率加成(负面惩罚不变) - 在线名单显示 VIP 图标和管理员🛡️标识 - VIP 用户进入/离开使用专属颜色和标题 - 后台侧栏新增「👑 VIP 会员等级」入口
This commit is contained in:
@@ -84,6 +84,27 @@
|
||||
};
|
||||
userList.appendChild(allDiv);
|
||||
|
||||
// ── AI 小助手(仅当全局开关开启时显示)──
|
||||
if (window.chatContext.chatBotEnabled) {
|
||||
let botDiv = document.createElement('div');
|
||||
botDiv.className = 'user-item';
|
||||
botDiv.style.background = 'linear-gradient(135deg, #e0f2fe, #f0fdf4)';
|
||||
botDiv.style.borderLeft = '3px solid #22c55e';
|
||||
botDiv.innerHTML = '<span style="font-size:14px; margin-right:3px;">🤖</span>' +
|
||||
'<span class="user-name" style="color: #16a34a; font-weight: bold;">AI小助手</span>';
|
||||
botDiv.onclick = () => {
|
||||
toUserSelect.value = 'AI小助手';
|
||||
document.getElementById('content').focus();
|
||||
};
|
||||
userList.appendChild(botDiv);
|
||||
|
||||
// 在发言对象下拉框中也添加 AI 小助手
|
||||
let botOption = document.createElement('option');
|
||||
botOption.value = 'AI小助手';
|
||||
botOption.textContent = '🤖 AI小助手';
|
||||
toUserSelect.appendChild(botOption);
|
||||
}
|
||||
|
||||
// 获取排序方式
|
||||
const sortSelect = document.getElementById('user-sort-select');
|
||||
const sortBy = sortSelect ? sortSelect.value : 'default';
|
||||
@@ -112,10 +133,20 @@
|
||||
item.dataset.username = username;
|
||||
|
||||
const headface = user.headface || '1.GIF';
|
||||
// VIP 图标和管理员标识
|
||||
let badges = '';
|
||||
if (user.vip_icon) {
|
||||
const vipColor = user.vip_color || '#f59e0b';
|
||||
badges +=
|
||||
`<span style="font-size:12px; margin-left:2px; color:${vipColor};" title="${user.vip_name || 'VIP'}">${user.vip_icon}</span>`;
|
||||
}
|
||||
if (user.is_admin) {
|
||||
badges += `<span style="font-size:11px; margin-left:2px;" title="管理员">🛡️</span>`;
|
||||
}
|
||||
|
||||
item.innerHTML = `
|
||||
<img class="user-head" src="/images/headface/${headface}" onerror="this.src='/images/headface/1.GIF'">
|
||||
<span class="user-name">${username}</span>
|
||||
<span class="user-name">${username}</span>${badges}
|
||||
`;
|
||||
|
||||
item.onclick = () => {
|
||||
@@ -330,8 +361,16 @@
|
||||
|
||||
const sysDiv = document.createElement('div');
|
||||
sysDiv.className = 'msg-line';
|
||||
sysDiv.innerHTML =
|
||||
`<span style="color: green">【欢迎】${msg}</span><span class="msg-time">(${timeStr})</span>`;
|
||||
|
||||
// VIP 用户使用专属颜色和图标
|
||||
if (user.vip_icon && user.vip_name) {
|
||||
const vipColor = user.vip_color || '#f59e0b';
|
||||
sysDiv.innerHTML =
|
||||
`<span style="color: ${vipColor}; font-weight: bold;">【${user.vip_icon} ${user.vip_name}】${msg}</span><span class="msg-time">(${timeStr})</span>`;
|
||||
} else {
|
||||
sysDiv.innerHTML =
|
||||
`<span style="color: green">【欢迎】${msg}</span><span class="msg-time">(${timeStr})</span>`;
|
||||
}
|
||||
container.appendChild(sysDiv);
|
||||
scrollToBottom();
|
||||
});
|
||||
@@ -343,7 +382,14 @@
|
||||
|
||||
const sysDiv = document.createElement('div');
|
||||
sysDiv.className = 'msg-line sys-msg';
|
||||
sysDiv.innerHTML = `<span style="color: gray">☆ ${user.username} 离开了聊天室 ☆</span>`;
|
||||
// VIP 用户离开也带专属颜色
|
||||
if (user.vip_icon && user.vip_name) {
|
||||
const vipColor = user.vip_color || '#f59e0b';
|
||||
sysDiv.innerHTML =
|
||||
`<span style="color: ${vipColor}">☆ ${user.vip_icon}${user.vip_name} ${user.username} 潇洒离去 ☆</span>`;
|
||||
} else {
|
||||
sysDiv.innerHTML = `<span style="color: gray">☆ ${user.username} 离开了聊天室 ☆</span>`;
|
||||
}
|
||||
container.appendChild(sysDiv);
|
||||
scrollToBottom();
|
||||
});
|
||||
@@ -441,6 +487,15 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果发言对象是 AI 小助手,走专用机器人 API
|
||||
const toUser = formData.get('to_user');
|
||||
if (toUser === 'AI小助手') {
|
||||
contentInput.value = '';
|
||||
contentInput.focus();
|
||||
await sendToChatBot(content);
|
||||
return;
|
||||
}
|
||||
|
||||
submitBtn.disabled = true;
|
||||
|
||||
try {
|
||||
@@ -931,4 +986,133 @@
|
||||
fishingTimer = null;
|
||||
fishingReelTimeout = null;
|
||||
}
|
||||
|
||||
// ── AI 聊天机器人 ──────────────────────────────────
|
||||
let chatBotSending = false;
|
||||
|
||||
/**
|
||||
* 发送消息给 AI 机器人
|
||||
* 先在包厢窗口显示用户消息,再调用 API 获取回复
|
||||
*/
|
||||
async function sendToChatBot(content) {
|
||||
if (chatBotSending) {
|
||||
alert('AI 正在思考中,请稍候...');
|
||||
return;
|
||||
}
|
||||
chatBotSending = true;
|
||||
|
||||
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 userDiv = document.createElement('div');
|
||||
userDiv.className = 'msg-line';
|
||||
userDiv.innerHTML = `<span class="msg-user" style="color: #000099;">${window.chatContext.username}</span>` +
|
||||
`对<span style="color: #16a34a; font-weight: bold;">🤖AI小助手</span>说:` +
|
||||
`<span class="msg-content">${escapeHtml(content)}</span>` +
|
||||
` <span class="msg-time">(${timeStr})</span>`;
|
||||
container2.appendChild(userDiv);
|
||||
|
||||
// 显示"思考中"提示
|
||||
const thinkDiv = document.createElement('div');
|
||||
thinkDiv.className = 'msg-line';
|
||||
thinkDiv.innerHTML = '<span style="color: #16a34a;">🤖 <b>AI小助手</b> 正在思考中...</span>';
|
||||
container2.appendChild(thinkDiv);
|
||||
if (autoScroll) container2.scrollTop = container2.scrollHeight;
|
||||
|
||||
try {
|
||||
const res = await fetch(window.chatContext.chatBotUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
|
||||
'content'),
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: content,
|
||||
room_id: window.chatContext.roomId
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
// 移除"思考中"提示
|
||||
thinkDiv.remove();
|
||||
|
||||
const replyTime = new Date();
|
||||
const replyTimeStr = replyTime.getHours().toString().padStart(2, '0') + ':' +
|
||||
replyTime.getMinutes().toString().padStart(2, '0') + ':' +
|
||||
replyTime.getSeconds().toString().padStart(2, '0');
|
||||
|
||||
if (res.ok && data.status === 'success') {
|
||||
// 显示机器人回复
|
||||
const botDiv = document.createElement('div');
|
||||
botDiv.className = 'msg-line';
|
||||
botDiv.style.background = '#f0fdf4';
|
||||
botDiv.style.borderLeft = '3px solid #22c55e';
|
||||
botDiv.style.padding = '4px 8px';
|
||||
botDiv.style.margin = '2px 0';
|
||||
botDiv.style.borderRadius = '4px';
|
||||
botDiv.innerHTML = `<span style="color: #16a34a; font-weight: bold;">🤖 AI小助手</span>` +
|
||||
`对<span class="msg-user" style="color: #000099;">${window.chatContext.username}</span>说:` +
|
||||
`<span class="msg-content" style="color: #333;">${escapeHtml(data.reply)}</span>` +
|
||||
` <span class="msg-time">(${replyTimeStr})</span>` +
|
||||
` <span style="font-size:10px; color:#aaa;">[${data.provider}]</span>`;
|
||||
container2.appendChild(botDiv);
|
||||
} else {
|
||||
// 显示错误信息
|
||||
const errDiv = document.createElement('div');
|
||||
errDiv.className = 'msg-line';
|
||||
errDiv.innerHTML = `<span style="color: #dc2626;">🤖【AI小助手】${data.message || '回复失败,请稍后重试'}</span>` +
|
||||
` <span class="msg-time">(${replyTimeStr})</span>`;
|
||||
container2.appendChild(errDiv);
|
||||
}
|
||||
} catch (e) {
|
||||
thinkDiv.remove();
|
||||
const errDiv = document.createElement('div');
|
||||
errDiv.className = 'msg-line';
|
||||
errDiv.innerHTML = '<span style="color: #dc2626;">🤖【AI小助手】网络连接错误,请稍后重试</span>';
|
||||
container2.appendChild(errDiv);
|
||||
}
|
||||
|
||||
chatBotSending = false;
|
||||
if (autoScroll) container2.scrollTop = container2.scrollHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除与 AI 小助手的对话上下文
|
||||
*/
|
||||
async function clearChatBotContext() {
|
||||
try {
|
||||
const res = await fetch(window.chatContext.chatBotClearUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
|
||||
'content'),
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
const sysDiv = document.createElement('div');
|
||||
sysDiv.className = 'msg-line';
|
||||
sysDiv.innerHTML = '<span style="color: #16a34a;">🤖【系统】' + (data.message || '对话已重置') + '</span>';
|
||||
container2.appendChild(sysDiv);
|
||||
if (autoScroll) container2.scrollTop = container2.scrollHeight;
|
||||
} catch (e) {
|
||||
alert('清除失败:' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML 转义函数,防止 XSS
|
||||
*/
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user