重构:全局自定义弹窗系统 window.chatDialog
- 新增 chat/partials/global-dialog.blade.php(全局弹窗 HTML + JS) - 提供 chatDialog.alert() 和 chatDialog.confirm() 两个异步 API - Alpine.js userCardComponent 的 $alert/$confirm 代理到全局 API - toolbar 离开按钮统一改用 chatDialog.confirm(),移除独立 leave-confirm-modal - 支持动态标题颜色、淡入动画,兼容 Chrome/Edge/Firefox
This commit is contained in:
@@ -95,6 +95,9 @@
|
|||||||
@include('chat.partials.right-panel')
|
@include('chat.partials.right-panel')
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- ═══════════ 全局自定义弹窗(替代原生 alert/confirm,全页面可用) ═══════════ --}}
|
||||||
|
@include('chat.partials.global-dialog')
|
||||||
|
|
||||||
{{-- ═══════════ 聊天室交互脚本(独立文件维护) ═══════════ --}}
|
{{-- ═══════════ 聊天室交互脚本(独立文件维护) ═══════════ --}}
|
||||||
@include('chat.partials.user-actions')
|
@include('chat.partials.user-actions')
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
{{--
|
||||||
|
文件功能:全局自定义弹窗组件(替代原生 alert / confirm)
|
||||||
|
|
||||||
|
提供全局 JS API:
|
||||||
|
- window.chatDialog.alert(message, title?, color?) → Promise<void>
|
||||||
|
- window.chatDialog.confirm(message, title?, color?) → Promise<boolean>
|
||||||
|
|
||||||
|
任何 JS 代码(Alpine.js 组件、toolbar、scripts 等)均可直接调用,
|
||||||
|
无需使用浏览器原生弹窗,避免 Chrome/Edge 兼容性问题。
|
||||||
|
|
||||||
|
@author ChatRoom Laravel
|
||||||
|
@version 1.0.0
|
||||||
|
--}}
|
||||||
|
|
||||||
|
{{-- ─── 全局弹窗遮罩 ─── --}}
|
||||||
|
<div id="global-dialog-modal"
|
||||||
|
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.5);
|
||||||
|
z-index:999999; justify-content:center; align-items:center;">
|
||||||
|
<div id="global-dialog-box"
|
||||||
|
style="background:#fff; border-radius:10px; width:320px; max-width:90vw;
|
||||||
|
box-shadow:0 12px 40px rgba(0,0,0,.35); overflow:hidden;
|
||||||
|
animation:gdSlideIn .18s ease;">
|
||||||
|
|
||||||
|
{{-- 标题栏(颜色由 JS 动态设置) --}}
|
||||||
|
<div id="global-dialog-header" style="padding:13px 18px; font-size:14px; font-weight:bold; color:#fff;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- 内容区 --}}
|
||||||
|
<div id="global-dialog-message"
|
||||||
|
style="padding:18px 18px 14px; font-size:13px; color:#374151;
|
||||||
|
line-height:1.6; word-break:break-word;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- 按钮区 --}}
|
||||||
|
<div style="display:flex; gap:10px; padding:0 18px 16px;">
|
||||||
|
<button id="global-dialog-cancel-btn" onclick="window.chatDialog._cancel()"
|
||||||
|
style="flex:1; padding:9px; background:#f3f4f6; color:#555;
|
||||||
|
border:1px solid #d1d5db; border-radius:6px;
|
||||||
|
font-size:13px; cursor:pointer; transition:background .15s;">
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button id="global-dialog-confirm-btn" onclick="window.chatDialog._confirm()"
|
||||||
|
style="flex:1; padding:9px; color:#fff; border:none;
|
||||||
|
border-radius:6px; font-size:13px; font-weight:bold;
|
||||||
|
cursor:pointer; transition:opacity .15s;">
|
||||||
|
确定
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@keyframes gdSlideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(.92) translateY(-8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#global-dialog-cancel-btn:hover {
|
||||||
|
background: #e5e7eb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#global-dialog-confirm-btn:hover {
|
||||||
|
opacity: .88;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 全局弹窗工具,提供 alert / confirm 两种模式。
|
||||||
|
* 替代浏览器原生弹窗,兼容 Chrome/Edge/Safari。
|
||||||
|
*
|
||||||
|
* 用法:
|
||||||
|
* await window.chatDialog.alert('操作成功!', '提示', '#16a34a');
|
||||||
|
* const yes = await window.chatDialog.confirm('确定要删除吗?', '请确认');
|
||||||
|
* if (yes) { ... }
|
||||||
|
*/
|
||||||
|
window.chatDialog = (function() {
|
||||||
|
let _resolve = null;
|
||||||
|
|
||||||
|
const $ = id => document.getElementById(id);
|
||||||
|
|
||||||
|
/** 打开弹窗内部方法 */
|
||||||
|
function _open({
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
color,
|
||||||
|
type
|
||||||
|
}) {
|
||||||
|
$('global-dialog-header').textContent = title;
|
||||||
|
$('global-dialog-header').style.background = color;
|
||||||
|
$('global-dialog-message').textContent = message;
|
||||||
|
$('global-dialog-confirm-btn').style.background = color;
|
||||||
|
|
||||||
|
// 撤销/alert 模式隐藏取消按钮
|
||||||
|
const cancelBtn = $('global-dialog-cancel-btn');
|
||||||
|
cancelBtn.style.display = type === 'confirm' ? '' : 'none';
|
||||||
|
|
||||||
|
// 调整确认按钮宽度
|
||||||
|
$('global-dialog-confirm-btn').style.flex = type === 'confirm' ? '1' : '1 1 100%';
|
||||||
|
|
||||||
|
$('global-dialog-modal').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* 显示提示弹窗(替代 alert)。
|
||||||
|
*
|
||||||
|
* @param {string} message 提示内容
|
||||||
|
* @param {string} title 标题(默认:提示)
|
||||||
|
* @param {string} color 标题栏颜色(默认蓝色)
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
alert(message, title = '提示', color = '#336699') {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
_resolve = resolve;
|
||||||
|
_open({
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
color,
|
||||||
|
type: 'alert'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示确认弹窗(替代 confirm)。
|
||||||
|
*
|
||||||
|
* @param {string} message 确认内容
|
||||||
|
* @param {string} title 标题(默认:请确认)
|
||||||
|
* @param {string} color 标题栏颜色(默认红色)
|
||||||
|
* @return {Promise<boolean>} 用户点确定返回 true,取消返回 false
|
||||||
|
*/
|
||||||
|
confirm(message, title = '请确认', color = '#cc4444') {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
_resolve = resolve;
|
||||||
|
_open({
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
color,
|
||||||
|
type: 'confirm'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 点确定按钮 */
|
||||||
|
_confirm() {
|
||||||
|
_resolve?.(true);
|
||||||
|
this._hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 点取消按钮 */
|
||||||
|
_cancel() {
|
||||||
|
_resolve?.(false);
|
||||||
|
this._hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 关闭弹窗 */
|
||||||
|
_hide() {
|
||||||
|
$('global-dialog-modal').style.display = 'none';
|
||||||
|
_resolve = null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// 点击遮罩层关闭(等同点取消)
|
||||||
|
document.getElementById('global-dialog-modal').addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
window.chatDialog._cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -35,32 +35,9 @@
|
|||||||
<div class="tool-btn" onclick="window.open('{{ route('leaderboard.index') }}', '_blank')" title="排行榜">排行
|
<div class="tool-btn" onclick="window.open('{{ route('leaderboard.index') }}', '_blank')" title="排行榜">排行
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div class="tool-btn" style="color: #ffaaaa;" onclick="showLeaveConfirm()" title="离开聊天室">离开
|
<div class="tool-btn" style="color: #ffaaaa;"
|
||||||
</div>
|
onclick="window.chatDialog.confirm('确定要离开聊天室吗?', '离开聊天室').then(ok => { if (ok) leaveRoom(); })" title="离开聊天室">
|
||||||
</div>
|
离开
|
||||||
|
|
||||||
{{-- ═══════════ 离开确认弹窗(自定义,避免 Chrome 原生 confirm 冲突)═══════════ --}}
|
|
||||||
<div id="leave-confirm-modal"
|
|
||||||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.5); z-index:99999;
|
|
||||||
justify-content:center; align-items:center;">
|
|
||||||
<div
|
|
||||||
style="background:#fff; border-radius:8px; width:300px; box-shadow:0 8px 32px rgba(0,0,0,.3);
|
|
||||||
overflow:hidden;">
|
|
||||||
<div
|
|
||||||
style="background:linear-gradient(135deg,#cc4444,#e06060); color:#fff;
|
|
||||||
padding:12px 16px; font-size:14px; font-weight:bold;">
|
|
||||||
⚠️ 离开聊天室</div>
|
|
||||||
<div style="padding:20px 16px; font-size:13px; color:#444; text-align:center;">
|
|
||||||
确定要离开聊天室吗?
|
|
||||||
</div>
|
|
||||||
<div style="display:flex; gap:10px; padding:0 16px 16px;">
|
|
||||||
<button onclick="hideLeaveConfirm()"
|
|
||||||
style="flex:1; padding:8px; background:#eee; color:#555; border:1px solid #ccc;
|
|
||||||
border-radius:4px; font-size:13px; cursor:pointer;">取消</button>
|
|
||||||
<button onclick="hideLeaveConfirm(); leaveRoom();"
|
|
||||||
style="flex:1; padding:8px; background:#cc4444; color:#fff; border:none;
|
|
||||||
border-radius:4px; font-size:13px; font-weight:bold; cursor:pointer;">确定离开</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -340,20 +317,6 @@
|
|||||||
btn.style.cursor = 'pointer';
|
btn.style.cursor = 'pointer';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/** 显示离开确认弹窗(替代原生 confirm,避免 Chrome 弹窗冲突) */
|
|
||||||
function showLeaveConfirm() {
|
|
||||||
document.getElementById('leave-confirm-modal').style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 隐藏离开确认弹窗 */
|
|
||||||
function hideLeaveConfirm() {
|
|
||||||
document.getElementById('leave-confirm-modal').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 点遮罩关闭
|
|
||||||
document.getElementById('leave-confirm-modal').addEventListener('click', function(e) {
|
|
||||||
if (e.target === this) hideLeaveConfirm();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{-- ═══════════ 商店弹窗 ═══════════ --}}
|
{{-- ═══════════ 商店弹窗 ═══════════ --}}
|
||||||
|
|||||||
@@ -103,6 +103,10 @@
|
|||||||
showPositionHistory: false, // 职务履历
|
showPositionHistory: false, // 职务履历
|
||||||
showAdminPanel: false, // 管理操作(管理操作+职务操作合并)
|
showAdminPanel: false, // 管理操作(管理操作+职务操作合并)
|
||||||
|
|
||||||
|
// 自定义弹窗:直接代理到全局 window.chatDialog
|
||||||
|
$alert: (...args) => window.chatDialog.alert(...args),
|
||||||
|
$confirm: (...args) => window.chatDialog.confirm(...args),
|
||||||
|
|
||||||
/** 获取用户资料 */
|
/** 获取用户资料 */
|
||||||
async fetchUser(username) {
|
async fetchUser(username) {
|
||||||
try {
|
try {
|
||||||
@@ -176,19 +180,28 @@
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
alert(data.message);
|
const ok = data.status === 'success';
|
||||||
if (data.status === 'success') {
|
this.$alert(
|
||||||
|
data.message,
|
||||||
|
ok ? '任命成功 ✨' : '操作失败',
|
||||||
|
ok ? '#16a34a' : '#cc4444'
|
||||||
|
);
|
||||||
|
if (ok) {
|
||||||
this.showUserModal = false;
|
this.showUserModal = false;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('网络异常');
|
this.$alert('网络异常,请稍后重试', '错误', '#cc4444');
|
||||||
}
|
}
|
||||||
this.appointLoading = false;
|
this.appointLoading = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 快速撤销 */
|
/** 快速撤销 */
|
||||||
async doRevoke() {
|
async doRevoke() {
|
||||||
if (!confirm('确定要撤销 ' + this.userInfo.username + ' 的职务吗?')) return;
|
const ok = await this.$confirm(
|
||||||
|
'确定要撤销 「' + this.userInfo.username + '」 的职务吗?撤销后将不再拥有相关权限。',
|
||||||
|
'撤销职务'
|
||||||
|
);
|
||||||
|
if (!ok) return;
|
||||||
this.appointLoading = true;
|
this.appointLoading = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(window.chatContext.revokeUrl, {
|
const res = await fetch(window.chatContext.revokeUrl, {
|
||||||
@@ -201,12 +214,17 @@
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
alert(data.message);
|
const revOk = data.status === 'success';
|
||||||
if (data.status === 'success') {
|
this.$alert(
|
||||||
|
data.message,
|
||||||
|
revOk ? '撤销成功' : '操作失败',
|
||||||
|
revOk ? '#6b7280' : '#cc4444'
|
||||||
|
);
|
||||||
|
if (revOk) {
|
||||||
this.showUserModal = false;
|
this.showUserModal = false;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('网络异常');
|
this.$alert('网络异常', '错误', '#cc4444');
|
||||||
}
|
}
|
||||||
this.appointLoading = false;
|
this.appointLoading = false;
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user