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:
@ -783,37 +783,37 @@
|
||||
showSettingsToast('请输入密码确认', true);
|
||||
return;
|
||||
}
|
||||
if (!confirm('确定要注销账号吗?\n\n注销后 7 天内重新登录可撤销注销,期满后将永久删除。')) {
|
||||
return;
|
||||
}
|
||||
deleteAccountBtn.disabled = true;
|
||||
deleteAccountBtn.textContent = '注销中...';
|
||||
showConfirm('注销账号', '确定要注销账号吗?\n\n注销后 7 天内重新登录可撤销注销,期满后将永久删除。').then(function(ok) {
|
||||
if (!ok) return;
|
||||
deleteAccountBtn.disabled = true;
|
||||
deleteAccountBtn.textContent = '注销中...';
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/settings/delete-account', true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) return;
|
||||
deleteAccountBtn.disabled = false;
|
||||
deleteAccountBtn.textContent = '确认注销';
|
||||
try {
|
||||
var res = JSON.parse(xhr.responseText);
|
||||
if (res.success) {
|
||||
showSettingsToast(res.message || '账号已注销');
|
||||
setTimeout(function () {
|
||||
window.location.href = '/';
|
||||
}, 2000);
|
||||
} else {
|
||||
showSettingsToast(res.message || '操作失败', true);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/settings/delete-account', true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) return;
|
||||
deleteAccountBtn.disabled = false;
|
||||
deleteAccountBtn.textContent = '确认注销';
|
||||
try {
|
||||
var res = JSON.parse(xhr.responseText);
|
||||
if (res.success) {
|
||||
showSettingsToast(res.message || '账号已注销');
|
||||
setTimeout(function () {
|
||||
window.location.href = '/';
|
||||
}, 2000);
|
||||
} else {
|
||||
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 () {
|
||||
var sid = btn.getAttribute('data-sid');
|
||||
if (!sid) return;
|
||||
if (!confirm('确定要踢出该设备吗?')) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '踢出中...';
|
||||
showConfirm('确认', '确定要踢出该设备吗?').then(function(ok) {
|
||||
if (!ok) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '踢出中...';
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('DELETE', '/api/settings/sessions/' + encodeURIComponent(sid), true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) return;
|
||||
btn.disabled = false;
|
||||
btn.textContent = '踢出';
|
||||
try {
|
||||
var res = JSON.parse(xhr.responseText);
|
||||
if (res.success) {
|
||||
showSettingsToast(res.message || '已踢出该设备');
|
||||
var item = btn.closest('.session-item');
|
||||
if (item) {
|
||||
item.style.transition = 'opacity .3s ease, transform .3s ease';
|
||||
item.style.opacity = '0';
|
||||
item.style.transform = 'translateX(20px)';
|
||||
setTimeout(function () { item.remove(); }, 300);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('DELETE', '/api/settings/sessions/' + encodeURIComponent(sid), true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) return;
|
||||
btn.disabled = false;
|
||||
btn.textContent = '踢出';
|
||||
try {
|
||||
var res = JSON.parse(xhr.responseText);
|
||||
if (res.success) {
|
||||
showSettingsToast(res.message || '已踢出该设备');
|
||||
var item = btn.closest('.session-item');
|
||||
if (item) {
|
||||
item.style.transition = 'opacity .3s ease, transform .3s ease';
|
||||
item.style.opacity = '0';
|
||||
item.style.transform = 'translateX(20px)';
|
||||
setTimeout(function () { item.remove(); }, 300);
|
||||
}
|
||||
} else {
|
||||
showSettingsToast(res.message || '操作失败', true);
|
||||
}
|
||||
} else {
|
||||
showSettingsToast(res.message || '操作失败', true);
|
||||
} catch (e) {
|
||||
showSettingsToast('操作失败', true);
|
||||
}
|
||||
} catch (e) {
|
||||
showSettingsToast('操作失败', true);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -870,38 +872,40 @@
|
||||
showSettingsToast('没有其他在线设备');
|
||||
return;
|
||||
}
|
||||
if (!confirm('确定要踢出所有其他设备(共 ' + otherCount + ' 台)吗?')) return;
|
||||
kickOthersBtn.disabled = true;
|
||||
kickOthersBtn.textContent = '处理中...';
|
||||
showConfirm('确认', '确定要踢出所有其他设备(共 ' + otherCount + ' 台)吗?').then(function(ok) {
|
||||
if (!ok) return;
|
||||
kickOthersBtn.disabled = true;
|
||||
kickOthersBtn.textContent = '处理中...';
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/settings/sessions/destroy-others', true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) return;
|
||||
kickOthersBtn.disabled = false;
|
||||
kickOthersBtn.textContent = '一键踢出其他设备';
|
||||
try {
|
||||
var res = JSON.parse(xhr.responseText);
|
||||
if (res.success) {
|
||||
showSettingsToast(res.message || '已踢出所有其他设备');
|
||||
var others = document.querySelectorAll('.session-item:not(.session-current)');
|
||||
others.forEach(function (item, i) {
|
||||
setTimeout(function () {
|
||||
item.style.transition = 'opacity .3s ease, transform .3s ease';
|
||||
item.style.opacity = '0';
|
||||
item.style.transform = 'translateX(20px)';
|
||||
setTimeout(function () { item.remove(); }, 300);
|
||||
}, i * 100);
|
||||
});
|
||||
} else {
|
||||
showSettingsToast(res.message || '操作失败', true);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/settings/sessions/destroy-others', true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) return;
|
||||
kickOthersBtn.disabled = false;
|
||||
kickOthersBtn.textContent = '一键踢出其他设备';
|
||||
try {
|
||||
var res = JSON.parse(xhr.responseText);
|
||||
if (res.success) {
|
||||
showSettingsToast(res.message || '已踢出所有其他设备');
|
||||
var others = document.querySelectorAll('.session-item:not(.session-current)');
|
||||
others.forEach(function (item, i) {
|
||||
setTimeout(function () {
|
||||
item.style.transition = 'opacity .3s ease, transform .3s ease';
|
||||
item.style.opacity = '0';
|
||||
item.style.transform = 'translateX(20px)';
|
||||
setTimeout(function () { item.remove(); }, 300);
|
||||
}, i * 100);
|
||||
});
|
||||
} else {
|
||||
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) {
|
||||
btn.addEventListener('click', function () {
|
||||
var id = btn.getAttribute('data-id');
|
||||
if (!confirm('确定要删除此收藏夹?其中的收藏内容也将被清除。')) return;
|
||||
api('DELETE', '/api/folders/' + id)
|
||||
.then(function (r) {
|
||||
if (r.success) {
|
||||
showSettingsToast('收藏夹已删除');
|
||||
loadFolders();
|
||||
} else {
|
||||
showSettingsToast(r.message || '删除失败', true);
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
showSettingsToast('删除失败', true);
|
||||
});
|
||||
showConfirm('确认', '确定要删除此收藏夹?其中的收藏内容也将被清除。').then(function(ok) {
|
||||
if (!ok) return;
|
||||
api('DELETE', '/api/folders/' + id)
|
||||
.then(function (r) {
|
||||
if (r.success) {
|
||||
showSettingsToast('收藏夹已删除');
|
||||
loadFolders();
|
||||
} else {
|
||||
showSettingsToast(r.message || '删除失败', true);
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
showSettingsToast('删除失败', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
@ -1079,24 +1085,25 @@
|
||||
|
||||
if (createBtn) {
|
||||
createBtn.addEventListener('click', function () {
|
||||
var name = prompt('请输入收藏夹名称(最多50个字符):');
|
||||
if (!name || !name.trim()) return;
|
||||
if (name.trim().length > 50) {
|
||||
showSettingsToast('收藏夹名称不能超过 50 个字符', true);
|
||||
return;
|
||||
}
|
||||
api('POST', '/api/folders', { name: name.trim(), description: '', is_public: false })
|
||||
.then(function (r) {
|
||||
if (r.success) {
|
||||
showSettingsToast('收藏夹已创建');
|
||||
loadFolders();
|
||||
} else {
|
||||
showSettingsToast(r.message || '创建失败', true);
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
showSettingsToast('创建失败', true);
|
||||
});
|
||||
showPrompt('请输入收藏夹名称(最多50个字符):').then(function(name) {
|
||||
if (!name || !name.trim()) return;
|
||||
if (name.trim().length > 50) {
|
||||
showSettingsToast('收藏夹名称不能超过 50 个字符', true);
|
||||
return;
|
||||
}
|
||||
api('POST', '/api/folders', { name: name.trim(), description: '', is_public: false })
|
||||
.then(function (r) {
|
||||
if (r.success) {
|
||||
showSettingsToast('收藏夹已创建');
|
||||
loadFolders();
|
||||
} else {
|
||||
showSettingsToast(r.message || '创建失败', true);
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
showSettingsToast('创建失败', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -59,42 +59,44 @@
|
||||
|
||||
<script nonce="{{.CSPNonce}}">
|
||||
function submitPost(id) {
|
||||
if (!confirm('确定要提交这篇草稿进行审核吗?')) return;
|
||||
|
||||
fetch('/api/studio/posts/' + id + '/submit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': getCSRFToken()
|
||||
}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(data.message || '提交失败');
|
||||
}
|
||||
showConfirm('确认', '确定要提交这篇草稿进行审核吗?').then(function(ok) {
|
||||
if (!ok) return;
|
||||
fetch('/api/studio/posts/' + id + '/submit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': getCSRFToken()
|
||||
}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(data.message || '提交失败');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deletePost(id) {
|
||||
if (!confirm('确定要删除这篇草稿吗?此操作不可恢复。')) return;
|
||||
|
||||
fetch('/api/studio/posts/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': getCSRFToken()
|
||||
}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(data.message || '删除失败');
|
||||
}
|
||||
showConfirm('确认', '确定要删除这篇草稿吗?此操作不可恢复。').then(function(ok) {
|
||||
if (!ok) return;
|
||||
fetch('/api/studio/posts/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': getCSRFToken()
|
||||
}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(data.message || '删除失败');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -80,22 +80,23 @@
|
||||
|
||||
<script nonce="{{.CSPNonce}}">
|
||||
function deletePost(id) {
|
||||
if (!confirm('确定要删除这篇文章吗?')) return;
|
||||
|
||||
fetch('/api/studio/posts/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': getCSRFToken()
|
||||
}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(data.message || '删除失败');
|
||||
}
|
||||
showConfirm('确认', '确定要删除这篇文章吗?').then(function(ok) {
|
||||
if (!ok) return;
|
||||
fetch('/api/studio/posts/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': getCSRFToken()
|
||||
}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(data.message || '删除失败');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -95,9 +95,9 @@
|
||||
loginInProgress = false;
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '登录';
|
||||
if (confirm(resp.message + '\n\n确认登录将撤销注销并恢复账号。')) {
|
||||
doConfirmRestore(email, password);
|
||||
}
|
||||
showConfirm('确认', resp.message + '\n\n确认登录将撤销注销并恢复账号。').then(function(ok) {
|
||||
if (ok) doConfirmRestore(email, password);
|
||||
});
|
||||
} else if (xhr.status === 200 && resp && resp.success) {
|
||||
window.MetaLab.toast(toast, '登录成功', false);
|
||||
setTimeout(function () {
|
||||
|
||||
@ -119,21 +119,23 @@
|
||||
|
||||
function removeFromFolder(folderId) {
|
||||
if (loading) return;
|
||||
if (!confirm('确定取消收藏吗?')) return;
|
||||
loading = true;
|
||||
api('DELETE', '/api/folders/' + folderId + '/posts/' + postId)
|
||||
.then(function(d) {
|
||||
loading = false;
|
||||
if (d.success) {
|
||||
currentFolderId = null;
|
||||
updateBtn(false);
|
||||
if (favCountEl) favCountEl.textContent = Math.max(0, (parseInt(favCountEl.textContent) || 0) - 1);
|
||||
hideModal();
|
||||
} else {
|
||||
showConfirm('确认', '确定取消收藏吗?').then(function(ok) {
|
||||
if (!ok) return;
|
||||
loading = true;
|
||||
api('DELETE', '/api/folders/' + folderId + '/posts/' + postId)
|
||||
.then(function(d) {
|
||||
loading = false;
|
||||
if (d.success) {
|
||||
currentFolderId = null;
|
||||
updateBtn(false);
|
||||
if (favCountEl) favCountEl.textContent = Math.max(0, (parseInt(favCountEl.textContent) || 0) - 1);
|
||||
hideModal();
|
||||
} else {
|
||||
alert(d.message || '操作失败');
|
||||
}
|
||||
})
|
||||
.catch(function() { loading = false; });
|
||||
});
|
||||
}
|
||||
|
||||
function createAndAdd(name) {
|
||||
|
||||
@ -29,20 +29,22 @@
|
||||
var submitBtn = document.getElementById('submitAuditBtn');
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', function() {
|
||||
if (!confirm('确认提交审核?审核通过后将公开发布。')) return;
|
||||
fetch('/api/posts/' + submitBtn.dataset.id + '/submit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': getCSRFToken()
|
||||
}
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.success) { window.location.reload(); }
|
||||
else { alert(d.message || '提交失败'); }
|
||||
})
|
||||
.catch(function() { alert('请求失败'); });
|
||||
showConfirm('确认', '确认提交审核?审核通过后将公开发布。').then(function(ok) {
|
||||
if (!ok) return;
|
||||
fetch('/api/posts/' + submitBtn.dataset.id + '/submit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': getCSRFToken()
|
||||
}
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.success) { window.location.reload(); }
|
||||
else { alert(d.message || '提交失败'); }
|
||||
})
|
||||
.catch(function() { alert('请求失败'); });
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
@ -55,9 +55,10 @@
|
||||
for (var i = 0; i < btns.length; i++) {
|
||||
if (btns[i]._bound) continue;
|
||||
btns[i]._bound = true;
|
||||
btns[i].addEventListener('click', function() {
|
||||
btns[i].addEventListener('click', async function() {
|
||||
var id = this.dataset.id;
|
||||
if (!confirm('确认删除此评论?')) return;
|
||||
var ok = await showConfirm('确认', '确认删除此评论?');
|
||||
if (!ok) return;
|
||||
fetch('/api/admin/comments/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: { 'X-CSRF-Token': csrfToken }
|
||||
|
||||
@ -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
|
||||
function roleLabel(role) {
|
||||
|
||||
@ -1,48 +1,53 @@
|
||||
(function() {
|
||||
// CSRF token 由 shared/utils.js 的 getCSRFToken() 统一提供
|
||||
// showConfirm / showPrompt / escapeHtml 由 shared/utils.js 统一提供
|
||||
// api 对象 (api.get/api.post/api.put) 由 admin/common.js 统一提供
|
||||
|
||||
function refreshPage() { window.location.reload(); }
|
||||
|
||||
function handleClick(sel, actionFn) {
|
||||
function handleClick(sel, confirmMsg, actionFn) {
|
||||
var btns = document.querySelectorAll(sel);
|
||||
btns.forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
btn.addEventListener('click', async function() {
|
||||
var id = btn.getAttribute('data-id');
|
||||
if (confirm('确定执行此操作吗?')) {
|
||||
actionFn(id);
|
||||
if (confirmMsg) {
|
||||
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')
|
||||
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
|
||||
.catch(function() { alert('操作失败'); });
|
||||
});
|
||||
|
||||
// 退回
|
||||
handleClick('.post-reject', function(id) {
|
||||
var reason = prompt('请输入退回理由(必填):');
|
||||
if (!reason || !reason.trim()) return;
|
||||
api.post('/api/admin/posts/' + id + '/reject', { reason: reason.trim() })
|
||||
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
|
||||
.catch(function() { alert('操作失败'); });
|
||||
handleClick('.post-reject', null, function(id) {
|
||||
showPrompt('请输入退回理由(必填):').then(function(reason) {
|
||||
if (!reason || !reason.trim()) return;
|
||||
api.post('/api/admin/posts/' + id + '/reject', { reason: reason.trim() })
|
||||
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
|
||||
.catch(function() { alert('操作失败'); });
|
||||
});
|
||||
});
|
||||
|
||||
// 锁定
|
||||
handleClick('.post-lock', function(id) {
|
||||
var reason = prompt('请输入锁定理由(必填):');
|
||||
if (!reason || !reason.trim()) return;
|
||||
api.post('/api/admin/posts/' + id + '/lock', { reason: reason.trim() })
|
||||
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
|
||||
.catch(function() { alert('操作失败'); });
|
||||
handleClick('.post-lock', null, function(id) {
|
||||
showPrompt('请输入锁定理由(必填):').then(function(reason) {
|
||||
if (!reason || !reason.trim()) return;
|
||||
api.post('/api/admin/posts/' + id + '/lock', { reason: reason.trim() })
|
||||
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
|
||||
.catch(function() { alert('操作失败'); });
|
||||
});
|
||||
});
|
||||
|
||||
// 解锁
|
||||
handleClick('.post-unlock', function(id) {
|
||||
handleClick('.post-unlock', '确定解锁此文章吗?', function(id) {
|
||||
api.post('/api/admin/posts/' + id + '/unlock')
|
||||
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
|
||||
.catch(function() { alert('操作失败'); });
|
||||
|
||||
@ -91,6 +91,91 @@ function api(method, url, data) {
|
||||
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)
|
||||
document.addEventListener('error', function(e) {
|
||||
var img = e.target;
|
||||
|
||||
Reference in New Issue
Block a user