重构:聊天室所有 alert() 改为 window.chatDialog.alert()
scripts.blade.php 全部 21 处原生 alert() 替换: - 成功类 → chatDialog.alert(..., '提示', '#16a34a') - 失败/错误类 → chatDialog.alert(..., '操作失败', '#cc4444') - 网络异常类 → chatDialog.alert(..., '网络异常', '#cc4444') - 连接断开/踢出 → chatDialog.alert(..., '连接警告', '#b45309') - 一般提示 → chatDialog.alert(..., '提示', '#336699') DEVELOPMENT.md 新增 §7.9 window.chatBanner 使用文档
This commit is contained in:
+168
@@ -711,6 +711,174 @@ if (ok) {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 7.9 全局大卡片通知 `window.chatBanner` ⭐
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> `chatBanner` 是居中弹出的沉浸式大卡片通知组件,适用于好友通知、任命公告、系统广播等重要事件。与 `chatDialog` 不同,它**不阻断操作流程**,支持自动消失和自定义按钮。
|
||||||
|
|
||||||
|
#### 文件位置
|
||||||
|
|
||||||
|
```
|
||||||
|
resources/views/chat/partials/scripts.blade.php ← chatBanner 定义(window.chatBanner)
|
||||||
|
app/Events/BannerNotification.php ← 广播事件(后端推送)
|
||||||
|
app/Http/Controllers/Admin/BannerBroadcastController.php ← 管理员推送接口
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 前端 API
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
window.chatBanner.show(options); // 显示大卡片
|
||||||
|
window.chatBanner.close(id); // 关闭指定 banner
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `show(options)` 参数说明
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 | 默认值 |
|
||||||
|
| ------------ | ---------- | ------------------------------------------- | --------------------------------- |
|
||||||
|
| `id` | `string` | 可选,banner DOM ID,同 ID 自动覆盖防重叠 | `'chat-banner-default'` |
|
||||||
|
| `icon` | `string` | 顶部 Emoji 图标 | 无 |
|
||||||
|
| `title` | `string` | 小标题(显示为 `══ 标题 ══`) | 无 |
|
||||||
|
| `name` | `string` | 大名字/主角行(自动 HTML 转义) | 无 |
|
||||||
|
| `body` | `string` | 主内容,支持有限 HTML(`<b><span><br>` 等) | 无 |
|
||||||
|
| `sub` | `string` | 副说明(小字,支持有限 HTML) | 无 |
|
||||||
|
| `gradient` | `string[]` | 渐变色数组,如 `['#4f46e5', '#db2777']` | `['#4f46e5','#7c3aed','#db2777']` |
|
||||||
|
| `titleColor` | `string` | 小标题字体颜色 | `'#fde68a'` |
|
||||||
|
| `autoClose` | `number` | 自动关闭时间(ms),`0` = 不自动关闭 | `5000` |
|
||||||
|
| `buttons` | `Button[]` | 按钮列表(见下) | 无(无按钮时不可点击) |
|
||||||
|
|
||||||
|
`buttons` 数组元素:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
label: string, // 按钮文字
|
||||||
|
color: string, // 背景色(CSS 颜色值)
|
||||||
|
onClick: (btn: HTMLElement, close: () => void) => void, // 点击回调
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ① 简单通知(5秒自动消失)
|
||||||
|
window.chatBanner.show({
|
||||||
|
icon: "🎉💚🎉",
|
||||||
|
title: "好友通知",
|
||||||
|
name: "lkddi1",
|
||||||
|
body: "将你加为好友了!",
|
||||||
|
sub: '<strong style="color:#a7f3d0;">你们现在互为好友 🎊</strong>',
|
||||||
|
gradient: ["#065f46", "#059669", "#10b981"],
|
||||||
|
titleColor: "#a7f3d0",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ② 带操作按钮(不自动关闭)
|
||||||
|
window.chatBanner.show({
|
||||||
|
id: "friend-add-banner",
|
||||||
|
icon: "💚📩",
|
||||||
|
title: "好友申请",
|
||||||
|
name: "lkddi1",
|
||||||
|
body: "将你加为好友了!",
|
||||||
|
sub: "但你还没有回加对方为好友",
|
||||||
|
gradient: ["#1e3a5f", "#1d4ed8", "#0891b2"],
|
||||||
|
titleColor: "#bae6fd",
|
||||||
|
autoClose: 0, // 不自动关闭,等待用户操作
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: "➕ 回加好友",
|
||||||
|
color: "#10b981",
|
||||||
|
onClick: (btn, close) => {
|
||||||
|
btn.textContent = "处理中…";
|
||||||
|
// ... 调用 API ...
|
||||||
|
close(); // 完成后手动关闭
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "稍后再说",
|
||||||
|
color: "rgba(255,255,255,0.15)",
|
||||||
|
onClick: (btn, close) => close(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// ③ 手动关闭指定 banner
|
||||||
|
window.chatBanner.close("friend-add-banner");
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 后端推送(管理员)
|
||||||
|
|
||||||
|
通过 `BannerNotification` 事件向**单个用户**或**整个房间**广播大卡片:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use App\Events\BannerNotification;
|
||||||
|
|
||||||
|
// 推给单个用户(私有频道 user.{username})
|
||||||
|
broadcast(new BannerNotification(
|
||||||
|
target: 'user',
|
||||||
|
targetId: 'lkddi',
|
||||||
|
options: [
|
||||||
|
'icon' => '📢',
|
||||||
|
'title' => '系统通知',
|
||||||
|
'body' => '你有一条新消息',
|
||||||
|
'gradient' => ['#4f46e5', '#7c3aed'],
|
||||||
|
'autoClose' => 6000,
|
||||||
|
]
|
||||||
|
));
|
||||||
|
|
||||||
|
// 推给整个房间(Presence 频道 room.{id})
|
||||||
|
broadcast(new BannerNotification(
|
||||||
|
target: 'room',
|
||||||
|
targetId: 1,
|
||||||
|
options: [
|
||||||
|
'icon' => '🎊',
|
||||||
|
'title' => '活动公告',
|
||||||
|
'body' => '双倍积分活动已开始!',
|
||||||
|
'gradient' => ['#065f46', '#059669'],
|
||||||
|
'autoClose' => 8000,
|
||||||
|
]
|
||||||
|
));
|
||||||
|
```
|
||||||
|
|
||||||
|
也可通过管理员 HTTP 接口(仅超级管理员):
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /admin/banner/broadcast
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"target": "room",
|
||||||
|
"target_id": 1,
|
||||||
|
"options": {
|
||||||
|
"icon": "📢",
|
||||||
|
"title": "公告",
|
||||||
|
"body": "服务器将于 10 分钟后重启",
|
||||||
|
"gradient": ["#374151", "#4b5563"],
|
||||||
|
"autoClose": 10000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 渐变配色速查
|
||||||
|
|
||||||
|
| 场景 | 渐变数组 |
|
||||||
|
| ----------- | --------------------------------------------- |
|
||||||
|
| 好友 / 成功 | `['#065f46', '#059669', '#10b981']`(绿色) |
|
||||||
|
| 申请 / 信息 | `['#1e3a5f', '#1d4ed8', '#0891b2']`(蓝色) |
|
||||||
|
| 任命 / 荣耀 | `['#4f46e5', '#7c3aed', '#db2777']`(紫粉) |
|
||||||
|
| 撤销 / 中性 | `['#374151', '#4b5563', '#6b7280']`(灰色) |
|
||||||
|
| 警告 / 紧急 | `['#7f1d1d', '#991b1b', '#dc2626']`(红色) |
|
||||||
|
| 系统 / 特权 | `['#1e1b4b', '#312e81', '#4338ca']`(深蓝紫) |
|
||||||
|
|
||||||
|
#### 安全说明
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
>
|
||||||
|
> - **前端控制台调用** `window.chatBanner.show()` 只影响**用户自己的浏览器**,无法推送给他人。
|
||||||
|
> - **HTTP 推送接口** `POST /admin/banner/broadcast` 受三层中间件保护(`chat.auth` + `chat.has_position` + `chat.level:super`),普通用户调用返回 403。
|
||||||
|
> - **内容净化**:`body`、`sub`、`title` 字段经过 `strip_tags` 白名单处理(允许 `<b><strong><em><span><br>`),防止 XSS。
|
||||||
|
> - **按钮 action 白名单**:仅允许 `close | add_friend | remove_friend | link`,禁止任意 JS 注入。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 八、常用命令速查
|
## 八、常用命令速查
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -498,7 +498,7 @@
|
|||||||
|
|
||||||
window.addEventListener('chat:kicked', (e) => {
|
window.addEventListener('chat:kicked', (e) => {
|
||||||
if (e.detail.username === window.chatContext.username) {
|
if (e.detail.username === window.chatContext.username) {
|
||||||
alert("您已被管理员踢出房间!" + (e.detail.reason ? "\n原因:" + e.detail.reason : ""));
|
window.chatDialog.alert('您已被管理员踢出房间!' + (e.detail.reason ? ' 原因:' + e.detail.reason : ''), '系统通知', '#cc4444');
|
||||||
window.location.href = "{{ route('rooms.index') }}";
|
window.location.href = "{{ route('rooms.index') }}";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1107,7 +1107,7 @@
|
|||||||
type
|
type
|
||||||
}),
|
}),
|
||||||
}).then(r => r.json()).then(data => {
|
}).then(r => r.json()).then(data => {
|
||||||
if (data.status !== 'success') alert(data.message);
|
if (data.status !== 'success') window.chatDialog.alert(data.message, '操作失败', '#cc4444');
|
||||||
}).catch(err => console.error('特效触发失败:', err));
|
}).catch(err => console.error('特效触发失败:', err));
|
||||||
}
|
}
|
||||||
window.triggerEffect = triggerEffect;
|
window.triggerEffect = triggerEffect;
|
||||||
@@ -1238,10 +1238,10 @@
|
|||||||
contentInput.value = '';
|
contentInput.value = '';
|
||||||
contentInput.focus();
|
contentInput.focus();
|
||||||
} else {
|
} else {
|
||||||
alert('发送失败: ' + (data.message || JSON.stringify(data.errors)));
|
window.chatDialog.alert('发送失败: ' + (data.message || JSON.stringify(data.errors)), '操作失败', '#cc4444');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('网络连接错误,消息发送失败!');
|
window.chatDialog.alert('网络连接错误,消息发送失败!', '网络错误', '#cc4444');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
@@ -1273,12 +1273,12 @@
|
|||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
const marquee = document.getElementById('announcement-text');
|
const marquee = document.getElementById('announcement-text');
|
||||||
if (marquee) marquee.textContent = newText.trim();
|
if (marquee) marquee.textContent = newText.trim();
|
||||||
alert('公告已更新!');
|
window.chatDialog.alert('公告已更新!', '提示', '#16a34a');
|
||||||
} else {
|
} else {
|
||||||
alert(data.message || '更新失败');
|
window.chatDialog.alert(data.message || '更新失败', '操作失败', '#cc4444');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('设置公告失败:' + e.message);
|
window.chatDialog.alert('设置公告失败:' + e.message, '操作失败', '#cc4444');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1303,10 +1303,10 @@
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok || data.status !== 'success') {
|
if (!res.ok || data.status !== 'success') {
|
||||||
alert(data.message || '发送失败');
|
window.chatDialog.alert(data.message || '发送失败', '操作失败', '#cc4444');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('发送失败:' + e.message);
|
window.chatDialog.alert('发送失败:' + e.message, '操作失败', '#cc4444');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1329,10 +1329,10 @@
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok || data.status !== 'success') {
|
if (!res.ok || data.status !== 'success') {
|
||||||
alert(data.message || '清屏失败');
|
window.chatDialog.alert(data.message || '清屏失败', '操作失败', '#cc4444');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('清屏失败:' + e.message);
|
window.chatDialog.alert('清屏失败:' + e.message, '操作失败', '#cc4444');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1454,7 +1454,7 @@
|
|||||||
|
|
||||||
// 检测登录态失效
|
// 检测登录态失效
|
||||||
if (response.status === 401 || response.status === 419) {
|
if (response.status === 401 || response.status === 419) {
|
||||||
alert('⚠️ 您的登录已失效(可能超时或在其他设备登录),请重新登录。');
|
window.chatDialog.alert('⚠️ 您的登录已失效(可能超时或在其他设备登录),请重新登录。', '连接警告', '#b45309');
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1510,7 +1510,7 @@
|
|||||||
heartbeatFailCount++;
|
heartbeatFailCount++;
|
||||||
|
|
||||||
if (heartbeatFailCount >= MAX_HEARTBEAT_FAILS) {
|
if (heartbeatFailCount >= MAX_HEARTBEAT_FAILS) {
|
||||||
alert('⚠️ 与服务器的连接已断开,请检查网络后重新登录。');
|
window.chatDialog.alert('⚠️ 与服务器的连接已断开,请检查网络后重新登录。', '连接警告', '#b45309');
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1617,7 +1617,7 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
alert('头像修改成功!');
|
window.chatDialog.alert('头像修改成功!', '提示', '#16a34a');
|
||||||
const myName = window.chatContext.username;
|
const myName = window.chatContext.username;
|
||||||
if (onlineUsers[myName]) {
|
if (onlineUsers[myName]) {
|
||||||
onlineUsers[myName].headface = data.headface;
|
onlineUsers[myName].headface = data.headface;
|
||||||
@@ -1625,10 +1625,10 @@
|
|||||||
renderUserList();
|
renderUserList();
|
||||||
closeAvatarPicker();
|
closeAvatarPicker();
|
||||||
} else {
|
} else {
|
||||||
alert(data.message || '修改失败');
|
window.chatDialog.alert(data.message || '修改失败', '操作失败', '#cc4444');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('网络错误');
|
window.chatDialog.alert('网络错误', '网络异常', '#cc4444');
|
||||||
}
|
}
|
||||||
|
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
@@ -1656,7 +1656,7 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (!res.ok || data.status !== 'success') {
|
if (!res.ok || data.status !== 'success') {
|
||||||
alert(data.message || '钓鱼失败');
|
window.chatDialog.alert(data.message || '钓鱼失败', '操作失败', '#cc4444');
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1704,7 +1704,7 @@
|
|||||||
}, data.wait_time * 1000);
|
}, data.wait_time * 1000);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('网络错误:' + e.message);
|
window.chatDialog.alert('网络错误:' + e.message, '网络异常', '#cc4444');
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1757,7 +1757,7 @@
|
|||||||
}
|
}
|
||||||
if (autoScroll) container2.scrollTop = container2.scrollHeight;
|
if (autoScroll) container2.scrollTop = container2.scrollHeight;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('网络错误:' + e.message);
|
window.chatDialog.alert('网络错误:' + e.message, '网络异常', '#cc4444');
|
||||||
}
|
}
|
||||||
|
|
||||||
resetFishingBtn();
|
resetFishingBtn();
|
||||||
@@ -1784,7 +1784,7 @@
|
|||||||
*/
|
*/
|
||||||
async function sendToChatBot(content) {
|
async function sendToChatBot(content) {
|
||||||
if (chatBotSending) {
|
if (chatBotSending) {
|
||||||
alert('AI 正在思考中,请稍候...');
|
window.chatDialog.alert('AI 正在思考中,请稍候...', '提示', '#336699');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chatBotSending = true;
|
chatBotSending = true;
|
||||||
@@ -1859,7 +1859,7 @@
|
|||||||
container2.appendChild(sysDiv);
|
container2.appendChild(sysDiv);
|
||||||
if (autoScroll) container2.scrollTop = container2.scrollHeight;
|
if (autoScroll) container2.scrollTop = container2.scrollHeight;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('清除失败:' + e.message);
|
window.chatDialog.alert('清除失败:' + e.message, '操作失败', '#cc4444');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user