fix: 修复 utils.js 中 showConfirm/showPrompt/showToast 的 CSP style-src 违规

- showToast: style.cssText 替换为逐个 style.property 赋值
- showConfirm: 移除 style.cssText 和 innerHTML 中的 style 属性,改用 createElement + setStyles 辅助函数 + addEventListener
- showPrompt: 同上,移除所有 inline style 和 onclick
- 新增 setStyles 辅助函数,批量设置单个 style 属性以满足 CSP 要求
This commit is contained in:
2026-06-02 21:45:00 +08:00
parent fa3b20d1bc
commit dd6efd0c50

View File

@ -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, '<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>';
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 = '<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);
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);
});