重构:聊天室所有 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
DEVELOPMENT.md
168
DEVELOPMENT.md
@@ -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
|
||||
|
||||
@@ -498,7 +498,7 @@
|
||||
|
||||
window.addEventListener('chat:kicked', (e) => {
|
||||
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') }}";
|
||||
}
|
||||
});
|
||||
@@ -1107,7 +1107,7 @@
|
||||
type
|
||||
}),
|
||||
}).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));
|
||||
}
|
||||
window.triggerEffect = triggerEffect;
|
||||
@@ -1238,10 +1238,10 @@
|
||||
contentInput.value = '';
|
||||
contentInput.focus();
|
||||
} else {
|
||||
alert('发送失败: ' + (data.message || JSON.stringify(data.errors)));
|
||||
window.chatDialog.alert('发送失败: ' + (data.message || JSON.stringify(data.errors)), '操作失败', '#cc4444');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('网络连接错误,消息发送失败!');
|
||||
window.chatDialog.alert('网络连接错误,消息发送失败!', '网络错误', '#cc4444');
|
||||
console.error(error);
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
@@ -1273,12 +1273,12 @@
|
||||
if (data.status === 'success') {
|
||||
const marquee = document.getElementById('announcement-text');
|
||||
if (marquee) marquee.textContent = newText.trim();
|
||||
alert('公告已更新!');
|
||||
window.chatDialog.alert('公告已更新!', '提示', '#16a34a');
|
||||
} else {
|
||||
alert(data.message || '更新失败');
|
||||
window.chatDialog.alert(data.message || '更新失败', '操作失败', '#cc4444');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('设置公告失败:' + e.message);
|
||||
window.chatDialog.alert('设置公告失败:' + e.message, '操作失败', '#cc4444');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1303,10 +1303,10 @@
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok || data.status !== 'success') {
|
||||
alert(data.message || '发送失败');
|
||||
window.chatDialog.alert(data.message || '发送失败', '操作失败', '#cc4444');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('发送失败:' + e.message);
|
||||
window.chatDialog.alert('发送失败:' + e.message, '操作失败', '#cc4444');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1329,10 +1329,10 @@
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok || data.status !== 'success') {
|
||||
alert(data.message || '清屏失败');
|
||||
window.chatDialog.alert(data.message || '清屏失败', '操作失败', '#cc4444');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('清屏失败:' + e.message);
|
||||
window.chatDialog.alert('清屏失败:' + e.message, '操作失败', '#cc4444');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1454,7 +1454,7 @@
|
||||
|
||||
// 检测登录态失效
|
||||
if (response.status === 401 || response.status === 419) {
|
||||
alert('⚠️ 您的登录已失效(可能超时或在其他设备登录),请重新登录。');
|
||||
window.chatDialog.alert('⚠️ 您的登录已失效(可能超时或在其他设备登录),请重新登录。', '连接警告', '#b45309');
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
@@ -1510,7 +1510,7 @@
|
||||
heartbeatFailCount++;
|
||||
|
||||
if (heartbeatFailCount >= MAX_HEARTBEAT_FAILS) {
|
||||
alert('⚠️ 与服务器的连接已断开,请检查网络后重新登录。');
|
||||
window.chatDialog.alert('⚠️ 与服务器的连接已断开,请检查网络后重新登录。', '连接警告', '#b45309');
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
@@ -1617,7 +1617,7 @@
|
||||
const data = await res.json();
|
||||
|
||||
if (data.status === 'success') {
|
||||
alert('头像修改成功!');
|
||||
window.chatDialog.alert('头像修改成功!', '提示', '#16a34a');
|
||||
const myName = window.chatContext.username;
|
||||
if (onlineUsers[myName]) {
|
||||
onlineUsers[myName].headface = data.headface;
|
||||
@@ -1625,10 +1625,10 @@
|
||||
renderUserList();
|
||||
closeAvatarPicker();
|
||||
} else {
|
||||
alert(data.message || '修改失败');
|
||||
window.chatDialog.alert(data.message || '修改失败', '操作失败', '#cc4444');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('网络错误');
|
||||
window.chatDialog.alert('网络错误', '网络异常', '#cc4444');
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
@@ -1656,7 +1656,7 @@
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok || data.status !== 'success') {
|
||||
alert(data.message || '钓鱼失败');
|
||||
window.chatDialog.alert(data.message || '钓鱼失败', '操作失败', '#cc4444');
|
||||
btn.disabled = false;
|
||||
return;
|
||||
}
|
||||
@@ -1704,7 +1704,7 @@
|
||||
}, data.wait_time * 1000);
|
||||
|
||||
} catch (e) {
|
||||
alert('网络错误:' + e.message);
|
||||
window.chatDialog.alert('网络错误:' + e.message, '网络异常', '#cc4444');
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
@@ -1757,7 +1757,7 @@
|
||||
}
|
||||
if (autoScroll) container2.scrollTop = container2.scrollHeight;
|
||||
} catch (e) {
|
||||
alert('网络错误:' + e.message);
|
||||
window.chatDialog.alert('网络错误:' + e.message, '网络异常', '#cc4444');
|
||||
}
|
||||
|
||||
resetFishingBtn();
|
||||
@@ -1784,7 +1784,7 @@
|
||||
*/
|
||||
async function sendToChatBot(content) {
|
||||
if (chatBotSending) {
|
||||
alert('AI 正在思考中,请稍候...');
|
||||
window.chatDialog.alert('AI 正在思考中,请稍候...', '提示', '#336699');
|
||||
return;
|
||||
}
|
||||
chatBotSending = true;
|
||||
@@ -1859,7 +1859,7 @@
|
||||
container2.appendChild(sysDiv);
|
||||
if (autoScroll) container2.scrollTop = container2.scrollHeight;
|
||||
} catch (e) {
|
||||
alert('清除失败:' + e.message);
|
||||
window.chatDialog.alert('清除失败:' + e.message, '操作失败', '#cc4444');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user