refactor: 提取公共工具函数到 shared/utils.js,消除 DRY 重复

- escapeHtml 8 处定义合并为 shared/utils.js 1 处:删除 shortcode.js、posts/show.html、settings/index.html、comments.js、energy.js、common.js 中的重复定义
- formatTime 6 处定义合并为 1 处:统一 ISO 时间格式化逻辑
- api() 5 处定义收敛:前台页面使用 shared/utils.js 统一 api(method,url,data),admin 使用 common.js 的 api 对象
- showToast 3 处定义合并为 1 处:自包含内联样式,前后台通用
- admin/posts.js api 调用迁移为 api.post()
This commit is contained in:
2026-06-02 19:50:52 +08:00
parent 746deca754
commit a8a52f5fd0
8 changed files with 96 additions and 164 deletions

View File

@ -11,30 +11,14 @@
var pagination = document.getElementById('commentPagination');
var loading = document.getElementById('commentLoading');
function api(url, opts) {
opts = opts || {};
return fetch(url, opts).then(function(r) { return r.json(); });
}
function escapeHtml(text) {
if (!text) return '';
var d = document.createElement('div');
d.textContent = text;
return d.innerHTML;
}
function formatTime(iso) {
var d = new Date(iso);
var pad = function(n) { return n < 10 ? '0' + n : n; };
return d.getFullYear() + '-' + pad(d.getMonth()+1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
}
// escapeHtml / formatTime / api 由 shared/utils.js 统一提供
function loadComments(page) {
loading.style.display = 'block';
var url = '/api/admin/comments?page=' + page + '&page_size=20';
if (currentKeyword) url += '&keyword=' + encodeURIComponent(currentKeyword);
api(url).then(function(d) {
api('GET', url).then(function(d) {
loading.style.display = 'none';
if (!d.success) return;
totalPages = d.data.total_pages || 1;

View File

@ -4,8 +4,8 @@
不包含任何页面特有逻辑
===================================================== */
// --- AJAX 封装 ---
// 注意getCSRFToken() 由共享 utils.js 提供
// --- AJAX 封装 (管理后台专用对象式 API) ---
// 注意:escapeHtml / formatTime / showToast / getCSRFToken 由 shared/utils.js 统一提供
const api = {
async get(url) {
const res = await fetch(url, {
@ -37,27 +37,6 @@ const api = {
}
};
// --- Toast 通知 ---
function showToast(message, type) {
const container = document.querySelector('.toast-container') || createToastContainer();
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.3s';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
function createToastContainer() {
const el = document.createElement('div');
el.className = 'toast-container';
document.body.appendChild(el);
return el;
}
// --- 确认弹窗 ---
function showConfirm(title, message) {
return new Promise((resolve) => {
@ -88,23 +67,6 @@ function showConfirm(title, message) {
});
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// --- 时间格式化 ---
function formatTime(isoStr) {
if (!isoStr) return '—';
const d = new Date(isoStr);
return d.getFullYear() + '-' +
String(d.getMonth() + 1).padStart(2, '0') + '-' +
String(d.getDate()).padStart(2, '0') + ' ' +
String(d.getHours()).padStart(2, '0') + ':' +
String(d.getMinutes()).padStart(2, '0');
}
// --- 角色标签 ---
// 数据源由服务端 model.RoleDisplayNames 注入为 window.__ROLE_NAMES
function roleLabel(role) {

View File

@ -185,17 +185,7 @@
}
}
function formatTime(isoStr) {
if (!isoStr) return '';
var d = new Date(isoStr);
var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
}
function escapeHtml(text) {
if (!text) return '';
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
}
// escapeHtml / formatTime 由 shared/utils.js 统一提供
function showMsg(msgEl, text, type) {
if (!msgEl) return;

View File

@ -1,16 +1,6 @@
(function() {
// CSRF token 由 shared/utils.js 的 getCSRFToken() 统一提供
function api(url, method, body) {
return fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCSRFToken()
},
body: body ? JSON.stringify(body) : undefined
}).then(function(r) { return r.json(); });
}
// api 对象 (api.get/api.post/api.put) 由 admin/common.js 统一提供
function refreshPage() { window.location.reload(); }
@ -28,7 +18,7 @@
// 审核通过
handleClick('.post-approve', function(id) {
api('/api/admin/posts/' + id + '/approve', 'POST')
api.post('/api/admin/posts/' + id + '/approve')
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
.catch(function() { alert('操作失败'); });
});
@ -37,7 +27,7 @@
handleClick('.post-reject', function(id) {
var reason = prompt('请输入退回理由(必填):');
if (!reason || !reason.trim()) return;
api('/api/admin/posts/' + id + '/reject', 'POST', { reason: reason.trim() })
api.post('/api/admin/posts/' + id + '/reject', { reason: reason.trim() })
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
.catch(function() { alert('操作失败'); });
});
@ -46,14 +36,14 @@
handleClick('.post-lock', function(id) {
var reason = prompt('请输入锁定理由(必填):');
if (!reason || !reason.trim()) return;
api('/api/admin/posts/' + id + '/lock', 'POST', { reason: reason.trim() })
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) {
api('/api/admin/posts/' + id + '/unlock', 'POST')
api.post('/api/admin/posts/' + id + '/unlock')
.then(function(d) { if (d.success) refreshPage(); else alert(d.message); })
.catch(function() { alert('操作失败'); });
});