feat: prompt/confirm 替换为自定义弹窗

- shared/utils.js 新增 showConfirm()/showPrompt() 通用函数(inline CSS,无需额外样式)
- admin/common.js 移除重复 showConfirm(),统一使用 utils.js
- admin/posts.js: 3 处 confirm/prompt 替换(审核/退回/锁定)
- admin/comments.js: 1 处 confirm 替换
- settings/index.html: 4 处 confirm + 1 处 prompt 替换
- studio/posts.html + drafts.html: 3 处 confirm 替换
- post-view.js + login.js + post-favorite.js: 3 处 confirm 替换
This commit is contained in:
2026-06-02 20:11:48 +08:00
parent 2e6e7fc057
commit c73b131b83
10 changed files with 318 additions and 243 deletions

View File

@ -783,37 +783,37 @@
showSettingsToast('请输入密码确认', true); showSettingsToast('请输入密码确认', true);
return; return;
} }
if (!confirm('确定要注销账号吗?\n\n注销后 7 天内重新登录可撤销注销,期满后将永久删除。')) { showConfirm('注销账号', '确定要注销账号吗?\n\n注销后 7 天内重新登录可撤销注销,期满后将永久删除。').then(function(ok) {
return; if (!ok) return;
} deleteAccountBtn.disabled = true;
deleteAccountBtn.disabled = true; deleteAccountBtn.textContent = '注销中...';
deleteAccountBtn.textContent = '注销中...';
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/settings/delete-account', true); xhr.open('POST', '/api/settings/delete-account', true);
xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return; if (xhr.readyState !== 4) return;
deleteAccountBtn.disabled = false; deleteAccountBtn.disabled = false;
deleteAccountBtn.textContent = '确认注销'; deleteAccountBtn.textContent = '确认注销';
try { try {
var res = JSON.parse(xhr.responseText); var res = JSON.parse(xhr.responseText);
if (res.success) { if (res.success) {
showSettingsToast(res.message || '账号已注销'); showSettingsToast(res.message || '账号已注销');
setTimeout(function () { setTimeout(function () {
window.location.href = '/'; window.location.href = '/';
}, 2000); }, 2000);
} else { } else {
showSettingsToast(res.message || '操作失败', true); showSettingsToast(res.message || '操作失败', true);
}
} catch (e) {
showSettingsToast('操作失败', true);
} }
} catch (e) { };
showSettingsToast('操作失败', true); xhr.send(JSON.stringify({
} password: deletePwd.value,
}; reason: deleteReason ? deleteReason.value : ''
xhr.send(JSON.stringify({ }));
password: deletePwd.value, });
reason: deleteReason ? deleteReason.value : ''
}));
}); });
} }
@ -829,36 +829,38 @@
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
var sid = btn.getAttribute('data-sid'); var sid = btn.getAttribute('data-sid');
if (!sid) return; if (!sid) return;
if (!confirm('确定要踢出该设备吗?')) return; showConfirm('确认', '确定要踢出该设备吗?').then(function(ok) {
btn.disabled = true; if (!ok) return;
btn.textContent = '踢出中...'; btn.disabled = true;
btn.textContent = '踢出中...';
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('DELETE', '/api/settings/sessions/' + encodeURIComponent(sid), true); xhr.open('DELETE', '/api/settings/sessions/' + encodeURIComponent(sid), true);
xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return; if (xhr.readyState !== 4) return;
btn.disabled = false; btn.disabled = false;
btn.textContent = '踢出'; btn.textContent = '踢出';
try { try {
var res = JSON.parse(xhr.responseText); var res = JSON.parse(xhr.responseText);
if (res.success) { if (res.success) {
showSettingsToast(res.message || '已踢出该设备'); showSettingsToast(res.message || '已踢出该设备');
var item = btn.closest('.session-item'); var item = btn.closest('.session-item');
if (item) { if (item) {
item.style.transition = 'opacity .3s ease, transform .3s ease'; item.style.transition = 'opacity .3s ease, transform .3s ease';
item.style.opacity = '0'; item.style.opacity = '0';
item.style.transform = 'translateX(20px)'; item.style.transform = 'translateX(20px)';
setTimeout(function () { item.remove(); }, 300); setTimeout(function () { item.remove(); }, 300);
}
} else {
showSettingsToast(res.message || '操作失败', true);
} }
} else { } catch (e) {
showSettingsToast(res.message || '操作失败', true); showSettingsToast('操作失败', true);
} }
} catch (e) { };
showSettingsToast('操作失败', true); xhr.send();
} });
};
xhr.send();
}); });
}); });
@ -870,38 +872,40 @@
showSettingsToast('没有其他在线设备'); showSettingsToast('没有其他在线设备');
return; return;
} }
if (!confirm('确定要踢出所有其他设备(共 ' + otherCount + ' 台)吗?')) return; showConfirm('确认', '确定要踢出所有其他设备(共 ' + otherCount + ' 台)吗?').then(function(ok) {
kickOthersBtn.disabled = true; if (!ok) return;
kickOthersBtn.textContent = '处理中...'; kickOthersBtn.disabled = true;
kickOthersBtn.textContent = '处理中...';
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/settings/sessions/destroy-others', true); xhr.open('POST', '/api/settings/sessions/destroy-others', true);
xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return; if (xhr.readyState !== 4) return;
kickOthersBtn.disabled = false; kickOthersBtn.disabled = false;
kickOthersBtn.textContent = '一键踢出其他设备'; kickOthersBtn.textContent = '一键踢出其他设备';
try { try {
var res = JSON.parse(xhr.responseText); var res = JSON.parse(xhr.responseText);
if (res.success) { if (res.success) {
showSettingsToast(res.message || '已踢出所有其他设备'); showSettingsToast(res.message || '已踢出所有其他设备');
var others = document.querySelectorAll('.session-item:not(.session-current)'); var others = document.querySelectorAll('.session-item:not(.session-current)');
others.forEach(function (item, i) { others.forEach(function (item, i) {
setTimeout(function () { setTimeout(function () {
item.style.transition = 'opacity .3s ease, transform .3s ease'; item.style.transition = 'opacity .3s ease, transform .3s ease';
item.style.opacity = '0'; item.style.opacity = '0';
item.style.transform = 'translateX(20px)'; item.style.transform = 'translateX(20px)';
setTimeout(function () { item.remove(); }, 300); setTimeout(function () { item.remove(); }, 300);
}, i * 100); }, i * 100);
}); });
} else { } else {
showSettingsToast(res.message || '操作失败', true); showSettingsToast(res.message || '操作失败', true);
}
} catch (e) {
showSettingsToast('操作失败', true);
} }
} catch (e) { };
showSettingsToast('操作失败', true); xhr.send();
} });
};
xhr.send();
}); });
} }
@ -1056,19 +1060,21 @@
listEl.querySelectorAll('.fav-folder-del').forEach(function (btn) { listEl.querySelectorAll('.fav-folder-del').forEach(function (btn) {
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
var id = btn.getAttribute('data-id'); var id = btn.getAttribute('data-id');
if (!confirm('确定要删除此收藏夹?其中的收藏内容也将被清除。')) return; showConfirm('确认', '确定要删除此收藏夹?其中的收藏内容也将被清除。').then(function(ok) {
api('DELETE', '/api/folders/' + id) if (!ok) return;
.then(function (r) { api('DELETE', '/api/folders/' + id)
if (r.success) { .then(function (r) {
showSettingsToast('收藏夹已删除'); if (r.success) {
loadFolders(); showSettingsToast('收藏夹已删除');
} else { loadFolders();
showSettingsToast(r.message || '删除失败', true); } else {
} showSettingsToast(r.message || '删除失败', true);
}) }
.catch(function () { })
showSettingsToast('删除失败', true); .catch(function () {
}); showSettingsToast('删除失败', true);
});
});
}); });
}); });
}) })
@ -1079,24 +1085,25 @@
if (createBtn) { if (createBtn) {
createBtn.addEventListener('click', function () { createBtn.addEventListener('click', function () {
var name = prompt('请输入收藏夹名称最多50个字符'); showPrompt('请输入收藏夹名称最多50个字符').then(function(name) {
if (!name || !name.trim()) return; if (!name || !name.trim()) return;
if (name.trim().length > 50) { if (name.trim().length > 50) {
showSettingsToast('收藏夹名称不能超过 50 个字符', true); showSettingsToast('收藏夹名称不能超过 50 个字符', true);
return; return;
} }
api('POST', '/api/folders', { name: name.trim(), description: '', is_public: false }) api('POST', '/api/folders', { name: name.trim(), description: '', is_public: false })
.then(function (r) { .then(function (r) {
if (r.success) { if (r.success) {
showSettingsToast('收藏夹已创建'); showSettingsToast('收藏夹已创建');
loadFolders(); loadFolders();
} else { } else {
showSettingsToast(r.message || '创建失败', true); showSettingsToast(r.message || '创建失败', true);
} }
}) })
.catch(function () { .catch(function () {
showSettingsToast('创建失败', true); showSettingsToast('创建失败', true);
}); });
});
}); });
} }

View File

@ -59,42 +59,44 @@
<script nonce="{{.CSPNonce}}"> <script nonce="{{.CSPNonce}}">
function submitPost(id) { function submitPost(id) {
if (!confirm('确定要提交这篇草稿进行审核吗?')) return; showConfirm('确认', '确定要提交这篇草稿进行审核吗?').then(function(ok) {
if (!ok) return;
fetch('/api/studio/posts/' + id + '/submit', { fetch('/api/studio/posts/' + id + '/submit', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRF-Token': getCSRFToken() 'X-CSRF-Token': getCSRFToken()
} }
}) })
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
location.reload(); location.reload();
} else { } else {
alert(data.message || '提交失败'); alert(data.message || '提交失败');
} }
});
}); });
} }
function deletePost(id) { function deletePost(id) {
if (!confirm('确定要删除这篇草稿吗?此操作不可恢复。')) return; showConfirm('确认', '确定要删除这篇草稿吗?此操作不可恢复。').then(function(ok) {
if (!ok) return;
fetch('/api/studio/posts/' + id, { fetch('/api/studio/posts/' + id, {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRF-Token': getCSRFToken() 'X-CSRF-Token': getCSRFToken()
} }
}) })
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
location.reload(); location.reload();
} else { } else {
alert(data.message || '删除失败'); alert(data.message || '删除失败');
} }
});
}); });
} }

View File

@ -80,22 +80,23 @@
<script nonce="{{.CSPNonce}}"> <script nonce="{{.CSPNonce}}">
function deletePost(id) { function deletePost(id) {
if (!confirm('确定要删除这篇文章吗?')) return; showConfirm('确认', '确定要删除这篇文章吗?').then(function(ok) {
if (!ok) return;
fetch('/api/studio/posts/' + id, { fetch('/api/studio/posts/' + id, {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRF-Token': getCSRFToken() 'X-CSRF-Token': getCSRFToken()
} }
}) })
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
location.reload(); location.reload();
} else { } else {
alert(data.message || '删除失败'); alert(data.message || '删除失败');
} }
});
}); });
} }

View File

@ -95,9 +95,9 @@
loginInProgress = false; loginInProgress = false;
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.textContent = '登录'; submitBtn.textContent = '登录';
if (confirm(resp.message + '\n\n确认登录将撤销注销并恢复账号。')) { showConfirm('确认', resp.message + '\n\n确认登录将撤销注销并恢复账号。').then(function(ok) {
doConfirmRestore(email, password); if (ok) doConfirmRestore(email, password);
} });
} else if (xhr.status === 200 && resp && resp.success) { } else if (xhr.status === 200 && resp && resp.success) {
window.MetaLab.toast(toast, '登录成功', false); window.MetaLab.toast(toast, '登录成功', false);
setTimeout(function () { setTimeout(function () {

View File

@ -119,21 +119,23 @@
function removeFromFolder(folderId) { function removeFromFolder(folderId) {
if (loading) return; if (loading) return;
if (!confirm('确定取消收藏吗?')) return; showConfirm('确认', '确定取消收藏吗?').then(function(ok) {
loading = true; if (!ok) return;
api('DELETE', '/api/folders/' + folderId + '/posts/' + postId) loading = true;
.then(function(d) { api('DELETE', '/api/folders/' + folderId + '/posts/' + postId)
loading = false; .then(function(d) {
if (d.success) { loading = false;
currentFolderId = null; if (d.success) {
updateBtn(false); currentFolderId = null;
if (favCountEl) favCountEl.textContent = Math.max(0, (parseInt(favCountEl.textContent) || 0) - 1); updateBtn(false);
hideModal(); if (favCountEl) favCountEl.textContent = Math.max(0, (parseInt(favCountEl.textContent) || 0) - 1);
} else { hideModal();
} else {
alert(d.message || '操作失败'); alert(d.message || '操作失败');
} }
}) })
.catch(function() { loading = false; }); .catch(function() { loading = false; });
});
} }
function createAndAdd(name) { function createAndAdd(name) {

View File

@ -29,20 +29,22 @@
var submitBtn = document.getElementById('submitAuditBtn'); var submitBtn = document.getElementById('submitAuditBtn');
if (submitBtn) { if (submitBtn) {
submitBtn.addEventListener('click', function() { submitBtn.addEventListener('click', function() {
if (!confirm('确认提交审核?审核通过后将公开发布。')) return; showConfirm('确认', '确认提交审核?审核通过后将公开发布。').then(function(ok) {
fetch('/api/posts/' + submitBtn.dataset.id + '/submit', { if (!ok) return;
method: 'POST', fetch('/api/posts/' + submitBtn.dataset.id + '/submit', {
headers: { method: 'POST',
'Content-Type': 'application/json', headers: {
'X-CSRF-Token': getCSRFToken() 'Content-Type': 'application/json',
} 'X-CSRF-Token': getCSRFToken()
}) }
.then(function(r) { return r.json(); }) })
.then(function(d) { .then(function(r) { return r.json(); })
if (d.success) { window.location.reload(); } .then(function(d) {
else { alert(d.message || '提交失败'); } if (d.success) { window.location.reload(); }
}) else { alert(d.message || '提交失败'); }
.catch(function() { alert('请求失败'); }); })
.catch(function() { alert('请求失败'); });
});
}); });
} }
})(); })();

View File

@ -55,9 +55,10 @@
for (var i = 0; i < btns.length; i++) { for (var i = 0; i < btns.length; i++) {
if (btns[i]._bound) continue; if (btns[i]._bound) continue;
btns[i]._bound = true; btns[i]._bound = true;
btns[i].addEventListener('click', function() { btns[i].addEventListener('click', async function() {
var id = this.dataset.id; var id = this.dataset.id;
if (!confirm('确认删除此评论?')) return; var ok = await showConfirm('确认', '确认删除此评论?');
if (!ok) return;
fetch('/api/admin/comments/' + id, { fetch('/api/admin/comments/' + id, {
method: 'DELETE', method: 'DELETE',
headers: { 'X-CSRF-Token': csrfToken } headers: { 'X-CSRF-Token': csrfToken }

View File

@ -37,36 +37,6 @@ const api = {
} }
}; };
// --- 确认弹窗 ---
function showConfirm(title, message) {
return new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay show';
overlay.innerHTML = `
<div class="modal-box">
<h3>${escapeHtml(title)}</h3>
<p>${escapeHtml(message)}</p>
<div class="modal-actions">
<button class="btn btn-outline" id="modalCancel">取消</button>
<button class="btn btn-danger" id="modalConfirm">确认</button>
</div>
</div>`;
document.body.appendChild(overlay);
overlay.querySelector('#modalCancel').onclick = () => {
overlay.remove();
resolve(false);
};
overlay.querySelector('#modalConfirm').onclick = () => {
overlay.remove();
resolve(true);
};
overlay.addEventListener('click', (e) => {
if (e.target === overlay) { overlay.remove(); resolve(false); }
});
});
}
// --- 角色标签 --- // --- 角色标签 ---
// 数据源由服务端 model.RoleDisplayNames 注入为 window.__ROLE_NAMES // 数据源由服务端 model.RoleDisplayNames 注入为 window.__ROLE_NAMES
function roleLabel(role) { function roleLabel(role) {

View File

@ -1,48 +1,53 @@
(function() { (function() {
// CSRF token 由 shared/utils.js 的 getCSRFToken() 统一提供 // CSRF token 由 shared/utils.js 的 getCSRFToken() 统一提供
// showConfirm / showPrompt / escapeHtml 由 shared/utils.js 统一提供
// api 对象 (api.get/api.post/api.put) 由 admin/common.js 统一提供 // api 对象 (api.get/api.post/api.put) 由 admin/common.js 统一提供
function refreshPage() { window.location.reload(); } function refreshPage() { window.location.reload(); }
function handleClick(sel, actionFn) { function handleClick(sel, confirmMsg, actionFn) {
var btns = document.querySelectorAll(sel); var btns = document.querySelectorAll(sel);
btns.forEach(function(btn) { btns.forEach(function(btn) {
btn.addEventListener('click', function() { btn.addEventListener('click', async function() {
var id = btn.getAttribute('data-id'); var id = btn.getAttribute('data-id');
if (confirm('确定执行此操作吗?')) { if (confirmMsg) {
actionFn(id); var ok = await showConfirm('确认', confirmMsg);
if (!ok) return;
} }
actionFn(id);
}); });
}); });
} }
// 审核通过 // 审核通过
handleClick('.post-approve', function(id) { handleClick('.post-approve', '确定审核通过此文章吗?', function(id) {
api.post('/api/admin/posts/' + id + '/approve') api.post('/api/admin/posts/' + id + '/approve')
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); }) .then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
.catch(function() { alert('操作失败'); }); .catch(function() { alert('操作失败'); });
}); });
// 退回 // 退回
handleClick('.post-reject', function(id) { handleClick('.post-reject', null, function(id) {
var reason = prompt('请输入退回理由(必填):'); showPrompt('请输入退回理由(必填):').then(function(reason) {
if (!reason || !reason.trim()) return; if (!reason || !reason.trim()) return;
api.post('/api/admin/posts/' + id + '/reject', { reason: reason.trim() }) api.post('/api/admin/posts/' + id + '/reject', { reason: reason.trim() })
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); }) .then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
.catch(function() { alert('操作失败'); }); .catch(function() { alert('操作失败'); });
});
}); });
// 锁定 // 锁定
handleClick('.post-lock', function(id) { handleClick('.post-lock', null, function(id) {
var reason = prompt('请输入锁定理由(必填):'); showPrompt('请输入锁定理由(必填):').then(function(reason) {
if (!reason || !reason.trim()) return; if (!reason || !reason.trim()) return;
api.post('/api/admin/posts/' + id + '/lock', { reason: reason.trim() }) api.post('/api/admin/posts/' + id + '/lock', { reason: reason.trim() })
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); }) .then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
.catch(function() { alert('操作失败'); }); .catch(function() { alert('操作失败'); });
});
}); });
// 解锁 // 解锁
handleClick('.post-unlock', function(id) { handleClick('.post-unlock', '确定解锁此文章吗?', function(id) {
api.post('/api/admin/posts/' + id + '/unlock') api.post('/api/admin/posts/' + id + '/unlock')
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); }) .then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
.catch(function() { alert('操作失败'); }); .catch(function() { alert('操作失败'); });

View File

@ -91,6 +91,91 @@ function api(method, url, data) {
return fetch(url, opts).then(function (r) { return r.json(); }); return fetch(url, opts).then(function (r) { return r.json(); });
} }
/**
* 确认弹窗 — 替代原生 confirm(),统一全站风格
* @param {string} title — 弹窗标题
* @param {string} message — 提示消息(支持 \n 换行)
* @returns {Promise<boolean>} 用户确认返回 true取消返回 false
*/
function showConfirm(title, message) {
return new Promise(function(resolve) {
var safeTitle = escapeHtml(title);
var safeMsg = escapeHtml(message).replace(/\n/g, '<br>');
var overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.4);z-index:9998;display:flex;align-items:center;justify-content:center;';
overlay.innerHTML = '<div style="background:#fff;border-radius:8px;padding:28px 32px;min-width:320px;max-width:440px;box-shadow:0 8px 30px rgba(0,0,0,0.15);">'
+ '<h3 style="margin:0 0 12px 0;font-size:17px;color:#2d3436;">' + safeTitle + '</h3>'
+ '<p style="margin:0 0 20px 0;font-size:14px;line-height:1.6;color:#636e72;">' + safeMsg + '</p>'
+ '<div style="display:flex;gap:10px;justify-content:flex-end;">'
+ '<button id="mlbModalCancel" style="padding:8px 20px;border:1px solid #d1d5db;border-radius:6px;background:#fff;color:#374151;font-size:14px;cursor:pointer;">取消</button>'
+ '<button id="mlbModalConfirm" style="padding:8px 20px;border:none;border-radius:6px;background:#dc2626;color:#fff;font-size:14px;cursor:pointer;">确认</button>'
+ '</div></div>';
document.body.appendChild(overlay);
function close(val) {
overlay.remove();
resolve(val);
}
overlay.querySelector('#mlbModalCancel').onclick = function() { close(false); };
overlay.querySelector('#mlbModalConfirm').onclick = function() { close(true); };
overlay.addEventListener('click', function(e) {
if (e.target === overlay) close(false);
});
// Enter 键确认
var onKey = function(e) {
if (e.key === 'Enter') { document.removeEventListener('keydown', onKey); close(true); }
if (e.key === 'Escape') { document.removeEventListener('keydown', onKey); close(false); }
};
document.addEventListener('keydown', onKey);
});
}
/**
* 输入弹窗 — 替代原生 prompt(),统一全站风格
* @param {string} message — 提示文本
* @param {string} [defaultValue=''] — 输入框默认值
* @returns {Promise<string|null>} 用户输入的值,取消返回 null
*/
function showPrompt(message, defaultValue) {
return new Promise(function(resolve) {
var safeMsg = escapeHtml(message);
var overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.4);z-index:9998;display:flex;align-items:center;justify-content:center;';
overlay.innerHTML = '<div style="background:#fff;border-radius:8px;padding:28px 32px;min-width:320px;max-width:440px;box-shadow:0 8px 30px rgba(0,0,0,0.15);">'
+ '<p style="margin:0 0 16px 0;font-size:14px;line-height:1.6;color:#2d3436;">' + safeMsg + '</p>'
+ '<input id="mlbPromptInput" type="text" style="width:100%;padding:10px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:14px;color:#2d3436;outline:none;box-sizing:border-box;" value="' + escapeHtml(defaultValue || '') + '" placeholder="">'
+ '<div style="display:flex;gap:10px;justify-content:flex-end;margin-top:16px;">'
+ '<button id="mlbPromptCancel" style="padding:8px 20px;border:1px solid #d1d5db;border-radius:6px;background:#fff;color:#374151;font-size:14px;cursor:pointer;">取消</button>'
+ '<button id="mlbPromptConfirm" style="padding:8px 20px;border:none;border-radius:6px;background:#0984e3;color:#fff;font-size:14px;cursor:pointer;">确认</button>'
+ '</div></div>';
document.body.appendChild(overlay);
var input = overlay.querySelector('#mlbPromptInput');
function close(val) {
overlay.remove();
resolve(val);
}
overlay.querySelector('#mlbPromptCancel').onclick = function() { close(null); };
overlay.querySelector('#mlbPromptConfirm').onclick = function() { close(input.value); };
overlay.addEventListener('click', function(e) {
if (e.target === overlay) close(null);
});
// Enter 确认Escape 取消
var onKey = function(e) {
if (e.key === 'Enter') { document.removeEventListener('keydown', onKey); close(input.value); }
if (e.key === 'Escape') { document.removeEventListener('keydown', onKey); close(null); }
};
document.addEventListener('keydown', onKey);
// 自动聚焦输入框
setTimeout(function() { input.focus(); input.select(); }, 50);
});
}
// 全局头像图片加载失败回退(替代 onerror 内联事件,满足 CSP 无 unsafe-inline // 全局头像图片加载失败回退(替代 onerror 内联事件,满足 CSP 无 unsafe-inline
document.addEventListener('error', function(e) { document.addEventListener('error', function(e) {
var img = e.target; var img = e.target;