diff --git a/templates/shared/static/js/utils.js b/templates/shared/static/js/utils.js
index e5e3296..b6a03f2 100644
--- a/templates/shared/static/js/utils.js
+++ b/templates/shared/static/js/utils.js
@@ -57,13 +57,25 @@ function showToast(message, type) {
if (!container) {
container = document.createElement('div');
container.className = 'toast-container';
- container.style.cssText = 'position:fixed;top:20px;right:20px;z-index:9999;display:flex;flex-direction:column;gap:8px;pointer-events:none';
+ container.style.position = 'fixed';
+ container.style.top = '20px';
+ container.style.right = '20px';
+ container.style.zIndex = '9999';
+ container.style.display = 'flex';
+ container.style.flexDirection = 'column';
+ container.style.gap = '8px';
+ container.style.pointerEvents = 'none';
document.body.appendChild(container);
}
var toast = document.createElement('div');
toast.className = 'toast toast-' + type;
toast.textContent = message;
- toast.style.cssText = 'background:' + bg + ';color:#fff;padding:10px 24px;border-radius:6px;font-size:14px;pointer-events:auto';
+ toast.style.background = bg;
+ toast.style.color = '#fff';
+ toast.style.padding = '10px 24px';
+ toast.style.borderRadius = '6px';
+ toast.style.fontSize = '14px';
+ toast.style.pointerEvents = 'auto';
container.appendChild(toast);
setTimeout(function () {
toast.style.opacity = '0';
@@ -91,6 +103,15 @@ function api(method, url, data) {
return fetch(url, opts).then(function (r) { return r.json(); });
}
+/**
+ * 批量设置元素 style 属性(满足 CSP style-src 限制,避免 cssText 被拦截)
+ * @param {HTMLElement} el — 目标元素
+ * @param {object} styles — 键值对 { property: value }
+ */
+function setStyles(el, styles) {
+ Object.keys(styles).forEach(function(k) { el.style[k] = styles[k]; });
+}
+
/**
* 确认弹窗 — 替代原生 confirm(),统一全站风格
* @param {string} title — 弹窗标题
@@ -102,15 +123,59 @@ function showConfirm(title, message) {
var safeTitle = escapeHtml(title);
var safeMsg = escapeHtml(message).replace(/\n/g, '
');
+ // 遮罩层
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 = '
'
- + '
' + safeTitle + '
'
- + '
' + safeMsg + '
'
- + '
'
- + ''
- + ''
- + '
';
+ setStyles(overlay, {
+ position: 'fixed', inset: '0', background: 'rgba(0,0,0,0.4)',
+ zIndex: '9998', display: 'flex', alignItems: 'center', justifyContent: 'center'
+ });
+
+ // 弹窗容器
+ var box = document.createElement('div');
+ setStyles(box, {
+ background: '#fff', borderRadius: '8px', padding: '28px 32px',
+ minWidth: '320px', maxWidth: '440px',
+ boxShadow: '0 8px 30px rgba(0,0,0,0.15)'
+ });
+
+ // 标题
+ var h3 = document.createElement('h3');
+ setStyles(h3, { margin: '0 0 12px 0', fontSize: '17px', color: '#2d3436' });
+ h3.textContent = safeTitle;
+ box.appendChild(h3);
+
+ // 消息
+ var p = document.createElement('p');
+ setStyles(p, { margin: '0 0 20px 0', fontSize: '14px', lineHeight: '1.6', color: '#636e72' });
+ p.innerHTML = safeMsg; // 已转义,安全
+ box.appendChild(p);
+
+ // 按钮容器
+ var btnRow = document.createElement('div');
+ setStyles(btnRow, { display: 'flex', gap: '10px', justifyContent: 'flex-end' });
+
+ // 取消按钮
+ var btnCancel = document.createElement('button');
+ setStyles(btnCancel, {
+ padding: '8px 20px', border: '1px solid #d1d5db', borderRadius: '6px',
+ background: '#fff', color: '#374151', fontSize: '14px', cursor: 'pointer'
+ });
+ btnCancel.textContent = '取消';
+ btnCancel.addEventListener('click', function() { close(false); });
+ btnRow.appendChild(btnCancel);
+
+ // 确认按钮
+ var btnConfirm = document.createElement('button');
+ setStyles(btnConfirm, {
+ padding: '8px 20px', border: 'none', borderRadius: '6px',
+ background: '#dc2626', color: '#fff', fontSize: '14px', cursor: 'pointer'
+ });
+ btnConfirm.textContent = '确认';
+ btnConfirm.addEventListener('click', function() { close(true); });
+ btnRow.appendChild(btnConfirm);
+
+ box.appendChild(btnRow);
+ overlay.appendChild(box);
document.body.appendChild(overlay);
function close(val) {
@@ -118,8 +183,6 @@ function showConfirm(title, message) {
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);
});
@@ -142,26 +205,72 @@ 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 = ''
- + '
' + safeMsg + '
'
- + '
'
- + '
'
- + ''
- + ''
- + '
';
- document.body.appendChild(overlay);
+ setStyles(overlay, {
+ position: 'fixed', inset: '0', background: 'rgba(0,0,0,0.4)',
+ zIndex: '9998', display: 'flex', alignItems: 'center', justifyContent: 'center'
+ });
- var input = overlay.querySelector('#mlbPromptInput');
+ // 弹窗容器
+ var box = document.createElement('div');
+ setStyles(box, {
+ background: '#fff', borderRadius: '8px', padding: '28px 32px',
+ minWidth: '320px', maxWidth: '440px',
+ boxShadow: '0 8px 30px rgba(0,0,0,0.15)'
+ });
+
+ // 消息
+ var p = document.createElement('p');
+ setStyles(p, { margin: '0 0 16px 0', fontSize: '14px', lineHeight: '1.6', color: '#2d3436' });
+ p.innerHTML = safeMsg;
+ box.appendChild(p);
+
+ // 输入框
+ var input = document.createElement('input');
+ input.type = 'text';
+ input.id = 'mlbPromptInput';
+ setStyles(input, {
+ width: '100%', padding: '10px 12px', border: '1px solid #d1d5db',
+ borderRadius: '6px', fontSize: '14px', color: '#2d3436',
+ outline: 'none', boxSizing: 'border-box'
+ });
+ input.value = escapeHtml(defaultValue || '');
+ box.appendChild(input);
+
+ // 按钮容器
+ var btnRow = document.createElement('div');
+ setStyles(btnRow, { display: 'flex', gap: '10px', justifyContent: 'flex-end', marginTop: '16px' });
+
+ // 取消按钮
+ var btnCancel = document.createElement('button');
+ setStyles(btnCancel, {
+ padding: '8px 20px', border: '1px solid #d1d5db', borderRadius: '6px',
+ background: '#fff', color: '#374151', fontSize: '14px', cursor: 'pointer'
+ });
+ btnCancel.textContent = '取消';
+ btnCancel.addEventListener('click', function() { close(null); });
+ btnRow.appendChild(btnCancel);
+
+ // 确认按钮
+ var btnConfirm = document.createElement('button');
+ setStyles(btnConfirm, {
+ padding: '8px 20px', border: 'none', borderRadius: '6px',
+ background: '#0984e3', color: '#fff', fontSize: '14px', cursor: 'pointer'
+ });
+ btnConfirm.textContent = '确认';
+ btnConfirm.addEventListener('click', function() { close(input.value); });
+ btnRow.appendChild(btnConfirm);
+
+ box.appendChild(btnRow);
+ overlay.appendChild(box);
+ document.body.appendChild(overlay);
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);
});